Go打造REST Server【三】:用Web框架来实现

介绍

在这一部分中,我们将使用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.WrapFgin.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数据的重要基础功能。在这种情况下绑定意味着解析请求的内容(可以是JSONYAML或其他格式),验证它们并将它们的值分配给Go结构体。在这里,我们在没有任何验证的情况下为PageRequest使用了一种非常基本的绑定形式,但值得一试Gin提供的更高级的选项。

createPageHandlerGin版本比我们的早期版本短很多,因为ShouldBindJSON正在执行从请求中解析JSON的工作。

需要注意的另一件事是,我们现在不需要响应ID的一次性结构。相反,我们使用gin.H,它只是map[string]interface{}的别名,非常有效地以最少的类型和语法构建响应。

Gin附加功能

在这个例子中,我们只使用了GinWeb应用程序开发人员提供的一小部分功能。Gin带有许多预先打包的附加功能,例如常用的中间件、身份验证和用于呈现HTML模板的功能。如果没有框架,这些都很难实现,但使用Gin肯定会使其更快,代码更少,至少对于简单的情况是这样。

限制

Web框架便利性的另一面是使用它们时可能会遇到的限制和风格不匹配,在我们的简单示例中,我们已经遇到了一个限制:Gin路由中缺乏正则表达式支持,这意味着复杂的路由匹配需要更多代码来解析和验证。

任何包和工具都可能有局限性,但框架因其非常普遍而使局限性变得更加重要。我们会在 Gorilla/mux中发现一个限制,这将成为我们应用程序的障碍。然后我们可以用另一个路由包替换它。虽然过渡无疑会产生一些成本,但其影响将是局部的,因为只有路由配置受到影响。

代码

本节源码见Github

猜你喜欢

转载自juejin.im/post/7055853612454379527