Go ReverseProxy 处理重定向错误: “http: invalid Read on closed Body”

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

Go ReverseProxy handle redirect error: "http: invalid Read on closed Body"

问题

这是我的代码,当原始服务器返回302时,反向代理会修改请求并重新提供服务,但如果请求带有主体,它会打印一个错误:http: invalid Read on closed Body

  1. proxy = httputil.NewSingleHostReverseProxy(url)
  2. proxy.ErrorLog = proxyLogger
  3. proxy.ModifyResponse = func(response *http.Response) error {
  4. if response.StatusCode > 300 && response.StatusCode < 400 {
  5. location, err := response.Location()
  6. if err != nil {
  7. return err
  8. }
  9. return &RedirectErr{location: location}
  10. }
  11. return nil
  12. }
  13. var rewindBody func(r *http.Request)
  14. proxy.Director = func(request *http.Request) {
  15. if redirect := request.Header.Get("redirect"); redirect != "" {
  16. request.Header.Del("redirect")
  17. u, _ := urlpkg.Parse(redirect)
  18. rewriteRequestURL(request, u)
  19. return
  20. }
  21. rewriteRequestURL(request, url)
  22. }
  23. proxy.ErrorHandler = func(writer http.ResponseWriter, request *http.Request, err error) {
  24. var redirect *RedirectErr
  25. if redirect, ok = err.(*RedirectErr); !ok {
  26. proxy.ErrorLog.Printf("http: proxy error: %v", err)
  27. writer.WriteHeader(http.StatusBadGateway)
  28. } else {
  29. request.Header.Set("redirect", redirect.location.String())
  30. proxy.ServeHTTP(writer, request)
  31. return
  32. }
  33. }

我尝试使用fakeCloseReadCloser,但出现了新的错误:"error: net/http: HTTP/1.x transport connection broken: http: ContentLength=75 with Body length 0"

英文:

here is my code, when origin server return 302, reverse proxy will modify the request and serve it again, but if the request with a body, it print an error: http: invalid Read on closed Body

  1. proxy = httputil.NewSingleHostReverseProxy(url)
  2. proxy.ErrorLog = proxyLogger
  3. proxy.ModifyResponse = func(response *http.Response) error {
  4. if response.StatusCode &gt; 300 &amp;&amp; response.StatusCode &lt; 400 {
  5. location, err := response.Location()
  6. if err != nil {
  7. return err
  8. }
  9. return &amp;RedirectErr{location: location}
  10. }
  11. return nil
  12. }
  13. var rewindBody func(r *http.Request)
  14. proxy.Director = func(request *http.Request) {
  15. if redirect := request.Header.Get(&quot;redirect&quot;); redirect != &quot;&quot; {
  16. request.Header.Del(&quot;redirect&quot;)
  17. u, _ := urlpkg.Parse(redirect)
  18. rewriteRequestURL(request, u)
  19. return
  20. }
  21. rewriteRequestURL(request, url)
  22. }
  23. proxy.ErrorHandler = func(writer http.ResponseWriter, request *http.Request, err error) {
  24. var redirect *RedirectErr
  25. if redirect, ok = err.(*RedirectErr); !ok {
  26. proxy.ErrorLog.Printf(&quot;http: proxy error: %v&quot;, err)
  27. writer.WriteHeader(http.StatusBadGateway)
  28. } else {
  29. request.Header.Set(&quot;redirect&quot;, redirect.location.String())
  30. proxy.ServeHTTP(writer, request)
  31. return
  32. }
  33. }

I try to use fakeCloseReadCloser, but it comes new error: "error: net/http: HTTP/1.x transport connection broken: http: ContentLength=75 with Body length 0"

答案1

得分: 1

最后,我解决了这个问题。我们需要确保请求体不关闭,并且在第二次传输之前需要倒回请求体。

  1. // 在传输之后,请求体将关闭,
  2. // 因此,如果我们需要重定向它,我们必须包装请求体并确保它不会关闭
  3. // 我们将在完成后处理它。
  4. // 参见此问题:https://github.com/flynn/flynn/issues/872
  5. type fakeCloseReadCloser struct {
  6. io.ReadCloser
  7. }
  8. func (w *fakeCloseReadCloser) Close() error {
  9. return nil
  10. }
  11. func (w *fakeCloseReadCloser) RealClose() error {
  12. if w.ReadCloser == nil {
  13. return nil
  14. }
  15. return w.ReadCloser.Close()
  16. }
  17. type Proxy struct {
  18. *httputil.ReverseProxy
  19. }
  20. func (p *Proxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
  21. req.Body = &fakeCloseReadCloser{req.Body}
  22. // 请求体只能读取一次,因此我们使用此函数使请求体在第二次转发时可用
  23. if req.Body != nil {
  24. bodyBytes, _ := io.ReadAll(req.Body)
  25. _ = req.Body.(*fakeCloseReadCloser).RealClose()
  26. req.Body = &fakeCloseReadCloser{io.NopCloser(bytes.NewBuffer(bodyBytes))}
  27. req.GetBody = func() (io.ReadCloser, error) {
  28. body := io.NopCloser(bytes.NewBuffer(bodyBytes))
  29. return body, nil
  30. }
  31. }
  32. p.ReverseProxy.ServeHTTP(rw, req)
  33. _ = req.Body.(*fakeCloseReadCloser).RealClose()
  34. }
  35. // 根据给定的分片返回代理
  36. func (pp *ProxyPool) Get(url *urlpkg.URL) *Proxy {
  37. pp.mutex.Lock()
  38. defer pp.mutex.Unlock()
  39. p, ok := pp.pool[url.String()]
  40. if !ok {
  41. // 当原始目标服务器返回302时,
  42. // proxy.ModifyResponse将返回一个包含位置的自定义重定向错误,
  43. // proxy.ErrorHandler捕获错误并修改请求,
  44. // 添加一个“redirect”头字段,并再次Serve它。
  45. // 此时,proxy.Director将检测到请求头中的“redirect”字段
  46. // 并将请求发送到最终目标服务器。
  47. rp := httputil.NewSingleHostReverseProxy(url)
  48. px := &Proxy{ReverseProxy: rp}
  49. px.ErrorLog = proxyLogger
  50. px.ModifyResponse = func(response *http.Response) error {
  51. if response.StatusCode > 300 && response.StatusCode < 400 {
  52. location, err := response.Location()
  53. if err != nil {
  54. return err
  55. }
  56. return &RedirectErr{location: location}
  57. }
  58. return nil
  59. }
  60. px.Director = func(request *http.Request) {
  61. if redirect := request.Header.Get("redirect"); redirect != "" {
  62. request.Header.Del("redirect")
  63. u, _ := urlpkg.Parse(redirect)
  64. // 倒回
  65. if request.GetBody != nil {
  66. b, _ := request.GetBody()
  67. request.Body = b
  68. }
  69. rewriteRequestURL(request, u)
  70. return
  71. }
  72. rewriteRequestURL(request, url)
  73. }
  74. px.ErrorHandler = func(writer http.ResponseWriter, request *http.Request, err error) {
  75. var redirect *RedirectErr
  76. if redirect, ok = err.(*RedirectErr); !ok {
  77. p.ErrorLog.Printf("http: proxy error: %v", err)
  78. writer.WriteHeader(http.StatusBadGateway)
  79. } else {
  80. request.Header.Set("redirect", redirect.location.String())
  81. px.ReverseProxy.ServeHTTP(writer, request)
  82. return
  83. }
  84. }
  85. pp.pool[url.Host] = px
  86. p = px
  87. }
  88. return p
  89. }

希望对你有帮助!

英文:

Finally , I resoled this. We need ensure that the request body not close.
and we should rewind the body before second transport.

  1. // after transport, the request body will close,
  2. // so if we need redirect it, we have to wrap the body and ensure that it won&#39;t be close
  3. // we will handle it after we finished.
  4. // see this issue: https://github.com/flynn/flynn/issues/872
  5. type fakeCloseReadCloser struct {
  6. io.ReadCloser
  7. }
  8. func (w *fakeCloseReadCloser) Close() error {
  9. return nil
  10. }
  11. func (w *fakeCloseReadCloser) RealClose() error {
  12. if w.ReadCloser == nil {
  13. return nil
  14. }
  15. return w.ReadCloser.Close()
  16. }
  17. type Proxy struct {
  18. *httputil.ReverseProxy
  19. }
  20. func (p *Proxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
  21. req.Body = &amp;fakeCloseReadCloser{req.Body}
  22. // The body can only be read once, so we use this func to make the body available on the second forwarding
  23. if req.Body != nil {
  24. bodyBytes, _ := io.ReadAll(req.Body)
  25. _ = req.Body.(*fakeCloseReadCloser).RealClose()
  26. req.Body = &amp;fakeCloseReadCloser{io.NopCloser(bytes.NewBuffer(bodyBytes))}
  27. req.GetBody = func() (io.ReadCloser, error) {
  28. body := io.NopCloser(bytes.NewBuffer(bodyBytes))
  29. return body, nil
  30. }
  31. }
  32. p.ReverseProxy.ServeHTTP(rw, req)
  33. _ = req.Body.(*fakeCloseReadCloser).RealClose()
  34. }
  35. // Get returns a proxy for the given shard.
  36. func (pp *ProxyPool) Get(url *urlpkg.URL) *Proxy {
  37. pp.mutex.Lock()
  38. defer pp.mutex.Unlock()
  39. p, ok := pp.pool[url.String()]
  40. if !ok {
  41. // When the original target server returns 302,
  42. // proxy.ModifyResponse will return a customized redirect error containing location,
  43. // proxy.ErrorHandler catches the error and modifies the request,
  44. // adds a &quot;redirect&quot; header field to it, and Serve it again.
  45. // At this point proxy.Director will detect the &quot;redirect&quot; field in the request header
  46. // and send the request to the final target server.
  47. rp := httputil.NewSingleHostReverseProxy(url)
  48. px := &amp;Proxy{ReverseProxy: rp}
  49. px.ErrorLog = proxyLogger
  50. px.ModifyResponse = func(response *http.Response) error {
  51. if response.StatusCode &gt; 300 &amp;&amp; response.StatusCode &lt; 400 {
  52. location, err := response.Location()
  53. if err != nil {
  54. return err
  55. }
  56. return &amp;RedirectErr{location: location}
  57. }
  58. return nil
  59. }
  60. px.Director = func(request *http.Request) {
  61. if redirect := request.Header.Get(&quot;redirect&quot;); redirect != &quot;&quot; {
  62. request.Header.Del(&quot;redirect&quot;)
  63. u, _ := urlpkg.Parse(redirect)
  64. // rewind
  65. if request.GetBody != nil {
  66. b, _ := request.GetBody()
  67. request.Body = b
  68. }
  69. rewriteRequestURL(request, u)
  70. return
  71. }
  72. rewriteRequestURL(request, url)
  73. }
  74. px.ErrorHandler = func(writer http.ResponseWriter, request *http.Request, err error) {
  75. var redirect *RedirectErr
  76. if redirect, ok = err.(*RedirectErr); !ok {
  77. p.ErrorLog.Printf(&quot;http: proxy error: %v&quot;, err)
  78. writer.WriteHeader(http.StatusBadGateway)
  79. } else {
  80. request.Header.Set(&quot;redirect&quot;, redirect.location.String())
  81. px.ReverseProxy.ServeHTTP(writer, request)
  82. return
  83. }
  84. }
  85. pp.pool[url.Host] = px
  86. p = px
  87. }
  88. return p
  89. }

huangapple
  • 本文由 发表于 2023年7月13日 11:50:31
  • 转载请务必保留本文链接:https://go.coder-hub.com/76675783.html
匿名

发表评论

匿名网友

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

确定