Skip to content

Commit 80e54b5

Browse files
committed
Add test for stream, add specific exception for stream, better timeout option
1 parent b995147 commit 80e54b5

File tree

8 files changed

+206
-13
lines changed

8 files changed

+206
-13
lines changed

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,14 @@ Here is the list of available options:
4242
* remote_socket: Specify the remote socket where the library should send the request to
4343

4444
Can be a tcp remote : tcp://hostname:port
45+
4546
Can be a unix remote : unix://hostname:port
4647

4748
Do not use a tls / ssl scheme, this is handle by the ssl option.
49+
4850
If not set, the client will try to determine it from the request uri or host header.
4951

50-
* timeout : Timeout for writing request or reading response on the remote
52+
* timeout : Timeout in __milliseconds__ for writing request and reading response on the remote
5153
* ssl : Activate or deactivate the ssl / tls encryption
5254
* stream_context_options : Custom options for the context of the stream, same as [PHP stream context options](http://php.net/manual/en/context.php)
5355

src/Exception/StreamException.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
namespace Http\Socket\Exception;
4+
5+
use Http\Client\Exception;
6+
7+
class StreamException extends \RuntimeException implements Exception
8+
{
9+
}
10+

src/Exception/TimeoutException.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
3+
namespace Http\Socket\Exception;
4+
5+
class TimeoutException extends StreamException
6+
{
7+
}
8+

src/ResponseReader.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,6 @@ protected function readResponse(RequestInterface $request, $socket)
3535
{
3636
$headers = [];
3737
$reason = null;
38-
$status = null;
39-
$protocol = null;
4038

4139
while (($line = fgets($socket)) !== false) {
4240
if (rtrim($line) === '') {
@@ -45,6 +43,12 @@ protected function readResponse(RequestInterface $request, $socket)
4543
$headers[] = trim($line);
4644
}
4745

46+
$metadatas = stream_get_meta_data($socket);
47+
48+
if (array_key_exists('timed_out', $metadatas) && true === $metadatas['timed_out']) {
49+
throw new NetworkException("Error while reading response, stream timed out", $request);
50+
}
51+
4852
$parts = explode(' ', array_shift($headers), 3);
4953

5054
if (count($parts) <= 1) {

src/SocketHttpClient.php

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -88,13 +88,14 @@ protected function createSocket(RequestInterface $request, $remote, $useSsl)
8888
{
8989
$errNo = null;
9090
$errMsg = null;
91-
92-
$socket = @stream_socket_client($remote, $errNo, $errMsg, $this->config['timeout'], STREAM_CLIENT_CONNECT, $this->config['stream_context']);
91+
$socket = @stream_socket_client($remote, $errNo, $errMsg, floor($this->config['timeout'] / 1000), STREAM_CLIENT_CONNECT, $this->config['stream_context']);
9392

9493
if (false === $socket) {
9594
throw new NetworkException($errMsg, $request);
9695
}
9796

97+
stream_set_timeout($socket, floor($this->config['timeout'] / 1000), $this->config['timeout'] % 1000);
98+
9899
if ($useSsl) {
99100
if (false === @stream_socket_enable_crypto($socket, true, $this->config['ssl_method'])) {
100101
throw new NetworkException(sprintf('Cannot enable tls: %s', error_get_last()['message']), $request);
@@ -125,13 +126,11 @@ protected function configure(array $config = [])
125126
{
126127
$resolver = new OptionsResolver();
127128
$resolver->setDefaults($this->config);
128-
$resolver->setDefault('stream_context', function (Options $options, $previousValue) {
129+
$resolver->setDefault('stream_context', function (Options $options) {
129130
return stream_context_create($options['stream_context_options'], $options['stream_context_param']);
130131
});
131132

132-
$resolver->setDefault('timeout', function (Options $options, $previousValue) {
133-
return ini_get('default_socket_timeout');
134-
});
133+
$resolver->setDefault('timeout', ini_get('default_socket_timeout') * 1000);
135134

136135
$resolver->setAllowedTypes('stream_context_options', 'array');
137136
$resolver->setAllowedTypes('stream_context_param', 'array');

src/Stream.php

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
namespace Http\Socket;
44

5+
use Http\Socket\Exception\StreamException;
6+
use Http\Socket\Exception\TimeoutException;
57
use Psr\Http\Message\StreamInterface;
68

79
/**
@@ -117,15 +119,15 @@ public function isSeekable()
117119
*/
118120
public function seek($offset, $whence = SEEK_SET)
119121
{
120-
throw new \RuntimeException("This stream is not seekable");
122+
throw new StreamException("This stream is not seekable");
121123
}
122124

123125
/**
124126
* {@inheritDoc}
125127
*/
126128
public function rewind()
127129
{
128-
throw new \RuntimeException("This stream is not seekable");
130+
throw new StreamException("This stream is not seekable");
129131
}
130132

131133
/**
@@ -141,7 +143,7 @@ public function isWritable()
141143
*/
142144
public function write($string)
143145
{
144-
throw new \RuntimeException("This stream is not writable");
146+
throw new StreamException("This stream is not writable");
145147
}
146148

147149
/**
@@ -162,11 +164,20 @@ public function read($length)
162164
}
163165

164166
if ($this->getSize() < ($this->readed + $length)) {
165-
throw new \RuntimeException("Cannot read more than %s", $this->getSize() - $this->readed);
167+
throw new StreamException("Cannot read more than %s", $this->getSize() - $this->readed);
168+
}
169+
170+
if ($this->getSize() === $this->readed) {
171+
return "";
166172
}
167173

168174
// Even if we request a length a non blocking stream can return less data than asked
169175
$read = fread($this->socket, $length);
176+
177+
if ($this->getMetadata('timed_out')) {
178+
throw new TimeoutException("Stream timed out while reading data");
179+
}
180+
170181
$this->readed += strlen($read);
171182

172183
return $read;

tests/SocketHttpClientTest.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,4 +182,15 @@ public function testNetworkExceptionOnSslError()
182182
$client = $this->createClient(['remote_socket' => '127.0.0.1:19999', 'ssl' => true]);
183183
$client->get('/', []);
184184
}
185+
186+
/**
187+
* @expectedException \Http\Client\Exception\NetworkException
188+
*/
189+
public function testNetworkExceptionOnTimeout()
190+
{
191+
$this->startServer('tcp-server');
192+
193+
$client = $this->createClient(['timeout' => 10]);
194+
$client->get('http://php.net', []);
195+
}
185196
}

tests/StreamTest.php

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
<?php
2+
3+
namespace Http\Socket\Tests;
4+
5+
use Http\Socket\Stream;
6+
7+
class StreamTest extends \PHPUnit_Framework_TestCase
8+
{
9+
public function createSocket($body, $useSize = true)
10+
{
11+
$socket = fopen('php://memory', 'rw');
12+
fwrite($socket, $body);
13+
fseek($socket, 0);
14+
15+
return new Stream($socket, $useSize ? strlen($body) : null);
16+
}
17+
18+
public function testToString()
19+
{
20+
$stream = $this->createSocket("Body");
21+
22+
$this->assertEquals("Body", $stream->__toString());
23+
$stream->close();
24+
}
25+
26+
public function testSubsequentCallIsEmpty()
27+
{
28+
$stream = $this->createSocket("Body");
29+
30+
$this->assertEquals("Body", $stream->getContents());
31+
$this->assertEmpty($stream->getContents());
32+
$stream->close();
33+
}
34+
35+
public function testDetach()
36+
{
37+
$stream = $this->createSocket("Body");
38+
$socket = $stream->detach();
39+
40+
$this->assertTrue(is_resource($socket));
41+
$this->assertNull($stream->detach());
42+
}
43+
44+
public function testTell()
45+
{
46+
$stream = $this->createSocket("Body");
47+
48+
$this->assertEquals(0, $stream->tell());
49+
$this->assertEquals("Body", $stream->getContents());
50+
$this->assertEquals(4, $stream->tell());
51+
}
52+
53+
public function testEof()
54+
{
55+
$socket = fopen('php://memory', 'rw+');
56+
fwrite($socket, "Body");
57+
fseek($socket, 0);
58+
$stream = new Stream($socket);
59+
60+
$this->assertEquals("Body", $stream->getContents());
61+
fwrite($socket, "\0");
62+
$this->assertTrue($stream->eof());
63+
$stream->close();
64+
}
65+
66+
public function testNotSeekable()
67+
{
68+
$stream = $this->createSocket("Body");
69+
70+
$this->assertFalse($stream->isSeekable());
71+
72+
try {
73+
$stream->seek(0);
74+
} catch (\Exception $e) {
75+
$this->assertInstanceOf('Http\Socket\Exception\StreamException', $e);
76+
}
77+
}
78+
79+
public function testNoRewing()
80+
{
81+
$stream = $this->createSocket("Body");
82+
83+
try {
84+
$stream->rewind();
85+
} catch (\Exception $e) {
86+
$this->assertInstanceOf('Http\Socket\Exception\StreamException', $e);
87+
}
88+
}
89+
90+
public function testNotWritable()
91+
{
92+
$stream = $this->createSocket("Body");
93+
94+
$this->assertFalse($stream->isWritable());
95+
96+
try {
97+
$stream->write("Test");
98+
} catch (\Exception $e) {
99+
$this->assertInstanceOf('Http\Socket\Exception\StreamException', $e);
100+
}
101+
}
102+
103+
public function testIsReadable()
104+
{
105+
$stream = $this->createSocket("Body");
106+
107+
$this->assertTrue($stream->isReadable());
108+
}
109+
110+
public function testTimeout()
111+
{
112+
$socket = fsockopen("php.net", 80);
113+
socket_set_timeout($socket, 0, 100);
114+
115+
$stream = new Stream($socket);
116+
117+
try {
118+
$stream->getContents();
119+
} catch (\Exception $e) {
120+
$this->assertInstanceOf('Http\Socket\Exception\TimeoutException', $e);
121+
}
122+
}
123+
124+
public function testMetadatas()
125+
{
126+
$stream = $this->createSocket("Body", false);
127+
128+
$this->assertEquals("PHP", $stream->getMetadata("wrapper_type"));
129+
$this->assertEquals("MEMORY", $stream->getMetadata("stream_type"));
130+
$this->assertEquals("php://memory", $stream->getMetadata("uri"));
131+
$this->assertFalse($stream->getMetadata("timed_out"));
132+
$this->assertFalse($stream->getMetadata("eof"));
133+
$this->assertTrue($stream->getMetadata("blocked"));
134+
}
135+
136+
public function testClose()
137+
{
138+
$socket = fopen('php://memory', 'rw+');
139+
fwrite($socket, "Body");
140+
fseek($socket, 0);
141+
142+
$stream = new Stream($socket);
143+
$stream->close();
144+
145+
$this->assertFalse(is_resource($socket));
146+
}
147+
}
148+

0 commit comments

Comments
 (0)