newPassthroughRouter returns a simple reverse-proxy implementation which will be used when a route is not handled specifically by a [intercept.Provider]. A single reverse proxy is created per provider and reused across all requests.
(prov provider.Provider, logger slog.Logger, m *metrics.Metrics, tracer trace.Tracer)
| 25 | // by a [intercept.Provider]. |
| 26 | // A single reverse proxy is created per provider and reused across all requests. |
| 27 | func newPassthroughRouter(prov provider.Provider, logger slog.Logger, m *metrics.Metrics, tracer trace.Tracer) http.HandlerFunc { |
| 28 | provBaseURL, err := url.Parse(prov.BaseURL()) |
| 29 | if err != nil { |
| 30 | return newInvalidBaseURLHandler(prov, logger, m, tracer, err) |
| 31 | } |
| 32 | if _, err := url.JoinPath(provBaseURL.Path, "/"); err != nil { |
| 33 | return newInvalidBaseURLHandler(prov, logger, m, tracer, err) |
| 34 | } |
| 35 | |
| 36 | // Transport tuned for streaming (no response header timeout). |
| 37 | t := &http.Transport{ |
| 38 | Proxy: http.ProxyFromEnvironment, |
| 39 | ForceAttemptHTTP2: true, |
| 40 | MaxIdleConns: 100, |
| 41 | IdleConnTimeout: 90 * time.Second, |
| 42 | TLSHandshakeTimeout: 10 * time.Second, |
| 43 | ExpectContinueTimeout: 1 * time.Second, |
| 44 | } |
| 45 | |
| 46 | // Build the passthrough proxy, reused across all requests for this provider. |
| 47 | // Rewrite sets proxy headers. For centralized requests, KeyFailoverTransport |
| 48 | // handles auth and failover. BYOK requests pass through. |
| 49 | proxy := &httputil.ReverseProxy{ |
| 50 | Rewrite: func(pr *httputil.ProxyRequest) { |
| 51 | rewritePassthroughRequest(pr, provBaseURL) |
| 52 | }, |
| 53 | Transport: keypool.NewKeyFailoverTransport( |
| 54 | apidump.NewPassthroughMiddleware(t, prov.APIDumpDir(), prov.Name(), logger, quartz.NewReal()), |
| 55 | prov.KeyFailoverConfig(logger), |
| 56 | ), |
| 57 | ErrorHandler: func(rw http.ResponseWriter, req *http.Request, e error) { |
| 58 | if _, ok := errors.AsType[*http.MaxBytesError](e); ok { |
| 59 | writeRequestBodyTooLarge(rw) |
| 60 | } else { |
| 61 | logger.Warn(req.Context(), "reverse proxy error", slog.Error(e), slog.F("path", req.URL.Path)) |
| 62 | http.Error(rw, "upstream proxy error", http.StatusBadGateway) |
| 63 | } |
| 64 | }, |
| 65 | } |
| 66 | |
| 67 | return func(w http.ResponseWriter, r *http.Request) { |
| 68 | if m != nil { |
| 69 | m.PassthroughCount.WithLabelValues(prov.Name(), r.URL.Path, r.Method).Add(1) |
| 70 | } |
| 71 | |
| 72 | ctx, span := startSpan(r, tracer) |
| 73 | defer span.End() |
| 74 | |
| 75 | proxy.ServeHTTP(w, r.WithContext(ctx)) |
| 76 | } |
| 77 | } |
| 78 | |
| 79 | // rewritePassthroughRequest configures the outbound request for the upstream and |
| 80 | // applies proxy headers. |