跳转到主要内容

本文提供了有关使用普通 Go HTTP 处理程序编写 JSON REST API 的随机提示。

  • 使用匿名类型解析 JSON
  • 使用 http.MaxBytesReader 限制请求长度
  • 使用 map[string]interface{} 生成 JSON
  • 使用 MarshalJSON 自定义 JSON 输出
  • 使用中间件处理错误
  • 使用结构对处理程序进行分组
  • 使用分段/编码

使用匿名类型解析 JSON


而不是声明全局类型:

type ArticleRequest struct {
    Name string
}

func handler(w http.ResponseWriter, req *http.Request) {
    article := new(ArticleRequest)
    if err := json.NewDecoder(req.Body).Decode(article); err != nil {
        panic(err)
    }
}

您可以改为声明匿名内联类型:

func handler(w http.ResponseWriter, req *http.Request) {
    var in struct {
        Name string
    }
    if err := json.NewDecoder(req.Body).Decode(&in); err != nil {
        panic(err)
    }
}


优点:

  • 没有悬空类型。
  • 处理程序彼此分离。
  • 类型在使用它们的地方声明。

使用 http.MaxBytesReader 限制请求长度


默认情况下,Go 不对传入请求的长度施加任何限制。您应该使用 MaxBytesReader 自己处理。

func handler(w http.ResponseWriter, req *http.Request) {
    req.Body = http.MaxBytesReader(w, req.Body, 1<<20) // 1MB
}


要快速计算字节数,请使用以下技巧:

  • 3 << 10 - 3 kilobytes.
  • 3 << 20 - 3 megabytes.
  • 3 << 30 - 3 gigabytes.

 


使用 map[string]interface{} 生成 JSON


通常不值得声明一个结构来生成 JSON 响应。使用地图更容易,而且速度稍慢。一些框架甚至为 map[string]interface{} 提供了一个简短的类型别名,例如 gin.H 或 treemux.H。

type H map[string]interface{}

func handler(w http.ResponseWriter, req *http.Request) {
    if err := json.NewEncoder(w).Encode(H{
        "articles": articles,
        "count": count,
    }); err != nil {
        panic(err)
    }
}

使用 MarshalJSON 自定义 JSON 输出


您可以编写以下代码来自定义 JSON 输出,但它失败并出现致命错误:堆栈溢出错误。

type User struct{
    Name string
}

func (u *User) MarshalJSON() ([]byte, error) {
    if u.Name == "" {
        u.Name = "anonymous"
    }
    // This call causes infinite recursion.
    return json.Marshal(u)
}

您可以通过使用原始类型作为基础声明一个新类型来修复它:

type jsonUser User

func (u *User) MarshalJSON() ([]byte, error) {
    if u.Name == "" {
        u.Name = "anonymous"
    }
    return json.Marshal((*jsonUser)(u))
}

使用中间件处理错误

而不是编写这样的代码:

func handler(w http.ResponseWriter, req *http.Request) {
    if processRequest(req); err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }
    if err := json.NewEncoder(w).Encode(H{}); err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
}


您可以创建一个为您处理错误的中间件:

func handler(w http.ResponseWriter, req *http.Request) error {
    if processRequest(req); err != nil {
        return err
    }
    if err := json.NewEncoder(w).Encode(H{}); err != nil {
        return err
    }
    return nil
}

func errorHandler(next func(w http.ResponseWriter, req *http.Request) error) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
        if err := next(w, req); err != nil {
            // You should change status code depending on the error.
            http.Error(w, err.Error(), http.StatusBadRequest)
        }
    })
}

或者您可以使用开箱即用的提供此类功能的 echo 或 treemux。

使用结构对处理程序进行分组


而不是使用普通函数:

const rowLimit = 100
const rateLimit = 10

func showUser(w http.ResponseWriter, req *http.Request) {}
func listUsers(w http.ResponseWriter, req *http.Request) {}
func delUser(w http.ResponseWriter, req *http.Request) {}

最好定义一个结构并将所有相关状态存储在那里:

type UserHandler struct{
    rowLimit  int
    rateLimit int
}

func (h *UserHandler) Show(w http.ResponseWriter, req *http.Request) {}
func (h *UserHandler) List(w http.ResponseWriter, req *http.Request) {}
func (h *UserHandler) Del(w http.ResponseWriter, req *http.Request) {}

使用分段/编码


segmentio/encoding 是 encoding/json 的直接替代品,比原始包快 2-3 倍。开始使用它所需要做的就是更新导入路径:

-import "encoding/json"
+import "github.com/segmentio/encoding/json"


它还提供了直接与 []byte 一起工作的较低级别的 API,并且效率更高:

func Append(b []byte, x interface{}, flags AppendFlags) ([]byte, error)
func Parse(b []byte, x interface{}, flags ParseFlags) ([]byte, error)
文章链接