ReverseProxy 依赖于 golang 中的 request.Body
ReverseProxy depending on the request.Body in golang
我想构建一个 http 反向代理来检查 HTTP 正文,然后将 HTTP 请求发送到它的上游服务器。你怎么能在 go 中做到这一点?
初始尝试(如下)失败,因为 ReverseProxy 复制传入请求、修改它并发送但正文已被读取。
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。
还有其他优雅的方式吗?
您可以将 Director
处理程序设置为 httputil.ReverseProxy
文档:https://golang.org/pkg/net/http/httputil/#ReverseProxy
这是一个示例代码,它从 localhost:8080
到 localhost:3333
的请求和代理中读取内容主体
package main
import (
"bytes"
"io/ioutil"
"log"
"net/http"
"net/http/httputil"
)
func main() {
director := func(req *http.Request) {
if req.Body != nil {
// read all bytes from content body and create new stream using it.
bodyBytes, _ := ioutil.ReadAll(req.Body)
req.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))
// create new request for parsing the body
req2, _ := http.NewRequest(req.Method, req.URL.String(), bytes.NewReader(bodyBytes))
req2.Header = req.Header
req2.ParseForm()
log.Println(req2.Form)
}
req.URL.Host = "localhost:3333"
req.URL.Scheme = "http"
}
proxy := &httputil.ReverseProxy{Director: director}
log.Fatalln(http.ListenAndServe(":8080", proxy))
}
编辑:
如上所述,在这种情况下,解析后的表单将为空。您将需要从正文中手动解析表单。
request.Body
是一个 io.ReaderCloser
,因为它描述了 tcp 连接的 rx
部分。但是在您的用例中,您需要阅读所有内容,因为您正在将正文解析为一种形式。这里的技巧是使用派生自已读取数据的 io.ReaderCloser
对象重新分配 r.Body
。这是我会做的:
1。获取请求主体的引用作为字节切片:
// before calling r.ParseForm(), get the body
// as a byte slice
body, err := ioutil.ReadAll(r.Body)
2。解析 form
后重新赋值 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()
方法。
把所有东西放在一起
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)
}
我想构建一个 http 反向代理来检查 HTTP 正文,然后将 HTTP 请求发送到它的上游服务器。你怎么能在 go 中做到这一点?
初始尝试(如下)失败,因为 ReverseProxy 复制传入请求、修改它并发送但正文已被读取。
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。 还有其他优雅的方式吗?
您可以将 Director
处理程序设置为 httputil.ReverseProxy
文档:https://golang.org/pkg/net/http/httputil/#ReverseProxy
这是一个示例代码,它从 localhost:8080
到 localhost:3333
package main
import (
"bytes"
"io/ioutil"
"log"
"net/http"
"net/http/httputil"
)
func main() {
director := func(req *http.Request) {
if req.Body != nil {
// read all bytes from content body and create new stream using it.
bodyBytes, _ := ioutil.ReadAll(req.Body)
req.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))
// create new request for parsing the body
req2, _ := http.NewRequest(req.Method, req.URL.String(), bytes.NewReader(bodyBytes))
req2.Header = req.Header
req2.ParseForm()
log.Println(req2.Form)
}
req.URL.Host = "localhost:3333"
req.URL.Scheme = "http"
}
proxy := &httputil.ReverseProxy{Director: director}
log.Fatalln(http.ListenAndServe(":8080", proxy))
}
编辑:
如上所述,在这种情况下,解析后的表单将为空。您将需要从正文中手动解析表单。
request.Body
是一个 io.ReaderCloser
,因为它描述了 tcp 连接的 rx
部分。但是在您的用例中,您需要阅读所有内容,因为您正在将正文解析为一种形式。这里的技巧是使用派生自已读取数据的 io.ReaderCloser
对象重新分配 r.Body
。这是我会做的:
1。获取请求主体的引用作为字节切片:
// before calling r.ParseForm(), get the body
// as a byte slice
body, err := ioutil.ReadAll(r.Body)
2。解析 form
后重新赋值 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()
方法。
把所有东西放在一起
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)
}