Slide 1

Slide 1 text

Hack HTTP Request and Response Interfaces Yuuki Takezawa PHPerKaigi 2019

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

PHP-FIG?

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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.

Slide 10

Slide 10 text

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


Slide 11

Slide 11 text

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


Slide 12

Slide 12 text

re"/(.?)/"

Slide 13

Slide 13 text

No content

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

don't worry!

Slide 16

Slide 16 text

Package Manager • We are currently working to move 
 to a package manager that fully supports 
 multiple languages.

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

For Hack • hhvm/hack-http-request-response-interfaces • hack-interface-standards
 container / log

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

HackͰ͸Ͳ͏ͳͷ͔

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

no mixed example MessageInterface

Slide 25

Slide 25 text

public function withAddedHeader($name, $value);

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

StreamInterface

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

Dispose?

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

Facebook\Experimental\Http\Message\RequestInterface

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

Facebook\Experimental\Http\Message\ResponseInterface

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

To ReadHandle / WriteHandle • MessageInterfaceͷgetBodyɺwithStreamͰ
 StreamʹΞΫηεͯ͠ॻ͖ࠐΈɾಡΈࠐΈ • ReadɺWriteʹผΕͨͨΊɺ
 ͜ΕΒΛར༻͢Δ͜ͱ͕Ͱ͖ͳ͘ͳͬͯ͠·ͬͨ

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

No content

Slide 48

Slide 48 text

No content

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

No content

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

WriteHandle • WriteHandle͸ඇಉظʹ͢Δ͜ͱ͕ॏཁ
 (asyncΛ࢖͏) • we really don't want a standard that encourages hiding HH\Asio\join

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

No content

Slide 66

Slide 66 text

No content

Slide 67

Slide 67 text

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

Slide 68

Slide 68 text

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

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

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

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

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

Slide 76

Slide 76 text

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

Slide 77

Slide 77 text

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

Slide 78

Slide 78 text

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

Slide 79

Slide 79 text

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

Slide 80

Slide 80 text

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

Slide 81

Slide 81 text

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

Slide 82

Slide 82 text

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

Slide 83

Slide 83 text

No content

Slide 84

Slide 84 text

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

Slide 85

Slide 85 text

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