Go 使用自定义http.ResponseWriter根据代理请求的响应写入cookie?

Go 使用自定义http.ResponseWriter根据代理请求的响应写入cookie?,go,Go,我的原始问题被标记为问题的副本。我没有幸实现它,并且怀疑我的问题被误解了,所以我的问题结束了,我从一个更具体的问题开始 我试图在请求中设置一个基于响应头的cookie,该响应头来自反向代理的中间件 以下是工作流程: 用户请求 Go应用程序使用ReverseProxy将该请求代理给 baz.com设置响应标题X-FOO Go应用程序通过使用X-FOO响应头的值设置MYAPPFOOcookie来修改响应 cookie将写入用户的浏览器 有人建议使用一个自定义的http.ResponseWrite

我的原始问题被标记为问题的副本。我没有幸实现它,并且怀疑我的问题被误解了,所以我的问题结束了,我从一个更具体的问题开始

我试图在请求中设置一个基于响应头的cookie,该响应头来自反向代理的中间件

以下是工作流程:

  • 用户请求
  • Go应用程序使用ReverseProxy将该请求代理给
  • baz.com设置响应标题
    X-FOO
  • Go应用程序通过使用
    X-FOO
    响应头的值设置
    MYAPPFOO
    cookie来修改响应
  • 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
实现这一点,但我确实希望通过中间件来实现这一点,以保持从配置动态创建代理的代码干净。(不在示例代码中)

来自有关方法的文档: (增加重点)

  • Header()http.Header

    调用WriteHeader(或Write)后更改标头映射没有任何意义 效果除非修改的标题是拖车

  • WriteHeader(statusCode-int)

    WriteHeader发送具有提供状态的HTTP响应头 代码

  • 写入([]字节)(int,错误)

    如果尚未调用WriteHeader,则Write调用 WriteHeader(http.StatusOK)在写入数据之前

这应该突出说明为什么不能在
next.ServeHTTP(w,r)
调用后设置cookie,这是因为该调用执行的中间件链中的一个处理程序正在直接或间接调用
WriteHeader
Write

因此,为了能够在
next.ServeHTTP(w,r)
调用后设置cookie,您需要确保中间件链中的任何处理程序都不会在原始
http.ResponseWriter
实例上调用
WriteHeader
Write
。一种方法是将原始实例包装在自定义的
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)
        }
    })
}
从有关方法的文档中: (增加重点)

  • Header()http.Header

    调用WriteHeader(或Write)后更改标头映射没有任何意义 效果除非修改的标题是拖车

  • WriteHeader(statusCode-int)

    WriteHeader发送具有提供状态的HTTP响应头 代码

  • 写入([]字节)(int,错误)

    如果尚未调用WriteHeader,则Write调用 WriteHeader(http.StatusOK)在写入数据之前

这应该突出说明为什么不能在
next.ServeHTTP(w,r)
调用后设置cookie,这是因为该调用执行的中间件链中的一个处理程序正在直接或间接调用
WriteHeader
Write

因此,为了能够在
next.ServeHTTP(w,r)
调用后设置cookie,您需要确保中间件链中的任何处理程序都不会在原始
http.ResponseWriter
实例上调用
WriteHeader
Write
。一种方法是将原始实例包装在自定义的
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)
        }
    })
}

您确实理解为什么在调用
next.ServeHTTP
之前
setCookie
工作,但在调用之后不工作吗?是的。我只是强调我的难题,我无法将cookie设置为可以获取所需的头,反之亦然。好的,所以您知道,一旦您向客户端发送响应,我猜这就是
下一步要做的事情。ServeHTTP(w,r)
完成后,您就不能再发送额外的头了(除了预告片,但我相信不允许有定置Cookie)。因此,您要做的是在编写响应之前编写Set-Cookie标头。我能想到的唯一最符合您要求的方法是使用自定义响应编写器,该编写器可将响应的编写推迟到您完成之后。类似这样的内容:(尚未测试,但您了解要点)。基本上,你要做的是在你写响应的主体(或状态码)之前先写标题,这是在名称中。你如何做取决于你,但客户响应编写器肯定是完成它的一种方法。“我的难题是,我无法将cookie设置为可以获取标题的位置”请记住,您可以将cookie设置为
值,就像您可以读取
值一样,只是在通过HTTP发送响应后,您设置为
的任何内容都不会神奇地出现在已发送的响应中。这一点在:
在调用WriteHeader(或Write)后更改标头映射无效,除非修改的标头是拖车。