Совместное использование контекста handleFunc и modifyResponse в обратном прокси Go - PullRequest
0 голосов
/ 17 марта 2019

Я пытаюсь изучить Go и подумал, что хороший маленький проект - это A / B-тестовый прокси для размещения перед веб-сервером. Мало ли я знал, что Go по сути предлагает обратный прокси-сервер из коробки, поэтому настройка была простой. Я получил его до того момента, когда я проксирую трафик, но вот в чем дело, у меня проблемы с реализацией реальной функциональности, потому что там, где у меня есть доступ к ответу, у меня нет доступа к назначенным вариантам A / B-теста:

  • В handleFunc я присваиваю вариации каждого теста запросу, чтобы вышестоящий сервер также мог знать об этом и использовать if для реализаций в своем бэкэнде.
  • Я устанавливаю cookie со всеми тестами и вариантами, как по запросу, который передается в апстрим, так и по ответу, который возвращается клиенту.
  • Тесты, состоящие из пары "найти / заменить", будут выполнять мутации в теле ответа после того, как ответ возвращается с вышестоящего сервера.
  • Я пытаюсь использовать modifyResponse функцию httputil.ReverseProxy, чтобы выполнить мутацию ответа.

Проблема в том, что я не могу понять, как разделить назначенные варианты между handleFunc и 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
}

1 Ответ

2 голосов
/ 17 марта 2019

Используйте стандарт 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
...