Slide 1

Slide 1 text

Build your own web server with

Slide 2

Slide 2 text

@igorwesome

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

no js

Slide 5

Slide 5 text

HTTP: The Protocol

Slide 6

Slide 6 text

Verbs •GET •POST

Slide 7

Slide 7 text

Verbs •GET •POST •PUT •DELETE

Slide 8

Slide 8 text

Verbs •GET •POST •PUT •DELETE •HEAD •OPTIONS •TRACE •CONNECT

Slide 9

Slide 9 text

• 1xx - Special protocol stuff • 2xx - All good • 3xx - Redirection • 4xx - You are stupid • 5xx - Oh crap! I’m a fucking moron. Status codes

Slide 10

Slide 10 text

• RFC 2324: HTCPCP • 418 - I’m a Teapot Status codes

Slide 11

Slide 11 text

Status codes • 7xx - Developer Error • 710 - PHP • 712 - NoSQL • 720 - Unpossible • 724 - This line should be unreachable • 745 - I don’t always test my code, but when I do I do it in production • 759 - Unexpexted T_PAAMAYIM_NEKUDOTAYIM

Slide 12

Slide 12 text

Redirection • 301 - Moved Permanently • 302 - Found (misused) • 303 - See Other • 305 - Use Proxy • 307 - Temporary Redirect • Location header

Slide 13

Slide 13 text

Content Negotiation Accept: text/html Can haz html?

Slide 14

Slide 14 text

Content Negotiation Accept: text/html Can haz html? Content-Type: application/json Server says no kthxbai

Slide 15

Slide 15 text

Caching • Cache-Control: public/private, max-age • ETag / If-Match • Last-Modified / If-Modified-Since • 403 - Not Modified

Slide 16

Slide 16 text

Authentication

Slide 17

Slide 17 text

Basic Auth • 401 - Unauthorized • 403 - Forbidden • WWW-Authenticate: Basic realm=”foo” • Authorization: base64(username:password)

Slide 18

Slide 18 text

Digest Auth • WWW-Authenticate: • Digest realm=”foo” • qop=”auth” • nonce=”123” • opaque=”456” • Authorization: • ha1 = md5( username:realm:pass ) • ha2 = md5( method:digestURI ) • value = md5(ha1:nonce:ha2)

Slide 19

Slide 19 text

Keep-Alive

Slide 20

Slide 20 text

Streaming • Transfer-Encoding: chunked • dechex(strlen($chunk)) \r\n $chunk \r\n • 0 \r\n \r\n

Slide 21

Slide 21 text

Privacy GET /index.html HTTP/1.1. Host: 127.0.0.1. Connection: keep-alive. User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_4) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/ 21.0.1180.82 Safari/537.1. Accept: text/html,application/xhtml+xml,application/ xml;q=0.9,*/*;q=0.8. Referer: http://127.0.0.1/foo.html. Accept-Encoding: gzip,deflate,sdch. Accept-Language: en-US,en;q=0.8. Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3. .

Slide 22

Slide 22 text

Trailers

Slide 23

Slide 23 text

HTTP/1.1 RFC 2616 read it, you know you want to!

Slide 24

Slide 24 text

The Socket API

Slide 25

Slide 25 text

________ < socket > -------- \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || ||

Slide 26

Slide 26 text

Salami Pizza +---+--------------+----------------+ | 7 | Application | HTTP, FTP, DNS | | 6 | Presentation | TLS | | 5 | Session | TCP | | 4 | Transport | TCP, UDP | | 3 | Network | IP | | 2 | Data Link | MAC, PPP | | 1 | Physical | | +---+--------------+----------------+

Slide 27

Slide 27 text

#include #include #include socket() bind() listen() accept()

Slide 28

Slide 28 text

Binding to ports Binding to IPs

Slide 29

Slide 29 text

PHP Socket API $address = '0.0.0.0'; $port = 5000; $sock = socket_create(AF_INET, SOCK_STREAM, 0); socket_bind($sock, $address, $port); socket_listen($sock); while (true) { $conn = socket_accept($sock); socket_write($conn, date(DATE_RFC822)."\n"); socket_close($conn); }

Slide 30

Slide 30 text

Stream Sockets

Slide 31

Slide 31 text

$sock = stream_socket_server('tcp://0.0.0.0:5000'); while (true) { $conn = stream_socket_accept($sock, -1); fwrite($conn, date(DATE_RFC822)."\n"); fclose($conn); }

Slide 32

Slide 32 text

$contents = file_get_contents('https://igor.io');

Slide 33

Slide 33 text

$context = stream_context_create([ 'http' => [ 'method' => 'POST', 'header' => "Accept: application/json\r\n", 'content' => '{"secret":"data"}', ] ]); $contents = file_get_contents('https://igor.io', false, $context); var_dump($http_response_header);

Slide 34

Slide 34 text

$sock = stream_socket_server('tcp://0.0.0.0:5000'); while (true) { $response = implode("\r\n", [ 'HTTP/1.1 200 OK', 'Content-Length: 2', '', 'Hi', ]); $conn = stream_socket_accept($sock, -1); fwrite($conn, $response); }

Slide 35

Slide 35 text

$s=stream_socket_server('tcp://[::1]:81'); while($c=stream_socket_accept($s,-1)) fwrite($c,"HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nHi");

Slide 36

Slide 36 text

Parsing

Slide 37

Slide 37 text

GET / HTTP/1.1 <--- request line Host: igor.io <--- header <--- line feed <--- body (blank)

Slide 38

Slide 38 text

$request = implode("\r\n", [ 'GET / HTTP/1.1', 'Host: igor.io', '', '', ]);

Slide 39

Slide 39 text

$request = implode("\r\n", [ 'GET / HTTP/1.1', 'Host: igor.io', '', '', ]); $expected = [ 'method' => 'GET', 'path' => '/', 'protocol' => 'HTTP/1.1', 'headers' => ['Host' => 'igor.io'], 'body' => '', ];

Slide 40

Slide 40 text

$request = implode("\r\n", [ 'GET / HTTP/1.1', 'Host: igor.io', '', '', ]); $expected = [ 'method' => 'GET', 'path' => '/', 'protocol' => 'HTTP/1.1', 'headers' => ['Host' => 'igor.io'], 'body' => '', ]; assert($expected === parseRequest($request));

Slide 41

Slide 41 text

TDD because edorian said so!

Slide 42

Slide 42 text

function parseRequest($request) { $lines = explode("\r\n", $request); $requestLine = array_shift($lines); list($method, $path, $protocol) = explode(' ', $requestLine); $headers = []; while ($header = array_shift($lines)) { list($name, $value) = explode(':', $header, 2); $headers[trim($name)] = trim($value); } $body = array_shift($lines); return compact('method', 'path', 'protocol', 'headers', 'body'); }

Slide 43

Slide 43 text

HTTP/1.1 200 OK <--- status line Content-Length: 2 <--- headers <--- line feed Hi <--- body

Slide 44

Slide 44 text

$responseSpec = [200, ['Content-Length' => '2'], 'Hi']; $response = implode("\r\n", [ 'HTTP/1.1 200 OK', 'Content-Length: 2', '', 'Hi', ]); assert($response === buildResponse($responseSpec));

Slide 45

Slide 45 text

$responseSpec = [200, ['Content-Length' => '2'], 'Hi']; $response = implode("\r\n", [ 'HTTP/1.1 200 OK', 'Content-Length: 2', '', 'Hi', ]); assert($response === buildResponse($responseSpec));

Slide 46

Slide 46 text

function buildResponse(array $responseSpec) { list($status, $headers, $body) = $responseSpec; return buildResponseStatusLine($status). buildResponseHeaders($headers). "\r\n". $body; }

Slide 47

Slide 47 text

function buildResponseStatusLine($status) { $statusMap = [ 200 => 'OK', 404 => 'Not Found', ]; return "HTTP/1.1 $status {$statusMap[$status]}\r\n"; }

Slide 48

Slide 48 text

function buildResponseHeaders(array $headers) { $flatHeaders = []; foreach ($headers as $name => $value) { $flatHeaders[] = "$name: $value"; } return implode("\r\n", $flatHeaders)."\r\n"; }

Slide 49

Slide 49 text

$conn = stream_socket_accept($sock, -1); $request = fread($conn, 512); $parsedRequest = parseRequest($request); switch ($parsedRequest['path']) { case '/': $responseSpec = [200, ['Content-Length' => 3], "Hi\n"]; break; default: $responseSpec = [404, ['Content-Length' => 10], "Not found\n"]; } fwrite($conn, buildResponse($responseSpec));

Slide 50

Slide 50 text

Problem? • Missing error handling • Assumption: client closes connection • Assumption: request can be read in one go • Assumption: request no larger than 512 bytes • Assumption: request/response body fits in memory • Blocking I/O

Slide 51

Slide 51 text

Asynchronous I/O

Slide 52

Slide 52 text

$readable = $read ?: null; $writable = $write ?: null; $except = null; if (stream_select($readable, $writable, $except, 1)) { if ($readable) { foreach ($readable as $stream) { ... } } if ($writable) { foreach ($writable as $stream) { ... } } }

Slide 53

Slide 53 text

$server = stream_socket_server('tcp://127.0.0.1:1337'); $read = [$server]; $write = []; ... // deep in the event loop foreach ($readable as $stream) { if ($server === $stream) { $conn = stream_socket_accept($server, 0); $read[] = $conn; } else { $data = fread($stream, 1024); echo $data; } }

Slide 54

Slide 54 text

Event-driven, non-blocking I/O with PHP.

Slide 55

Slide 55 text

$loop = React\EventLoop\Factory::create(); $socket = new React\Socket\Server($loop); $http = new React\Http\Server($socket, $loop); $http->on('request', function ($req, $rep) { $rep->writeHead(); $rep->end("Hello World!\n"); }); $socket->listen(8080); $loop->run();

Slide 56

Slide 56 text

Use cases?

Slide 57

Slide 57 text

github.com/igorw/webserver-talk

Slide 58

Slide 58 text

Questions? @igorwesome speakerdeck.com /u/igorw nodephp.org