ReverseProxy取决于请求.golang中的主体 [英] ReverseProxy depending on the request.Body in golang

查看:36
本文介绍了ReverseProxy取决于请求.golang中的主体的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想构建一个HTTP反向代理,以检查HTTP正文,然后向其上游服务器发送HTTP请求.你怎么去做呢?

I want to build a http reverse proxy which checks the HTTP body and send HTTP requests to it's upstream servers after that. How can you do that in go?

由于ReverseProxy会复制传入的请求,对其进行修改并发送,但初始内容已被读取,因此初始尝试失败(如下).

Initial attempt (follows) fails because ReverseProxy copies the incoming request, modifies it and sends but the body is already read.

func main() {
    backendServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        b, err := ioutil.ReadAll(r.Body)
        if err != nil {
            http.Error(w, fmt.Sprintf("ioutil.ReadAll: %s", err), 500)
            return
        }
        // expecting to see hoge=fuga
        fmt.Fprintf(w, "this call was relayed by the reverse proxy, body: %s", string(b))
    }))
    defer backendServer.Close()

    rpURL, err := url.Parse(backendServer.URL)
    if err != nil {
        log.Fatal(err)
    }

    proxy := func(u *url.URL) http.Handler {
        p := httputil.NewSingleHostReverseProxy(u)
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            if err := r.ParseForm(); err != nil {
                http.Error(w, fmt.Sprintf("ParseForm: %s", err), 500)
                return
            }
            p.ServeHTTP(w, r)
        })
    }(rpURL)
    frontendProxy := httptest.NewServer(proxy)
    defer frontendProxy.Close()

    resp, err := http.Post(frontendProxy.URL, "application/x-www-form-urlencoded", bytes.NewBufferString("hoge=fuga"))
    if err != nil {
        log.Fatalf("http.Post: %s", err)
    }

    b, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        log.Fatalf("ioutil.ReadAll: %s", err)
    }

    fmt.Printf("%s", b)
}
// shows: "http: proxy error: http: ContentLength=9 with Body length 0"

然后我的下一个尝试是将整个主体读取为bytes.Reader并使用它来检查主体内容,并在开始发送给上游服务器之前从头开始查找.但是然后我必须重新实现ReverseProxy,这是我想避免的.还有其他优雅的方式吗?

Then my next attempt would be to read the whole body into bytes.Reader and use that to check the body content, and Seek to the beginning before sending to upstream servers. But then I have to re-implement ReverseProxy which I would like to avoid. Is there any other elegant way?

推荐答案

如上所述,在这种情况下,已解析的表单将为空.您将需要从正文中手动解析表单.

As commented above, the parsed form will be empty in this case. You will need to manually parse the form from the body.

request.Body 是一个 io.ReaderCloser ,因为它描述了tcp连接的 rx 部分.但是,在您的用例中,您需要阅读所有内容,因为您正在将主体解析为一种形式.这里的技巧是用从已经读取的数据派生的 io.ReaderCloser 对象重新分配 r.Body .这就是我要做的:

The request.Body is a io.ReaderCloser, because this describes the rx part of a tcp connection. But in your use case you need to read everything since you are parsing the body into a form. The trick here is to reassign the r.Body with a io.ReaderCloser object derived from the already read data. Here is what I would do:

  // before calling r.ParseForm(), get the body
  // as a byte slice
  body, err := ioutil.ReadAll(r.Body)

2.解析表单后重新分配r.Body

  // after calling r.ParseForm(), reassign body
  r.Body = ioutil.NopCloser(bytes.NewBuffer(body))

bytes.NewBuffer(body)将正文字节片转换为 io.Reader ,而 ioutil.NopCloser 转换io.Reader 转换为 io.ReaderCloser ,并使用 nop Close()方法.

bytes.NewBuffer(body) converts the body byte slice into a io.Reader, and ioutil.NopCloser convertts a io.Reader into a io.ReaderCloser with a nop Close() method.

  package main

  import "net/http"
  import "net/http/httputil"
  import "net/url"
  import "net/http/httptest"
  import "fmt"
  import "log"
  import "bytes"
  import "io/ioutil"

  func main() {
    backendServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        b, err := ioutil.ReadAll(r.Body)
        if err != nil {
            http.Error(w, fmt.Sprintf("ioutil.ReadAll: %s", err), 500)
            return
        }
        // expecting to see hoge=fuga
        fmt.Fprintf(w, "this call was relayed by the reverse proxy, body: %s", string(b))
    }))
    defer backendServer.Close()

    rpURL, err := url.Parse(backendServer.URL)
    if err != nil {
        log.Fatal(err)
    }

    proxy := func(u *url.URL) http.Handler {
        p := httputil.NewSingleHostReverseProxy(u)
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            // read out body into a slice
            body, err := ioutil.ReadAll(r.Body)
            if err != nil {
                http.Error(w, fmt.Sprintf("Error reading body: %s", err), 500)
                return
            }

            // inspect current body here
            if err := r.ParseForm(); err != nil {
                http.Error(w, fmt.Sprintf("ParseForm: %s", err), 500)
                return
            }

            // assign a new body with previous byte slice
            r.Body = ioutil.NopCloser(bytes.NewBuffer(body))
            p.ServeHTTP(w, r)
        })
    }(rpURL)
    frontendProxy := httptest.NewServer(proxy)
    defer frontendProxy.Close()

    resp, err := http.Post(
        frontendProxy.URL,
        "application/x-www-form-urlencoded",
        bytes.NewBufferString("hoge=fuga"))
    if err != nil {
        log.Fatalf("http.Post: %s", err)
    }

    b, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        log.Fatalf("ioutil.ReadAll: %s", err)
    }

    fmt.Printf("%s", b)
  }

这篇关于ReverseProxy取决于请求.golang中的主体的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆