Skip to content

Commit 4217d82

Browse files
committed
🛀
1 parent 2baebfe commit 4217d82

File tree

10 files changed

+121
-81
lines changed

10 files changed

+121
-81
lines changed

src/ClientException.php

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,5 @@
1313
namespace chillerlan\HTTP;
1414

1515
use Psr\Http\Client\ClientExceptionInterface;
16-
use Exception;
1716

18-
class ClientException extends Exception implements ClientExceptionInterface{
19-
20-
}
17+
class ClientException extends \Exception implements ClientExceptionInterface{}

src/CurlClient.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,8 @@ public function __construct(
6969
* @throws \Exception If processing the request is impossible (eg. bad configuration).
7070
*/
7171
public function sendRequest(RequestInterface $request):ResponseInterface{
72-
$handle = new CurlHandle($request, $this->responseFactory->createResponse(), $this->options);
72+
/** @var \chillerlan\HTTP\CurlHandle $handle */
73+
$handle = new $this->options->curlHandle($request, $this->responseFactory->createResponse(), $this->options);
7374
$handle->init();
7475

7576
curl_exec($handle->curl);

src/CurlHandle.php

Lines changed: 84 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -93,25 +93,22 @@ public function reset():void{
9393
}
9494

9595
/**
96-
* @return \Psr\Http\Message\ResponseInterface
96+
* @return array
9797
*/
98-
public function init(){
99-
100-
$v = $this->request->getProtocolVersion();
98+
protected function initCurlOptions():array{
10199

102-
switch($v){
103-
case '1.0': $v = CURL_HTTP_VERSION_1_0; break;
104-
case '1.1': $v = CURL_HTTP_VERSION_1_1; break;
105-
case '2.0': $v = CURL_HTTP_VERSION_2_0; break;
106-
default: $v = CURL_HTTP_VERSION_NONE;
107-
}
100+
$v = [
101+
'1.0' => CURL_HTTP_VERSION_1_0,
102+
'1.1' => CURL_HTTP_VERSION_1_1,
103+
'2.0' => CURL_HTTP_VERSION_2_0,
104+
];
108105

109-
$options = [
106+
return [
110107
CURLOPT_HEADER => false,
111108
CURLOPT_RETURNTRANSFER => false,
112109
CURLOPT_FOLLOWLOCATION => false,
113110
CURLOPT_URL => (string)$this->request->getUri()->withFragment(''),
114-
CURLOPT_HTTP_VERSION => $v,
111+
CURLOPT_HTTP_VERSION => $v[$this->request->getProtocolVersion()] ?? CURL_HTTP_VERSION_NONE,
115112
CURLOPT_USERAGENT => $this->options->user_agent,
116113
CURLOPT_PROTOCOLS => CURLPROTO_HTTP | CURLPROTO_HTTPS,
117114
CURLOPT_SSL_VERIFYPEER => true,
@@ -122,59 +119,50 @@ public function init(){
122119
CURLOPT_WRITEFUNCTION => [$this, 'writefunction'],
123120
CURLOPT_HEADERFUNCTION => [$this, 'headerfunction'],
124121
];
122+
}
125123

126-
$userinfo = $this->request->getUri()->getUserInfo();
127-
128-
if(!empty($userinfo)){
129-
$options[CURLOPT_USERPWD] = $userinfo;
130-
}
131-
132-
/*
133-
* Some HTTP methods cannot have payload:
134-
*
135-
* - GET — cURL will automatically change method to PUT or POST
136-
* if we set CURLOPT_UPLOAD or CURLOPT_POSTFIELDS.
137-
* - HEAD — cURL treats HEAD as GET request with a same restrictions.
138-
* - TRACE — According to RFC7231: a client MUST NOT send a message body in a TRACE request.
139-
*/
140-
$method = $this->request->getMethod();
124+
/**
125+
* @param array $options
126+
*
127+
* @return void
128+
*/
129+
protected function setBodyOptions(array &$options):void{
141130
$body = $this->request->getBody();
142131
$bodySize = $body->getSize();
143132

144-
if(in_array($method, ['DELETE', 'PATCH', 'POST', 'PUT'], true) && $bodySize !== 0){
145-
146-
if($body->isSeekable()){
147-
$body->rewind();
148-
}
133+
if($bodySize === 0){
134+
return;
135+
}
149136

150-
// Message has non empty body.
151-
if($bodySize === null || $bodySize > 1 << 20){
152-
// Avoid full loading large or unknown size body into memory
153-
$options[CURLOPT_UPLOAD] = true;
137+
if($body->isSeekable()){
138+
$body->rewind();
139+
}
154140

155-
if($bodySize !== null){
156-
$options[CURLOPT_INFILESIZE] = $bodySize;
157-
}
141+
// Message has non empty body.
142+
if($bodySize === null || $bodySize > 1 << 20){
143+
// Avoid full loading large or unknown size body into memory
144+
$options[CURLOPT_UPLOAD] = true;
158145

159-
$options[CURLOPT_READFUNCTION] = [$this, 'readfunction'];
160-
}
161-
// Small body can be loaded into memory
162-
else{
163-
$options[CURLOPT_POSTFIELDS] = (string)$body;
146+
if($bodySize !== null){
147+
$options[CURLOPT_INFILESIZE] = $bodySize;
164148
}
165149

150+
$options[CURLOPT_READFUNCTION] = [$this, 'readfunction'];
166151
}
167-
168-
// This will set HTTP method to "HEAD".
169-
if($method === 'HEAD'){
170-
$options[CURLOPT_NOBODY] = true;
171-
}
172-
// GET is a default method. Other methods should be specified explicitly.
173-
elseif($method !== 'GET'){
174-
$options[CURLOPT_CUSTOMREQUEST] = $method;
152+
// Small body can be loaded into memory
153+
else{
154+
$options[CURLOPT_POSTFIELDS] = (string)$body;
175155
}
176156

177-
$curlHeaders = [];
157+
}
158+
159+
/**
160+
* @param array $options
161+
*
162+
* @return array
163+
*/
164+
protected function initCurlHeaders(array $options):array{
165+
$headers = [];
178166

179167
foreach($this->request->getHeaders() as $name => $values){
180168
$header = strtolower($name);
@@ -202,22 +190,59 @@ public function init(){
202190

203191
// cURL requires a special format for empty headers.
204192
// See https://github.com/guzzle/guzzle/issues/1882 for more details.
205-
$curlHeaders[] = $value === ''
206-
? $name.';'
207-
: $name.': '.$value;
193+
$headers[] = $value === '' ? $name.';' : $name.': '.$value;
208194
}
209195

210196
}
211197

212-
$options[CURLOPT_HTTPHEADER] = $curlHeaders;
198+
return $headers;
199+
}
200+
201+
/**
202+
* @return resource cURL handle
203+
*/
204+
public function init(){
205+
$options = $this->initCurlOptions();
206+
207+
$userinfo = $this->request->getUri()->getUserInfo();
208+
209+
if(!empty($userinfo)){
210+
$options[CURLOPT_USERPWD] = $userinfo;
211+
}
212+
213+
/*
214+
* Some HTTP methods cannot have payload:
215+
*
216+
* - GET — cURL will automatically change method to PUT or POST
217+
* if we set CURLOPT_UPLOAD or CURLOPT_POSTFIELDS.
218+
* - HEAD — cURL treats HEAD as GET request with a same restrictions.
219+
* - TRACE — According to RFC7231: a client MUST NOT send a message body in a TRACE request.
220+
*/
221+
$method = $this->request->getMethod();
222+
223+
if(in_array($method, ['DELETE', 'PATCH', 'POST', 'PUT'], true)){
224+
$this->setBodyOptions($options);
225+
}
226+
227+
// This will set HTTP method to "HEAD".
228+
if($method === 'HEAD'){
229+
$options[CURLOPT_NOBODY] = true;
230+
}
231+
232+
// GET is a default method. Other methods should be specified explicitly.
233+
if($method !== 'GET'){
234+
$options[CURLOPT_CUSTOMREQUEST] = $method;
235+
}
236+
237+
$options[CURLOPT_HTTPHEADER] = $this->initCurlHeaders($options);
213238

214239
// If the Expect header is not present, prevent curl from adding it
215-
if (!$this->request->hasHeader('Expect')) {
240+
if(!$this->request->hasHeader('Expect')){
216241
$options[CURLOPT_HTTPHEADER][] = 'Expect:';
217242
}
218243

219244
// cURL sometimes adds a content-type by default. Prevent this.
220-
if (!$this->request->hasHeader('Content-Type')) {
245+
if(!$this->request->hasHeader('Content-Type')){
221246
$options[CURLOPT_HTTPHEADER][] = 'Content-Type:';
222247
}
223248

src/HTTPOptions.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
* @property array $curl_options
2020
* @property string $ca_info
2121
* @property bool $ssl_verifypeer
22+
* @property string $curlHandle
2223
*/
2324
class HTTPOptions extends SettingsContainerAbstract{
2425
use HTTPOptionsTrait;

src/HTTPOptionsTrait.php

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,13 @@ trait HTTPOptionsTrait{
4949
*/
5050
protected $ssl_verifypeer = true;
5151

52+
/**
53+
* The CurlHandleInterface to use in CurlClient::sendRequest()
54+
*
55+
* @var string
56+
*/
57+
protected $curlHandle = CurlHandle::class;
58+
5259
/**
5360
* HTTPOptionsTrait constructor
5461
*
@@ -166,6 +173,7 @@ protected function setCA():void{
166173
}
167174
}
168175

176+
// @codeCoverageIgnoreStart
169177
$msg = 'No system CA bundle could be found in any of the the common system locations. '
170178
.'In order to verify peer certificates, you will need to supply the path on disk to a certificate bundle via '
171179
.'HTTPOptions::$ca_info or HTTPOptions::$curl_options. If you do not need a specific certificate bundle, '
@@ -174,7 +182,8 @@ protected function setCA():void{
174182
.'to the path of the file, allowing you to omit the $ca_info or $curl_options setting. '
175183
.'See http://curl.haxx.se/docs/sslcerts.html for more information.';
176184

177-
throw new ClientException($msg); // @codeCoverageIgnore
185+
throw new ClientException($msg);
186+
// @codeCoverageIgnoreEnd
178187
}
179188

180189
}

src/Psr7/MultipartStream.php

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
use InvalidArgumentException, RuntimeException;
1919

2020
/**
21-
* @property \chillerlan\HTTP\Psr7\AppendStream $stream
21+
* @property \chillerlan\HTTP\Psr7\Stream $stream
2222
*/
2323
final class MultipartStream extends StreamAbstract{
2424

@@ -27,11 +27,6 @@ final class MultipartStream extends StreamAbstract{
2727
*/
2828
protected $boundary;
2929

30-
/**
31-
* @var \Psr\Http\Message\StreamFactoryInterface
32-
*/
33-
protected $streamFactory;
34-
3530
/**
3631
* @var bool
3732
*/
@@ -72,12 +67,12 @@ public function build():MultipartStream{
7267

7368
if(!$this->built){
7469
$this->stream->write("--{$this->getBoundary()}--\r\n");
70+
71+
$this->built = true;
7572
}
7673

7774
$this->stream->rewind();
7875

79-
$this->built = true;
80-
8176
return $this;
8277
}
8378

@@ -135,7 +130,6 @@ public function addElement(array $e):MultipartStream{
135130
}
136131
}
137132

138-
139133
$this->stream->write('--'.$this->boundary."\r\n");
140134

141135
foreach($e['headers'] as $key => $value){
@@ -144,7 +138,6 @@ public function addElement(array $e):MultipartStream{
144138

145139
$this->stream->write("\r\n".$e['contents']->getContents()."\r\n");
146140

147-
148141
return $this;
149142
}
150143

@@ -191,7 +184,7 @@ public function isWritable():bool{
191184
* @inheritdoc
192185
*/
193186
public function write($string):int{
194-
throw new RuntimeException('Cannot write to a MultipartStream, use the "addElement" method instead.');
187+
throw new RuntimeException('Cannot write to a MultipartStream, use MultipartStream::addElement() instead.');
195188
}
196189

197190
/**

src/Psr7/Stream.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ public function __toString(){
8484
}
8585
catch(Exception $e){
8686
// https://bugs.php.net/bug.php?id=53648
87-
trigger_error('StreamDecoratorTrait::__toString exception: '.$e->getMessage(), E_USER_ERROR);
87+
trigger_error('Stream::__toString exception: '.$e->getMessage(), E_USER_ERROR);
8888

8989
return '';
9090
}

0 commit comments

Comments
 (0)