diff --git a/Cargo.toml b/Cargo.toml index 3ff71a3d..db220227 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,7 @@ members = [ "actix-server-config", "actix-test-server", "actix-threadpool", + "actix-tower", "actix-utils", "router", ] diff --git a/actix-tower/CHANGES.md b/actix-tower/CHANGES.md new file mode 100644 index 00000000..06729a2e --- /dev/null +++ b/actix-tower/CHANGES.md @@ -0,0 +1,2 @@ +# Changes + diff --git a/actix-tower/Cargo.toml b/actix-tower/Cargo.toml new file mode 100644 index 00000000..e309b131 --- /dev/null +++ b/actix-tower/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "actix-tower" +version = "0.1.0" +authors = ["Nikolay Kim ", "Marcus Griep "] +description = "Actix Tower" +keywords = ["network", "framework", "async", "futures"] +homepage = "https://actix.rs" +repository = "https://github.com/actix/actix-net.git" +documentation = "https://docs.rs/actix-tower/" +categories = ["network-programming", "asynchronous"] +license = "MIT/Apache-2.0" +exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"] +edition = "2018" +workspace = ".." + +[badges] +travis-ci = { repository = "actix/actix-tower", branch = "master" } +appveyor = { repository = "actix/actix-net" } +codecov = { repository = "actix/actix-tower", branch = "master", service = "github" } + +[lib] +name = "actix_tower" +path = "src/lib.rs" + +[dependencies] +actix-service = "0.3.6" +futures = "0.1.24" +tower-service = "0.2.0" + diff --git a/actix-tower/src/lib.rs b/actix-tower/src/lib.rs new file mode 100644 index 00000000..1e985db2 --- /dev/null +++ b/actix-tower/src/lib.rs @@ -0,0 +1,191 @@ +//! Utilities to ease interoperability with services based on the `tower-service` crate. + +use actix_service::Service as ActixService; +use std::marker::PhantomData; +use tower_service::Service as TowerService; + +/// Compatibility wrapper associating a `tower_service::Service` with a particular +/// `Request` type, so that it can be used as an `actix_service::Service`. +pub struct TowerCompat { + inner: S, + _phantom: PhantomData, +} + +impl TowerCompat { + /// Wraps a `tower_service::Service` in a compatibility wrapper. + pub fn new(inner: S) -> Self { + TowerCompat { + inner, + _phantom: PhantomData, + } + } +} + +/// Extension trait for wrapping `tower_service::Service` instances for use as +/// an `actix_service::Service`. +pub trait TowerServiceExt { + /// Wraps a `tower_service::Service` in a compatibility wrapper. + fn compat(self) -> TowerCompat + where + Self: TowerService + Sized; +} + +impl TowerServiceExt for S { + fn compat(self) -> TowerCompat + where + Self: TowerService + Sized + { + TowerCompat::new(self) + } +} + +impl ActixService for TowerCompat +where + S: TowerService, +{ + type Request = R; + type Response = S::Response; + type Error = S::Error; + type Future = S::Future; + + fn poll_ready(&mut self) -> futures::Poll<(), Self::Error> { + TowerService::poll_ready(&mut self.inner) + } + + fn call(&mut self, req: Self::Request) -> Self::Future { + TowerService::call(&mut self.inner, req) + } +} + +#[cfg(test)] +mod tests { + use super::TowerServiceExt; + use actix_service::{Service as ActixService, ServiceExt, Transform}; + use futures::{future::FutureResult, Async, Poll, Future}; + use tower_service::Service as TowerService; + + struct RandomService; + impl TowerService for RandomService { + type Response = u32; + type Error = (); + type Future = FutureResult; + + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + Ok(Async::Ready(())) + } + + fn call(&mut self, _req: R) -> Self::Future { + futures::finished(4) + } + } + + #[test] + fn tower_service_as_actix_service_returns_4() { + let mut s = RandomService.compat(); + + assert_eq!(Ok(Async::Ready(())), s.poll_ready()); + + assert_eq!(Ok(Async::Ready(4)), s.call(()).poll()); + } + + #[test] + fn tower_service_as_actix_service_can_combine() { + let mut s = RandomService.compat().map(|x| x + 1); + + assert_eq!(Ok(Async::Ready(())), s.poll_ready()); + + assert_eq!(Ok(Async::Ready(5)), s.call(()).poll()); + } + + struct AddOneService; + impl TowerService for AddOneService { + type Response = u32; + type Error = (); + type Future = FutureResult; + + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + Ok(Async::Ready(())) + } + + fn call(&mut self, req: u32) -> Self::Future { + futures::finished(req + 1) + } + } + + #[test] + fn tower_services_as_actix_services_chained() { + let s1 = RandomService.compat(); + let s2 = AddOneService.compat(); + let s3 = AddOneService.compat(); + + let mut s = s1.and_then(s2).and_then(s3); + + assert_eq!(Ok(Async::Ready(())), s.poll_ready()); + + assert_eq!(Ok(Async::Ready(6)), s.call(()).poll()); + } + + #[test] + fn tower_services_as_actix_services_chained_2() { + let s1 = RandomService.compat(); + let s2 = AddOneService.compat(); + let s3 = AddOneService.compat(); + let s4 = RandomService.compat(); + + let mut s = s1.and_then(s2).and_then(s3).and_then(s4); + + assert_eq!(Ok(Async::Ready(())), s.poll_ready()); + + assert_eq!(Ok(Async::Ready(4)), s.call(()).poll()); + } + + struct DoMathTransform(S); + impl ActixService for DoMathTransform + where + S: ActixService, + S::Future: 'static, + { + type Request = S::Request; + type Response = u32; + type Error = S::Error; + type Future = Box>; + + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + self.0.poll_ready() + } + + fn call(&mut self, req: Self::Request) -> Self::Future { + let fut = self.0.call(req).map(|x| x * 17); + Box::new(fut) + } + } + + struct DoMath; + impl Transform for DoMath + where + S: ActixService, + S::Future: 'static, + { + type Request = S::Request; + type Response = u32; + type Error = S::Error; + type Transform = DoMathTransform; + type InitError = (); + type Future = FutureResult; + + fn new_transform(&self, service: S) -> Self::Future { + futures::finished(DoMathTransform(service)) + } + } + + #[test] + fn tower_service_as_actix_service_can_be_transformed() { + let transform = DoMath; + + let mut s = transform.new_transform(RandomService.compat()).wait().unwrap(); + + assert_eq!(Ok(Async::Ready(())), s.poll_ready()); + + assert_eq!(Ok(Async::Ready(68)), s.call(()).poll()); + } +} \ No newline at end of file