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

    View Slide

  2. @igorwesome

    View Slide

  3. View Slide

  4. no js

    View Slide

  5. HTTP: The Protocol

    View Slide

  6. Verbs
    •GET
    •POST

    View Slide

  7. Verbs
    •GET
    •POST
    •PUT
    •DELETE

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide

  13. Content Negotiation
    Accept: text/html
    Can haz html?

    View Slide

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

    View Slide

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

    View Slide

  16. Authentication

    View Slide

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

    View Slide

  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)

    View Slide

  19. Keep-Alive

    View Slide

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

    View Slide

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

    View Slide

  22. Trailers

    View Slide

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

    View Slide

  24. The Socket API

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  28. Binding to ports
    Binding to IPs

    View Slide

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

    View Slide

  30. Stream Sockets

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  36. Parsing

    View Slide

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

    View Slide

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

    View Slide

  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' => '',
    ];

    View Slide

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

    View Slide

  41. TDD
    because edorian said so!

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  51. Asynchronous I/O

    View Slide

  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) {
    ...
    }
    }
    }

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  56. Use cases?

    View Slide

  57. github.com/igorw/webserver-talk

    View Slide

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

    View Slide