elton - 高性能简单的 Go Web 框架


Apache-2.0
跨平台
Google Go

软件简介

前言

开始接触后端开发是从 nodejs 开始,最开始使用的框架是 express,后来陆续接触了其它的框架,觉得最熟悉的还是koa。使用 golang
做后端开发时,对比使用过 gin,echo 以及 iris 三个框架,它们的用法都类似(都支持中间件,中间件的处理也类似),但是在开发过程中还是钟情于
koa 的处理方式,失败则 throw error,成功则将响应数据赋值至 ctx.body,简单易懂。

概述

造一个新的轮子的时候,首先考虑的是满足自己的需求,弱水三千只取一瓢饮,对我来说,新轮子的满足我所需要的一瓢则是重中之重。

  • 请求经过中间件的处理方式为由外至内,响应时再由内至外
  • 所有的处理函数都一致(参数、类型等),每个处理函数都有可以是其它处理函数的前置中间件
  • 请求处理成功时,直接赋值至 Body(interface{}),由中间件将 interface{} 序列化(如 json,xml 等)
  • 请求处理失败时,返回 error,由中间件将 error 转换为相应的 HTTP 响应(golang 中的 error 为 interface,可自定义相应的 Error 实例)

elton 参考 koa 的实现,能够简单的添加各类中间件,中间件的执行也和 koa
一样,如下图所示的洋葱图,从外层往内层递进,再从内层返回外层(也可以未至最内层则直接往上返回)。

下面我们先看一下简单的处理成功与出错的例子:

package main

import (
    "bytes"
    "errors"

    "github.com/vicanso/elton"
)

func main() {
    d := elton.New()

    d.GET("/", func(c *elton.Context) (err error) {
        c.BodyBuffer = bytes.NewBufferString("hello world")
        return
    })

    d.GET("/error", func(c *elton.Context) (err error) {
        err = errors.New("my error")
        return
    })

    d.ListenAndServe(":7001")
}

如代码所示,处理过程非常简单,响应数据由BodyBuffer中获取,如果流程出错,直接返回Error则可。不过例子中的与koa的处理还是有所差距,koa中返回数据时可以随意的指定各种Object(转换为json),而在golang中因为是强类型,所以需要使用interface{}的形式返回,再通过中间件将各类不同的struct转换至BodyBuffer,以及设置Content- Type

中间件

在 elton 中有两类中间件是必须了解的,一是将 Context.Body(interface{}) 转换至对应的响应数据的 Responder
中间件,另外一个就是将 Error 转换至对应的响应数据的 Error 中间件。在详解这两类中间件之前,我们首先来了解一下 elton
的中间件的一些基本概念。

elton 中间件的处理函数是Handler func(*Context) error,可以通过Use方法添加至全局的中间件,也可单独添加至单一组或单一的路由处理。中间件处理也非常简单,如果出错,返回
Error(后续的处理函数不再执行)。在当前函数中已完成处理,则无需要调用Context.Next(),需要转至下一处理函数,则调用Context.Next()则可。

package main

import (
    "bytes"
    "log"
    "time"

    "github.com/vicanso/elton"
)

func main() {
    d := elton.New()

    // logger
    d.Use(func(c *elton.Context) (err error) {
        err = c.Next()
        rt := c.GetHeader("X-Response-Time")
        log.Printf("%s %s - %s\n", c.Request.Method, c.Request.RequestURI, rt)
        return
    })

    // x-response-time
    d.Use(func(c *elton.Context) (err error) {
        start := time.Now()
        err = c.Next()
        c.SetHeader("X-Response-Time", time.Since(start).String())
        return
    })

    d.GET("/", func(c *elton.Context) (err error) {
        c.BodyBuffer = bytes.NewBufferString("hello world")
        return
    })

    d.ListenAndServe(":7001")
}

responder 中间件

HTTP 的响应主要分三部分,HTTP 响应状态码,HTTP 响应头以及 HTTP 响应体。前两部分比较简单,格式统一,但是 HTTP
响应体对于不同的应用有所不同。在 elton 的处理中,会将 BodyBuffer 的相应数据在响应时作为 HTTP 响应体输出。在实际应用中,有些会使用
json,有些是 xml 或者自定义的响应格式。因此在elton是提供了Body(interface{})
属性,允许将响应数据赋值至此字段,再由相应的中间件转换为对应的 BodyBuffer 以及设置Content-Type

在实际使用中,HTTP 接口的响应主要还是以json为主,因此 elton-
responder
提供了将 Body 转换为对应的
BodyBuffer(json) 的处理,主要的处理如下:

// New create a responder
func New(config Config) elton.Handler {
    skipper := config.Skipper
    if skipper == nil {
        skipper = elton.DefaultSkipper
    }
    marshal := standardJSON.Marshal
    if config.Fastest {
        marshal = fastJSON.Marshal
    }
    return func(c *elton.Context) (err error) {
        if skipper(c) {
            return c.Next()
        }
        err = c.Next()
        if err != nil {
            return
        }
        // 如果已设置了BodyBuffer,则已生成好响应数据,跳过
        if c.BodyBuffer != nil {
            return
        }

        if c.StatusCode == 0 && c.Body == nil {
            // 如果status eltone 与 body 都为空,则为非法响应
            err = errInvalidResponse
            return
        }
        // 如果body是reader,则跳过
        if c.IsReaderBody() {
            return
        }

        ct := elton.HeaderContentType

        hadContentType := false
        // 判断是否已设置响应头的Content-Type
        if c.GetHeader(ct) != "" {
            hadContentType = true
        }

        statusCode := c.StatusCode
        if statusCode == 0 {
            statusCode = http.StatusOK
        }

        var body []byte
        if c.Body != nil {
            switch c.Body.(type) {
            case string:
                if !hadContentType {
                    c.SetHeader(ct, elton.MIMETextPlain)
                }
                body = []byte(c.Body.(string))
            case []byte:
                if !hadContentType {
                    c.SetHeader(ct, elton.MIMEBinary)
                }
                body = c.Body.([]byte)
            default:
                // 转换为json
                buf, err := marshal(c.Body)
                if err != nil {
                    c.Cod().EmitError(c, err)
                    statusCode = http.StatusInternalServerError
                    he := hes.NewWithErrorStatusCode(err, statusCode)
                    he.Exception = true
                    c.SetHeader(ct, elton.MIMEApplicationJSON)
                    body = he.ToJSON()
                    err = nil
                } else {
                    if !hadContentType {
                        c.SetHeader(ct, elton.MIMEApplicationJSON)
                    }
                    body = buf
                }
            }
        }
        c.BodyBuffer = bytes.NewBuffer(body)
        c.StatusCode = statusCode
        return nil
    }
}

代码的处理步骤如下:

1、前置判断是否跳过中间件,主要判断条件为:是否出错,或者已设置BodyBuffer(表示已完成响应数据的处理)或者 Body 为 Reader
(以流的形式输出响应数据)。

2、如果 Body 的类型为 string,则将 string 转换为 bytes,如果未设置数据类型,则设置为text/plain; charset=UTF-8

3、如果 Body 的类型为 []byte,如果未设置数据类型,则设置为application/octet-stream

4、对于其它类型,则使用json.Marshal转换为对应的 []byte,如果未设置数据类型,则设置为application/json; charset=UTF-8

通过此中间件,在开发时可以简单的将各种 struct 对象,map 对象以json的形式返回,无需要单独处理数据转换,方便快捷。如果应用需要以 xml
等其它形式返回,则可自己编写自定义中间件来做转换处理。

error handler 中间件

elton 中默认的 Error
处理只是简单的输出err.Error(),而且状态码也只是简单的使用StatusInternalServerError,无法满足应用中的各类定制的出错方式。因此一般建议编写自定义的出错处理中间件,根据自定义的
Error 对象生成相应的出错响应数据。如 elton-error-handler 则针对返回的
hes对象对应生成相应的状态码,响应类型以及响应数据(json):

// New create a error handler
func New(config Config) elton.Handler {
    skipper := config.Skipper
    if skipper == nil {
        skipper = elton.DefaultSkipper
    }
    return func(c *elton.Context) error {
        if skipper(c) {
            return c.Next()
        }
        err := c.Next()
        // 如果没有出错,直接返回
        if err == nil {
            return nil
        }
        he, ok := err.(*hes.Error)
        if !ok {
            he = hes.Wrap(err)
            he.StatusCode = http.StatusInternalServerError
            he.Exception = true
            he.Category = errErrorHandlerCategory
        }
        c.StatusCode = he.StatusCode
        if config.ResponseType == "json" {
            buf := he.ToJSON()
            c.BodyBuffer = bytes.NewBuffer(buf)
            c.SetHeader(elton.HeaderContentType, elton.MIMEApplicationJSON)
        } else {
            c.BodyBuffer = bytes.NewBufferString(he.Error())
            c.SetHeader(elton.HeaderContentType, elton.MIMETextPlain)
        }

        return nil
    }
}

后记

elton 提供更简单方便的 WEB
开发体验,主要实现的代码非常简单,更多的功能都依赖于各类中间件。需要查阅更多的中间件以及文档说明请查阅 github 项目中的说明。