Skip to content

Commit 9a2a4c1

Browse files
authored
Merge pull request #167 from php-http/feature/seekable-stream
Add always seekable body plugin
2 parents 2b8aa3c + a3c421c commit 9a2a4c1

File tree

5 files changed

+204
-0
lines changed

5 files changed

+204
-0
lines changed
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
3+
namespace spec\Http\Client\Common\Plugin;
4+
5+
use Http\Client\Common\Plugin;
6+
use Http\Client\Common\Plugin\RequestSeekableBodyPlugin;
7+
use Http\Message\Stream\BufferedStream;
8+
use PhpSpec\ObjectBehavior;
9+
use Prophecy\Argument;
10+
use Psr\Http\Message\RequestInterface;
11+
use Psr\Http\Message\StreamInterface;
12+
13+
class RequestSeekableBodyPluginSpec extends ObjectBehavior
14+
{
15+
public function it_is_initializable()
16+
{
17+
$this->shouldHaveType(RequestSeekableBodyPlugin::class);
18+
}
19+
20+
public function it_is_a_plugin()
21+
{
22+
$this->shouldImplement(Plugin::class);
23+
}
24+
25+
public function it_decorate_request_body_if_not_seekable(RequestInterface $request, StreamInterface $requestStream)
26+
{
27+
$request->getBody()->shouldBeCalled()->willReturn($requestStream);
28+
$requestStream->isSeekable()->shouldBeCalled()->willReturn(false);
29+
$requestStream->getSize()->willReturn(null);
30+
31+
$request->withBody(Argument::type(BufferedStream::class))->shouldBeCalled()->willReturn($request);
32+
33+
$this->handleRequest($request, PluginStub::next(), function () {});
34+
}
35+
36+
public function it_does_not_decorate_request_body_if_seekable(RequestInterface $request, StreamInterface $requestStream)
37+
{
38+
$request->getBody()->shouldBeCalled()->willReturn($requestStream);
39+
$requestStream->isSeekable()->shouldBeCalled()->willReturn(true);
40+
$requestStream->getSize()->willReturn(null);
41+
42+
$request->withBody(Argument::type(BufferedStream::class))->shouldNotBeCalled();
43+
44+
$this->handleRequest($request, PluginStub::next(), function () {});
45+
}
46+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?php
2+
3+
namespace spec\Http\Client\Common\Plugin;
4+
5+
use GuzzleHttp\Psr7\Request;
6+
use Http\Client\Common\Plugin;
7+
use Http\Client\Common\Plugin\ResponseSeekableBodyPlugin;
8+
use Http\Client\Promise\HttpFulfilledPromise;
9+
use Http\Message\Stream\BufferedStream;
10+
use PhpSpec\ObjectBehavior;
11+
use Prophecy\Argument;
12+
use Psr\Http\Message\ResponseInterface;
13+
use Psr\Http\Message\StreamInterface;
14+
15+
class ResponseSeekableBodyPluginSpec extends ObjectBehavior
16+
{
17+
public function it_is_initializable()
18+
{
19+
$this->shouldHaveType(ResponseSeekableBodyPlugin::class);
20+
}
21+
22+
public function it_is_a_plugin()
23+
{
24+
$this->shouldImplement(Plugin::class);
25+
}
26+
27+
public function it_decorate_response_body_if_not_seekable(ResponseInterface $response, StreamInterface $responseStream)
28+
{
29+
$next = function () use ($response) {
30+
return new HttpFulfilledPromise($response->getWrappedObject());
31+
};
32+
33+
$response->getBody()->shouldBeCalled()->willReturn($responseStream);
34+
$responseStream->isSeekable()->shouldBeCalled()->willReturn(false);
35+
$responseStream->getSize()->willReturn(null);
36+
37+
$response->withBody(Argument::type(BufferedStream::class))->shouldBeCalled()->willReturn($response);
38+
39+
$this->handleRequest(new Request('GET', '/'), $next, function () {});
40+
}
41+
42+
public function it_does_not_decorate_response_body_if_seekable(ResponseInterface $response, StreamInterface $responseStream)
43+
{
44+
$next = function () use ($response) {
45+
return new HttpFulfilledPromise($response->getWrappedObject());
46+
};
47+
48+
$response->getBody()->shouldBeCalled()->willReturn($responseStream);
49+
$responseStream->isSeekable()->shouldBeCalled()->willReturn(true);
50+
$responseStream->getSize()->willReturn(null);
51+
52+
$response->withBody(Argument::type(BufferedStream::class))->shouldNotBeCalled();
53+
54+
$this->handleRequest(new Request('GET', '/'), $next, function () {});
55+
}
56+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Http\Client\Common\Plugin;
6+
7+
use Http\Message\Stream\BufferedStream;
8+
use Http\Promise\Promise;
9+
use Psr\Http\Message\RequestInterface;
10+
11+
/**
12+
* Allow body used in request to be always seekable.
13+
*
14+
* @author Joel Wurtz <joel.wurtz@gmail.com>
15+
*/
16+
final class RequestSeekableBodyPlugin extends SeekableBodyPlugin
17+
{
18+
/**
19+
* {@inheritdoc}
20+
*/
21+
public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise
22+
{
23+
if (!$request->getBody()->isSeekable()) {
24+
$request = $request->withBody(new BufferedStream($request->getBody(), $this->useFileBuffer, $this->memoryBufferSize));
25+
}
26+
27+
return $next($request);
28+
}
29+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Http\Client\Common\Plugin;
6+
7+
use Http\Message\Stream\BufferedStream;
8+
use Http\Promise\Promise;
9+
use Psr\Http\Message\RequestInterface;
10+
use Psr\Http\Message\ResponseInterface;
11+
12+
/**
13+
* Allow body used in response to be always seekable.
14+
*
15+
* @author Joel Wurtz <joel.wurtz@gmail.com>
16+
*/
17+
final class ResponseSeekableBodyPlugin extends SeekableBodyPlugin
18+
{
19+
/**
20+
* {@inheritdoc}
21+
*/
22+
public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise
23+
{
24+
return $next($request)->then(function (ResponseInterface $response) {
25+
if ($response->getBody()->isSeekable()) {
26+
return $response;
27+
}
28+
29+
return $response->withBody(new BufferedStream($response->getBody(), $this->useFileBuffer, $this->memoryBufferSize));
30+
});
31+
}
32+
}

src/Plugin/SeekableBodyPlugin.php

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Http\Client\Common\Plugin;
6+
7+
use Http\Client\Common\Plugin;
8+
use Symfony\Component\OptionsResolver\OptionsResolver;
9+
10+
/**
11+
* @internal
12+
*/
13+
abstract class SeekableBodyPlugin implements Plugin
14+
{
15+
protected $useFileBuffer;
16+
17+
protected $memoryBufferSize;
18+
19+
/**
20+
* @param array $config {
21+
*
22+
* @var bool $use_file_buffer Whether this plugin should use a file as a buffer if the stream is too big, defaults to true
23+
* @var int $memory_buffer_size Max memory size in bytes to use for the buffer before it use a file, defaults to 2097152 (2 mb)
24+
* }
25+
*/
26+
public function __construct(array $config = [])
27+
{
28+
$resolver = new OptionsResolver();
29+
$resolver->setDefaults([
30+
'use_file_buffer' => true,
31+
'memory_buffer_size' => 2097152,
32+
]);
33+
$resolver->setAllowedTypes('use_file_buffer', 'bool');
34+
$resolver->setAllowedTypes('memory_buffer_size', 'int');
35+
36+
$options = $resolver->resolve($config);
37+
38+
$this->useFileBuffer = $options['use_file_buffer'];
39+
$this->memoryBufferSize = $options['memory_buffer_size'];
40+
}
41+
}

0 commit comments

Comments
 (0)