Slim 4 Content Length Middleware position

Hello,

In the docs, it is written :

This middleware should be placed on the center of the middleware stack so it gets executed last.

And the usage example puts it just after AppFactory::create().

But shouldn’t it be positioned after the other middlewares and be executed first instead ? By being executed first, that also means it will be the last middleware by which the response will go through. On the other hand, by being executed last, it will be the first middleware by which the response will go through, it will set the Content-Length header but if any of the next outer middlewares modify the response and its size change, the Content-Length header value will be erroneous.

I’m not planning to use the middleware, but just curious and trying to understand if I’m missing something.

Thanks !

Hi @Khivar The reason is that the Slim middleware is LIFO and not FIFO. Read more

Hello, thanks for the answer. I understood that it was LIFO and that’s why I think it doesn’t work the way it is documented. I will make an example which follows the information from your link :

$contentLengthMiddleware = new ContentLengthMiddleware();
$app->add($contentLengthMiddleware);

$app->add(function (Request $request, RequestHandler $handler) {
    $response = $handler->handle($request);
    $existingContent = (string) $response->getBody();

    $response = new Response();
    $response->getBody()->write('BEFORE '.$existingContent);
    return $response;
});

Will execute the middlewares in the order :

  1. Prepend middleware
  2. ContentLengthMiddleware

So, as per the documentation the ContentLengthMiddleware is indeed placed first in the code and executed last. But that won’t work, the response will not have the Content Length header set, because the response will exit first through the ContentLengthMiddleware which will set the Content Length header and then through the Prepend middleware which will create a new response. So if any middleware modify the response in any way it will not work. If you prepend something to the response, you will not have the Content Length header set, and if your middleware append something to the response via $response->getBody()->write() it will not display the content added due to the response being truncated because of the Content Length header previously set. For it to work, the ContentLengthMiddleware should be executed first and not last, so the request will go through it first and the response will go through it last to set the Content Length header, and so be positioned last in the code. Like this :

$app->add(function (Request $request, RequestHandler $handler) {
    $response = $handler->handle($request);
    $existingContent = (string) $response->getBody();

    $response = new Response();
    $response->getBody()->write('BEFORE '.$existingContent);
    return $response;
});

$contentLengthMiddleware = new ContentLengthMiddleware();
$app->add($contentLengthMiddleware);

The problem here is this line:

$app->add(function (Request $request, RequestHandler $handler) {
    $response = $handler->handle($request); // <-- this invokes the next middleware handler
   // ...
}

If you call $response = $handler->handle($request); then the next middleware will be invoked, in this case the ContentLengthMiddleware. The ContentLengthMiddleware retrieves the content size of the response body, before you append the new content. This is the reason for the incorrect Content-Length value.

To fix this just move the ContentLengthMiddleware after your action handler:

$app->add(function (Request $request, RequestHandler $handler) {
  // ...
};

$app->add(ContentLengthMiddleware::class);

Indeed, the ContentLengthMiddleware doesn’t work if you put it at the first position and put a second outgoing middleware after it.

Ok thanks so that confirms the thoughts in my initial post and that the documentation is wrong. The ContentLengthMiddleware must be executed first and not last and must be declared, code wise, after all middlewares which can modify the response and not be the declared as the first middleware. What is the way to fix the documentation, I don’t see it in the github repository ?

I guess the documentation is correct, because your example is a special use case. In the most cases you would not create a your own Response object because the Slim Response creator will create and pass the response object for you into the action handler.

As we just agreed, the way stated in the documentation doesn’t work in all cases. There is literally no advantage to executing the content length middleware last but there are drawbacks. Executing the Content Length middleware first will work in all cases and has no drawback. The “special” use case I used is a middleware example coming directly from the documentation itself. How can the documentation be correct if it’s not sure to work, and if you do the opposite of what is stated then it will work all the time ? It boggles my mind.

TBH, the LIFO middleware in Slim still confuses me :wink: even after years of using Slim now. Feel free to create a PR here.