Skip to main content

ntex/web/
app.rs

1#![allow(clippy::new_without_default)]
2use std::marker::PhantomData;
3
4use crate::error::{Failure, IntoFailure};
5use crate::http::{Request, Response};
6use crate::router::ResourceDef;
7use crate::service::{Identity, ServiceChainFactory, map_state_factory};
8use crate::{Cfg, IntoServiceFactory, Middleware, Service, ServiceFactory, factory};
9
10use super::app_service::{AppFactory, WebServiceRouter};
11use super::config::{ServiceConfig, WebAppConfig};
12use super::error::{WebError, WebResponseError};
13use super::service::{AppServiceFactory, ServiceFactoryWrapper, WebServiceFactory};
14use super::stack::{Filter, WebStack};
15use super::{HttpService, Resource, Route, State, WebRequest, WebResponse};
16
17/// The main builder for a web application.
18///
19/// Start with `App::new()` then add routes, resources,
20/// scopes, middleware, filters, application state, and a fallback response.
21///
22/// Middleware and filters run before routing. ntex then chooses the resource
23/// or scope that matches the request. If nothing matches, the application
24/// returns `404 Not Found` unless you provide a custom fallback.
25///
26/// Add application-wide settings, such as middleware, filters,
27/// [`App::with_config()`], and case-insensitive routing, before adding the
28/// first route or service. After that, the builder becomes [`AppServices`],
29/// where you can continue adding routes and services.
30///
31/// ```rust
32/// use ntex::web::{self, middleware, App, HttpResponse};
33///
34/// App::default()
35///     .middleware(middleware::Logger::default())
36///     .service(
37///         web::resource("/users")
38///             .route(web::get().to(async || "users"))
39///             .route(web::post().to(async || HttpResponse::Created())),
40///     )
41///     .default_service(
42///         web::to(async || HttpResponse::NotFound().body("Not found")),
43///     );
44/// ```
45#[derive(derive_more::Debug)]
46#[debug("App")]
47pub struct App<St: State, In = (), Out = In, M = Identity, F = Filter<St, In>> {
48    middleware: M,
49    filter: ServiceChainFactory<F, St, WebRequest<In>>,
50    external: Vec<ResourceDef>,
51    config: Option<Cfg<WebAppConfig>>,
52    case_insensitive: bool,
53    ph: PhantomData<Out>,
54}
55
56/// An application builder that already has routing configuration.
57///
58/// You usually do not need to name this type. It is returned naturally after
59/// calling [`App::route()`], [`App::service()`], [`App::configure()`], or
60/// [`App::default_service()`].
61///
62/// From here, you can add more routes and services or set the application
63/// fallback. Return the finished builder from the factory passed to
64/// [`web::server()`]. The [`AppServices::build()`] and
65/// [`AppServices::build_with()`] methods are available when you need to
66/// connect the application to a lower-level HTTP service yourself.
67///
68/// [`web::server()`]: super::server
69#[derive(derive_more::Debug)]
70#[debug("AppServices")]
71pub struct AppServices<St: State, In, Out, M, F> {
72    middleware: M,
73    filter: ServiceChainFactory<F, St, WebRequest<In>>,
74    services: Vec<Box<dyn AppServiceFactory<St, Out>>>,
75    default: Option<HttpService<St, Out>>,
76    external: Vec<ResourceDef>,
77    config: Option<Cfg<WebAppConfig>>,
78    case_insensitive: bool,
79    ph: PhantomData<In>,
80}
81
82impl Default for App<()> {
83    fn default() -> Self {
84        App {
85            middleware: Identity,
86            filter: factory(Filter::new()),
87            config: None,
88            external: Vec::new(),
89            case_insensitive: false,
90            ph: PhantomData,
91        }
92    }
93}
94
95impl<St: State, In> App<St, In, In> {
96    /// Create application builder. Application can be configured with a builder-like pattern.
97    #[must_use]
98    pub fn new() -> Self {
99        App {
100            middleware: Identity,
101            filter: factory(Filter::new()),
102            config: None,
103            external: Vec::new(),
104            case_insensitive: false,
105            ph: PhantomData,
106        }
107    }
108}
109
110impl<St, In, Out, M, F> App<St, In, Out, M, F>
111where
112    St: State,
113    In: 'static,
114    Out: 'static,
115    F: ServiceFactory<
116            St,
117            WebRequest<In>,
118            Res = WebRequest<Out>,
119            Error = WebError<St, St::Error>,
120            InitError = Failure,
121        >,
122{
123    /// Run external configuration as part of the application building
124    /// process.
125    ///
126    /// This function is useful for moving parts of configuration to a
127    /// different module or even library. For example,
128    /// some of the resource's configuration could be moved to different module.
129    ///
130    /// ```rust,ignore
131    /// use ntex::web::{self, middleware, App, HttpResponse};
132    ///
133    /// // this function could be located in different module
134    /// fn config(cfg: &mut web::ServiceConfig) {
135    ///     cfg.service(web::resource("/test")
136    ///         .route(web::get().to(async || { HttpResponse::Ok() }))
137    ///         .route(web::head().to(async || { HttpResponse::MethodNotAllowed() }))
138    ///     );
139    /// }
140    ///
141    /// fn main() {
142    ///     let app = App::default()
143    ///         .middleware(middleware::Logger::default())
144    ///         .configure(config)  // <- register resources
145    ///         .route("/index.html", web::get().to(async || { HttpResponse::Ok() }));
146    /// }
147    /// ```
148    #[must_use]
149    pub fn configure(
150        self,
151        f: impl FnOnce(&mut ServiceConfig<St, Out>),
152    ) -> AppServices<St, In, Out, M, F> {
153        let mut cfg = ServiceConfig::new(self.external);
154        f(&mut cfg);
155
156        AppServices {
157            services: cfg.services,
158            default: None,
159            filter: self.filter,
160            middleware: self.middleware,
161            config: self.config,
162            external: cfg.external,
163            case_insensitive: self.case_insensitive,
164            ph: PhantomData,
165        }
166    }
167
168    /// Register a route for an application path.
169    ///
170    /// This is shorthand for creating a [`Resource`] with one route and
171    /// registering it with [`App::service()`]. The route's method and custom
172    /// guards are promoted to resource guards.
173    ///
174    /// Each call creates a separate resource, so the same path can be
175    /// registered more than once with different guards.
176    ///
177    /// ```rust
178    /// use ntex::web::{self, App, HttpResponse};
179    ///
180    /// App::default()
181    ///     .route("/items", web::get().to(async || "list"))
182    ///     .route("/items", web::post().to(async || HttpResponse::Created()));
183    /// ```
184    #[must_use]
185    pub fn route(self, path: &str, mut route: Route<St, Out>) -> AppServices<St, In, Out, M, F> {
186        self.service(
187            Resource::new(path)
188                .add_guards(route.take_guards())
189                .route(route),
190        )
191    }
192
193    /// Registers a web service with the application.
194    ///
195    /// A service defines its own path and guards through [`WebServiceFactory`].
196    /// Common services include [`Resource`], [`Scope`](crate::web::Scope), handlers created with
197    /// route attribute macros, and custom services built with `web::service()`.
198    ///
199    /// Use a resource to group several routes, filters, middleware, or a
200    /// fallback under one path. Use a scope to group services under a shared
201    /// path prefix.
202    ///
203    /// If no registered service matches the request path and guards, the
204    /// application's default service is used.
205    ///
206    /// ```rust
207    /// use ntex::web::{self, App, HttpResponse};
208    ///
209    /// App::default()
210    ///     .service(
211    ///         web::resource("/users")
212    ///             .route(web::get().to(async || "users"))
213    ///             .route(web::post().to(async || HttpResponse::Created())),
214    ///     )
215    ///     .service(
216    ///         web::scope("/api")
217    ///             .route("/health", web::get().to(async || "OK")),
218    ///     );
219    /// ```
220    #[must_use]
221    pub fn service<S>(self, factory: S) -> AppServices<St, In, Out, M, F>
222    where
223        S: WebServiceFactory<St, Out> + 'static,
224    {
225        AppServices {
226            services: vec![Box::new(ServiceFactoryWrapper::new(factory))],
227            default: None,
228            filter: self.filter,
229            middleware: self.middleware,
230            config: self.config,
231            external: self.external,
232            case_insensitive: self.case_insensitive,
233            ph: PhantomData,
234        }
235    }
236
237    /// Set the fallback service for unmatched application requests.
238    ///
239    /// The fallback is called when no top-level resource or scope matches the
240    /// request path and guards. Without a custom fallback, the application
241    /// returns `404 Not Found`.
242    ///
243    /// A matched resource or scope handles its own routing failures, so its
244    /// requests do not fall through to this service.
245    ///
246    /// ```rust
247    /// use ntex::web::{self, App, HttpRequest, HttpResponse};
248    ///
249    /// async fn not_found(req: HttpRequest) -> HttpResponse {
250    ///     HttpResponse::NotFound()
251    ///         .body(format!("No resource for {}", req.path()))
252    /// }
253    ///
254    /// App::default()
255    ///     .route("/health", web::get().to(async || "ready"))
256    ///     .default_service(web::to(not_found));
257    /// ```
258    #[must_use]
259    pub fn default_service<U>(
260        self,
261        f: impl IntoServiceFactory<U, St, WebRequest<Out>>,
262    ) -> AppServices<St, In, Out, M, F>
263    where
264        U: ServiceFactory<St, WebRequest<Out>, Res = WebResponse> + 'static,
265        U::Error: WebResponseError<St, St::Error>,
266        U::InitError: IntoFailure,
267    {
268        // create and configure default resource
269        let default = Some(HttpService::new(
270            f.into_factory()
271                .map_err(WebError::from_err)
272                .map_init_err(IntoFailure::fail),
273        ));
274
275        AppServices {
276            default,
277            services: Vec::new(),
278            filter: self.filter,
279            middleware: self.middleware,
280            config: self.config,
281            external: self.external,
282            case_insensitive: self.case_insensitive,
283            ph: PhantomData,
284        }
285    }
286
287    /// Register an external resource.
288    ///
289    /// External resources are useful for URL generation purposes only
290    /// and are never considered for matching at request time. Calls to
291    /// `HttpRequest::url_for()` will work as expected.
292    ///
293    /// ```rust
294    /// use ntex::web::{self, App, HttpRequest, HttpResponse, error::UrlGenerationError};
295    ///
296    /// async fn index(req: HttpRequest) -> Result<HttpResponse, UrlGenerationError> {
297    ///     let url = req.url_for("youtube", &["asdlkjqme"])?;
298    ///     assert_eq!(url.as_str(), "https://youtube.com/watch/asdlkjqme");
299    ///     Ok(HttpResponse::Ok().into())
300    /// }
301    ///
302    /// fn main() {
303    ///     let app = App::default()
304    ///         .external_resource("youtube", "https://youtube.com/watch/{video_id}")
305    ///         .service(web::resource("/index.html").route(
306    ///             web::get().to(index)));
307    /// }
308    /// ```
309    #[must_use]
310    pub fn external_resource(mut self, name: impl AsRef<str>, url: impl AsRef<str>) -> Self {
311        let mut rdef = ResourceDef::new(url.as_ref());
312        rdef.set_name(name.as_ref());
313        self.external.push(rdef);
314        self
315    }
316
317    /// Set the application's runtime configuration.
318    ///
319    /// [`WebAppConfig`] contains connection metadata used by the application,
320    /// such as the host, secure-connection flag, local address, and request pool
321    /// size. It can also store typed configuration values with
322    /// [`WebAppConfig::set_state()`]; those values are available through
323    /// [`HttpRequest::app_state()`](crate::web::HttpRequest::app_state) and [`WebRequest::app_state()`].
324    ///
325    /// Without an explicit configuration, each request uses the
326    /// [`WebAppConfig`] from its I/O context, or the default configuration if
327    /// the request has no associated I/O object. This method overrides that
328    /// selection for every request handled by this application.
329    ///
330    /// This configuration is separate from the service-level application state
331    /// represented by `St`. To register routes and services from an external
332    /// function, use [`App::configure()`] instead.
333    ///
334    /// ```rust
335    /// use ntex::web::{self, App, HttpRequest, WebAppConfig};
336    ///
337    /// async fn index(req: HttpRequest) -> String {
338    ///     let value = req.app_state::<usize>().copied().unwrap_or_default();
339    ///     format!("Configured value: {value}")
340    /// }
341    ///
342    /// let config = WebAppConfig::new()
343    ///     .set_host("www.example.com".to_owned())
344    ///     .set_secure()
345    ///     .set_state(42usize);
346    ///
347    /// App::default()
348    ///     .with_config(config)
349    ///     .route("/", web::get().to(index));
350    /// ```
351    #[must_use]
352    pub fn with_config(mut self, cfg: impl Into<Cfg<WebAppConfig>>) -> Self {
353        self.config = Some(cfg.into());
354        self
355    }
356
357    /// Registers a request filter.
358    ///
359    /// Application filters run before the application router selects a
360    /// resource or scope. Filters are called in registration order, and each
361    /// filter receives the [`WebRequest`] returned by the previous one.
362    ///
363    /// A filter can inspect or modify the request, or use
364    /// [`WebRequest::map_state()`] to change its request-local state type. It
365    /// must return another `WebRequest` to continue processing. Returning an
366    /// error stops the filter chain and prevents routing; the error is handled
367    /// through [`WebResponseError`].
368    ///
369    /// Application middleware wraps the filter and router, so middleware runs
370    /// before filters on the inbound path.
371    ///
372    /// ```rust
373    /// use std::convert::Infallible;
374    /// use ntex::web::{self, App, WebRequest};
375    ///
376    /// async fn authenticate(
377    ///     req: WebRequest<()>,
378    /// ) -> Result<WebRequest<&'static str>, Infallible> {
379    ///     Ok(req.map_state(|()| "alice"))
380    /// }
381    ///
382    /// async fn index(_state: &(), user: &'static str) -> String {
383    ///     format!("Hello, {user}!")
384    /// }
385    ///
386    /// App::new()
387    ///     .filter(authenticate)
388    ///     .route("/", web::get().to_with_state(index));
389    /// ```
390    #[must_use]
391    pub fn filter<Sf, R>(
392        self,
393        filter: impl IntoServiceFactory<Sf, St, WebRequest<Out>>,
394    ) -> App<
395        St,
396        In,
397        R,
398        M,
399        impl ServiceFactory<
400            St,
401            WebRequest<In>,
402            Res = WebRequest<R>,
403            Error = WebError<St, St::Error>,
404            InitError = Failure,
405        >,
406    >
407    where
408        Sf: ServiceFactory<St, WebRequest<Out>, Res = WebRequest<R>>,
409        Sf::Error: WebResponseError<St, St::Error>,
410        Sf::InitError: IntoFailure,
411    {
412        App {
413            filter: self.filter.and_then(
414                filter
415                    .into_factory()
416                    .map_err(WebError::from_err)
417                    .map_init_err(IntoFailure::fail),
418            ),
419            middleware: self.middleware,
420            config: self.config,
421            external: self.external,
422            case_insensitive: self.case_insensitive,
423            ph: PhantomData,
424        }
425    }
426
427    /// Registers a middleware for this application.
428    ///
429    /// Use application middleware for work that should apply to every request,
430    /// such as logging, response headers, or authentication. It runs before
431    /// the application filter and router on the way in, and can inspect or
432    /// modify the response on the way back.
433    ///
434    /// Middleware may also return a response without calling the service it
435    /// wraps. In that case, the rest of the application pipeline is skipped.
436    ///
437    /// Requests pass through middleware in the order it was added. Responses
438    /// travel back in the opposite order. In this example, `DefaultHeaders`
439    /// sees the request before `Logger`, while `Logger` sees the response
440    /// before `DefaultHeaders`.
441    ///
442    /// Custom middleware should call the wrapped service through
443    /// [`Ctx::call()`](crate::Ctx::call) so readiness and lifecycle events are handled
444    /// correctly.
445    ///
446    /// ```rust
447    /// use ntex::web::{self, middleware, App};
448    ///
449    /// App::default()
450    ///     .middleware(
451    ///         middleware::DefaultHeaders::new()
452    ///             .header("x-application", "example"),
453    ///     )
454    ///     .middleware(middleware::Logger::default())
455    ///     .route("/", web::get().to(async || "Hello"));
456    /// ```
457    #[must_use]
458    pub fn middleware<U>(self, mw: U) -> App<St, In, Out, WebStack<St, U, M>, F> {
459        App {
460            middleware: WebStack::new(mw, self.middleware),
461            filter: self.filter,
462            config: self.config,
463            external: self.external,
464            case_insensitive: self.case_insensitive,
465            ph: PhantomData,
466        }
467    }
468
469    #[must_use]
470    /// Use ascii case-insensitive routing.
471    ///
472    /// Only static segments could be case-insensitive.
473    pub fn case_insensitive_routing(mut self) -> Self {
474        self.case_insensitive = true;
475        self
476    }
477}
478
479impl<St, In, Out, M, F> AppServices<St, In, Out, M, F>
480where
481    St: State,
482    Out: 'static,
483    F: ServiceFactory<
484            St,
485            WebRequest<In>,
486            Res = WebRequest<Out>,
487            Error = WebError<St, St::Error>,
488            InitError = Failure,
489        >,
490{
491    /// Register a route for an application path.
492    ///
493    /// This is shorthand for creating a [`Resource`] with one route and
494    /// registering it with [`AppServices::service()`]. The route's method and
495    /// custom guards are promoted to resource guards.
496    ///
497    /// Consequently, if those guards reject a request, the generated resource
498    /// does not match and the application router continues searching. If
499    /// nothing else matches, the application default service is used. To use a
500    /// resource-level fallback such as the built-in `405 Method Not Allowed`,
501    /// register an explicit [`Resource`] and add routes with
502    /// [`Resource::route()`].
503    ///
504    /// Each call creates a separate resource, so the same path can be
505    /// registered more than once with different guards.
506    ///
507    /// ```rust
508    /// use ntex::web::{self, App, HttpResponse};
509    ///
510    /// App::default()
511    ///     .route("/items", web::get().to(async || "list"))
512    ///     .route(
513    ///         "/items",
514    ///         web::post().to(async || HttpResponse::Created()),
515    ///     );
516    /// ```
517    #[must_use]
518    pub fn route(self, path: &str, mut route: Route<St, Out>) -> Self {
519        self.service(
520            Resource::new(path)
521                .add_guards(route.take_guards())
522                .route(route),
523        )
524    }
525
526    /// Registers another web service with the application.
527    ///
528    /// This has the same behavior as [`App::service()`]. The service supplies
529    /// its own path and guards and becomes part of the application's top-level
530    /// router.
531    ///
532    /// If none of the registered services match, the application's default
533    /// service is used.
534    #[must_use]
535    pub fn service<S>(mut self, factory: S) -> Self
536    where
537        S: WebServiceFactory<St, Out> + 'static,
538    {
539        self.services
540            .push(Box::new(ServiceFactoryWrapper::new(factory)));
541        self
542    }
543
544    /// Set the fallback service for unmatched application requests.
545    ///
546    /// The fallback is called when no top-level resource or scope matches the
547    /// request path and guards. Without a custom fallback, the application
548    /// returns `404 Not Found`.
549    ///
550    /// A matched resource or scope handles its own routing failures, so its
551    /// requests do not fall through to this service.
552    ///
553    /// ```rust
554    /// use ntex::web::{self, App, HttpRequest, HttpResponse};
555    ///
556    /// async fn not_found(req: HttpRequest) -> HttpResponse {
557    ///     HttpResponse::NotFound()
558    ///         .body(format!("No resource for {}", req.path()))
559    /// }
560    ///
561    /// App::default()
562    ///     .route("/health", web::get().to(async || "ready"))
563    ///     .default_service(web::to(not_found));
564    /// ```
565    #[must_use]
566    pub fn default_service<U>(mut self, f: impl IntoServiceFactory<U, St, WebRequest<Out>>) -> Self
567    where
568        U: ServiceFactory<St, WebRequest<Out>, Res = WebResponse> + 'static,
569        U::Error: WebResponseError<St, St::Error>,
570        U::InitError: IntoFailure,
571    {
572        // create and configure default resource
573        self.default = Some(HttpService::new(
574            f.into_factory()
575                .map_err(WebError::from_err)
576                .map_init_err(IntoFailure::fail),
577        ));
578
579        self
580    }
581}
582
583impl<St, In, Out, M, F> AppServices<St, In, Out, M, F>
584where
585    St: State,
586    In: 'static,
587    Out: 'static,
588    F: ServiceFactory<
589            St,
590            WebRequest<In>,
591            Res = WebRequest<Out>,
592            Error = WebError<St, St::Error>,
593            InitError = Failure,
594        >,
595    M: Middleware<WebServiceRouter<St, In, Out, F::Service>, St> + 'static,
596    M::Service: Service<St, WebRequest<()>, Res = WebResponse, Error = WebError<St, St::Error>>,
597{
598    /// Builds the application into a service factory.
599    ///
600    /// The returned factory accepts [`Request`] values and uses application
601    /// state supplied by the surrounding service pipeline. It can be passed to
602    /// [`HttpService`] when building an HTTP server manually.
603    ///
604    /// Applications passed to [`web::server()`] do not normally need an
605    /// explicit call to `build()`.
606    ///
607    /// ```rust
608    /// use ntex::web;
609    ///
610    /// let factory = web::App::default()
611    ///     .route("/", web::get().to(async || "Hello"))
612    ///     .build();
613    /// ```
614    ///
615    /// [`HttpService`]: crate::http::HttpService
616    /// [`web::server()`]: super::server
617    pub fn build(
618        self,
619    ) -> impl ServiceFactory<
620        St,
621        Request,
622        Res = Response,
623        Error = WebError<St, St::Error>,
624        InitError = Failure,
625    > {
626        IntoServiceFactory::<AppFactory<St, In, Out, M, F>, St, Request>::into_factory(self)
627    }
628}
629
630impl<St, In, Out, M, F> AppServices<St, In, Out, M, F>
631where
632    St: State,
633    In: 'static,
634    Out: 'static,
635    F: ServiceFactory<
636            St,
637            WebRequest<In>,
638            Res = WebRequest<Out>,
639            Error = WebError<St, St::Error>,
640            InitError = Failure,
641        >,
642    M: Middleware<WebServiceRouter<St, In, Out, F::Service>, St> + 'static,
643    M::Service: Service<St, WebRequest<()>, Res = WebResponse, Error = WebError<St, St::Error>>,
644{
645    /// Builds the application with a fixed application state.
646    ///
647    /// Unlike [`AppServices::build()`], which takes its application state from
648    /// the surrounding service pipeline, this method stores `state` in the
649    /// returned factory. Web handlers, filters, middleware, and services use
650    /// this fixed state even when the outer pipeline uses a different state
651    /// type.
652    ///
653    /// ```rust
654    /// use ntex::web;
655    ///
656    /// #[derive(Clone)]
657    /// struct AppState {
658    ///     greeting: &'static str,
659    /// }
660    ///
661    /// impl web::State for AppState {
662    ///     type Error = web::DefaultError;
663    /// }
664    ///
665    /// async fn index(state: &AppState, _request_state: ()) -> String {
666    ///     state.greeting.to_owned()
667    /// }
668    ///
669    /// let app = web::App::<AppState>::new()
670    ///     .route("/", web::get().to_with_state(index))
671    ///     .build_with::<()>(AppState { greeting: "Hello" });
672    /// ```
673    pub fn build_with<Outer>(
674        self,
675        state: St,
676    ) -> impl ServiceFactory<
677        Outer,
678        Request,
679        Res = Response,
680        Error = WebError<St, St::Error>,
681        InitError = Failure,
682    >
683    where
684        St: Clone,
685    {
686        map_state_factory(
687            state,
688            IntoServiceFactory::<AppFactory<St, In, Out, M, F>, St, Request>::into_factory(self),
689        )
690    }
691}
692
693impl<St, In, Out, M, F> IntoServiceFactory<AppFactory<St, In, Out, M, F>, St, Request>
694    for AppServices<St, In, Out, M, F>
695where
696    St: State,
697    In: 'static,
698    Out: 'static,
699    F: ServiceFactory<
700            St,
701            WebRequest<In>,
702            Res = WebRequest<Out>,
703            Error = WebError<St, St::Error>,
704            InitError = Failure,
705        >,
706    M: Middleware<WebServiceRouter<St, In, Out, F::Service>, St> + 'static,
707    M::Service: Service<St, WebRequest<()>, Res = WebResponse, Error = WebError<St, St::Error>>,
708{
709    fn into_factory(self) -> AppFactory<St, In, Out, M, F> {
710        AppFactory::new(
711            self.middleware,
712            self.filter,
713            self.services,
714            self.default,
715            self.config,
716            self.external,
717            self.case_insensitive,
718        )
719    }
720}
721
722#[cfg(test)]
723mod tests {
724    use std::{cell::Cell, convert::Infallible, rc::Rc};
725
726    use super::*;
727    use crate::http::{Method, StatusCode, header, header::HeaderValue};
728    use crate::web::test::{TestRequest, call_service, init_service, read_body};
729    use crate::web::{self, HttpRequest, HttpResponse, middleware::DefaultHeaders};
730
731    #[crate::rt_test]
732    async fn test_default_resource() {
733        let srv = App::new()
734            .service(web::resource("/test").to(async || HttpResponse::Ok()))
735            .build()
736            .pipeline(())
737            .await
738            .unwrap();
739        let req = TestRequest::with_uri("/test").to_request();
740        let resp = srv.call(req).await.unwrap();
741        assert_eq!(resp.status(), StatusCode::OK);
742
743        let req = TestRequest::with_uri("/blah").to_request();
744        let resp = srv.call(req).await.unwrap();
745        assert_eq!(resp.status(), StatusCode::NOT_FOUND);
746
747        let srv = App::new()
748            .service(web::resource("/test").to(async || HttpResponse::Ok()))
749            .service(
750                web::resource("/test2")
751                    .default_service(async move |r: WebRequest<()>| {
752                        Ok::<_, Infallible>(r.into_response(HttpResponse::Created()))
753                    })
754                    .route(web::get().to(async || HttpResponse::Ok())),
755            )
756            .default_service(async move |r: WebRequest<()>| {
757                Ok::<_, Infallible>(r.into_response(HttpResponse::MethodNotAllowed()))
758            })
759            .build()
760            .pipeline(())
761            .await
762            .unwrap();
763
764        let req = TestRequest::with_uri("/blah").to_request();
765        let resp = srv.call(req).await.unwrap();
766        assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
767
768        let req = TestRequest::with_uri("/test2").to_request();
769        let resp = srv.call(req).await.unwrap();
770        assert_eq!(resp.status(), StatusCode::OK);
771
772        let req = TestRequest::with_uri("/test2")
773            .method(Method::POST)
774            .to_request();
775        let resp = srv.call(req).await.unwrap();
776        assert_eq!(resp.status(), StatusCode::CREATED);
777    }
778
779    #[crate::rt_test]
780    async fn test_filter() {
781        let filter = Rc::new(Cell::new(false));
782        let filter2 = filter.clone();
783        let srv = init_service(
784            App::new()
785                .filter(async move |req: WebRequest<()>| {
786                    filter2.set(true);
787                    Ok::<_, Infallible>(req)
788                })
789                .route("/test", web::get().to(async || HttpResponse::Ok())),
790        )
791        .await;
792        let req = TestRequest::with_uri("/test").to_request();
793        let resp = call_service(&srv, req).await;
794        assert_eq!(resp.status(), StatusCode::OK);
795        assert!(filter.get());
796    }
797
798    #[crate::rt_test]
799    async fn test_wrap() {
800        let srv = init_service(
801            App::new()
802                .middleware(
803                    DefaultHeaders::new()
804                        .header(header::CONTENT_TYPE, HeaderValue::from_static("0001")),
805                )
806                .route("/test", web::get().to(async || HttpResponse::Ok())),
807        )
808        .await;
809        let req = TestRequest::with_uri("/test").to_request();
810        let resp = call_service(&srv, req).await;
811        assert_eq!(resp.status(), StatusCode::OK);
812        assert_eq!(
813            resp.headers().get(header::CONTENT_TYPE).unwrap(),
814            HeaderValue::from_static("0001")
815        );
816    }
817
818    #[crate::rt_test]
819    async fn test_router_wrap() {
820        let srv = init_service(
821            App::new()
822                .middleware(
823                    DefaultHeaders::new()
824                        .header(header::CONTENT_TYPE, HeaderValue::from_static("0001")),
825                )
826                .route("/test", web::get().to(async || HttpResponse::Ok())),
827        )
828        .await;
829        let req = TestRequest::with_uri("/test").to_request();
830        let resp = call_service(&srv, req).await;
831        assert_eq!(resp.status(), StatusCode::OK);
832        assert_eq!(
833            resp.headers().get(header::CONTENT_TYPE).unwrap(),
834            HeaderValue::from_static("0001")
835        );
836    }
837
838    #[crate::rt_test]
839    async fn test_case_insensitive_router() {
840        let srv = init_service(
841            App::new()
842                .case_insensitive_routing()
843                .route("/test", web::get().to(async || HttpResponse::Ok())),
844        )
845        .await;
846        let req = TestRequest::with_uri("/test").to_request();
847        let resp = call_service(&srv, req).await;
848        assert_eq!(resp.status(), StatusCode::OK);
849
850        let req = TestRequest::with_uri("/Test").to_request();
851        let resp = call_service(&srv, req).await;
852        assert_eq!(resp.status(), StatusCode::OK);
853    }
854
855    #[crate::rt_test]
856    async fn test_extension() {
857        let cfg = WebAppConfig::new().set_state(10usize);
858
859        let srv = init_service(
860            App::new()
861                .with_config(cfg)
862                .filter(async move |req: WebRequest<()>| {
863                    assert_eq!(*req.app_state::<usize>().unwrap(), 10);
864                    Ok::<_, Infallible>(req)
865                })
866                .service(web::resource("/").to(async move |req: HttpRequest| {
867                    assert_eq!(*req.app_state::<usize>().unwrap(), 10);
868                    HttpResponse::Ok()
869                })),
870        )
871        .await;
872
873        let req = TestRequest::default().to_request();
874        let resp = srv.call(req).await.unwrap();
875        assert_eq!(resp.status(), StatusCode::OK);
876    }
877
878    #[crate::rt_test]
879    async fn test_external_resource() {
880        use crate::util::Bytes;
881
882        let srv = init_service(
883            App::new()
884                .external_resource("youtube", "https://youtube.com/watch/{video_id}")
885                .route(
886                    "/test",
887                    web::get().to(async move |req: HttpRequest| {
888                        HttpResponse::Ok()
889                            .body(format!("{}", req.url_for("youtube", ["12345"]).unwrap()))
890                    }),
891                ),
892        )
893        .await;
894        let req = TestRequest::with_uri("/test").to_request();
895        let resp = call_service(&srv, req).await;
896        assert_eq!(resp.status(), StatusCode::OK);
897        let body = read_body(resp).await;
898        assert_eq!(body, Bytes::from_static(b"https://youtube.com/watch/12345"));
899    }
900
901    #[crate::rt_test]
902    async fn test_pooled_request_resource_map() {
903        use crate::util::Bytes;
904
905        async fn url(req: HttpRequest) -> HttpResponse {
906            HttpResponse::Ok().body(format!("{}", req.url_for("docs", ["1"]).unwrap()))
907        }
908
909        let srv1 = init_service(
910            App::new()
911                .external_resource("docs", "https://one.example.com/{page}")
912                .route("/test", web::get().to(url)),
913        )
914        .await;
915        let srv2 = init_service(
916            App::new()
917                .external_resource("docs", "https://two.example.com/{page}")
918                .route("/test", web::get().to(url)),
919        )
920        .await;
921
922        for _ in 0..2 {
923            for (srv, expected) in [
924                (&srv1, &b"https://one.example.com/1"[..]),
925                (&srv2, &b"https://two.example.com/1"[..]),
926            ] {
927                let req = TestRequest::with_uri("/test").to_request();
928                let resp = call_service(srv, req).await;
929                assert_eq!(read_body(resp).await, Bytes::from_static(expected));
930            }
931        }
932    }
933
934    #[crate::rt_test]
935    async fn test_app_default_service() {
936        let srv =
937            init_service(App::default().default_service(web::to(async || HttpResponse::Created())))
938                .await;
939        let resp = call_service(&srv, TestRequest::with_uri("/any").to_request()).await;
940        assert_eq!(resp.status(), StatusCode::CREATED);
941        srv.shutdown().await;
942    }
943
944    #[crate::rt_test]
945    async fn test_app_service_init_errors() {
946        use crate::service::{fn_factory, fn_service};
947
948        let attempts = Rc::new(Cell::new(0));
949        let attempts2 = attempts.clone();
950        let failing = move || {
951            let attempts = attempts2.clone();
952            fn_factory(async move |(): &()| {
953                attempts.set(attempts.get() + 1);
954                if attempts.get() % 2 == 1 {
955                    Err(std::io::Error::other("init error"))
956                } else {
957                    Ok(fn_service(async |req: WebRequest<()>| {
958                        Ok::<_, Infallible>(req.into_response(HttpResponse::Ok()))
959                    }))
960                }
961            })
962        };
963
964        let srv = init_service(
965            App::new()
966                .service(web::service("/test").build(failing()))
967                .default_service(failing()),
968        )
969        .await;
970
971        // failed services are not cached, next request creates them again
972        for path in ["/test", "/unknown"] {
973            let resp = call_service(&srv, TestRequest::with_uri(path).to_request()).await;
974            assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
975            let resp = call_service(&srv, TestRequest::with_uri(path).to_request()).await;
976            assert_eq!(resp.status(), StatusCode::OK);
977            let resp = call_service(&srv, TestRequest::with_uri(path).to_request()).await;
978            assert_eq!(resp.status(), StatusCode::OK);
979        }
980        assert_eq!(attempts.get(), 4);
981    }
982
983    #[crate::rt_test]
984    async fn test_app_filter_error() {
985        let srv = init_service(
986            App::new()
987                .filter(async |req: WebRequest<()>| {
988                    if req.path() == "/denied" {
989                        Err(std::io::Error::from(std::io::ErrorKind::PermissionDenied))
990                    } else {
991                        Ok(req)
992                    }
993                })
994                .route("/{tail}*", web::to(async || HttpResponse::Ok())),
995        )
996        .await;
997        let resp = call_service(&srv, TestRequest::with_uri("/allowed").to_request()).await;
998        assert_eq!(resp.status(), StatusCode::OK);
999        let resp = call_service(&srv, TestRequest::with_uri("/denied").to_request()).await;
1000        assert_eq!(resp.status(), StatusCode::FORBIDDEN);
1001    }
1002}