Skip to content

Commit 1ede4e0

Browse files
committed
Add always seekable body plugin
1 parent 109db1d commit 1ede4e0

File tree

2 files changed

+163
-0
lines changed

2 files changed

+163
-0
lines changed
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
<?php
2+
3+
namespace spec\Http\Client\Common\Plugin;
4+
5+
use Http\Client\Common\Plugin;
6+
use Http\Client\Common\Plugin\AlwaysSeekableBodyPlugin;
7+
use Http\Client\Promise\HttpFulfilledPromise;
8+
use Http\Message\Stream\BufferedStream;
9+
use PhpSpec\ObjectBehavior;
10+
use Prophecy\Argument;
11+
use Psr\Http\Message\RequestInterface;
12+
use Psr\Http\Message\ResponseInterface;
13+
use Psr\Http\Message\StreamInterface;
14+
15+
class AlwaysSeekableBodyPluginSpec extends ObjectBehavior
16+
{
17+
public function it_is_initializable()
18+
{
19+
$this->shouldHaveType(AlwaysSeekableBodyPlugin::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(RequestInterface $request, ResponseInterface $response, StreamInterface $responseStream, StreamInterface $requestStream)
28+
{
29+
$next = function () use ($response) {
30+
return new HttpFulfilledPromise($response->getWrappedObject());
31+
};
32+
33+
$request->getBody()->shouldBeCalled()->willReturn($requestStream);
34+
$requestStream->isSeekable()->shouldBeCalled()->willReturn(true);
35+
36+
$response->getBody()->shouldBeCalled()->willReturn($responseStream);
37+
$responseStream->isSeekable()->shouldBeCalled()->willReturn(false);
38+
$responseStream->getSize()->willReturn(null);
39+
40+
$response->withBody(Argument::type(BufferedStream::class))->shouldBeCalled()->willReturn($response);
41+
42+
$this->handleRequest($request, $next, function () {});
43+
}
44+
45+
public function it_does_not_decorate_response_body_if_seekable(RequestInterface $request, ResponseInterface $response, StreamInterface $responseStream, StreamInterface $requestStream)
46+
{
47+
$next = function () use ($response) {
48+
return new HttpFulfilledPromise($response->getWrappedObject());
49+
};
50+
51+
$request->getBody()->shouldBeCalled()->willReturn($requestStream);
52+
$requestStream->isSeekable()->shouldBeCalled()->willReturn(true);
53+
54+
$response->getBody()->shouldBeCalled()->willReturn($responseStream);
55+
$responseStream->isSeekable()->shouldBeCalled()->willReturn(true);
56+
$responseStream->getSize()->willReturn(null);
57+
58+
$response->withBody(Argument::type(BufferedStream::class))->shouldNotBeCalled();
59+
60+
$this->handleRequest($request, $next, function () {});
61+
}
62+
63+
public function it_decorate_request_body_if_not_seekable(RequestInterface $request, ResponseInterface $response, StreamInterface $responseStream, StreamInterface $requestStream)
64+
{
65+
$next = function () use ($response) {
66+
return new HttpFulfilledPromise($response->getWrappedObject());
67+
};
68+
69+
$response->getBody()->willReturn($responseStream);
70+
$responseStream->isSeekable()->willReturn(true);
71+
72+
$request->getBody()->shouldBeCalled()->willReturn($requestStream);
73+
$requestStream->isSeekable()->shouldBeCalled()->willReturn(false);
74+
$requestStream->getSize()->willReturn(null);
75+
76+
$request->withBody(Argument::type(BufferedStream::class))->shouldBeCalled();
77+
78+
$this->handleRequest($request, $next, function () {});
79+
}
80+
81+
public function it_does_not_decorate_request_body_if_seekable(RequestInterface $request, ResponseInterface $response, StreamInterface $responseStream, StreamInterface $requestStream)
82+
{
83+
$next = function () use ($response) {
84+
return new HttpFulfilledPromise($response->getWrappedObject());
85+
};
86+
87+
$response->getBody()->willReturn($responseStream);
88+
$responseStream->isSeekable()->willReturn(true);
89+
90+
$request->getBody()->shouldBeCalled()->willReturn($requestStream);
91+
$requestStream->isSeekable()->shouldBeCalled()->willReturn(true);
92+
$requestStream->getSize()->willReturn(null);
93+
94+
$request->withBody(Argument::type(BufferedStream::class))->shouldNotBeCalled();
95+
96+
$this->handleRequest($request, $next, function () {});
97+
}
98+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Http\Client\Common\Plugin;
6+
7+
use Http\Client\Common\Plugin;
8+
use Http\Message\Stream\BufferedStream;
9+
use Http\Promise\Promise;
10+
use Psr\Http\Message\RequestInterface;
11+
use Psr\Http\Message\ResponseInterface;
12+
use Symfony\Component\OptionsResolver\OptionsResolver;
13+
14+
/**
15+
* Decorate the body of the request and the response if it's not seekable by using Http\Message\Stream\BufferedStream
16+
*
17+
* @author Joel Wurtz <joel.wurtz@gmail.com>
18+
*/
19+
final class AlwaysSeekableBodyPlugin implements Plugin
20+
{
21+
private $useFileBuffer;
22+
23+
private $memoryBufferSize;
24+
25+
/**
26+
* @param array $config {
27+
*
28+
* @var bool $use_file_buffer Whether this plugin should use a file as a buffer if the stream is too big, defaults to true
29+
* @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)
30+
* }
31+
*/
32+
public function __construct(array $config = [])
33+
{
34+
$resolver = new OptionsResolver();
35+
$resolver->setDefaults([
36+
'use_file_buffer' => true,
37+
'memory_buffer_size' => 2097152
38+
]);
39+
$resolver->setAllowedTypes('use_file_buffer', 'bool');
40+
$resolver->setAllowedTypes('memory_buffer_size', 'int');
41+
42+
$options = $resolver->resolve($config);
43+
44+
$this->useFileBuffer = $options['use_file_buffer'];
45+
$this->memoryBufferSize = $options['memory_buffer_size'];
46+
}
47+
48+
/**
49+
* {@inheritdoc}
50+
*/
51+
public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise
52+
{
53+
if (!$request->getBody()->isSeekable()) {
54+
$request = $request->withBody(new BufferedStream($request->getBody(), $this->useFileBuffer, $this->memoryBufferSize));
55+
}
56+
57+
return $next($request)->then(function (ResponseInterface $response) {
58+
if ($response->getBody()->isSeekable()) {
59+
return $response;
60+
}
61+
62+
return $response->withBody(new BufferedStream($response->getBody(), $this->useFileBuffer, $this->memoryBufferSize));
63+
});
64+
}
65+
}

0 commit comments

Comments
 (0)