I'm writing a middleware that consumes request.body and does some validation like so:
before-matched {
request-body -> (:$email, :$captcha-token, :$captcha-solution, *%_) {
# Validate the email.
unless Email::Valid.mx($email).so {
response.status = 400;
content 'application/json', %(message => 'Invalid Email');
}
# Validate captcha.
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);
}
}
I tried consuming request.body multiple times and the connection hangs. "inside" is never printed (from the example above).
Here is a reproducible example:
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}'";
}
}
}
}
When making a request to this server with curl -v 'http://127.0.0.1:10000' --data-raw 'some-text'
it hangs after printing these lines:
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
does return a promise, I'm not sure I understand what is happening after that. I tried using this but consuming request.body
once has the same behavior. Am I doing this wrong?
If wanting to both consume the request body in middleware and make it available for the standard request handler, then the middleware needs to reinstate it by calling set-body
. A working example can be found in the Cro OpenAPI request validator middleware.
For your example, the change would be:
request-body-text -> $body {
put "in before-matched: `{$body}'";
request.set-body($body);
}
The addition of the set-body
call results in the desired output:
in before-matched: `this is raw data'
in post route before request-body-text
Promise.new(scheduler => ThreadPoolScheduler.new(uncaught_handler => Callable), status => PromiseStatus::Planned)
in post route: `this is raw data'
A peek-body
and similar has been proposed to ease writing middleware that wishes to inspect the body, but hasn't been implemented yet.