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

HTTP & Middleware

Rob Allen
November 26, 2018

HTTP & Middleware

An introductory exploration of HTTP and Middleware in PHP

Rob Allen

November 26, 2018
Tweet

More Decks by Rob Allen

Other Decks in Programming

Transcript

  1. Slim Created by Josh Lockhart (phptherightway.com) • PSR-2 coding style

    • PSR-4 autoloading • PSR-7 Request and Response objects • PSR-11 container support • Middleware architecture (PSR-15 upcoming) Rob Allen ~ @akrabat
  2. Request GET / HTTP/1.1 Host: akrabat.com User-Agent: Mozilla/5.0 (Macintosh; Intel

    Mac OS X 10.14; rv:63.0) Gecko/20100101 Firefox/63.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-GB,en;q=0.5 Accept-Encoding: gzip, deflate, br Connection: keep-alive If-Modified-Since: Mon, 04 Nov 2018 16:21:02 GMT Cache-Control: max-age=0 Rob Allen ~ @akrabat
  3. Response HTTP/1.1 200 OK Server: nginx/1.7.6 Date: Mon, 04 Nov

    2018 16:27:06 GMT Content-Type: text/html; charset=UTF-8 Content-Length: 16091 Connection: keep-alive Keep-Alive: timeout=65 Vary: Accept-Encoding, Cookie Cache-Control: max-age=3, must-revalidate Content-Encoding: gzip Last-Modified: Mon, 04 Apr 2016 16:21:02 GMT Strict-Transport-Security: max-age=15768000 <!DOCTYPE html> <head> Rob Allen ~ @akrabat
  4. How do we do this in PHP? Request: • $_SERVER,

    $_GET, $_POST, $_COOKIE, $_FILES • apache_request_headers() / getallheaders() • php://input Rob Allen ~ @akrabat
  5. How do we do this in PHP? Response: • header()

    • http_response_code() • header_list() / headers_sent() • echo (& ob_*() family) Rob Allen ~ @akrabat
  6. How do we do this in PHP? URI: scheme://username:password@hostname:port/path?arg=value#anchor •

    parse_url() • parse_str() • http_build_query() Rob Allen ~ @akrabat
  7. PSR 7: HTTP messaging • Provides for a uniform access

    to HTTP messages • A set of interfaces for requests and responses • Created by Framework Interoperability Group Rob Allen ~ @akrabat
  8. ServerRequestInterface In addition to RequestInterface: • Server parameters • Query

    string arguments • Deserialised body • Uploaded Files • Attributes Rob Allen ~ @akrabat
  9. Response • Protocol version • Status code (& reason phrase)

    • Headers • Body Rob Allen ~ @akrabat
  10. Immutability Request, ServerRequest, Response & Uri are immutable $uri =

    new Uri('https://api.joind.in/v2.1/events'); $uri2 = $uri->withQuery('?filter=upcoming'); $uri3 = $uri->withQuery('?filter=cfp'); Rob Allen ~ @akrabat
  11. Immutability Request, ServerRequest, Response & Uri are immutable $uri =

    new Uri('https://api.joind.in/v2.1/events'); $uri2 = $uri->withQuery('?filter=upcoming'); $uri3 = $uri->withQuery('?filter=cfp'); public function withQuery($query) { $clone = clone $this; $clone->query = $this->filterQuery($query); return $clone; } Rob Allen ~ @akrabat
  12. What you meant! $response = new Response(); $response = $response->withStatus(302);

    $response = $response->withHeader('Location', 'http://example.com'); // or $response = new Response(); $response = $response->withStatus(302) ->withHeader('Location', 'http://example.com'); Rob Allen ~ @akrabat
  13. Headers Reading from the message: $request->hasHeader('Accept'); // boolean $request->getHeader('Accept'); //

    array $request->getHeaderLine('Accept'); // string Updating the message: $response = $response->withHeader('Accept', 'application/xml'); $response = $response->withAddedHeader('Accept', 'text/xml'); $response = $response->withoutHeader('Location'); Rob Allen ~ @akrabat
  14. Streams Allows for very large message bodies in a memory

    efficient manner $largeFile = __DIR__ . '/brand_guidelines.ppt'; return (new Response()) ->withHeader('Content-Type', 'application/vnd.ms-powerpoint') ->withHeader('Content-Length', (string) filesize($largeFile)) ->withBody(new Stream($largeFile)); Rob Allen ~ @akrabat
  15. Handling strings For strings, use the php:://temp stream: $response =

    $response->withBody(new Stream('php://temp')); $response->getBody()->write('Hello world!'); Rob Allen ~ @akrabat
  16. Mutable! $body->write('Hello'); $body->write(' World!'); $response = (new Response()) ->withStatus(200, 'OK')

    ->withHeader('Content-Type', 'application/html') ->withBody($body); Mitigate: Use read-only streams for server requests and client responses Rob Allen ~ @akrabat
  17. ServerRequest Attributes Allow passing of data from one component to

    the next // component 1 function findCountry($request) { $country = $this->geoLocate($request); $request = $request->withAttribute('country', $country); return $request; } // In an action method function listAction($request, $response, $args) { $country = $request->getAttribute('country'); // do something now that we know the country } Rob Allen ~ @akrabat
  18. This is not new • NodeJS: Connect • Python: WSGI

    • Ruby: Rack • PHP: StackPHP, Slim Rob Allen ~ @akrabat
  19. PSR-7 Middleware function (ServerRequestInterface $request, ResponseInterface $response, callable $next) :

    ResponseInterface { // do something before // call down the chain to the next middleware if ($next) { $response = $next($request, $response); } // do something with $response after return $response; } Rob Allen ~ @akrabat
  20. PSR-7 Middleware function (ServerRequestInterface $request, ResponseInterface $response, callable $next) :

    ResponseInterface { // do something before // call down the chain to the next middleware if ($next) { $response = $next($request, $response); } // do something with $response after return $response; } Rob Allen ~ @akrabat
  21. PSR-7 Middleware function (ServerRequestInterface $request, ResponseInterface $response, callable $next) :

    ResponseInterface { // do something before // call down the chain to the next middleware if ($next) { $response = $next($request, $response); } // do something with $response after return $response; } Rob Allen ~ @akrabat
  22. PSR-7 Middleware function (ServerRequestInterface $request, ResponseInterface $response, callable $next) :

    ResponseInterface { // do something before // call down the chain to the next middleware if ($next) { $response = $next($request, $response); } // do something with $response after return $response; } Rob Allen ~ @akrabat
  23. PSR-7 Middleware function (ServerRequestInterface $request, ResponseInterface $response, callable $next) :

    ResponseInterface { // do something before // call down the chain to the next middleware if ($next) { $response = $next($request, $response); } // do something with $response after return $response; } Rob Allen ~ @akrabat
  24. What can you do with Middleware? Session CORS CSRF Logging

    Firewall Debug bar Authentication Throttling Error handling HTTP cache Honeypot Validation CSP etc... Rob Allen ~ @akrabat
  25. Middleware dispatching $app = new Slim\App(); $app->add($mw1); $app->add(SomeMiddleware::class); $app->add('MoreMiddleware'); $app->add([$instance,

    'methodName']); $app->add(function ($request, $response, $next) { return $next($request, $response); }); $response = $app->run(); Rob Allen ~ @akrabat
  26. Data transfer between middleware Attributes allow passing of data from

    one middleware to the next class IpAddress { public function __invoke($request, $response, $next) { $ipAddress = $this->determineClientIpAddress($request); $request = $request->withAttribute('ip_address', $ipAddress); return $response = $next($request, $response); } // full source: github.com/akrabat/ip-address-middleware } Rob Allen ~ @akrabat
  27. Data transfer between middleware Attributes allow passing of data from

    one middleware to the next class IpAddressMiddleware { public function __invoke($request, $response, $next) { $ipAddress = $this->determineClientIpAddress($request); $request = $request->withAttribute('ip_address', $ipAddress); return $response = $next($request, $response); } // full source: github.com/akrabat/ip-address-middleware } Rob Allen ~ @akrabat
  28. Data transfer between middleware Attributes allow passing of data from

    one middleware to the next class IpAddressMiddleware { public function __invoke($request, $response, $next) { $ipAddress = $this->determineClientIpAddress($request); $request = $request->withAttribute('ip_address', $ipAddress); return $response = $next($request, $response); } // full source: github.com/akrabat/ip-address-middleware } Rob Allen ~ @akrabat
  29. Data transfer between middleware Attributes allow passing of data from

    one middleware to the next class IpAddressMiddleware { public function __invoke($request, $response, $next) { $ipAddress = $this->determineClientIpAddress($request); $request = $request->withAttribute('ip_address', $ipAddress); return $response = $next($request, $response); } // full source: github.com/akrabat/ip-address-middleware } Rob Allen ~ @akrabat
  30. Data transfer between middleware Attributes allow passing of data from

    one middleware to the next class IpAddressMiddleware { public function __invoke($request, $response, $next) { $ipAddress = $this->determineClientIpAddress($request); $request = $request->withAttribute('ip_address', $ipAddress); return $response = $next($request, $response); } // full source: github.com/akrabat/ip-address-middleware } Rob Allen ~ @akrabat
  31. Data transfer between middleware Read the attribute class GuardMiddleware {

    protected $allowedIps = [10.0.0.1, 192.168.0.1] public function __invoke($request, $response, $next) { $ipAddress = $request->getAttribute('ip_address'); if (!in_array($ipAddress, $this->allowedIps)) { return $response->withStatus(401); } return $next($request, $response); } } Rob Allen ~ @akrabat
  32. Data transfer between middleware Read the attribute class GuardMiddleware {

    protected $allowedIps = [10.0.0.1, 192.168.0.1] public function __invoke($request, $response, $next) { $ipAddress = $request->getAttribute('ip_address'); if (!in_array($ipAddress, $this->allowedIps)) { return $response->withStatus(401); } return $next($request, $response); } } Rob Allen ~ @akrabat
  33. Data transfer between middleware Read the attribute class GuardMiddleware {

    protected $allowedIps = [10.0.0.1, 192.168.0.1] public function __invoke($request, $response, $next) { $ipAddress = $request->getAttribute('ip_address'); if (!in_array($ipAddress, $this->allowedIps)) { return $response->withStatus(401); } return $next($request, $response); } } Rob Allen ~ @akrabat
  34. Data transfer between middleware Read the attribute class GuardMiddleware {

    protected $allowedIps = [10.0.0.1, 192.168.0.1] public function __invoke($request, $response, $next) { $ipAddress = $request->getAttribute('ip_address'); if (!in_array($ipAddress, $this->allowedIps)) { return $response->withStatus(401); } return $next($request, $response); } } Rob Allen ~ @akrabat
  35. Data transfer between middleware Read the attribute class GuardMiddleware {

    protected $allowedIps = [10.0.0.1, 192.168.0.1] public function __invoke($request, $response, $next) { $ipAddress = $request->getAttribute('ip_address'); if (!in_array($ipAddress, $this->allowedIps)) { return $response->withStatus(401); } return $next($request, $response); } } Rob Allen ~ @akrabat
  36. PSR-15 MiddlewareInterface namespace Psr\Http\Server; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; interface MiddlewareInterface

    { public function process( ServerRequestInterface $request, RequestHandlerInterface $handler ) : ResponseInterface; } Rob Allen ~ @akrabat
  37. PSR-15 MiddlewareInterface namespace Psr\Http\Server; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; interface MiddlewareInterface

    { public function process( ServerRequestInterface $request, RequestHandlerInterface $handler ) : ResponseInterface; } Rob Allen ~ @akrabat
  38. PSR-15 MiddlewareInterface namespace Psr\Http\Server; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; interface MiddlewareInterface

    { public function process( ServerRequestInterface $request, RequestHandlerInterface $handler ) : ResponseInterface; } Rob Allen ~ @akrabat
  39. PSR-15 MiddlewareInterface namespace Psr\Http\Server; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; interface MiddlewareInterface

    { public function process( ServerRequestInterface $request, RequestHandlerInterface $handler ) : ResponseInterface; } Rob Allen ~ @akrabat
  40. PSR-15 MiddlewareInterface namespace Psr\Http\Server; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; interface MiddlewareInterface

    { public function process( ServerRequestInterface $request, RequestHandlerInterface $handler ) : ResponseInterface; } Rob Allen ~ @akrabat
  41. Structure of PSR-15 middleware class TimerMiddleware implements MiddlewareInterface { public

    function process($request, $handler) { $start = microtime(true); $response = $handler->handle($request); $taken = microtime(true) - $start; $response->getBody()->write("<!-- Time: $taken -->"); return $response; } } Rob Allen ~ @akrabat
  42. Structure of PSR-15 middleware class TimerMiddleware implements MiddlewareInterface { public

    function process($request, $handler) { $start = microtime(true); $response = $handler->handle($request); $taken = microtime(true) - $start; $response->getBody()->write("<!-- Time: $taken -->"); return $response; } } Rob Allen ~ @akrabat
  43. Structure of PSR-15 middleware class TimerMiddleware implements MiddlewareInterface { public

    function process($request, $handler) { $start = microtime(true); $response = $handler->handle($request); $taken = microtime(true) - $start; $response->getBody()->write("<!-- Time: $taken -->"); return $response; } } Rob Allen ~ @akrabat
  44. Structure of PSR-15 middleware class TimerMiddleware implements MiddlewareInterface { public

    function process($request, $handler) { $start = microtime(true); $response = $handler->handle($request); $taken = microtime(true) - $start; $response->getBody()->write("<!-- Time: $taken -->"); return $response; } } Rob Allen ~ @akrabat
  45. Structure of PSR-15 middleware class TimerMiddleware implements MiddlewareInterface { public

    function process($request, $handler) { $start = microtime(true); $response = $handler->handle($request); $taken = microtime(true) - $start; $response->getBody()->write("<!-- Time: $taken -->"); return $response; } } Rob Allen ~ @akrabat
  46. Structure of PSR-15 middleware class TimerMiddleware implements MiddlewareInterface { public

    function process($request, $handler) { $start = microtime(true); $response = $handler->handle($request); $taken = microtime(true) - $start; $response->getBody()->write("<!-- Time: $taken -->"); return $response; } } Rob Allen ~ @akrabat
  47. A typical PSR-15 RequestHandler namespace App\Handler; // (use statements here)

    class HomePageHandler implements RequestHandlerInterface { public function handle(ServerRequestInterface $request) : ResponseInterface { return new HtmlResponse( '<p>Hello World</p>' ); } } Rob Allen ~ @akrabat
  48. A typical PSR-15 RequestHandler namespace App\Handler; // (use statements here)

    class HomePageHandler implements RequestHandlerInterface { public function handle(ServerRequestInterface $request) : ResponseInterface { return new HtmlResponse( '<p>Hello World</p>' ); } } Rob Allen ~ @akrabat
  49. A typical PSR-15 RequestHandler namespace App\Handler; // (use statements here)

    class HomePageHandler implements RequestHandlerInterface { public function handle(ServerRequestInterface $request) : ResponseInterface { return new HtmlResponse( '<p>Hello World</p>' ); } } Rob Allen ~ @akrabat
  50. Zend Expressive: PSR-15 $app = new Zend\Expressive\Application(/* ... */); //

    add middleware $app->pipe(ErrorHandler::class); $app->pipe(RouteMiddleware::class); $app->pipe(IpAddress::class); $app->pipe(DispatchMiddleware::class); // add routes $app->get('/', App\Handler\HomePageHandler::class); $app->get('/api/ping', App\Handler\PingHandler::class); // run $app->run(); Rob Allen ~ @akrabat
  51. Slim Framework: PSR-7 $app = new Slim\App(); // add middleware

    $app->add(IpAddress::class); // add routes $app->get('/', App\Action\HomePageAction::class); $app->get('/api/ping', 'App\Controller\ApiController:pingAction'); // run $app->run(); Rob Allen ~ @akrabat
  52. Middleware components Search on Packagist • https://packagist.org/search/?q=psr-7%20middleware • https://packagist.org/search/?query=psr-15%20middleware Ones

    I use: • akrabat: ip-address-middleware & proxy-detection-middleware • tuupola: cors-middleware, slim-jwt-auth & slim-basic-auth • franzl: whoops-middleware • oscarotero: psr7-middlewares & psr15-middlewares Rob Allen ~ @akrabat