From a8b3ce9829026b99442cfe36851914fa2b6c9189 Mon Sep 17 00:00:00 2001 From: PJ Dietz Date: Sat, 30 May 2015 19:06:16 -0400 Subject: [PATCH] Add documentation for Messages --- docs/source/index.rst | 1 + docs/source/messages.rst | 307 +++++++++++++++++++++++++++++++++++++++ docs/source/overview.rst | 1 - 3 files changed, 308 insertions(+), 1 deletion(-) create mode 100644 docs/source/messages.rst diff --git a/docs/source/index.rst b/docs/source/index.rst index 84a462a..bda4298 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -103,6 +103,7 @@ Contents overview getting-started + messages middleware router uri-templates diff --git a/docs/source/messages.rst b/docs/source/messages.rst new file mode 100644 index 0000000..2fb259a --- /dev/null +++ b/docs/source/messages.rst @@ -0,0 +1,307 @@ +Messages and PSR-7 +================== + +WellRESTed uses PSR-7_ as the interfaces for HTTP messages. This section provides an introduction to working with these interfaces and the implementations provided with WellRESTed. For more information, please read PSR-7_. + +Obtaining Instances +------------------- + +When working with middleware_, you generally will not need to create requests and responses yourself, as these are passed into the middleware when it is dispatched. + +In `Getting Started`_, we saw that middleware looks like this: + +.. code-block:: php + + /** + * @param Psr\Http\Message\ServerRequestInterface $request + * @param Psr\Http\Message\ResponseInterface $response + * @param callable $next + * @return Psr\Http\Message\ResponseInterface + */ + function ($request, $response, $next) { } + +When middleware is called, it receives a ``Psr\Http\Message\ServerRequestInterface`` instance representing the client's request and a ``Psr\Http\Message\ResponseInterface`` instance that serves as a starting place for the response to output to the client. These instances are created by the ``WellRESTed\Server`` when you call ``WellRESTed\Server::respond``. + +.. note:: + + If you want to provide your own custom request and response (either to adjust the initial settings or to use a different implementation), you can do so by passing request and response instances as the first and second parameters to ``WellRESTed\Server::respond``. + +Requests +-------- + +The ``$request`` variable passed to middleware represents the request message sent by the client. Middleware can inspect this variable to read information such as the request path, method, query, headers, and body. + +Let's start with a very simple GET request to the path ``/cats/?color=orange``. + +.. code-block:: http + + GET /cats/ HTTP/1.1 + Host: example.com + Cache-control: no-cache + +You can read information from the request in your middleware like this: + +.. code-block:: php + + function ($request, $response, $next) { + + $path = $request->getRequestTarget(); + // "/cats/?color=orange" + + $method = $request->getMethod(); + // "GET" + + $query = $request->getQueryParams(); + /* + Array + ( + [color] => orange + ) + */ + + } + +This example middleware shows that you can use: + + - ``getRequestTarget()`` to read the path and query string for the request + - ``getMethod()`` to read the HTTP verb (e.g., GET, POST, OPTIONS, DELETE) + - ``getQueryParams()`` to read the query as an associative array + +Let's move on to some more intersting features. + +Headers +^^^^^^^ + +The request above also included a ``Cache-control: no-cache`` header. You can read this header a number of ways. The simplest way is with the ``getHeaderLine($name)`` method. + +Call ``getHeaderLine($name)`` and pass the case-insensitive name of a header. The method will return the value for the header, or an empty string. + +.. code-block:: php + + function ($request, $response, $next) { + + // This message contains a "Cache-control: no-cache" header. + $cacheControl = $request->getHeaderLine("cache-control"); + // "no-cache" + + // This message does not contain any authoriation headers. + $authoriziation = $request->getHeaderLine("authorization"); + // "" + + } + +.. note:: + + All methods relating to headers treat header field name case insensitively. + + +Because HTTP messages may contain multiple headers with the same field name, ``getHeaderLine($name)`` has one other feature: If multiple headers with the same field name are present in the message, ``getHeaderLine($name)`` returns a string containing all of the values for that field, concatenated by commas. This is more common with responses, paricularaly with the ``Set-cookie`` header, but is still possible for requests. + +You may also use ``hasHeader($name)`` to test if a header exists, ``getHeader($name)`` to receive an array of values for this field name, and ``getHeaders()`` to receive an associative array of headers where each key is a field name and each value is an array of field values. + + +Body +^^^^ + +PSR-7_ provides access to the body of the request as a stream and—when possible—as a parsed object or array. Let's start by looking at a request with form fields made available as an array. + +Parsed Body +~~~~~~~~~~~ + +When the request contains form fields (i.e., the ``Content-type`` header is either ``application/x-www-form-urlencoded`` or ``multipart/form-data``), the request makes the form fields available via the ``getParsedBody`` method. This provides access to the fields without needing to rely on the ``$_POST`` superglobal. + +Given this request: + +.. code-block:: http + + POST /cats/ HTTP/1.1 + Host: example.com + Content-type: application/x-www-form-urlencoded + Content-length: 23 + + name=Molly&color=Calico + +We can read the parsed body like this: + +.. code-block:: php + + function ($request, $response, $next) { + + $cat = $request->getParsedBody(); + /* + Array + ( + [name] => Molly + [color] => calico + ) + */ + + } + +Body Stream +~~~~~~~~~~~ + +For other content types, use the ``getBody`` method to get a stream containing the contents of request entity body. + +Using a JSON representation of our cat, we can make a request like this: + +.. code-block:: http + + POST /cats/ HTTP/1.1 + Host: example.com + Content-type: application/json + Content-length: 46 + + { + "name": "Molly", + "color": "Calico" + } + +We can read and parse the JSON body, and even provide it **as** the parsedBody for later middleware like this: + +.. code-block:: php + + function ($request, $response, $next) { + + $cat = json_decode((string) $request->getBody()); + /* + stdClass Object + ( + [name] => Molly + [color] => calico + ) + */ + + $request = $request->withParsedBody($cat); + + } + + +Because the entity body of a request or response can be very large, PSR-7_ represents bodies as streams using the ``Psr\Htt\Message\StreamInterface`` (see PSR-7_ Section 1.3). + +The JSON example cast the stream to a string, but we can also do things like copy the stream to a local file: + +.. code-block:: php + + function ($request, $response, $next) { + + // Store the body to a temp file. + $chunkSize = 2048; // Number of bytes to read at once. + $localPath = tempnam(sys_get_temp_dir(), "body"); + $h = fopen($localPath, "wb"); + $body = $rqst->getBody(); + while (!$body->eof()) { + fwrite($h, $body->read($chunkSize)); + } + fclose($h); + + } + +Paramters +^^^^^^^^^ + +PSR-7_ eliminates the need to read from many of the superglobals. We already saw how ``getParsedBody`` takes the place of reading directly from ``$_POST`` and ``getQueryParams`` replaces reading from ``$_GET``. Here are some other ``ServerRequestInterface`` methods with **brief** descriptions. Please see PSR-7_ for full details, particularly for ``getUploadedFiles``. + +.. list-table:: + :header-rows: 1 + + * - Method + - Replaces + - Note + * - getServerParams + - $_SERVER + - Data related to the request environment + * - getCookieParams + - $_COOKIE + - Compatible with the structure of $_COOKIE + * - getQueryParams + - $_GET + - Deserialized query string arguments, if any + * - getParsedBody + - $_POST + - Request body as an object or array + * - getUploadedFiles + - $_FILES + - Normalized tree of file upload data + +Attributes +^^^^^^^^^^ + +``ServerRequestInterface`` provides another useful feature called "attributes". Attributes are key-value pairs associated with the request that can be, well, pretty much anything. + +The primary use for attributes in WellRESTed is to provide access to path variables when using `template routes`_ or `regex routes`_. + +For example, the template route ``/cats/{name}`` matches routes such as ``/cats/Molly`` and ``/cats/Oscar``. When the route is dispatched, the router takes the portion of the actual request path matched by ``{name}`` and provides it as an attribute. + +For a request to ``/cats/Rufus``: + +.. code-block:: php + + function ($request, $response, $next) { + + $name = $request->getAttribute("name"); + // "Rufus" + + } + +When calling ``getAttribute``, you can optionally provide a default value as the second argument. The value of this argument will be returned if the request has no attribute with that name. + +.. code-block:: php + + function ($request, $response, $next) { + + // Request has no attribute "dog" + $name = $request->getAttribute("dog", "Bear"); + // "Bear" + + } + +Middleware can also use attributes as a way to provide extra information to subsequent middleware. For example, an authorization middleware could obtain an object representing a user and store is as the "user" attribute which later middleware could read. + +.. code-block:: php + + $auth = function ($request, $response, $next) { + + try { + $user = readUserFromCredentials($request); + } catch (NoCredentialsSupplied $e) { + return $response->withStatus(401); + } catch (UserNotAllowedHere $e) { + return $response->withStatus(403); + } + + // Store this as an attribute. + $request = $request->withAttribute("user", $user); + + // Call $next, passing the request with the added attribute. + return $next($request, $response); + + }; + + $subsequent = function ($request, $response, $next) { + + // Read the "user" attribute added by a previous middleware. + $user = $request->getAttribute("user"); + + // Do something with $user + + } + + $server = new \WellRESTed\Server(); + $server->add($auth); + $server->add($subsequent); // Must be added AFTER $auth to get "user" + $server->respond(); + +Finally, attributes provide a nice way to provide a `dependency injection`_ container for to your middleware. + +Requests +-------- + +Coming soon! + +.. _PSR-7: http://www.php-fig.org/psr/psr-7/ +.. _Getting Started: getting-started.html +.. _Middleware: middleware.html +.. _template routes: router.html#template-routes +.. _regex routes: router.html#regex-routes +.. _dependency injection: dependency-injection.html diff --git a/docs/source/overview.rst b/docs/source/overview.rst index 37d0f41..8332d98 100644 --- a/docs/source/overview.rst +++ b/docs/source/overview.rst @@ -47,4 +47,3 @@ Licensed using the `MIT license `_. THE SOFTWARE. .. _Composer: http://getcomposer.org/ -.. _PHP cURL: http://php.net/manual/en/book.curl.php