Skip to content

Commit

Permalink
Improve request decoding performance + major clean ups (#67)
Browse files Browse the repository at this point in the history
* experiment with multipart

* 3.0.1

* remove uselss dependency
  • Loading branch information
rawleyfowler authored Nov 9, 2023
1 parent 19f6c44 commit 5a78ca1
Show file tree
Hide file tree
Showing 14 changed files with 190 additions and 162 deletions.
2 changes: 1 addition & 1 deletion META6.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,5 @@
"Test::Util::ServerPort",
"Cro::HTTP::Client"
],
"version": "3.0.0"
"version": "3.0.1"
}
28 changes: 25 additions & 3 deletions it/01-basic.rakutest
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,16 @@ use Humming-Bird::Core;
use Test::Util::ServerPort;
use Cro::HTTP::Client;

plan 3;

my $body = 'abc';

get('/', -> $request, $response {
$response.write($body);
});
});

post('/form', -> $request, $response {
ok $request.body;
ok $request.content;
});

my $port = get-unused-port;

Expand All @@ -29,4 +32,23 @@ lives-ok({ $response = await $client.get('/') });
ok $response, 'Was response a success?';
is (await $response.body-text), 'abc', 'Is response body OK?';

# TODO: Fix this.
#lives-ok({
#$response = await $client.post: '/form',
# content-type => 'multipart/form-data',
# body => [
# name => 'foo',
# age => 123,
# Cro::HTTP::Body::MultiPartFormData::Part.new(
# headers => [Cro::HTTP::Header.new(
# name => 'Content-type',
# value => 'image/jpeg'
# )],
# name => 'photo',
# filename => 'baobao.jpg',
# body-blob => slurp('t/static/baobao.jpg', :bin)
# )
# ];
#});

done-testing;
24 changes: 2 additions & 22 deletions lib/Humming-Bird/Advice.rakumod
Original file line number Diff line number Diff line change
@@ -1,29 +1,9 @@
=begin pod
=head1 Humming-Bird::Advice
Simple advice for the Humming-Bird web-framework. Advice are end-of-cycle
middlewares. They take a Response and return a Response.
=head2 Exported advice
=head3 advice-logger
=for code
use Humming-Bird::Core;
use Humming-Bird::Advice;
advice(&advice-logger);
This advice will concisely log all traffic leaving the application.
=end pod

use v6;

use Humming-Bird::Core;
use Humming-Bird::Glue;

unit module Humming-Bird::Advice;

sub advice-logger(Response:D $response --> Response:D) is export {
sub advice-logger(Humming-Bird::Glue::Response:D $response --> Humming-Bird::Glue::Response:D) is export {
my $log = "{ $response.status.Int } { $response.status } | { $response.initiator.path } | ";
$log ~= $response.header('Content-Type') ?? $response.header('Content-Type') !! "no-content";

Expand Down
74 changes: 6 additions & 68 deletions lib/Humming-Bird/Backend/HTTPServer.rakumod
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use Humming-Bird::Glue;

unit class Humming-Bird::Backend::HTTPServer does Humming-Bird::Backend;

my constant $DEFAULT-RN = "\r\n\r\n".encode.Buf;
my constant $RNRN = "\r\n\r\n".encode.Buf;
my constant $RN = "\r\n".encode.Buf;

has Channel:D $.requests .= new;
Expand Down Expand Up @@ -44,7 +44,7 @@ method !respond(&handler) {
react {
whenever $.requests -> $request {
CATCH { default { .say } }
my $hb-request = Humming-Bird::Glue::Request.decode($request<data>.decode);
my $hb-request = Humming-Bird::Glue::Request.decode($request<data>);
my Humming-Bird::Glue::Response $response = &handler($hb-request);
$request<connection><socket>.write: $response.encode;
$request<connection><closed> = True with $hb-request.header('keep-alive');
Expand All @@ -53,56 +53,6 @@ method !respond(&handler) {
}
}

method !handle-request($data is rw, $index is rw, $connection) {
my $request = {
:$connection,
data => Buf.new
};

my @header-lines = Buf.new($data[0..$index]).decode.lines.tail(*-1).grep({ .chars });
return unless @header-lines.elems;

$request<data> ~= $data.subbuf(0, $index);

my $content-length = $data.elems - $index;
for @header-lines -> $header {
my ($key, $value) = $header.split(': ', 2, :skip-empty);
given $key.lc {
when 'content-length' {
$content-length = +$value // ($data.elems - $index);
}
when 'transfer-encoding' {
if $value.chomp.lc.index('chunked') !~~ Nil {
my Int $i;
my Int $b;
while $i < $data.elems {
$i++ while $data[$i] != $RN[0]
&& $data[$i+1] != $RN[1]
&& $i + 1 < $data.elems;

last if $i + 1 >= $data.elems;

$b = :16($data[0..$i].decode);
last if $data.elems < $i + $b;
if $b == 0 {
try $data .= subbuf(3);
last;
}

$i += 2;
$request<data> ~= $data.subbuf($i, $i+$b-3);
try $data .= subbuf($i+$b+2);
$i = 0;
}
}
}
}
}

$request<data> ~= $data.subbuf($index, $content-length+4);
$.requests.send: $request;
}

method listen(&handler) {
react {
self!timeout;
Expand All @@ -117,24 +67,12 @@ method listen(&handler) {
$!lock.protect({ @!connections.push: %connection-map });

whenever $connection.Supply: :bin -> $bytes {
my Buf $data .= new;
my Int:D $idx = 0;
my $req;

CATCH { default { .say } }
$data ~= $bytes;
%connection-map<last-active> = now;
while $idx++ < $data.elems - 4 {
# Read up to headers
$idx--, last if $data[$idx] == $DEFAULT-RN[0]
&& $data[$idx+1] == $DEFAULT-RN[1]
&& $data[$idx+2] == $DEFAULT-RN[2]
&& $data[$idx+3] == $DEFAULT-RN[3];
}

$idx += 4;

self!handle-request($data, $idx, %connection-map);
$.requests.send: {
connection => %connection-map,
data => $bytes;
};
}

CATCH { default { .say; $connection.close; %connection-map<closed> = True } }
Expand Down
30 changes: 14 additions & 16 deletions lib/Humming-Bird/Core.rakumod
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use Humming-Bird::Glue;

unit module Humming-Bird::Core;

our constant $VERSION = '3.0.0';
our constant $VERSION = '3.0.1';

### ROUTING SECTION
my constant $PARAM_IDX = ':';
Expand Down Expand Up @@ -187,26 +187,24 @@ if %loc{$request.method}.static {

# If we don't support the request method on this route.
without %loc{$request.method} {
return METHOD-NOT-ALLOWED($request);
}
return METHOD-NOT-ALLOWED($request);
}

try {
# This is how we pass to error handlers.
CATCH {
when %ERROR{.^name}:exists { return %ERROR{.^name}($_, SERVER-ERROR($request)) }
default {
my $err = $_;
with %*ENV<HUMMING_BIRD_ENV> {
if .lc ~~ 'prod' | 'production' {
return SERVER-ERROR($request);
}
# This is how we pass to error handlers.
CATCH {
when %ERROR{.^name}:exists { return %ERROR{.^name}($_, SERVER-ERROR($request)) }
default {
my $err = $_;
with %*ENV<HUMMING_BIRD_ENV> {
if .lc ~~ 'prod' | 'production' {
return SERVER-ERROR($request);
}
return SERVER-ERROR($request).html("<h1>500 Internal Server Error</h1><br><i> $err <br> { $err.backtrace.nice } </i>");
}
return SERVER-ERROR($request).html("<h1>500 Internal Server Error</h1><br><i> $err <br> { $err.backtrace.nice } </i>");
}

return %loc{$request.method}($request);
}

return %loc{$request.method}($request);
}

sub get(Str:D $path, &callback, @middlewares = List.new) is export {
Expand Down
Loading

0 comments on commit 5a78ca1

Please sign in to comment.