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}