介绍
在这一部分中,我们将使用Go
中最流行的Web
框架之一Gin
重新实现我们的REST
服务器。
选择Web框架
Go
现在有几个流行的Web
框架,它们都有其优点。我们的目标不是对这些框架进行冗长的比较和讨论,而是研究使用框架的代码与不使用框架的代码相比如何。
选择Gin
是因为它是最受欢迎的项目之一(从GitHub的star数来看),而且它看起来很小且易于上手和使用。Gin
的文档还有很多不足之处,但该框架非常直观,很容易上手。
Gin
的优点在于它不会强迫使用任何特定风格的应用程序开发(例如MVC
)。使用Gin
几乎感觉就像在没有框架的情况下编写代码,只是可以获得很多工具和好东西来用更少的代码实现目标。
Gin
的路由
我们再main
函数使用新的Gin
并注册路由:
func main() {
router := gin.Default()
server := NewPageServer()
router.POST("/page/", server.createPageHandler)
router.GET("/page/", server.getAllPagesHandler)
router.DELETE("/page/", server.deleteAllPagesHandler)
router.PUT("/page/", server.updatePageHandler)
router.GET("/page/{id:[0-9]+}/", server.getPageHandler)
router.DELETE("/page/{id:[0-9]+}/", server.deletePageHandler)
router.GET("/tag/", server.tagHandler)
router.GET("/due/", server.dueHandler)
log.Fatal(http.ListenAndServe("localhost:8880", router))
}
复制代码
对gin.Default()
的调用返回一个默认引擎,这是Gin
的主要类型,充当路由并提供其他功能。具体来说,Default
仅注册用于崩溃恢复和日志记录的基本中间件,稍后将详细介绍中间件。
路由注册现在应该看起来很熟悉,它与gorilla/mux
版本略有相似,但略有不同:
- 不是选择
HTTP
方法作为路由并附加Go
方法调用,而是将其编码在注册名称中;例如,router.POST
而不是router.HandleFunc(...).Methods("POST")
。 - 虽然
Gorilla
在路由中支持正则表达式匹配,但Gin
不支持。
Handlers
让我们来看看一些带有Gin
的处理程序,从最简单的开始,这里是getAllPagesHandler
:
func (p *PageServer) getAllPagesHandler(c *gin.Context) {
allTasks := p.store.GetAllPages()
c.JSON(http.StatusOK, allTasks)
}
复制代码
这里有一些有趣的事情需要注意:
Gin
的处理程序没有标准的Go HTTP
处理程序签名;相反,他们只需要一个gin.Context
,它可以用来分析请求并构造响应。Gin
确实可以通过gin.WrapF
和gin.WrapH
辅助函数与标准处理程序进行交互。- 与我们服务器的早期版本相比,不需要手动记录每个请求,因为
Gin
的默认记录中间件已经这样做了(例如终端颜色和报告每个请求的处理时间)。 - 我们也不必再实现
renderJSON
函数,因为Gin
有自己的Context.JSON
来将JSON
渲染为响应。
现在让我们检查一个稍微复杂的有参数的处理程序:
func (p *PageServer) getPageHandler(c *gin.Context) {
id, err := strconv.Atoi(c.Params.ByName("id"))
if err != nil {
c.String(http.StatusBadRequest, err.Error())
return
}
task, err := p.store.GetPage(id)
if err != nil {
c.String(http.StatusNotFound, err.Error())
return
}
c.JSON(http.StatusOK, task)
}
复制代码
这里要注意的部分是参数处理,Gin
通过Context.Params
提供对路由参数(以冒号开头的路由部分,如:id
)的访问。
然而,与Gorilla
不同的是,Gin
在其路由中不支持正则表达式(这可能是出于性能考虑,因为Gin
以快速路由而为称)。因此,我们必须处理id
参数的整数解析。
绑定
我们要详细研究的最后一个处理程序是createTaskHandler
;它处理一个携带重要数据的请求:
type PageRequest struct {
Text string `json:"text"`
Tags []string `json:"tags"`
Due time.Time `json:"due"`
}
func (p *PageServer) createPageHandler(c *gin.Context) {
var ret PageRequest
if err := c.ShouldBindJSON(&ret); err != nil {
c.String(http.StatusBadRequest, err.Error())
}
id := p.store.CreatePage(ret.Text, ret.Tags, ret.Due)
c.JSON(http.StatusOK, gin.H{"Id": id})
}
复制代码
Gin
具有用于将请求绑定到Go
数据的重要基础功能。在这种情况下绑定意味着解析请求的内容(可以是JSON
、YAML
或其他格式),验证它们并将它们的值分配给Go
结构体。在这里,我们在没有任何验证的情况下为PageRequest
使用了一种非常基本的绑定形式,但值得一试Gin
提供的更高级的选项。
createPageHandler
的Gin
版本比我们的早期版本短很多,因为ShouldBindJSON
正在执行从请求中解析JSON
的工作。
需要注意的另一件事是,我们现在不需要响应ID
的一次性结构。相反,我们使用gin.H
,它只是map[string]interface{}
的别名,非常有效地以最少的类型和语法构建响应。
Gin
附加功能
在这个例子中,我们只使用了Gin
为Web
应用程序开发人员提供的一小部分功能。Gin
带有许多预先打包的附加功能,例如常用的中间件、身份验证和用于呈现HTML
模板的功能。如果没有框架,这些都很难实现,但使用Gin
肯定会使其更快,代码更少,至少对于简单的情况是这样。
限制
Web
框架便利性的另一面是使用它们时可能会遇到的限制和风格不匹配,在我们的简单示例中,我们已经遇到了一个限制:Gin
路由中缺乏正则表达式支持,这意味着复杂的路由匹配需要更多代码来解析和验证。
任何包和工具都可能有局限性,但框架因其非常普遍而使局限性变得更加重要。我们会在 Gorilla/mux
中发现一个限制,这将成为我们应用程序的障碍。然后我们可以用另一个路由包替换它。虽然过渡无疑会产生一些成本,但其影响将是局部的,因为只有路由配置受到影响。
代码
本节源码见Github