Go http, send incoming http.request to another server using client.Do

Here is my usage example

We have one foobar service which has two legacy versions and version_2_of_doom (both in go)

To make the transition from legacy to version_2_of_doom , for the first time we would like to have two versions together and receive a POST request (since there was only one POST api call in it) on both.

As I see it, how to do it. Would

  • changing legacy code at the beginning of the handler to duplicate the request for version_2_of_doom

      func(w http.ResponseWriter, req *http.Request) { req.URL.Host = "v2ofdoom.local:8081" req.Host = "v2ofdoom.local:8081" client := &http.Client{} client.Do(req) // legacy code 

but it doesn't seem as simple as this

it fails with http: Request.RequestURI can't be set in client requests.

Is there a known method for performing such an action (for example, transfer without touching) a http.Request to another server?

+5
source share
2 answers

You need to copy the desired values ​​into a new query. Since this is very similar to what the reverse proxy does, you can see that "net / http / httputil" is for ReverseProxy .

Create a new request and copy only those parts of the request that you want to send to the next server. You will also need to read and fix the request body if you intend to use it in both places:

 func handler(w http.ResponseWriter, req *http.Request) { // we need to buffer the body if we want to read it here and send it // in the request. body, err := ioutil.ReadAll(req.Body) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } // you can reassign the body if you need to parse it as multipart req.Body = ioutil.NopCloser(bytes.NewReader(body)) // create a new url from the raw RequestURI sent by the client url := fmt.Sprintf("%s://%s%s", proxyScheme, proxyHost, req.RequestURI) proxyReq, err := http.NewRequest(req.Method, url, bytes.NewReader(body)) // We may want to filter some headers, otherwise we could just use a shallow copy // proxyReq.Header = req.Header proxyReq.Header = make(http.Header) for h, val := range req.Header { proxyReq.Header[h] = val } resp, err := httpClient.Do(proxyReq) if err != nil { http.Error(w, err.Error(), http.StatusBadGateway) return } defer resp.Body.Close() // legacy code } 
+4
source

In my experience, the easiest way to achieve this was to simply create a new query and copy all the necessary query attributes into a new query object:

 func(rw http.ResponseWriter, req *http.Request) { url := req.URL url.Host = "v2ofdoom.local:8081" proxyReq, err := http.NewRequest(req.Method, url.String(), req.Body) if err != nil { // handle error } proxyReq.Header.Set("Host", req.Host) proxyReq.Header.Set("X-Forwarded-For", req.RemoteAddr) for header, values := range req.Header { for _, value := range values { proxyReq.Header.Add(header, value) } } client := &http.Client{} proxyRes, err := client.Do(proxyReq) // and so on... 

This approach has the advantage of not changing the original request object (perhaps your handler function or any middleware functions that live on your stack still need the original object?).

+2
source

Source: https://habr.com/ru/post/1240350/


All Articles