diff --git a/CHANGES.md b/CHANGES.md index 31fa4690..876e1c03 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,7 +4,8 @@ ### Added * Add `ServiceRequest::parts_mut`. [#2177] * Add extractors for `Uri` and `Method`. [#2263] -* Add extractor for `ConnectionInfo` and `PeerAddr`. [#2263] +* Add extractors for `ConnectionInfo` and `PeerAddr`. [#2263] +* Add `Route::service` for using hand-written services as handlers. [#2262] ### Changed * Change compression algorithm features flags. [#2250] @@ -13,6 +14,7 @@ [#2177]: https://github.com/actix/actix-web/pull/2177 [#2250]: https://github.com/actix/actix-web/pull/2250 [#2271]: https://github.com/actix/actix-web/pull/2271 +[#2262]: https://github.com/actix/actix-web/pull/2262 [#2263]: https://github.com/actix/actix-web/pull/2263 diff --git a/Cargo.toml b/Cargo.toml index 320751c6..779b5225 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -113,7 +113,6 @@ flate2 = "1.0.13" zstd = "0.7" rand = "0.8" rcgen = "0.8" -serde_derive = "1.0" tls-openssl = { package = "openssl", version = "0.10.9" } tls-rustls = { package = "rustls", version = "0.19.0" } diff --git a/src/extract.rs b/src/extract.rs index d7b67cd9..592f7ab8 100644 --- a/src/extract.rs +++ b/src/extract.rs @@ -54,7 +54,7 @@ pub trait FromRequest: Sized { /// use actix_web::{web, dev, App, Error, HttpRequest, FromRequest}; /// use actix_web::error::ErrorBadRequest; /// use futures_util::future::{ok, err, Ready}; -/// use serde_derive::Deserialize; +/// use serde::Deserialize; /// use rand; /// /// #[derive(Debug, Deserialize)] @@ -145,7 +145,7 @@ where /// use actix_web::{web, dev, App, Result, Error, HttpRequest, FromRequest}; /// use actix_web::error::ErrorBadRequest; /// use futures_util::future::{ok, err, Ready}; -/// use serde_derive::Deserialize; +/// use serde::Deserialize; /// use rand; /// /// #[derive(Debug, Deserialize)] @@ -265,7 +265,7 @@ impl FromRequest for Method { #[doc(hidden)] impl FromRequest for () { type Error = Infallible; - type Future = Ready>; + type Future = Ready>; type Config = (); fn from_request(_: &HttpRequest, _: &mut Payload) -> Self::Future { @@ -376,7 +376,7 @@ mod m { mod tests { use actix_http::http::header; use bytes::Bytes; - use serde_derive::Deserialize; + use serde::Deserialize; use super::*; use crate::test::TestRequest; diff --git a/src/lib.rs b/src/lib.rs index 9d8bf62e..920abccb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -149,7 +149,9 @@ pub mod dev { pub use actix_http::{Extensions, Payload, PayloadStream, RequestHead, ResponseHead}; pub use actix_router::{Path, ResourceDef, ResourcePath, Url}; pub use actix_server::Server; - pub use actix_service::{always_ready, forward_ready, Service, Transform}; + pub use actix_service::{ + always_ready, fn_factory, fn_service, forward_ready, Service, Transform, + }; pub(crate) fn insert_slash(mut patterns: Vec) -> Vec { for path in &mut patterns { diff --git a/src/request.rs b/src/request.rs index bff66f08..5c5c43d2 100644 --- a/src/request.rs +++ b/src/request.rs @@ -347,7 +347,7 @@ impl Drop for HttpRequest { /// # Examples /// ``` /// use actix_web::{web, App, HttpRequest}; -/// use serde_derive::Deserialize; +/// use serde::Deserialize; /// /// /// extract `Thing` from request /// async fn index(req: HttpRequest) -> String { diff --git a/src/route.rs b/src/route.rs index 44f7e30b..d85b940b 100644 --- a/src/route.rs +++ b/src/route.rs @@ -5,7 +5,7 @@ use std::{future::Future, rc::Rc}; use actix_http::http::Method; use actix_service::{ boxed::{self, BoxService, BoxServiceFactory}, - Service, ServiceFactory, + Service, ServiceFactory, ServiceFactoryExt, }; use futures_core::future::LocalBoxFuture; @@ -128,9 +128,10 @@ impl Route { /// Set handler function, use request extractors for parameters. /// + /// # Examples /// ``` /// use actix_web::{web, http, App}; - /// use serde_derive::Deserialize; + /// use serde::Deserialize; /// /// #[derive(Deserialize)] /// struct Info { @@ -154,7 +155,7 @@ impl Route { /// /// ``` /// # use std::collections::HashMap; - /// # use serde_derive::Deserialize; + /// # use serde::Deserialize; /// use actix_web::{web, App}; /// /// #[derive(Deserialize)] @@ -184,6 +185,53 @@ impl Route { self.service = boxed::factory(HandlerService::new(handler)); self } + + /// Set raw service to be constructed and called as the request handler. + /// + /// # Examples + /// ``` + /// # use std::convert::Infallible; + /// # use futures_util::future::LocalBoxFuture; + /// # use actix_web::{*, dev::*, http::header}; + /// struct HelloWorld; + /// + /// impl Service for HelloWorld { + /// type Response = ServiceResponse; + /// type Error = Infallible; + /// type Future = LocalBoxFuture<'static, Result>; + /// + /// always_ready!(); + /// + /// fn call(&self, req: ServiceRequest) -> Self::Future { + /// let (req, _) = req.into_parts(); + /// + /// let res = HttpResponse::Ok() + /// .insert_header(header::ContentType::plaintext()) + /// .body("Hello world!"); + /// + /// Box::pin(async move { Ok(ServiceResponse::new(req, res)) }) + /// } + /// } + /// + /// App::new().route( + /// "/", + /// web::get().service(fn_factory(|| async { Ok(HelloWorld) })), + /// ); + /// ``` + pub fn service(mut self, service_factory: S) -> Self + where + S: ServiceFactory< + ServiceRequest, + Response = ServiceResponse, + Error = E, + InitError = (), + Config = (), + > + 'static, + E: Into + 'static, + { + self.service = boxed::factory(service_factory.map_err(Into::into)); + self + } } #[cfg(test)] @@ -192,9 +240,12 @@ mod tests { use actix_rt::time::sleep; use bytes::Bytes; - use serde_derive::Serialize; + use futures_core::future::LocalBoxFuture; + use serde::Serialize; - use crate::http::{Method, StatusCode}; + use crate::dev::{always_ready, fn_factory, fn_service, Service}; + use crate::http::{header, Method, StatusCode}; + use crate::service::{ServiceRequest, ServiceResponse}; use crate::test::{call_service, init_service, read_body, TestRequest}; use crate::{error, web, App, HttpResponse}; @@ -268,4 +319,65 @@ mod tests { let body = read_body(resp).await; assert_eq!(body, Bytes::from_static(b"{\"name\":\"test\"}")); } + + #[actix_rt::test] + async fn test_service_handler() { + struct HelloWorld; + + impl Service for HelloWorld { + type Response = ServiceResponse; + type Error = crate::Error; + type Future = LocalBoxFuture<'static, Result>; + + always_ready!(); + + fn call(&self, req: ServiceRequest) -> Self::Future { + let (req, _) = req.into_parts(); + + let res = HttpResponse::Ok() + .insert_header(header::ContentType::plaintext()) + .body("Hello world!"); + + Box::pin(async move { Ok(ServiceResponse::new(req, res)) }) + } + } + + let srv = init_service( + App::new() + .route( + "/hello", + web::get().service(fn_factory(|| async { Ok(HelloWorld) })), + ) + .route( + "/bye", + web::get().service(fn_factory(|| async { + Ok::<_, ()>(fn_service(|req: ServiceRequest| async { + let (req, _) = req.into_parts(); + + let res = HttpResponse::Ok() + .insert_header(header::ContentType::plaintext()) + .body("Goodbye, and thanks for all the fish!"); + + Ok::<_, Infallible>(ServiceResponse::new(req, res)) + })) + })), + ), + ) + .await; + + let req = TestRequest::get().uri("/hello").to_request(); + let resp = call_service(&srv, req).await; + assert_eq!(resp.status(), StatusCode::OK); + let body = read_body(resp).await; + assert_eq!(body, Bytes::from_static(b"Hello world!")); + + let req = TestRequest::get().uri("/bye").to_request(); + let resp = call_service(&srv, req).await; + assert_eq!(resp.status(), StatusCode::OK); + let body = read_body(resp).await; + assert_eq!( + body, + Bytes::from_static(b"Goodbye, and thanks for all the fish!") + ); + } }