Skip to content

Instantly share code, notes, and snippets.

@lambdageek
Last active July 16, 2025 18:13
Show Gist options
  • Save lambdageek/9fb3cdd71d2a071b10468bec4435ddb0 to your computer and use it in GitHub Desktop.
Save lambdageek/9fb3cdd71d2a071b10468bec4435ddb0 to your computer and use it in GitHub Desktop.
//! 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);
}
@lambdageek
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment