英文:
cancel a web request and handle errors inside the ReverseProxy Director function
问题
我在想是否可以在ReverseProxy.Director
函数内部取消一个网络请求或向客户端发送内部响应。
假设我们执行了某些会抛出错误的操作,或者有其他原因不希望转发请求。
proxy := &httputil.ReverseProxy{
Director: func(r *http.Request) {
err := somethingThatThrows()
},
}
http.Handle("/", proxy)
下面是一种解决方案,但它不如上述使用代理的方式简洁。我也不确定应该在多大程度上修改请求。似乎应该在Director
函数中进行这样的操作。
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
err := somethingThatThrows()
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
proxy.ServeHTTP(w, r)
})
英文:
I am wondering if it would be possible to cancel a web request or send an internal response to the client inside the ReverseProxy.Director
function.
Suppose we do something that throws an error, or we have other reason to not forward the request.
proxy := &httputil.ReverseProxy{
Director: func(r *http.Request) {
err := somethingThatThrows()
},
}
http.Handle("/", proxy)
A solution to this might be the below, but it's not as neat as the above way to use the proxy. I am also not sure to which degree the request should be modified that way. The director seems to be the place to do that.
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
err := somethingThatThrows()
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
proxy.ServeHTTP(w, r)
})
答案1
得分: 3
如果可能的话,可以取消一个网络请求。你可以取消传递给Director
函数的请求,但是有一些细节需要考虑:
- 取消请求的正确方式是取消其上下文。
- 你不能取消没有自己设置(截止时间|超时|取消函数)的上下文 → 也就是说,你必须有访问
cancel
函数的权限 → 也就是说,你不能取消由其他人创建的父级上下文。 - 传递给
Director
函数的*http.Request
是原始请求的克隆。
根据上述要点,你可以将Director
中的请求替换为具有可取消上下文的另一个请求。代码示例如下:
proxy := &httputil.ReverseProxy{
Director: func(req *http.Request) {
// 创建可取消上下文,并重新设置请求
ctx, cancel := context.WithCancel(req.Context())
*req = *req.WithContext(ctx)
err := somethingThatThrows()
if err != nil {
cancel()
return
}
},
}
然后,上述代码本身不会执行任何其他操作。应该发生的是,实现了http.RoundTripper
的httputil.ReverseProxy.Transport
函数在实际发送任何内容到上游服务之前,检查请求上下文是否被取消。
Director
的文档说明如下:
Director
必须是一个将请求修改为使用Transport
发送的新请求的函数。
当未提供Transport
时,它将回退到http.DefaultTransport
,当上下文被取消时会中止请求。当前代码(Go 1.17.5)如下所示:
select {
case <-ctx.Done():
req.closeBody()
return nil, ctx.Err()
default:
}
如果你提供自己的http.RoundTripper
实现,你可能希望自己实现该行为。还要记住,如果上下文不可取消,上下文完成通道为nil
,因此你必须设置取消函数并调用cancel()
,以便让该select
运行“完成”分支。
或者在ReverseProxy.Director
内部向客户端发送内部响应。
根据上述文档中的同一引用,你不应该在Director
函数内部向http.ResponseWriter
写入内容,即使你在其周围进行了封闭。正如你所看到的,Director
本身并没有将http.ResponseWriter
作为参数传递,这应该已经是一个不言自明的细节。
如果你想在无法转发请求的情况下指定其他行为,并且假设无论http.RoundTripper
的实现如何,当请求上下文被取消时返回error
,你可以提供自己的ReverseProxy.ErrorHandler
函数:
proxy.ErrorHandler = func(writer http.ResponseWriter, request *http.Request, err error) {
// 检查错误
// 向writer写入内容
}
当Transport
返回错误时,包括来自取消请求的错误时,将调用ErrorHandler
,并且它确实将http.ResponseWriter
作为参数。
英文:
> if it would be possible to cancel a web request [...]
You can cancel the request that is passed to the Director
function, BUT there are some details to consider:
- the correct way to cancel a request is to cancel its context
- you can not cancel contexts where you didn't set a (deadline|timeout|cancelfunc) yourself → i.e. you must have access to the
cancel
function → i.e. you can't cancel parent contexts created by someone else. - the
*http.Request
passed toDirector
function is a clone of the original request
Based on the points above, you can replace the request in the Director
with another one that has a cancellable context. It may look like the following:
proxy := &httputil.ReverseProxy{
Director: func(req *http.Request) {
// create a cancellable context, and re-set the request
ctx, cancel := context.WithCancel(req.Context())
*req = *req.WithContext(ctx)
err := somethingThatThrows()
if err != nil {
cancel()
return
}
},
}
Then the code above doesn't do anything else by itself. What should happen is that the httputil.ReverseProxy.Transport
function, which implements http.RoundTripper
checks whether the request context is cancelled, before actually send anything to the upstream service.
The documentation of Director
states:
> Director must be a function which modifies the request into a new request to be sent using Transport.
When the Transport
is not provided, it will fall back to http.DefaultTransport
, which aborts the request when the context is cancelled. The current code (Go 1.17.5) looks like:
select {
case <-ctx.Done():
req.closeBody()
return nil, ctx.Err()
default:
}
If you provide your own implementation of http.RoundTripper
you may want to implement that behavior yourself. Remember also that the context done channel is nil
if it's not cancellable, so you have to set a cancel func and call cancel()
in order to have that select
run the "done" case.
<hr>
> or send an internal response to the client inside the ReverseProxy.Director
Based on the same quote above docs, you should not write to the http.ResponseWriter
from within the Director
function — assuming you are even closing around it. As you can see the Director
itself doesn't get the http.ResponseWriter
as an argument, and this should already be a self-explanatory detail.
If you want to specify some other behavior in case the request can't be forwarded, and assuming that whatever implementation of http.RoundTripper
is returning error
when the req context is cancelled, you can provide your ReverseProxy.ErrorHandler
function:
proxy.ErrorHandler = func(writer http.ResponseWriter, request *http.Request, err error) {
// inspect err
// write to writer
}
The ErrorHandler
will be invoked when Transport
returns error, including when the error comes from a cancelled request, and it does have http.ResponseWriter
as an argument.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论