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

Your next Web server will be written in... PHP

Your next Web server will be written in... PHP

Presentation at International PHP Conference 2017 Spring Edition in Berlin, Germany.

David Zuelke

May 30, 2017
Tweet

More Decks by David Zuelke

Other Decks in Programming

Transcript

  1. YOUR NEXT WEB SERVER
    WILL BE WRITTEN
    IN... PHP
    MIGHT
    International PHP Conference 2017 (Spring Edition)
    Berlin, Germany

    View Slide

  2. David Zuelke

    View Slide

  3. View Slide

  4. View Slide

  5. @dzuelke

    View Slide

  6. Slides: https://speakerdeck.com/dzuelke/

    View Slide

  7. CGI

    View Slide

  8. NCSA, 1993

    View Slide

  9. View Slide

  10. RFC 3875, 1997-2004

    View Slide

  11. View Slide

  12. /cgi-bin/counter.pl?site=8712

    View Slide

  13. View Slide

  14. View Slide

  15. HOW CGI WORKS
    1. Web server parses request from client
    2. Web server sets request info into environment

    (PATH_INFO, REQUEST_METHOD, HTTP_ACCEPT, …)
    3. Web server executes CGI script
    4. CGI script echos status, headers, and body
    5. Web server returns response to client

    View Slide

  16. slow

    View Slide

  17. (but simple)

    View Slide

  18. #!/bin/bash
    cat <Content-Type: text/html


    Hello World


    Hello World
    Your browser is ${HTTP_USER_AGENT:-unknown}
    This page was served by $(hostname)


    EOF

    View Slide

  19. CGI VARIABLES (SEE $_SERVER)
    • Server info:

    SERVER_SOFTWARE, SERVER_NAME, GATEWAY_INTERFACE
    • Request info:

    SERVER_PROTOCOL, SERVER_PORT, REQUEST_METHOD,
    PATH_INFO, PATH_TRANSLATED, QUERY_STRING,
    REMOTE_ADDR, CONTENT_TYPE, CONTENT_LENGTH, …
    • Request headers:

    HTTP_HOST, HTTP_ACCEPT, HTTP_USER_AGENT, …

    View Slide

  20. PHP's SAPIs

    & EXECUTION MODEL

    View Slide

  21. a SAPI is the "gateway" to the PHP engine

    View Slide

  22. marshals input and output from and to the interface

    View Slide

  23. PHP WEB SAPI (SIMPLIFIED)
    • populate $_SERVER and $_ENV
    • parse QUERY_STRING into $_GET
    • " application/x-www-form-urlencoded into $_POST
    • " multipart/form-data into $_FILES
    • return header() data as headers, anything echo()d as content

    View Slide

  24. PHP was built for web scripting, for CGI

    View Slide

  25. execution model modeled around statelessness "blank slate"

    View Slide

  26. PHP CGI EXECUTION LIFECYCLE

    (SIMPLIFIED)
    1. core init, load extensions etc
    2. MINIT for all modules (extension initialization etc)
    3. SAPI ready for (one and only) request
    4. RINIT for all modules (e.g. ext-session if session.auto_start)
    5. script executes
    6. RSHUTDOWN
    7. engine cleanup (unset all variables and state)
    8. MSHUTDOWN

    View Slide

  27. all of that on each CGI request

    View Slide

  28. NEIN NEIN
    NEIN NEIN
    DAS IST ZU
    LANGSAM

    View Slide

  29. mod_php

    View Slide

  30. embeds PHP into the Apache httpd process

    View Slide

  31. mod_php EXECUTION LIFECYCLE

    (SIMPLIFIED)
    1. SAPI ready for (next) request
    2. RINIT for all modules (e.g. ext-session if
    session.auto_start)
    3. script executes
    4. RSHUTDOWN
    5. engine cleanup (unset all variables and state)

    View Slide

  32. but now PHP is in each httpd process

    View Slide

  33. even when serving static files

    View Slide

  34. APACHE PROCESS MODELS
    • mpm_prefork creates worker processes

    (each with a PHP)
    • mpm_worker uses threads

    (so you need ZTS, and stuff will generally crash a lot)
    • mpm_event uses an event loop

    (best, but you can't embed something like PHP at all)

    View Slide

  35. so... what do we do?

    View Slide

  36. ¯\_(ツ)_/¯

    View Slide

  37. "let's just use Nginx!"

    View Slide

  38. FastCGI

    View Slide

  39. protocol similar to CGI, but over a socket

    View Slide

  40. persistent server process

    View Slide

  41. old fcgi SAPI: web server manages FastCGI child processes

    View Slide

  42. newer FPM: PHP manages its own child processes' lifecycle

    View Slide

  43. no overhead in web server for static content

    View Slide

  44. web server can use threading or whatever

    View Slide

  45. still re-executes from ~RINIT for each request

    View Slide

  46. still bootstraps Symfony/Laravel/… on each request

    View Slide

  47. RUBY & PYTHON

    View Slide

  48. Rack & WSGI

    View Slide

  49. RUBY/RACK
    app = Proc.new do |env|
    ['200', {'Content-Type' => 'text/plain'}, ['Hello World']]
    end
    def application(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/plain')])
    yield 'Hello World\n'
    PYTHON/WSGI

    View Slide

  50. THE RACK/WSGI STACKS
    1. Web server:

    Unicorn, Gunicorn, Puma, Tornado, Nginx with
    Phusion Passenger, Apache with mod_wsgi, ...
    2. Middlewares:

    Routing, authentication, filtering, post-processing, ...
    3. Application/framework:

    Rails, Django, Sinatra, Flask, ...

    View Slide

  51. NATIVE PHP WEB SERVERS

    View Slide

  52. PHP IS NOW READY
    • PHP 7+ performance is amazing
    • Almost all engine errors are catchable since PHP 7
    • Signal handling without ticks in PHP 7.1
    • Concurrency frameworks and event lib extensions

    View Slide

  53. FRAMEWORKS FOR EVENT-
    DRIVEN NON-BLOCKING I/O
    • http://reactphp.org
    • https://icicle.io
    • http://amphp.org

    View Slide

  54. IT'S ALL A REACTOR
    echo "-- before run()\n";
    Amp\run(function() {
    Amp\repeat(function() {
    echo "tick\n";
    }, $msInterval = 1000);
    Amp\once("Amp\stop", $msDelay = 5000);
    });
    echo "-- after run()\n";

    View Slide

  55. SIMPLE WEB SERVER, ReactPHP
    $app = function ($request, $response) {
    $response->writeHead(200, array('Content-Type' => 'text/plain'));
    $response->end("Hello World\n");
    };
    $loop = React\EventLoop\Factory::create();
    $socket = new React\Socket\Server($loop);
    $http = new React\Http\Server($socket, $loop);
    $http->on('request', $app);
    $socket->listen(1337);
    $loop->run();

    View Slide

  56. https://github.com/M6Web/PhpProcessManagerBundle & https://github.com/php-pm/php-pm

    View Slide

  57. $kernel = new AppKernel('prod', false);
    $reactHttp->on('request', function ($request, $response) use ($kernel) {
    $headers = $request->getHeaders();
    if (in_array(strtoupper($request->getMethod()), ['POST','PUT','DELETE','PATCH'])
    && isset($headers['Content-Type'])
    && (0 === strpos($headers['Content-Type'], 'application/x-www-form-urlencoded'))
    )
    parse_str($request->getBody(), $post);
    $sfRequest = new Symfony\Component\HttpFoundation\Request(
    $request->getQuery(), // query string
    $post ?? [], // parsed POST payload
    array(), // attributes (PATH_INFO etc)
    array(), // $_COOKIES
    $request->getFiles(), // $_FILES
    array(), // $_SERVER
    $request->getBody() // raw request body
    );
    $sfRequest->setMethod($request->getMethod());
    $sfRequest->headers->replace($headers);
    $sfRequest->server->set('REQUEST_URI', $request->getPath());
    if (isset($headers['Host']))
    $sfRequest->server->set('SERVER_NAME', explode(':', $headers['Host'])[0]);
    $sfResponse = $kernel->handle($sfRequest);
    $response->writeHead($sfResponse->getStatusCode(), $sfResponse->headers->all());
    $response->end($sfResponse->getContent());
    $kernel->terminate($request, $response);
    });

    View Slide

  58. WHAT BECOMES POSSIBLE

    View Slide

  59. speeeeeeeeeed :)

    View Slide

  60. http://marcjschmidt.de/blog/2014/02/08/php-high-performance.html

    View Slide

  61. processing request data while it's still uploading

    View Slide

  62. handling Web Sockets in the same process

    View Slide

  63. git clone project && cd project && composer install && php server.php

    View Slide

  64. WHAT BECOMES IMPOSSIBLE

    View Slide

  65. native session handling

    View Slide

  66. ignoring memory leaks

    View Slide

  67. MAYBE, IN A BRIGHT FUTURE...

    View Slide

  68. PSR-7 (HTTP Message Interface) + PSR-15 (HTTP Middlewares) = ultimate interop :)

    View Slide

  69. a universe of useful middlewares :)

    View Slide

  70. competition between different native web servers :)

    View Slide

  71. "legacy" server that runs in FPM SAPI and translates a request :)

    View Slide

  72. "legacy" middleware that runs in new server and populates $_GET and friends :)

    View Slide

  73. READING MATERIAL
    • https://gnugat.github.io/2016/04/13/super-speed-sf-react-php.html
    • http://blog.kelunik.com/2015/09/20/getting-started-with-amp.html
    • http://blog.kelunik.com/2015/10/21/getting-started-with-aerys.html
    • http://blog.kelunik.com/2015/10/20/getting-started-with-aerys-
    websockets.html
    • http://marcjschmidt.de/blog/2014/02/08/php-high-performance.html
    • http://marcjschmidt.de/blog/2016/04/16/php-high-performance-reactphp-
    jarves-symfony-follow-up.html

    View Slide

  74. The End

    View Slide

  75. THANK YOU FOR LISTENING!
    Questions? Ask me: @dzuelke & [email protected]

    View Slide