そろそろPerlでのHTTP/2について触れたい

 そろそろPerlでのHTTP/2について触れたい

YAPC::Okinawa 2018 ONNASON
Mar 03, 2018

2d524d56b2ddff49363c995f2d795da0?s=128

Yuji Shimada

March 03, 2018
Tweet

Transcript

  1. 2.
  2. 6.
  3. 8.
  4. 13.
  5. 17.
  6. 30.
  7. 34.

    Web͕஗͍͍͔ͭ͘ͷཧ༝(1) • ωοτϫʔΫͷଳҬ͕ڱ͍ʢ·ͩ·ͩ3Gճઢ΋ଟ͍ʣ • ղܾࡦͷྫ • ૹ৴͢ΔσʔλΛѹॖͯ͠ݮΒ͢ • ΑΓૣ͍ճઢΛීٴͤ͞Δ •

    10GbpsͷݻఆճઢΛීٴͤ͞Δ • 5Gͷొ৔Ͱ௿஗Ԇ͔ͭ20Gbpsඈ͹ͤΔͧ • 120TbpsͷւఈέʔϒϧʹҾ͖௚͢ʢPLCNʣ
  8. 43.
  9. 44.

    HTTPύΠϓϥΠϯ • ႈ౳ͳϦΫΤετͰ͔͠ར༻Ͱ͖ͳ͍ • GET΍HEAD͕࿈ଓ͢ΔϦΫΤετͰ͸ར༻Մೳ • PUT΍DELETEͰ΋ར༻Ͱ͖Δ৔߹͕͋Δ • Ϩεϙϯε͸ϦΫΤετΛૹͬͨॱ൪Ͱड৴͠ͳ͚Ε͹ͳΒͳ͍ •

    Head of Line (HOL) Blocking ͸݁ہൃੜ͢Δ • ຆͲͷϒϥ΢βͰ࣮૷͞Ε͍ͯͳ͍͔σϑΥϧτͰແޮ • ΫϥΠΞϯτଆ͸ߟྀ͢Δ͜ͱ͕ଟ࣮͘૷͕೉͍͠
  10. 57.

    ϑϨʔϜͷϑΥʔϚοτ # 9ΦΫςοτͷϔομʔͱՄม௕ͷϖΠϩʔυ͔Β੒Δ # ͔ͬ͜ͷதͷ਺ࣈ͸1Ϗοτ +-----------------------------------------------+ | Length (24) |

    +---------------+---------------+---------------+ | Type (8) | Flags (8) | +-+-+-----------+---------------+-------------------------------+ |R| Stream Identifier (31) | +=+=============================================================+ | Frame Payload (0...) ... +———————————————————————————————+
  11. 58.

    ϑϨʔϜͷछྨ 5ZQF छྨ ໾ׂ  %"5" ϦΫΤετϨεϙϯεͷϘσΟʔ  )&"%&34 ϦΫΤετϨεϙϯεͷϔομʔ

     13*03*5: ετϦʔϜͷ༏ઌॱҐΛࢦఆʢΫϥΠΞϯτͷΈʣ  345@453&". ΤϥʔͳͲͰετϦʔϜΛऴྃ͢Δͱ͖ʹ࢖༻  4&55*/(4 ઀ଓઃఆΛมߋ͢Δ  164)@130.*4& αʔόʔϓογϡΛ༧ࠂ͢ΔʢαʔόʔͷΈʣ  1*/( ઀ଓͷੜଘ֬ೝ  (0"8": ΤϥʔͳͲͰ઀ଓΛऴྃ͢Δͱ͖ʹ࢖༻  8*/%08@61%"5& ΢Οϯυ΢αΠζΛมߋ͢Δ  $0/5*/6"5*0/ αΠζͷେ͖ͳ)&"%&34164)@130.*4&ͷஅย
  12. 67.

    ॳճϦΫΤετͷྫ # http://www.example.com/ ʹϦΫΤετͨ͠৔߹ > GET / HTTP/1.1 > Host:

    www.example.com > Connection: Upgrade, HTTP2-Settings > Upgrade: h2c > HTTP2-Settings: AAMAAABkAARAAAAAAAIAAAAA
  13. 68.

    ॳճϦΫΤετͷྫ # http://www.example.com/ ʹϦΫΤετͨ͠৔߹ > GET / HTTP/1.1 > Host:

    www.example.com > Connection: Upgrade, HTTP2-Settings > Upgrade: h2c > HTTP2-Settings: AAMAAABkAARAAAAAAAIAAAAA < HTTP/1.1 101 Switching Protocols < Connection: Upgrade < Upgrade: h2c # ͔͜͜ΒHTTP/2઀ଓ < server: perl-Protocol-HTTP2/1.08 < content-length: 13 < cache-control: max-age=3600 < date: Thu, 01 Mar 2018 14:30:15 GMT < last-modified: Thu, 01 Mar 2018 14:30:15 GMT < hello, world!
  14. 70.

    HTTP/2Ͱ઀ଓΛߦ͏ํ๏ (https઀ଓͷ৔߹) • HTTP/2͸TLS1.2Ҏ্͕ඞਢ • application-layer protocol negotiation (ALPN) extension

    ͕ར ༻Ͱ͖Δඞཁ͕͋Δ • ҉߸εΠʔτ͸ TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256Ҏ্͕ඞਢ • TLSϋϯυγΣΠΫ͕ऴΘͬͨޙʹʮHTTP/2ίωΫγϣϯϓϦ ϑΣΠεʯͱ͍͏24ΦΫςοτͷσʔλΛૹ৴͢Δ
  15. 71.

    ίωΫγϣϯϓϦϑΣΠε • “PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n” Λૹ৴ • ͜Ε͸HTTP/1.1ͷαʔόʔͩͱ PRI ͱ͍͏

    METHOD ͱͯ͠ղऍ͞Εͯ౴͑ΒΕͳ͍ͷͰ HTTP/2ʹରԠ͍ͯ͠ͳ͍ͱ൑அͰ͖Δ • ௚ޙʹαʔόʔ͔Β SETTINGS ϑϨʔϜʢޙड़ʣ ͕ฦͬͯ͘Ε͹੒ޭ
  16. 72.
  17. 75.

    ͨͱ͑͹ • ετϦʔϜͷ༏ઌ౓ • ϑϩʔ੍ޚ • HTTP/2࣌୅ͷυϝΠϯγϟʔσΟϯά • Smart Shadingͷݕ౼

    • ίωΫγϣϯ࠶ར༻ͱϫΠϧυΧʔυূ໌ॻ • 421 Misdirected Requestͷར༻΍OriginϑϨʔϜͷݕ౼ • αʔόʔϓογϡͱϦόʔεϓϩΩγʔ • 103 Early Hintsͷར༻
  18. 83.
  19. 89.

    HTTP/2 ClientΛࢼ͢ • ͱΓ͋͑ͣαʔόʔ͸ϩʔΧϧʹ nghttpd ͱ ͍͏΍ͭΛ —no_tls Ͱཱͯ·͢ •

    ΧϨϯτσΟϨΫτϦͷϑΝΠϧΛදࣔͯ͘͠ ΕΔͷͰద౰ʹ index.html ΛͰ্ͬͪ͛·͢ • Protocol::HTTP2::ClientͰΞΫηεͯ͠Έ·͢
  20. 90.

    nghttpdΛىಈ͢Δ $ echo 'Hello, HTTP/2!!' >! index.html # index.html Λ࡞͓ͬͯ͘

    $ nghttpd —no-tls -v 8080 IPv6: listen :::8080 IPv4: listen 0.0.0.0:8080
  21. 91.

    ClientΦϒδΣΫτͷ࡞੒ use Protocol::HTTP2::Client; use Protocol::HTTP2::Constant qw(constant_name); my $client = Protocol::HTTP2::Client->new(

    on_change_state => sub { my ($stream_id, $previous_state, $current_state) = @_; printf( "Stream %i changed state from %s to %s\n", $stream_id, const_name(states => $previous_state), const_name(states => $current_state), ); }, on_error => sub { my $error = shift; printf "Error occurred: %s\n", const_name(errors => $error); }, );
  22. 92.

    ϦΫΤετͷ࡞੒ (͜ͷ࣌఺Ͱ͸·ͩϦΫΤετ͸ඈ͹ͳ͍) my $port = 80; my $host = ‘example.com';

    $client->request( # HTTP/2 headers ':scheme' => 'http', ':authority' => "$host:$port", ':path' => '/', ':method' => 'GET', # HTTP/1.1 headers headers => [ 'accept' => '*/*', 'user-agent' => "perl-Protocol-HTTP2/$Protocol::HTTP2::VERSION", ], on_done => sub { my ($headers, $data) = @_; use Data::Dumper; warn Dumper [ $headers, $data ]; }, );
  23. 93.

    tcpίωΫγϣϯΛுΔ use AnyEvent; use AnyEvent::Socket; use AnyEvent::Handler; my $w =

    AnyEvent->condvar; tcp_connect $host, $port, sub { my ($fh) = @_ or die “connection failed: $!”; my $handle; $handle = AnyEvent::Handle->new( fh => $fh, autocork => 1, on_error => sub { $_[0]->destroy; warn "connection error\n” $w->send; }, on_eof => sub { $handle->destroy; $w->send; }, ); # ࣍ͷεϥΠυʹͭͮ͘ }; $w->recv;
  24. 94.

    ϦΫΤετΛૹड৴͢Δ tcp_connect $host, $port, sub { … # ϦΫΤετΛૹ৴ while

    (my $frame = $client->next_frame) { $handle->push_write($frame); } # ϨεϙϯεΛॲཧ $handle->on_read(sub { my $handle = shift; $client->feed(delete $handle->{rbuf}); while (my $frame = $client->next_frame) { $handle->push_write($frame); } $handle->push_shutdown if $client->shutdown; }); }; $w->recv;
  25. 96.

    ࣮ߦͯ͠Έ·͢ $ perl no_tls_client.pl Stream 1 changed state from IDLE

    to HALF_CLOSED Stream 1 changed state from HALF_CLOSED to CLOSED [ [ ":status", "200", "server", "nghttpd nghttp2/1.30.0", "cache-control", "max-age=3600", "date", "Fri, 02 Mar 2018 11:03:24 GMT", "content-length", "16", "last-modified", "Fri, 02 Mar 2018 11:02:50 GMT" ], "Hello, HTTP/2!!\n" ]
  26. 100.

    αʔόʔͷ࣮૷ my $w = AnyEvent->condvar; tcp_server '127.0.0.1', 8080, sub {

    my ($fh, $peer_host, $peer_port) = @_; my $handle; $handle = AnyEvent::Handle->new( fh => $fh, autocork => 1, on_error => sub { my ($handle, $fatal, $message) = @_; $handle->destroy; say "connection error (fatal: $fatal, message: $message)"; }, on_eof => sub { $handle->destroy; }, ); # ࣍ͷεϥΠυ΁ }; $w->recv;
  27. 101.

    αʔόʔͷ࣮૷ tcp_server '127.0.0.1', 8080, sub { … my $server; $server

    = Protocol::HTTP2::Server->new( on_request => sub { my ($stream_id, $headers, $data) = @_; my $message = "hello, world!"; $server->response( ':status' => 200, stream_id => $stream_id, headers => [ 'server' => 'perl-Protocol-HTTP2/1.08', 'content-length' => length($message), ], data => $message, ); }, ); # ࣍ͷεϥΠυʹଓ͘ }; $w->recv;
  28. 102.

    αʔόʔͷ࣮૷ tcp_server '127.0.0.1', 8080, sub { … while (my $frame

    = $server->next_frame) { $handle->push_write($frame); } $handle->on_read(sub { my $handle = shift; $server->feed($handle->{rbuf}); $handle->{rbuf} = undef; while (my $frame = $server->next_frame) { $handle->push_write($frame); } $handle->push_shutdown if $server->shutdown; }); }; $w->recv;
  29. 103.

    αʔόʔΛىಈͯ͠ ϦΫΤετૹͬͯΈΔ $ perl no_tls_server.pl $ perl no_tls_client.pl Stream 1

    changed state from IDLE to HALF_CLOSED Stream 1 changed state from HALF_CLOSED to CLOSED [ [ ":status", "200", "server", "perl-Protocol-HTTP2/1.08" ], "hello, world!" ]
  30. 105.
  31. 108.

    ฒྻϦΫΤετΛࢼ͢ ʢΫϥΠΞϯτଆൈਮʣ $client->request( # ετϦʔϜ1 # HTTP/2 headers ':scheme' =>

    'http', ':authority' => "$host:$port", ':path' => “/dummy.iso", # ڊେͳϑΝΠϧͷऔಘ ':method' => 'GET', … )->request( # ετϦʔϜ3 # HTTP/2 headers ':scheme' => 'http', ':authority' => "$host:$port", ':path' => "/", ':method' => 'GET', … );
  32. 109.

    ฒྻϦΫΤετΛࢼ͢ ʢαʔόʔଆൈਮʣ on_request => sub { my ($stream_id, $headers, $data)

    = @_; my $header_map = { @$headers }; if ($header_map->{':path'} eq '/') { # path ͰॲཧΛৼΓ෼͚Δ # લͱҰॹ } elsif ($header_map->{':path'} eq '/dummy.iso') { aio_load './dummy.iso', sub { # ಡΈࠐΈͰϒϩοΫ͠ͳ͍Α͏ʹ aio_load Λ࢖༻ my ($data) = @_; $server->response( ':status' => 200, stream_id => $stream_id, headers => [ 'server' => 'perl-Protocol-HTTP2/1.08', 'content-length' => length($data), ], data => $data, ); }; } },
  33. 110.

    ࣮ࡍʹࢼͯ͠ΈΔ $ perl no_tls_server_multistream.pl $ perl no_tls_client_multistream.pl Stream 1 changed

    state from IDLE to HALF_CLOSED Stream 3 changed state from IDLE to HALF_CLOSED Stream 3 changed state from HALF_CLOSED to CLOSED [ [ ":status", "200", "server", "perl-Protocol-HTTP2/1.08", "content-length", "13" ] ] Stream 1 changed state from HALF_CLOSED to CLOSED [ [ ":status", "200", "server", "perl-Protocol-HTTP2/1.08", "content-length", "102400" ] ]
  34. 113.
  35. 117.

    TLSʹରԠ ʢαʔόʔฤʣ Net::SSLeay::initialize(); tcp_server ‘127.0.0.1’, 443, sub { my ($fh,

    $peer_host, $peer_port) = @_; my $tls; eval { $tls = AnyEvent::TLS->new( method => 'TLSv1_2', cert_file => ‘/etc/letsencrypt/live/http2-test.api.moe/fullchain.pem’, key_file => ‘/etc/letsencrypt/live/http2-test.api.moe/privkey.pem’, ); # ͜͜Ͱ҉߸εΠʔτΛબ୒͢Δ # ࣍ͷεϥΠυ΁ }; if ($@) { print "Some problem with SSL CTX: $@\n"; $w->send; return; } …
  36. 118.

    TLSʹରԠ ʢαʔόʔฤʣ … eval { … # ECDH curve (

    Net-SSLeay >= 1.56, openssl >= 1.0.0 ) if (exists &Net::SSLeay::CTX_set_tmp_ecdh) { my $curve = Net::SSLeay::OBJ_txt2nid('prime256v1'); my $ecdh = Net::SSLeay::EC_KEY_new_by_curve_name($curve); Net::SSLeay::CTX_set_tmp_ecdh( $tls->ctx, $ecdh ); Net::SSLeay::EC_KEY_free($ecdh); } # ALPN (Net-SSLeay > 1.55, openssl >= 1.0.2) if (exists &Net::SSLeay::CTX_set_alpn_select_cb) { Net::SSLeay::CTX_set_alpn_select_cb( $tls->ctx, [Protocol::HTTP2::ident_tls] ); } # NPN (Net-SSLeay > 1.45, openssl >= 1.0.1) elsif (exists &Net::SSLeay::CTX_set_next_protos_advertised_cb) { Net::SSLeay::CTX_set_next_protos_advertised_cb( $tls->ctx, [Protocol::HTTP2::ident_tls] ); } }; # ࣍ͷεϥΠυ΁
  37. 119.

    TLSʹରԠ ʢαʔόʔฤʣ my $handle; $handle = AnyEvent::Handle->new( fh => $fh,

    tls => 'accept', tls_ctx => $tls, autocork => 1, on_error => sub { my ($handle, $fatal, $message) = @_; $handle->destroy; warn "connection error (fatal: $fatal, message: $message)"; }, on_eof => sub { $handle->destroy; }, );
  38. 122.

    TLSʹରԠ͔ͨ֬͠ೝ $ curl -v —http2 https://http2-test.api.moe/ தུ… * Using HTTP2,

    server supports multi-use * Connection state changed (HTTP/2 confirmed) * Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0 * Using Stream ID: 1 (easy handle 0x7ff59c00cc00) > GET / HTTP/2 > Host: http2-test.api.moe > User-Agent: curl/7.54.0 > Accept: */* > * Connection state changed (MAX_CONCURRENT_STREAMS updated)! < HTTP/2 200 < server: perl-Protocol-HTTP2/1.08 < content-length: 21 < cache-control: max-age=3600 < date: Fri, 02 Mar 2018 14:58:59 GMT < last-modified: Fri, 02 Mar 2018 14:58:59 GMT < * Connection #0 to host http2-test.api.moe left intact Hello, HTTP/2 World!!
  39. 125.

    TLSʹରԠ ʢΫϥΠΞϯτฤʣ Net::SSLeay::initialize(); tcp_connect $host, $port, sub { my ($fh)

    = @_ or die "connection failed: $!"; my $tls; eval { $tls = AnyEvent::TLS->new(method => ‘TLSv1_2’); # ALPN (Net-SSLeay > 1.55, openssl >= 1.0.2) if (exists &Net::SSLeay::CTX_set_alpn_protos) { Net::SSLeay::CTX_set_alpn_protos( $tls->ctx, [Protocol::HTTP2::ident_tls] ); } # NPN (Net-SSLeay > 1.45, openssl >= 1.0.1) elsif (exists &Net::SSLeay::CTX_set_next_proto_select_cb) { Net::SSLeay::CTX_set_next_proto_select_cb( $tls->ctx, [Protocol::HTTP2::ident_tls] ); } else { die "ALPN and NPN is not supported\n"; } }; # ࣍ͷεϥΠυ΁
  40. 126.

    TLSʹରԠ ʢΫϥΠΞϯτฤʣ … my $handle; $handle = AnyEvent::Handle->new( fh =>

    $fh, tls => "connect", tls_ctx => $tls, autocork => 1, on_error => sub { $_[0]->destroy; print "connection error\n"; $w->send; }, on_eof => sub { $handle->destroy; $w->send; } ); …
  41. 129.

    TLSʹରԠ͔ͨ֬͠ೝ $ perl tls_client.pl [/Users/shimada.yuji/misc/yapc-okinawa] Stream 1 changed state from

    IDLE to HALF_CLOSED Stream 1 changed state from HALF_CLOSED to CLOSED [ [ ":status", "200", "server", "perl-Protocol-HTTP2/1.08", "content-length", "21", "cache-control", "max-age=3600", "date", "Fri, 02 Mar 2018 15:24:03 GMT", "last-modified", "Fri, 02 Mar 2018 15:24:03 GMT" ], "Hello, HTTP/2 World!!" ]
  42. 133.
  43. 137.

    ͜Μͳײ͡Ͱ࢖͑Δ(1) my $apns = Net::APNs::HTTP2->new( is_development => 1, auth_key =>

    'auth_key.p8', key_id => $key_id, team_id => $team_id, bundle_id => $bundle_id, );
  44. 138.

    ͜Μͳײ͡Ͱ࢖͑Δ(2) while (1) { # ͜Ε͸ͳʹ͔͠Βͷαʔόʔͱ͔ϫʔΧʔͰӬଓతʹಈ࡞͍ͯ͠Δͱࢥ͍ͬͯͩ͘͞ $apns->prepare($device_token, { aps =>

    { alert => 'some message', badge => 1, }, }, sub { my ($header, $content) = @_; # $header = [ # ":status" => "200", # "apns-id" => "82B34E17-370A-DBF4-5046-FF56A4EA1FAF", # ]; ... }); # You can chainged $apns->prepare(...)->prepare(...)->prepare(...); # send all prepared requests in parallel $apns->send; # do something } # must call `close` when finished $apns->close;
  45. 140.
  46. 142.

    αʔόʔϓογϡΛ࣮૷͢Δ • “/push.html”ʹϦΫΤετΛߦ͏ͱ”/style.css”͕αʔ όʔϓογϡ͞ΕΔΑ͏ʹ͢Δ • มߋ͢Δ৔ॴ͸ on_request() ͷதͷΈ • push()

    ͸ response() ΑΓ΋ઌʹهड़͠ͳ͚Ε͹ͳΒͳ͍ • ChromeͰΞΫηε͠style.cssͷinitiatorʹ”Push”ͱग़ͯ ͍ͨΒ੒ޭ
  47. 143.

    αʔόʔϓογϡͷ࣮૷ if ($header_map->{':path'} eq '/push.html') { $server->push( ':authority' => $host

    . ':' . $port, ':method' => 'GET', ':path' => '/style.css', ':scheme' => 'https', stream_id => $stream_id, ); aio_load './push.html', sub { my ($data) = @_; $server->response( ':status' => 200, stream_id => $stream_id, headers => [ 'server' => 'perl-Protocol-HTTP2/1.08', 'content-length' => length($data), 'content-type' => 'text/html', ], data => $data, ); }; } elsif ($header_map->{':path'} eq '/style.css') { # ࣍ͷεϥΠυ }
  48. 144.

    αʔόʔϓογϡͷ࣮૷ elsif ($header_map->{':path'} eq '/style.css') { aio_load './style.css', sub {

    my ($data) = @_; $server->response( ':status' => 200, stream_id => $stream_id, headers => [ 'server' => 'perl-Protocol-HTTP2/1.08', 'content-length' => length($data), 'content-type' => 'text/css', ], data => $data, ); }; } …
  49. 148.

    αʔόʔϓογϡͷड৴ my $client = Protocol::HTTP2::Client->new( on_push => sub { my

    ($push_headers) = @_; print "Server want to push some resource to us\n”; return sub { my ($headers, $data) = @_; print "Received promised resource\n"; }; }, on_change_state => sub { }, on_error => sub { }, );
  50. 150.

    ࣮ࡍʹࢼͯ͠ΈΔ $ perl tls_client_server_push.pl Stream 1 changed state from IDLE

    to HALF_CLOSED Stream 2 changed state from IDLE to RESERVED Server want to push some resource to us Stream 1 changed state from HALF_CLOSED to CLOSED [ [ தུ ], "<html>\n <head>\n <title>HTTP/2 Server Push Test</title>\n <link rel=\"stylesheet\" href=\"/style.css\">\n </head>\n <body>\n <h1>Server <span class=\"push\">Push</span> Test</h1>\n </body\n</html>\n" ] Stream 2 changed state from RESERVED to HALF_CLOSED Stream 2 changed state from HALF_CLOSED to CLOSED Received promised resource $VAR1 = [ [ ':status', '200', 'server', 'perl-Protocol-HTTP2/1.08', 'content-length', '26', 'content-type', 'text/css' ], '.push { color: red; } ' ];
  51. 152.
  52. 158.

    ϦΫΤετͯ͠ΈΔ ʢࠓճ͸ —no_tls Ͱʣ $ shuvgey —listen 8080 —no_tls app.psgi

    $ perl no_tls_client.pl Stream 1 changed state from IDLE to HALF_CLOSED Stream 1 changed state from HALF_CLOSED to CLOSED [ [ ":status", "200", "content-type", "text/plain", "content-lenth", "13", "server", "Shuvgey/0.09" ], "hello, world!" ]
  53. 162.
  54. 164.
  55. 166.
  56. 168.
  57. 169.

    ࣮૷ࣗମ͸͋Δ • Grpc::XS • ࠷৽ͷϓϩτίϧόοϑΝʔͰϏϧυͰ͖ͳ͍ • ϏϧυͰ͖Δ؀ڥͱόʔδϣϯ͕ෆ໌ • Google::Protocol::Buffer::Dynamic •

    ࠷৽ͷϓϩτίϧόοϑΝʔͰϏϧυͰ͖ͳ͍ • ϏϧυͰ͖Δ؀ڥͱόʔδϣϯ͕ෆ໌ • protoxs-perl • ߋ৽͕10೥લ͔Β͋Δྺ࢙͋ΔϞδϡʔϧ • proto2·Ͱ͔͠ରԠ͍ͯ͠ͳ͍
  58. 173.
  59. 174.