|
3 | 3 | package middleware
|
4 | 4 |
|
5 | 5 | import (
|
| 6 | + "context" |
6 | 7 | "fmt"
|
7 | 8 | "net/http"
|
8 | 9 | "net/http/httputil"
|
| 10 | + "strings" |
9 | 11 |
|
10 | 12 | "github.com/labstack/echo/v4"
|
11 | 13 | )
|
12 | 14 |
|
| 15 | +// StatusCodeContextCanceled is HTTP status code for when client closed connection |
| 16 | +// regrettably, there is no standard error code for "client closed connection", but |
| 17 | +// for historical reasons we can use a code that a lot of people are already using; |
| 18 | +// using 5xx is problematic for users; |
| 19 | +const StatusCodeContextCanceled = 499 |
| 20 | + |
13 | 21 | func proxyHTTP(tgt *ProxyTarget, c echo.Context, config ProxyConfig) http.Handler {
|
14 | 22 | proxy := httputil.NewSingleHostReverseProxy(tgt.URL)
|
15 | 23 | proxy.ErrorHandler = func(resp http.ResponseWriter, req *http.Request, err error) {
|
16 | 24 | desc := tgt.URL.String()
|
17 | 25 | if tgt.Name != "" {
|
18 | 26 | desc = fmt.Sprintf("%s(%s)", tgt.Name, tgt.URL.String())
|
19 | 27 | }
|
20 |
| - httpError := echo.NewHTTPError(http.StatusBadGateway, fmt.Sprintf("remote %s unreachable, could not forward: %v", desc, err)) |
21 |
| - httpError.Internal = err |
22 |
| - c.Set("_error", httpError) |
| 28 | + // if the client canceled the request (usually this means they closed |
| 29 | + // the connection, so they won't see any response), we can report it |
| 30 | + // as a client error (4xx) and not a server error (5xx); unfortunately |
| 31 | + // the Go standard library, at least at time of writing in late 2020, |
| 32 | + // obnoxiously wraps the exported, standard context.Canceled error with |
| 33 | + // an unexported garbage value that we have to do a substring check for: |
| 34 | + // https://github.com/golang/go/blob/6965b01ea248cabb70c3749fd218b36089a21efb/src/net/net.go#L416-L430 |
| 35 | + if err == context.Canceled || strings.Contains(err.Error(), "operation was canceled") { |
| 36 | + httpError := echo.NewHTTPError(StatusCodeContextCanceled, fmt.Sprintf("client closed connection: %v", err)) |
| 37 | + httpError.Internal = err |
| 38 | + c.Set("_error", httpError) |
| 39 | + } else { |
| 40 | + httpError := echo.NewHTTPError(http.StatusBadGateway, fmt.Sprintf("remote %s unreachable, could not forward: %v", desc, err)) |
| 41 | + httpError.Internal = err |
| 42 | + c.Set("_error", httpError) |
| 43 | + } |
23 | 44 | }
|
24 | 45 | proxy.Transport = config.Transport
|
25 | 46 | proxy.ModifyResponse = config.ModifyResponse
|
|
0 commit comments