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.

D6ccd6409910643d05ddaea3b2cd6f13?s=128

David Zuelke

May 30, 2017
Tweet

Transcript

  1. YOUR NEXT WEB SERVER WILL BE WRITTEN IN... PHP MIGHT

    International PHP Conference 2017 (Spring Edition) Berlin, Germany
  2. David Zuelke

  3. None
  4. dz@heroku.com

  5. @dzuelke

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

  7. CGI

  8. NCSA, 1993

  9. None
  10. RFC 3875, 1997-2004

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

  13. None
  14. None
  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
  16. slow

  17. (but simple)

  18. #!/bin/bash cat <<EOF Content-Type: text/html <html> <head> <title>Hello World</title> </head>

    <body> <h1>Hello World</h1> <p>Your browser is ${HTTP_USER_AGENT:-unknown}</p> <p>This page was served by $(hostname)</p> </body> </html> EOF
  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, …
  20. PHP's SAPIs
 & EXECUTION MODEL

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

  22. marshals input and output from and to the interface

  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
  24. PHP was built for web scripting, for CGI

  25. execution model modeled around statelessness "blank slate"

  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
  27. all of that on each CGI request

  28. NEIN NEIN NEIN NEIN DAS IST ZU LANGSAM

  29. mod_php

  30. embeds PHP into the Apache httpd process

  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)
  32. but now PHP is in each httpd process

  33. even when serving static files

  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)
  35. so... what do we do?

  36. ¯\_(ツ)_/¯

  37. "let's just use Nginx!"

  38. FastCGI

  39. protocol similar to CGI, but over a socket

  40. persistent server process

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

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

  43. no overhead in web server for static content

  44. web server can use threading or whatever

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

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

  47. RUBY & PYTHON

  48. Rack & WSGI

  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
  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, ...
  51. NATIVE PHP WEB SERVERS

  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
  53. FRAMEWORKS FOR EVENT- DRIVEN NON-BLOCKING I/O • http://reactphp.org • https://icicle.io

    • http://amphp.org
  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";
  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();
  56. https://github.com/M6Web/PhpProcessManagerBundle & https://github.com/php-pm/php-pm

  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); });
  58. WHAT BECOMES POSSIBLE

  59. speeeeeeeeeed :)

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

  61. processing request data while it's still uploading

  62. handling Web Sockets in the same process

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

    php server.php
  64. WHAT BECOMES IMPOSSIBLE

  65. native session handling

  66. ignoring memory leaks

  67. MAYBE, IN A BRIGHT FUTURE...

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

    interop :)
  69. a universe of useful middlewares :)

  70. competition between different native web servers :)

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

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

    and friends :)
  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
  74. The End

  75. THANK YOU FOR LISTENING! Questions? Ask me: @dzuelke & dz@heroku.com