What is middleware?
What exactly is middleware? In real application when the request comes to the server it has to go through the different request handlers. For example, it could be authentication, validation, ACL, logger, caching and so on. Consider the request-response circle as an onion and when a request comes in, it has to go through the different layers of this onion, to get to the core. And every middleware is a layer of the onion. It is a callable object that receives the request and can modify it (or modify the response) before passing it to the next middleware in the chain (to the next layer of the onion).
Let’s start with a simple server example:
This code represents a dummy server, that returns
Hello world responses to all incoming requests. But for our needs it is OK. Now, what if we want to log all incoming requests? So, let’s add a line with
When we run our server and make a request to it (I use Curl in terminal) we see a log output on the server console:
And now we can extract this logging logic into the logging middleware. ReactPHP middleware:
- is a
ServerRequestInterfaceas the first argument and optional callable as the second one
- returns a
ResponseInterface(or any promise which can be consumed by
Promise\resolveresolving to a
$next($request)to continue chaining to the next middleware or returns explicitly to abort the chain
So, following these rules a logging middleware function will look like this:
The server constructor can accept an array of callables, where we can pass our middleware:
This code does the same logging. When the request comes in our first
$loggingMiddleware is executed. It prints out a log message to the server console and then passes a request object to the next middleware which returns a response and ends the chain. This is a very simple example and doesn’t show the real power of middleware when you have some complicated logic, where you modify request and response objects during the request-response life-cycle.
Attaching middleware to a server
For better understanding middleware we can use a simple video streaming server from one of the previous articles. Here is the source code for it:
How does it work? When you open your browser on URL
127.0.0.1:8000 and don’t provide any query params the server returns a blank page with
Video streaming server message. To open a video in the browser you can specify
video query param like this:
http://127.0.0.1:8000/?video=bunny.mpg. If there is a file called
bunny.mpg in server
media directory, the server starts streaming this file. Very simple.
getMimeTypeByExtension()is a custom function to detect file MIME type by its extension. You can find its implementation in Video streaming server article.
You can notice that this request handling logic can be separated into three parts:
- a plain text response, when there is no
- 404 response, when a requested file is not found.
- a streaming response.
These three parts are good candidates for middleware. Let’s start with the first one:
$queryParamMiddleware. It simply checks query params. If
video param is present it passes the request to the next middleware, otherwise, it stops the chain and returns a plain text response:
Then, if the request has reached the second middleware, that means that we have
video query param. So, we can check if a specified file exists on the server. If not we return 404 response, otherwise we continue chaining to the next middleware:
fopenhere to check if a file exists.
file_exists()call is blocking and may lead to race conditions, so it is not recommended to use it in an asynchronous application.
And the last third middleware opens a stream, wraps it into ReactPHP
\React\Stream\ReadableResourceStream object and returns it as a response body. This middleware doesn’t accept
$next argument because it is the last middleware in our chain. But, notice that it
uses an event loop to create
Now, having all these three middleware we can provide them to the
Server constructor as an array:
The code looks much cleaner than having all this request handling logic in one callback. Our request-response cycle consists of three layers of the middleware onion:
When the request comes in it has to go through all these layers. And each layer decides whether to continue chaining or we are done and a response should be returned.
When middleware becomes too complicated it can be extracted to its own class, that implements magic
__invoke() method. This allows customizing middleware on the fly.
PHP community has already standardized middleware under PSR-7: HTTP message interfaces, but ReactPHP doesn’t provide any interfaces for middleware implementations. So, don’t confuse PSR-7 middleware and ReactPHP HTTP middleware. As you can notice ReactPHP middleware doesn’t accept the response object, but only request:
So, it may look like there is no way to modify the response. But it is not exactly the truth. It may look a little tricky, but you can. Let me show how.
In this example we are going to add some headers to the response. We create a server with an array of two middleware: the first one is going to add a custom header to the resulting response, and the second one simply returns the response:
So, how can we modify the response returned by the next middleware? We know that the
$next variable represents the next middleware in the chain, so we can explicitly call it and pass a request object to it:
In this snippet, the first middleware simply continues the chain and returns the response from the next middleware. We can assign the
Response from the next middleware to a variable, modify it and then return a modified response:
And now we can add our custom
X-Custom header with
foo value and check if everything works as expected:
Curl in terminal with
-i flag to receive the response with headers. You scan ee that the server returns a response from the second middleware with
Hello world message. And also response headers contain our
Middleware under the hood of the Server
ReactPHP HTTP Component comes with three middleware implementations:
All of them under the hood are included in
Server class, so there is no need to explicitly pass them. Why these particular middleware? Because they are required to match PHP’s request behavior.
Limiting concurrent requests
LimitConcurrentRequestsMiddleware can be used to limit how many next handlers can be executed concurrently.
Server class tries to determine this number automatically according to your
php.ini settings (it uses
post_max_size values). But a predefined maximum number of pending handlers is
100. This middleware has its own queue. If the number of pending handlers exceeds the allowed limit, the request goes to the queue and its streaming body is paused. Once one of the pending requests is done, the middleware fetches the oldest pending request from the queue and resumes its streaming body.
To demonstrate how it works, we can attach a timer for 2 seconds in one of the middleware to simulate a busy server:
Then when running two parallel Curl requests we can see that they both are resolved with a delay of 2 seconds:
And now see what happens if we use
LimitConcurrentRequestsMiddleware and set the limit to 1:
The requests are queued. While the first request is being processed, the second one is stored in the middleware’s queue. After two seconds, when the first request is done, the second one is dispatched from the queue and then processed. In this way, we have actually removed concurrency and incoming requests are processed by server simply one by one.
Buffering request body
When POST or PUT request reaches HTTP server we can get access to its body by calling
$request->getParsedBody(). This method returns an associative array that represents a parsed request body. Under the hood, the server receives a request which body is a stream. So, behind the scenes,
React\Http\Server at first uses
RequestBodyBufferMiddleware to buffer this stream in memory. The request is buffered until its body end has been reached and then the next middleware in the chain will be called with a complete, buffered request. And the next middleware is
Parsing request body
When a request body is parsed it goes to
RequestBodyParserMiddleware which actually parses form fields and file uploads. This middleware makes it possible to receive request params, when you call
To check how it works we again use Curl from terminal and provide some form data
curl 127.0.0.1:8000 --data "param1=value1¶m2=value2":
Under the hood this middleware parses requests that use
Content-Type: application/x-www-form-urlencoded or
Content-Type: multipart/form-data headers. That allows us to get access to both request body and uploads.
To get an instance of the uploaded file you can
$request->getUploadedFiles(). This method returns an array of
Psr\Http\Message\UploadedFileInterface instances. Each upload from the submitted request is represented by its own instance:
For example, we can upload a simple text file from terminal
curl 127.0.0.1:8000 -F "email@example.com" (flag
This code simply returns an uploaded file name as a response.
To store an uploaded file use
UploadedFileInterface::getStream() method, which returns an instance of the
Psr\Http\Message\StreamInterface. To get the contents of the uploaded file we can cast this object to a string. For example, this snippet opens a writable stream and stores the contents of the uploaded file in
moveTo()method that can be used exactly to store an upload. But this method by its nature is going to be blocking (as a recommended alternative to calling
move_uploaded_file()), so it can’t be used in an asynchronous application. That is the reason why we have to use
A chain of middleware between the server and your application is a powerful tool to customize the way how your request and response behave. In addition to already built-in
Server middleware, there is a list of third-party middleware created by ReactPHP community.
You can find examples from this article on GitHub.
This article is a part of the ReactPHP Series.
Event-driven PHP with ReactPHP
The book about asynchronous PHP that you NEED!
A complete guide to writing asynchronous applications with ReactPHP. Discover event-driven architecture and non-blocking I/O with PHP!Minimum price: 5.99$