newPassthroughRouter returns a simple reverse-proxy implementation which will be used when a route is not handled specifically by a [intercept.Provider].
(prov provider.Provider, logger slog.Logger, m *metrics.Metrics, tracer trace.Tracer)
| 22 | // newPassthroughRouter returns a simple reverse-proxy implementation which will be used when a route is not handled specifically |
| 23 | // by a [intercept.Provider]. |
| 24 | func newPassthroughRouter(prov provider.Provider, logger slog.Logger, m *metrics.Metrics, tracer trace.Tracer) http.HandlerFunc { |
| 25 | return func(w http.ResponseWriter, r *http.Request) { |
| 26 | if m != nil { |
| 27 | m.PassthroughCount.WithLabelValues(prov.Name(), r.URL.Path, r.Method).Add(1) |
| 28 | } |
| 29 | |
| 30 | ctx, span := tracer.Start(r.Context(), "Passthrough", trace.WithAttributes( |
| 31 | attribute.String(tracing.PassthroughURL, r.URL.String()), |
| 32 | attribute.String(tracing.PassthroughMethod, r.Method), |
| 33 | )) |
| 34 | defer span.End() |
| 35 | |
| 36 | upURL, err := url.Parse(prov.BaseURL()) |
| 37 | if err != nil { |
| 38 | logger.Warn(ctx, "failed to parse provider base URL", slog.Error(err)) |
| 39 | http.Error(w, "request error", http.StatusBadGateway) |
| 40 | span.SetStatus(codes.Error, "failed to parse provider base URL: "+err.Error()) |
| 41 | return |
| 42 | } |
| 43 | |
| 44 | // Append the request path to the upstream base path. |
| 45 | reqPath, err := url.JoinPath(upURL.Path, r.URL.Path) |
| 46 | if err != nil { |
| 47 | logger.Warn(ctx, "failed to join upstream path", slog.Error(err), slog.F("upstream_path", upURL.Path), slog.F("request_path", r.URL.Path)) |
| 48 | http.Error(w, "failed to join upstream path", http.StatusInternalServerError) |
| 49 | span.SetStatus(codes.Error, "failed to join upstream path: "+err.Error()) |
| 50 | return |
| 51 | } |
| 52 | // Ensure leading slash, proxied requests should have absolute paths. |
| 53 | // JoinPath can return relative paths, eg. when upURL path is empty. |
| 54 | if len(reqPath) == 0 || reqPath[0] != '/' { |
| 55 | reqPath = "/" + reqPath |
| 56 | } |
| 57 | |
| 58 | // Build a reverse proxy to the upstream. |
| 59 | proxy := &httputil.ReverseProxy{ |
| 60 | Director: func(req *http.Request) { |
| 61 | // Set scheme/host to upstream. |
| 62 | req.URL.Scheme = upURL.Scheme |
| 63 | req.URL.Host = upURL.Host |
| 64 | req.URL.Path = reqPath |
| 65 | req.URL.RawPath = "" |
| 66 | |
| 67 | // Preserve query string. |
| 68 | req.URL.RawQuery = r.URL.RawQuery |
| 69 | |
| 70 | // Set Host header for upstream. |
| 71 | req.Host = upURL.Host |
| 72 | span.SetAttributes(attribute.String(tracing.PassthroughUpstreamURL, req.URL.String())) |
| 73 | |
| 74 | // Copy headers from client. |
| 75 | req.Header = r.Header.Clone() |
| 76 | |
| 77 | // Standard proxy headers. |
| 78 | host, _, herr := net.SplitHostPort(r.RemoteAddr) |
| 79 | if herr != nil { |
| 80 | host = r.RemoteAddr |
| 81 | } |