StreamClient   A
last analyzed

Complexity

Total Complexity 14

Size/Duplication

Total Lines 121
Duplicated Lines 0 %

Importance

Changes 9
Bugs 0 Features 0
Metric Value
eloc 60
c 9
b 0
f 0
dl 0
loc 121
rs 10
wmc 14

4 Methods

Rating   Name   Duplication   Size   Complexity  
A getRequestHeaders() 0 14 4
A createResponse() 0 32 5
A sendRequest() 0 26 2
A getContextOptions() 0 29 3
1
<?php
2
/**
3
 * Class StreamClient
4
 *
5
 * @created      23.02.2019
6
 * @author       smiley <[email protected]>
7
 * @copyright    2019 smiley
8
 * @license      MIT
9
 */
10
11
namespace chillerlan\HTTP\Psr18;
12
13
use chillerlan\HTTP\Utils\HeaderUtil;
14
use Psr\Http\Message\{RequestInterface, ResponseInterface};
15
use function explode, file_get_contents, get_headers, in_array, intval, restore_error_handler,
16
	set_error_handler, stream_context_create, strtolower, str_starts_with, trim;
17
18
class StreamClient extends HTTPClientAbstract{
19
20
	/**
21
	 * @inheritDoc
22
	 */
23
	public function sendRequest(RequestInterface $request):ResponseInterface{
24
25
		$errorHandler = function(int $errno, string $errstr):bool{
26
			$this->logger->error('StreamClient error #'.$errno.': '.$errstr);
27
28
			throw new ClientException($errstr, $errno);
29
		};
30
31
		set_error_handler($errorHandler);
32
33
		$context      = stream_context_create($this->getContextOptions($request));
34
		$requestUri   = (string)$request->getUri()->withFragment('');
35
		$responseBody = file_get_contents($requestUri, false, $context);
36
		$response     = $this->createResponse(get_headers($requestUri, true, $context));
37
38
		restore_error_handler();
39
40
		$body = $this->streamFactory !== null
41
			? $this->streamFactory->createStream()
42
			: $response->getBody()
43
		;
44
45
		$body->write($responseBody);
46
		$body->rewind();
47
48
		return $response->withBody($body);
49
	}
50
51
	/**
52
	 *
53
	 */
54
	protected function getContextOptions(RequestInterface $request):array{
55
		$method = $request->getMethod();
56
		$body   = in_array($method, ['DELETE', 'PATCH', 'POST', 'PUT'], true)
57
			? $request->getBody()->getContents()
58
			: null;
59
60
		$options = [
61
			'http' => [
62
				'method'           => $method,
63
				'header'           => $this->getRequestHeaders($request),
64
				'content'          => $body,
65
#				'protocol_version' => '1.1', // 1.1 is default from PHP 8.0
66
				'user_agent'       => $this->options->user_agent,
0 ignored issues
show
Bug Best Practice introduced by
The property $user_agent is declared protected in chillerlan\HTTP\HTTPOptions. Since you implement __get, consider adding a @property or @property-read.
Loading history...
67
				'max_redirects'    => 0,
68
				'timeout'          => 5,
69
			],
70
			'ssl'  => [
71
				'verify_peer'         => $this->options->ssl_verifypeer,
0 ignored issues
show
Bug Best Practice introduced by
The property $ssl_verifypeer is declared protected in chillerlan\HTTP\HTTPOptions. Since you implement __get, consider adding a @property or @property-read.
Loading history...
72
				'verify_depth'        => 3,
73
				'peer_name'           => $request->getUri()->getHost(),
74
				'ciphers'             => 'HIGH:!SSLv2:!SSLv3',
75
				'disable_compression' => true,
76
			],
77
		];
78
79
		$ca                  = $this->options->ca_info_is_path ? 'capath' : 'cafile';
0 ignored issues
show
Bug Best Practice introduced by
The property $ca_info_is_path is declared protected in chillerlan\HTTP\HTTPOptions. Since you implement __get, consider adding a @property or @property-read.
Loading history...
80
		$options['ssl'][$ca] = $this->options->ca_info;
0 ignored issues
show
Bug Best Practice introduced by
The property $ca_info is declared protected in chillerlan\HTTP\HTTPOptions. Since you implement __get, consider adding a @property or @property-read.
Loading history...
81
82
		return $options;
83
	}
84
85
	/**
86
	 *
87
	 */
88
	protected function getRequestHeaders(RequestInterface $request):array{
89
		$headers = [];
90
91
		foreach($request->getHeaders() as $name => $values){
92
			$name = strtolower($name);
93
94
			foreach($values as $value){
95
				// cURL requires a special format for empty headers.
96
				// See https://github.com/guzzle/guzzle/issues/1882 for more details.
97
				$headers[] = $value === '' ? $name.';' : $name.': '.$value;
98
			}
99
		}
100
101
		return $headers;
102
	}
103
104
	/**
105
	 * @param string[] $headers
106
	 */
107
	protected function createResponse(array $headers):ResponseInterface{
108
		$h = [];
109
110
		$httpversion = '';
111
		$statuscode  = 0;
112
		$statustext  = '';
113
114
		foreach($headers as $k => $v){
115
116
			if($k === 0 && str_starts_with($v, 'HTTP')){
117
				$status = explode(' ', $v, 3);
118
119
				$httpversion = explode('/', $status[0], 2)[1];
120
				$statuscode  = intval($status[1]);
121
				$statustext  = trim($status[2]);
122
123
				continue;
124
			}
125
126
			$h[$k] = $v;
127
		}
128
129
		$response = $this->responseFactory
130
			->createResponse($statuscode, $statustext)
131
			->withProtocolVersion($httpversion)
132
		;
133
134
		foreach(HeaderUtil::normalize($h) as $k => $v){
135
			$response = $response->withAddedHeader($k, $v);
136
		}
137
138
		return $response;
139
	}
140
141
}
142