自从go语言r59版本(一个1.0之前的版本)以来,我一直在写Go程序,并且在过去七年里一直在Go中构建HTTP API和服务.
多年来,我编写服务的方式发生了变化,所以我想分享今天如何编写服务 - 以防模式对您和您的工作有用.
1. Server Struct
server
type server struct {
db * someDatabase
router * someRouter
email EmailSender
}
共享依赖项是结构的字段
2. routes.go
routes.go
package app
func(s * server)routes(){
s.router.HandleFunc("/ api/",s.handleAPI())
s.router.HandleFunc("/ about",s.handleAbout())
s.router .HandleFunc("/",s.handleIndex())
}
URL错误报告routes.go
3. server 挂载 handler
HTTPhandler
func(s * server)handleSomething()http.HandlerFunc {...}
4. return Handler
我的处理函数实际上并不处理Request,它们返回一个handler函数.
闭包环境
func(s * server)handleSomething()http.HandlerFunc {
thing:= prepareThing()
return func(w http.ResponseWriter,r * http.Request){
// use thing
}
}
prepareThing
确保只读取共享数据,如果处理程序正在修改任何内容,请记住您需要一个互斥锁或其他东西来保护它.
5. 参数是handler函数的依赖
如果特定处理程序具有依赖项,请将其作为参数.
func(s * server)handleGreeting(format string)http.HandlerFunc {
return func(w http.ResponseWriter,r * http.Request){
fmt.Fprintf(w,format,"World")
}
}
format处理程序可以访问该变量.
Handler
http.HandlerFunchttp.Handler
func(s * server)handleSomething()http.HandlerFunc {
return func(w http.ResponseWriter,r * http.Request){
...
}
}
它们或多或少是可以互换的,所以只需选择更容易阅读的内容.对我来说,就是这样http.HandlerFunc.
5. Middleware中间件
/
func(s * server)adminOnly(h http.HandlerFunc)http.HandlerFunc {
return func(w http.ResponseWriter,r * http.Request){
if!currentUser(r).IsAdmin {
http.NotFound(w,r)
return
}
h(w,r)
}
}
IsAdminfalseHTTP 404 Not Foundaborth
IsAdmintrue
routes.go
package app
func(s * server)routes(){
s.router.HandleFunc("/ api
/",s.handleAPI())s.router.HandleFunc("/ about",s.handleAbout())
s.router .HandleFunc("/",s.handleIndex())
s.router.HandleFunc("/ admin",s.adminOnly( s.handleAdminIndex()))
}
7. Request 和 Response类
请求响应
如果是这种情况,您可以在函数内定义它们.
func(s * server)handleSomething()http.HandlerFunc {
type request struct {
Name string
}
type response struct {
Greeting string`json :"greeting"`
}
return func(w http.ResponseWriter,r * http.Request){
. ..
}
}
包空间类型命名为相同
在测试代码中,您只需将类型复制到测试函数中并执行相同的操作即可.要么…
8. 测试框架
请求/响应
这是一个为需要了解您的代码的后代做一些故事讲述的机会.
/greet
func TestGreet(t * testing.T){
is:= is.New(t)
p:= struct {
Name string`json :"name"`
} {
Name:"Mat Ryer",
}
var buf bytes.Buffer
err: = json.NewEncoder(&buf).Encode(p)
is.NoErr(err)// json.NewEncoder
req,err:= http.NewRequest(http.MethodPost,"/ greet",&buf)
is.NoErr(err)
/ / ...这里有更多测试代码
从这个测试中可以清楚地看出,我们关心的唯一领域就是Name人.
9. sync.Once 配置依赖项
如果我在准备处理程序时必须做任何昂贵的事情,我会推迟到第一次调用该处理程序时.
这改善了应用程序启动时间
func(s * server)handleTemplate(files string ...)http.HandlerFunc {
var(
init sync.Once
tpl * template.Template
err error
)
return func(w http.ResponseWriter,r * http.Request){
init.Do (func(){
tpl,err = template.ParseFiles(files ...)
})
if err!= nil {
http.Error(w,err.Error(),http.StatusInternalServerError)
return
}
// use tpl
}
}
10. sync.Once 确保代码只执行一次
其他调用(其他人发出相同的请求)将一直阻塞,直到完成.
错误检查在init函数之外,所以如果出现问题我们仍然会出现错误,并且不会在日志中丢失错误
如果未调用处理程序,则永远不会完成昂贵的工作 - 根据代码的部署方式,这可能会带来很大的好处
请记住,执行此操作时,您将初始化时间从启动时移至运行时(首次访问端点时).我经常使用Google App Engine,所以这对我来说很有意义,但是你的情况可能会有所不同,所以值得思考何时何地使用sync.Once这样.
11. 服务器是可测试的
我们的服务器类型非常可测试.
func TestHandleAbout(t * testing.T){
is:= is.New(t)
srv:= server {
db:mockDatabase,
email:mockEmailSender,
}
srv.routes()
req,err:= http.NewRequest("GET" ,"/ about",nil)
is.NoErr(错误)
w:= httptest.NewRecorder()
srv.ServeHTTP(w,req)
is.Equal(w.StatusCode,http.StatusOK)
}
ServeHTTPhttptest.NewRecorderTestify