Request > getBody > getSize Returns null

Good morning,

I would like to comment something I found, I’m not sure if I missed some steps, or, is something already known. I haven’t found info about this subject.

I’m currently migrating from Slim v3 into v4 using slim/psr7 to create the requests. Before now I was using the getSize function in the PSR7 StreamInterface to measure the requests size, but, when changed to the new version it didn’t work as before, I can’t get the request size from this function, it always return null. After digging a bit the code I have found that the base PHP resource on my requests has changed from previous versions, the library slim/psr7 creates a request with a php://input resource which doesn’t allow the fstat operation done in the Stream class of the same library, so, it can’t get the size from the resource. Before now it was php://temp w+ which allows the fstat function.

slim/psr7 Body creation
slim/psr7 Stream getSize

POC

<?php
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\Factory\AppFactory;

require __DIR__ . '/../vendor/autoload.php';

$app = AppFactory::create();

$app->post('/hello/{name}', function (Request $request, Response $response, array $args) {
    $name = $args['name'];

    $result = $request->getBody()->getSize() === null
        ? 'getSize returns null'
        : 'getSize returns ' . $request->getBody()->getSize();

    $response->getBody()->write($result);

    return $response;
});

$app->run();

These are the dependencies

{
    "require": {
        "slim/slim": "^4.12",
        "slim/psr7": "^1.6"
    }
}

Finally, I call this endpoint with

POST http://localhost:8010/hello/test
content-type: application/json
Accept: text/plain

{"test": "test"}

Thank you in advance!

I just have tested this behavior also with other PSR-7 implementations, such as Nyholm and I get the same result (null). Nyholm also uses php://input within the ServerRequestCreator::fromGlobals method.

Indeed, it looks like a php://input resource / stream does not provide support for fstat. See here:

I assume that all PSR-7 implementations are affected by this bug, and I’m a bit confused
about it.

Hello,

Thanks for your quick response, I have tried Guzzle implementation also, and it still uses the php://temp writable resource, Laminas uses php://input, it depends on their implementation.

By now, my intention is to continue with the Slim/Psr-7 implementation, I was thinking about using the Content-Length header, I suppose it will be mostly the same.

You can also try this workaround:

$realSize = strlen((string)$request->getBody());

Hi,

Sorry I can’t answer until now. It was my first thought, but, the request body is being read into memory, on large requests, depending on the usage it can duplicate the memory needed.

Thank you so much for the proposal, have a nice weekend!

Indeed, this is not the best solution, because it reads the request into memory.
Another, more memory efficient way would be to read the last position of the stream:

$size = $request->getBody()->tell();

I just found out, that when you send POST request as multipart/form-data the input stream length is always null, because PHP itself has already processed that stream to populate the $_POST variable. Just in this case, the request size would be null anyway.

Hi,

Thanks!, something like this can measure this kind of resource and leave it as it was… It works with php://input

    if($request->getBody()->isSeekable()) {
        $currentPosition = $request->getBody()->tell();
        $request->getBody()->seek(0, SEEK_END);
        $sizeInBytes = $request->getBody()->tell();
        $request->getBody()->seek($currentPosition);
    }

I have edited the post, to find the last position is 0, SEEK_END, if the post body is empty, seek with position -1 will fail

1 Like

I havent tested everything already, but this could be an alternative way to get the size on the Stream class…

    public function getSize(): ?int
    {
        if ($this->stream && !$this->size && $this->isSeekable() && !$this->isPipe()) {
            $currentPosition = ftell($this->stream);
            fseek($this->stream, 0, SEEK_END);
            $endPosition = ftell($this->stream);
            fseek($this->stream, $currentPosition, SEEK_SET);
            if (false !== $endPosition) {
                $this->size = $endPosition;
            }
        }
        return $this->size;
    }