1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Http\Client\Socket; |
4
|
|
|
|
5
|
|
|
use Http\Client\Exception\NetworkException; |
6
|
|
|
use Http\Client\HttpClient; |
7
|
|
|
use Http\Discovery\MessageFactoryDiscovery; |
8
|
|
|
use Http\Message\ResponseFactory; |
9
|
|
|
use Psr\Http\Message\RequestInterface; |
10
|
|
|
use Symfony\Component\OptionsResolver\Options; |
11
|
|
|
use Symfony\Component\OptionsResolver\OptionsResolver; |
12
|
|
|
|
13
|
|
|
/** |
14
|
|
|
* Socket Http Client. |
15
|
|
|
* |
16
|
|
|
* Use stream and socket capabilities of the core of PHP to send HTTP requests |
17
|
|
|
* |
18
|
|
|
* @author Joel Wurtz <[email protected]> |
19
|
|
|
*/ |
20
|
|
|
class Client implements HttpClient |
21
|
|
|
{ |
22
|
|
|
use RequestWriter; |
23
|
|
|
use ResponseReader; |
24
|
|
|
|
25
|
|
|
private $config = [ |
26
|
|
|
'remote_socket' => null, |
27
|
|
|
'timeout' => null, |
28
|
|
|
'stream_context_options' => [], |
29
|
|
|
'stream_context_param' => [], |
30
|
|
|
'ssl' => null, |
31
|
|
|
'write_buffer_size' => 8192, |
32
|
|
|
'ssl_method' => STREAM_CRYPTO_METHOD_TLS_CLIENT, |
33
|
|
|
]; |
34
|
|
|
|
35
|
|
|
/** |
36
|
|
|
* Constructor. |
37
|
|
|
* |
38
|
|
|
* @param ResponseFactory $responseFactory Response factory for creating response |
39
|
|
|
* @param array $config { |
40
|
|
|
* |
41
|
|
|
* @var string $remote_socket Remote entrypoint (can be a tcp or unix domain address) |
42
|
|
|
* @var int $timeout Timeout before canceling request |
43
|
|
|
* @var array $stream_context_options Context options as defined in the PHP documentation |
44
|
|
|
* @var array $stream_context_param Context params as defined in the PHP documentation |
45
|
|
|
* @var bool $ssl Use ssl, default to scheme from request, false if not present |
46
|
|
|
* @var int $write_buffer_size Buffer when writing the request body, defaults to 8192 |
47
|
|
|
* @var int $ssl_method Crypto method for ssl/tls, see PHP doc, defaults to STREAM_CRYPTO_METHOD_TLS_CLIENT |
48
|
|
|
* } |
49
|
|
|
*/ |
50
|
64 |
|
public function __construct(ResponseFactory $responseFactory = null, array $config = []) |
51
|
|
|
{ |
52
|
64 |
|
if (null === $responseFactory) { |
53
|
53 |
|
$responseFactory = MessageFactoryDiscovery::find(); |
54
|
53 |
|
} |
55
|
|
|
|
56
|
64 |
|
$this->responseFactory = $responseFactory; |
57
|
64 |
|
$this->config = $this->configure($config); |
58
|
64 |
|
} |
59
|
|
|
|
60
|
|
|
/** |
61
|
|
|
* {@inheritdoc} |
62
|
|
|
*/ |
63
|
64 |
|
public function sendRequest(RequestInterface $request) |
64
|
|
|
{ |
65
|
64 |
|
$remote = $this->config['remote_socket']; |
66
|
64 |
|
$useSsl = $this->config['ssl']; |
67
|
|
|
|
68
|
64 |
|
if (!$request->hasHeader('Connection')) { |
69
|
11 |
|
$request = $request->withHeader('Connection', 'close'); |
70
|
11 |
|
} |
71
|
|
|
|
72
|
64 |
|
if( !$request->hasHeader('Content-Length') && false != ($bodySize = $request->getBody()->getSize()) ) { |
|
|
|
|
73
|
58 |
|
//$request = $request->withHeader('Content-Length', $bodySize); |
|
|
|
|
74
|
57 |
|
} |
75
|
|
|
|
76
|
63 |
|
if (null === $remote) { |
77
|
61 |
|
$remote = $this->determineRemoteFromRequest($request); |
78
|
61 |
|
} |
79
|
|
|
|
80
|
63 |
|
if (null === $useSsl) { |
81
|
|
|
$useSsl = ($request->getUri()->getScheme() === 'https'); |
82
|
|
|
} |
83
|
59 |
|
|
84
|
59 |
|
$socket = $this->createSocket($request, $remote, $useSsl); |
85
|
59 |
|
|
86
|
1 |
|
try { |
87
|
|
|
$this->writeRequest($socket, $request, $this->config['write_buffer_size']); |
88
|
1 |
|
$response = $this->readResponse($request, $socket); |
89
|
|
|
} catch (\Exception $e) { |
90
|
|
|
$this->closeSocket($socket); |
91
|
58 |
|
|
92
|
|
|
throw $e; |
93
|
|
|
} |
94
|
|
|
|
95
|
|
|
return $response; |
96
|
|
|
} |
97
|
|
|
|
98
|
|
|
/** |
99
|
|
|
* Create the socket to write request and read response on it. |
100
|
|
|
* |
101
|
|
|
* @param RequestInterface $request Request for |
102
|
|
|
* @param string $remote Entrypoint for the connection |
103
|
|
|
* @param bool $useSsl Whether to use ssl or not |
104
|
|
|
* |
105
|
63 |
|
* @throws NetworkException When the connection fail |
106
|
|
|
* |
107
|
63 |
|
* @return resource Socket resource |
108
|
63 |
|
*/ |
109
|
63 |
|
protected function createSocket(RequestInterface $request, $remote, $useSsl) |
110
|
|
|
{ |
111
|
63 |
|
$errNo = null; |
112
|
3 |
|
$errMsg = null; |
113
|
|
|
$socket = @stream_socket_client($remote, $errNo, $errMsg, floor($this->config['timeout'] / 1000), STREAM_CLIENT_CONNECT, $this->config['stream_context']); |
114
|
|
|
|
115
|
60 |
|
if (false === $socket) { |
116
|
|
|
throw new NetworkException($errMsg, $request); |
117
|
60 |
|
} |
118
|
3 |
|
|
119
|
1 |
|
stream_set_timeout($socket, floor($this->config['timeout'] / 1000), $this->config['timeout'] % 1000); |
120
|
|
|
|
121
|
2 |
|
if ($useSsl) { |
122
|
|
|
if (false === @stream_socket_enable_crypto($socket, true, $this->config['ssl_method'])) { |
123
|
59 |
|
throw new NetworkException(sprintf('Cannot enable tls: %s', error_get_last()['message']), $request); |
124
|
|
|
} |
125
|
|
|
} |
126
|
|
|
|
127
|
|
|
return $socket; |
128
|
|
|
} |
129
|
|
|
|
130
|
|
|
/** |
131
|
1 |
|
* Close the socket, used when having an error. |
132
|
|
|
* |
133
|
1 |
|
* @param resource $socket |
134
|
1 |
|
*/ |
135
|
|
|
protected function closeSocket($socket) |
136
|
|
|
{ |
137
|
|
|
fclose($socket); |
138
|
|
|
} |
139
|
|
|
|
140
|
|
|
/** |
141
|
|
|
* Return configuration for the socket client. |
142
|
|
|
* |
143
|
64 |
|
* @param array $config Configuration from user |
144
|
|
|
* |
145
|
64 |
|
* @return array Configuration resolved |
146
|
64 |
|
*/ |
147
|
64 |
|
protected function configure(array $config = []) |
148
|
64 |
|
{ |
149
|
64 |
|
$resolver = new OptionsResolver(); |
150
|
|
|
$resolver->setDefaults($this->config); |
151
|
64 |
|
$resolver->setDefault('stream_context', function (Options $options) { |
152
|
|
|
return stream_context_create($options['stream_context_options'], $options['stream_context_param']); |
153
|
64 |
|
}); |
154
|
64 |
|
|
155
|
64 |
|
$resolver->setDefault('timeout', ini_get('default_socket_timeout') * 1000); |
156
|
64 |
|
|
157
|
|
|
$resolver->setAllowedTypes('stream_context_options', 'array'); |
158
|
64 |
|
$resolver->setAllowedTypes('stream_context_param', 'array'); |
159
|
|
|
$resolver->setAllowedTypes('stream_context', 'resource'); |
160
|
|
|
$resolver->setAllowedTypes('ssl', ['bool', 'null']); |
161
|
|
|
|
162
|
|
|
return $resolver->resolve($config); |
163
|
|
|
} |
164
|
|
|
|
165
|
|
|
/** |
166
|
|
|
* Return remote socket from the request. |
167
|
|
|
* |
168
|
|
|
* @param RequestInterface $request |
169
|
|
|
* |
170
|
58 |
|
* @throws NetworkException When no remote can be determined from the request |
171
|
|
|
* |
172
|
58 |
|
* @return string |
173
|
1 |
|
*/ |
174
|
|
|
private function determineRemoteFromRequest(RequestInterface $request) |
175
|
|
|
{ |
176
|
57 |
|
if ($request->getUri()->getHost() == '' && !$request->hasHeader('Host')) { |
177
|
57 |
|
throw new NetworkException('Cannot find connection endpoint for this request', $request); |
178
|
57 |
|
} |
179
|
|
|
|
180
|
|
|
$host = $request->getUri()->getHost(); |
181
|
57 |
|
$port = $request->getUri()->getPort() ?: ($request->getUri()->getScheme() == 'https' ? 443 : 80); |
182
|
1 |
|
$endpoint = sprintf('%s:%s', $host, $port); |
183
|
1 |
|
|
184
|
|
|
// If use the host header if present for the endpoint |
185
|
57 |
|
if (empty($host) && $request->hasHeader('Host')) { |
186
|
|
|
$endpoint = $request->getHeaderLine('Host'); |
187
|
|
|
} |
188
|
|
|
|
189
|
|
|
return sprintf('tcp://%s', $endpoint); |
190
|
|
|
} |
191
|
|
|
} |
192
|
|
|
|
This check looks for the bodies of
if
statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.These
if
bodies can be removed. If you have an empty if but statements in theelse
branch, consider inverting the condition.could be turned into
This is much more concise to read.