Skip to content

Commit

Permalink
[HotReload] fin.
Browse files Browse the repository at this point in the history
  • Loading branch information
rawleyfowler committed Mar 3, 2024
1 parent d13810d commit 3e1e4e4
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 45 deletions.
3 changes: 2 additions & 1 deletion META6.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
"JSON::Fast:auth<cpan:TIMOTIMO>",
"URI::Encode:auth<zef:raku-community-modules>",
"UUID::V4:auth<zef:masukomi>",
"Terminal::ANSIColor:auth<zef:lizmat>"
"Terminal::ANSIColor:auth<zef:lizmat>",
"File::Find:auth<zef:raku-community-modules>"
],
"description": "A simple and composable web applications framework.",
"license": "MIT",
Expand Down
53 changes: 24 additions & 29 deletions lib/Humming-Bird/Backend/HTTPServer.rakumod
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,9 @@ my constant $RN = "\r\n".encode.Buf;
has Channel:D $.requests .= new;
has Lock $!lock .= new;
has @!connections;
has $!conn-channel;

method close {
$_.close for @!connections;
$!conn-channel.close;
}

method !timeout {
Expand Down Expand Up @@ -72,39 +70,36 @@ method !respond(&handler) {
}

method listen(&handler) {
react {
self!timeout;
self!respond(&handler);
$!conn-channel = IO::Socket::Async.listen($.addr // '0.0.0.0', $.port).Channel;
whenever $!conn-channel -> $connection {
my %connection-map := {
socket => $connection,
last-active => now
}
self!timeout;
self!respond(&handler);
react whenever IO::Socket::Async.listen($.addr // '0.0.0.0', $.port) -> $connection {
my %connection-map := {
socket => $connection,
last-active => now
}

whenever $connection.Supply: :bin -> $bytes {
CATCH { default { .say } }
%connection-map<last-active> = now;
react whenever $connection.Supply: :bin -> $bytes {
CATCH { default { .say } }
%connection-map<last-active> = now;

my $header-request = False;
if %connection-map<request>:!exists {
%connection-map<request> = Humming-Bird::Glue::Request.decode($bytes);
$header-request = True;
}

my $hb-request = %connection-map<request>;
if !$header-request {
$hb-request.body.append: $bytes;
}
my $header-request = False;
if %connection-map<request>:!exists {
%connection-map<request> = Humming-Bird::Glue::Request.decode($bytes);
$header-request = True;
}

my $content-length = $hb-request.header('Content-Length');
if (!$content-length.defined || ($hb-request.body.bytes == $content-length)) {
$.requests.send: %connection-map;
}
my $hb-request = %connection-map<request>;
if !$header-request {
$hb-request.body.append: $bytes;
}

CATCH { default { .say; $connection.close; %connection-map<closed> = True } }
my $content-length = $hb-request.header('Content-Length');
if (!$content-length.defined || ($hb-request.body.bytes == $content-length)) {
$.requests.send: %connection-map;
}
}

CATCH { default { .say; $connection.close; %connection-map<closed> = True } }
}
}

Expand Down
2 changes: 1 addition & 1 deletion lib/Humming-Bird/Core.rakumod
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,7 @@ sub listen(Int:D $port, Str:D $addr = '0.0.0.0', :$no-block, :$timeout = 3, :$ba
colored('Warning', 'yellow'),
': Humming-Bird is currently running in DEV mode, please set HUMMING_BIRD_ENV to PROD or PRODUCTION to enable PRODUCTION mode.',
"\n"
) without ($*ENV<HUMMING_BIRD_ENV>);
) if (%*ENV<HUMMING_BIRD_ENV>:exists && (%*ENV<HUMMING_BIRD_ENV> ne 'PROD' || %*ENV<HUMMING_BIRD_ENV> ne 'PRODUCTION'));
if $no-block {
start {
$server.listen(&handle);
Expand Down
72 changes: 58 additions & 14 deletions lib/Humming-Bird/Plugin/HotReload.rakumod
Original file line number Diff line number Diff line change
Expand Up @@ -2,38 +2,82 @@ use MONKEY-TYPING;
use Humming-Bird::Plugin;
use Humming-Bird::Core;
use Humming-Bird::Backend;
use File::Find;

unit class Humming-Bird::Plugin::HotReload does Humming-Bird::Plugin;

my $temp-file = $*CWD ~ '/.humming-bird.hotreload';

my sub find-dirs(IO::Path:D $dir) {
slip $dir.IO, slip find :$dir, :type<dir>
}

# Credits to: https://github.com/raku-community-modules/IO-Notification-Recursive
sub watch-recursive(IO(Cool) $start, Bool :$update) is export {
supply {
my sub watch-it(IO::Path:D $io) {
whenever $io.watch -> $e {
if $update {
if $e.event ~~ FileRenamed && $e.path.d {
watch-it($_) for find-dirs $e.path;
}
}
emit $e;
}
}
watch-it($_) for find-dirs $start;
}
}

class Humming-Bird::Backend::HotReload does Humming-Bird::Backend {
has $.backend handles <port addr timeout>;
has Channel:D $!reload-chan .= new;
has $!should-refresh = False;
has $!proc;

method listen(&handler) {
self!observe();
self!start-server();

react whenever $!reload-chan -> $reload {
if ($reload === True) {
$.backend.close;
self!start-server();
say "\n" ~ 'Humming-Bird HotReload PID: ' ~ (await $!proc.pid) ~ "\n";

react {
whenever signal(SIGINT) { $temp-file.IO.unlink; exit; }
whenever Supply.interval(1, 2) {
if ($!should-refresh) {
say 'File change detected, refreshing Humming-Bird...';
self!kill-server();
self!start-server();
$!should-refresh = False;
}
}
}
}

method !kill-server {
$!proc.kill(9);
}

method !start-server {
start {
listen(self.port, self.addr);
}
# Devious, evil, dangerous, hack for HotReload.... :)
my $contents = $*PROGRAM-NAME.IO.slurp;
$contents = $contents.subst(/plugin\s\'?\"?HotReload\'?\"?';'?/, '', :g);

try shell 'reset';

$temp-file.IO.spurt: $contents;
$!proc = Proc::Async.new('raku', $temp-file);
$!proc.bind-stdout($*OUT);
$!proc.bind-stderr($*ERR);
$!proc.start;
}

method !observe {
react whenever IO::Notification.watch-path('.') {
say "$^file changed, reloading Humming-Bird...";
$!reload-chan.send: True;
}
my $observer = watch-recursive('.');
$observer.tap({
Lock.new.protect({ $!should-refresh = True; }) unless $^file.path.ends-with($temp-file);
});
}
}

method register($server is rw, %routes, @middleware, @advice, **@args) {
$server := Humming-Bird::Backend::HotReload.new(backend => $server, timeout => 3);
$server = Humming-Bird::Backend::HotReload.new(timeout => 1);
}

0 comments on commit 3e1e4e4

Please sign in to comment.