Hack HTTP Request and Response Interfaces

Hack HTTP Request and Response Interfaces

For PHPerKaigi2019

17d4ef53b432ebf7c566fd6a11345570?s=128

yuuki takezawa

March 29, 2019
Tweet

Transcript

  1. Hack HTTP Request and Response Interfaces Yuuki Takezawa PHPerKaigi 2019

  2. Profile • ஛ᖒ ༗و / ytake • גࣜձࣾΞΠελΠϧ CTO •

    PHP, Hack, Go, Scala • Apache Hadoop, Apache Spark, Apache Kafka
 • twitter https://twitter.com/ex_takezawa • facebook https://www.facebook.com/yuuki.takezawa • github https://github.com/ytake
  3. None
  4. Defines common cross-framework interfaces to represent HTTP requests and responses

  5. PHP-FIG?

  6. PHP-FIG? • PHP Framework Interop Group • ͓ͳ͡Έ PSR-x •

    PHP Standards Recommendation
  7. PSR-7 was designed for PHP, not Hack, and some descisions

    do not fit smoothly with Hack's type system.
  8. HackͰPHP • PHPͷΠϯλʔϑΣʔεΛHackͰར༻͢Δʹ͸ɺ
 HackʹܕΛೝࣝͤ͞ΔͨΊʹผ్هड़͢Δඞཁ͕͋Γ • hhiϑΝΠϧΛ༻ҙ͠ɺHackͰܕΛೝࣝͰ͖ΔΑ͏ʹ • hack-psr/psr7-http-message-hhi

  9. Additionally, with the planned end of PHP support in HHVM,

    it will stop being possible to use the canonical definitions or common implementations of PSR-7 in Hack code.
  10. Ending PHP Support, and The Future Of Hack • PHP

    Is Unsupported(·ͩͪΐͬͱಈ͘) • গͣͭ͠PHPͷػೳഉআ

  11. Ending PHP Support, and The Future Of Hack • .hack

    ֦ுࢠͷ௥Ճ
 <?hh // strict Λهड़ͤͣͱ΋strictʹ (ਪ঑) • extract()ͳͲͷ࡟আ • remove behavior that exists in PHP Arrays but not Hack Arrays or Hack Collections.

  12. re"/(.?)/"

  13. None
  14. HHVM 4.0 has dropped support for Composer, please use PHP

    instead. Aborting.
  15. don't worry!

  16. Package Manager • We are currently working to move 


    to a package manager that fully supports 
 multiple languages.
  17. Package Manager • Hack͸YarnΛϕʔεʹͨ͠Package Manager΁Ҡߦத
 (Β͍͠)
 - https://github.com/composer/composer/issues/7734
 - https://github.com/yarnpkg/yarn/issues/7001


    - https://github.com/esy/esy/issues/850
  18. For Hack • hhvm/hack-http-request-response-interfaces • hack-interface-standards
 container / log

  19. Difference between PSR-7 And hack-http-request-response- interfaces

  20. ग़య: http://weierophinney.github.io/2015-10-20-PSR-7-and-Middleware

  21. ग़య: http://weierophinney.github.io/2015-10-20-PSR-7-and-Middleware

  22. HackͰ͸Ͳ͏ͳͷ͔

  23. enum HTTPMethod: string { PUT = 'PUT'; GET = 'GET';

    POST = 'POST'; HEAD = 'HEAD'; PATCH = 'PATCH'; TRACE = 'TRACE'; DELETE = 'DELETE'; OPTIONS = 'OPTIONS'; CONNECT = 'CONNECT'; }
  24. no mixed example MessageInterface

  25. public function withAddedHeader($name, $value);

  26. * @param string $name Case-insensitive header field name to add.

    * @param string|string[] $value Header value(s). * @return static
  27. public function withAddedHeader( string $name, vec<string> $values ): this;

  28. /** * Retrieves a message header value by the given

    case-insensitive name. * * This method returns an array of all the header values of the given * case-insensitive header name. * * If the header does not appear in the message, this method MUST return an * empty array. * * @param string $name Case-insensitive header field name. * @return string[] An array of string values as provided for the given * header. If the header does not appear in the message, this method MUST * return an empty array. */ public function getHeader($name);
  29. /** * Retrieves all message header values. * * The

    keys represent the header name as it will be sent over the wire, and * each value is a vec of strings associated with the header. * * While header names are not case-sensitive, getHeaders() will preserve the * exact case in which headers were originally specified. * * Implementations *MUST* return header names of the form `Foo-Bar`, but keys * should be considered case-insensitive. * * Implementations *MAY* choose to normalize some headers in different ways, * for example, `ETag` instead of `Etag`. */ public function getHeaders(): dict<string, vec<string>>;
  30. dict[ 'content-type' => vec[ 'application/json' ], ]

  31. StreamInterface

  32. To HH\Lib\Experimental\IO\ReadHandle HH\Lib\Experimental\IO\WriteHandle

  33. <<__Memoize>> public static function requestInput(): IO\ReadHandle { /* HH_IGNORE_ERROR[2049] __PHPStdLib

    */ /* HH_IGNORE_ERROR[4107] __PHPStdLib */ return new self(\fopen('php://input', 'r')); } <<__Memoize>> public static function requestOutput(): IO\WriteHandle{ /* HH_IGNORE_ERROR[2049] __PHPStdLib */ /* HH_IGNORE_ERROR[4107] __PHPStdLib */ return new self(\fopen('php://output', 'w')); }
  34. final class PipeHandle extends NativeHandle { public static function createPair():

    (this, this) { /* HH_IGNORE_ERROR[2049] intentionally not in HHI */ /* HH_IGNORE_ERROR[4107] intentionally not in HHI */ list($r, $w) = Native\pipe() as (resource, resource); return tuple(new self($r), new self($w)); } }
  35. /** Create a pair of handles, where writes to the

    `WriteHandle` can be * read from the `ReadHandle`. */ function pipe_non_disposable(): (ReadHandle, WriteHandle) { return _Private\PipeHandle::createPair(); }
  36. Dispose?

  37. interface IDisposable { /** * This method is invoked exactly

    once at the end of the scope of the * using statement, unless the program terminates with a fatal error. */ public function __dispose(): void; } interface IAsyncDisposable { /** * This method is invoked exactly once at the end of the scope of the * await using statement, unless the program terminates with a fatal error. */ public function __disposeAsync(): Awaitable<void>; }
  38. <<__ReturnDisposable>> protected function reader(): Filesystem\DisposableFileReadHandle { return Filesystem\open_read_only($this->filename); } public

    async function saveAsync( Serializer\SerializeInterface $serializer ): Awaitable<void> { if($this->exists()) { return; } await using $handle = $this->writer(); await $handle->writeAsync($serializer->serialize()); await $handle->closeAsync(); }
  39. <<__ReturnDisposable>> protected function reader(): Filesystem\DisposableFileReadHandle { return Filesystem\open_read_only($this->filename); } public

    async function saveAsync( Serializer\SerializeInterface $serializer ): Awaitable<void> { if($this->exists()) { return; } await using $handle = $this->writer(); await $handle->writeAsync($serializer->serialize()); await $handle->closeAsync(); }
  40. <<__ReturnDisposable>> protected function reader(): Filesystem\DisposableFileReadHandle { return Filesystem\open_read_only($this->filename); } public

    async function saveAsync( Serializer\SerializeInterface $serializer ): Awaitable<void> { if($this->exists()) { return; } await using $handle = $this->writer(); await $handle->writeAsync($serializer->serialize()); await $handle->closeAsync(); }
  41. Facebook\Experimental\Http\Message\RequestInterface

  42. /** * Gets the body of the message. */ public

    function getBody(): IO\ReadHandle; /** * Return an instance with the specified message body. * * This method MUST be implemented in such a way as to retain the * immutability of the message, and MUST return a new instance that has the * new body stream. */ public function withBody(IO\ReadHandle $body): this;
  43. Facebook\Experimental\Http\Message\ResponseInterface

  44. /** * Gets the body of the message. */ public

    function getBody(): IO\WriteHandle; /** * Return an instance with the specified message body. * * This method MUST be implemented in such a way as to retain the * immutability of the message, and MUST return a new instance that has the * new body stream. */ public function withBody(IO\WriteHandle $body): this;
  45. To ReadHandle / WriteHandle • MessageInterfaceͷgetBodyɺwithStreamͰ
 StreamʹΞΫηεͯ͠ॻ͖ࠐΈɾಡΈࠐΈ • ReadɺWriteʹผΕͨͨΊɺ
 ͜ΕΒΛར༻͢Δ͜ͱ͕Ͱ͖ͳ͘ͳͬͯ͠·ͬͨ

  46. To ReadHandle / WriteHandle • we need separte {Server,Client}{Request,Response} interfaces

    too in a server context, request body is read, response body is write
 but it's the opposite for clients
  47. None
  48. None
  49. To ReadHandle / WriteHandle • server context the response is

    both read and write
 the controller writes the body
 the emitter reads the body to send it to the client
  50. public function process( ServerRequestInterface $request, RequestHandlerInterface $handler ): ResponseInterface {

    if ($this->checkRequest($request)) { try { $request = $request->withParsedBody( $this->parse($request->getBody()) ); } catch (Exception $exception) { throw HttpErrorException::create(400, [], $exception); } } return $handler->handle($request); }
  51. public function process( ServerRequestInterface $request, RequestHandlerInterface $handler ): ResponseInterface {

    if ($this->checkRequest($request)) { try { $request = $request->withParsedBody( $this->parse($request->getBody()) ); } catch (Exception $exception) { throw HttpErrorException::create(400, [], $exception); } } return $handler->handle($request); }
  52. PSR-7ϥΠΫʹͰ͖ͳ͍ɾɾʁ

  53. None
  54. type HandlerFunc func(ResponseWriter, *Request) // ServeHTTP calls f(w, r). func

    (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { f(w, r) }
  55. <<__Sealed( FileHandle::class, PipeHandle::class, StdioHandle::class )>> abstract class NativeHandle implements IO\ReadHandle,

    IO\WriteHandle { // }
  56. ಠ࣮ࣗ૷͢Δ৔߹͸ UserspaceHandleΠϯλʔϑΣʔεΛ

  57. final class PipeHandle extends NativeHandle { public static function createPair():

    (this, this) { /* HH_IGNORE_ERROR[2049] intentionally not in HHI */ /* HH_IGNORE_ERROR[4107] intentionally not in HHI */ list($r, $w) = Native\pipe() as (resource, resource); return tuple(new self($r), new self($w)); } }
  58. list($read, $write) = IO\pipe_non_disposable();

  59. list($read, $write) = IO\pipe_non_disposable(); $decode = \json_encode(dict['body' => 'message']); await

    $write->writeAsync($decode); await $read->readAsync();
  60. list($read, $write) = IO\pipe_non_disposable(); $decode = \json_encode(dict['body' => 'message']); await

    $write->writeAsync($decode); await $read->readAsync();
  61. list($read, $write) = IO\pipe_non_disposable(); $decode = \json_encode(dict['body' => 'message']); await

    $write->writeAsync($decode); await $read->readAsync();
  62. list($read, $write) = IO\pipe_non_disposable(); $decode = \json_encode(dict['body' => 'message']); await

    $write->writeAsync($decode); await $read->readAsync();
  63. WriteHandle • WriteHandle͸ඇಉظʹ͢Δ͜ͱ͕ॏཁ
 (asyncΛ࢖͏) • we really don't want a

    standard that encourages hiding HH\Asio\join
  64. Hackͷasync/awaitʹ͍ͭͯ ͓ܰ͘͞Β͍

  65. None
  66. None
  67. Asynchronous Operations namespace Hack\UserDocumentation\AsyncOps\Basics\Examples\AsyncCurl; use namespace HH\Lib\Vec; async function curl_A():

    Awaitable<string> { $x = await \HH\Asio\curl_exec("http://example.com/"); return $x; } async function curl_B(): Awaitable<string> { $y = await \HH\Asio\curl_exec("http://example.net/"); return $y; } async function async_curl(): Awaitable<void> { $start = \microtime(true); list($a, $b) = await Vec\from_async(vec[curl_A(), curl_B()]); $end = \microtime(true); echo "Total time taken: " . \strval($end - $start) . " seconds\n"; } <<__EntryPoint>> function main():void { \HH\Asio\join(async_curl()); }
  68. Asynchronous Operations namespace Hack\UserDocumentation\AsyncOps\Basics\Examples\AsyncCurl; use namespace HH\Lib\Vec; async function curl_A():

    Awaitable<string> { $x = await \HH\Asio\curl_exec("http://example.com/"); return $x; } async function curl_B(): Awaitable<string> { $y = await \HH\Asio\curl_exec("http://example.net/"); return $y; } async function async_curl(): Awaitable<void> { $start = \microtime(true); list($a, $b) = await Vec\from_async(vec[curl_A(), curl_B()]); $end = \microtime(true); echo "Total time taken: " . \strval($end - $start) . " seconds\n"; } <<__EntryPoint>> function main():void { \HH\Asio\join(async_curl()); }
  69. Asynchronous Operations namespace Hack\UserDocumentation\AsyncOps\Basics\Examples\AsyncCurl; use namespace HH\Lib\Vec; async function curl_A():

    Awaitable<string> { $x = await \HH\Asio\curl_exec("http://example.com/"); return $x; } async function curl_B(): Awaitable<string> { $y = await \HH\Asio\curl_exec("http://example.net/"); return $y; } async function async_curl(): Awaitable<void> { $start = \microtime(true); list($a, $b) = await Vec\from_async(vec[curl_A(), curl_B()]); $end = \microtime(true); echo "Total time taken: " . \strval($end - $start) . " seconds\n"; } <<__EntryPoint>> function main():void { \HH\Asio\join(async_curl()); }
  70. Asynchronous Operations namespace Hack\UserDocumentation\AsyncOps\Basics\Examples\AsyncCurl; use namespace HH\Lib\Vec; async function curl_A():

    Awaitable<string> { $x = await \HH\Asio\curl_exec("http://example.com/"); return $x; } async function curl_B(): Awaitable<string> { $y = await \HH\Asio\curl_exec("http://example.net/"); return $y; } async function async_curl(): Awaitable<void> { $start = \microtime(true); list($a, $b) = await Vec\from_async(vec[curl_A(), curl_B()]); $end = \microtime(true); echo "Total time taken: " . \strval($end - $start) . " seconds\n"; } <<__EntryPoint>> function main():void { \HH\Asio\join(async_curl()); }
  71. Asynchronous Operations • ϝΠϯεϨουͰ࣮ߦ • ڠௐతϚϧνλεΫ • https://docs.hhvm.com/hack/asynchronous- operations/some-basics

  72. // Emulate a client-server environment list($cr, $sw) = IO\pipe_non_disposable(); list($sr,

    $cw) = IO\pipe_non_disposable(); await Tuple\from_async( async { // client await $cw->writeAsync("Herp\n"); $response = await $cr->readLineAsync(); expect($response)->toBeSame("Derp\n"); await $cw->writeAsync("Foo\n"); $response = await $cr->readLineAsync(); expect($response)->toBeSame("Bar\n"); }, async { // server $request = await $sr->readLineAsync(); expect($request)->toBeSame("Herp\n"); await $sw->writeAsync("Derp\n"); $request = await $sr->readLineAsync(); expect($request)->toBeSame("Foo\n"); await $sw->writeAsync("Bar\n"); }, );
  73. // Emulate a client-server environment list($cr, $sw) = IO\pipe_non_disposable(); list($sr,

    $cw) = IO\pipe_non_disposable(); await Tuple\from_async( async { // client await $cw->writeAsync("Herp\n"); $response = await $cr->readLineAsync(); expect($response)->toBeSame("Derp\n"); await $cw->writeAsync("Foo\n"); $response = await $cr->readLineAsync(); expect($response)->toBeSame("Bar\n"); }, async { // server $request = await $sr->readLineAsync(); expect($request)->toBeSame("Herp\n"); await $sw->writeAsync("Derp\n"); $request = await $sr->readLineAsync(); expect($request)->toBeSame("Foo\n"); await $sw->writeAsync("Bar\n"); }, );
  74. // Emulate a client-server environment list($cr, $sw) = IO\pipe_non_disposable(); list($sr,

    $cw) = IO\pipe_non_disposable(); await Tuple\from_async( async { // client await $cw->writeAsync("Herp\n"); $response = await $cr->readLineAsync(); expect($response)->toBeSame("Derp\n"); await $cw->writeAsync("Foo\n"); $response = await $cr->readLineAsync(); expect($response)->toBeSame("Bar\n"); }, async { // server $request = await $sr->readLineAsync(); expect($request)->toBeSame("Herp\n"); await $sw->writeAsync("Derp\n"); $request = await $sr->readLineAsync(); expect($request)->toBeSame("Foo\n"); await $sw->writeAsync("Bar\n"); }, );
  75. // Emulate a client-server environment list($cr, $sw) = IO\pipe_non_disposable(); list($sr,

    $cw) = IO\pipe_non_disposable(); await Tuple\from_async( async { // client await $cw->writeAsync("Herp\n"); $response = await $cr->readLineAsync(); expect($response)->toBeSame("Derp\n"); await $cw->writeAsync("Foo\n"); $response = await $cr->readLineAsync(); expect($response)->toBeSame("Bar\n"); }, async { // server $request = await $sr->readLineAsync(); expect($request)->toBeSame("Herp\n"); await $sw->writeAsync("Derp\n"); $request = await $sr->readLineAsync(); expect($request)->toBeSame("Foo\n"); await $sw->writeAsync("Bar\n"); }, );
  76. // Emulate a client-server environment list($cr, $sw) = IO\pipe_non_disposable(); list($sr,

    $cw) = IO\pipe_non_disposable(); await Tuple\from_async( async { // client await $cw->writeAsync("Herp\n"); $response = await $cr->readLineAsync(); expect($response)->toBeSame("Derp\n"); await $cw->writeAsync("Foo\n"); $response = await $cr->readLineAsync(); expect($response)->toBeSame("Bar\n"); }, async { // server $request = await $sr->readLineAsync(); expect($request)->toBeSame("Herp\n"); await $sw->writeAsync("Derp\n"); $request = await $sr->readLineAsync(); expect($request)->toBeSame("Foo\n"); await $sw->writeAsync("Bar\n"); }, );
  77. // Emulate a client-server environment list($cr, $sw) = IO\pipe_non_disposable(); list($sr,

    $cw) = IO\pipe_non_disposable(); await Tuple\from_async( async { // client await $cw->writeAsync("Herp\n"); $response = await $cr->readLineAsync(); expect($response)->toBeSame("Derp\n"); await $cw->writeAsync("Foo\n"); $response = await $cr->readLineAsync(); expect($response)->toBeSame("Bar\n"); }, async { // server $request = await $sr->readLineAsync(); expect($request)->toBeSame("Herp\n"); await $sw->writeAsync("Derp\n"); $request = await $sr->readLineAsync(); expect($request)->toBeSame("Foo\n"); await $sw->writeAsync("Bar\n"); }, );
  78. // Emulate a client-server environment list($cr, $sw) = IO\pipe_non_disposable(); list($sr,

    $cw) = IO\pipe_non_disposable(); await Tuple\from_async( async { // client await $cw->writeAsync("Herp\n"); $response = await $cr->readLineAsync(); expect($response)->toBeSame("Derp\n"); await $cw->writeAsync("Foo\n"); $response = await $cr->readLineAsync(); expect($response)->toBeSame("Bar\n"); }, async { // server $request = await $sr->readLineAsync(); expect($request)->toBeSame("Herp\n"); await $sw->writeAsync("Derp\n"); $request = await $sr->readLineAsync(); expect($request)->toBeSame("Foo\n"); await $sw->writeAsync("Bar\n"); }, );
  79. // Emulate a client-server environment list($cr, $sw) = IO\pipe_non_disposable(); list($sr,

    $cw) = IO\pipe_non_disposable(); await Tuple\from_async( async { // client await $cw->writeAsync("Herp\n"); $response = await $cr->readLineAsync(); expect($response)->toBeSame("Derp\n"); await $cw->writeAsync("Foo\n"); $response = await $cr->readLineAsync(); expect($response)->toBeSame("Bar\n"); }, async { // server $request = await $sr->readLineAsync(); expect($request)->toBeSame("Herp\n"); await $sw->writeAsync("Derp\n"); $request = await $sr->readLineAsync(); expect($request)->toBeSame("Foo\n"); await $sw->writeAsync("Bar\n"); }, );
  80. // Emulate a client-server environment list($cr, $sw) = IO\pipe_non_disposable(); list($sr,

    $cw) = IO\pipe_non_disposable(); await Tuple\from_async( async { // client await $cw->writeAsync("Herp\n"); $response = await $cr->readLineAsync(); expect($response)->toBeSame("Derp\n"); await $cw->writeAsync("Foo\n"); $response = await $cr->readLineAsync(); expect($response)->toBeSame("Bar\n"); }, async { // server $request = await $sr->readLineAsync(); expect($request)->toBeSame("Herp\n"); await $sw->writeAsync("Derp\n"); $request = await $sr->readLineAsync(); expect($request)->toBeSame("Foo\n"); await $sw->writeAsync("Bar\n"); }, );
  81. // Emulate a client-server environment list($cr, $sw) = IO\pipe_non_disposable(); list($sr,

    $cw) = IO\pipe_non_disposable(); await Tuple\from_async( async { // client await $cw->writeAsync("Herp\n"); $response = await $cr->readLineAsync(); expect($response)->toBeSame("Derp\n"); await $cw->writeAsync("Foo\n"); $response = await $cr->readLineAsync(); expect($response)->toBeSame("Bar\n"); }, async { // server $request = await $sr->readLineAsync(); expect($request)->toBeSame("Herp\n"); await $sw->writeAsync("Derp\n"); $request = await $sr->readLineAsync(); expect($request)->toBeSame("Foo\n"); await $sw->writeAsync("Bar\n"); }, );
  82. // Emulate a client-server environment list($cr, $sw) = IO\pipe_non_disposable(); list($sr,

    $cw) = IO\pipe_non_disposable(); await Tuple\from_async( async { // client await $cw->writeAsync("Herp\n"); $response = await $cr->readLineAsync(); expect($response)->toBeSame("Derp\n"); await $cw->writeAsync("Foo\n"); $response = await $cr->readLineAsync(); expect($response)->toBeSame("Bar\n"); }, async { // server $request = await $sr->readLineAsync(); expect($request)->toBeSame("Herp\n"); await $sw->writeAsync("Derp\n"); $request = await $sr->readLineAsync(); expect($request)->toBeSame("Foo\n"); await $sw->writeAsync("Bar\n"); }, );
  83. None
  84. ·ͱΊ • PSR-7ͷhhi͔Β࢝·ͬͨ • HackͷܕΛ׆༻͠࠷దԽ͞ΕͨInterface • ύΠϓϥΠϯͱॲཧʹ͍ͭͯߟ͑ͯΈΑ͏

  85. if you would like to chat about hacklang, you are

    welcome to join us on slack