(c *echo.Context, tgt *ProxyTarget, config ProxyConfig)
| 420 | const StatusCodeContextCanceled = 499 |
| 421 | |
| 422 | func proxyHTTP(c *echo.Context, tgt *ProxyTarget, config ProxyConfig) http.Handler { |
| 423 | proxy := httputil.NewSingleHostReverseProxy(tgt.URL) |
| 424 | proxy.ErrorHandler = func(resp http.ResponseWriter, req *http.Request, err error) { |
| 425 | desc := tgt.URL.String() |
| 426 | if tgt.Name != "" { |
| 427 | desc = fmt.Sprintf("%s(%s)", tgt.Name, tgt.URL.String()) |
| 428 | } |
| 429 | // If the client canceled the request (usually by closing the connection), we can report a |
| 430 | // client error (4xx) instead of a server error (5xx) to correctly identify the situation. |
| 431 | // The Go standard library (at of late 2020) wraps the exported, standard |
| 432 | // context. Canceled error with unexported garbage value requiring a substring check, see |
| 433 | // https://github.com/golang/go/blob/6965b01ea248cabb70c3749fd218b36089a21efb/src/net/net.go#L416-L430 |
| 434 | // From Caddy https://github.com/caddyserver/caddy/blob/afa778ae05503f563af0d1015cdf7e5e78b1eeec/modules/caddyhttp/reverseproxy/reverseproxy.go#L1352 |
| 435 | if errors.Is(err, context.Canceled) || strings.Contains(err.Error(), "operation was canceled") { |
| 436 | httpError := echo.NewHTTPError(StatusCodeContextCanceled, "client closed connection").Wrap(err) |
| 437 | c.Set("_error", httpError) |
| 438 | } else { |
| 439 | httpError := echo.NewHTTPError( |
| 440 | http.StatusBadGateway, |
| 441 | "remote server unreachable, could not proxy request", |
| 442 | ).Wrap(fmt.Errorf("server: %s, err: %w", desc, err)) |
| 443 | c.Set("_error", httpError) |
| 444 | } |
| 445 | } |
| 446 | proxy.Transport = config.Transport |
| 447 | proxy.ModifyResponse = config.ModifyResponse |
| 448 | return proxy |
| 449 | } |
no test coverage detected
searching dependent graphs…