Skip to main content

pingora_proxy/
lib.rs

1// Copyright 2026 Cloudflare, Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! # pingora-proxy
16//!
17//! Programmable HTTP proxy built on top of [pingora_core].
18//!
19//! # Features
20//! - HTTP/1.x and HTTP/2 for both downstream and upstream
21//! - Connection pooling
22//! - TLSv1.3, mutual TLS, customizable CA
23//! - Request/Response scanning, modification or rejection
24//! - Dynamic upstream selection
25//! - Configurable retry and failover
26//! - Fully programmable and customizable at any stage of a HTTP request
27//!
28//! # How to use
29//!
30//! Users of this crate defines their proxy by implementing [ProxyHttp] trait, which contains the
31//! callbacks to be invoked at each stage of a HTTP request.
32//!
33//! Then the service can be passed into [`http_proxy_service()`] for a [pingora_core::server::Server] to
34//! run it.
35//!
36//! See `examples/load_balancer.rs` for a detailed example.
37
38use async_trait::async_trait;
39use bytes::Bytes;
40use futures::future::BoxFuture;
41use futures::future::FutureExt;
42use http::{header, version::Version, Method};
43use log::{debug, error, trace, warn};
44use once_cell::sync::Lazy;
45use pingora_http::{RequestHeader, ResponseHeader};
46use std::fmt::Debug;
47use std::future::{poll_fn, Future};
48use std::str;
49use std::sync::{
50    atomic::{AtomicBool, AtomicU64, AtomicU8, AtomicUsize, Ordering},
51    Arc,
52};
53use std::task::Poll;
54use std::time::Duration;
55use tokio::sync::{mpsc, Notify};
56use tokio::time;
57
58use pingora_cache::NoCacheReason;
59use pingora_core::apps::{
60    HttpPersistentSettings, HttpServerApp, HttpServerOptions, ReusedHttpStream,
61};
62use pingora_core::connectors::http::custom;
63use pingora_core::connectors::{http::Connector, ConnectorOptions};
64use pingora_core::modules::http::compression::ResponseCompressionBuilder;
65use pingora_core::modules::http::{HttpModuleCtx, HttpModules};
66use pingora_core::protocols::http::client::HttpSession as ClientSession;
67use pingora_core::protocols::http::custom::CustomMessageWrite;
68use pingora_core::protocols::http::subrequest::server::SubrequestHandle;
69use pingora_core::protocols::http::v1::client::HttpSession as HttpSessionV1;
70use pingora_core::protocols::http::v2::server::H2Options;
71use pingora_core::protocols::http::HttpTask;
72use pingora_core::protocols::http::ServerSession as HttpSession;
73use pingora_core::protocols::http::SERVER_NAME;
74use pingora_core::protocols::Stream;
75use pingora_core::protocols::{Digest, UniqueID};
76use pingora_core::server::configuration::ServerConf;
77use pingora_core::server::{RuntimeOpts, ShutdownWatch};
78use pingora_core::upstreams::peer::{HttpPeer, Peer};
79use pingora_error::{Error, ErrorSource, ErrorType::*, OrErr, Result};
80
81const TASK_BUFFER_SIZE: usize = 4;
82
83/// Caps per-proxy padding and one-time shutdown fan-out on very large hosts.
84const MAX_SHUTDOWN_NOTIFY_SHARDS: usize = 256;
85
86type DownstreamCustomMessageReader =
87    Box<dyn futures::Stream<Item = Result<Bytes>> + Unpin + Send + Sync + 'static>;
88
89mod proxy_cache;
90mod proxy_common;
91mod proxy_custom;
92mod proxy_h1;
93mod proxy_h2;
94mod proxy_purge;
95mod proxy_trait;
96pub mod subrequest;
97
98use subrequest::{BodyMode, Ctx as SubrequestCtx};
99
100pub use proxy_cache::range_filter::{range_header_filter, MultiRangeInfo, RangeType};
101pub use proxy_purge::PurgeStatus;
102pub use proxy_trait::{FailToProxy, ProxyHttp, ProxyWarnLogContext};
103
104pub mod prelude {
105    pub use crate::{http_proxy, http_proxy_service, ProxyHttp, ProxyWarnLogContext, Session};
106}
107
108pub type ProcessCustomSession<SV, C> = Arc<
109    dyn Fn(Arc<HttpProxy<SV, C>>, Stream, &ShutdownWatch) -> BoxFuture<'static, Option<Stream>>
110        + Send
111        + Sync
112        + Unpin
113        + 'static,
114>;
115
116/// Shutdown [`Notify`] sharded by worker thread.
117///
118/// Every request that parks in `read_request()` registers a shutdown waiter and
119/// unregisters it when the read completes. Both operations lock the `Notify`'s
120/// internal mutex, so a single `Notify` shared across the whole proxy becomes a
121/// contention hot spot on many-core machines. Sharding keeps waiter
122/// registration on a (mostly) thread-local shard while shutdown notifies every
123/// shard.
124struct ShardedNotify {
125    shards: Box<[NotifyShard]>,
126}
127
128/// Align each shard so its [`Notify`] state and waiter-list mutex do not share a
129/// cache line with an adjacent shard. Without padding, writes made while adding
130/// or removing waiters can falsely share a cache line with an independent shard,
131/// forcing cache-coherence protocols such as MESI to transfer or invalidate that
132/// line between cores. These transfers are especially expensive when they cross
133/// the interconnect between sockets on a NUMA system.
134///
135/// The 128-byte alignment separates adjacent shards on systems with common
136/// 64- or 128-byte cache lines. This trades bounded padding for avoiding false
137/// sharing between shards; waiters assigned to the same shard can still contend.
138#[repr(align(128))]
139struct NotifyShard(Notify);
140
141impl ShardedNotify {
142    /// Create enough shards for the configured worker threads, rounded up
143    /// to preserve mask-based indexing and bounded by [`MAX_SHUTDOWN_NOTIFY_SHARDS`].
144    fn new(worker_threads: usize) -> Self {
145        let shards = worker_threads
146            .max(1)
147            .checked_next_power_of_two()
148            .unwrap_or(MAX_SHUTDOWN_NOTIFY_SHARDS)
149            .min(MAX_SHUTDOWN_NOTIFY_SHARDS);
150        ShardedNotify {
151            shards: (0..shards).map(|_| NotifyShard(Notify::new())).collect(),
152        }
153    }
154
155    /// Return the shard assigned to the current thread.
156    ///
157    /// A task can migrate after registering, but its [`Notified`](tokio::sync::futures::Notified)
158    /// future remains bound to this shard and shutdown notifies every shard.
159    fn local(&self) -> &Notify {
160        static NEXT_THREAD_ID: AtomicUsize = AtomicUsize::new(0);
161        thread_local! {
162            static THREAD_ID: usize = NEXT_THREAD_ID.fetch_add(1, Ordering::Relaxed);
163        }
164        let id = THREAD_ID.with(|id| *id);
165        // the shard count is a power of two
166        &self.shards[id & (self.shards.len() - 1)].0
167    }
168
169    /// Notify waiters on every shard, including tasks polled by a different
170    /// worker after registering.
171    fn notify_waiters(&self) {
172        for shard in self.shards.iter() {
173            shard.0.notify_waiters();
174        }
175    }
176}
177
178/// The concrete type that holds the user defined HTTP proxy.
179///
180/// Users don't need to interact with this object directly.
181pub struct HttpProxy<SV, C = ()>
182where
183    C: custom::Connector, // Upstream custom connector
184{
185    inner: SV, // TODO: name it better than inner
186    client_upstream: Connector<C>,
187    shutdown: ShardedNotify,
188    shutdown_flag: Arc<AtomicBool>,
189    pub server_options: Option<HttpServerOptions>,
190    pub h2_options: Option<H2Options>,
191    pub downstream_modules: HttpModules,
192    #[cfg(feature = "upstream_modules")]
193    pub upstream_modules: HttpModules,
194    max_retries: usize,
195    process_custom_session: Option<ProcessCustomSession<SV, C>>,
196}
197
198impl<SV> HttpProxy<SV, ()> {
199    /// Create a new [`HttpProxy`] with the given [`ProxyHttp`] implementation and [`ServerConf`].
200    ///
201    /// After creating an `HttpProxy`, you should call [`HttpProxy::handle_init_modules()`] to
202    /// initialize the downstream modules before processing requests.
203    ///
204    /// For most use cases, prefer using [`http_proxy_service()`] which wraps the `HttpProxy` in a
205    /// [`Service`]. This constructor is useful when you need to integrate `HttpProxy` into a custom
206    /// accept loop (e.g., for SNI-based routing decisions before TLS termination).
207    ///
208    /// # Example
209    ///
210    /// ```ignore
211    /// use pingora_proxy::HttpProxy;
212    /// use std::sync::Arc;
213    ///
214    /// let mut proxy = HttpProxy::new(my_proxy_app, server_conf);
215    /// proxy.handle_init_modules();
216    /// let proxy = Arc::new(proxy);
217    /// // Use proxy.process_new_http() in your custom accept loop
218    /// ```
219    pub fn new(inner: SV, conf: Arc<ServerConf>) -> Self {
220        HttpProxy {
221            inner,
222            client_upstream: Connector::new(Some(ConnectorOptions::from_server_conf(&conf))),
223            shutdown: ShardedNotify::new(conf.threads),
224            shutdown_flag: Arc::new(AtomicBool::new(false)),
225            server_options: None,
226            h2_options: None,
227            downstream_modules: HttpModules::new(),
228            #[cfg(feature = "upstream_modules")]
229            upstream_modules: HttpModules::new(),
230            max_retries: conf.max_retries,
231            process_custom_session: None,
232        }
233    }
234}
235
236impl<SV, C> HttpProxy<SV, C>
237where
238    C: custom::Connector,
239{
240    fn new_custom(
241        inner: SV,
242        conf: Arc<ServerConf>,
243        connector: C,
244        on_custom: Option<ProcessCustomSession<SV, C>>,
245        server_options: Option<HttpServerOptions>,
246        client_options: Option<ConnectorOptions>,
247    ) -> Self
248    where
249        SV: ProxyHttp + Send + Sync + 'static,
250        SV::CTX: Send + Sync,
251    {
252        let client_options =
253            client_options.unwrap_or_else(|| ConnectorOptions::from_server_conf(&conf));
254        let client_upstream = Connector::new_custom(Some(client_options), connector);
255
256        HttpProxy {
257            inner,
258            client_upstream,
259            shutdown: ShardedNotify::new(conf.threads),
260            shutdown_flag: Arc::new(AtomicBool::new(false)),
261            server_options,
262            downstream_modules: HttpModules::new(),
263            #[cfg(feature = "upstream_modules")]
264            upstream_modules: HttpModules::new(),
265            max_retries: conf.max_retries,
266            process_custom_session: on_custom,
267            h2_options: None,
268        }
269    }
270
271    /// Return the number of times a pooled upstream connection was found to contain
272    /// unexpected data from the server.
273    pub fn unexpected_data_connection_count(&self) -> u64 {
274        self.client_upstream.unexpected_data_connection_count()
275    }
276
277    /// Return a shared reference to the unexpected data connection counter for periodic metric reporting.
278    pub fn unexpected_data_connection_counter(&self) -> Arc<AtomicU64> {
279        self.client_upstream.unexpected_data_connection_counter()
280    }
281
282    /// Initialize the downstream modules for this proxy.
283    ///
284    /// This method must be called after creating an [`HttpProxy`] with [`HttpProxy::new()`]
285    /// and before processing any requests. It invokes [`ProxyHttp::init_downstream_modules()`]
286    /// to set up any HTTP modules configured by the user's proxy implementation.
287    ///
288    /// Note: When using [`http_proxy_service()`] or [`http_proxy_service_with_name()`],
289    /// this method is called automatically.
290    pub fn handle_init_modules(&mut self)
291    where
292        SV: ProxyHttp,
293    {
294        self.inner
295            .init_downstream_modules(&mut self.downstream_modules);
296        #[cfg(feature = "upstream_modules")]
297        self.inner.init_upstream_modules(&mut self.upstream_modules);
298    }
299
300    /// Resolve when `http_cleanup()` has been called.
301    ///
302    /// The waiter is registered on the current thread's shard before
303    /// `shutdown_flag` is checked, so a shutdown firing in between cannot be
304    /// missed: either the flag load sees the store, or the registered waiter
305    /// receives the notification.
306    async fn await_shutdown(&self) {
307        let notified = self.shutdown.local().notified();
308        tokio::pin!(notified);
309
310        poll_fn(|context| {
311            if notified.as_mut().poll(context).is_ready()
312                || self.shutdown_flag.load(Ordering::Acquire)
313            {
314                Poll::Ready(())
315            } else {
316                Poll::Pending
317            }
318        })
319        .await;
320    }
321
322    async fn handle_new_request(
323        &self,
324        mut downstream_session: Box<HttpSession>,
325    ) -> Option<Box<HttpSession>>
326    where
327        SV: ProxyHttp + Send + Sync,
328        SV::CTX: Send + Sync,
329    {
330        // phase 1 read request header
331
332        let res = tokio::select! {
333            biased; // biased select is cheaper, and we don't want to drop already buffered requests
334            res = downstream_session.read_request() => { res }
335            _ = self.await_shutdown() => {
336                // service shutting down, dropping the connection to stop more req from coming in
337                return None;
338            }
339        };
340        match res {
341            Ok(true) => {
342                // TODO: check n==0
343                debug!("Successfully get a new request");
344            }
345            Ok(false) => {
346                return None; // TODO: close connection?
347            }
348            Err(mut e) => {
349                e.as_down();
350                if matches!(e.etype, InvalidHTTPHeader) {
351                    debug!(
352                        "Fail to proxy: {e}, downstream session type: {}",
353                        downstream_session.session_type()
354                    );
355                    downstream_session
356                        .respond_error(400)
357                        .await
358                        .unwrap_or_else(|e| {
359                            error!("failed to send error response to downstream: {e}");
360                        });
361                } else {
362                    // otherwise the connection must be broken, no need to send anything
363                    error!(
364                        "Fail to proxy: {e}, downstream session type: {}",
365                        downstream_session.session_type()
366                    );
367                }
368                downstream_session.shutdown().await;
369                return None;
370            }
371        }
372        trace!(
373            "Request header: {:?}",
374            downstream_session.req_header().as_ref()
375        );
376        // CONNECT method proxying is not default supported by the proxy http logic itself,
377        // since the tunneling process changes the request-response flow.
378        // https://datatracker.ietf.org/doc/html/rfc9110#name-connect
379        // Also because the method impacts message framing in a way is currently unaccounted for
380        // (https://datatracker.ietf.org/doc/html/rfc9112#section-6.3-2.2)
381        // it is safest to disallow use of the method by default.
382        if !self
383            .server_options
384            .as_ref()
385            .is_some_and(|opts| opts.allow_connect_method_proxying)
386            && downstream_session.req_header().method == Method::CONNECT
387        {
388            downstream_session
389                .respond_error(405)
390                .await
391                .unwrap_or_else(|e| {
392                    error!("failed to send error response to downstream: {e}");
393                });
394            downstream_session.shutdown().await;
395            return None;
396        }
397        Some(downstream_session)
398    }
399
400    // return bool: server_session can be reused, and error if any
401    async fn proxy_to_upstream(
402        &self,
403        session: &mut Session,
404        ctx: &mut SV::CTX,
405    ) -> (bool, Option<Box<Error>>)
406    where
407        SV: ProxyHttp + Send + Sync,
408        SV::CTX: Send + Sync,
409    {
410        let peer = match self.inner.upstream_peer(session, ctx).await {
411            Ok(p) => p,
412            Err(e) => return (false, Some(e)),
413        };
414
415        let client_session = self.client_upstream.get_http_session(&*peer).await;
416        match client_session {
417            Ok((client_session, client_reused)) => {
418                let (server_reused, error) = match client_session {
419                    ClientSession::H1(mut h1) => {
420                        let (server_reused, client_reuse, error) = self
421                            .proxy_to_h1_upstream(session, &mut h1, client_reused, &peer, ctx)
422                            .await;
423                        if client_reuse {
424                            let session = ClientSession::H1(h1);
425                            self.client_upstream
426                                .release_http_session(session, &*peer, peer.idle_timeout())
427                                .await;
428                        }
429                        (server_reused, error)
430                    }
431                    ClientSession::H2(mut h2) => {
432                        let (server_reused, mut error) = self
433                            .proxy_to_h2_upstream(session, &mut h2, client_reused, &peer, ctx)
434                            .await;
435                        let session = ClientSession::H2(h2);
436                        self.client_upstream
437                            .release_http_session(session, &*peer, peer.idle_timeout())
438                            .await;
439
440                        if let Some(e) = error.as_mut() {
441                            // try to downgrade if A. origin says so or B. origin sends an invalid
442                            // response, which usually means origin h2 is not production ready
443                            if matches!(e.etype, H2Downgrade | InvalidH2) {
444                                if peer
445                                    .get_alpn()
446                                    .is_none_or(|alpn| alpn.get_min_http_version() == 1)
447                                {
448                                    // Add the peer to prefer h1 so that all following requests
449                                    // will use h1
450                                    self.client_upstream.prefer_h1(&*peer);
451                                } else {
452                                    // the peer doesn't allow downgrading to h1 (e.g. gRPC)
453                                    e.retry = false.into();
454                                }
455                            }
456                        }
457
458                        (server_reused, error)
459                    }
460                    ClientSession::Custom(mut c) => {
461                        let (server_reused, error) = self
462                            .proxy_to_custom_upstream(session, &mut c, client_reused, &peer, ctx)
463                            .await;
464                        let session = ClientSession::Custom(c);
465                        self.client_upstream
466                            .release_http_session(session, &*peer, peer.idle_timeout())
467                            .await;
468                        (server_reused, error)
469                    }
470                };
471                (
472                    server_reused,
473                    error.map(|e| {
474                        self.inner
475                            .error_while_proxy(&peer, session, e, ctx, client_reused)
476                    }),
477                )
478            }
479            Err(mut e) => {
480                e.as_up();
481                let new_err = self.inner.fail_to_connect(session, &peer, ctx, e);
482                (false, Some(new_err.into_up()))
483            }
484        }
485    }
486
487    async fn upstream_filter(
488        &self,
489        session: &mut Session,
490        task: &mut HttpTask,
491        ctx: &mut SV::CTX,
492    ) -> Result<Option<Duration>>
493    where
494        SV: ProxyHttp + Send + Sync,
495        SV::CTX: Send + Sync,
496    {
497        let duration = match task {
498            HttpTask::Header(header, _eos) => {
499                self.inner
500                    .upstream_response_filter(session, header, ctx)
501                    .await?;
502                None
503            }
504            HttpTask::Body(data, eos) | HttpTask::UpgradedBody(data, eos) => self
505                .inner
506                .upstream_response_body_filter(session, data, *eos, ctx)?,
507            HttpTask::Trailer(Some(trailers)) => {
508                self.inner
509                    .upstream_response_trailer_filter(session, trailers, ctx)?;
510                None
511            }
512            _ => {
513                // task does not support a filter
514                None
515            }
516        };
517
518        Ok(duration)
519    }
520
521    async fn finish(
522        &self,
523        mut session: Session,
524        ctx: &mut SV::CTX,
525        reuse: bool,
526        error: Option<Box<Error>>,
527    ) -> Option<ReusedHttpStream>
528    where
529        SV: ProxyHttp + Send + Sync,
530        SV::CTX: Send + Sync,
531    {
532        self.inner
533            .logging(&mut session, error.as_deref(), ctx)
534            .await;
535
536        if let Some(e) = error {
537            session.downstream_session.on_proxy_failure(e);
538        }
539
540        if reuse {
541            // TODO: log error
542            let mut persistent_settings = HttpPersistentSettings::for_session(&session);
543            if let Some(uc) = self.inner.persist_connection_context(&session, ctx) {
544                persistent_settings.set_user_context(uc);
545            }
546            session
547                .downstream_session
548                .finish()
549                .await
550                .ok()
551                .flatten()
552                .map(|s| ReusedHttpStream::from_reusable_stream(s, persistent_settings))
553        } else {
554            None
555        }
556    }
557
558    fn cleanup_sub_req(&self, session: &mut Session) {
559        if let Some(ctx) = session.subrequest_ctx.as_mut() {
560            ctx.release_write_lock();
561        }
562    }
563}
564
565use pingora_cache::HttpCache;
566use pingora_core::protocols::http::compression::ResponseCompressionCtx;
567
568/// The established HTTP session
569///
570/// This object is what users interact with in order to access the request itself or change the proxy
571/// behavior.
572pub struct Session {
573    /// the HTTP session to downstream (the client)
574    pub downstream_session: Box<HttpSession>,
575    /// The interface to control HTTP caching
576    pub cache: HttpCache,
577    /// (de)compress responses coming into the proxy (from upstream)
578    pub upstream_compression: ResponseCompressionCtx,
579    /// ignore downstream range (skip downstream range filters)
580    pub ignore_downstream_range: bool,
581    /// Were the upstream request headers modified?
582    pub upstream_headers_mutated_for_cache: bool,
583    /// Upstream predicate for whether this HTTP/1 request is an upgrade.
584    h1_upgrade_request_status: H1UpgradeRequestStatus,
585    /// The context from parent request, if this is a subrequest.
586    pub subrequest_ctx: Option<Box<SubrequestCtx>>,
587    /// Handle to allow spawning subrequests, assigned by the `Subrequest` app logic.
588    pub subrequest_spawner: Option<SubrequestSpawner>,
589    // Downstream filter modules
590    pub downstream_modules_ctx: HttpModuleCtx,
591    /// Upstream filter modules. These run before `upstream_compression` and see the raw
592    /// (pre-compression) upstream response body.
593    #[cfg(feature = "upstream_modules")]
594    pub upstream_modules_ctx: HttpModuleCtx,
595    /// Upstream response body bytes received (payload only). Set by proxy layer.
596    /// TODO: move this into an upstream session digest for future fields.
597    upstream_body_bytes_received: usize,
598    /// Request body bytes written to the upstream (payload only). Set by proxy layer.
599    ///
600    /// `None` when the proxy layer does not track it (HTTP/2 and custom upstreams), which is
601    /// deliberately distinct from `Some(0)` so that "not measured" cannot be mistaken for
602    /// "a request body was dropped".
603    upstream_body_bytes_sent: Option<usize>,
604    /// Whether proxy task filtering has seen a downstream 101 upgrade header.
605    downstream_task_seen_upgraded: bool,
606    /// Upstream write pending time. Set by proxy layer (HTTP/1.x only).
607    upstream_write_pending_time: Duration,
608    /// Flag that is set when the shutdown process has begun.
609    shutdown_flag: Arc<AtomicBool>,
610}
611
612impl Session {
613    fn new(
614        downstream_session: impl Into<Box<HttpSession>>,
615        downstream_modules: &HttpModules,
616        #[cfg(feature = "upstream_modules")] upstream_modules: &HttpModules,
617        shutdown_flag: Arc<AtomicBool>,
618    ) -> Self {
619        Session {
620            downstream_session: downstream_session.into(),
621            cache: HttpCache::new(),
622            // disable both upstream and downstream compression
623            upstream_compression: ResponseCompressionCtx::new(0, false, false),
624            ignore_downstream_range: false,
625            upstream_headers_mutated_for_cache: false,
626            h1_upgrade_request_status: H1UpgradeRequestStatus::default(),
627            subrequest_ctx: None,
628            subrequest_spawner: None, // optionally set later on
629            downstream_modules_ctx: downstream_modules.build_ctx(),
630            #[cfg(feature = "upstream_modules")]
631            upstream_modules_ctx: upstream_modules.build_ctx(),
632            upstream_body_bytes_received: 0,
633            upstream_body_bytes_sent: None,
634            downstream_task_seen_upgraded: false,
635            upstream_write_pending_time: Duration::ZERO,
636            shutdown_flag,
637        }
638    }
639
640    /// Create a new [Session] from the given [Stream]
641    ///
642    /// This function is mostly used for testing and mocking, given the downstream modules and
643    /// shutdown flags will never be set.
644    pub fn new_h1(stream: Stream) -> Self {
645        let modules = HttpModules::new();
646        Self::new(
647            Box::new(HttpSession::new_http1(stream)),
648            &modules,
649            #[cfg(feature = "upstream_modules")]
650            &HttpModules::new(),
651            Arc::new(AtomicBool::new(false)),
652        )
653    }
654
655    /// Create a new [Session] from the given [Stream] with modules
656    ///
657    /// This function is mostly used for testing and mocking, given the shutdown flag will never be
658    /// set.
659    pub fn new_h1_with_modules(stream: Stream, downstream_modules: &HttpModules) -> Self {
660        Self::new(
661            Box::new(HttpSession::new_http1(stream)),
662            downstream_modules,
663            #[cfg(feature = "upstream_modules")]
664            &HttpModules::new(),
665            Arc::new(AtomicBool::new(false)),
666        )
667    }
668
669    /// Run upstream module filters on the given [`HttpTask`].
670    ///
671    /// Upstream modules process each task **before** `upstream_compression` and
672    /// see the raw (pre-compression) upstream response. Like the downstream
673    /// module path, `response_trailer_filter` and `response_done_filter` return
674    /// values are converted to body tasks when present.
675    #[cfg(feature = "upstream_modules")]
676    pub async fn upstream_modules_filter_task(&mut self, t: &mut HttpTask) -> Result<()> {
677        match t {
678            HttpTask::Header(header, eos) => {
679                self.upstream_modules_ctx
680                    .response_header_filter(header, *eos)
681                    .await?;
682            }
683            HttpTask::Body(body, eos) | HttpTask::UpgradedBody(body, eos) => {
684                self.upstream_modules_ctx.response_body_filter(body, *eos)?;
685            }
686            HttpTask::Trailer(trailers) => {
687                if let Some(buf) = self
688                    .upstream_modules_ctx
689                    .response_trailer_filter(trailers)?
690                {
691                    *t = HttpTask::Body(Some(buf), true);
692                }
693            }
694            HttpTask::Done => {
695                if let Some(buf) = self.upstream_modules_ctx.response_done_filter()? {
696                    *t = HttpTask::Body(Some(buf), true);
697                }
698            }
699            HttpTask::Failed(_) => {}
700        }
701        Ok(())
702    }
703
704    pub fn as_downstream_mut(&mut self) -> &mut HttpSession {
705        &mut self.downstream_session
706    }
707
708    pub fn as_downstream(&self) -> &HttpSession {
709        &self.downstream_session
710    }
711
712    /// Write HTTP response with the given error code to the downstream.
713    pub async fn respond_error(&mut self, error: u16) -> Result<()> {
714        self.as_downstream_mut().respond_error(error).await
715    }
716
717    /// Write HTTP response with the given error code to the downstream with a body.
718    pub async fn respond_error_with_body(&mut self, error: u16, body: Bytes) -> Result<()> {
719        self.as_downstream_mut()
720            .respond_error_with_body(error, body)
721            .await
722    }
723
724    /// Write the given HTTP response header to the downstream
725    ///
726    /// Different from directly calling [HttpSession::write_response_header], this function also
727    /// invokes the filter modules.
728    pub async fn write_response_header(
729        &mut self,
730        mut resp: Box<ResponseHeader>,
731        end_of_stream: bool,
732    ) -> Result<()> {
733        self.downstream_modules_ctx
734            .response_header_filter(&mut resp, end_of_stream)
735            .await?;
736        self.downstream_session.write_response_header(resp).await
737    }
738
739    /// Similar to `write_response_header()`, this fn will clone the `resp` internally
740    pub async fn write_response_header_ref(
741        &mut self,
742        resp: &ResponseHeader,
743        end_of_stream: bool,
744    ) -> Result<(), Box<Error>> {
745        self.write_response_header(Box::new(resp.clone()), end_of_stream)
746            .await
747    }
748
749    /// Write the given HTTP response body chunk to the downstream
750    ///
751    /// Different from directly calling [HttpSession::write_response_body], this function also
752    /// invokes the filter modules.
753    pub async fn write_response_body(
754        &mut self,
755        mut body: Option<Bytes>,
756        end_of_stream: bool,
757    ) -> Result<()> {
758        self.downstream_modules_ctx
759            .response_body_filter(&mut body, end_of_stream)?;
760
761        if body.is_none() && !end_of_stream {
762            return Ok(());
763        }
764
765        let data = body.unwrap_or_default();
766        self.downstream_session
767            .write_response_body(data, end_of_stream)
768            .await
769    }
770
771    // Run downstream module response filters on a single task, updating
772    // `seen_upgraded` to track whether an upgrade has been seen. Used by both
773    // `send_downstream_proxy_task` and `write_response_tasks`.
774    async fn downstream_response_task_filter(
775        &mut self,
776        task: &mut HttpTask,
777        seen_upgraded: &mut bool,
778    ) -> Result<()> {
779        match task {
780            HttpTask::Header(resp, end) => {
781                if *seen_upgraded {
782                    return reject_unexpected_task_after_h1_upgrade(self, "header", *seen_upgraded);
783                }
784                self.downstream_modules_ctx
785                    .response_header_filter(resp, *end)
786                    .await?;
787                reject_mismatched_h1_upgrade_101(self, resp, "downstream_module_header_filter")
788                    .map_err(|e| e.into_in())?;
789                if resp.status == http::StatusCode::SWITCHING_PROTOCOLS
790                    && self.downstream_session.is_upgrade(resp) == Some(true)
791                {
792                    *seen_upgraded = true;
793                }
794            }
795            HttpTask::Body(data, end) => {
796                if *seen_upgraded {
797                    return reject_unexpected_task_after_h1_upgrade(self, "body", *seen_upgraded);
798                }
799                self.downstream_modules_ctx
800                    .response_body_filter(data, *end)?;
801            }
802            HttpTask::UpgradedBody(data, end) => {
803                if !*seen_upgraded {
804                    return reject_unexpected_upgraded_body_before_h1_upgrade(self, *seen_upgraded);
805                }
806                self.downstream_modules_ctx
807                    .response_body_filter(data, *end)?;
808            }
809            HttpTask::Trailer(trailers) => {
810                if *seen_upgraded {
811                    return reject_unexpected_task_after_h1_upgrade(
812                        self,
813                        "trailer",
814                        *seen_upgraded,
815                    );
816                }
817                if let Some(buf) = self
818                    .downstream_modules_ctx
819                    .response_trailer_filter(trailers)?
820                {
821                    // Write the trailers into the body if the filter
822                    // returns a buffer.
823                    //
824                    // Note, this will not work if end of stream has already
825                    // been seen or we've written content-length bytes.
826                    // (Trailers should never come after upgraded body)
827                    *task = HttpTask::Body(Some(buf), true);
828                }
829            }
830            HttpTask::Done => {
831                // `Done` can be sent in certain response paths to mark end
832                // of response if not already done via trailers or body with
833                // end flag set.
834                // If the filter returns body bytes on Done,
835                // write them into the response. After a 101, those bytes are
836                // already in the upgraded protocol and must not be HTTP-framed.
837                //
838                // Note, this will not work if end of stream has already
839                // been seen or we've written content-length bytes.
840                if let Some(buf) = self.downstream_modules_ctx.response_done_filter()? {
841                    *task = if *seen_upgraded {
842                        HttpTask::UpgradedBody(Some(buf), true)
843                    } else {
844                        HttpTask::Body(Some(buf), true)
845                    };
846                }
847            }
848            _ => { /* Failed */ }
849        }
850        Ok(())
851    }
852
853    /// Queue a downstream proxy task for cancel-safe writing after running
854    /// downstream module filters. This allows decoupling cache writes from
855    /// downstream writes.
856    ///
857    /// Only works with sessions that support the proxy task API.
858    ///
859    /// # Panics
860    /// Panics if the session doesn't support the proxy task API.
861    /// Use `write_response_tasks()` for sessions that don't support the proxy task API.
862    pub async fn send_downstream_proxy_task(&mut self, mut task: HttpTask) -> Result<()> {
863        let mut seen_upgraded = self.downstream_task_seen_upgraded || self.was_upgraded();
864        self.downstream_response_task_filter(&mut task, &mut seen_upgraded)
865            .await?;
866        self.downstream_task_seen_upgraded = seen_upgraded;
867        self.downstream_session.send_downstream_proxy_task(task);
868        Ok(())
869    }
870
871    /// Enable or disable the cancel-safe proxy task API for this session.
872    ///
873    /// When disabled, the proxy falls back to the blocking `write_response_tasks`
874    /// path. This can be called from request filters to opt out on a per-request
875    /// basis.
876    pub fn set_proxy_tasks_enabled(&mut self, enabled: bool) {
877        self.downstream_session.set_proxy_tasks_enabled(enabled);
878    }
879
880    /// Check if there are pending downstream tasks queued for writing.
881    /// Used for backpressure - don't queue more cache tasks if we have pending writes.
882    /// Returns false for sessions that don't support the proxy task API.
883    pub fn has_pending_downstream_tasks(&self) -> bool {
884        self.downstream_session.supports_proxy_task_api()
885            && self.downstream_session.has_pending_downstream_proxy_tasks()
886    }
887
888    /// Write all queued downstream proxy tasks. This is cancel-safe and can be called
889    /// in a select! loop while waiting for upstream tasks.
890    /// For sessions that don't support the proxy task API, this is a no-op.
891    pub async fn write_downstream_proxy_tasks(&mut self) -> Result<bool> {
892        if self.downstream_session.supports_proxy_task_api() {
893            self.downstream_session.write_downstream_proxy_tasks().await
894        } else {
895            Ok(false)
896        }
897    }
898
899    pub async fn write_response_tasks(&mut self, mut tasks: Vec<HttpTask>) -> Result<bool> {
900        let mut seen_upgraded = self.downstream_task_seen_upgraded || self.was_upgraded();
901        for task in tasks.iter_mut() {
902            self.downstream_response_task_filter(task, &mut seen_upgraded)
903                .await?;
904        }
905        self.downstream_task_seen_upgraded = seen_upgraded;
906        self.downstream_session.response_duplex_vec(tasks).await
907    }
908
909    /// Mark the upstream headers as modified by caching. This should lead to range filters being
910    /// skipped when responding to the downstream.
911    pub fn mark_upstream_headers_mutated_for_cache(&mut self) {
912        self.upstream_headers_mutated_for_cache = true;
913    }
914
915    /// Check whether the upstream headers were marked as mutated during the request.
916    pub fn upstream_headers_mutated_for_cache(&self) -> bool {
917        self.upstream_headers_mutated_for_cache
918    }
919
920    fn set_upstream_h1_upgrade_request_status(&mut self, upstream_is_upgrade_req: bool) {
921        self.h1_upgrade_request_status = H1UpgradeRequestStatus {
922            upstream: Some(upstream_is_upgrade_req),
923        };
924    }
925
926    fn h1_upgrade_request_snapshot(&self) -> H1UpgradeRequestSnapshot {
927        H1UpgradeRequestSnapshot {
928            downstream: self.downstream_session.is_upgrade_req(),
929            upstream: self.h1_upgrade_request_status.upstream,
930        }
931    }
932
933    /// Get the total upstream response body bytes received (payload only) recorded by the proxy layer.
934    pub fn upstream_body_bytes_received(&self) -> usize {
935        self.upstream_body_bytes_received
936    }
937
938    /// Set the total upstream response body bytes received (payload only). Intended for internal use by proxy layer.
939    pub(crate) fn set_upstream_body_bytes_received(&mut self, n: usize) {
940        self.upstream_body_bytes_received = n;
941    }
942
943    /// Get the request body bytes written to the upstream (payload only) recorded by the proxy
944    /// layer.
945    ///
946    /// Returns `None` when the proxy layer does not track it (HTTP/2 and custom upstreams).
947    pub fn upstream_body_bytes_sent(&self) -> Option<usize> {
948        self.upstream_body_bytes_sent
949    }
950
951    /// Set the request body bytes written to the upstream (payload only). Intended for internal
952    /// use by proxy layer.
953    pub(crate) fn set_upstream_body_bytes_sent(&mut self, n: usize) {
954        self.upstream_body_bytes_sent = Some(n);
955    }
956
957    /// Get the upstream write pending time recorded by the proxy layer. Returns [`Duration::ZERO`] for HTTP/2.
958    pub fn upstream_write_pending_time(&self) -> Duration {
959        self.upstream_write_pending_time
960    }
961
962    /// Set the upstream write pending time. Intended for internal use by proxy layer.
963    pub(crate) fn set_upstream_write_pending_time(&mut self, d: Duration) {
964        self.upstream_write_pending_time = d;
965    }
966
967    /// Is the proxy process in the process of shutting down (e.g. due to graceful upgrade)?
968    pub fn is_process_shutting_down(&self) -> bool {
969        self.shutdown_flag.load(Ordering::Acquire)
970    }
971
972    pub fn downstream_custom_message(&mut self) -> Result<Option<DownstreamCustomMessageReader>> {
973        if let Some(custom_session) = self.downstream_session.as_custom_mut() {
974            custom_session
975                .take_custom_message_reader()
976                .map(Some)
977                .ok_or(Error::explain(
978                    ReadError,
979                    "can't extract custom reader from downstream",
980                ))
981        } else {
982            Ok(None)
983        }
984    }
985
986    fn take_downstream_custom_message_reader(
987        &mut self,
988        downstream_custom_message_writer: &mut Option<Box<dyn CustomMessageWrite>>,
989    ) -> Result<Option<DownstreamCustomMessageReader>> {
990        if downstream_custom_message_writer.is_none() {
991            return Ok(None);
992        }
993
994        let Some(custom_session) = self.downstream_session.as_custom_mut() else {
995            return Ok(None);
996        };
997
998        let Some(reader) = custom_session.take_custom_message_reader() else {
999            if let Some(writer) = downstream_custom_message_writer.take() {
1000                custom_session.restore_custom_message_writer(writer)?;
1001            }
1002            return Err(Error::explain(
1003                ReadError,
1004                "can't extract custom reader from downstream",
1005            ));
1006        };
1007
1008        Ok(Some(reader))
1009    }
1010}
1011
1012#[derive(Clone, Copy, Debug, Default)]
1013struct H1UpgradeRequestStatus {
1014    upstream: Option<bool>,
1015}
1016
1017#[derive(Clone, Copy, Debug)]
1018struct H1UpgradeRequestSnapshot {
1019    downstream: bool,
1020    upstream: Option<bool>,
1021}
1022
1023impl H1UpgradeRequestSnapshot {
1024    fn mismatch(self) -> bool {
1025        // No upstream predicate means this helper cannot prove a mismatch. The
1026        // current proxy paths record it before upstream responses can be handled.
1027        matches!(self.upstream, Some(upstream) if self.downstream != upstream)
1028    }
1029}
1030
1031/// Rejects a 101 response when the downstream and upstream H1 upgrade state differs.
1032///
1033/// Upstream and downstream must agree that this request is an upgrade before a
1034/// 101 can establish a tunnel. Otherwise one side changes protocol while the
1035/// other stays in HTTP handling, allowing tunneled traffic to bypass request
1036/// processing or corrupt the connection state.
1037fn reject_mismatched_h1_upgrade_101(
1038    session: &Session,
1039    header: &ResponseHeader,
1040    stage: &'static str,
1041) -> Result<()> {
1042    if header.status != http::StatusCode::SWITCHING_PROTOCOLS {
1043        return Ok(());
1044    }
1045
1046    let status = session.h1_upgrade_request_snapshot();
1047    if status.mismatch() {
1048        return Error::e_explain(
1049            InvalidHTTPHeader,
1050            format!(
1051                "received 101 response with mismatched upstream/downstream upgrade status: stage={stage}, downstream_upgrade_req={}, upstream_upgrade_req={:?}, downstream_was_upgraded={}, downstream_task_seen_upgraded={}, response_version={:?}, response_upgrade_header_present={}, response_connection_header_present={}",
1052                status.downstream,
1053                status.upstream,
1054                session.was_upgraded(),
1055                session.downstream_task_seen_upgraded,
1056                header.version,
1057                header.headers.get(http::header::UPGRADE).is_some(),
1058                header.headers.get(http::header::CONNECTION).is_some(),
1059            ),
1060        );
1061    }
1062    Ok(())
1063}
1064
1065fn reject_unexpected_task_after_h1_upgrade(
1066    session: &Session,
1067    task: &'static str,
1068    task_filter_seen_upgraded: bool,
1069) -> Result<()> {
1070    let status = session.h1_upgrade_request_snapshot();
1071    Error::e_explain(
1072        InvalidHTTPHeader,
1073        format!(
1074            "received {task} task after downstream 101 upgrade: downstream_upgrade_req={}, upstream_upgrade_req={:?}, downstream_was_upgraded={}, downstream_task_seen_upgraded={}, task_filter_seen_upgraded={}",
1075            status.downstream,
1076            status.upstream,
1077            session.was_upgraded(),
1078            session.downstream_task_seen_upgraded,
1079            task_filter_seen_upgraded
1080        ),
1081    )
1082    .map_err(|e| e.into_in())
1083}
1084
1085fn reject_unexpected_upgraded_body_before_h1_upgrade(
1086    session: &Session,
1087    task_filter_seen_upgraded: bool,
1088) -> Result<()> {
1089    let status = session.h1_upgrade_request_snapshot();
1090    Error::e_explain(
1091        InvalidHTTPHeader,
1092        format!(
1093            "received upgraded body task before downstream 101 upgrade: downstream_upgrade_req={}, upstream_upgrade_req={:?}, downstream_was_upgraded={}, downstream_task_seen_upgraded={}, task_filter_seen_upgraded={}",
1094            status.downstream,
1095            status.upstream,
1096            session.was_upgraded(),
1097            session.downstream_task_seen_upgraded,
1098            task_filter_seen_upgraded
1099        ),
1100    )
1101    .map_err(|e| e.into_in())
1102}
1103
1104impl AsRef<HttpSession> for Session {
1105    fn as_ref(&self) -> &HttpSession {
1106        &self.downstream_session
1107    }
1108}
1109
1110impl AsMut<HttpSession> for Session {
1111    fn as_mut(&mut self) -> &mut HttpSession {
1112        &mut self.downstream_session
1113    }
1114}
1115
1116use std::ops::{Deref, DerefMut};
1117
1118impl Deref for Session {
1119    type Target = HttpSession;
1120
1121    fn deref(&self) -> &Self::Target {
1122        &self.downstream_session
1123    }
1124}
1125
1126impl DerefMut for Session {
1127    fn deref_mut(&mut self) -> &mut Self::Target {
1128        &mut self.downstream_session
1129    }
1130}
1131
1132// generic HTTP 502 response sent when proxy_upstream_filter refuses to connect to upstream
1133static BAD_GATEWAY: Lazy<ResponseHeader> = Lazy::new(|| {
1134    let mut resp = ResponseHeader::build(http::StatusCode::BAD_GATEWAY, Some(3)).unwrap();
1135    resp.insert_header(header::SERVER, &SERVER_NAME[..])
1136        .unwrap();
1137    resp.insert_header(header::CONTENT_LENGTH, 0).unwrap();
1138    resp.insert_header(header::CACHE_CONTROL, "private, no-store")
1139        .unwrap();
1140
1141    resp
1142});
1143
1144impl<SV, C> HttpProxy<SV, C>
1145where
1146    C: custom::Connector,
1147{
1148    async fn process_request(
1149        self: &Arc<Self>,
1150        mut session: Session,
1151        mut ctx: <SV as ProxyHttp>::CTX,
1152    ) -> Option<ReusedHttpStream>
1153    where
1154        SV: ProxyHttp + Send + Sync + 'static,
1155        <SV as ProxyHttp>::CTX: Send + Sync,
1156    {
1157        if let Err(e) = self
1158            .inner
1159            .early_request_filter(&mut session, &mut ctx)
1160            .await
1161        {
1162            return self
1163                .handle_error(session, &mut ctx, e, "Fail to early filter request:")
1164                .await;
1165        }
1166
1167        if self.inner.allow_spawning_subrequest(&session, &ctx) {
1168            session.subrequest_spawner = Some(SubrequestSpawner::new(self.clone()));
1169        }
1170
1171        let req = session.downstream_session.req_header_mut();
1172
1173        // Built-in downstream request filters go first
1174        if let Err(e) = session
1175            .downstream_modules_ctx
1176            .request_header_filter(req)
1177            .await
1178        {
1179            return self
1180                .handle_error(
1181                    session,
1182                    &mut ctx,
1183                    e,
1184                    "Failed in downstream modules request filter:",
1185                )
1186                .await;
1187        }
1188
1189        match self.inner.request_filter(&mut session, &mut ctx).await {
1190            Ok(response_sent) => {
1191                if response_sent {
1192                    // TODO: log error
1193                    self.inner.logging(&mut session, None, &mut ctx).await;
1194                    self.cleanup_sub_req(&mut session);
1195                    let mut persistent_settings = HttpPersistentSettings::for_session(&session);
1196                    if let Some(uc) = self.inner.persist_connection_context(&session, &ctx) {
1197                        persistent_settings.set_user_context(uc);
1198                    }
1199                    return session
1200                        .downstream_session
1201                        .finish()
1202                        .await
1203                        .ok()
1204                        .flatten()
1205                        .map(|s| ReusedHttpStream::from_reusable_stream(s, persistent_settings));
1206                }
1207                /* else continue */
1208            }
1209            Err(e) => {
1210                return self
1211                    .handle_error(session, &mut ctx, e, "Fail to filter request:")
1212                    .await;
1213            }
1214        }
1215
1216        if let Some((reuse, err)) = self.proxy_cache(&mut session, &mut ctx).await {
1217            // cache hit
1218            return self.finish(session, &mut ctx, reuse, err).await;
1219        }
1220        // either uncacheable, or cache miss
1221
1222        // there should not be a write lock in the sub req ctx after this point
1223        self.cleanup_sub_req(&mut session);
1224
1225        // decide if the request is allowed to go to upstream
1226        match self
1227            .inner
1228            .proxy_upstream_filter(&mut session, &mut ctx)
1229            .await
1230        {
1231            Ok(proxy_to_upstream) => {
1232                if !proxy_to_upstream {
1233                    // The hook can choose to write its own response, but if it doesn't, we respond
1234                    // with a generic 502
1235                    if session.cache.enabled() {
1236                        // drop the cache lock that this request may be holding onto
1237                        session.cache.disable(NoCacheReason::DeclinedToUpstream);
1238                    }
1239                    if session.response_written().is_none() {
1240                        match session.write_response_header_ref(&BAD_GATEWAY, true).await {
1241                            Ok(()) => {}
1242                            Err(e) => {
1243                                return self
1244                                    .handle_error(
1245                                        session,
1246                                        &mut ctx,
1247                                        e,
1248                                        "Error responding with Bad Gateway:",
1249                                    )
1250                                    .await;
1251                            }
1252                        }
1253                    }
1254
1255                    return self.finish(session, &mut ctx, true, None).await;
1256                }
1257                /* else continue */
1258            }
1259            Err(e) => {
1260                if session.cache.enabled() {
1261                    session.cache.disable(NoCacheReason::InternalError);
1262                }
1263
1264                return self
1265                    .handle_error(
1266                        session,
1267                        &mut ctx,
1268                        e,
1269                        "Error deciding if we should proxy to upstream:",
1270                    )
1271                    .await;
1272            }
1273        }
1274
1275        let mut retries: usize = 0;
1276
1277        let mut server_reuse = false;
1278        let mut proxy_error: Option<Box<Error>> = None;
1279
1280        while retries < self.max_retries {
1281            retries += 1;
1282
1283            let (reuse, e) = self.proxy_to_upstream(&mut session, &mut ctx).await;
1284            server_reuse = reuse;
1285
1286            match e {
1287                Some(error) => {
1288                    let retry = error.retry();
1289                    // only log error that will be retried here, the final error will be logged below
1290                    if retry
1291                        && !self.inner.suppress_proxy_warn_log(
1292                            &session,
1293                            &ctx,
1294                            &error,
1295                            ProxyWarnLogContext::UpstreamRetry,
1296                        )
1297                    {
1298                        warn!(
1299                            "Fail to proxy: {}, tries: {}, retry: {}, {}",
1300                            error,
1301                            retries,
1302                            retry,
1303                            self.inner.request_summary(&session, &ctx)
1304                        );
1305                    }
1306                    proxy_error = Some(error);
1307                    if !retry {
1308                        break;
1309                    }
1310                }
1311                None => {
1312                    proxy_error = None;
1313                    break;
1314                }
1315            };
1316        }
1317
1318        // serve stale if error
1319        // Check both error and cache before calling the function because await is not cheap
1320        // allow unwrap until if let chains
1321        #[allow(clippy::unnecessary_unwrap)]
1322        let serve_stale_result = if proxy_error.is_some() && session.cache.can_serve_stale_error() {
1323            self.handle_stale_if_error(&mut session, &mut ctx, proxy_error.as_ref().unwrap())
1324                .await
1325        } else {
1326            None
1327        };
1328
1329        let final_error = if let Some((reuse, stale_cache_error)) = serve_stale_result {
1330            // don't reuse server conn if serve stale polluted it
1331            server_reuse = server_reuse && reuse;
1332            stale_cache_error
1333        } else {
1334            proxy_error
1335        };
1336
1337        if let Some(e) = final_error.as_ref() {
1338            // If we have errored and are still holding a cache lock, release it.
1339            if session.cache.enabled() {
1340                let reason = if *e.esource() == ErrorSource::Upstream {
1341                    NoCacheReason::UpstreamError
1342                } else {
1343                    NoCacheReason::InternalError
1344                };
1345                session.cache.disable(reason);
1346            }
1347            let res = self.inner.fail_to_proxy(&mut session, e, &mut ctx).await;
1348
1349            // final error will have > 0 status unless downstream connection is dead
1350            if !self.inner.suppress_error_log(&session, &ctx, e) {
1351                error!(
1352                    "Fail to proxy: {}, status: {}, tries: {}, retry: {}, {}",
1353                    e,
1354                    res.error_code,
1355                    retries,
1356                    false, // we never retry here
1357                    self.inner.request_summary(&session, &ctx),
1358                );
1359            }
1360        }
1361
1362        // logging() will be called in finish()
1363        self.finish(session, &mut ctx, server_reuse, final_error)
1364            .await
1365    }
1366
1367    async fn handle_error(
1368        &self,
1369        mut session: Session,
1370        ctx: &mut <SV as ProxyHttp>::CTX,
1371        e: Box<Error>,
1372        context: &str,
1373    ) -> Option<ReusedHttpStream>
1374    where
1375        SV: ProxyHttp + Send + Sync + 'static,
1376        <SV as ProxyHttp>::CTX: Send + Sync,
1377    {
1378        let res = self.inner.fail_to_proxy(&mut session, &e, ctx).await;
1379        if !self.inner.suppress_error_log(&session, ctx, &e) {
1380            error!(
1381                "{context} {}, status: {}, {}",
1382                e,
1383                res.error_code,
1384                self.inner.request_summary(&session, ctx)
1385            );
1386        }
1387        self.inner.logging(&mut session, Some(&e), ctx).await;
1388        self.cleanup_sub_req(&mut session);
1389
1390        session.downstream_session.on_proxy_failure(e);
1391
1392        if res.can_reuse_downstream {
1393            let mut persistent_settings = HttpPersistentSettings::for_session(&session);
1394            if let Some(uc) = self.inner.persist_connection_context(&session, ctx) {
1395                persistent_settings.set_user_context(uc);
1396            }
1397            session
1398                .downstream_session
1399                .finish()
1400                .await
1401                .ok()
1402                .flatten()
1403                .map(|s| ReusedHttpStream::from_reusable_stream(s, persistent_settings))
1404        } else {
1405            None
1406        }
1407    }
1408}
1409
1410/* Make process_subrequest() a trait to workaround https://github.com/rust-lang/rust/issues/78649
1411   if process_subrequest() is implemented as a member of HttpProxy, rust complains
1412
1413error[E0391]: cycle detected when computing type of `proxy_cache::<impl at pingora-proxy/src/proxy_cache.rs:7:1: 7:23>::proxy_cache::{opaque#0}`
1414   --> pingora-proxy/src/proxy_cache.rs:13:10
1415    |
141613  |     ) -> Option<(bool, Option<Box<Error>>)>
1417
1418*/
1419#[async_trait]
1420pub trait Subrequest {
1421    async fn process_subrequest(
1422        self: Arc<Self>,
1423        session: Box<HttpSession>,
1424        sub_req_ctx: Box<SubrequestCtx>,
1425    );
1426}
1427
1428#[async_trait]
1429impl<SV, C> Subrequest for HttpProxy<SV, C>
1430where
1431    SV: ProxyHttp + Send + Sync + 'static,
1432    <SV as ProxyHttp>::CTX: Send + Sync,
1433    C: custom::Connector,
1434{
1435    async fn process_subrequest(
1436        self: Arc<Self>,
1437        session: Box<HttpSession>,
1438        sub_req_ctx: Box<SubrequestCtx>,
1439    ) {
1440        debug!("starting subrequest");
1441
1442        let mut session = match self.handle_new_request(session).await {
1443            Some(downstream_session) => Session::new(
1444                downstream_session,
1445                &self.downstream_modules,
1446                #[cfg(feature = "upstream_modules")]
1447                &self.upstream_modules,
1448                self.shutdown_flag.clone(),
1449            ),
1450            None => return, // bad request
1451        };
1452
1453        // no real downstream to keepalive, but it doesn't matter what is set here because at the end
1454        // of this fn the dummy connection will be dropped
1455        session.set_keepalive(None);
1456
1457        session.subrequest_ctx.replace(sub_req_ctx);
1458        trace!("processing subrequest");
1459        let ctx = self.inner.new_ctx();
1460        self.process_request(session, ctx).await;
1461        trace!("subrequest done");
1462    }
1463}
1464
1465/// A handle to the underlying HTTP proxy app that allows spawning subrequests.
1466pub struct SubrequestSpawner {
1467    app: Arc<dyn Subrequest + Send + Sync>,
1468}
1469
1470/// A [`PreparedSubrequest`] that is ready to run.
1471pub struct PreparedSubrequest {
1472    app: Arc<dyn Subrequest + Send + Sync>,
1473    session: Box<HttpSession>,
1474    sub_req_ctx: Box<SubrequestCtx>,
1475}
1476
1477impl PreparedSubrequest {
1478    pub async fn run(self) {
1479        self.app
1480            .process_subrequest(self.session, self.sub_req_ctx)
1481            .await
1482    }
1483
1484    pub fn session(&self) -> &HttpSession {
1485        self.session.as_ref()
1486    }
1487
1488    pub fn session_mut(&mut self) -> &mut HttpSession {
1489        self.session.deref_mut()
1490    }
1491}
1492
1493impl SubrequestSpawner {
1494    /// Create a new [`SubrequestSpawner`].
1495    pub fn new(app: Arc<dyn Subrequest + Send + Sync>) -> SubrequestSpawner {
1496        SubrequestSpawner { app }
1497    }
1498
1499    /// Spawn a background subrequest and return a join handle.
1500    // TODO: allow configuring the subrequest session before use
1501    pub fn spawn_background_subrequest(
1502        &self,
1503        session: &HttpSession,
1504        ctx: SubrequestCtx,
1505    ) -> tokio::task::JoinHandle<()> {
1506        let new_app = self.app.clone(); // Clone the Arc
1507        let (mut session, handle) = subrequest::create_session(session);
1508        if ctx.body_mode() == BodyMode::NoBody {
1509            session
1510                .as_subrequest_mut()
1511                .expect("created subrequest session")
1512                .clear_request_body_headers();
1513        }
1514        let sub_req_ctx = Box::new(ctx);
1515        handle.drain_tasks();
1516        tokio::spawn(async move {
1517            new_app
1518                .process_subrequest(Box::new(session), sub_req_ctx)
1519                .await;
1520        })
1521    }
1522
1523    /// Create a subrequest that listens to `HttpTask`s sent from the returned `Sender`
1524    /// and sends `HttpTask`s to the returned `Receiver`.
1525    ///
1526    /// To run that subrequest, call `run()`.
1527    // TODO: allow configuring the subrequest session before use
1528    pub fn create_subrequest(
1529        &self,
1530        session: &HttpSession,
1531        ctx: SubrequestCtx,
1532    ) -> (PreparedSubrequest, SubrequestHandle) {
1533        let new_app = self.app.clone(); // Clone the Arc
1534        let (mut session, handle) = subrequest::create_session(session);
1535        if ctx.body_mode() == BodyMode::NoBody {
1536            session
1537                .as_subrequest_mut()
1538                .expect("created subrequest session")
1539                .clear_request_body_headers();
1540        }
1541        let sub_req_ctx = Box::new(ctx);
1542        (
1543            PreparedSubrequest {
1544                app: new_app,
1545                session: Box::new(session),
1546                sub_req_ctx,
1547            },
1548            handle,
1549        )
1550    }
1551}
1552
1553#[async_trait]
1554impl<SV, C> HttpServerApp for HttpProxy<SV, C>
1555where
1556    SV: ProxyHttp + Send + Sync + 'static,
1557    <SV as ProxyHttp>::CTX: Send + Sync,
1558    C: custom::Connector,
1559{
1560    async fn process_new_http(
1561        self: &Arc<Self>,
1562        mut session: HttpSession,
1563        shutdown: &ShutdownWatch,
1564    ) -> Option<ReusedHttpStream> {
1565        // Extract user context from the previous request before the session is moved into the Box
1566        let prev_user_ctx = session.take_connection_user_context();
1567
1568        let session = Box::new(session);
1569
1570        // TODO: keepalive pool, use stack
1571        let mut session = match self.handle_new_request(session).await {
1572            Some(downstream_session) => Session::new(
1573                downstream_session,
1574                &self.downstream_modules,
1575                #[cfg(feature = "upstream_modules")]
1576                &self.upstream_modules,
1577                self.shutdown_flag.clone(),
1578            ),
1579            None => return None, // bad request
1580        };
1581
1582        if *shutdown.borrow() {
1583            // stop downstream from reusing if this service is shutting down soon
1584            session.set_keepalive(None);
1585        }
1586
1587        let mut ctx = self.inner.new_ctx();
1588
1589        // Deliver user context from the previous request on this reused connection
1590        if let Some(prev_ctx) = prev_user_ctx {
1591            self.inner
1592                .on_connection_reuse(&mut session, &mut ctx, prev_ctx);
1593        }
1594
1595        self.process_request(session, ctx).await
1596    }
1597
1598    async fn http_cleanup(&self) {
1599        self.shutdown_flag.store(true, Ordering::Release);
1600        // Notify all keepalived requests blocking on read_request() to abort
1601        self.shutdown.notify_waiters();
1602    }
1603
1604    fn server_options(&self) -> Option<&HttpServerOptions> {
1605        self.server_options.as_ref()
1606    }
1607
1608    fn h2_options(&self) -> Option<H2Options> {
1609        self.h2_options.clone()
1610    }
1611    async fn process_custom_session(
1612        self: Arc<Self>,
1613        stream: Stream,
1614        shutdown: &ShutdownWatch,
1615    ) -> Option<Stream> {
1616        let app = self.clone();
1617
1618        let Some(process_custom_session) = app.process_custom_session.as_ref() else {
1619            warn!("custom was called on an empty on_custom");
1620            return None;
1621        };
1622
1623        process_custom_session(self.clone(), stream, shutdown).await
1624    }
1625
1626    // TODO implement h2_options
1627}
1628
1629use pingora_core::services::listening::{RuntimeOptsOverride, Service};
1630
1631/// Create an [`HttpProxy`] without wrapping it in a [`Service`].
1632///
1633/// This is useful when you need to integrate `HttpProxy` into a custom accept loop,
1634/// for example when implementing SNI-based routing that decides between TLS passthrough
1635/// and TLS termination on a single port.
1636///
1637/// The returned `HttpProxy` is fully initialized and ready to process requests via
1638/// [`HttpServerApp::process_new_http()`].
1639///
1640/// # Example
1641///
1642/// ```ignore
1643/// use pingora_proxy::http_proxy;
1644/// use std::sync::Arc;
1645///
1646/// // Create the proxy
1647/// let proxy = Arc::new(http_proxy(&server_conf, my_proxy_app));
1648///
1649/// // In your custom accept loop:
1650/// loop {
1651///     let (stream, addr) = listener.accept().await?;
1652///
1653///     // Peek SNI, decide routing...
1654///     if should_terminate_tls {
1655///         let tls_stream = my_acceptor.accept(stream).await?;
1656///         let session = HttpSession::new_http1(Box::new(tls_stream));
1657///         proxy.process_new_http(session, &shutdown).await;
1658///     }
1659/// }
1660/// ```
1661pub fn http_proxy<SV>(conf: &Arc<ServerConf>, inner: SV) -> HttpProxy<SV>
1662where
1663    SV: ProxyHttp,
1664{
1665    let mut proxy = HttpProxy::new(inner, conf.clone());
1666    proxy.handle_init_modules();
1667    proxy
1668}
1669
1670/// Create a [Service] from the user implemented [ProxyHttp].
1671///
1672/// The returned [Service] can be hosted by a [pingora_core::server::Server] directly.
1673pub fn http_proxy_service<SV>(conf: &Arc<ServerConf>, inner: SV) -> Service<HttpProxy<SV, ()>>
1674where
1675    SV: ProxyHttp,
1676{
1677    http_proxy_service_with_name(conf, inner, "Pingora HTTP Proxy Service")
1678}
1679
1680/// Create a [Service] from the user implemented [ProxyHttp].
1681///
1682/// The returned [Service] can be hosted by a [pingora_core::server::Server] directly.
1683pub fn http_proxy_service_with_name<SV>(
1684    conf: &Arc<ServerConf>,
1685    inner: SV,
1686    name: &str,
1687) -> Service<HttpProxy<SV, ()>>
1688where
1689    SV: ProxyHttp,
1690{
1691    let mut proxy = HttpProxy::new(inner, conf.clone());
1692    proxy.handle_init_modules();
1693    Service::new(name.to_string(), proxy)
1694}
1695
1696/// Create a [Service] from the user implemented [ProxyHttp].
1697///
1698/// The returned [Service] can be hosted by a [pingora_core::server::Server] directly.
1699pub fn http_proxy_service_with_name_custom<SV, C>(
1700    conf: &Arc<ServerConf>,
1701    inner: SV,
1702    name: &str,
1703    connector: C,
1704    on_custom: ProcessCustomSession<SV, C>,
1705) -> Service<HttpProxy<SV, C>>
1706where
1707    SV: ProxyHttp + Send + Sync + 'static,
1708    SV::CTX: Send + Sync + 'static,
1709    C: custom::Connector,
1710{
1711    let mut proxy =
1712        HttpProxy::new_custom(inner, conf.clone(), connector, Some(on_custom), None, None);
1713    proxy.handle_init_modules();
1714
1715    Service::new(name.to_string(), proxy)
1716}
1717
1718/// A builder for a [Service] that can be used to create a [HttpProxy] instance
1719///
1720/// The [ProxyServiceBuilder] can be used to construct a [HttpProxy] service with a custom name,
1721/// connector, and custom session handler.
1722///
1723pub struct ProxyServiceBuilder<SV, C>
1724where
1725    SV: ProxyHttp + Send + Sync + 'static,
1726    SV::CTX: Send + Sync + 'static,
1727    C: custom::Connector,
1728{
1729    conf: Arc<ServerConf>,
1730    inner: SV,
1731    name: String,
1732    connector: C,
1733    custom: Option<ProcessCustomSession<SV, C>>,
1734    server_options: Option<HttpServerOptions>,
1735    client_options: Option<ConnectorOptions>,
1736    runtime_opts_override: Option<RuntimeOptsOverride>,
1737}
1738
1739impl<SV> ProxyServiceBuilder<SV, ()>
1740where
1741    SV: ProxyHttp + Send + Sync + 'static,
1742    SV::CTX: Send + Sync + 'static,
1743{
1744    /// Create a new [ProxyServiceBuilder] with the given [ServerConf] and [ProxyHttp]
1745    /// implementation.
1746    ///
1747    /// The returned builder can be used to construct a [HttpProxy] service with a custom name,
1748    /// connector, and custom session handler.
1749    ///
1750    /// The [ProxyServiceBuilder] will default to using the [ProxyHttp] implementation and no custom
1751    /// session handler.
1752    ///
1753    pub fn new(conf: &Arc<ServerConf>, inner: SV) -> Self {
1754        ProxyServiceBuilder {
1755            conf: conf.clone(),
1756            inner,
1757            name: "Pingora HTTP Proxy Service".into(),
1758            connector: (),
1759            custom: None,
1760            server_options: None,
1761            client_options: None,
1762            runtime_opts_override: None,
1763        }
1764    }
1765}
1766
1767impl<SV, C> ProxyServiceBuilder<SV, C>
1768where
1769    SV: ProxyHttp + Send + Sync + 'static,
1770    SV::CTX: Send + Sync + 'static,
1771    C: custom::Connector,
1772{
1773    /// Sets the name of the [HttpProxy] service.
1774    pub fn name(mut self, name: impl AsRef<str>) -> Self {
1775        self.name = name.as_ref().to_owned();
1776        self
1777    }
1778
1779    /// Set a custom connector and custom session handler for the [ProxyServiceBuilder].
1780    ///
1781    /// The custom connector is used to establish a connection to the upstream server.
1782    ///
1783    /// The custom session handler is used to handle custom protocol specific logic
1784    /// between the proxy and the upstream server.
1785    ///
1786    /// Returns a new [ProxyServiceBuilder] with the custom connector and session handler.
1787    pub fn custom<C2: custom::Connector>(
1788        self,
1789        connector: C2,
1790        on_custom: ProcessCustomSession<SV, C2>,
1791    ) -> ProxyServiceBuilder<SV, C2> {
1792        let Self {
1793            conf,
1794            inner,
1795            name,
1796            server_options,
1797            client_options,
1798            runtime_opts_override,
1799            ..
1800        } = self;
1801        ProxyServiceBuilder {
1802            conf,
1803            inner,
1804            name,
1805            connector,
1806            custom: Some(on_custom),
1807            server_options,
1808            client_options,
1809            runtime_opts_override,
1810        }
1811    }
1812
1813    /// Set the upstream client connector options for the [ProxyServiceBuilder].
1814    ///
1815    /// Returns a new [ProxyServiceBuilder] with the upstream client connector options set.
1816    pub fn client_options(mut self, options: ConnectorOptions) -> Self {
1817        self.client_options = Some(options);
1818        self
1819    }
1820
1821    /// Set the server options for the [ProxyServiceBuilder].
1822    ///
1823    /// Returns a new [ProxyServiceBuilder] with the server options set.
1824    pub fn server_options(mut self, options: HttpServerOptions) -> Self {
1825        self.server_options = Some(options);
1826        self
1827    }
1828
1829    /// Set a runtime options override for the [Service] built by this builder.
1830    ///
1831    /// Returning [`None`] from the override uses the global runtime options.
1832    pub fn runtime_opts_override<F>(mut self, override_fn: F) -> Self
1833    where
1834        F: Fn(&RuntimeOpts) -> Option<RuntimeOpts> + Send + Sync + 'static,
1835    {
1836        self.runtime_opts_override = Some(Arc::new(override_fn));
1837        self
1838    }
1839
1840    /// Builds a new [Service] from the [ProxyServiceBuilder].
1841    ///
1842    /// This function takes ownership of the [ProxyServiceBuilder] and returns a new [Service] with
1843    /// a fully initialized [HttpProxy].
1844    ///
1845    /// The returned [Service] is ready to be used by a [pingora_core::server::Server].
1846    pub fn build(self) -> Service<HttpProxy<SV, C>> {
1847        let Self {
1848            conf,
1849            inner,
1850            name,
1851            connector,
1852            custom,
1853            server_options,
1854            client_options,
1855            runtime_opts_override,
1856        } = self;
1857
1858        let mut proxy = HttpProxy::new_custom(
1859            inner,
1860            conf,
1861            connector,
1862            custom,
1863            server_options,
1864            client_options,
1865        );
1866
1867        proxy.handle_init_modules();
1868        let mut service = Service::new(name, proxy);
1869        if let Some(runtime_opts_override) = runtime_opts_override {
1870            service.set_runtime_opts_override(runtime_opts_override);
1871        }
1872        service
1873    }
1874}
1875
1876#[cfg(test)]
1877mod tests {
1878    use super::*;
1879    use pingora_core::modules::http::{HttpModule, HttpModuleBuilder};
1880    use pingora_core::protocols::l4::stream::Stream as L4Stream;
1881    use pingora_core::protocols::l4::virt::{VirtualSockOpt, VirtualSocket, VirtualSocketStream};
1882    use pingora_error::RetryType;
1883    use std::pin::Pin;
1884    use std::sync::atomic::{AtomicBool, Ordering};
1885    use std::sync::Mutex;
1886    use std::task::{Context, Poll};
1887    use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
1888
1889    #[derive(Debug)]
1890    struct StaticVirtualSocket {
1891        read_buf: Vec<u8>,
1892        read_pos: usize,
1893        write_buf: Arc<Mutex<Vec<u8>>>,
1894    }
1895
1896    impl StaticVirtualSocket {
1897        fn new(read_buf: &[u8], write_buf: Arc<Mutex<Vec<u8>>>) -> Self {
1898            Self {
1899                read_buf: read_buf.to_vec(),
1900                read_pos: 0,
1901                write_buf,
1902            }
1903        }
1904    }
1905
1906    impl AsyncRead for StaticVirtualSocket {
1907        fn poll_read(
1908            mut self: Pin<&mut Self>,
1909            _cx: &mut Context<'_>,
1910            buf: &mut ReadBuf<'_>,
1911        ) -> Poll<std::io::Result<()>> {
1912            let remaining = self.read_buf.len() - self.read_pos;
1913            let to_read = remaining.min(buf.remaining());
1914            if to_read > 0 {
1915                buf.put_slice(&self.read_buf[self.read_pos..self.read_pos + to_read]);
1916                self.read_pos += to_read;
1917            }
1918            Poll::Ready(Ok(()))
1919        }
1920    }
1921
1922    impl AsyncWrite for StaticVirtualSocket {
1923        fn poll_write(
1924            self: Pin<&mut Self>,
1925            _cx: &mut Context<'_>,
1926            buf: &[u8],
1927        ) -> Poll<std::io::Result<usize>> {
1928            self.write_buf.lock().unwrap().extend_from_slice(buf);
1929            Poll::Ready(Ok(buf.len()))
1930        }
1931
1932        fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
1933            Poll::Ready(Ok(()))
1934        }
1935
1936        fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
1937            Poll::Ready(Ok(()))
1938        }
1939    }
1940
1941    impl VirtualSocket for StaticVirtualSocket {
1942        fn set_socket_option(&self, _opt: VirtualSockOpt) -> std::io::Result<()> {
1943            Ok(())
1944        }
1945    }
1946
1947    async fn new_request_session(request: &[u8], written: Arc<Mutex<Vec<u8>>>) -> Session {
1948        let socket = StaticVirtualSocket::new(request, written);
1949        let stream = L4Stream::from(VirtualSocketStream::new(Box::new(socket)));
1950        let mut session = Session::new_h1(Box::new(stream));
1951        session.read_request().await.unwrap();
1952        session
1953    }
1954
1955    async fn new_upgrade_request_session(written: Arc<Mutex<Vec<u8>>>) -> Session {
1956        new_request_session(
1957            b"GET / HTTP/1.1\r\nHost: example.com\r\nUpgrade: websocket\r\nConnection: Upgrade\r\n\r\n",
1958            written,
1959        )
1960        .await
1961    }
1962
1963    struct DefaultRetryProxy;
1964
1965    #[async_trait]
1966    impl ProxyHttp for DefaultRetryProxy {
1967        type CTX = ();
1968
1969        fn new_ctx(&self) -> Self::CTX {}
1970
1971        async fn upstream_peer(
1972            &self,
1973            _session: &mut Session,
1974            _ctx: &mut Self::CTX,
1975        ) -> Result<Box<HttpPeer>> {
1976            unreachable!()
1977        }
1978    }
1979
1980    fn default_policy_would_retry_for_session(
1981        session: &mut Session,
1982        retry: RetryType,
1983        client_reused: bool,
1984    ) -> bool {
1985        let mut error = Error::new_up(ReadError);
1986        error.retry = retry;
1987
1988        DefaultRetryProxy
1989            .error_while_proxy(
1990                &HttpPeer::new("127.0.0.1:80", false, "".to_string()),
1991                session,
1992                error,
1993                &mut (),
1994                client_reused,
1995            )
1996            .retry()
1997    }
1998
1999    async fn default_policy_would_retry(
2000        request: &[u8],
2001        retry: RetryType,
2002        client_reused: bool,
2003    ) -> bool {
2004        let mut session = new_request_session(request, Arc::new(Mutex::new(Vec::new()))).await;
2005        default_policy_would_retry_for_session(&mut session, retry, client_reused)
2006    }
2007
2008    async fn buffered_put_session(body_len: usize) -> Session {
2009        let mut request =
2010            format!("PUT / HTTP/1.1\r\nHost: example.com\r\nContent-Length: {body_len}\r\n\r\n")
2011                .into_bytes();
2012        request.resize(request.len() + body_len, b'a');
2013
2014        let mut session = new_request_session(&request, Arc::new(Mutex::new(Vec::new()))).await;
2015        session.enable_retry_buffering();
2016        while session.read_request_body().await.unwrap().is_some() {}
2017        session
2018    }
2019
2020    #[tokio::test]
2021    async fn default_retry_policy_requires_an_idempotent_method() {
2022        let decided_retry = RetryType::Decided(true);
2023        assert!(
2024            default_policy_would_retry(
2025                b"GET / HTTP/1.1\r\nHost: example.com\r\n\r\n",
2026                decided_retry,
2027                false,
2028            )
2029            .await
2030        );
2031        assert!(
2032            default_policy_would_retry(
2033                b"PUT / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 0\r\n\r\n",
2034                decided_retry,
2035                false,
2036            )
2037            .await
2038        );
2039        assert!(
2040            !default_policy_would_retry(
2041                b"POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 0\r\n\r\n",
2042                decided_retry,
2043                false,
2044            )
2045            .await
2046        );
2047        assert!(
2048            !default_policy_would_retry(
2049                b"PATCH / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 0\r\n\r\n",
2050                decided_retry,
2051                false,
2052            )
2053            .await
2054        );
2055    }
2056
2057    #[tokio::test]
2058    async fn default_retry_policy_resolves_reused_only() {
2059        let request = b"GET / HTTP/1.1\r\nHost: example.com\r\n\r\n";
2060
2061        assert!(default_policy_would_retry(request, RetryType::ReusedOnly, true).await);
2062        assert!(!default_policy_would_retry(request, RetryType::ReusedOnly, false).await);
2063    }
2064
2065    #[tokio::test]
2066    async fn default_retry_policy_requires_an_untruncated_body_buffer() {
2067        let mut complete = buffered_put_session(64 * 1024).await;
2068        assert!(!complete.retry_buffer_truncated());
2069        assert!(default_policy_would_retry_for_session(
2070            &mut complete,
2071            RetryType::Decided(true),
2072            false,
2073        ));
2074
2075        let mut truncated = buffered_put_session(64 * 1024 + 1).await;
2076        assert!(truncated.retry_buffer_truncated());
2077        assert!(!default_policy_would_retry_for_session(
2078            &mut truncated,
2079            RetryType::Decided(true),
2080            false,
2081        ));
2082        assert!(!default_policy_would_retry_for_session(
2083            &mut truncated,
2084            RetryType::ReusedOnly,
2085            true,
2086        ));
2087    }
2088
2089    fn upgrade_response_header() -> ResponseHeader {
2090        let mut header =
2091            ResponseHeader::build(http::StatusCode::SWITCHING_PROTOCOLS, Some(2)).unwrap();
2092        header
2093            .insert_header(http::header::UPGRADE, "websocket")
2094            .unwrap();
2095        header
2096            .insert_header(http::header::CONNECTION, "Upgrade")
2097            .unwrap();
2098        header
2099    }
2100
2101    struct SwitchTo101Module;
2102
2103    #[async_trait]
2104    impl HttpModule for SwitchTo101Module {
2105        async fn response_header_filter(
2106            &mut self,
2107            resp: &mut ResponseHeader,
2108            _end_of_stream: bool,
2109        ) -> Result<()> {
2110            resp.set_status(http::StatusCode::SWITCHING_PROTOCOLS)?;
2111            resp.set_version(Version::HTTP_11);
2112            Ok(())
2113        }
2114
2115        fn as_any(&self) -> &dyn std::any::Any {
2116            self
2117        }
2118
2119        fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
2120            self
2121        }
2122    }
2123
2124    struct SwitchTo101ModuleBuilder;
2125
2126    impl HttpModuleBuilder for SwitchTo101ModuleBuilder {
2127        fn init(&self) -> pingora_core::modules::http::Module {
2128            Box::new(SwitchTo101Module)
2129        }
2130    }
2131
2132    struct DoneBytesModule {
2133        called: Arc<AtomicBool>,
2134    }
2135
2136    #[async_trait]
2137    impl HttpModule for DoneBytesModule {
2138        fn response_done_filter(&mut self) -> Result<Option<Bytes>> {
2139            self.called.store(true, Ordering::Release);
2140            Ok(Some(Bytes::from_static(b"hello")))
2141        }
2142
2143        fn as_any(&self) -> &dyn std::any::Any {
2144            self
2145        }
2146
2147        fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
2148            self
2149        }
2150    }
2151
2152    struct DoneBytesModuleBuilder {
2153        called: Arc<AtomicBool>,
2154    }
2155
2156    impl HttpModuleBuilder for DoneBytesModuleBuilder {
2157        fn init(&self) -> pingora_core::modules::http::Module {
2158            Box::new(DoneBytesModule {
2159                called: self.called.clone(),
2160            })
2161        }
2162    }
2163
2164    struct DoneEmptyModule {
2165        called: Arc<AtomicBool>,
2166    }
2167
2168    impl HttpModule for DoneEmptyModule {
2169        fn response_done_filter(&mut self) -> Result<Option<Bytes>> {
2170            self.called.store(true, Ordering::Release);
2171            Ok(None)
2172        }
2173
2174        fn as_any(&self) -> &dyn std::any::Any {
2175            self
2176        }
2177
2178        fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
2179            self
2180        }
2181    }
2182
2183    struct DoneEmptyModuleBuilder {
2184        called: Arc<AtomicBool>,
2185    }
2186
2187    impl HttpModuleBuilder for DoneEmptyModuleBuilder {
2188        fn init(&self) -> pingora_core::modules::http::Module {
2189            Box::new(DoneEmptyModule {
2190                called: self.called.clone(),
2191            })
2192        }
2193    }
2194
2195    fn assert_raw_upgrade_payload(written: &[u8]) {
2196        assert!(
2197            written.starts_with(b"HTTP/1.1 101 Switching Protocols\r\n"),
2198            "unexpected response: {:?}",
2199            String::from_utf8_lossy(written)
2200        );
2201        assert!(
2202            written.ends_with(b"\r\n\r\nhello"),
2203            "upgrade payload should be written as raw tunneled bytes: {:?}",
2204            String::from_utf8_lossy(written)
2205        );
2206        assert!(
2207            !written
2208                .windows(b"\r\n5\r\nhello".len())
2209                .any(|w| w == b"\r\n5\r\nhello"),
2210            "upgrade payload must not be chunk framed: {:?}",
2211            String::from_utf8_lossy(written)
2212        );
2213    }
2214
2215    #[tokio::test]
2216    async fn write_response_tasks_rejects_body_after_101() {
2217        let written = Arc::new(Mutex::new(Vec::new()));
2218        let mut session = new_upgrade_request_session(written.clone()).await;
2219
2220        let err = session
2221            .write_response_tasks(vec![
2222                HttpTask::Header(Box::new(upgrade_response_header()), false),
2223                HttpTask::Body(Some(Bytes::from_static(b"hello")), true),
2224            ])
2225            .await
2226            .unwrap_err();
2227
2228        assert_eq!(err.etype(), &InvalidHTTPHeader);
2229        assert_eq!(err.esource(), &ErrorSource::Internal);
2230        assert!(written.lock().unwrap().is_empty());
2231    }
2232
2233    #[tokio::test]
2234    async fn write_response_tasks_allows_upgraded_body_after_101() {
2235        let written = Arc::new(Mutex::new(Vec::new()));
2236        let mut session = new_upgrade_request_session(written.clone()).await;
2237
2238        let response_done = session
2239            .write_response_tasks(vec![
2240                HttpTask::Header(Box::new(upgrade_response_header()), false),
2241                HttpTask::UpgradedBody(Some(Bytes::from_static(b"hello")), true),
2242            ])
2243            .await
2244            .unwrap();
2245
2246        assert!(response_done);
2247        let written = written.lock().unwrap().clone();
2248        assert_raw_upgrade_payload(&written);
2249    }
2250
2251    #[tokio::test]
2252    async fn write_response_tasks_rejects_upgraded_body_before_101() {
2253        let written = Arc::new(Mutex::new(Vec::new()));
2254        let mut session = new_upgrade_request_session(written.clone()).await;
2255        session.set_upstream_h1_upgrade_request_status(true);
2256
2257        let err = session
2258            .write_response_tasks(vec![HttpTask::UpgradedBody(
2259                Some(Bytes::from_static(b"hello")),
2260                true,
2261            )])
2262            .await
2263            .unwrap_err();
2264
2265        assert_eq!(err.etype(), &InvalidHTTPHeader);
2266        assert_eq!(err.esource(), &ErrorSource::Internal);
2267        assert!(written.lock().unwrap().is_empty());
2268    }
2269
2270    #[tokio::test]
2271    async fn write_response_tasks_rejects_trailer_after_101() {
2272        let written = Arc::new(Mutex::new(Vec::new()));
2273        let mut session = new_upgrade_request_session(written.clone()).await;
2274
2275        let err = session
2276            .write_response_tasks(vec![
2277                HttpTask::Header(Box::new(upgrade_response_header()), false),
2278                HttpTask::Trailer(Some(Box::new(http::HeaderMap::new()))),
2279            ])
2280            .await
2281            .unwrap_err();
2282
2283        assert_eq!(err.etype(), &InvalidHTTPHeader);
2284        assert_eq!(err.esource(), &ErrorSource::Internal);
2285        assert!(written.lock().unwrap().is_empty());
2286    }
2287
2288    #[tokio::test]
2289    async fn write_response_tasks_runs_done_filter_after_101_as_upgraded_body() {
2290        let written = Arc::new(Mutex::new(Vec::new()));
2291        let called = Arc::new(AtomicBool::new(false));
2292        let socket = StaticVirtualSocket::new(
2293            b"GET / HTTP/1.1\r\nHost: example.com\r\nUpgrade: websocket\r\nConnection: Upgrade\r\n\r\n",
2294            written.clone(),
2295        );
2296        let stream = L4Stream::from(VirtualSocketStream::new(Box::new(socket)));
2297        let mut modules = HttpModules::new();
2298        modules.add_module(Box::new(DoneBytesModuleBuilder {
2299            called: called.clone(),
2300        }));
2301
2302        let mut session = Session::new_h1_with_modules(Box::new(stream), &modules);
2303        session.read_request().await.unwrap();
2304
2305        let response_done = session
2306            .write_response_tasks(vec![
2307                HttpTask::Header(Box::new(upgrade_response_header()), false),
2308                HttpTask::Done,
2309            ])
2310            .await
2311            .unwrap();
2312
2313        assert!(response_done);
2314        assert!(called.load(Ordering::Acquire));
2315        let written = written.lock().unwrap().clone();
2316        assert_raw_upgrade_payload(&written);
2317    }
2318
2319    #[tokio::test]
2320    async fn write_response_tasks_allows_empty_done_after_101() {
2321        let written = Arc::new(Mutex::new(Vec::new()));
2322        let called = Arc::new(AtomicBool::new(false));
2323        let socket = StaticVirtualSocket::new(
2324            b"GET / HTTP/1.1\r\nHost: example.com\r\nUpgrade: websocket\r\nConnection: Upgrade\r\n\r\n",
2325            written.clone(),
2326        );
2327        let stream = L4Stream::from(VirtualSocketStream::new(Box::new(socket)));
2328        let mut modules = HttpModules::new();
2329        modules.add_module(Box::new(DoneEmptyModuleBuilder {
2330            called: called.clone(),
2331        }));
2332
2333        let mut session = Session::new_h1_with_modules(Box::new(stream), &modules);
2334        session.read_request().await.unwrap();
2335
2336        let response_done = session
2337            .write_response_tasks(vec![
2338                HttpTask::Header(Box::new(upgrade_response_header()), false),
2339                HttpTask::Done,
2340            ])
2341            .await
2342            .unwrap();
2343
2344        assert!(response_done);
2345        assert!(called.load(Ordering::Acquire));
2346        let written = written.lock().unwrap().clone();
2347        assert!(
2348            written.starts_with(b"HTTP/1.1 101 Switching Protocols\r\n"),
2349            "unexpected response: {:?}",
2350            String::from_utf8_lossy(&written)
2351        );
2352        assert!(
2353            written.ends_with(b"\r\n\r\n"),
2354            "empty Done filter should only finish the upgraded response: {:?}",
2355            String::from_utf8_lossy(&written)
2356        );
2357    }
2358
2359    #[tokio::test]
2360    async fn write_response_tasks_rejects_module_created_101_with_upgrade_mismatch() {
2361        let written = Arc::new(Mutex::new(Vec::new()));
2362        let socket = StaticVirtualSocket::new(
2363            b"GET / HTTP/1.1\r\nHost: example.com\r\nUpgrade: websocket\r\nConnection: Upgrade\r\n\r\n",
2364            written.clone(),
2365        );
2366        let stream = L4Stream::from(VirtualSocketStream::new(Box::new(socket)));
2367        let mut modules = HttpModules::new();
2368        modules.add_module(Box::new(SwitchTo101ModuleBuilder));
2369
2370        let mut session = Session::new_h1_with_modules(Box::new(stream), &modules);
2371        session.read_request().await.unwrap();
2372        session.h1_upgrade_request_status = H1UpgradeRequestStatus {
2373            upstream: Some(false),
2374        };
2375
2376        let err = session
2377            .write_response_tasks(vec![
2378                HttpTask::Header(
2379                    Box::new(ResponseHeader::build(200, Some(0)).unwrap()),
2380                    false,
2381                ),
2382                HttpTask::Body(Some(Bytes::from_static(b"hello")), true),
2383            ])
2384            .await
2385            .unwrap_err();
2386
2387        assert_eq!(err.etype(), &InvalidHTTPHeader);
2388        assert_eq!(err.esource(), &ErrorSource::Internal);
2389        assert!(written.lock().unwrap().is_empty());
2390    }
2391
2392    #[tokio::test]
2393    async fn write_response_tasks_rejects_module_created_101_before_body() {
2394        let written = Arc::new(Mutex::new(Vec::new()));
2395        let socket = StaticVirtualSocket::new(
2396            b"GET / HTTP/1.1\r\nHost: example.com\r\nUpgrade: websocket\r\nConnection: Upgrade\r\n\r\n",
2397            written.clone(),
2398        );
2399        let stream = L4Stream::from(VirtualSocketStream::new(Box::new(socket)));
2400        let mut modules = HttpModules::new();
2401        modules.add_module(Box::new(SwitchTo101ModuleBuilder));
2402
2403        let mut session = Session::new_h1_with_modules(Box::new(stream), &modules);
2404        session.read_request().await.unwrap();
2405
2406        let err = session
2407            .write_response_tasks(vec![
2408                HttpTask::Header(
2409                    Box::new(ResponseHeader::build(200, Some(0)).unwrap()),
2410                    false,
2411                ),
2412                HttpTask::Body(Some(Bytes::from_static(b"hello")), true),
2413            ])
2414            .await
2415            .unwrap_err();
2416
2417        assert_eq!(err.etype(), &InvalidHTTPHeader);
2418        assert_eq!(err.esource(), &ErrorSource::Internal);
2419        assert!(written.lock().unwrap().is_empty());
2420    }
2421
2422    #[tokio::test]
2423    async fn send_downstream_proxy_task_rejects_body_after_101() {
2424        let written = Arc::new(Mutex::new(Vec::new()));
2425        let mut session = new_upgrade_request_session(written.clone()).await;
2426        session.set_proxy_tasks_enabled(true);
2427
2428        session
2429            .send_downstream_proxy_task(HttpTask::Header(
2430                Box::new(upgrade_response_header()),
2431                false,
2432            ))
2433            .await
2434            .unwrap();
2435        let err = session
2436            .send_downstream_proxy_task(HttpTask::Body(Some(Bytes::from_static(b"hello")), true))
2437            .await
2438            .unwrap_err();
2439
2440        assert_eq!(err.etype(), &InvalidHTTPHeader);
2441        assert_eq!(err.esource(), &ErrorSource::Internal);
2442        assert!(written.lock().unwrap().is_empty());
2443    }
2444
2445    #[tokio::test]
2446    async fn send_downstream_proxy_task_allows_upgraded_body_after_101() {
2447        let written = Arc::new(Mutex::new(Vec::new()));
2448        let mut session = new_upgrade_request_session(written.clone()).await;
2449        session.set_proxy_tasks_enabled(true);
2450
2451        session
2452            .send_downstream_proxy_task(HttpTask::Header(
2453                Box::new(upgrade_response_header()),
2454                false,
2455            ))
2456            .await
2457            .unwrap();
2458        session
2459            .send_downstream_proxy_task(HttpTask::UpgradedBody(
2460                Some(Bytes::from_static(b"hello")),
2461                true,
2462            ))
2463            .await
2464            .unwrap();
2465
2466        let response_done = session.write_downstream_proxy_tasks().await.unwrap();
2467
2468        assert!(response_done);
2469        let written = written.lock().unwrap().clone();
2470        assert_raw_upgrade_payload(&written);
2471    }
2472
2473    #[tokio::test]
2474    async fn send_downstream_proxy_task_rejects_upgraded_body_before_101() {
2475        let written = Arc::new(Mutex::new(Vec::new()));
2476        let mut session = new_upgrade_request_session(written.clone()).await;
2477        session.set_upstream_h1_upgrade_request_status(true);
2478        session.set_proxy_tasks_enabled(true);
2479
2480        let err = session
2481            .send_downstream_proxy_task(HttpTask::UpgradedBody(
2482                Some(Bytes::from_static(b"hello")),
2483                true,
2484            ))
2485            .await
2486            .unwrap_err();
2487
2488        assert_eq!(err.etype(), &InvalidHTTPHeader);
2489        assert_eq!(err.esource(), &ErrorSource::Internal);
2490        assert!(!session.has_pending_downstream_tasks());
2491        assert!(written.lock().unwrap().is_empty());
2492    }
2493
2494    #[tokio::test]
2495    async fn send_downstream_proxy_task_runs_done_filter_after_101_as_upgraded_body() {
2496        let written = Arc::new(Mutex::new(Vec::new()));
2497        let called = Arc::new(AtomicBool::new(false));
2498        let socket = StaticVirtualSocket::new(
2499            b"GET / HTTP/1.1\r\nHost: example.com\r\nUpgrade: websocket\r\nConnection: Upgrade\r\n\r\n",
2500            written.clone(),
2501        );
2502        let stream = L4Stream::from(VirtualSocketStream::new(Box::new(socket)));
2503        let mut modules = HttpModules::new();
2504        modules.add_module(Box::new(DoneBytesModuleBuilder {
2505            called: called.clone(),
2506        }));
2507
2508        let mut session = Session::new_h1_with_modules(Box::new(stream), &modules);
2509        session.read_request().await.unwrap();
2510        session.set_proxy_tasks_enabled(true);
2511
2512        session
2513            .send_downstream_proxy_task(HttpTask::Header(
2514                Box::new(upgrade_response_header()),
2515                false,
2516            ))
2517            .await
2518            .unwrap();
2519        session
2520            .send_downstream_proxy_task(HttpTask::Done)
2521            .await
2522            .unwrap();
2523
2524        let response_done = session.write_downstream_proxy_tasks().await.unwrap();
2525
2526        assert!(response_done);
2527        assert!(called.load(Ordering::Acquire));
2528        let written = written.lock().unwrap().clone();
2529        assert_raw_upgrade_payload(&written);
2530    }
2531
2532    /// A socket whose reads never complete, like an idle keep-alive connection
2533    /// waiting for its next request.
2534    #[derive(Debug)]
2535    struct PendingVirtualSocket;
2536
2537    impl AsyncRead for PendingVirtualSocket {
2538        fn poll_read(
2539            self: Pin<&mut Self>,
2540            _cx: &mut Context<'_>,
2541            _buf: &mut ReadBuf<'_>,
2542        ) -> Poll<std::io::Result<()>> {
2543            Poll::Pending
2544        }
2545    }
2546
2547    impl AsyncWrite for PendingVirtualSocket {
2548        fn poll_write(
2549            self: Pin<&mut Self>,
2550            _cx: &mut Context<'_>,
2551            buf: &[u8],
2552        ) -> Poll<std::io::Result<usize>> {
2553            Poll::Ready(Ok(buf.len()))
2554        }
2555
2556        fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
2557            Poll::Ready(Ok(()))
2558        }
2559
2560        fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
2561            Poll::Ready(Ok(()))
2562        }
2563    }
2564
2565    impl VirtualSocket for PendingVirtualSocket {
2566        fn set_socket_option(&self, _opt: VirtualSockOpt) -> std::io::Result<()> {
2567            Ok(())
2568        }
2569    }
2570
2571    struct NoopProxy;
2572
2573    #[async_trait]
2574    impl ProxyHttp for NoopProxy {
2575        type CTX = ();
2576        fn new_ctx(&self) -> Self::CTX {}
2577        async fn upstream_peer(
2578            &self,
2579            _session: &mut Session,
2580            _ctx: &mut Self::CTX,
2581        ) -> Result<Box<HttpPeer>> {
2582            Err(Error::new(InternalError))
2583        }
2584    }
2585
2586    fn pending_session() -> Box<HttpSession> {
2587        let stream = L4Stream::from(VirtualSocketStream::new(Box::new(PendingVirtualSocket)));
2588        Box::new(HttpSession::new_http1(Box::new(stream)))
2589    }
2590
2591    #[tokio::test(flavor = "multi_thread", worker_threads = 4)]
2592    async fn shutdown_wakes_parked_read_requests() {
2593        let conf = ServerConf {
2594            threads: 4,
2595            ..ServerConf::default()
2596        };
2597        let proxy = Arc::new(HttpProxy::new(NoopProxy, Arc::new(conf)));
2598        let handles: Vec<_> = (0..8)
2599            .map(|_| {
2600                let proxy = proxy.clone();
2601                tokio::spawn(async move { proxy.handle_new_request(pending_session()).await })
2602            })
2603            .collect();
2604        // let the tasks park in read_request()
2605        time::sleep(Duration::from_millis(50)).await;
2606        proxy.http_cleanup().await;
2607        for handle in handles {
2608            let session = time::timeout(Duration::from_secs(5), handle)
2609                .await
2610                .expect("shutdown did not wake the parked read")
2611                .unwrap();
2612            assert!(session.is_none());
2613        }
2614    }
2615
2616    #[tokio::test]
2617    async fn shutdown_before_read_request_parks_returns_immediately() {
2618        let proxy = Arc::new(HttpProxy::new(NoopProxy, Arc::new(ServerConf::default())));
2619        proxy.http_cleanup().await;
2620        // a request that parks after notify_waiters() already fired must not
2621        // wait for a notification that will never come
2622        let session = time::timeout(
2623            Duration::from_secs(5),
2624            proxy.handle_new_request(pending_session()),
2625        )
2626        .await
2627        .expect("read_request parked after shutdown");
2628        assert!(session.is_none());
2629    }
2630}