在上下文中使用mgo

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
}