Skip to main content

actix_http/
builder.rs

1use std::{fmt, marker::PhantomData, net, rc::Rc, time::Duration};
2
3use actix_codec::Framed;
4use actix_service::{IntoServiceFactory, Service, ServiceFactory};
5
6use crate::{
7    body::{BoxBody, MessageBody},
8    config::{DEFAULT_H2_CONN_WINDOW_SIZE, DEFAULT_H2_STREAM_WINDOW_SIZE},
9    h1::{self, ExpectHandler, H1Service, UpgradeHandler},
10    service::HttpService,
11    ConnectCallback, Extensions, KeepAlive, Request, Response, ServiceConfigBuilder,
12};
13
14/// An HTTP service builder.
15///
16/// This type can construct an instance of [`HttpService`] through a builder-like pattern.
17pub struct HttpServiceBuilder<T, S, X = ExpectHandler, U = UpgradeHandler> {
18    keep_alive: KeepAlive,
19    client_request_timeout: Duration,
20    client_disconnect_timeout: Duration,
21    tcp_nodelay: Option<bool>,
22    secure: bool,
23    local_addr: Option<net::SocketAddr>,
24    h1_allow_half_closed: bool,
25    h2_conn_window_size: u32,
26    h2_stream_window_size: u32,
27    expect: X,
28    upgrade: Option<U>,
29    on_connect_ext: Option<Rc<ConnectCallback<T>>>,
30    _phantom: PhantomData<S>,
31}
32
33impl<T, S> Default for HttpServiceBuilder<T, S, ExpectHandler, UpgradeHandler>
34where
35    S: ServiceFactory<Request, Config = ()>,
36    S::Error: Into<Response<BoxBody>> + 'static,
37    S::InitError: fmt::Debug,
38    <S::Service as Service<Request>>::Future: 'static,
39{
40    fn default() -> Self {
41        HttpServiceBuilder {
42            // ServiceConfig parts (make sure defaults match)
43            keep_alive: KeepAlive::default(),
44            client_request_timeout: Duration::from_secs(5),
45            client_disconnect_timeout: Duration::ZERO,
46            tcp_nodelay: None,
47            secure: false,
48            local_addr: None,
49            h1_allow_half_closed: true,
50            h2_conn_window_size: DEFAULT_H2_CONN_WINDOW_SIZE,
51            h2_stream_window_size: DEFAULT_H2_STREAM_WINDOW_SIZE,
52
53            // dispatcher parts
54            expect: ExpectHandler,
55            upgrade: None,
56            on_connect_ext: None,
57            _phantom: PhantomData,
58        }
59    }
60}
61
62impl<T, S, X, U> HttpServiceBuilder<T, S, X, U>
63where
64    S: ServiceFactory<Request, Config = ()>,
65    S::Error: Into<Response<BoxBody>> + 'static,
66    S::InitError: fmt::Debug,
67    <S::Service as Service<Request>>::Future: 'static,
68    X: ServiceFactory<Request, Config = (), Response = Request>,
69    X::Error: Into<Response<BoxBody>>,
70    X::InitError: fmt::Debug,
71    U: ServiceFactory<(Request, Framed<T, h1::Codec>), Config = (), Response = ()>,
72    U::Error: fmt::Display,
73    U::InitError: fmt::Debug,
74{
75    /// Set connection keep-alive setting.
76    ///
77    /// Applies to HTTP/1.1 keep-alive and HTTP/2 ping-pong.
78    ///
79    /// By default keep-alive is 5 seconds.
80    pub fn keep_alive<W: Into<KeepAlive>>(mut self, val: W) -> Self {
81        self.keep_alive = val.into();
82        self
83    }
84
85    /// Set connection secure state
86    pub fn secure(mut self) -> Self {
87        self.secure = true;
88        self
89    }
90
91    /// Set the local address that this service is bound to.
92    pub fn local_addr(mut self, addr: net::SocketAddr) -> Self {
93        self.local_addr = Some(addr);
94        self
95    }
96
97    /// Set client request timeout (for first request).
98    ///
99    /// Defines a timeout for reading client request header. If the client does not transmit the
100    /// request head within this duration, the connection is terminated with a `408 Request Timeout`
101    /// response error.
102    ///
103    /// A duration of zero disables the timeout.
104    ///
105    /// By default, the client timeout is 5 seconds.
106    pub fn client_request_timeout(mut self, dur: Duration) -> Self {
107        self.client_request_timeout = dur;
108        self
109    }
110
111    #[doc(hidden)]
112    #[deprecated(since = "3.0.0", note = "Renamed to `client_request_timeout`.")]
113    pub fn client_timeout(self, dur: Duration) -> Self {
114        self.client_request_timeout(dur)
115    }
116
117    /// Set client connection disconnect timeout.
118    ///
119    /// Defines a timeout for disconnect connection. If a disconnect procedure does not complete
120    /// within this time, the request get dropped. This timeout affects secure connections.
121    ///
122    /// A duration of zero disables the timeout.
123    ///
124    /// By default, the disconnect timeout is disabled.
125    pub fn client_disconnect_timeout(mut self, dur: Duration) -> Self {
126        self.client_disconnect_timeout = dur;
127        self
128    }
129
130    /// Sets `TCP_NODELAY` value on accepted TCP connections.
131    pub fn tcp_nodelay(mut self, nodelay: bool) -> Self {
132        self.tcp_nodelay = Some(nodelay);
133        self
134    }
135
136    #[doc(hidden)]
137    #[deprecated(since = "3.0.0", note = "Renamed to `client_disconnect_timeout`.")]
138    pub fn client_disconnect(self, dur: Duration) -> Self {
139        self.client_disconnect_timeout(dur)
140    }
141
142    /// Sets whether HTTP/1 connections should support half-closures.
143    ///
144    /// Clients can choose to shutdown their writer-side of the connection after completing their
145    /// request and while waiting for the server response. Setting this to `false` will cause the
146    /// server to abort the connection handling as soon as it detects an EOF from the client.
147    ///
148    /// The default behavior is to allow, i.e. `true`
149    pub fn h1_allow_half_closed(mut self, allow: bool) -> Self {
150        self.h1_allow_half_closed = allow;
151        self
152    }
153
154    /// Sets initial stream-level flow control window size for HTTP/2 connections.
155    ///
156    /// See [`ServiceConfigBuilder::h2_initial_window_size`] for more details.
157    pub fn h2_initial_window_size(mut self, size: u32) -> Self {
158        self.h2_stream_window_size = size;
159        self
160    }
161
162    /// Sets initial connection-level flow control window size for HTTP/2 connections.
163    ///
164    /// See [`ServiceConfigBuilder::h2_initial_connection_window_size`] for more details.
165    pub fn h2_initial_connection_window_size(mut self, size: u32) -> Self {
166        self.h2_conn_window_size = size;
167        self
168    }
169
170    /// Provide service for `EXPECT: 100-Continue` support.
171    ///
172    /// Service get called with request that contains `EXPECT` header.
173    /// Service must return request in case of success, in that case
174    /// request will be forwarded to main service.
175    pub fn expect<F, X1>(self, expect: F) -> HttpServiceBuilder<T, S, X1, U>
176    where
177        F: IntoServiceFactory<X1, Request>,
178        X1: ServiceFactory<Request, Config = (), Response = Request>,
179        X1::Error: Into<Response<BoxBody>>,
180        X1::InitError: fmt::Debug,
181    {
182        HttpServiceBuilder {
183            keep_alive: self.keep_alive,
184            client_request_timeout: self.client_request_timeout,
185            client_disconnect_timeout: self.client_disconnect_timeout,
186            tcp_nodelay: self.tcp_nodelay,
187            secure: self.secure,
188            local_addr: self.local_addr,
189            h1_allow_half_closed: self.h1_allow_half_closed,
190            h2_conn_window_size: self.h2_conn_window_size,
191            h2_stream_window_size: self.h2_stream_window_size,
192            expect: expect.into_factory(),
193            upgrade: self.upgrade,
194            on_connect_ext: self.on_connect_ext,
195            _phantom: PhantomData,
196        }
197    }
198
199    /// Provide service for custom `Connection: UPGRADE` support.
200    ///
201    /// If service is provided then normal requests handling get halted
202    /// and this service get called with original request and framed object.
203    pub fn upgrade<F, U1>(self, upgrade: F) -> HttpServiceBuilder<T, S, X, U1>
204    where
205        F: IntoServiceFactory<U1, (Request, Framed<T, h1::Codec>)>,
206        U1: ServiceFactory<(Request, Framed<T, h1::Codec>), Config = (), Response = ()>,
207        U1::Error: fmt::Display,
208        U1::InitError: fmt::Debug,
209    {
210        HttpServiceBuilder {
211            keep_alive: self.keep_alive,
212            client_request_timeout: self.client_request_timeout,
213            client_disconnect_timeout: self.client_disconnect_timeout,
214            tcp_nodelay: self.tcp_nodelay,
215            secure: self.secure,
216            local_addr: self.local_addr,
217            h1_allow_half_closed: self.h1_allow_half_closed,
218            h2_conn_window_size: self.h2_conn_window_size,
219            h2_stream_window_size: self.h2_stream_window_size,
220            expect: self.expect,
221            upgrade: Some(upgrade.into_factory()),
222            on_connect_ext: self.on_connect_ext,
223            _phantom: PhantomData,
224        }
225    }
226
227    /// Sets the callback to be run on connection establishment.
228    ///
229    /// Has mutable access to a data container that will be merged into request extensions.
230    /// This enables transport layer data (like client certificates) to be accessed in middleware
231    /// and handlers.
232    pub fn on_connect_ext<F>(mut self, f: F) -> Self
233    where
234        F: Fn(&T, &mut Extensions) + 'static,
235    {
236        self.on_connect_ext = Some(Rc::new(f));
237        self
238    }
239
240    /// Finish service configuration and create a service for the HTTP/1 protocol.
241    pub fn h1<F, B>(self, service: F) -> H1Service<T, S, B, X, U>
242    where
243        B: MessageBody,
244        F: IntoServiceFactory<S, Request>,
245        S::Error: Into<Response<BoxBody>>,
246        S::InitError: fmt::Debug,
247        S::Response: Into<Response<B>>,
248    {
249        let cfg = ServiceConfigBuilder::new()
250            .keep_alive(self.keep_alive)
251            .client_request_timeout(self.client_request_timeout)
252            .client_disconnect_timeout(self.client_disconnect_timeout)
253            .tcp_nodelay(self.tcp_nodelay)
254            .secure(self.secure)
255            .local_addr(self.local_addr)
256            .h1_allow_half_closed(self.h1_allow_half_closed)
257            .h2_initial_window_size(self.h2_stream_window_size)
258            .h2_initial_connection_window_size(self.h2_conn_window_size)
259            .build();
260
261        H1Service::with_config(cfg, service.into_factory())
262            .expect(self.expect)
263            .upgrade(self.upgrade)
264            .on_connect_ext(self.on_connect_ext)
265    }
266
267    /// Finish service configuration and create a service for the HTTP/2 protocol.
268    #[cfg(feature = "http2")]
269    pub fn h2<F, B>(self, service: F) -> crate::h2::H2Service<T, S, B>
270    where
271        F: IntoServiceFactory<S, Request>,
272        S::Error: Into<Response<BoxBody>> + 'static,
273        S::InitError: fmt::Debug,
274        S::Response: Into<Response<B>> + 'static,
275
276        B: MessageBody + 'static,
277    {
278        let cfg = ServiceConfigBuilder::new()
279            .keep_alive(self.keep_alive)
280            .client_request_timeout(self.client_request_timeout)
281            .client_disconnect_timeout(self.client_disconnect_timeout)
282            .tcp_nodelay(self.tcp_nodelay)
283            .secure(self.secure)
284            .local_addr(self.local_addr)
285            .h1_allow_half_closed(self.h1_allow_half_closed)
286            .h2_initial_window_size(self.h2_stream_window_size)
287            .h2_initial_connection_window_size(self.h2_conn_window_size)
288            .build();
289
290        crate::h2::H2Service::with_config(cfg, service.into_factory())
291            .on_connect_ext(self.on_connect_ext)
292    }
293
294    /// Finish service configuration and create `HttpService` instance.
295    pub fn finish<F, B>(self, service: F) -> HttpService<T, S, B, X, U>
296    where
297        F: IntoServiceFactory<S, Request>,
298        S::Error: Into<Response<BoxBody>> + 'static,
299        S::InitError: fmt::Debug,
300        S::Response: Into<Response<B>> + 'static,
301
302        B: MessageBody + 'static,
303    {
304        let cfg = ServiceConfigBuilder::new()
305            .keep_alive(self.keep_alive)
306            .client_request_timeout(self.client_request_timeout)
307            .client_disconnect_timeout(self.client_disconnect_timeout)
308            .tcp_nodelay(self.tcp_nodelay)
309            .secure(self.secure)
310            .local_addr(self.local_addr)
311            .h1_allow_half_closed(self.h1_allow_half_closed)
312            .h2_initial_window_size(self.h2_stream_window_size)
313            .h2_initial_connection_window_size(self.h2_conn_window_size)
314            .build();
315
316        HttpService::with_config(cfg, service.into_factory())
317            .expect(self.expect)
318            .upgrade(self.upgrade)
319            .on_connect_ext(self.on_connect_ext)
320    }
321}