Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Webserver (froscon)

Webserver (froscon)

Build your own Webserver with PHP.

Igor Wiedler

August 26, 2012
Tweet

More Decks by Igor Wiedler

Other Decks in Programming

Transcript

  1. Build your own web server with

  2. @igorwesome

  3. None
  4. no js

  5. HTTP: The Protocol

  6. Verbs •GET •POST

  7. Verbs •GET •POST •PUT •DELETE

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

  9. • 1xx - Special protocol stuff • 2xx - All

    good • 3xx - Redirection • 4xx - You are stupid • 5xx - Oh crap! I’m a fucking moron. Status codes
  10. • RFC 2324: HTCPCP • 418 - I’m a Teapot

    Status codes
  11. 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
  12. Redirection • 301 - Moved Permanently • 302 - Found

    (misused) • 303 - See Other • 305 - Use Proxy • 307 - Temporary Redirect • Location header
  13. Content Negotiation Accept: text/html Can haz html?

  14. Content Negotiation Accept: text/html Can haz html? Content-Type: application/json Server

    says no kthxbai
  15. Caching • Cache-Control: public/private, max-age • ETag / If-Match •

    Last-Modified / If-Modified-Since • 403 - Not Modified
  16. Authentication

  17. Basic Auth • 401 - Unauthorized • 403 - Forbidden

    • WWW-Authenticate: Basic realm=”foo” • Authorization: base64(username:password)
  18. 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)
  19. Keep-Alive

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

    0 \r\n \r\n
  21. 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. .
  22. Trailers

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

  24. The Socket API

  25. ________ < socket > -------- \ ^__^ \ (oo)\_______ (__)\

    )\/\ ||----w | || ||
  26. 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 | | +---+--------------+----------------+
  27. #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> socket() bind() listen() accept()

  28. Binding to ports Binding to IPs

  29. 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); }
  30. Stream Sockets

  31. $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); }
  32. $contents = file_get_contents('https://igor.io');

  33. $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);
  34. $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); }
  35. $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");

  36. Parsing

  37. GET / HTTP/1.1 <--- request line Host: igor.io <--- header

    <--- line feed <--- body (blank)
  38. $request = implode("\r\n", [ 'GET / HTTP/1.1', 'Host: igor.io', '',

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

    '', ]); $expected = [ 'method' => 'GET', 'path' => '/', 'protocol' => 'HTTP/1.1', 'headers' => ['Host' => 'igor.io'], 'body' => '', ];
  40. $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));
  41. TDD because edorian said so!

  42. 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'); }
  43. HTTP/1.1 200 OK <--- status line Content-Length: 2 <--- headers

    <--- line feed Hi <--- body
  44. $responseSpec = [200, ['Content-Length' => '2'], 'Hi']; $response = implode("\r\n",

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

    [ 'HTTP/1.1 200 OK', 'Content-Length: 2', '', 'Hi', ]); assert($response === buildResponse($responseSpec));
  46. function buildResponse(array $responseSpec) { list($status, $headers, $body) = $responseSpec; return

    buildResponseStatusLine($status). buildResponseHeaders($headers). "\r\n". $body; }
  47. function buildResponseStatusLine($status) { $statusMap = [ 200 => 'OK', 404

    => 'Not Found', ]; return "HTTP/1.1 $status {$statusMap[$status]}\r\n"; }
  48. function buildResponseHeaders(array $headers) { $flatHeaders = []; foreach ($headers as

    $name => $value) { $flatHeaders[] = "$name: $value"; } return implode("\r\n", $flatHeaders)."\r\n"; }
  49. $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));
  50. 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
  51. Asynchronous I/O

  52. $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) { ... } } }
  53. $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; } }
  54. Event-driven, non-blocking I/O with PHP.

  55. $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();
  56. Use cases?

  57. github.com/igorw/webserver-talk

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