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?
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"
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?
You can set Director
handler to httputil.ReverseProxy
Document: https://golang.org/pkg/net/http/httputil/#ReverseProxy
Here's an example code which reads content body from request and proxies from localhost:8080
to 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))
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With