dougou2937
2016-01-11 14:40
浏览 143
已采纳

转到http,使用客户端将传入的http.request发送到其他服务器。

Here my use case

We have one services "foobar" which has two version legacy and version_2_of_doom (both in go)

In order to make the transition from legacy to version_2_of_doom , we would like in a first time, to have the two version alongside, and have the POST request (as there's only one POST api call in this ) received on both.

The way I see how to do it. Would be

  1. modifying the code of legacy at the beginning of the handler, in order to duplicate the request to 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 seems to not be as straightforward as this

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

Is there a well-known method to do this kind of action (i.e transfering without touching) a http.Request to an other server ?

  • 点赞
  • 写回答
  • 关注问题
  • 收藏
  • 邀请回答

2条回答 默认 最新

  • dongsu3138 2016-01-11 15:48
    已采纳

    You need to copy the values you want into a new request. Since this is very similar to what a reverse proxy does, you may want to look at what "net/http/httputil" does for ReverseProxy.

    Create a new request, and copy only the parts of the request you want to send to the next server. You will also need to read and buffer the request body if you intend to use it 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
    }
    
    点赞 打赏 评论
  • doucang8303 2016-01-11 14:56

    In my experience, the easiest way to achieve this was to simply create a new request and copy all request attributes that you need into the new request 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 benefit of not modifying the original request object (maybe your handler function or any middleware functions that are living in your stack still need the original object?).

    点赞 打赏 评论

相关推荐 更多相似问题