Last active
July 16, 2025 18:13
-
-
Save lambdageek/9fb3cdd71d2a071b10468bec4435ddb0 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//! A server with some middleware | |
//! | |
//! We have some server process [`server`] | |
//! that produces byte slices `&[u8]` and feeds them to some | |
//! [`ServerResponder`] client. Since the client gets the | |
//! bytes by reference and it must be prepared to receive them | |
//! for any lifetime, it's effectively a `for<'a> Fn(&'a [u8])` callback. | |
//! | |
//! Now to make things more interesting, we also want to have some middleware | |
//! that transforms the initial vector slice into something. | |
//! That something can either be a value or it can be a reference with the same | |
//! server-selected runtime. (So for example the middleware could convert the bytes | |
//! to a [`String`] or it can leave them alone as [`&'a [u8]`] for some `'a`) | |
//! | |
//! | |
//! Now let's make the Middleware and ServerResponder into traits. | |
//! | |
//! The code below (see [`main`]) shows how we can make middlware | |
//! that either makes values ([`CallbackMiddleware`]) or returns references ([`CallbackRefMiddleware`]) while | |
//! being sufficiently general and working over any lifetime `'a` | |
/// Middleware takes some bytes and creates some kind | |
/// of intermediate byproduct `Self::Product` | |
trait Middleware<'a> { | |
type Product : 'a; | |
fn transform(&self, bytes: &'a [u8]) -> Self::Product; | |
} | |
/// A server responder just consumes some input | |
trait ServerResponder<TInput> { | |
fn respond(&self, input: TInput); | |
} | |
/// The server takes some middleware and a responder and | |
/// feeds them some bytes | |
fn server<M,R>(middleware: std::sync::Arc<M>, responder: R) | |
where | |
M: for<'a> Middleware<'a> + Sync + Send + 'static, | |
R: for<'a> ServerResponder<<M as Middleware<'a>>::Product> + Send + 'static | |
{ | |
let s = foo(); | |
let middleware = middleware.clone(); | |
let t = std::thread::spawn(move ||{ | |
responder.respond(middleware.transform(s.as_bytes())) | |
}); | |
t.join().unwrap(); | |
() | |
} | |
/// Just a sample source of bytes | |
fn foo() -> String { | |
format!("hello pid:{pid}, tid:{tid:?}", pid = std::process::id(), tid = std::thread::current().id()) | |
} | |
/// Some middleware that does nothing | |
struct IdentityMiddleware; | |
impl<'a> Middleware<'a> for IdentityMiddleware { | |
type Product = &'a [u8]; | |
fn transform(&self, bytes: &'a [u8]) -> Self::Product { | |
bytes | |
} | |
} | |
/// A responder that expects a ref to a slice of bytes and prints them | |
struct PrintBytesResponder; | |
impl<'a> ServerResponder<&'a [u8]> for PrintBytesResponder { | |
fn respond(&self, input: &'a [u8]) { | |
println!("Got {s}", s = str::from_utf8(input).unwrap()) | |
} | |
} | |
/// A middleware that calls a callback that returns a value R | |
struct CallbackMiddleware<R> | |
{ | |
callback: Box<dyn for<'a> Fn(&'a [u8]) -> R + Send + Sync + 'static>, | |
} | |
impl<'a, R> Middleware<'a> for CallbackMiddleware<R> | |
where | |
R: 'a, | |
{ | |
type Product = R; | |
fn transform(&self, bytes: &'a [u8]) -> Self::Product { | |
(self.callback)(bytes) | |
} | |
} | |
impl<R> CallbackMiddleware<R> | |
{ | |
pub fn new<F>(callback: F) -> Self | |
where | |
F: for<'a> Fn(&'a [u8]) -> R + Send + Sync + 'static, | |
{ | |
Self { callback: Box::new(callback)} | |
} | |
} | |
/// A callback that takes a slice of bytes and | |
/// returns a reference to some result type with the same | |
/// lifetime as the bytes | |
struct CallbackRefMiddleware<R, F> | |
where | |
R: ?Sized, | |
F: for<'a> Fn(&'a [u8]) -> &'a R + Send + Sync + 'static | |
{ | |
callback: Box<F>, | |
} | |
impl<'a, R, F> Middleware<'a> for CallbackRefMiddleware<R, F> | |
where | |
R: 'a + ?Sized, | |
F: for <'b> Fn(&'b [u8]) -> &'b R + Send + Sync + 'static | |
{ | |
type Product = &'a R; | |
fn transform(&self, bytes: &'a [u8]) -> Self::Product { | |
(self.callback)(bytes) | |
} | |
} | |
impl<R, F> CallbackRefMiddleware<R, F> | |
where | |
R: ?Sized, | |
F: for<'b> Fn(&'b [u8]) -> &'b R + Send + Sync + 'static | |
{ | |
pub fn new(callback: F) -> Self | |
{ | |
Self { callback: Box::new(callback)} | |
} | |
} | |
/// A responder that consumes a String and prints it | |
struct PrintStringResponder; | |
impl ServerResponder<String> for PrintStringResponder { | |
fn respond(&self, input: String) { | |
println!("Got {input}") | |
} | |
} | |
/// A responder that takes a string slice and prints it | |
struct PrintStrResponder; | |
impl<'a> ServerResponder<&'a str> for PrintStrResponder { | |
fn respond(&self, input: &'a str) { | |
println!("Got {input}") | |
} | |
} | |
pub fn main() { | |
// make an identity middleware the hard way by using a callback | |
let mid = std::sync::Arc::new (CallbackRefMiddleware::new(|bytes| bytes)); | |
let resp = PrintBytesResponder; | |
server(mid, resp); | |
// make a middleware that handles the String conversion | |
let mid = std::sync::Arc::new (CallbackMiddleware::new(|bytes| { | |
str::from_utf8(bytes).unwrap().to_string() | |
})); | |
let resp = PrintStringResponder; | |
server(mid, resp); | |
// make a midway middleware that converts to a string slice | |
let mid = std::sync::Arc::new (CallbackRefMiddleware::new(|bytes| { | |
str::from_utf8(bytes).unwrap() | |
})); | |
let resp = PrintStrResponder; | |
server(mid, resp); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Rust playground