Использование пользовательского http.ResponseWriter для записи файлов cookie на основе ответа от прокси-запроса? - PullRequest
0 голосов
/ 09 ноября 2019

Мой оригинальный вопрос здесь помечен как дубликат этого вопроса. Мне не повезло реализовать его, и я подозреваю, что моя проблема неправильно понята, поэтому с закрытым вопросом я начинаю заново с более конкретного вопроса.

Я пытаюсь установить cookie на основе заголовка ответа отв промежуточном программном обеспечении в запросе с обратным прокси*http://baz.com

baz.com устанавливает заголовок ответа X-FOO Приложение Go изменяет ответ, устанавливая cookie MYAPPFOO со значением заголовка ответа X-FOO Файл cookie записывается в браузер пользователя

. Было предложено, что пользовательский http.ResponseWriter будет работать, но после попытки поиска дополнительной информации неясно, как к этому подойти.

Поскольку я не могу понять концепцию настраиваемого ResponseWriter для моего варианта использования, я опубликую код, который более точно демонстрирует, что я пытался сделать в момент, когда я застрял:

package main

import (

    "github.com/gorilla/mux"
    "log"
    "net/http"
    "net/http/httputil"
    "net/url"
)

func setCookie(w http.ResponseWriter, name string, value string) {
    ...
    http.SetCookie(w, &cookie)
}

func handler(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

        // setCookie() works here
        // but I cannot access w.Header().Get("X-FOO")

        next.ServeHTTP(w, r)

        // I can access w.Header().Get("X-FOO") here
        // but setCookie() does not cookie the user's browser

        // If I could do it all in one place, this is what I would do:
        if r.Method == "POST" && r.URL.String() == "/login" {
            foo := w.Header().Get("X-FOO")
            setCookie(w, "MYAPPFOO", foo)

        }
    })
}

func main() {

    r := mux.NewRouter()
    r.Use(handler)
    proxy := httputil.NewSingleHostReverseProxy("https://baz.example.com/")
    r.PathPrefix("/").Handler(proxy)
    log.Fatal(http.ListenAndServe(":9001", r))
}

Как примечание, я смог сделать эту работу с ReverseProxy.ModifyResponse, как рекомендовано в комментариях к моему последнему вопросу, но я бы очень хотел добиться этого с помощью промежуточного программного обеспечения, чтобы сохранить код, который динамически создает прокси из конфигурации, чистым. (не в примере кода)

1 Ответ

1 голос
/ 09 ноября 2019

Из документации по http.ResponseWriter методы: (выделение добавлено)

  • Header() http.Header:

    Изменение карты заголовков после того, как вызов WriteHeader (или Write) не действует , если только измененные заголовки не являются трейлерами.

  • WriteHeader(statusCode int):

    WriteHeader отправляет HTTP-заголовок ответа с предоставленным кодом состояния.

  • Write([]byte) (int, error):

    Если WriteHeader еще не был вызван, Write вызывает WriteHeader (http.StatusOK) перед записью данных.

Это должновыделите причину, по которой вы не можете установить cookie после вызова next.ServeHTTP(w, r), заключающегося в том, что один из обработчиков в цепочке промежуточного программного обеспечения, выполняемой этим вызовом, вызывает либо WriteHeader, либо Write прямо или косвенно.

Таким образом, чтобы иметь возможность установить cookie после next.ServeHTTP(w, r) вызова, необходимо убедиться, что ни один из обработчиков в цепочке промежуточного программного обеспечения не вызывает WriteHeader или Write в исходном http.ResponseWriter экземпляре. Один из способов сделать это - обернуть исходный экземпляр в пользовательскую реализацию http.ResponseWriter, которая будет откладывать запись ответа до тех пор, пока вы не закончите с установкой cookie.


Например, что-то вроде этого:

type responsewriter struct {
    w    http.ResponseWriter
    buf  bytes.Buffer
    code int
}

func (rw *responsewriter) Header() http.Header {
    return rw.w.Header()
}

func (rw *responsewriter) WriteHeader(statusCode int) {
    rw.code = statusCode
}

func (rw *responsewriter) Write(data []byte) (int, error) {
    return rw.buf.Write(data)
}

func (rw *responsewriter) Done() (int64, error) {
    if rw.code > 0 {
        rw.w.WriteHeader(rw.code)
    }
    return io.Copy(rw.w, &rw.buf)
}

И вы бы использовали это так в своем промежуточном программном обеспечении:

func handler(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        rw := &responsewriter{w: w}
        next.ServeHTTP(rw, r)

        if r.Method == "POST" && r.URL.String() == "/login" {
            foo := rw.Header().Get("X-FOO")
            setCookie(rw, "MYAPPFOO", foo)
        }

        if _, err := rw.Done(); err != nil {
            log.Println(err)
        }
    })
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...