i5ting / httprpc

express migrate to koa
3 stars 0 forks source link

demo #12

Closed i5ting closed 1 year ago

i5ting commented 1 year ago

demo

koa-compose可以实现 koa 中间件的组合,通过将多个中间件函数合并成一个大的中间件函数,从而实现多个中间件函数的有序执行。下面是一个实现koa-compose的Rust示例代码:

type Middleware = fn(&mut Context, &mut dyn FnMut() -> Result<(), Error>) -> Result<(), Error>;

fn compose(middleware: Vec) -> Middleware { move |ctx: &mut Context, next: &mut dyn FnMut() -> Result<(), Error>| { // 递归执行中间件函数 fn dispatch(mut i: usize, ctx: &mut Context, middleware: &Vec, next: &mut dyn FnMut() -> Result<(), Error>) -> Result<(), Error> { if i == middleware.len() { return next(); } let mw = middleware[i]; mw(ctx, &mut || dispatch(i + 1, ctx, middleware, next)) } dispatch(0, ctx, &middleware, next) } } 这里的Middleware类型是一个闭包,它接收一个上下文对象Context和一个next参数(也是一个闭包),next表示执行下一个中间件函数的方法。compose函数接收一个中间件函数数组,返回一个合成的中间件函数。在合成的中间件函数中,我们使用了递归调用的方式,依次执行每个中间件函数。当所有中间件函数执行完毕后,最后执行next方法。

使用rust await实现koa-compose这样的中间件机制,并给出中间件await前后增加日志的调用示例

好的,下面是使用Rust async/await语法实现Koa-Compose中间件机制,并在中间件await前后增加日志的示例代码和调用示例:

use std::future::Future;
use std::pin::Pin;

type NextFn = Box<dyn FnOnce() -> Pin<Box<dyn Future<Output = ()>>> + Send>;
type MiddlewareFn = Box<dyn Fn(Context, NextFn) -> Pin<Box<dyn Future<Output = ()>>> + Send>;

#[derive(Default)]
struct Context {
    state: String,
}

async fn middleware1(ctx: Context, next: NextFn) {
    println!("Entering middleware1...");
    println!("middleware1: {}", ctx.state);
    next().await;
    println!("Leaving middleware1...");
}

async fn middleware2(ctx: Context, next: NextFn) {
    println!("Entering middleware2...");
    println!("middleware2: {}", ctx.state);
    next().await;
    println!("Leaving middleware2...");
}

async fn middleware3(ctx: Context, next: NextFn) {
    println!("Entering middleware3...");
    println!("middleware3: {}", ctx.state);
    next().await;
    println!("Leaving middleware3...");
}

fn compose(middleware_list: Vec<MiddlewareFn>) -> MiddlewareFn {
    let mut mw = middleware_list.last().unwrap().clone();
    for m in middleware_list.into_iter().rev().skip(1) {
        let next = mw;
        mw = Box::new(move |ctx: Context, n: NextFn| -> Pin<Box<dyn Future<Output = ()>>> {
            let next = next;
            let fut = async move { n().await };
            Box::pin(async move {
                m(ctx.clone(), next).await;
                fut.await;
            })
        });
    }
    mw
}

#[tokio::main]
async fn main() {
    let mut ctx = Context::default();
    ctx.state = "Hello World!".to_owned();
    let middleware_list = vec![Box::new(middleware1), Box::new(middleware2), Box::new(middleware3)];
    let composed_mw = compose(middleware_list);
    let fut = Box::pin(async move { composed_mw(ctx, Box::new(|| Box::pin(async { () }))).await });
    fut.await;
}

在这个示例中,我们在每个中间件函数的await前后分别增加了日志输出,以便更好地观察中间件的执行顺序。我们使用clone()方法复制了Context对象,以便在异步闭包中使用。

在compose函数中,我们使用嵌套的异步闭包来执行中间件函数和下一个中间件函数。在这个嵌套的闭包中,我们首先调用当前中间件函数,然后使用await关键字等待下一个中间件函数的执行结果。

在main函数中,我们创建了一个Context对象,并将其state属性设置为"Hello World!"。然后,我们使用compose函数将三个中间件函数合成为一个中间件函数composed_mw。最后,我们使用Box::pin将一个异步闭包转换成一个Pin<Box<dyn Future>>类型的Future,并使用await关键字等待这个Future的执行结果。

如果你运行这个示例代码,你会看到如下输出:

Entering middleware1... middleware1: Hello World! Entering middleware2... middleware2: Hello World! Entering middleware3... middleware3: Hello World! Leaving middleware3... Leaving middleware2... Leaving middleware1...

这说明我们的Koa-Compose中间件机制已经成功地合成了三个中间件函数,并按照正确的顺序执行它们。同时,我们也成功地加入了日志输出,方便我们观察中间件函数的执行过程。

i5ting commented 1 year ago

由于Rust的异步编程模型和JavaScript不同,因此Koa的中间件机制无法直接用宏await实现。不过,可以使用Future和async/await语法模拟出类似的中间件机制,实现方式如下:

定义一个Middleware类型,它接受一个NextFuture类型的参数,表示下一个中间件,同时返回一个Future类型的值。 type NextFuture = Box<dyn Future<Output = Result<(), String>> + Send + Sync>;

type Middleware = fn(NextFuture) -> Future<Output = Result<(), String>>; 定义compose函数,它接受多个中间件函数作为参数,返回一个复合后的中间件函数。 fn compose(middlewares: Vec) -> Middleware { let mut middlewares = middlewares;

// 在最后一个中间件之后添加一个空函数作为下一个中间件
middlewares.push(|_next| async { Ok(()) });

// 递归调用中间件函数,形成一个Future链
let mut next = Box::new(async { Ok(()) }) as NextFuture;
for middleware in middlewares.into_iter().rev() {
    let current_next = next;
    next = Box::new(middleware(current_next));
}

// 返回第一个中间件
return move |next| Box::new(next.then(move |result| next(result))) as Future<Output = Result<(), String>>;

} 使用compose函数创建一个复合的中间件函数,并在调用每个中间件前后增加日志输出。 async fn middleware1(next: NextFuture) -> Result<(), String> { println!("middleware1: before"); let result = next.await; println!("middleware1: after"); result }

async fn middleware2(next: NextFuture) -> Result<(), String> { println!("middleware2: before"); let result = next.await; println!("middleware2: after"); result }

[tokio::main]

async fn main() { let middleware = compose(vec![middleware1, middleware2]); middleware(Box::new(async { Ok(()) })).await.unwrap(); } 在上面的示例中,compose函数将middleware1和middleware2合并成一个复合的中间件函数,并在调用每个中间件前后输出日志。程序的输出如下:

middleware2: before middleware1: before middleware1: after middleware2: after

i5ting commented 1 year ago

使用rust 宏 await实现koa-compose这样的中间件机制,中间件参数有context和next,并给出中间件await前后增加日志的调用示例

ChatGPT3.5 首先,需要使用futures::future::Future来实现await,所以需要将中间件的返回值类型改为Box<dyn Future + Send + 'static>。

接下来,就可以使用macro_rules!定义一个koa_compose宏,该宏接收一个中间件列表,并返回一个组合了所有中间件的函数,该函数接收context和next作为参数,然后将它们传递给第一个中间件,并执行组合后的中间件链。

下面是一个实现了上述要求的示例代码:

use futures::future::{FutureExt, join_all};

#[macro_export]
macro_rules! koa_compose {
    (@inner $fn_name: ident, [$($middlewares:ident),*]) => {
        fn $fn_name(context: Context, next: Next) -> Box<dyn Future<Output = ()> + Send + 'static> {
            async move {
                $(log::info!("Entering middleware {}", stringify!($middlewares));
                let middleware_future = $middlewares(context, next);
                middleware_future.await;
                log::info!("Leaving middleware {}", stringify!($middlewares));)*
            }.boxed()
        }
    };
    ($($middlewares:ident),+) => {
        koa_compose!(@inner koa_handler, [$($middlewares),*]); koa_handler
    };
}

type Context = ();
type Next = Box<dyn FnOnce(Context) -> Box<dyn Future<Output = ()> + Send + 'static> + Send + 'static>;

async fn middleware_one(context: Context, next: Next) -> Box<dyn Future<Output = ()> + Send + 'static> {
    log::info!("Entering middleware_one");
    next(context).await;
    log::info!("Leaving middleware_one");
    Box::pin(async move {})
}

async fn middleware_two(context: Context, next: Next) -> Box<dyn Future<Output = ()> + Send + 'static> {
    log::info!("Entering middleware_two");
    next(context).await;
    log::info!("Leaving middleware_two");
    Box::pin(async move {})
}

fn main() {
    let mut rt = tokio::runtime::Runtime::new().unwrap();
    rt.block_on(async move {
        let middlewares = koa_compose!(middleware_one, middleware_two);
        let _result = middlewares(Context {}, Box::new(|_| Box::pin(async move {})));
    });
}

在上面的示例中,我们定义了koa_compose宏,它接收一个中间件列表,并返回一个组合了所有中间件的函数。每个中间件都被定义为一个异步函数,它接收context和next作为参数。在中间件的实现中,我们加入了日志记录,以便更好地了解中间件链的执行顺序。

在主函数中,我们使用koa_compose宏创建了一个组合了两个中间件的函数,并调用该函数来执行中间件链。注意,我们在next调用中传递了一个空的异步函数,因为这是中间件链的末尾,没有下一个中间件要执行了。