如果已经设置了标头,如何使用自定义的HTTP处理程序/中间件?

huangapple go评论70阅读模式
英文:

How to use custom http handlers/middleware if headers already set?

问题

我正在尝试在Go语言中链接HTTP处理程序以提供一些附加功能,就像这样:

package router

import (
    // snip
    "github.com/gorilla/mux"
    "github.com/gorilla/handlers"
    "net/http"
)

// snip

r := mux.NewRouter()
/* 路由代码 */
var h http.Handler
h = r
if useGzip {
    h = handlers.CompressHandler(h)
}
if useLogFile {
    fn := pathToLog
    accessLog, err := os.OpenFile(fn, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0666)
    if err != nil {
        panic(err)
    }
    h = handlers.CombinedLoggingHandler(accessLog, h)
}

// 等等...

问题是,如果gorilla/mux路由器指向的控制器中已经设置了任何HTTP头(例如,w.WriteHeader(404)w.Header().Set("Content-Type", "application/json")),这会导致任何试图设置或添加自己头部的“包装”处理程序静默失败,比如压缩处理程序。除非我在某个地方忘记捕获错误,否则我看不到任何错误,但浏览器会收到无效的响应。

有没有一种优雅的方式来处理这个问题,而不是只是将头部存储在某个地方,然后让最终的处理程序来写入它们?如果可能的话,我希望避免重写处理程序的代码。

英文:

I'm trying to chain HTTP handlers in go to provide some added functionality, like this:

package router

import (
    // snip
    "github.com/gorilla/mux"
    "github.com/gorilla/handlers"
	"net/http"
)

// snip

r := mux.NewRouter()
/* routing code */
var h http.Handler
h = r
if useGzip {
	h = handlers.CompressHandler(h)
}
if useLogFile {
	fn := pathToLog
	accessLog, err := os.OpenFile(fn, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0666)
	if err != nil {
		panic(err)
	}
	h = handlers.CombinedLoggingHandler(accessLog, h)
}

// etc...

The problem is, if any HTTP headers are already set by one of the controllers that the gorilla/mux router points to (for example, w.WriteHeader(404) or w.Header().Set("Content-Type", "application/json")) - this silently breaks any "wrapper" handler trying to set or add its own headers, like the compress handler. I can't see any errors, unless I forgot to catch one somewhere, but the browser gets an invalid response.

Is there any graceful way to deal with this, short of just stashing the headers somewhere and then leaving the final handler to write them? It seems like that would mean rewriting the handlers' code, which I'd love to avoid if at all possible.

答案1

得分: 2

一旦调用w.WriteHeader(404),头部信息就会发送出去,所以你不能再添加其他内容了。
最好的方法是缓存状态码,在处理链的最后写入。

例如,你可以为http.ResponseWriter提供自己的包装器,重新实现WriteHeader()来保存状态值。然后添加一个名为Commit()的方法来实际写入状态码。
在最后一个处理程序中调用Commit()。当然,你需要确定哪个处理程序是最后一个。

英文:

Once you call w.WriteHeader(404), the header goes on a wire. So you can't add to it anymore.
Best way you can do is to buffer status code and write it at the end of a chain.

For example, you can provide your own wrapper for http.ResponseWriter that would re-implement WriteHeader() to save status value. Then add method Commit() to actually write it.
Call Commit() in the last handler. You have to determine somehow which handler is last, of course.

答案2

得分: 1

我遇到了同样的静默失败行为。但只在我使用WritheHeader设置除StatusOK以外的状态码的处理程序中出现问题。我认为问题出在CompressHandler的这部分代码:

if h.Get("Content-Type") == "" {
	h.Set("Content-Type", http.DetectContentType(b))
}

当我在自己的处理程序中显式设置内容类型时,问题似乎得到了解决:

w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.WriteHeader(code)
英文:

I experienced the same silently-failing behaviour. But only in handlers where I did WritheHeader to set a status code other than StatusOK. I think things went wrong in this part of CompressHandler:

if h.Get("Content-Type") == "" {
	h.Set("Content-Type", http.DetectContentType(b))
}

Which appears to be resolved when explicitly setting the content type in my own handler:

w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.WriteHeader(code)

huangapple
  • 本文由 发表于 2014年12月23日 07:28:18
  • 转载请务必保留本文链接:https://go.coder-hub.com/27612005.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定