Skip to content

Commit

Permalink
Merge pull request cakephp#4023 from cakephp/http-docs
Browse files Browse the repository at this point in the history
Start HTTP Server/Middleware docs.
  • Loading branch information
markstory committed Jun 8, 2016
2 parents 186721e + 0d72c6f commit ea33c5b
Show file tree
Hide file tree
Showing 21 changed files with 757 additions and 86 deletions.
88 changes: 58 additions & 30 deletions en/appendices/3-3-migration-guide.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ Deprecations
* ``header()`` use ``getHeaderLine()`` instead.
* ``cookie()`` use ``getCookie()`` instead.
* ``version()`` use ``getProtocolVersion()`` instead.
* Dispatcher Filters are now deprecated. Use :doc:`/controllers/middleware`
instead.

Behavior Changes
================
Expand All @@ -35,42 +37,35 @@ behavior that may effect your application:
when generating application local URLs. Previously string URLs would have the
base path prepended to them, while array URLs would not.

PSR7 Middleware Support Added
=============================

In tandem with the deprecation of Dispatcher Filters, support for PSR7
middleware has been added. Middleware is part of the new HTTP stack that is an
opt-in component of CakePHP 3.3.0. By using the new HTTP stack, you can take
advantage of features like:

* Using middleware from plugins, and libraries outside of CakePHP.
* Leverage the same response object methods in both the responses you get from
``Http\Client`` and the responses your application generates.
* Be able to augment the response objects emitted by error handling and asset
delivery.

See the :doc:`/controllers/middleware` chapter and :ref:`adding-http-stack`
sections for more information and how to add the new HTTP stack to an existing
application.

Http Client is now PSR7 Compatible
==================================

``Cake\Network\Http\Client`` has been moved to ``Cake\Http\Client``. Its request
and response objects now implement the
`PSR7 interfaces <http://www.php-fig.org/psr/psr-7/>`__. Several methods on
``Cake\Http\Client\Response`` are now deprecated, see above for more information.

Routing
=======

* ``Router::parse()``, ``RouteCollection::parse()`` and ``Route::parse()`` had
a ``$method`` argument added. It defaults to 'GET'. This new parameter reduces
reliance on global state, and necessary for the PSR7 work integration to be done.
* When building resource routes, you can now define a prefix. This is useful
when defining nested resources as you can create specialized controllers for
nested resources.

Console
=======

* Shell tasks that are invoked directly from the CLI no longer have their
``_welcome`` method invoked. They will also have the ``requested`` parameter
set now.
* ``Shell::err()`` will now apply the 'error' style to text. The default
styling is red text.

Request
=======

* ``Request::is()`` and ``Request::addDetector()`` now supports additional
arguments in detectors. This allows detector callables to operate on
additional parameters.
``Cake\Http\Client\Response`` are now deprecated, see above for more
information.

ORM
===
ORM Improvements
================

* Additional support has been added for mapping complex data types. This makes
it easier to work with geo-spatial types, and data that cannot be represented
Expand Down Expand Up @@ -108,8 +103,41 @@ Validation
``Validator::notEmpty()`` now accept a list of fields. This allows you
to more concisely define the fields that are required.


Other Enhancements
==================

Routing
-------

* ``Router::parse()``, ``RouteCollection::parse()`` and ``Route::parse()`` had
a ``$method`` argument added. It defaults to 'GET'. This new parameter reduces
reliance on global state, and necessary for the PSR7 work integration to be
done.
* When building resource routes, you can now define a prefix. This is useful
when defining nested resources as you can create specialized controllers for
nested resources.
* Dispatcher Filters are now deprecated. Use :doc:`/controllers/middleware`
instead.

Console
-------

* Shell tasks that are invoked directly from the CLI no longer have their
``_welcome`` method invoked. They will also have the ``requested`` parameter
set now.
* ``Shell::err()`` will now apply the 'error' style to text. The default
styling is red text.

Request
-------

* ``Request::is()`` and ``Request::addDetector()`` now supports additional
arguments in detectors. This allows detector callables to operate on
additional parameters.

Debugging Functions
===================
-------------------

* The ``pr()``, ``debug()``, and ``pj()`` functions now return the value being
dumped. This makes them easier to use when values are being returned.
Expand Down
1 change: 1 addition & 0 deletions en/contents.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ Contents
development/configuration
development/routing
controllers/request-response
controllers/middleware
controllers
views
orm
Expand Down
263 changes: 263 additions & 0 deletions en/controllers/middleware.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
Middleware
##########

Middleware objects give you the ability to 'wrap' your application in re-usable,
composable layers of Request handling, or response building logic. Middleware
are part of the new HTTP stack in CakePHP that leverages the PSR7 request and
response interfaces. By leveraging the PSR7 standard you can use any PSR7
compatible middleware available on `The Packagist <https://thepackagist.org>`__.

CakePHP provides several middleware out of the box:

* ``Cake\Error\Middleware\ErrorHandlerMiddleware`` traps exceptions from the
wrapped middleware and renders an error page using the
:doc:`/development/errors` Exception handler.
* ``Cake\Routing\AssetMiddleware`` checks whether the request is referring to a
theme or plugin asset file, such as a CSS, JavaScript or image file stored in
either a plugin's webroot folder or the corresponding one for a Theme.
* ``Cake\Routing\Middleware\RoutingMiddleware`` uses the ``Router`` to parse the
incoming URL and assign routing parameters to the request.
* ``Cake\I18n\Middleware\LocaleSelectorMiddleware`` enables automatic language
switching from the ``Accept-Language`` header sent by the browser.

Using Middleware
================

You attach middleware in your ``App\Application`` class' ``middleware`` method.
If you don't have an ``App\Application`` class, see the section on
:ref:`adding-http-stack` for more information. Your application's ``middleware``
hook method will be called early in the request process, you can use the
``Middleware`` object to attach middleware::

namespace App;

use Cake\Http\BaseApplication;

class Application extends BaseApplication
{
public functin middleware($middleware)
{
$error = new \Cake\Error\Middleware\ErrorHandlerMiddleware();
$middleware->push($error);
return $middleware;
}
}

In addition to pushing onto the end of the ``MiddlewareStack`` you can do
a variety of operations::

$layer = new \App\Middleware\CustomMiddleware;

// Pushed middleware will be last in line.
$middleware->push($layer);

// Prepended middleware will be first in line.
$middleware->prepend($layer);

// Insert in a specific slot. If the slot is out of
// bounds, it will be added to the end.
$middleware->insertAt(2, $layer);

// Insert before another middleware.
// If the named class cannot be found,
// an exception will be raised.
$middleware->insertBefore(
'Cake\Error\Middleware\ErrorHandlerMiddleware',
$layer
);

// Insert after another middleware.
// If the named class cannot be found, the
// middleware will added to the end.
$middleware->insertAfter(
'Cake\Error\Middleware\ErrorHandlerMiddleware',
$layer
);

Adding Middleware from Plugins
------------------------------

After the middleware stack has been prepared by the application, the
``Server.buildMiddleware`` event is triggered. This event can be useful to add
middleware from plugins. Plugins can register listeners in their bootstrap
scripts, that add middleware::

// In ContactManager plugin bootstrap.php
use Cake\Event\EventManager;

EventManager::instance()->on(
'Server.buildMiddleware',
function ($event, $middleware) {
$middleware->push(new ContactPluginMiddleware());
});

PSR7 Requests and Responses
===========================

Middleware and the new HTTP stack are built on top of the `PSR7 Request
& Response Interfaces <http://www.php-fig.org/psr/psr-7/>`__. While all
middleware will be exposed to these interfaces, your controllers, components,
and views will *not*.

Interacting with Requests
-------------------------

The ``RequestInterface`` provides methods for interacting with the headers,
method, URI, and body of a request. To interact with the headers, you can::

// Read a header as text
$value = $request->getHeaderLine(‘Content-Type’);

// Read header as an array
$value = $request->getHeader(‘Content-Type’);

// Read all the headers as an associative array.
$headers = $request->getHeaders();

Requests also give access to the cookies and uploaded files they contain::

// Get an array of cookie values.
$cookies = $request->getCookieParams();

// Get a list of UploadedFile objects
$files = $request->getUploadedFiles();

// Read the file data.
$files[0]->getStream();
$files[0]->getSize();
$files[0]->getClientFileName();

// Move the file.
$files[0]->moveTo($targetPath);

Requests contain a URI object, which contains methods for interacting with the
requested URI::

// Get the URI
$uri = $request->getUri();

// Read data out of the URI.
$path = $uri->getPath();
$query = $uri->getQuery();
$host = $uri->getHost();

Lastly, you can interact with a request's 'attributes'. CakePHP uses these
attributes to carry framework specific request parameters. There are 3 important
attributes in any request handled by CakePHP:

* ``base`` contains the base directory for your application if there is one.
* ``webroot`` contains the webroot directory for your application.
* ``params`` contains the results of route matching once routing rules have been
processed.

Interacting with Responses
--------------------------

The methods available to create a server response are the same as those
available when interacting with :ref:`httpclient-response-objects`. While the
interface is the same the usage scenarios are different.

When modifying the response, it is important to remember that responses are
**immutable**. You must always remember to store the results of any setter
method. For example::

// This does *not* modify $response. The new object was not
// assigned to a variable.
$response->withHeader('Content-Type', 'application/json');

// This works!
$newResponse = $response->withHeader('Content-Type', 'application/json');

Most often you'll be setting headers and response bodies on requests::

// Assign headers and a status code
$response = $response->withHeader('Content-Type', 'application/json')
->withHeader('Pragma', 'no-cache')
->withStatus(422);

// Write to the body
$body = $response->getBody();
$body->write(json_encode(['errno' => $errorCode]));

Creating Middleware
===================

Middleware can either be implemented as anonymous functions (Closures), or as
invokable classes. While Closures are suitable for smaller tasks they make
testing harder, and can create a complicated ``Application`` class. Middleware
classes in CakePHP have a few conventions:

* Middleware class files should be put in **src/Middleware**. For example:
**src/Middleware/CorsMiddleware.php**
* Middleware classes should be suffixed with ``Middleware``. For example:
``LinkMiddleware``.
* Middleware are expected to implement the middleware protocol.

Middleware Protocol
-------------------

While not a formal interface (yet), Middleware do have a soft-interface or
protocol. The protocol is as follows:

#. Middleware must implement ``__invoke($request, $response, $next)``.
#. Middleware must return an object implementing the PSR7 ``ResponseInterface``.

Middleware can return a response either by calling ``$next`` or by creating
their own response. We can see both options in our simple middleware::

// In src/Middleware/SimpleMiddleware.php
namespace App\Middleware;

class SimpleMiddleware
{
function __invoke($request, $response, $next)
{
// If we find /simple/ in the URL return a simple response.
if (strpos($request->getUri()->getPath(), '/simple/') !== false) {
$body = $response->getBody();
$body->write('Thanks!');
return $response->withStatus(202)
->withHeader('Content-Type', 'text/plain')
->withBody($body);
}

// Calling $next() delegates control to then *next* middleware
// In your application's stack.
$response = $next($request, $response);

// We could further modify the response before returning it.
return $response;
}
}

Now that we've made a very simple middleware, let's attach it to our
application::

// In src/Application.php
namespace App;

use App\Middleware\SimpleMiddleware;

class Application
{
public function middleware($middleware)
{
// Push your simple middleware onto the stack
$middleware->push(new SimpleMiddleware());

// Push some more middleware onto the stack

return $middleware;
}
}

.. _adding-http-stack:

Adding the new HTTP Stack to an Existing Application
====================================================

TODO

.. meta::
:title lang=en: Http Middleware
:keywords lang=en: http, middleware, psr7, request, response, wsgi, application, baseapplication
Loading

0 comments on commit ea33c5b

Please sign in to comment.