使用gocraft中间件检查HTTP请求的正文

时间:2013-12-24 21:35:28

标签: http go hmac

到目前为止,我一直在使用gocraft-web包来对HTTP服务进行一些开发。它真的很棒,因为你可以在其中粘贴中间件来检查标题中是否存在Cookie等内容。

目前我想要实施请求签名。让客户端签署请求很容易,但我想用一个共同的中间件检查所有端点。基本上,中间件需要找到要检查的密钥,计算请求HMAC,并根据提供的HMAC(可能在Authorization标头中)进行检查。

计算实际的HMAC非常容易。

问题是:在中间件中读取消息使其无法用于最终端点。

我提出的最佳解决方案(示例如下所示)是从中间件中的Request读取所有内容并将其填充回bytes.Buffer以供稍后阅读。有一个更好的方法吗?目前的实施看起来有些过时。

将所有内容读入内存很糟糕,但我可能只是将我的服务放在代理后面并且无论如何限制请求的大小。实际内容总是很小(小于5千字节)。这种方法引入的额外副本可能非常慢,但计算消息的HMAC并不是一件好事。

这样做的好处在于它是透明的:它可以与任何其他需要从Request.Body读取的http代码一起使用而不会有任何魔法。

我想我可能会更加流畅并使用io.TeeReader

到目前为止,这是我的解决方案。如果你发布到localhost:3300一些JSON,它会在服务器进程中将sha512打印到终端,但响应也能够包含密钥和列表的列表。其中的价值观。

package main

import "fmt"
import "github.com/gocraft/web"
import "net/http"
import "bytes"
import "crypto/sha512"
import "io"
import "encoding/hex"
import "encoding/json"

type Context struct{}

type echoer struct {
    *bytes.Buffer
}

func (e echoer) Close() error {
    //Just do nothing to make the interface happy
    return nil
}

func middlewareThatLooksAtBody(rw web.ResponseWriter, req *web.Request, next web.NextMiddlewareFunc) {
    var replacement echoer
    replacement.Buffer = &bytes.Buffer{}

    hash := sha512.New()

    hash.Write([]byte(req.Method))
    reader := req.Body

    var bytes []byte = make([]byte, 64)
    for {
        amount, err := reader.Read(bytes)

        fmt.Printf("Read %d bytes\n", amount)

        if err != nil {
            if err == io.EOF {
                break
            }
            panic(err)
        }
        if amount == 0 {
            break
        }

        hash.Write(bytes)
        replacement.Write(bytes)
    }
    //Is this needed?
    reader.Close()

    //replacement.Seek(0, 0)
    req.Body = replacement

    fmt.Printf("%v\n", hex.EncodeToString(hash.Sum(nil)))

    next(rw, req)
}

func echoJson(rw web.ResponseWriter, req *web.Request) {
    dec := json.NewDecoder(req.Body)
    var obj map[string]interface{}
    err := dec.Decode(&obj)

    if err != nil {
        rw.WriteHeader(http.StatusBadRequest)
        fmt.Fprintf(rw, "%v\n", err)
        return
    }

    for k, v := range obj {
        fmt.Fprintf(rw, "%v = %v\n", k, v)
    }
}

func main() {
    router := web.New(Context{})

    router.Middleware(middlewareThatLooksAtBody)
    router.Post("/", echoJson)
    http.ListenAndServe("localhost:3300", router)

}

1 个答案:

答案 0 :(得分:0)

根据您的描述,您似乎需要读取请求正文中的所有字节,无论您的处理程序将执行什么操作。

如果是这样,那么你至少有几个选项可以避免额外的副本:

1)将阅读内容存储在gocraft上下文中。

2)在中间件中进行所有正文数据处理和验证,并将处理结果存储在上下文中。

当然,这意味着您的处理程序现在必须知道他们应该在上下文中查找内容而不是req.Body。

我认为,根据您的要求,这是一个不错的权衡。