猿问

转到 http,使用 client.Do 将传入的 http.request 发送到其他服务器

这是我的用例


我们有一个服务“foobar”,它有两个版本legacy 和version_2_of_doom (都在运行中)


为了进行从legacy到的转换version_2_of_doom,我们希望第一次将两个版本放在一起,并在两个版本上都收到 POST 请求(因为这里只有一个 POST api 调用)。


我看到如何去做的方式。将是


修改legacy 处理程序开头的代码,以便将请求复制到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 

但似乎没有这么简单


它失败了 http: Request.RequestURI can't be set in client requests.


是否有一种众所周知的方法来执行这种操作(即在不接触的情况下传输)ahttp.Request到其他服务器?


慕桂英546537
浏览 197回答 3
3回答

慕妹3146593

您需要将所需的值复制到新请求中。由于这与反向代理的作用非常相似,您可能需要查看“net/http/httputil”对ReverseProxy.创建一个新请求,并仅复制要发送到下一个服务器的请求部分。如果您打算在两个地方使用它,您还需要读取和缓冲请求正文: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}

狐的传说

根据我的经验,实现这一点的最简单方法是简单地创建一个新请求并将您需要的所有请求属性复制到新的请求对象中: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...这种方法的好处是不修改原始请求对象(也许您的处理程序函数或存在于堆栈中的任何中间件函数仍然需要原始对象?)。

明月笑刀无情

使用原始请求(仅在原始请求仍需要时才复制或复制):func handler(w http.ResponseWriter, r *http.Request) {    // Step 1: rewrite URL    URL, _ := url.Parse("https://full_generic_url:123/x/y")    r.URL.Scheme = URL.Scheme    r.URL.Host = URL.Host    r.URL.Path = singleJoiningSlash(URL.Path, r.URL.Path)    r.RequestURI = ""    // Step 2: adjust Header    r.Header.Set("X-Forwarded-For", r.RemoteAddr)    // note: client should be created outside the current handler()    client := &http.Client{}     // Step 3: execute request    resp, err := client.Do(r)    if err != nil {        http.Error(w, err.Error(), http.StatusInternalServerError)        return    }    // Step 4: copy payload to response writer    copyHeader(w.Header(), resp.Header)    w.WriteHeader(resp.StatusCode)    io.Copy(w, resp.Body)    resp.Body.Close()}// copyHeader and singleJoiningSlash are copy from "/net/http/httputil/reverseproxy.go"func copyHeader(dst, src http.Header) {    for k, vv := range src {        for _, v := range vv {            dst.Add(k, v)        }    }}func singleJoiningSlash(a, b string) string {    aslash := strings.HasSuffix(a, "/")    bslash := strings.HasPrefix(b, "/")    switch {    case aslash && bslash:        return a + b[1:]    case !aslash && !bslash:        return a + "/" + b    }    return a + b}
随时随地看视频慕课网APP

相关分类

Go
我要回答