Skip to main content

pingora_proxy/
proxy_trait.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
15use super::*;
16use pingora_cache::{
17    key::HashBinary,
18    CacheKey, CacheMeta, ForcedFreshness, HitHandler, PurgeAction,
19    RespCacheable::{self, *},
20};
21use proxy_cache::range_filter::{self};
22use std::any::Any;
23use std::time::Duration;
24
25/// Context for proxy warning logs that can be suppressed by
26/// [`ProxyHttp::suppress_proxy_warn_log`].
27///
28/// These contexts are distinct from final proxy errors, which are handled by
29/// [`ProxyHttp::suppress_error_log`].
30///
31/// Experimental: this API may change or be removed until indicated otherwise.
32#[derive(Clone, Copy, Debug, Eq, PartialEq)]
33#[non_exhaustive]
34pub enum ProxyWarnLogContext {
35    /// A proxy upstream attempt failed with a retryable error.
36    UpstreamRetry,
37    /// A downstream error was ignored so cache fill could continue.
38    DownstreamCache,
39}
40
41/// The interface to control the HTTP proxy
42///
43/// The methods in [ProxyHttp] are filters/callbacks which will be performed on all requests at their
44/// particular stage (if applicable).
45///
46/// If any of the filters returns [Result::Err], the request will fail, and the error will be logged.
47#[cfg_attr(not(doc_async_trait), async_trait)]
48pub trait ProxyHttp {
49    /// The per request object to share state across the different filters
50    type CTX;
51
52    /// Define how the `ctx` should be created.
53    fn new_ctx(&self) -> Self::CTX;
54
55    /// Define where the proxy should send the request to.
56    ///
57    /// The returned [HttpPeer] contains the information regarding where and how this request should
58    /// be forwarded to.
59    async fn upstream_peer(
60        &self,
61        session: &mut Session,
62        ctx: &mut Self::CTX,
63    ) -> Result<Box<HttpPeer>>;
64
65    /// Set up downstream modules.
66    ///
67    /// In this phase, users can add or configure [HttpModules] before the server starts up.
68    ///
69    /// In the default implementation of this method, [ResponseCompressionBuilder] is added
70    /// and disabled.
71    fn init_downstream_modules(&self, modules: &mut HttpModules) {
72        // Add disabled downstream compression module by default
73        modules.add_module(ResponseCompressionBuilder::enable(0));
74    }
75
76    /// Set up upstream modules.
77    ///
78    /// In this phase, users can add [HttpModules] that will process upstream responses
79    /// **before** `upstream_compression`. This is the correct place to register modules
80    /// that need to observe the raw (pre-compression) upstream response body, such as
81    /// a dictionary store for shared dictionary compression.
82    ///
83    /// Upstream modules are ordered by [`HttpModuleBuilder::order()`]: higher values run
84    /// first. They are invoked on each upstream response task (header, body, trailers)
85    /// before `upstream_compression` processes the task.
86    ///
87    /// By default this method does nothing.
88    ///
89    /// This method requires the `upstream_modules` feature to be enabled.
90    #[cfg(feature = "upstream_modules")]
91    fn init_upstream_modules(&self, _modules: &mut HttpModules) {}
92
93    /// Handle the incoming request.
94    ///
95    /// In this phase, users can parse, validate, rate limit, perform access control and/or
96    /// return a response for this request.
97    ///
98    /// If the user already sent a response to this request, an `Ok(true)` should be returned so that
99    /// the proxy would exit. The proxy continues to the next phases when `Ok(false)` is returned.
100    ///
101    /// By default this filter does nothing and returns `Ok(false)`.
102    async fn request_filter(&self, _session: &mut Session, _ctx: &mut Self::CTX) -> Result<bool>
103    where
104        Self::CTX: Send + Sync,
105    {
106        Ok(false)
107    }
108
109    /// Handle the incoming request before any downstream module is executed.
110    ///
111    /// This function is similar to [Self::request_filter()] but executes before any other logic,
112    /// including downstream module logic. The main purpose of this function is to provide finer
113    /// grained control of the behavior of the modules.
114    ///
115    /// Note that because this function is executed before any module that might provide access
116    /// control or rate limiting, logic should stay in request_filter() if it can in order to be
117    /// protected by said modules.
118    async fn early_request_filter(&self, _session: &mut Session, _ctx: &mut Self::CTX) -> Result<()>
119    where
120        Self::CTX: Send + Sync,
121    {
122        Ok(())
123    }
124
125    /// Returns whether this session is allowed to spawn subrequests.
126    ///
127    /// This function is checked after [Self::early_request_filter] to allow that filter to configure
128    /// this if required. This will also run for subrequests themselves, which may allowed to spawn
129    /// their own subrequests.
130    ///
131    /// Note that this doesn't prevent subrequests from being spawned based on the session by proxy
132    /// core functionality, e.g. background cache revalidation requires spawning subrequests.
133    fn allow_spawning_subrequest(&self, _session: &Session, _ctx: &Self::CTX) -> bool
134    where
135        Self::CTX: Send + Sync,
136    {
137        false
138    }
139
140    /// Handle the incoming request body.
141    ///
142    /// This function will be called every time a piece of request body is received. The `body` is
143    /// **not the entire request body**.
144    ///
145    /// The async nature of this function allows to throttle the upload speed and/or executing
146    /// heavy computation logic such as WAF rules on offloaded threads without blocking the threads
147    /// who process the requests themselves.
148    async fn request_body_filter(
149        &self,
150        _session: &mut Session,
151        _body: &mut Option<Bytes>,
152        _end_of_stream: bool,
153        _ctx: &mut Self::CTX,
154    ) -> Result<()>
155    where
156        Self::CTX: Send + Sync,
157    {
158        Ok(())
159    }
160
161    /// This filter decides if the request is cacheable and what cache backend to use
162    ///
163    /// The caller can interact with `Session.cache` to enable caching.
164    ///
165    /// By default this filter does nothing which effectively disables caching.
166    // Ideally only session.cache should be modified, TODO: reflect that in this interface
167    fn request_cache_filter(&self, _session: &mut Session, _ctx: &mut Self::CTX) -> Result<()>
168    where
169        Self::CTX: Send + Sync,
170    {
171        Ok(())
172    }
173
174    /// This callback generates the cache key.
175    ///
176    /// This callback is called only when cache is enabled for this request.
177    ///
178    /// There is no sensible default cache key for all proxy applications. The
179    /// correct key depends on which request properties affect upstream responses
180    /// (e.g. `Vary` headers, custom request filters that modify the origin host).
181    /// Getting this wrong leads to cache poisoning.
182    ///
183    /// See `pingora-proxy/tests/utils/server_utils.rs` for a minimal (not
184    /// production-ready) reference implementation.
185    ///
186    /// # Panics
187    ///
188    /// The default implementation panics. You **must** override this method when
189    /// caching is enabled.
190    fn cache_key_callback(&self, _session: &Session, _ctx: &mut Self::CTX) -> Result<CacheKey> {
191        unimplemented!("cache_key_callback must be implemented when caching is enabled")
192    }
193
194    /// This callback is invoked when a cacheable response is ready to be admitted to cache.
195    fn cache_miss(&self, session: &mut Session, _ctx: &mut Self::CTX) {
196        session.cache.cache_miss();
197    }
198
199    /// This filter is called after a successful cache lookup and before the
200    /// cache asset is ready to be used.
201    ///
202    /// This filter allows the user to log or force invalidate the asset, or
203    /// to adjust the body reader associated with the cache hit.
204    /// This also runs on stale hit assets (for which `is_fresh` is false).
205    ///
206    /// The value returned indicates if the force invalidation should be used,
207    /// and which kind. Returning `None` indicates no forced invalidation
208    async fn cache_hit_filter(
209        &self,
210        _session: &mut Session,
211        _meta: &CacheMeta,
212        _hit_handler: &mut HitHandler,
213        _is_fresh: bool,
214        _ctx: &mut Self::CTX,
215    ) -> Result<Option<ForcedFreshness>>
216    where
217        Self::CTX: Send + Sync,
218    {
219        Ok(None)
220    }
221
222    /// Decide if a request should continue to upstream after not being served from cache.
223    ///
224    /// returns: Ok(true) if the request should continue, Ok(false) if a response was written by the
225    /// callback and the session should be finished, or an error
226    ///
227    /// This filter can be used for deferring checks like rate limiting or access control to when they
228    /// actually needed after cache miss.
229    ///
230    /// By default the session will attempt to be reused after returning Ok(false). It is the
231    /// caller's responsibility to disable keepalive or drain the request body if needed.
232    async fn proxy_upstream_filter(
233        &self,
234        _session: &mut Session,
235        _ctx: &mut Self::CTX,
236    ) -> Result<bool>
237    where
238        Self::CTX: Send + Sync,
239    {
240        Ok(true)
241    }
242
243    /// Decide if the response is cacheable
244    fn response_cache_filter(
245        &self,
246        _session: &Session,
247        _resp: &ResponseHeader,
248        _ctx: &mut Self::CTX,
249    ) -> Result<RespCacheable> {
250        Ok(Uncacheable(NoCacheReason::Custom("default")))
251    }
252
253    /// Decide how to generate cache vary key from both request and response
254    ///
255    /// None means no variance is needed.
256    fn cache_vary_filter(
257        &self,
258        _meta: &CacheMeta,
259        _ctx: &mut Self::CTX,
260        _req: &RequestHeader,
261    ) -> Option<HashBinary> {
262        // default to None for now to disable vary feature
263        None
264    }
265
266    /// Decide if the incoming request's condition _fails_ against the cached response.
267    ///
268    /// Returning `Ok(true)` means that the response does _not_ match against the condition, and
269    /// that the proxy can return `304 Not Modified` downstream.
270    ///
271    /// An example is a conditional GET request with `If-None-Match: "foobar"`. If the cached
272    /// response contains the `ETag: "foobar"`, then the condition fails, and `304 Not Modified`
273    /// should be returned. Else, the condition passes which means the full `200 OK` response must
274    /// be sent.
275    fn cache_not_modified_filter(
276        &self,
277        session: &Session,
278        resp: &ResponseHeader,
279        _ctx: &mut Self::CTX,
280    ) -> Result<bool> {
281        Ok(
282            pingora_core::protocols::http::conditional_filter::not_modified_filter(
283                session.req_header(),
284                resp,
285            ),
286        )
287    }
288
289    /// This filter is called when cache is enabled to determine what byte range to return (in both
290    /// cache hit and miss cases) from the response body. It is only used when caching is enabled,
291    /// otherwise the upstream is responsible for any filtering. It allows users to define the range
292    /// this request is for via its return type `range_filter::RangeType`.
293    ///
294    /// It also allow users to modify the response header accordingly.
295    ///
296    /// The default implementation can handle a single-range as per [RFC7232].
297    ///
298    /// [RFC7232]: https://www.rfc-editor.org/rfc/rfc7232
299    fn range_header_filter(
300        &self,
301        session: &mut Session,
302        resp: &mut ResponseHeader,
303        _ctx: &mut Self::CTX,
304    ) -> range_filter::RangeType {
305        const DEFAULT_MAX_RANGES: Option<usize> = Some(200);
306        proxy_cache::range_filter::range_header_filter(
307            session.req_header(),
308            resp,
309            DEFAULT_MAX_RANGES,
310        )
311    }
312
313    /// Modify the request before it is sent to the upstream.
314    ///
315    /// Unlike [Self::request_filter()], this filter allows changing the request headers sent to
316    /// the upstream. Automatic upstream request-header policy configured on the selected peer is
317    /// applied before this callback. Headers deliberately added by this callback are treated as
318    /// application-controlled upstream behavior, including framing and protocol-upgrade fields.
319    /// For an HTTP/1 upstream, if the downstream request has a non-empty body and this callback
320    /// leaves neither `Content-Length` nor `Transfer-Encoding`, Pingora adds
321    /// `Transfer-Encoding: chunked`.
322    async fn upstream_request_filter(
323        &self,
324        _session: &mut Session,
325        _upstream_request: &mut RequestHeader,
326        _ctx: &mut Self::CTX,
327    ) -> Result<()>
328    where
329        Self::CTX: Send + Sync,
330    {
331        Ok(())
332    }
333
334    /// Adjust upstream modules before they process the response header.
335    ///
336    /// This filter is called when the upstream response header arrives, before upstream modules
337    /// (such as `upstream_compression`) run their response header filter. Use this to configure
338    /// module behavior based on the response, e.g. setting a dictionary for dictionary-based
339    /// content encoding.
340    ///
341    /// This filter may be called more than once per request if the upstream sends informational
342    /// (1xx) response headers before the final response. Implementations can check
343    /// [`upstream_response.status.is_informational()`](http::StatusCode::is_informational) to
344    /// distinguish informational headers from the final response if needed.
345    ///
346    /// `end_of_stream` indicates whether the response header is also the end of the response
347    /// (e.g. for HEAD responses or 304s with no body).
348    ///
349    /// The response header is provided as an immutable reference. To modify the response header
350    /// itself, use [`Self::upstream_response_filter()`] instead.
351    ///
352    /// This filter requires the `upstream_modules` feature to be enabled.
353    #[cfg(feature = "upstream_modules")]
354    async fn adjust_upstream_modules(
355        &self,
356        _session: &mut Session,
357        _upstream_response: &ResponseHeader,
358        _end_of_stream: bool,
359        _ctx: &mut Self::CTX,
360    ) -> Result<()>
361    where
362        Self::CTX: Send + Sync,
363    {
364        Ok(())
365    }
366
367    /// Modify the response header from the upstream
368    ///
369    /// The modification is before caching, so any change here will be stored in the cache if enabled.
370    ///
371    /// Responses served from cache won't trigger this filter. If the cache needed revalidation,
372    /// only the 304 from upstream will trigger the filter (though it will be merged into the
373    /// cached header, not served directly to downstream).
374    async fn upstream_response_filter(
375        &self,
376        _session: &mut Session,
377        _upstream_response: &mut ResponseHeader,
378        _ctx: &mut Self::CTX,
379    ) -> Result<()>
380    where
381        Self::CTX: Send + Sync,
382    {
383        Ok(())
384    }
385
386    /// Modify the response header before it is send to the downstream
387    ///
388    /// The modification is after caching. This filter is called for all responses including
389    /// responses served from cache.
390    async fn response_filter(
391        &self,
392        _session: &mut Session,
393        _upstream_response: &mut ResponseHeader,
394        _ctx: &mut Self::CTX,
395    ) -> Result<()>
396    where
397        Self::CTX: Send + Sync,
398    {
399        Ok(())
400    }
401
402    // custom_forwarding is called when downstream and upstream connections are successfully established.
403    #[doc(hidden)]
404    async fn custom_forwarding(
405        &self,
406        _session: &mut Session,
407        _ctx: &mut Self::CTX,
408        _custom_message_to_upstream: Option<mpsc::Sender<Bytes>>,
409        _custom_message_to_downstream: mpsc::Sender<Bytes>,
410    ) -> Result<()>
411    where
412        Self::CTX: Send + Sync,
413    {
414        Ok(())
415    }
416
417    // received a custom message from the downstream before sending it to the upstream.
418    #[doc(hidden)]
419    async fn downstream_custom_message_proxy_filter(
420        &self,
421        _session: &mut Session,
422        custom_message: Bytes,
423        _ctx: &mut Self::CTX,
424        _final_hop: bool,
425    ) -> Result<Option<Bytes>>
426    where
427        Self::CTX: Send + Sync,
428    {
429        Ok(Some(custom_message))
430    }
431
432    // received a custom message from the upstream before sending it to the downstream.
433    #[doc(hidden)]
434    async fn upstream_custom_message_proxy_filter(
435        &self,
436        _session: &mut Session,
437        custom_message: Bytes,
438        _ctx: &mut Self::CTX,
439        _final_hop: bool,
440    ) -> Result<Option<Bytes>>
441    where
442        Self::CTX: Send + Sync,
443    {
444        Ok(Some(custom_message))
445    }
446
447    /// Similar to [Self::upstream_response_filter()] but for response body
448    ///
449    /// This function will be called every time a piece of response body is received. The `body` is
450    /// **not the entire response body**.
451    fn upstream_response_body_filter(
452        &self,
453        _session: &mut Session,
454        _body: &mut Option<Bytes>,
455        _end_of_stream: bool,
456        _ctx: &mut Self::CTX,
457    ) -> Result<Option<Duration>> {
458        Ok(None)
459    }
460
461    /// Similar to [Self::upstream_response_filter()] but for response trailers
462    fn upstream_response_trailer_filter(
463        &self,
464        _session: &mut Session,
465        _upstream_trailers: &mut header::HeaderMap,
466        _ctx: &mut Self::CTX,
467    ) -> Result<()> {
468        Ok(())
469    }
470
471    /// Similar to [Self::response_filter()] but for response body chunks
472    fn response_body_filter(
473        &self,
474        _session: &mut Session,
475        _body: &mut Option<Bytes>,
476        _end_of_stream: bool,
477        _ctx: &mut Self::CTX,
478    ) -> Result<Option<Duration>>
479    where
480        Self::CTX: Send + Sync,
481    {
482        Ok(None)
483    }
484
485    /// Similar to [Self::response_filter()] but for response trailers.
486    /// Note, returning an Ok(Some(Bytes)) will result in the downstream response
487    /// trailers being written to the response body.
488    ///
489    /// TODO: make this interface more intuitive
490    async fn response_trailer_filter(
491        &self,
492        _session: &mut Session,
493        _upstream_trailers: &mut header::HeaderMap,
494        _ctx: &mut Self::CTX,
495    ) -> Result<Option<Bytes>>
496    where
497        Self::CTX: Send + Sync,
498    {
499        Ok(None)
500    }
501
502    /// This filter is called when the entire response is sent to the downstream successfully or
503    /// there is a fatal error that terminate the request.
504    ///
505    /// An error log is already emitted if there is any error. This phase is used for collecting
506    /// metrics and sending access logs.
507    async fn logging(&self, _session: &mut Session, _e: Option<&Error>, _ctx: &mut Self::CTX)
508    where
509        Self::CTX: Send + Sync,
510    {
511    }
512
513    /// Called after [`Self::logging`] when the downstream connection will be reused for another
514    /// HTTP/1.x keepalive request. The returned value, if any, will be carried to the next
515    /// request on this connection and delivered via [`Self::on_connection_reuse`].
516    ///
517    /// Use this to persist debugging or timing information across keepalive requests.
518    /// This is only called for HTTP/1.x keepalive connections, not for HTTP/2.
519    /// It is also called on error paths when the downstream connection is eligible for reuse.
520    ///
521    /// The default implementation returns `None` (no context persisted).
522    fn persist_connection_context(
523        &self,
524        _session: &Session,
525        _ctx: &Self::CTX,
526    ) -> Option<Box<dyn Any + Send + Sync>> {
527        None
528    }
529
530    /// Called at the start of a new request on a reused HTTP/1.x keepalive connection,
531    /// before [`Self::early_request_filter`]. The `prev_ctx` argument is the value returned
532    /// by [`Self::persist_connection_context`] from the previous request on this connection.
533    ///
534    /// This is only called for HTTP/1.x keepalive connections, not for HTTP/2.
535    /// It is not called when `persist_connection_context` returned `None` on the previous request.
536    ///
537    /// Use this to transfer state from the previous request into the new request's context.
538    fn on_connection_reuse(
539        &self,
540        _session: &mut Session,
541        _ctx: &mut Self::CTX,
542        _prev_ctx: Box<dyn Any + Send + Sync>,
543    ) {
544    }
545
546    /// A value of true means that the log message will be suppressed. The default value is false.
547    ///
548    /// See also: [`Self::suppress_proxy_warn_log`].
549    fn suppress_error_log(&self, _session: &Session, _ctx: &Self::CTX, _error: &Error) -> bool {
550        false
551    }
552
553    /// A value of true means that the proxy warning log message will be suppressed.
554    /// The default value is false.
555    ///
556    /// This hook currently applies to retryable proxy upstream failures and downstream errors
557    /// ignored while cache fill continues. Final proxy errors are still handled by
558    /// [`Self::suppress_error_log`].
559    ///
560    /// Suppressing retry warning logs can remove the only per-retry audit record. Callers that
561    /// suppress these logs should provide alternative observability, such as metrics or logs in
562    /// their implementation of this hook.
563    ///
564    /// This hook runs inline on retry and cache-error paths, so implementations should be cheap.
565    ///
566    /// Experimental: this API may change or be removed until indicated otherwise.
567    fn suppress_proxy_warn_log(
568        &self,
569        _session: &Session,
570        _ctx: &Self::CTX,
571        _error: &Error,
572        _context: ProxyWarnLogContext,
573    ) -> bool {
574        false
575    }
576
577    /// This filter is called when there is an error **after** a connection is established (or reused)
578    /// to the upstream.
579    ///
580    /// By default, this hook forces retry to false, regardless of the incoming retry state, when
581    /// the request method is non-idempotent or the body retry buffer was truncated. For eligible
582    /// requests, [`pingora_error::RetryType::ReusedOnly`] errors are retried only on a reused
583    /// connection.
584    ///
585    /// Implementations that override this hook replace the default policy and are responsible for
586    /// deciding when a retry is safe.
587    fn error_while_proxy(
588        &self,
589        peer: &HttpPeer,
590        session: &mut Session,
591        e: Box<Error>,
592        _ctx: &mut Self::CTX,
593        client_reused: bool,
594    ) -> Box<Error> {
595        let mut e = e.more_context(format!("Peer: {}", peer));
596        if !session.req_header().method.is_idempotent() || session.as_ref().retry_buffer_truncated()
597        {
598            e.set_retry(false);
599        } else {
600            e.retry.decide_reuse(client_reused);
601        }
602        e
603    }
604
605    /// This filter is called when there is an error in the process of establishing a connection
606    /// to the upstream.
607    ///
608    /// In this filter the user can decide whether the error is retry-able by marking the error `e`.
609    ///
610    /// If the error can be retried, [Self::upstream_peer()] will be called again so that the user
611    /// can decide whether to send the request to the same upstream or another upstream that is possibly
612    /// available.
613    fn fail_to_connect(
614        &self,
615        _session: &mut Session,
616        _peer: &HttpPeer,
617        _ctx: &mut Self::CTX,
618        e: Box<Error>,
619    ) -> Box<Error> {
620        e
621    }
622
623    /// This filter is called when the request encounters a fatal error.
624    ///
625    /// Users may write an error response to the downstream if the downstream is still writable.
626    ///
627    /// The response status code of the error response may be returned for logging purposes.
628    /// Additionally, the user can return whether this session may be reused in spite of the error.
629    /// Today this reuse status is only respected for errors that occur prior to upstream peer
630    /// selection, and the keepalive configured on the `Session` itself still takes precedent.
631    async fn fail_to_proxy(
632        &self,
633        session: &mut Session,
634        e: &Error,
635        _ctx: &mut Self::CTX,
636    ) -> FailToProxy
637    where
638        Self::CTX: Send + Sync,
639    {
640        let code = match e.etype() {
641            HTTPStatus(code) => *code,
642            _ => {
643                match e.esource() {
644                    ErrorSource::Upstream => 502,
645                    ErrorSource::Downstream => {
646                        match e.etype() {
647                            WriteError | ReadError | ConnectionClosed => {
648                                /* conn already dead */
649                                0
650                            }
651                            _ => 400,
652                        }
653                    }
654                    ErrorSource::Internal | ErrorSource::Unset => 500,
655                }
656            }
657        };
658        if code > 0 {
659            session.respond_error(code).await.unwrap_or_else(|e| {
660                error!("failed to send error response to downstream: {e}");
661            });
662        }
663
664        FailToProxy {
665            error_code: code,
666            // default to no reuse, which is safest
667            can_reuse_downstream: false,
668        }
669    }
670
671    /// Decide whether should serve stale when encountering an error or during revalidation
672    ///
673    /// An implementation should follow
674    /// <https://datatracker.ietf.org/doc/html/rfc9111#section-4.2.4>
675    /// <https://www.rfc-editor.org/rfc/rfc5861#section-4>
676    ///
677    /// This filter is only called if cache is enabled.
678    // 5xx HTTP status will be encoded as ErrorType::HTTPStatus(code)
679    fn should_serve_stale(
680        &self,
681        _session: &mut Session,
682        _ctx: &mut Self::CTX,
683        error: Option<&Error>, // None when it is called during stale while revalidate
684    ) -> bool {
685        // A cache MUST NOT generate a stale response unless
686        // it is disconnected
687        // or doing so is explicitly permitted by the client or origin server
688        // (e.g. headers or an out-of-band contract)
689        error.is_some_and(|e| e.esource() == &ErrorSource::Upstream)
690    }
691
692    /// This filter is called when the request just established or reused a connection to the upstream
693    ///
694    /// This filter allows user to log timing and connection related info.
695    async fn connected_to_upstream(
696        &self,
697        _session: &mut Session,
698        _reused: bool,
699        _peer: &HttpPeer,
700        #[cfg(unix)] _fd: std::os::unix::io::RawFd,
701        #[cfg(windows)] _sock: std::os::windows::io::RawSocket,
702        _digest: Option<&Digest>,
703        _ctx: &mut Self::CTX,
704    ) -> Result<()>
705    where
706        Self::CTX: Send + Sync,
707    {
708        Ok(())
709    }
710
711    /// This callback is invoked every time request related error log needs to be generated
712    ///
713    /// Users can define what is important to be written about this request via the returned string.
714    fn request_summary(&self, session: &Session, _ctx: &Self::CTX) -> String {
715        session.as_ref().request_summary()
716    }
717
718    /// Whether the request should be used to invalidate(delete) the HTTP cache
719    ///
720    /// - `true`: this request will be used to invalidate the cache.
721    /// - `false`: this request is a treated as a normal request
722    fn is_purge(&self, _session: &Session, _ctx: &Self::CTX) -> bool {
723        false
724    }
725
726    /// What a purge request should do to the cached asset.
727    ///
728    /// Only consulted when [`ProxyHttp::is_purge`] returns `true`. The default deletes the asset.
729    /// Returning [`PurgeAction::Expire`] asks to keep it and mark it stale instead, so it
730    /// revalidates against the origin rather than being refetched in full. Storage that cannot
731    /// mark an entry stale falls back to deleting it.
732    fn purge_action(&self, _session: &Session, _ctx: &Self::CTX) -> PurgeAction {
733        PurgeAction::Delete
734    }
735
736    /// This filter is called after the proxy cache generates the downstream response to the purge
737    /// request (to invalidate or delete from the HTTP cache), based on the purge status, which
738    /// indicates whether the request succeeded or failed.
739    ///
740    /// The filter allows the user to modify or replace the generated downstream response.
741    /// If the filter returns `Err`, the proxy will instead send a 500 response.
742    fn purge_response_filter(
743        &self,
744        _session: &Session,
745        _ctx: &mut Self::CTX,
746        _purge_status: PurgeStatus,
747        _purge_response: &mut std::borrow::Cow<'static, ResponseHeader>,
748    ) -> Result<()> {
749        Ok(())
750    }
751}
752
753/// Context struct returned by `fail_to_proxy`.
754pub struct FailToProxy {
755    pub error_code: u16,
756    pub can_reuse_downstream: bool,
757}