2 Commits

Author SHA1 Message Date
Tim Kuehn
a6758fd1f9 BeforeRequest hook chaining.
It's unintuitive that serve.before(hook1).before(hook2) executes in
reverse order, with hook2 going before hook1. With BeforeRequestList,
users can write `before().then(hook1).then(hook2).serving(serve)`, and
it will run hook1, then hook2, then the service fn.
2023-12-29 23:13:06 -08:00
Tim Kuehn
2c241cc809 Fix readme and release notes. 2023-12-29 21:03:09 -08:00
8 changed files with 190 additions and 56 deletions

View File

@@ -118,13 +118,8 @@ implement it for our Server struct.
struct HelloServer; struct HelloServer;
impl World for HelloServer { impl World for HelloServer {
// Each defined rpc generates two items in the trait, a fn that serves the RPC, and async fn hello(self, _: context::Context, name: String) -> String {
// an associated type representing the future output by the fn. format!("Hello, {name}!")
type HelloFut = Ready<String>;
fn hello(self, _: context::Context, name: String) -> Self::HelloFut {
future::ready(format!("Hello, {name}!"))
} }
} }
``` ```

View File

@@ -14,7 +14,7 @@
### New Features ### New Features
- Request hooks are added to the serve trait, so that it's easy to hook in cross-cutting - Request hooks are added to the serve trait, so that it's easy to hook in cross-cutting
functionality look throttling, authorization, etc. functionality like throttling, authorization, etc.
- The Client trait is back! This makes it possible to hook in generic client functionality like load - The Client trait is back! This makes it possible to hook in generic client functionality like load
balancing, retries, etc. balancing, retries, etc.

View File

@@ -25,7 +25,8 @@ use tarpc::{
context, serde_transport, context, serde_transport,
server::{ server::{
incoming::{spawn_incoming, Incoming}, incoming::{spawn_incoming, Incoming},
BaseChannel, Serve, request_hook::{self, BeforeRequestList},
BaseChannel,
}, },
tokio_serde::formats::Json, tokio_serde::formats::Json,
ClientMessage, Response, ServerError, Transport, ClientMessage, Response, ServerError, Transport,
@@ -141,19 +142,21 @@ async fn main() -> anyhow::Result<()> {
let (add_listener1, addr1) = listen_on_random_port().await?; let (add_listener1, addr1) = listen_on_random_port().await?;
let (add_listener2, addr2) = listen_on_random_port().await?; let (add_listener2, addr2) = listen_on_random_port().await?;
let something_bad_happened = Arc::new(AtomicBool::new(false)); let something_bad_happened = Arc::new(AtomicBool::new(false));
let server = AddServer.serve().before(move |_: &mut _, _: &_| { let server = request_hook::before()
let something_bad_happened = something_bad_happened.clone(); .then_fn(move |_: &mut _, _: &_| {
async move { let something_bad_happened = something_bad_happened.clone();
if something_bad_happened.fetch_xor(true, Ordering::Relaxed) { async move {
Err(ServerError::new( if something_bad_happened.fetch_xor(true, Ordering::Relaxed) {
io::ErrorKind::NotFound, Err(ServerError::new(
"Gamma Ray!".into(), io::ErrorKind::NotFound,
)) "Gamma Ray!".into(),
} else { ))
Ok(()) } else {
Ok(())
}
} }
} })
}); .serving(AddServer.serve());
let add_server = add_listener1 let add_server = add_listener1
.chain(add_listener2) .chain(add_listener2)
.map(BaseChannel::with_defaults); .map(BaseChannel::with_defaults);

View File

@@ -36,7 +36,7 @@ pub mod limits;
pub mod incoming; pub mod incoming;
use request_hook::{ use request_hook::{
AfterRequest, AfterRequestHook, BeforeAndAfterRequestHook, BeforeRequest, BeforeRequestHook, AfterRequest, BeforeRequest, HookThenServe, HookThenServeThenHook, ServeThenHook,
}; };
/// Settings that control the behavior of [channels](Channel). /// Settings that control the behavior of [channels](Channel).
@@ -116,12 +116,12 @@ pub trait Serve {
/// let response = serve.serve(context::current(), 1); /// let response = serve.serve(context::current(), 1);
/// assert!(block_on(response).is_err()); /// assert!(block_on(response).is_err());
/// ``` /// ```
fn before<Hook>(self, hook: Hook) -> BeforeRequestHook<Self, Hook> fn before<Hook>(self, hook: Hook) -> HookThenServe<Self, Hook>
where where
Hook: BeforeRequest<Self::Req>, Hook: BeforeRequest<Self::Req>,
Self: Sized, Self: Sized,
{ {
BeforeRequestHook::new(self, hook) HookThenServe::new(self, hook)
} }
/// Runs a hook after completion of a request. /// Runs a hook after completion of a request.
@@ -159,12 +159,12 @@ pub trait Serve {
/// let response = serve.serve(context::current(), 1); /// let response = serve.serve(context::current(), 1);
/// assert!(block_on(response).is_err()); /// assert!(block_on(response).is_err());
/// ``` /// ```
fn after<Hook>(self, hook: Hook) -> AfterRequestHook<Self, Hook> fn after<Hook>(self, hook: Hook) -> ServeThenHook<Self, Hook>
where where
Hook: AfterRequest<Self::Resp>, Hook: AfterRequest<Self::Resp>,
Self: Sized, Self: Sized,
{ {
AfterRequestHook::new(self, hook) ServeThenHook::new(self, hook)
} }
/// Runs a hook before and after execution of the request. /// Runs a hook before and after execution of the request.
@@ -212,12 +212,12 @@ pub trait Serve {
fn before_and_after<Hook>( fn before_and_after<Hook>(
self, self,
hook: Hook, hook: Hook,
) -> BeforeAndAfterRequestHook<Self::Req, Self::Resp, Self, Hook> ) -> HookThenServeThenHook<Self::Req, Self::Resp, Self, Hook>
where where
Hook: BeforeRequest<Self::Req> + AfterRequest<Self::Resp>, Hook: BeforeRequest<Self::Req> + AfterRequest<Self::Resp>,
Self: Sized, Self: Sized,
{ {
BeforeAndAfterRequestHook::new(self, hook) HookThenServeThenHook::new(self, hook)
} }
} }

View File

@@ -16,7 +16,10 @@ mod after;
mod before_and_after; mod before_and_after;
pub use { pub use {
after::{AfterRequest, AfterRequestHook}, after::{AfterRequest, ServeThenHook},
before::{BeforeRequest, BeforeRequestHook}, before::{
before_and_after::BeforeAndAfterRequestHook, before, BeforeRequest, BeforeRequestCons, BeforeRequestList, BeforeRequestNil,
HookThenServe,
},
before_and_after::HookThenServeThenHook,
}; };

View File

@@ -29,18 +29,18 @@ where
} }
/// A Service function that runs a hook after request execution. /// A Service function that runs a hook after request execution.
pub struct AfterRequestHook<Serv, Hook> { pub struct ServeThenHook<Serv, Hook> {
serve: Serv, serve: Serv,
hook: Hook, hook: Hook,
} }
impl<Serv, Hook> AfterRequestHook<Serv, Hook> { impl<Serv, Hook> ServeThenHook<Serv, Hook> {
pub(crate) fn new(serve: Serv, hook: Hook) -> Self { pub(crate) fn new(serve: Serv, hook: Hook) -> Self {
Self { serve, hook } Self { serve, hook }
} }
} }
impl<Serv: Clone, Hook: Clone> Clone for AfterRequestHook<Serv, Hook> { impl<Serv: Clone, Hook: Clone> Clone for ServeThenHook<Serv, Hook> {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self { Self {
serve: self.serve.clone(), serve: self.serve.clone(),
@@ -49,7 +49,7 @@ impl<Serv: Clone, Hook: Clone> Clone for AfterRequestHook<Serv, Hook> {
} }
} }
impl<Serv, Hook> Serve for AfterRequestHook<Serv, Hook> impl<Serv, Hook> Serve for ServeThenHook<Serv, Hook>
where where
Serv: Serve, Serv: Serve,
Hook: AfterRequest<Serv::Resp>, Hook: AfterRequest<Serv::Resp>,
@@ -62,7 +62,7 @@ where
mut ctx: context::Context, mut ctx: context::Context,
req: Serv::Req, req: Serv::Req,
) -> Result<Serv::Resp, ServerError> { ) -> Result<Serv::Resp, ServerError> {
let AfterRequestHook { let ServeThenHook {
serve, mut hook, .. serve, mut hook, ..
} = self; } = self;
let mut resp = serve.serve(ctx, req).await; let mut resp = serve.serve(ctx, req).await;

View File

@@ -22,6 +22,38 @@ pub trait BeforeRequest<Req> {
async fn before(&mut self, ctx: &mut context::Context, req: &Req) -> Result<(), ServerError>; async fn before(&mut self, ctx: &mut context::Context, req: &Req) -> Result<(), ServerError>;
} }
/// A list of hooks that run in order before request execution.
pub trait BeforeRequestList<Req>: BeforeRequest<Req> {
/// The hook returned by `BeforeRequestList::then`.
type Then<Next>: BeforeRequest<Req>
where
Next: BeforeRequest<Req>;
/// Returns a hook that, when run, runs two hooks, first `self` and then `next`.
fn then<Next: BeforeRequest<Req>>(self, next: Next) -> Self::Then<Next>;
/// Same as `then`, but helps the compiler with type inference when Next is a closure.
fn then_fn<
Next: FnMut(&mut context::Context, &Req) -> Fut,
Fut: Future<Output = Result<(), ServerError>>,
>(
self,
next: Next,
) -> Self::Then<Next>
where
Self: Sized,
{
self.then(next)
}
/// The service fn returned by `BeforeRequestList::serving`.
type Serve<S: Serve<Req = Req>>: Serve<Req = Req>;
/// Runs the list of request hooks before execution of the given serve fn.
/// This is equivalent to `serve.before(before_request_chain)` but may be syntactically nicer.
fn serving<S: Serve<Req = Req>>(self, serve: S) -> Self::Serve<S>;
}
impl<F, Fut, Req> BeforeRequest<Req> for F impl<F, Fut, Req> BeforeRequest<Req> for F
where where
F: FnMut(&mut context::Context, &Req) -> Fut, F: FnMut(&mut context::Context, &Req) -> Fut,
@@ -33,27 +65,19 @@ where
} }
/// A Service function that runs a hook before request execution. /// A Service function that runs a hook before request execution.
pub struct BeforeRequestHook<Serv, Hook> { #[derive(Clone)]
pub struct HookThenServe<Serv, Hook> {
serve: Serv, serve: Serv,
hook: Hook, hook: Hook,
} }
impl<Serv, Hook> BeforeRequestHook<Serv, Hook> { impl<Serv, Hook> HookThenServe<Serv, Hook> {
pub(crate) fn new(serve: Serv, hook: Hook) -> Self { pub(crate) fn new(serve: Serv, hook: Hook) -> Self {
Self { serve, hook } Self { serve, hook }
} }
} }
impl<Serv: Clone, Hook: Clone> Clone for BeforeRequestHook<Serv, Hook> { impl<Serv, Hook> Serve for HookThenServe<Serv, Hook>
fn clone(&self) -> Self {
Self {
serve: self.serve.clone(),
hook: self.hook.clone(),
}
}
}
impl<Serv, Hook> Serve for BeforeRequestHook<Serv, Hook>
where where
Serv: Serve, Serv: Serve,
Hook: BeforeRequest<Serv::Req>, Hook: BeforeRequest<Serv::Req>,
@@ -66,10 +90,121 @@ where
mut ctx: context::Context, mut ctx: context::Context,
req: Self::Req, req: Self::Req,
) -> Result<Serv::Resp, ServerError> { ) -> Result<Serv::Resp, ServerError> {
let BeforeRequestHook { let HookThenServe {
serve, mut hook, .. serve, mut hook, ..
} = self; } = self;
hook.before(&mut ctx, &req).await?; hook.before(&mut ctx, &req).await?;
serve.serve(ctx, req).await serve.serve(ctx, req).await
} }
} }
/// Returns a request hook builder that runs a series of hooks before request execution.
///
/// Example
///
/// ```rust
/// use futures::{executor::block_on, future};
/// use tarpc::{context, ServerError, server::{Serve, serve, request_hook::{self,
/// BeforeRequest, BeforeRequestList}}};
/// use std::{cell::Cell, io};
///
/// let i = Cell::new(0);
/// let serve = request_hook::before()
/// .then_fn(|_, _| async {
/// assert!(i.get() == 0);
/// i.set(1);
/// Ok(())
/// })
/// .then_fn(|_, _| async {
/// assert!(i.get() == 1);
/// i.set(2);
/// Ok(())
/// })
/// .serving(serve(|_ctx, i| async move { Ok(i + 1) }));
/// let response = serve.clone().serve(context::current(), 1);
/// assert!(block_on(response).is_ok());
/// assert!(i.get() == 2);
/// ```
pub fn before() -> BeforeRequestNil {
BeforeRequestNil
}
/// A list of hooks that run in order before a request is executed.
#[derive(Clone, Copy)]
pub struct BeforeRequestCons<First, Rest>(First, Rest);
/// A noop hook that runs before a request is executed.
#[derive(Clone, Copy)]
pub struct BeforeRequestNil;
impl<Req, First: BeforeRequest<Req>, Rest: BeforeRequest<Req>> BeforeRequest<Req>
for BeforeRequestCons<First, Rest>
{
async fn before(&mut self, ctx: &mut context::Context, req: &Req) -> Result<(), ServerError> {
let BeforeRequestCons(first, rest) = self;
first.before(ctx, req).await?;
rest.before(ctx, req).await?;
Ok(())
}
}
impl<Req> BeforeRequest<Req> for BeforeRequestNil {
async fn before(&mut self, _: &mut context::Context, _: &Req) -> Result<(), ServerError> {
Ok(())
}
}
impl<Req, First: BeforeRequest<Req>, Rest: BeforeRequestList<Req>> BeforeRequestList<Req>
for BeforeRequestCons<First, Rest>
{
type Then<Next> = BeforeRequestCons<First, Rest::Then<Next>> where Next: BeforeRequest<Req>;
fn then<Next: BeforeRequest<Req>>(self, next: Next) -> Self::Then<Next> {
let BeforeRequestCons(first, rest) = self;
BeforeRequestCons(first, rest.then(next))
}
type Serve<S: Serve<Req = Req>> = HookThenServe<S, Self>;
fn serving<S: Serve<Req = Req>>(self, serve: S) -> Self::Serve<S> {
HookThenServe::new(serve, self)
}
}
impl<Req> BeforeRequestList<Req> for BeforeRequestNil {
type Then<Next> = BeforeRequestCons<Next, BeforeRequestNil> where Next: BeforeRequest<Req>;
fn then<Next: BeforeRequest<Req>>(self, next: Next) -> Self::Then<Next> {
BeforeRequestCons(next, BeforeRequestNil)
}
type Serve<S: Serve<Req = Req>> = S;
fn serving<S: Serve<Req = Req>>(self, serve: S) -> S {
serve
}
}
#[test]
fn before_request_list() {
use crate::server::serve;
use futures::executor::block_on;
use std::cell::Cell;
let i = Cell::new(0);
let serve = before()
.then_fn(|_, _| async {
assert!(i.get() == 0);
i.set(1);
Ok(())
})
.then_fn(|_, _| async {
assert!(i.get() == 1);
i.set(2);
Ok(())
})
.serving(serve(|_ctx, i| async move { Ok(i + 1) }));
let response = serve.clone().serve(context::current(), 1);
assert!(block_on(response).is_ok());
assert!(i.get() == 2);
}

View File

@@ -11,13 +11,13 @@ use crate::{context, server::Serve, ServerError};
use std::marker::PhantomData; use std::marker::PhantomData;
/// A Service function that runs a hook both before and after request execution. /// A Service function that runs a hook both before and after request execution.
pub struct BeforeAndAfterRequestHook<Req, Resp, Serv, Hook> { pub struct HookThenServeThenHook<Req, Resp, Serv, Hook> {
serve: Serv, serve: Serv,
hook: Hook, hook: Hook,
fns: PhantomData<(fn(Req), fn(Resp))>, fns: PhantomData<(fn(Req), fn(Resp))>,
} }
impl<Req, Resp, Serv, Hook> BeforeAndAfterRequestHook<Req, Resp, Serv, Hook> { impl<Req, Resp, Serv, Hook> HookThenServeThenHook<Req, Resp, Serv, Hook> {
pub(crate) fn new(serve: Serv, hook: Hook) -> Self { pub(crate) fn new(serve: Serv, hook: Hook) -> Self {
Self { Self {
serve, serve,
@@ -27,9 +27,7 @@ impl<Req, Resp, Serv, Hook> BeforeAndAfterRequestHook<Req, Resp, Serv, Hook> {
} }
} }
impl<Req, Resp, Serv: Clone, Hook: Clone> Clone impl<Req, Resp, Serv: Clone, Hook: Clone> Clone for HookThenServeThenHook<Req, Resp, Serv, Hook> {
for BeforeAndAfterRequestHook<Req, Resp, Serv, Hook>
{
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self { Self {
serve: self.serve.clone(), serve: self.serve.clone(),
@@ -39,7 +37,7 @@ impl<Req, Resp, Serv: Clone, Hook: Clone> Clone
} }
} }
impl<Req, Resp, Serv, Hook> Serve for BeforeAndAfterRequestHook<Req, Resp, Serv, Hook> impl<Req, Resp, Serv, Hook> Serve for HookThenServeThenHook<Req, Resp, Serv, Hook>
where where
Serv: Serve<Req = Req, Resp = Resp>, Serv: Serve<Req = Req, Resp = Resp>,
Hook: BeforeRequest<Req> + AfterRequest<Resp>, Hook: BeforeRequest<Req> + AfterRequest<Resp>,
@@ -48,7 +46,7 @@ where
type Resp = Resp; type Resp = Resp;
async fn serve(self, mut ctx: context::Context, req: Req) -> Result<Serv::Resp, ServerError> { async fn serve(self, mut ctx: context::Context, req: Req) -> Result<Serv::Resp, ServerError> {
let BeforeAndAfterRequestHook { let HookThenServeThenHook {
serve, mut hook, .. serve, mut hook, ..
} = self; } = self;
hook.before(&mut ctx, &req).await?; hook.before(&mut ctx, &req).await?;