[英]Share context between handleFunc and modifyResponse in Go reverse proxy
I'm trying to learn Go and figured a nice little project would be an A/B testing proxy to put in front of a web server. 我正在尝试学习Go,并发现一个不错的小项目是放在Web服务器前的A / B测试代理。 Little did I know Go essentially offers a reverse proxy out of the box, so the setup was easy.
我几乎不知道Go本质上提供了开箱即用的反向代理,因此设置很容易。 I've got it to the point where I'm proxying traffic, but here's the thing, I have trouble implementing the actual functionality because wherever I have access to the response, I don't have access to assigned A/B test variations:
我已经到了代理流量的地步,但实际上,我无法实现实际的功能,因为无论我在哪里访问响应,都无法访问分配的A / B测试版本:
handleFunc
I'm assigning variations of each test to the request, so the upstream server can also be aware of it and use if for implementations in it's backend. handleFunc
我为请求分配了每个测试的变体,因此上游服务器也可以意识到这一点,并在后端的实现中使用if。 modifyResponse
function of httputil.ReverseProxy
to do the response mutation. modifyResponse
的功能httputil.ReverseProxy
做响应突变。 The problem is that I can't figure out how to share the assigned variations between the handleFunc
and modifyResponse
, without changing the upstream server. 问题是,我不知道如何在不更改上游服务器的情况下共享
handleFunc
和modifyResponse
之间分配的变体。 I'd like to be able to share this context (basically a map[string]string
somehow. 我希望能够共享此上下文(基本上是某种方式的
map[string]string
。
Here's a distilled version of my code, where my question basically is, how can modifyRequest
know about random assignments that happened in handleFunc
? 这是我的代码的
modifyRequest
版本,基本上我的问题是, 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
}
Use a standard context.Context
. 使用标准
context.Context
。 This is accessible in your handler via the *http.Request
. 这可以在您的处理程序中通过
*http.Request
。 And the request is also accessible via the *http.Response
argument to modifyResponse
. 而且该请求也可以通过
*http.Response
参数来modifyResponse
。
In your handler: 在您的处理程序中:
ctx := req.Context()
// Set values, deadlines, etc.
req = req.WithContext(ctx)
reverseProxy.ServeHTTP(res, req)
Then in modifyResponse
: 然后在
modifyResponse
:
ctx := response.Request.Context()
// fetch values, check for cancellation, etc
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.