go - 在 Go 反向代理中的 handleFunc 和 modifyResponse 之间共享上下文
问题描述
我正在尝试学习 Go,并认为一个不错的小项目将是放置在 Web 服务器前面的 A/B 测试代理。我几乎不知道 Go 本质上提供了一个开箱即用的反向代理,所以设置很简单。我已经到了代理流量的地步,但事情就是这样,我在实现实际功能时遇到了麻烦,因为无论我在哪里可以访问响应,我都无法访问分配的 A/B 测试变体:
- 在
handleFunc
我将每个测试的变体分配给请求时,上游服务器也可以知道它并将 if 用于其后端的实现。 - 我正在设置一个包含所有测试和变体的 cookie,包括代理到上游的请求和返回给客户端的响应。
- 由查找/替换对组成的测试将在响应从上游服务器返回后对响应主体进行突变。
- 我正在尝试使用 的
modifyResponse
功能httputil.ReverseProxy
来进行响应突变。
问题是我无法弄清楚如何在handleFunc
and之间共享分配的变体modifyResponse
,而不更改上游服务器。我希望能够分享这个上下文(基本上是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
}
解决方案
使用标准context.Context
。这可以在您的处理程序中通过*http.Request
. 并且该请求也可以通过 to 的*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
推荐阅读
- batch-file - 使用批处理脚本更新属性
- javascript - 仅在 Javascript 中更新时从 txt 文件中获取值
- oracle - PLS-00049:错误的绑定变量“NEW.NEW_SKILL_DESC”
- google-apps-script - DriveApp.getFolders() 例外:所需权限:https://www.googleapis.com/auth/drive
- azure - 有没有办法以编程方式重新启动 azure 函数
- kubernetes - kubernetes:如何为 ConfigMap 文件导出现有的环境变量
- javascript - node_modules中包的多个版本,可以吗?
- view - 将多个模型传递给 ASP.NET MVC 中的视图
- bash - 如何在 bash 中将空分隔的字符串通过管道传输到数组中?
- c++ - Clang 格式使用连续缩进缩进内部初始化程序