英文:
Mongo client set in main function, functions in other modules receive nil value
问题
我有一个使用mux和mongo-driver的RESTful API。根据教程,我尝试在主包中设置服务器和mongo客户端,代码如下:
package main
import (
"context"
"fmt"
"net/http"
"time"
"github.com/gorilla/mux"
c "github.com/moonlightfight/elo-backend/config"
"github.com/moonlightfight/elo-backend/routes/admin"
"github.com/moonlightfight/elo-backend/routes/tournament"
"github.com/spf13/viper"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
var client *mongo.Client
func main() {
// 设置配置文件的文件名
viper.SetConfigName("config")
// 设置配置文件的路径
viper.AddConfigPath(".")
// 允许VIPER读取环境变量
viper.AutomaticEnv()
viper.SetConfigType("yml")
var configuration c.Configurations
if err := viper.ReadInConfig(); err != nil {
fmt.Printf("读取配置文件错误,%s", err)
}
err := viper.Unmarshal(&configuration)
if err != nil {
fmt.Printf("无法解码为结构体,%v", err)
}
ctx, _ := context.WithTimeout(context.Background(), 10*time.Second)
clientOptions := options.Client().ApplyURI(fmt.Sprintf("mongodb+srv://%s:%s@cluster0.ucnph.mongodb.net/%s?retryWrites=true&w=majority", configuration.Database.DBUser, configuration.Database.DBPass, configuration.Database.DBName))
port := fmt.Sprintf(":%d", configuration.Server.Port)
mongo.Connect(ctx, clientOptions)
router := mux.NewRouter()
router.HandleFunc("/api/admin", admin.CreateAdminEndpoint).Methods("POST")
router.HandleFunc("/api/admin/login", admin.AdminLoginEndpoint).Methods("POST")
router.HandleFunc("/api/tournament/getfromweb", tournament.GetTournamentData).Methods("GET")
fmt.Printf("服务器监听在 http://localhost%v", port)
http.ListenAndServe(port, router)
}
为了更简洁地管理我的代码,我设置了模块(如主要导入部分所示),用于处理mux将用于端点的函数。
在一个特定的情况下(处理"/api/admin"端点):
package admin
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"time"
"github.com/dgrijalva/jwt-go"
c "github.com/moonlightfight/elo-backend/config"
m "github.com/moonlightfight/elo-backend/models"
"github.com/spf13/viper"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
"golang.org/x/crypto/bcrypt"
)
var client *mongo.Client
// 其他代码
func CreateAdminEndpoint(response http.ResponseWriter, request *http.Request) {
response.Header().Set("content-type", "application/json")
var admin m.Admin
err := json.NewDecoder(request.Body).Decode(&admin)
if err != nil {
log.Println(err)
}
// 加密用户密码
admin.Password = HashPassword(admin.Password)
fmt.Println(client)
collection := client.Database("test").Collection("Admin")
ctx, ctxErr := context.WithTimeout(context.Background(), 5*time.Second)
if ctxErr != nil {
log.Println(ctxErr)
}
result, resErr := collection.InsertOne(ctx, admin)
if resErr != nil {
log.Println(resErr)
}
json.NewEncoder(response).Encode(result)
}
运行时,我收到以下错误:
2021/06/05 02:02:39 http: panic serving [::1]:53359: runtime error: invalid memory address or nil pointer dereference
这指向了在端点函数中定义collection的那一行,它被记录为具有nil值。显然,我没有正确地在模块中定义mongo客户端,并且不确定在多个模块之间保持此客户端连接的最佳实践。
英文:
I have a restful API utilizing mux and mongo-driver. Following a tutorial, I attempted to setup the server and mongo client like so in the main package:
package main
import (
"context"
"fmt"
"net/http"
"time"
"github.com/gorilla/mux"
c "github.com/moonlightfight/elo-backend/config"
"github.com/moonlightfight/elo-backend/routes/admin"
"github.com/moonlightfight/elo-backend/routes/tournament"
"github.com/spf13/viper"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
var client *mongo.Client
func main() {
// Set the file name of the configurations file
viper.SetConfigName("config")
// Set the path to look for the configurations file
viper.AddConfigPath(".")
// Enable VIPER to read Environment Variables
viper.AutomaticEnv()
viper.SetConfigType("yml")
var configuration c.Configurations
if err := viper.ReadInConfig(); err != nil {
fmt.Printf("Error reading config file, %s", err)
}
err := viper.Unmarshal(&configuration)
if err != nil {
fmt.Printf("Unable to decode into struct, %v", err)
}
ctx, _ := context.WithTimeout(context.Background(), 10*time.Second)
clientOptions := options.Client().ApplyURI(fmt.Sprintf("mongodb+srv://%s:%s@cluster0.ucnph.mongodb.net/%s?retryWrites=true&w=majority", configuration.Database.DBUser, configuration.Database.DBPass, configuration.Database.DBName))
port := fmt.Sprintf(":%d", configuration.Server.Port)
mongo.Connect(ctx, clientOptions)
router := mux.NewRouter()
router.HandleFunc("/api/admin", admin.CreateAdminEndpoint).Methods("POST")
router.HandleFunc("/api/admin/login", admin.AdminLoginEndpoint).Methods("POST")
router.HandleFunc("/api/tournament/getfromweb", tournament.GetTournamentData).Methods("GET")
fmt.Printf("server listening on http://localhost%v", port)
http.ListenAndServe(port, router)
}
Now, in order to manage my code more concisely, I set up modules (as you can see in the imports on main) to handle the functions that mux will use for the endpoints.
On one specific case (handling the "/api/admin" endpoint:
package admin
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"time"
"github.com/dgrijalva/jwt-go"
c "github.com/moonlightfight/elo-backend/config"
m "github.com/moonlightfight/elo-backend/models"
"github.com/spf13/viper"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
"golang.org/x/crypto/bcrypt"
)
var client *mongo.Client
// other code here
func CreateAdminEndpoint(response http.ResponseWriter, request *http.Request) {
response.Header().Set("content-type", "application/json")
var admin m.Admin
err := json.NewDecoder(request.Body).Decode(&admin)
if err != nil {
log.Println(err)
}
// encrypt user password
admin.Password = HashPassword(admin.Password)
fmt.Println(client)
collection := client.Database("test").Collection("Admin")
ctx, ctxErr := context.WithTimeout(context.Background(), 5*time.Second)
if ctxErr != nil {
log.Println(ctxErr)
}
result, resErr := collection.InsertOne(ctx, admin)
if resErr != nil {
log.Println(resErr)
}
json.NewEncoder(response).Encode(result)
}
And when ran, I receive the following error:
2021/06/05 02:02:39 http: panic serving [::1]:53359: runtime error: invalid memory address or nil pointer dereference
This points to the line where I define collection in the endpoint function, which logged as having a nil value. I am clearly not getting the mongo client properly defined in the module, and not sure of the best practice for maintaining this client connection across multiple modules.
答案1
得分: 0
避免使用全局变量的标准方法是定义一个表示服务器的struct
,其方法将作为处理程序。然后,这些方法共享struct
的数据,你可以将诸如mongo客户端之类的内容放在其中。
在你的admin
包中,可以这样写:
type Server struct {
client *mongo.Client
}
func NewServer(client *mongo.Client) *Server {
return &Server{client: client}
}
func (srv *Server) CreateAdminEndpoint(response http.ResponseWriter, request *http.Request) {
// ...
// 在这里使用srv.client
//
}
现在在main
函数中,你可以这样创建服务器:
client, err := mongo.Connect(...)
// 处理错误!
srv := admin.NewServer(client)
router := mux.NewRouter()
router.HandleFunc("/api/admin", srv.CreateAdminEndpoint).Methods("POST")
希望对你有所帮助!
英文:
The standard way of doing this while avoiding globals would be to define a struct
that represents your server, and its methods would be the handlers. Then the methods share the struct's data, and you place things like your mongo client in there.
Something like this (in your admin
package):
type Server struct {
client *mongo.Client
}
func NewServer(client *mongo.Client) *Server {
return &Server{client: client}
}
func (srv *Server) CreateAdminEndpoint(response http.ResponseWriter, request *http.Request) {
// ...
// use srv.client here
//
}
And now in main
, you create the server like this:
client, err := mongo.Connect(...)
// handle err!
srv := admin.NewServer(client)
router := mux.NewRouter()
router.HandleFunc("/api/admin", srv.CreateAdminEndpoint).Methods("POST")
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论