Lock in $30 Savings on PRO—Offer Ends Soon! ⏳

Hack HTTP Request and Response Interfaces

Hack HTTP Request and Response Interfaces

For PHPerKaigi2019

yuuki takezawa

March 29, 2019
Tweet

More Decks by yuuki takezawa

Other Decks in Programming

Transcript

  1. 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
  2. PSR-7 was designed for PHP, not Hack, and some descisions

    do not fit smoothly with Hack's type system.
  3. 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.
  4. Ending PHP Support, and The Future Of Hack • PHP

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

  5. 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.

  6. Package Manager • We are currently working to move 


    to a package manager that fully supports 
 multiple languages.
  7. enum HTTPMethod: string { PUT = 'PUT'; GET = 'GET';

    POST = 'POST'; HEAD = 'HEAD'; PATCH = 'PATCH'; TRACE = 'TRACE'; DELETE = 'DELETE'; OPTIONS = 'OPTIONS'; CONNECT = 'CONNECT'; }
  8. * @param string $name Case-insensitive header field name to add.

    * @param string|string[] $value Header value(s). * @return static
  9. /** * 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);
  10. /** * 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>>;
  11. <<__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')); }
  12. 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)); } }
  13. /** 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(); }
  14. 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>; }
  15. <<__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(); }
  16. <<__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(); }
  17. <<__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(); }
  18. /** * 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;
  19. /** * 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;
  20. 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
  21. 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
  22. 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); }
  23. 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); }
  24. type HandlerFunc func(ResponseWriter, *Request) // ServeHTTP calls f(w, r). func

    (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { f(w, r) }
  25. 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)); } }
  26. 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()); }
  27. 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()); }
  28. 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()); }
  29. 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()); }
  30. // 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"); }, );
  31. // 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"); }, );
  32. // 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"); }, );
  33. // 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"); }, );
  34. // 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"); }, );
  35. // 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"); }, );
  36. // 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"); }, );
  37. // 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"); }, );
  38. // 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"); }, );
  39. // 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"); }, );
  40. // 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"); }, );