【问题标题】:Share context between handleFunc and modifyResponse in Go reverse proxy在 Go 反向代理中的 handleFunc 和 modifyResponse 之间共享上下文
【发布时间】:2019-03-17 18:39:44
【问题描述】:

我正在尝试学习 Go,并认为一个不错的小项目将是一个放置在 Web 服务器前面的 A/B 测试代理。我几乎不知道 Go 本质上提供了一个开箱即用的反向代理,所以设置很简单。我已经到了代理流量的地步,但事情就是这样,我在实现实际功能时遇到了麻烦,因为无论我在哪里可以访问响应,我都无法访问分配的 A/B 测试变体:

  • handleFunc 中,我将每个测试的变体分配给请求,因此上游服务器也可以知道它并将 if 用于其后端的实现。
  • 我正在设置一个包含所有测试和变体的 cookie,包括代理到上游的请求和返回给客户端的响应。
  • 在响应从上游服务器返回后,由查找/替换对组成的测试将对响应正文进行更改。
  • 我正在尝试使用httputil.ReverseProxymodifyResponse 函数来进行响应突变。

问题是我不知道如何在不更改上游服务器的情况下共享handleFuncmodifyResponse 之间分配的变体。我希望能够分享这个上下文(基本上是 map[string]string 不知何故。

代码示例:

这是我的代码的提炼版本,我的问题基本上是,modifyRequest 如何知道handleFunc 中发生的随机分配?

package main

import (
    config2 "ab-proxy/config"
    "bytes"
    "fmt"
    "io/ioutil"
    "net/http"
    "net/http/httputil"
    "net/url"
    "strconv"
    "strings"
)

var config config2.ProxyConfig
var reverseProxy *httputil.ReverseProxy
var tests config2.Tests

func overwriteCookie(req *http.Request, cookie *http.Cookie) {
    // omitted for brevity, will replace a cookie header, instead of adding a second value
}

func parseRequestCookiesToAssignedTests(req *http.Request) map[string]string {
    // omitted for brevity, builds a map where the key is the identifier of the test, the value the assigned variant
}

func renderCookieForAssignedTests(assignedTests map[string]string) string {
    // omitted for brevity, builds a cookie string
}

func main () {
    var err error

    if  config, err = config2.LoadConfig(); err != nil {
        fmt.Println(err)

        return
    }

    if tests, err = config2.LoadTests(); err != nil {
        fmt.Println(err)

        return
    }

    upstreamUrl, _ := url.Parse("0.0.0.0:80")

    reverseProxy = httputil.NewSingleHostReverseProxy(upstreamUrl)
    reverseProxy.ModifyResponse = modifyResponse

    http.HandleFunc("/", handleRequest)

    if err := http.ListenAndServe("0.0.0.0:80", nil); err != nil {
        fmt.Println("Could not start proxy")
    }
}

func handleRequest(res http.ResponseWriter, req *http.Request) {
    assigned := parseRequestCookiesToAssignedTests(req)

    newCookies := make(map[string]string)

    for _, test := range tests.Entries {
        val, ok := assigned[test.Identifier]

        if ok {
            newCookies[test.Identifier] = val
        } else {
            newCookies[test.Identifier] = "not-assigned-yet" // this will be replaced by random variation assignment
        }
    }

    testCookie := http.Cookie{Name: config.Cookie.Name, Value: renderCookieForAssignedTests(newCookies)}

    // Add cookie to request to be sent to upstream
    overwriteCookie(req, &testCookie)

    // Add cookie to response to be returned to client
    http.SetCookie(res, &testCookie)

    reverseProxy.ServeHTTP(res, req)
}

func modifyResponse (response *http.Response) error {
    body, err := ioutil.ReadAll(response.Body)

    if err != nil {
        return  err
    }

    err = response.Body.Close()

    if err != nil {
        return err
    }

    response.Body = ioutil.NopCloser(bytes.NewReader(body))
    response.ContentLength = int64(len(body))
    response.Header.Set("Content-Length", strconv.Itoa(len(body)))

    return nil
}

【问题讨论】:

    标签: go proxy reverse-proxy


    【解决方案1】:

    使用标准的context.Context。这可以通过*http.Request 在您的处理程序中访问。并且该请求也可以通过*http.Response 参数访问modifyResponse

    在您的处理程序中:

    ctx := req.Context()
    // Set values, deadlines, etc.
    req = req.WithContext(ctx)
    reverseProxy.ServeHTTP(res, req)
    

    然后在modifyResponse:

    ctx := response.Request.Context()
    // fetch values, check for cancellation, etc
    

    【讨论】:

    • 啊,太棒了!即使是一个花哨的 IDE 也无法帮助我发现在 response 上什至有一个 .Request 属性,并且实际上有一个通用的 context.Context 类可以完全满足我的要求。这很有帮助,谢谢!
    猜你喜欢
    • 2011-08-06
    • 2018-10-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-26
    • 1970-01-01
    • 2013-07-10
    • 1970-01-01
    • 2016-07-26
    相关资源
    最近更新 更多