[转]golang-选择合适的Web框架
原文:https://brunoscheufler.com/blog/2019-04-26-choosing-the-right-go-web-framework
诚然,有很多库和框架可以在Go中构建现代Web服务,几乎适合您将要遇到的每种情况。但是有时候您可能只想使用一个最小的基础来构建,而不是拥有您可能永远不会在项目中使用的具有众多扩展和功能的成熟系统。从生态系统的角度来看,我已将要比较的库数量减少到了四个主要角色: gin, echo, chi 和 mux.
竞争者
- gin:作为基准,我喜欢将杜松子酒描述为Go的表达形式。作为最流行的Go Web框架之一,它建立在基于路由器和中间件的方法的基础上,您可以在其中定义附加到特定路由的请求处理程序。由于高度关注可扩展性,因此您可以为几乎每种Web应用程序方案构建合适的解决方案。如果您需要用于所有目的的工具,杜松子酒在这方面做得非常出色。包括有用的功能,例如参数路径,JSON到结构的绑定等,它几乎包含了大多数用例所需的一切。
- echo: 与gin相比,Echo暴露出令人惊讶的相似结构,提供了大量可用的中间件功能,例如JWT auth支持,CORS处理等。路由逻辑之间的最大区别是:gin是在fasthttprouter库之上构建的,其所有功能优点和缺点,echo使用一个独立的模块处理路由,这并不令人惊讶。
- chi: chi是上面系统中的准系统解决方案之一,我希望在上面的库中抛弃所有的麻烦,我强烈建议您在这些项目中使用出色的路由处理逻辑,但仍然希望使用低级API进行开发请求生命周期中的所有其他内容。与其他库一样,中间件功能也是一等公民,因此您不必自己构建它。我认为chi是我将在稍后描述的mux库和gin或echo之类的解决方案的完美结合。
- mux:如果您只需要专注于细粒度路径匹配的快速简单的路由实用程序,则mux是一个完美的选择!建立在多路复用器(因此得名)设计模式上,该模式基本上描述了路由逻辑和实际请求处理程序之间的链接层,实际上只处理选择要运行的适当请求处理程序。在某些情况下,这种简单性可以带来真正的好处!
构建示例RESTful应用程序
为了真正发现库实现之间的差异,我们将创建一个具有一些基本端点的简单RESTful Web服务。因为我们专注于路由匹配和请求处理的详细信息,所以将不包含任何实际的业务逻辑,而仅包括请求-响应生命周期功能。
从简单的GET端点开始
作为第一项任务,我决定为特定用户的详细信息构建一个完全简单的GET端点,该用户的名称作为route参数提供。响应将是一个简单的JSON对象(下面的结构),每个实现都以相同的方式键入该对象。
// This is the response struct that will be
// serialized and sent back
type StatusResponse struct {
Status string `json:"status"`
User string `json:"user"`
}
Gin
func UserGetHandler(c *gin.Context) {
// Create response object
body := &StatusResponse{
Status: "Hello world from gin!",
User: c.Param("user"),
}
// Send it off to the client
c.JSON(http.StatusOK, body)
}
func main() {
// Create gin router
router := gin.Default()
// Set up GET endpoint
// for route /users/<username>
router.GET(
"/users/:user",
UserGetHandler,
)
// Launch Gin and
// handle potential error
err := router.Run(":8003")
if err != nil {
panic(err)
}
}
Echo
// In addition to echo request handlers
// using a special context including
// all kinds of utilities, generated errors
// can be returned to handle them easily
func UserGetHandler(e echo.Context) error {
// Create response object
body := &StatusResponse{
Status: "Hello world from echo!",
User: e.Param("user"),
}
// In this case we can return the
// JSON function with our body
// as errors thrown here will
// be handled accordingly
return e.JSON(http.StatusOK, body)
}
func main() {
// Create echo instance
e := echo.New()
// Add endpoint route
// for /users/<username>
e.GET("/users/:user", UserGetHandler)
// Start echo and handle errors
e.Logger.Fatal(e.Start(":8002"))
}
Chi
func UserGetHandler(
w http.ResponseWriter,
r *http.Request,
) {
// Add Content-Type header
// to indicate JSON response
w.Header().Set(
"Content-Type",
"application/json",
)
// create status response
body := StatusResponse{
Status: "Hello world from chi!",
User: chi.URLParam(r, "user"),
}
serializedBody, _ := json.Marshal(body)
_, _ = w.Write(serializedBody)
}
func main() {
r := chi.NewRouter()
r.Get("/users/{user}", UserGetHandler)
log.Println("Listening on :8001")
log.Fatal(http.ListenAndServe(":8001", r))
}
mux
func UserGetHandler(
w http.ResponseWriter,
r *http.Request,
) {
vars := mux.Vars(r)
w.Header().Set(
"Content-Type",
"application/json",
)
body := StatusResponse{
Status: "Hello world from mux!",
User: vars["user"],
}
serializedBody, _ := json.Marshal(body)
_, _ = w.Write(serializedBody)
}
func main() {
r := mux.NewRouter()
r.HandleFunc("/users/{user}", UserGetHandler)
.Methods("GET")
log.Println("Listening on :8004")
log.Fatal(http.ListenAndServe(":8004", r))
}
all此处提供上述所有示例的完整代码。
查看代码,您已经可以看到每个库公开的功能范围:而echo和gin都使用其自己的上下文结构并提供格式化响应的帮助器,chi和mux利用了大多数裸机所使用的标准请求处理程序转而使用Web应用程序,这意味着您将不得不使用其他依赖项,或者围绕它构建自己的功能,如我在示例中所做的那样。但是,每个库中都包含诸如提取路由参数之类的基本帮助程序实用程序。现在让我们更深入一些,如果我们要处理JSON请求主体该怎么办?
处理请求主体
对于每个实现,请求正文都是相同的结构,下面直接添加以供参考:
// This simple struct will be deserialized
// and processed in the request handler
type RequestBody struct {
Name string `json:"name"`
}
Gin
func UserPostHandler(c *gin.Context) {
// Create empty request body
// struct used to bind actual body into
requestBody := &RequestBody{}
// Bind JSON content of request body to
// struct created above
err := c.BindJSON(requestBody)
if err != nil {
// Gin automatically returns an error
// response when the BindJSON operation
// fails, we simply have to stop this
// function from continuing to execute
return
}
// Create response object
body := &StatusResponse{
Status: "Hello world from echo!",
User: requestBody.Name,
}
// And send it off to the requesting client
c.JSON(http.StatusOK, body)
}
func main() {
router := gin.Default()
router.POST("/users", UserPostHandler)
err := router.Run(":8003")
if err != nil {
panic(err)
}
}
Echo
func UserPostHandler(e echo.Context) error {
// Similar to the gin implementation,
// we start off by creating an
// empty request body struct
requestBody := &RequestBody{}
// Bind body to the request body
// struct and check for potential
// errors
err := e.Bind(requestBody)
if err != nil {
// If an error was created by the
// Bind operation, we can utilize
// echo's request handler structure
// and simply return the error so
// it gets handled accordingly
return err
}
// Create status response
body := &StatusResponse{
Status: "Hello world from echo!",
User: requestBody.Name,
}
// Simply return error
return e.JSON(http.StatusOK, body)
}
func main() {
e := echo.New()
e.POST("/users", UserPostHandler)
e.Logger.Fatal(e.Start(":8002"))
}
Chi
func UserPostHandler(w http.ResponseWriter, r *http.Request) {
// Read complete request body
rawRequestBody, err := ioutil.ReadAll(r.Body)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
// Transform into RequestBody struct
requestBody := &RequestBody{}
err = json.Unmarshal(rawRequestBody, requestBody)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "application/json")
body := StatusResponse{
Status: "Hello world from chi!",
User: requestBody.Name,
}
serializedBody, _ := json.Marshal(body)
_, _ = w.Write(serializedBody)
}
func main() {
r := chi.NewRouter()
r.Post("/users", UserPostHandler)
log.Println("Listening on :8001")
log.Fatal(http.ListenAndServe(":8001", r))
}
Mux
func UserPostHandler(w http.ResponseWriter, r *http.Request) {
// Read complete request body
rawRequestBody, err := ioutil.ReadAll(r.Body)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
// Transform into RequestBody struct
requestBody := &RequestBody{}
err = json.Unmarshal(rawRequestBody, requestBody)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "application/json")
body := StatusResponse{
Status: "Hello world from mux!",
User: requestBody.Name,
}
serializedBody, _ := json.Marshal(body)
_, _ = w.Write(serializedBody)
}
func main() {
r := mux.NewRouter()
r.HandleFunc("/users", UserPostHandler).Methods("POST")
log.Println("Listening on :8004")
log.Fatal(http.ListenAndServe(":8004", r))
}
以上所有示例的完整代码均在此处提供
再一次,我们可以看到极简的库和功能齐全的库之间的区别。特别是对于将请求正文数据绑定到结构上,与gin和echo提供的单行代码相比,手动读取完整的输入并将其解组会产生巨大的开销。
我可以继续提供更复杂的用例的示例,但是我认为到目前为止,您可能已经注意到每个实现中都有一个重复出现的主题:使用gin和echo之类的功能强大的库,您可以用更少的代码编写功能,从而获得很多好处。行代码,作为回报,您牺牲了一些灵活性,在某些情况下还降低了性能。选择一个非常轻量级的路由帮助程序库(例如chi或mux)可以帮助您构建真正快速的Web应用程序,但是您必须花费更多的时间来实现或搜索缺少的功能以解析请求正文,处理响应等。
最终取决于您和您项目的需求,因此也许可以通过查看事实和数据来做出决定。作为记录,我为这篇文章选择的每个库都非常受欢迎,并且由大量的贡献者积极维护,因此您不必选择已经处于报废阶段的工具。
我对您的选择感兴趣!如果您有任何疑问,建议或其他反馈,请随时发送邮件。
- 点赞
- 收藏
- 关注作者
评论(0)