$30 off During Our Annual Pro Sale. View Details »

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. Hack HTTP Request and
    Response Interfaces
    Yuuki Takezawa
    PHPerKaigi 2019

    View Slide

  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

    View Slide

  3. View Slide

  4. Defines common cross-framework
    interfaces to represent HTTP
    requests and responses

    View Slide

  5. PHP-FIG?

    View Slide

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

    View Slide

  7. PSR-7 was designed for PHP, not
    Hack, and some descisions do not
    fit smoothly with Hack's type
    system.

    View Slide

  8. HackͰPHP
    • PHPͷΠϯλʔϑΣʔεΛHackͰར༻͢Δʹ͸ɺ

    HackʹܕΛೝࣝͤ͞ΔͨΊʹผ్هड़͢Δඞཁ͕͋Γ
    • hhiϑΝΠϧΛ༻ҙ͠ɺHackͰܕΛೝࣝͰ͖ΔΑ͏ʹ
    • hack-psr/psr7-http-message-hhi

    View Slide

  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.

    View Slide

  10. Ending PHP Support, and The Future Of Hack
    • PHP Is Unsupported(·ͩͪΐͬͱಈ͘)
    • গͣͭ͠PHPͷػೳഉআ


    View Slide

  11. Ending PHP Support, and The Future Of Hack
    • .hack ֦ுࢠͷ௥Ճ

    • extract()ͳͲͷ࡟আ
    • remove behavior that exists in PHP Arrays but not
    Hack Arrays or Hack Collections.


    View Slide

  12. re"/(.?)/"

    View Slide

  13. View Slide

  14. HHVM 4.0 has dropped support
    for Composer, please use PHP
    instead. Aborting.

    View Slide

  15. don't worry!

    View Slide

  16. Package Manager
    • We are currently working to move 

    to a package manager that fully supports 

    multiple languages.

    View Slide

  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

    View Slide

  18. For Hack
    • hhvm/hack-http-request-response-interfaces
    • hack-interface-standards

    container / log

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  22. HackͰ͸Ͳ͏ͳͷ͔

    View Slide

  23. enum HTTPMethod: string {
    PUT = 'PUT';
    GET = 'GET';
    POST = 'POST';
    HEAD = 'HEAD';
    PATCH = 'PATCH';
    TRACE = 'TRACE';
    DELETE = 'DELETE';
    OPTIONS = 'OPTIONS';
    CONNECT = 'CONNECT';
    }

    View Slide

  24. no mixed
    example MessageInterface

    View Slide

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

    View Slide

  26. * @param string $name Case-insensitive header field name to add.
    * @param string|string[] $value Header value(s).
    * @return static

    View Slide

  27. public function withAddedHeader(
    string $name,
    vec $values
    ): this;

    View Slide

  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);

    View Slide

  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>;

    View Slide

  30. dict[
    'content-type' => vec[
    'application/json'
    ],
    ]

    View Slide

  31. StreamInterface

    View Slide

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

    View Slide

  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'));
    }

    View Slide

  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));
    }
    }

    View Slide

  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();
    }

    View Slide

  36. Dispose?

    View Slide

  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;
    }

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  41. Facebook\Experimental\Http\Message\RequestInterface

    View Slide

  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;

    View Slide

  43. Facebook\Experimental\Http\Message\ResponseInterface

    View Slide

  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;

    View Slide

  45. To ReadHandle / WriteHandle
    • MessageInterfaceͷgetBodyɺwithStreamͰ

    StreamʹΞΫηεͯ͠ॻ͖ࠐΈɾಡΈࠐΈ
    • ReadɺWriteʹผΕͨͨΊɺ

    ͜ΕΒΛར༻͢Δ͜ͱ͕Ͱ͖ͳ͘ͳͬͯ͠·ͬͨ

    View Slide

  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

    View Slide

  47. View Slide

  48. View Slide

  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

    View Slide

  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);
    }

    View Slide

  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);
    }

    View Slide

  52. PSR-7ϥΠΫʹͰ͖ͳ͍ɾɾʁ

    View Slide

  53. View Slide

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

    View Slide

  55. <<__Sealed(
    FileHandle::class,
    PipeHandle::class,
    StdioHandle::class
    )>>
    abstract class NativeHandle
    implements IO\ReadHandle, IO\WriteHandle {
    //
    }

    View Slide

  56. ಠ࣮ࣗ૷͢Δ৔߹͸
    UserspaceHandleΠϯλʔϑΣʔεΛ

    View Slide

  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));
    }
    }

    View Slide

  58. list($read, $write) = IO\pipe_non_disposable();

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  63. WriteHandle
    • WriteHandle͸ඇಉظʹ͢Δ͜ͱ͕ॏཁ

    (asyncΛ࢖͏)
    • we really don't want a standard that encourages
    hiding HH\Asio\join

    View Slide

  64. Hackͷasync/awaitʹ͍ͭͯ
    ͓ܰ͘͞Β͍

    View Slide

  65. View Slide

  66. View Slide

  67. Asynchronous Operations
    namespace Hack\UserDocumentation\AsyncOps\Basics\Examples\AsyncCurl;
    use namespace HH\Lib\Vec;
    async function curl_A(): Awaitable {
    $x = await \HH\Asio\curl_exec("http://example.com/");
    return $x;
    }
    async function curl_B(): Awaitable {
    $y = await \HH\Asio\curl_exec("http://example.net/");
    return $y;
    }
    async function async_curl(): Awaitable {
    $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());
    }

    View Slide

  68. Asynchronous Operations
    namespace Hack\UserDocumentation\AsyncOps\Basics\Examples\AsyncCurl;
    use namespace HH\Lib\Vec;
    async function curl_A(): Awaitable {
    $x = await \HH\Asio\curl_exec("http://example.com/");
    return $x;
    }
    async function curl_B(): Awaitable {
    $y = await \HH\Asio\curl_exec("http://example.net/");
    return $y;
    }
    async function async_curl(): Awaitable {
    $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());
    }

    View Slide

  69. Asynchronous Operations
    namespace Hack\UserDocumentation\AsyncOps\Basics\Examples\AsyncCurl;
    use namespace HH\Lib\Vec;
    async function curl_A(): Awaitable {
    $x = await \HH\Asio\curl_exec("http://example.com/");
    return $x;
    }
    async function curl_B(): Awaitable {
    $y = await \HH\Asio\curl_exec("http://example.net/");
    return $y;
    }
    async function async_curl(): Awaitable {
    $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());
    }

    View Slide

  70. Asynchronous Operations
    namespace Hack\UserDocumentation\AsyncOps\Basics\Examples\AsyncCurl;
    use namespace HH\Lib\Vec;
    async function curl_A(): Awaitable {
    $x = await \HH\Asio\curl_exec("http://example.com/");
    return $x;
    }
    async function curl_B(): Awaitable {
    $y = await \HH\Asio\curl_exec("http://example.net/");
    return $y;
    }
    async function async_curl(): Awaitable {
    $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());
    }

    View Slide

  71. Asynchronous Operations
    • ϝΠϯεϨουͰ࣮ߦ
    • ڠௐతϚϧνλεΫ
    • https://docs.hhvm.com/hack/asynchronous-
    operations/some-basics

    View Slide

  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");
    },
    );

    View Slide

  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");
    },
    );

    View Slide

  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");
    },
    );

    View Slide

  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");
    },
    );

    View Slide

  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");
    },
    );

    View Slide

  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");
    },
    );

    View Slide

  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");
    },
    );

    View Slide

  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");
    },
    );

    View Slide

  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");
    },
    );

    View Slide

  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");
    },
    );

    View Slide

  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");
    },
    );

    View Slide

  83. View Slide

  84. ·ͱΊ
    • PSR-7ͷhhi͔Β࢝·ͬͨ
    • HackͷܕΛ׆༻͠࠷దԽ͞ΕͨInterface
    • ύΠϓϥΠϯͱॲཧʹ͍ͭͯߟ͑ͯΈΑ͏

    View Slide

  85. if you would like to chat about
    hacklang,
    you are welcome to join us on
    slack

    View Slide