You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Cro框架中如何多次消费request.body?连接阻塞问题求助

问题描述

我正在编写一个Cro中间件,通过消费request.body做验证,代码如下:

before-matched {
    request-body -> (:$email, :$captcha-token, :$captcha-solution, *%_) {
        # 验证邮箱
        unless Email::Valid.mx($email).so {
            response.status = 400;
            content 'application/json', %(message => 'Invalid Email');
        }

        # 验证验证码
        unless $captcha.validate($captcha-token, $captcha-solution) {
            response.status = 401;
            content 'application/json', %(message => 'Invalid Captcha');
        }
    }
}

post -> 'api', 'subscribe' {
    put "outside";
    request-body -> (:$name, :$email, *%_) {
        put "inside";
        dd $name, $email;
        content 'application/json', %(message => $name);
    }
}

多次消费request.body后连接阻塞,示例里的"inside"从未打印。

可复现代码

use Cro::HTTP::Server;
use Cro::HTTP::Router;

sub MAIN() {
    my Cro::Service $http = Cro::HTTP::Server.new(
        http => <1.1>,
        host => "127.0.0.1",
        port => 10000,
        application => routes()
    );
    $http.start;

    put "Listening at http://127.0.0.1:10000";
    react {
        whenever signal(SIGINT) {
            say "Shutting down...";
            $http.stop;
            done;
        }
    }
}

sub routes() {
    route {
        before-matched {
            request-body-text -> $body {
                put "in before-matched: `{$body}'";
            }
        }

        post -> {
            put "in post route before request-body-text";
            dd request.body-text;
            request-body-text -> $body {
                put "in post route: `{$body}'";
            }
        }
    }
}

请求与现象

用curl -v 'http://127.0.0.1:10000' --data-raw 'some-text'请求后,程序打印以下内容后挂起:

andinus@cadmium /tmp> raku cro-question-mre.raku
Listening at http://127.0.0.1:10000
in before-matched: `some-text'
in post route before request-body-text
Promise.new(scheduler => ThreadPoolScheduler.new(uncaught_handler => Callable), status => PromiseStatus::Planned)

request.body-text返回Promise,但后续无进展。即使只消费一次request.body也有同样问题,请问哪里操作错了?

解决方案

核心问题

HTTP请求体是流式数据,只能被消费一次。第一次在before-matched里用request-body-text读取后,请求体流已经耗尽,后续再尝试读取就会一直等待永远不会完成的Promise,导致连接阻塞。

解决步骤

  1. 缓存请求体内容:在中间件里读取请求体后,把内容缓存到请求的自定义属性中,后续路由直接使用缓存的内容,不要再尝试重新读取请求体流。
  2. 避免重复消费流:所有需要使用请求体的逻辑都依赖缓存的内容,不要多次调用request-body或request-body-text。

修改后的可复现代码

use Cro::HTTP::Server;
use Cro::HTTP::Router;

sub MAIN() {
    my Cro::Service $http = Cro::HTTP::Server.new(
        http => <1.1>,
        host => "127.0.0.1",
        port => 10000,
        application => routes()
    );
    $http.start;

    put "Listening at http://127.0.0.1:10000";
    react {
        whenever signal(SIGINT) {
            say "Shutting down...";
            $http.stop;
            done;
        }
    }
}

sub routes() {
    route {
        before-matched {
            request-body-text -> $body {
                put "in before-matched: `{$body}'";
                # 将请求体内容缓存到请求的自定义属性中
                request.cache-body = $body;
            }
        }

        post -> {
            put "in post route before cached body";
            # 直接使用缓存的内容,不再读取请求体流
            dd request.cache-body;
            put "in post route: `{request.cache-body}'";
            content 'text/plain', request.cache-body;
        }
    }
}

针对验证场景的优化

在你的验证中间件场景中,读取请求体做验证后,需要把解析后的参数也缓存下来,后续路由直接使用缓存的参数,避免重复解析:

before-matched {
    request-body -> (:$email, :$captcha-token, :$captcha-solution, *%rest) {
        # 邮箱验证
        unless Email::Valid.mx($email).so {
            response.status = 400;
            content 'application/json', %(message => 'Invalid Email');
        }

        # 验证码验证
        unless $captcha.validate($captcha-token, $captcha-solution) {
            response.status = 401;
            content 'application/json', %(message => 'Invalid Captcha');
        }

        # 缓存解析后的所有参数
        request.parsed-body = %(email => $email, captcha-token => $captcha-token, captcha-solution => $captcha-solution, |%rest);
    }
}

post -> 'api', 'subscribe' {
    put "outside";
    # 直接使用缓存的解析后参数
    my (:$name, :$email) = request.parsed-body;
    put "inside";
    dd $name, $email;
    content 'application/json', %(message => $name);
}

内容的提问来源于stack exchange,提问作者Andinus

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 01:09:59