pub struct ServerBuilder { /* private fields */ }Expand description
Builder for Server.
Construct with Server::builder, register routes, then call ServerBuilder::build.
Duplicate registration of the same (service, method) panics with a clear error, like axum.
Implementations§
Source§impl ServerBuilder
impl ServerBuilder
Sourcepub fn with_max_connections(self, n: usize) -> Self
pub fn with_max_connections(self, n: usize) -> Self
Limit the number of concurrent connections accepted by the server.
The value is also advertised in OPTIONS as Max-Connections if
not explicitly configured on the per-service options.
Sourcepub const fn with_timeouts(self, timeouts: ServerTimeouts) -> Self
pub const fn with_timeouts(self, timeouts: ServerTimeouts) -> Self
Install a full ServerTimeouts configuration in one shot.
See ServerTimeouts for the meaning of each field. Defaults are
None (no timeout); fields not set on the supplied value disable the
corresponding deadline.
Sourcepub const fn with_request_header_limit(self, bytes: usize) -> Self
pub const fn with_request_header_limit(self, bytes: usize) -> Self
Set the maximum ICAP request header block size, in bytes.
The limit includes the request line, all ICAP header lines, and the
terminating CRLFCRLF. The default is 64 KiB. Oversized request headers
receive 400 Bad Request and the connection is closed.
Sourcepub fn on_shutdown_event<F>(self, handler: F) -> Self
pub fn on_shutdown_event<F>(self, handler: F) -> Self
Register a callback that is called with ShutdownEvent during graceful shutdown.
The handler runs synchronously inside the accept loop task — keep it fast.
Use it for custom logging, metrics, or alerting. When not set, the server
logs via tracing::warn by default.
§Example
use icap_rs::{IcapResult, Server, ShutdownEvent};
#[tokio::main]
async fn main() -> IcapResult<()> {
let server = Server::builder()
.bind("127.0.0.1:1344")
.on_shutdown_event(|event| match event {
ShutdownEvent::Draining { active_connections, drain_timeout } => {
eprintln!("[shutdown] {active_connections} connection(s) still active");
if let Some(d) = drain_timeout {
eprintln!("[shutdown] force-close in {d:.1?}");
}
}
ShutdownEvent::DrainTimedOut { remaining_connections } => {
eprintln!("[shutdown] timed out, cancelling {remaining_connections}");
}
_ => {}
})
.build()
.await?;
server.run_until(async { tokio::signal::ctrl_c().await.ok(); }).await
}Sourcepub const fn with_compatibility_request_parser(self) -> Self
pub const fn with_compatibility_request_parser(self) -> Self
Enable legacy compatibility request parsing.
Strict RFC parsing is the default and requires every ICAP request,
including OPTIONS, to carry an Encapsulated header. This opt-in mode
accepts legacy OPTIONS requests without Encapsulated.
Sourcepub fn route<MIt, MItem, F, Fut>(
self,
service: &str,
methods: MIt,
handler: F,
options: Option<ServiceOptions>,
) -> Selfwhere
MIt: IntoIterator<Item = MItem>,
MItem: Into<Method>,
F: Fn(IncomingRequest) -> Fut + Send + Sync + 'static,
Fut: Future + Send + 'static,
Fut::Output: RouteOutput,
pub fn route<MIt, MItem, F, Fut>(
self,
service: &str,
methods: MIt,
handler: F,
options: Option<ServiceOptions>,
) -> Selfwhere
MIt: IntoIterator<Item = MItem>,
MItem: Into<Method>,
F: Fn(IncomingRequest) -> Fut + Send + Sync + 'static,
Fut: Future + Send + 'static,
Fut::Output: RouteOutput,
Register a service route for one or more ICAP methods.
- Each service must have a
ServiceOptionsvalue with an explicitISTag; routes without options are rejected bybuild. - Multiple calls to
.route(..)for the same service are allowed as long as methods do not overlap. - Registering the same method for the same service twice will
panic!with a clear message. - The same handler can be reused for multiple methods in a single call.
- Return
IcapResult<PreviewDecision>from the handler to make the route preview-aware. Such handlers are called withBody::Previewafter preview bytes arrive and before the server sends100 Continue. ReturningPreviewDecision::Continueresumes the RFC preview flow; the same handler is called again withBody::Fullafter the remainder is read.
§Handler invocation and the Allow: 204 header
The handler is always called for every REQMOD/RESPMOD request.
RFC 3507 §4.6 prohibits the server from returning 204 No Content unless
the client explicitly advertised Allow: 204. When the handler returns
204 but the client did not send Allow: 204, the server automatically
converts the response: it echoes the original embedded HTTP message in a
200 OK (or 206 Partial Content if Allow: 206 was sent).
This means handlers can always return Response::no_content() to signal
“no modification needed” — the server takes care of the RFC-compliant
wrapping regardless of what the client advertised.
Sourcepub fn route_reqmod<F, Fut>(
self,
service: &str,
handler: F,
options: Option<ServiceOptions>,
) -> Selfwhere
F: Fn(IncomingRequest) -> Fut + Send + Sync + 'static,
Fut: Future + Send + 'static,
Fut::Output: RouteOutput,
pub fn route_reqmod<F, Fut>(
self,
service: &str,
handler: F,
options: Option<ServiceOptions>,
) -> Selfwhere
F: Fn(IncomingRequest) -> Fut + Send + Sync + 'static,
Fut: Future + Send + 'static,
Fut::Output: RouteOutput,
Register a route for REQMOD only.
Convenience wrapper around route with methods = [Method::ReqMod].
See route for full semantics, including panic conditions on
duplicate (service, method) registration and ServiceOptions requirements.
§Panics
Panics if a REQMOD handler for the same service was already registered,
or if options is provided more than once for the same service.
Sourcepub fn route_respmod<F, Fut>(
self,
service: &str,
handler: F,
options: Option<ServiceOptions>,
) -> Selfwhere
F: Fn(IncomingRequest) -> Fut + Send + Sync + 'static,
Fut: Future + Send + 'static,
Fut::Output: RouteOutput,
pub fn route_respmod<F, Fut>(
self,
service: &str,
handler: F,
options: Option<ServiceOptions>,
) -> Selfwhere
F: Fn(IncomingRequest) -> Fut + Send + Sync + 'static,
Fut: Future + Send + 'static,
Fut::Output: RouteOutput,
Register a route for RESPMOD only.
Convenience wrapper around route with methods = [Method::RespMod].
See route for full semantics, including panic conditions on
duplicate (service, method) registration and ServiceOptions requirements.
§Panics
Panics if a RESPMOD handler for the same service was already registered,
or if options is provided more than once for the same service.
Sourcepub fn alias(self, from: &str, to: &str) -> Self
pub fn alias(self, from: &str, to: &str) -> Self
Add an alias for a service path: from → to.
Both ends are normalized to canonical request paths, so "scan" and
"/scan" refer to the same route. Useful to make the root path behave
like an existing service:
let builder = Server::builder()
.alias("/", "scan"); // "icap://host:1344/" is routed to "/scan"Notes:
- Aliases are applied after
default_serviceis considered for the root (“/”) path. - Up to 4 alias rewrites are applied to avoid cycles.
Sourcepub fn default_service(self, svc: &str) -> Self
pub fn default_service(self, svc: &str) -> Self
Set a default service for the root (“/”) path (e.g. "scan").
The value is normalized to a canonical request path, so "scan" and
"/scan" are equivalent.
Example:
let builder = Server::builder()
.default_service("scan");If a client sends icap://host:1344/ or an empty service, requests are internally
routed to the specified service path.
Sourcepub fn with_task_tracker(self, tracker: TaskTracker) -> Self
pub fn with_task_tracker(self, tracker: TaskTracker) -> Self
Register a TaskTracker for user-owned background tasks.
After all active connections drain following a shutdown signal, the server
calls TaskTracker::close on the tracker and waits for all tracked tasks
to finish before returning from Server::run_until.
If a drain timeout is configured via ServerTimeouts::with_shutdown_drain,
the remaining budget is shared: once the drain deadline fires the tracker
wait is skipped and the server returns immediately.
§Example
use icap_rs::{IcapResult, Server};
use tokio_util::task::TaskTracker;
#[tokio::main]
async fn main() -> IcapResult<()> {
let tracker = TaskTracker::new();
// Spawn a background task and track it so the server waits for it on shutdown.
tracker.spawn(async {
// background work ...
});
let server = Server::builder()
.bind("127.0.0.1:1344")
.with_task_tracker(tracker)
.build()
.await?;
server.run_until(async { tokio::signal::ctrl_c().await.ok(); }).await
}Sourcepub async fn build(self) -> IcapResult<Server>
pub async fn build(self) -> IcapResult<Server>
Finalize the builder and create a Server.