Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Perl 6 Standard Gateway Interface - PPW 2015

Perl 6 Standard Gateway Interface - PPW 2015

Talking about my work on P6SGI and the current state of the specification.

Sterling Hanenkamp

October 10, 2015
Tweet

More Decks by Sterling Hanenkamp

Other Decks in Programming

Transcript

  1. Yes, we're hiring... a lot. We're also helping hire lots

    of people at other places... Thanks for the plane ticket! 2
  2. hello.p6w use v6; sub app(%env) { start { 200, [

    Content-Type => 'text/plain' ], [ 'Hello World' ] } } 5
  3. Small Print All code samples in this presentation are non-normative

    and may not reflect the reality of the current specification or any other revision to the specification. They are for demonstration purposes only and they may not even compile. 6
  4. Perl 6: Making the World a Better Place Since Christmas

    CONCURRENCY Composable Concurrency Objects Promise Supply Automatic Type Coercion 7
  5. Goals Standardize the app/server API No 6PAN requirements for apps

    Simplicity Flexibility to optimize Flexibility to extend 8
  6. Implied Objectives Common tasks should be easy Unusual/unanticipated tasks should

    be possible and even portable Low-level API within a high-level language Making the policy choices required to solve the problem and no others. 9
  7. Environment %env passed to the application as an Associative Contains

    all the CGI-ish keys as well as custom p6sgi.* ones. Pretty similar to PSGI. 11
  8. A Response, I Promise use v6; sub app(%env) { start

    { 200, [ Content-Type => 'text/plain' ], [ 'Hello World' ] } } 12
  9. A Response-ish, I Promise use v6; sub app(%env) { on({

    .emit([ 200, [ Content-Type => 'text/plain' ], [ 'Hello World' ] ]); }).Promise; } 13
  10. Status/Headers Int (or coerces to an Int) List of Pairs

    (or something listy that contains something pairy) 14
  11. Message Payload Supply .emit('blah blah blah HTML blah'); .done; .quit(X::Adhoc.new(:message('bad

    stuff'));
 OR within on, die "bad stuff"; Or anything else that can .Supply! 15
  12. Message Payload use v6; sub app(%env) { start { 200,

    [ Content-Type => 'text/plain' ], [ 'Hello World' ] } } 16
  13. Message Payload use v6; sub app(%env) { start { 200,

    [ Content-Type => 'text/plain' ], Supply.from-list(['Hello World' ]) } } 17
  14. Message Payload use v6; sub app(%env) { start { 200,

    [ Content-Type => 'text/plain' ], ['Hello World' ].Supply } } 18
  15. Stringify-Encode Blobs are passed through as-is Arrays are treated as

    headers, encoded using ISO-8859-1 Anything else: stringify and encode using p6sgi.encode OR via detected Content-Type charset 19
  16. PSGI Delayed Response my $app = sub { my $env

    = shift; # Delays response until it fetches content from the network return sub { my $responder = shift; fetch_content_from_server(sub { my $content = shift; $responder->([ 200, $headers, [ $content ] ]); }); }; }; 22
  17. P6SGI Delayed Response my &app = sub (%env) { #

    Delays response until it fetches content from the network start { my $content = fetch-content-from-server(); 200, $headers, [ $content ] } } 23
  18. PSGI Streaming my $app = sub { my $env =

    shift; # immediately starts the response and stream the content return sub { my $responder = shift; my $writer = $responder->( [ 200, [ 'Content-Type', 'application/json' ]]); wait_for_events(sub { my $new_event = shift; if ($new_event) { $writer->write($new_event->as_json . "\n"); } else { $writer->close; } }); }; }; 24
  19. P6SGI Streaming my &app = sub (%env) { # immediately

    starts the response and stream the content start { my $events = wait-for-events(); 200, [ 'Content-Type' => 'application/json' ], $events.map({ .as-json ~ "\n" }); } } 25
  20. PSGI Middleware # $app is a simple PSGI application my

    $app = sub { my $env = shift; return [ '200', [ 'Content-Type' => 'text/plain' ], [ "Hello World" ] ]; }; # $xheader is a piece of middleware that wraps $app my $xheader = sub { my $env = shift; my $res = $app->($env); push @{$res->[1]}, 'X-PSGI-Used' => 1; return $res; }; 26
  21. P6GSI Middleware # &app is a simple PSGI application my

    &app = sub (%env) { 200, [ Content-Type => 'text/plain' ], [ "Hello World" ] } # &xheader is a piece of middleware that wraps some &app my &xheader = sub (%env) { nextsame.then(-> $r { my ($s, @h, $p) = $r.result; @h.push: 'P6SGI-Used' => 1; $s, @h, $p }); } &app.wrap(&xheader); 27
  22. Middleware: Payload sub debug-line-lengths(%env) { nextsame.then({ my ($s, $h, Supply()

    $p) = .result; $s, $h, $p.lines.map({ .chars ~ "\n" }) }); } 28
  23. Input: Supplies! use v6; sub app(%env) { start { my

    $form-str = ''; %env<p6sgi.input>.tap: -> $v { $form-str ~= $v }; %env<p6sgi.input>.wait; # Yeah, this is naïve. Get over it. my %params = |$form-str.split('&')».split('='); 200, [ Content-Type => 'text/html' ], do-search(%params<q>); } } 29
  24. Request Life Cycle Server MUST start the app ASAP The

    Server MUST begin sending the response from the App ASAP It is generally assumed that the application and server must run concurrently. (This is Perl 6, not some neolithic language without a decent threading model, after all.) Request Srv Application Response 30
  25. Request Life Cycle my $expect-continue = %env<SERVER_PROTOCOL> ~~ 'HTTP/1.1'|'HTTP/2' &&

    %env<HTTP_EXPECT> eq '100-continue'; if $expect-continue { 100, [ ], on -> $out { my ($s, @h, Supply() $b) = process-input(); @h.push: ':status' => $s; $out.emit(@h); $b.do: -> $v { $out.emit($v) {; }; } 31
  26. Raw HTTP/2 # Look, ma, no p6sgix.io! sub app(%env) {

    start { if %env<HTTP_CONNECTION> ~~ /\<Upgrade\>/ && %env<HTTP_UPGRADE> eq 'h2c' { 101, [ Connection => 'Upgrade', Upgrade => 'h2c' ], on -> $out { # beginning reading HTTP/2 frames and output HTTP/2 frames # using p6sgix.input and p6sgix.output }; } else { ... } } } 32
  27. HTTP/2 Extension if %env<SERVER_PROTOCOL> eq 'HTTP/1.1' && %env<HTTP_CONNECTION> ~~ /\<Upgrade\>/

    && %env<HTTP_UPGRADE> eq 'h2c' && any(|%env<p6sgix.protocol.upgrade>) ~~ 'h2c' { 101, [ P6SGIx-Upgrade => 'h2c' ], [] } if %env<PATH_INFO> eq '/index.html' { if %env<SERVER_PROTOCOL> eq 'HTTP/2' { %env<p6sgix.h2c.push-promise>.emit([ ':method' => 'GET', ':scheme' => 'http', ':path' => 'foo.png', ]); } 200, [ 'Content-Type' => 'text/html' ], ... } elsif %env<PATH_INFO> eq '/foo.png' { 200, [ 'Content-Type' => 'image/png' ], ... } 33
  28. WebSocket Extension if %env<SERVER_PROTOCOL> eq 'HTTP/1.1' && %env<HTTP_CONNECTION> ~~ /\<Upgrade\>/

    && %env<HTTP_UPGRADE> eq 'websocket' && any(|%env<p6sgix.protocol.upgrade>) ~~ 'websocket' { 101, [ P6SGIx-Upgrade => 'websocket' ], [] } # echo service elsif %env<SERVER_PROTOCOL> eq 'websocket' { on -> $out { %env<p6sgi.input> => -> $v { $out.emit($v); }; }; } 34
  29. Protocol Handling The overall specification assumes HTTP/1.x So far, provides

    special case handling for others: HTTP/2 (adds p6sgix.h2c.push-promise) WebSocket 35
  30. Custom Protocol Implementation New protocols can be implemented at any

    level They can be implemented according to specified extension They can be implemented some other way 36
  31. Ready Check sub app(%env) { start { 200, [ Content-Type

    => 'text/plain' ], on { await %env<p6sgi.ready>; for 0 .. * -> $n { .emit($n ~ "\n") } }; } } 37
  32. Header/Body Done Extensions sub app(%env) { start { 200, [

    Content-Type => 'text/plain' ], on { await %env<p6sgix.header.done>; .emit("HEADER WRITTEN OUT YAY!"); .done; await %env<p6sgix.body.done>; %env<p6sgi.errors>.emit("BODY WRITTEN OUT YAY!"); # TODO Lame. Come up with something better. } } } 38
  33. Output Block Detection Extension sub app(%env) { start { my

    $fh = "really-big-file.txt".IO.open(:r); 200, [ Content-Type => 'text/plain' ], on { %env<p6sgix.body.backpressure.supply> => -> $yes { sleep 10 if $yes; }, $fh => -> $chars { # throws an exception when this promise is broken %env<p6sgix.body.done>.result if %env<p6sgix.body.done>.status ~~ Broken; .emit($chars); } } } } 39