Google Analytics resumable upload failing - google-analytics-api

I'm attempting to do a resumable upload as described here.
When I perform the upload, I receive a response status of 400 Bad Request, and a response body of:
{
"error": {
"errors": [
{
"domain": "global",
"reason": "badContent",
"message": "Unsupported content with type: application/octet-stream"
}
],
"code": 400,
"message": "Unsupported content with type: application/octet-stream"
}
}
The script I'm using to perform the upload is in Go, here:
package main
import(
"bytes"
"fmt"
"io"
"io/ioutil"
"mime/multipart"
"net/http"
"os"
"path/filepath"
)
func main(){
// config
accessToken := "a valid token"
acctId := "a valid account id"
webPropertyId := "a valid web property id"
customDataSourceId := "a valid custom data source id"
path := "/a/path/to/a/validly/formatted/file.csv"
params := map[string]string{
"title": "My Document",
"author": "Becca Petrin",
"description": "Riveting stuff",
}
paramName := "file"
url := fmt.Sprintf("https://www.googleapis.com/upload/analytics/v3/management/accounts/%s/webproperties/%s/customDataSources/%s/uploads?uploadType=resumable", acctId, webPropertyId, customDataSourceId)
// create the body
file, err := os.Open(path)
if err != nil {
fmt.Println("Err opening file:", err.Error())
return
}
defer file.Close()
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
part, err := writer.CreateFormFile(paramName, filepath.Base(path))
if err != nil {
fmt.Println("Err creating form file:", err.Error())
return
}
_, err = io.Copy(part, file)
if err != nil {
fmt.Println("Err copying:", err.Error())
return
}
for k, v := range params {
_ = writer.WriteField(k, v)
}
if err := writer.Close(); err != nil {
fmt.Println("Err closing writer:", err.Error())
return
}
req, err := http.NewRequest("POST", url, body)
if err != nil {
fmt.Printf("Err creating request:", err.Error())
return
}
// add authorization
req.Header.Set("Authorization", "Bearer "+accessToken)
// add headers
// no multipart headers work, and "application/octet-stream"" doesn't work
// uncommenting and using "text/plain" results in a 200 without the expected response body
//req.Header.Add("Content-Type", "text/plain")
// execute request
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Err doing request:", err.Error())
return
}
fmt.Println("Response status:", resp.Status)
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println("Err reading resp body:", err.Error())
return
}
fmt.Printf("Response body: %s", b)
}
As noted in the comments, I receive the unsupported content type response if I don't include a Content-Type header. I also receive it if I use any multipart content types, or application/octet-stream. If I use text/plain, I receive a 200, but I don't receive the expected body.
What am I doing wrong? Thanks in advance!

I decided to take an alternate approach to this by trying through the Google Go client library, and I got it working with this snippet:
package main
import (
"errors"
"fmt"
"os"
"time"
google "google.golang.org/api/analytics/v3"
"golang.org/x/oauth2"
)
var (
accessToken = "a valid token"
acctId = "a valid account ID"
webPropertyId = "a valid web property ID"
customDataSourceId = "a valid custom data source ID"
filePath = "/path/to/file.csv"
)
func main(){
ctx1 := NewCallContext()
tokenSource := TokenSource{}
httpClient := oauth2.NewClient(ctx1, &tokenSource)
service, err := google.New(httpClient)
if err != nil {
fmt.Println("Err making client:", err.Error())
return
}
r, err := NewFileReader(filePath)
if err != nil {
fmt.Println("Err making reader:", err.Error())
return
}
ctx2 := NewCallContext()
call := service.Management.Uploads.UploadData(acctId, webPropertyId, customDataSourceId)
call = call.ResumableMedia(ctx2, r, 10, "application/octet-stream")
upload, err := call.Do()
if err != nil {
fmt.Println("Err doing call: %v", err)
return
}
fmt.Printf("%s", upload)
}
// http://golang.org/pkg/io/#ReaderAt
func NewFileReader(pathToFile string) (*FileReader, error) {
f, err := os.Open(pathToFile)
if err != nil {
return nil, err
}
return &FileReader{f}, nil
}
type FileReader struct {
f *os.File
}
func (s *FileReader) ReadAt(p []byte, off int64) (int, error) {
return s.f.ReadAt(p, off)
}
// https://godoc.org/golang.org/x/net/context#Context
func NewCallContext() *CallContext {
c := make(<-chan struct{})
return &CallContext{c}
}
type CallContext struct {
doneChan <-chan struct{}
}
func (s *CallContext) Deadline() (time.Time, bool) {
return time.Now().Add(time.Duration(10) * time.Second), true
}
func (s *CallContext) Done() <-chan struct{} {
return s.doneChan
}
func (s *CallContext) Err() error {
select {
case <- s.doneChan:
return errors.New("Done")
default:
}
return nil
}
func (s *CallContext) Value(key interface{}) interface{} {
return nil
}
// satisfies the oauth2 tokensource interface
type TokenSource struct {}
func (t *TokenSource) Token() (*oauth2.Token, error) {
return &oauth2.Token{AccessToken:accessToken}, nil
}

Related

(Golang) InvalidBSON when using custom MarshalBSON()

I am currently migrating some of my Python Services to GoLang, where I encountered some problems with the integration with the existing database.
When it comes to saving, the formatting of the date string is different, and I am not planning to change the format of my database entries.
So I decided to implement a time type, with custom (Un)MarshalBSON() methods, to keep pythons time formatting.
However when saving it, I get the following error:
2022/09/10 20:57:29 (InvalidBSON) Unrecognized BSON type 45 in element with field name 'created_at.09-10 20:57:27.798545' in object with _id: ObjectId('631cde192c2aad6a49bc52af')
I don't know how the field created_at.09-10 20:57:27.798545 came up.
Thank you for advance four your advice, here is my source code:
main.go:
package main
import (
"context"
"example/customdate/user"
"fmt"
"log"
)
func main() {
ctx := context.Background()
repo := user.NewRepository(ctx)
u1 := user.NewUser("Max")
u1, err := repo.Add(ctx, u1)
if err != nil {
log.Fatalln(err)
}
fmt.Println(u1)
}
user/repository.go:
package user
import (
"context"
"log"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
type Repository interface {
Get(ctx context.Context) ([]User, error)
Add(ctx context.Context, user *User) (*User, error)
}
type repository struct {
client *mongo.Client
}
func NewRepository(ctx context.Context) repository {
c, err := mongo.NewClient(options.Client().ApplyURI("mongodb://127.0.0.1:27017/"))
if err != nil {
log.Fatalln(err)
}
err = c.Connect(ctx)
if err != nil {
log.Fatal(err)
}
return repository{
client: c,
}
}
func (r *repository) Get(ctx context.Context) ([]User, error) {
result, err := r.client.Database("db").Collection("user").Find(ctx, map[string]interface{}{})
if err != nil {
return nil, err
}
var users []User
if err = result.All(ctx, &users); err != nil {
return nil, err
}
return users, nil
}
func (r *repository) Add(ctx context.Context, user *User) (*User, error) {
result, err := r.client.Database("db").Collection("user").InsertOne(ctx, user)
if err != nil {
return nil, err
}
user.ID = result.InsertedID
return user, nil
}
user/dto.go:
package user
import "time"
type CustomTime struct {
time.Time
}
func (t *CustomTime) MarshalBSON() ([]byte, error) {
str := t.Format("2006-01-02 15:04:05.999999")
return []byte(str), nil
}
func (t *CustomTime) UnmarshalBSON(raw []byte) error {
parsed, err := time.Parse("2006-01-02 15:04:05.999999", string(raw))
if err != nil {
return err
}
t = &CustomTime{parsed}
return nil
}
func Now() CustomTime {
return CustomTime{time.Now()}
}
type User struct {
ID interface{}
Name string `bson:"name"`
CreatedAt CustomTime `bson:"created_at"`
}
func NewUser(name string) *User {
return &User{
Name: name,
CreatedAt: Now(),
}
}

Setting up standard Go net/smtp with Office 365 fails with "Error tls: first record does not look like a TLS handshake"

I'm trying to create a simple Go emailing service using the default Go packages net/smtp - I know there's gomailer, but i'd like to use the standard library
I need help with configuring the tls/server setting to work with Office365
I believe that I have the correct host:
smtp.office365.com:587
From copying the documentation for smtp that Microsoft provide, however, I get the following error in my console when running the below code:
Error: tls: first record does not look like a TLS handshake
panic: runtime error: invalid memory address or nil pointer dereference
package main
import (
"fmt"
"net"
mail "net/mail"
smtp "net/smtp"
)
func main() {
from := mail.Address{"", "example#example.com"}
to := mail.Address{"", "example#example.com"}
subject := "My test subject"
body := "Test email body"
// Setup email headers
headers := make(map[string]string)
headers["From"] = from.String()
headers["To"] = to.String()
headers["Subject"] = subject
message := ""
for k, v := range headers {
message += fmt.Sprintf("%s: %s\r\n", k, v)
}
message += "\r\n" + body
servername := "smtp.office365.com:587"
host, _, _ := net.SplitHostPort(servername)
auth := smtp.PlainAuth("", "example#example.com", "password", host)
tlsconfig := &tls.Config{
InsecureSkipVerify: true,
ServerName: host,
}
conn, err := tls.Dial("tcp", "smtp.office365.com:587", tlsconfig)
if err != nil {
fmt.Println("tls.Dial Error: %s", err)
}
c, err := smtp.NewClient(conn, host)
if err != nil {
fmt.Println("smtp.NewClient Error: %s", err)
}
if err = c.Auth(auth); err != nil {
fmt.Println("c.Auth Error: %s", err)
}
if err = c.Mail(from.Address); err != nil {
fmt.Println("c.Mail Error: %s", err)
}
if err = c.Rcpt(to.Address); err != nil {
fmt.Println("c.Rcpt Error: %s", err)
}
w, err := c.Data()
if err != nil {
fmt.Println("c.Data Error: %s", err)
}
_, err = w.Write([]byte(message))
if err != nil {
fmt.Println("Error: %s", err)
}
err = w.Close()
if err != nil {
fmt.Println("reader Error: %s", err)
}
c.Quit()
}
Any examples of an O365 client will be appreciated, or anything that anyone can spot that seems suspect will be great
Thanks
Outlook.com no longer supports AUTH PLAIN authentication since August 2017.
https://support.microsoft.com/en-us/office/outlook-com-no-longer-supports-auth-plain-authentication-07f7d5e9-1697-465f-84d2-4513d4ff0145?ui=en-us&rs=en-us&ad=us
Use AUTH LOGIN
The following codes implement AUTH LOGIN
type loginAuth struct {
username, password string
}
func LoginAuth(username, password string) smtp.Auth {
return &loginAuth{username, password}
}
func (a *loginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
return "LOGIN", []byte(a.username), nil
}
func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
if more {
switch string(fromServer) {
case "Username:":
return []byte(a.username), nil
case "Password:":
return []byte(a.password), nil
default:
return nil, errors.New("Unknown from server")
}
}
return nil, nil
}
remove "InsecureSkipVerify: true"
tlsconfig := &tls.Config {
ServerName: host,
}
don't use tsl.Dial(), use net.Dial()
conn, err := net.Dial("tcp", "smtp.office365.com:587")
if err != nil {
return err
}
call StartTLS() after smtp.NewClient()
c, err := smtp.NewClient(conn, host)
if err != nil {
return err
}
if err = c.StartTLS(tlsconfig); err != nil {
return err
}
use AUTH LOGIN
auth := LoginAuth(fromAddress, password)
if err = c.Auth(auth); err != nil {
return err
}
The error message Error: tls: first record does not look like a TLS handshake is telling you what the problem is :-). If you try connecting to the server, you will see that (as any SMTP servers) it uses plain text:
telnet smtp.office365.com 587
Trying 2603:1026:c0b:10::2...
Connected to zrh-efz.ms-acdc.office.com.
Escape character is '^]'.
220 ZRAP278CA0003.outlook.office365.com Microsoft ESMTP MAIL Service ready at Mon, 11 Nov 2019 17:13:50 +0000
...
You need to use the STARTTLS command, see https://en.wikipedia.org/wiki/Opportunistic_TLS (and the RFCs pointed by that wiki page).
In Go, it is https://golang.org/pkg/net/smtp/#Client.StartTLS.
In your code I noticed
tlsconfig := &tls.Config{
InsecureSkipVerify: true, <== REMOVE THIS
ServerName: host,
}
Please remove the InsecureSkipVerify, it is, as the name implies, insecure and has nothing to do with the error you are facing.
Below worked fine with me:
package main
import (
"bytes"
"crypto/tls"
"errors"
"fmt"
"net"
"net/smtp"
"text/template"
)
type loginAuth struct {
username, password string
}
func LoginAuth(username, password string) smtp.Auth {
return &loginAuth{username, password}
}
func (a *loginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
return "LOGIN", []byte(a.username), nil
}
func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
if more {
switch string(fromServer) {
case "Username:":
return []byte(a.username), nil
case "Password:":
return []byte(a.password), nil
default:
return nil, errors.New("Unknown from server")
}
}
return nil, nil
}
func main() {
// Sender data.
from := "O365 logging name"
password := "O365 logging pasword"
// Receiver email address.
to := []string{
"receiver email",
}
// smtp server configuration.
smtpHost := "smtp.office365.com"
smtpPort := "587"
conn, err := net.Dial("tcp", "smtp.office365.com:587")
if err != nil {
println(err)
}
c, err := smtp.NewClient(conn, smtpHost)
if err != nil {
println(err)
}
tlsconfig := &tls.Config{
ServerName: smtpHost,
}
if err = c.StartTLS(tlsconfig); err != nil {
println(err)
}
auth := LoginAuth(from, password)
if err = c.Auth(auth); err != nil {
println(err)
}
t, _ := template.ParseFiles("template.html")
var body bytes.Buffer
mimeHeaders := "MIME-version: 1.0;\nContent-Type: text/html; charset=\"UTF-8\";\n\n"
body.Write([]byte(fmt.Sprintf("Subject: This is a test subject \n%s\n\n", mimeHeaders)))
t.Execute(&body, struct {
Name string
Message string
}{
Name: "Hasan Yousef",
Message: "This is a test message in a HTML template",
})
// Sending email.
err = smtp.SendMail(smtpHost+":"+smtpPort, auth, from, to, body.Bytes())
if err != nil {
fmt.Println(err)
return
}
fmt.Println("Email Sent!")
}
With the below template as bonus :)
<!-- template.html -->
<!DOCTYPE html>
<html>
<body>
<h3>Name:</h3><span>{{.Name}}</span><br/><br/>
<h3>Email:</h3><span>{{.Message}}</span><br/>
</body>
</html>
So the issue was all about authorisation. Firstly requiring that I use the StartTLS method on the client, and also that I write a function and methods to support LOGIN, something that the standard Go library doesn't support (for whatever reason)
See the functions and struct above the main()
Here's the full code, with the helper function, that can now successfully send an email through my O365 account:
package main
import (
"fmt"
"net"
"errors"
mail "net/mail"
smtp "net/smtp"
)
type loginAuth struct {
username, password string
}
func LoginAuth(username, password string) smtp.Auth {
return &loginAuth{username, password}
}
func (a *loginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
return "LOGIN", []byte{}, nil
}
func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
if more {
switch string(fromServer) {
case "Username:":
return []byte(a.username), nil
case "Password:":
return []byte(a.password), nil
default:
return nil, errors.New("Unknown fromServer")
}
}
return nil, nil
}
func main() {
from := mail.Address{"", "example#example.com"}
to := mail.Address{"", "example#example.com"}
subject := "My test subject"
body := "Test email body"
headers := make(map[string]string)
headers["From"] = from.String()
headers["To"] = to.String()
headers["Subject"] = subject
message := ""
for k, v := range headers {
message += fmt.Sprintf("%s: %s\r\n", k, v)
}
message += "\r\n" + body
tlsconfig := &tls.Config{
ServerName: host,
}
conn, err := tls.Dial("tcp", "smtp.office365.com:587", tlsconfig)
if err != nil {
fmt.Println("tls.Dial Error: ", err)
}
c, err := smtp.NewClient(conn, host)
if err != nil {
fmt.Println("smtp.NewClient Error: ", err)
}
if err = c.Auth(LoginAuth("example#example.com", "password")); err != nil {
fmt.Println("c.Auth Error: ", err)
return
}
if err = c.Mail(from.Address); err != nil {
fmt.Println("c.Mail Error: ", err)
}
if err = c.Rcpt(to.Address); err != nil {
fmt.Println("c.Rcpt Error: ", err)
}
w, err := c.Data()
if err != nil {
fmt.Println("c.Data Error: ", err)
}
_, err = w.Write([]byte(message))
if err != nil {
fmt.Println("Error: ", err)
}
err = w.Close()
if err != nil {
fmt.Println("reader Error: ", err)
}
c.Quit()
}

Post Request with PostgreSQL and json-api returns an empty body

After a POST request, i was expecting to have a last inserted record marshalled into json, but instead returns an empty body. What am i not doing well?
package models
import (
"encoding/json"
"errors"
"flag"
"fmt"
"log"
"net/http"
"strconv"
"github.com/go-chi/chi"
"github.com/google/jsonapi"
"github.com/thedevsaddam/renderer"
"github.com/xo/dburl"
)
var rnd = renderer.New()
var flagVerbose = flag.Bool("v", false, "verbose")
var FlagURL = flag.String("url", "postgres://postgres:#127.0.0.1/sweb", "url")
// Page represents a row from 'public.pages'.
type Page struct {
Tag string `jsonapi:"attr,tag"` // tag
Body string `jsonapi:"attr,body"` // body
Slug string `jsonapi:"attr,slug"` // slug
Title string `jsonapi:"attr,title"` // title
ID int `jsonapi:"primary,pages"` // id
Link string `jsonapi:"attr,link"` // link
// xo fields
_exists, _deleted bool
}
func (page Page) JSONAPILinks() *jsonapi.Links {
return &jsonapi.Links{
"self": fmt.Sprintf("https://%d", page.ID),
}
}
I believe this is the culprit. After inserting a record, it should return the last inserted record as specified.
func (p *Page) PInsert(db XODB) (*Page, error) {
var err error
// if already exist, bail
if p._exists {
return p, errors.New("insert failed: already exists")
}
// sql insert query, primary key provided by sequence
const sqlstr = `INSERT INTO public.pages (` +
`tag, body, slug, title` +
`) VALUES (` +
`$1, $2, $3, $4` +
`) RETURNING id, tag, body, title`
// run query
XOLog(sqlstr, p.Tag, p.Body, p.Slug, p.Title)
err = db.QueryRow(sqlstr, p.Tag, p.Body, p.Slug, p.Title).Scan(&p.ID, &p.Tag, &p.Body, &p.Title)
if err != nil {
return p, err
}
// set existence
p._exists = true
return p, nil
}
Update updates the Page in the database and return last inserted records.
The same should apply for the Update function
func (p *Page) Update(db XODB) (*Page, error) {
var err error
// if doesn't exist, bail
if !p._exists {
return p, errors.New("update failed: does not exist")
}
// if deleted, bail
if p._deleted {
return p, errors.New("update failed: marked for deletion")
}
// sql query
const sqlstr = `UPDATE public.pages SET (` +
`tag, body, slug, title` +
`) = ( ` +
`$1, $2, $3, $4` +
`) WHERE id = $5`
// run query
XOLog(sqlstr, p.Tag, p.Body, p.Slug, p.Title, p.ID)
_, err = db.Exec(sqlstr, p.Tag, p.Body, p.Slug, p.Title, p.ID)
return p, err
}
func (p *Page) PSave(db XODB) (*Page, error) {
if p.Exists() {
return p.Update(db)
}
return p.PInsert(db)
}
func NewPage(w http.ResponseWriter, r *http.Request) {
db, err := dburl.Open(*FlagURL)
defer db.Close()
if err != nil {
log.Fatal(err)
}
var page Page
//page := new(Page)
if err := jsonapi.UnmarshalPayload(r.Body, &page); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
p, err := page.PSave(db)
if err != nil {
fmt.Println(err)
if err := jsonapi.MarshalPayload(w, p); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
fmt.Println(err)
}
}
w.Header().Set("Content-Type", jsonapi.MediaType)
w.WriteHeader(http.StatusCreated)
}
This is the last function i believe the issue is happening from. the last inserted record supposed to be marshalled into json.
Your last section of code contains a number of mistakes. The relevant section (without the useless and obfuscating Printlns) is:
p, err := page.PSave(db)
if err != nil {
if err := jsonapi.MarshalPayload(w, p); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
w.Header().Set("Content-Type", jsonapi.MediaType)
w.WriteHeader(http.StatusCreated)
And the primary mistake is that json.MarshalPayload is only called when err != nil. In other words, you only serialize the page if you failed to save it.
The secondary mistake is that jsonapi.MarshalPayload will call Write on the http.ResponseWriter. This turns all subsequent calls to Header().Set and WriteHeader into no-ops.
More correct code would look like this.
// 1. Save the page in the database, bail on error
p, err := page.PSave(db)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// 2. Marshal the page into an intermediate buffer, bail on error
var buf bytes.Buffer
if err := jsonapi.MarshalPayload(&buf, p); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// 3. Write the entire response; failures to write the intermediate buffer
// cannot be communicated over HTTP
w.Header().Set("Content-Type", jsonapi.MediaType)
w.WriteHeader(http.StatusCreated)
if _, err := buf.WriteTo(w); err != nil {
log.Printf("failed to write response: %v", err)
return
}

Golang Client for Bitfinex API V2.0: How to Post Authenticated Requests

I'm trying to use the Bitfinex REST API V2.0 and here there is a description of how a request should be authenticated. Here below is my golang implementation:
func (c *Client) newAuthenticatedRequest(method string, refURL string, data map[string]interface{}) (*http.Request, error) {
path := "auth/r/" + refURL
rel, err := url.Parse(path)
if err != nil {
return nil, err
}
payload := "{}"
if data != nil {
p, err := json.Marshal(data)
if err != nil {
return nil, err
}
payload = string(p)
}
url := c.BaseURL.ResolveReference(rel)
req, err := http.NewRequest(method, url.String(), strings.NewReader(payload))
if err != nil {
return nil, err
}
nonce := utils.GetNonce()
req.Header.Add("Content-Type", "application/json")
req.Header.Add("Accept", "application/json")
req.Header.Add("bfx-nonce", nonce)
req.Header.Add("bfx-apikey", c.APIKey)
req.Header.Add("bfx-signature", c.signPayload("/api/v2/" + path + nonce + payload))
return req, nil
}
func (c *Client) signPayload(payload string) string {
sig := hmac.New(sha512.New384, []byte(c.APISecret))
sig.Write([]byte(payload))
return hex.EncodeToString(sig.Sum(nil))
}
func (c *Client) do(req *http.Request, v interface{}) (*Response, error) {
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
response, err := newResponse(resp)
if err != nil {
return nil, err
}
if v != nil {
err = json.Unmarshal(response.Body, v)
if err != nil {
return nil, err
}
}
return response, nil
}
func (s *WalletsService) All() ([]Wallet, error) {
req, err := s.client.newAuthenticatedRequest("POST", "wallets", nil)
if err != nil {
return nil, err
}
var w []Wallet
_, err = s.client.do(req, &w)
if err != nil {
return nil, err
}
return w, nil
}
The problem is that when i POST a request, I always get back error [10100] apikey: invalid. Is it correct to set the payload to {} when empty? I've tried any possible combination but no way.

Using mgo with context

I have been using mgo for my API but I'm seeing many current connections in my MongoDB (while using less than 5 devices for testing). By executing db.serverStatus().connections in my Mongo server I get: { "current" : 641, "available" : 838219, "totalCreated" : 1136 }. Below I transcript my issue in mgo's Github (Issue #429):
Is my way of using mgo in a web server the correct way? If not, can you give me a full example?
This code is not functional, take it as almost pseudo code (because of the missing parts like the imports or where the configs come from and models), but it is exactly how I am using mgo.
I must clarify that I'm building an API which is used by several mobile devices and webapps.
main.go
func main() {
mongoDBDialInfo := &mgo.DialInfo{
Addrs: []string{config.DatabaseURL},
Timeout: 60 * time.Second,
Database: config.DatabaseName,
Username: config.DatabaseUsername,
Password: config.DatabasePassword,
}
db, err := mgo.DialWithInfo(mongoDBDialInfo)
if err != nil {
log.Fatal("Cannot Dial Mongo: ", err)
}
defer db.Close()
db.SetMode(mgo.Monotonic, true)
phoneIndex := mgo.Index{
Key: []string{"pp"},
Unique: true,
DropDups: true,
Background: true,
Sparse: true,
}
err = db.DB(config.DatabaseName).C("users").EnsureIndex(phoneIndex)
if err != nil {
panic(err)
}
router := mux.NewRouter()
router.HandleFunc("/login", publicWithDB(login, db)).Methods("POST")
if err := http.ListenAndServe(":5000", router); err != nil {
log.Fatal(err)
}
}
func publicWithDB(fn http.HandlerFunc, db *mgo.Session) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
dbsession := db.Copy()
defer dbsession.Close()
fn(w, r.WithContext(context.WithValue(r.Context(), contextKeyDatabase, dbsession)))
}
}
func login(w http.ResponseWriter, r *http.Request) {
r.ParseForm() // Parses the request body
device := r.Form.Get("device")
var deviceid bson.ObjectId
if bson.IsObjectIdHex(device) {
deviceid = bson.ObjectIdHex(device)
}
db := r.Context().Value(contextKeyDatabase).(*mgo.Session)
var device models.Device
err := db.DB(config.DatabaseName).C("devices").FindId(deviceid).One(&device)
w.WriteHeader(200)
w.Write([]byte(utils.ResponseToString(models.Response{Status: 200, Message: "asdasd", Data: device})))
}
I'm posting this because I couldn't find a complete implementation.
Here's an example of how I have seen myself and others structure web apps in Go. This code is untested and is purely for example. It's missing imports and potentially has errors.
EDIT Added a middleware example.
main.go
package main
func main() {
mongoDBDialInfo := &mgo.DialInfo{
Addrs: []string{config.DatabaseURL},
Timeout: 60 * time.Second,
Database: config.DatabaseName,
Username: config.DatabaseUsername,
Password: config.DatabasePassword,
}
db, err := mgo.DialWithInfo(mongoDBDialInfo)
if err != nil {
log.Fatal("Cannot Dial Mongo: ", err)
}
defer db.Close()
db.SetMode(mgo.Monotonic, true)
phoneIndex := mgo.Index{
Key: []string{"pp"},
Unique: true,
DropDups: true,
Background: true,
Sparse: true,
}
err = db.DB(config.DatabaseName).C("users").EnsureIndex(phoneIndex)
if err != nil {
panic(err)
}
mgoAdapter := mongo.NewAdapter(db, config.DatabaseName)
deviceStore := mongo.NewDeviceStore(mgoAdapter)
userStore := mongo.NewUserStore(mgoAdapter)
loginController := controllers.NewLoginController(deviceStore)
router := mux.NewRouter()
router.HandleFunc("/login", middleware.AuthorizeUser(userStore)(http.HandlerFunc(loginController.Login)).Methods("POST")
if err := http.ListenAndServe(":5000", router); err != nil {
log.Fatal(err)
}
}
controllers/login.go
package controllers
type LoginController struct {
store DeviceStore
}
func NewLoginController(store stores.DeviceStore) *LoginController {
return &LoginController{
store: store,
}
}
func (c *LoginController) Login(w http.ResponseWriter, r *http.Request) {
r.ParseForm() // Parses the request body
device := r.Form.Get("device")
data, err := c.store.FindByDevice(device)
var respose models.Response
if err != nil {
w.WriteHeader(500)
response = models.Response{Status: 500, Message: fmt.Sprintf("error: %s", err)}
} else if data == nil {
w.WriteHeader(404)
response = models.Response{Status: 404, Message: "device not found"}
} else {
response = models.Response{Status: 200, Message: "device found", Data: data}
}
// Write sets header to 200 if it hasn't been set already
w.Write([]byte(utils.ResponseToString(response)))
}
stores/stores.go
package stores
type DeviceStore interface {
FindByDevice(device string) (*models.Device, error)
}
type UserStore interface {
FindByToken(token string) (*models.User, error)
}
middleware/auth.go
package middleware
func AuthorizeUser(store stores.UserStore) func(h *http.Handler) http.Handler {
return func(h *http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Logic for authorizing user
// Could also store user in the request context
})
}
}
mongo/mongo.go
package mongo
type Adapter struct {
session *mgo.Session
databaseName string
}
func NewAdapter(session *mgo.Session, dbName string) *Adapter {
return &Adapter{session: session, databaseName: dbName}
}
type deviceStore struct {
*Adapter
}
func NewDeviceStore(adapter *Adapter) stores.DeviceStore {
return &deviceStore{adapter}
}
const devices = "devices"
func (s *deviceStore) FindByDevice(d string) (*models.Device, err) {
sess := s.session.copy()
defer sess.close()
var deviceID bson.ObjectId
if bson.IsObjectIdHex(d) {
deviceID = bson.ObjectIdHex(d)
}
var device models.Device
err := db.DB(s.databaseName).C(devices).FindId(deviceID).One(&device)
if err == mgo.ErrNotFound {
return nil, nil
}
return &device, err
}
type userStore struct {
*Adapter
}
const users = "users"
func NewUserStore(adapter *Adapter) stores.UserStore {
return &userStore{adapter}
}
func (s *userStore) GetUserByToken(token string) (*models.User, error) {
sess := s.session.copy()
defer sess.close()
var user models.User
err := db.DB(s.databaseName).C(users).Find(bson.M{"token": token}).One(&user)
if err == mgo.ErrNotFound {
return nil, nil
}
return &user, err
}