Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
92.98% |
106 / 114 |
|
90.00% |
9 / 10 |
CRAP | |
0.00% |
0 / 1 |
HttpClient | |
92.98% |
106 / 114 |
|
90.00% |
9 / 10 |
36.45 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
request | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
createRequest | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
3 | |||
sendCore | |
90.24% |
74 / 82 |
|
0.00% |
0 / 1 |
25.58 | |||
send | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
get | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
post | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
put | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
patch | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
delete | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 |
1 | <?php |
2 | |
3 | declare(strict_types=1); |
4 | |
5 | namespace PeServer\Core\Http\Client; |
6 | |
7 | use Exception; |
8 | use PeServer\Core\Binary; |
9 | use PeServer\Core\Collection\Arr; |
10 | use PeServer\Core\Collection\Collections; |
11 | use PeServer\Core\DisposerBase; |
12 | use PeServer\Core\Http\Client\HttpClientOptions; |
13 | use PeServer\Core\Http\Client\HttpClientRequest; |
14 | use PeServer\Core\Http\Client\HttpClientResponse; |
15 | use PeServer\Core\Http\HttpHeader; |
16 | use PeServer\Core\Http\HttpMethod; |
17 | use PeServer\Core\Text; |
18 | use PeServer\Core\Throws\HttpClientRequestException; |
19 | use PeServer\Core\Throws\HttpClientStatusException; |
20 | use PeServer\Core\Throws\NotImplementedException; |
21 | use PeServer\Core\Web\Url; |
22 | |
23 | /** |
24 | * HTTPへ旅立つのだ。 |
25 | */ |
26 | class HttpClient extends DisposerBase |
27 | { |
28 | public function __construct( |
29 | private HttpClientOptions $options |
30 | ) { |
31 | } |
32 | |
33 | #region function |
34 | |
35 | public static function request(HttpClientRequest $request, HttpClientOptions $options): HttpClientResponse |
36 | { |
37 | $client = new self($options); |
38 | return $client->send($request); |
39 | } |
40 | |
41 | /** |
42 | * リクエストを内部用に変換。 |
43 | * |
44 | * @param HttpClientRequest $raw |
45 | * @return HttpClientRequest |
46 | */ |
47 | protected function createRequest(HttpClientRequest $raw): HttpClientRequest |
48 | { |
49 | // HTTPヘッダをオプションから構築してユーザー設定で上書き |
50 | $header = HttpHeader::createClientRequestHeader(); |
51 | $header->setValue('User-Agent', $this->options->userAgent); |
52 | |
53 | if ($raw->header !== null) { |
54 | foreach ($raw->header->getHeaderNames() as $name) { |
55 | $overwriteValues = $raw->header->getValues($name); |
56 | $header->setValues($name, $overwriteValues); |
57 | } |
58 | } |
59 | |
60 | return new HttpClientRequest( |
61 | $raw->url, |
62 | $raw->method, |
63 | $header, |
64 | $raw->content |
65 | ); |
66 | } |
67 | |
68 | protected function sendCore(HttpClientRequest $request): HttpClientResponse |
69 | { |
70 | $curlHandle = curl_init(); |
71 | |
72 | /** @var array<int,mixed> */ |
73 | $curlOptions = []; |
74 | |
75 | //-------------------------------------------------------------- |
76 | // 共通 |
77 | $curlOptions[CURLOPT_URL] = $request->url->toString($this->options->urlEncoding); //cspell:disable-line |
78 | $curlOptions[CURLOPT_RETURNTRANSFER] = true; //cspell:disable-line |
79 | $curlOptions[CURLOPT_HEADER] = true; //cspell:disable-line |
80 | $curlOptions[CURLOPT_CRLF] = false; //cspell:disable-line |
81 | |
82 | //-------------------------------------------------------------- |
83 | // リクエスト設定 |
84 | // メソッド |
85 | /** @var HttpHeader|null */ |
86 | $overwriteHeader = null; |
87 | //cspell:disable-next-line |
88 | $curlOptions[CURLOPT_CUSTOMREQUEST] = $request->method->value; |
89 | switch ($request->method) { |
90 | case HttpMethod::Get: |
91 | case HttpMethod::Head: |
92 | break; |
93 | |
94 | case HttpMethod::Post: |
95 | case HttpMethod::Put: |
96 | case HttpMethod::Delete: |
97 | case HttpMethod::Connect: |
98 | case HttpMethod::Options: |
99 | case HttpMethod::Trace: |
100 | case HttpMethod::Patch: |
101 | if ($request->content) { |
102 | $overwriteHeader = HttpHeader::createClientRequestHeader(); |
103 | $customHeader = $request->content->toHeader(); |
104 | foreach ($customHeader->getHeaderNames() as $name) { |
105 | $overwriteHeader->setValues($name, $customHeader->getValues($name)); |
106 | } |
107 | //cspell:disable-next-line |
108 | $curlOptions[CURLOPT_POSTFIELDS] = $request->content->toBody()->raw; |
109 | } else { |
110 | //cspell:disable-next-line |
111 | $curlOptions[CURLOPT_POSTFIELDS] = Text::EMPTY; |
112 | } |
113 | break; |
114 | |
115 | default: |
116 | throw new NotImplementedException(); |
117 | } |
118 | |
119 | // ヘッダ |
120 | $headers = []; |
121 | if ($request->header) { |
122 | $headers = $request->header->getHeaders(); |
123 | |
124 | if ($overwriteHeader) { |
125 | $headers = Arr::replace($headers, $overwriteHeader->getHeaders()); |
126 | } |
127 | } elseif ($overwriteHeader) { |
128 | // 上で使ってる $request->header は差し替えられたものなので何かしらオブジェクトが入っている |
129 | // なのでこっちに来ることはないと思うよ |
130 | $headers = $overwriteHeader->getHeaders(); |
131 | } |
132 | if (0 < Arr::getCount($headers)) { |
133 | $headerList = []; |
134 | foreach ($headers as $name => $values) { |
135 | $headerList[] = "$name: $values"; |
136 | } |
137 | //cspell:disable-next-line |
138 | $curlOptions[CURLOPT_HTTPHEADER] = $headerList; |
139 | } |
140 | |
141 | //-------------------------------------------------------------- |
142 | // オプション設定 |
143 | // リダイレクト |
144 | $curlOptions[CURLOPT_FOLLOWLOCATION] = $this->options->redirect->isEnabled; //cspell:disable-line |
145 | $curlOptions[CURLOPT_MAXREDIRS] = $this->options->redirect->count; //cspell:disable-line |
146 | $curlOptions[CURLOPT_AUTOREFERER] = $this->options->redirect->autoReferer; //cspell:disable-line |
147 | |
148 | // セキュリティ |
149 | $curlOptions[CURLOPT_DISALLOW_USERNAME_IN_URL] = !$this->options->security->urlAllowAuthentication; //cspell:disable-line |
150 | $curlOptions[CURLOPT_SSL_VERIFYPEER] = $this->options->security->sslVerifyPeer; //cspell:disable-line |
151 | $curlOptions[CURLOPT_SSL_VERIFYHOST] = $this->options->security->sslVerifyHost ? 2 : 0; //cspell:disable-line |
152 | |
153 | // プロキシ |
154 | if ($this->options->proxy) { |
155 | $curlOptions[CURLOPT_HTTPPROXYTUNNEL] = true; //cspell:disable-line |
156 | $curlOptions[CURLOPT_PROXYPORT] = $this->options->proxy->port; //cspell:disable-line |
157 | $curlOptions[CURLOPT_PROXY] = $this->options->proxy->host; //cspell:disable-line |
158 | if (!Text::isNullOrEmpty($this->options->proxy->userName) || !Text::isNullOrEmpty($this->options->proxy->password)) { |
159 | $curlOptions[CURLOPT_PROXYUSERPWD] = $this->options->proxy->userName . ':' . $this->options->proxy->password; |
160 | } |
161 | } |
162 | |
163 | curl_setopt_array($curlHandle, $curlOptions); |
164 | |
165 | /** @var string|false */ |
166 | $response = curl_exec($curlHandle); |
167 | $clientStatus = HttpClientStatus::create($curlHandle); |
168 | $information = HttpClientInformation::create($this->options->urlEncoding, $request, $curlHandle); |
169 | $aaa = $information->getEffectiveUrl(); |
170 | |
171 | /** @var HttpHeader|null */ |
172 | $responseHeader = null; |
173 | /** @var Binary|null */ |
174 | $responseContent = null; |
175 | |
176 | if ($response === false) { |
177 | $responseContent = new Binary(Text::EMPTY); |
178 | $responseHeader = HttpHeader::getClientResponseHeader(new Binary(Text::EMPTY)); |
179 | } else { |
180 | $rawResponse = new Binary($response); |
181 | |
182 | $headerSize = $information->getHeaderSize(); |
183 | $rawHeader = $rawResponse->getRange(0, $headerSize); |
184 | $responseHeader = HttpHeader::getClientResponseHeader($rawHeader); |
185 | $responseContent = $rawResponse->getRange($headerSize); |
186 | } |
187 | |
188 | $result = new HttpClientResponse( |
189 | $this->options, |
190 | $request, |
191 | $curlHandle, |
192 | $responseHeader, |
193 | $responseContent, |
194 | $information, |
195 | $clientStatus |
196 | ); |
197 | |
198 | |
199 | if ($result->clientStatus->isError()) { |
200 | throw new HttpClientStatusException($result); |
201 | } |
202 | |
203 | $httpStatus = $result->information->getHttpStatus(); |
204 | if ($httpStatus->isError()) { |
205 | throw new HttpClientRequestException($result); |
206 | } |
207 | |
208 | return $result; |
209 | } |
210 | |
211 | public function send(HttpClientRequest $request): HttpClientResponse |
212 | { |
213 | $request = $this->createRequest($request); |
214 | return $this->sendCore($request); |
215 | } |
216 | |
217 | final public function get(Url $url, ?HttpHeader $header = null): HttpClientResponse |
218 | { |
219 | $requestData = new HttpClientRequest($url, HttpMethod::Get, $header, null); |
220 | $request = $this->createRequest($requestData); |
221 | return $this->sendCore($request); |
222 | } |
223 | |
224 | final public function post(Url $url, ?HttpHeader $header = null, ?HttpClientContentBase $content = null): HttpClientResponse |
225 | { |
226 | $requestData = new HttpClientRequest($url, HttpMethod::Post, $header, $content); |
227 | $request = $this->createRequest($requestData); |
228 | return $this->sendCore($request); |
229 | } |
230 | |
231 | final public function put(Url $url, ?HttpHeader $header = null, ?HttpClientContentBase $content = null): HttpClientResponse |
232 | { |
233 | $requestData = new HttpClientRequest($url, HttpMethod::Put, $header, $content); |
234 | $request = $this->createRequest($requestData); |
235 | return $this->sendCore($request); |
236 | } |
237 | |
238 | final public function patch(Url $url, ?HttpHeader $header = null, ?HttpClientContentBase $content = null): HttpClientResponse |
239 | { |
240 | $requestData = new HttpClientRequest($url, HttpMethod::Patch, $header, $content); |
241 | $request = $this->createRequest($requestData); |
242 | return $this->sendCore($request); |
243 | } |
244 | |
245 | final public function delete(Url $url, ?HttpHeader $header = null, ?HttpClientContentBase $content = null): HttpClientResponse |
246 | { |
247 | $requestData = new HttpClientRequest($url, HttpMethod::Delete, $header, $content); |
248 | $request = $this->createRequest($requestData); |
249 | return $this->sendCore($request); |
250 | } |
251 | |
252 | #endregion |
253 | } |