Using mgo with context

*爱你&永不变心* 提交于 2020-01-07 04:40:21

问题


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.


回答1:


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
}


来源:https://stackoverflow.com/questions/43921168/using-mgo-with-context

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!