-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Wrap `file_get_contents($url)` or similar in `HttpClient` service so things using it can have tests written.
- Loading branch information
Showing
28 changed files
with
781 additions
and
266 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
<?php | ||
declare(strict_types = 1); | ||
|
||
namespace MichalSpacekCz\Http\Client; | ||
|
||
use MichalSpacekCz\Http\Exceptions\HttpClientRequestException; | ||
use MichalSpacekCz\Http\Exceptions\HttpStreamException; | ||
|
||
class HttpClient | ||
{ | ||
|
||
/** | ||
* @param array<string, string|int> $httpOptions | ||
* @param array<string, string|bool> $tlsOptions | ||
* @return resource | ||
*/ | ||
private function createStreamContext(HttpClientRequest $request, array $httpOptions = [], array $tlsOptions = []) | ||
{ | ||
$httpOptions = [ | ||
'ignore_errors' => true, // To suppress PHP Warning: [...] HTTP/1.0 500 Internal Server Error | ||
'header' => $request->getHeaders(), | ||
] + $httpOptions; | ||
if ($request->getUserAgent() !== null) { | ||
$httpOptions = ['user_agent' => str_replace('\\', '/', $request->getUserAgent())] + $httpOptions; | ||
} | ||
if ($request->getFollowLocation() !== null) { | ||
$httpOptions = ['follow_location' => (int)$request->getFollowLocation()] + $httpOptions; | ||
} | ||
if ($request->getTlsCaptureCertificate() !== null) { | ||
$tlsOptions = ['capture_peer_cert' => $request->getTlsCaptureCertificate()] + $tlsOptions; | ||
} | ||
if ($request->getTlsServerName() !== null) { | ||
$tlsOptions = ['peer_name' => $request->getTlsServerName()] + $tlsOptions; | ||
} | ||
return stream_context_create( | ||
[ | ||
'ssl' => $tlsOptions, | ||
'http' => $httpOptions, | ||
], | ||
[ | ||
'notification' => function (int $notificationCode, int $severity, ?string $message, int $messageCode): void { | ||
if ($severity === STREAM_NOTIFY_SEVERITY_ERR) { | ||
throw new HttpStreamException($notificationCode, $message, $messageCode); | ||
} | ||
}, | ||
], | ||
); | ||
} | ||
|
||
|
||
/** | ||
* @throws HttpClientRequestException | ||
*/ | ||
public function get(HttpClientRequest $request): HttpClientResponse | ||
{ | ||
$context = $this->createStreamContext($request); | ||
return $this->request($request, $context); | ||
} | ||
|
||
|
||
/** | ||
* @throws HttpClientRequestException | ||
*/ | ||
public function head(HttpClientRequest $request): HttpClientResponse | ||
{ | ||
$context = $this->createStreamContext( | ||
$request, | ||
['method' => 'HEAD'], | ||
); | ||
return $this->request($request, $context); | ||
} | ||
|
||
|
||
/** | ||
* @param array<string, string> $formData | ||
* @throws HttpClientRequestException | ||
*/ | ||
public function postForm(HttpClientRequest $request, array $formData = []): HttpClientResponse | ||
{ | ||
$request->addHeader('Content-Type', 'application/x-www-form-urlencoded'); | ||
$context = $this->createStreamContext( | ||
$request, | ||
['method' => 'POST', 'content' => http_build_query($formData)], | ||
); | ||
return $this->request($request, $context); | ||
} | ||
|
||
|
||
/** | ||
* @param resource $context | ||
* @throws HttpClientRequestException | ||
* @noinspection PhpRedundantCatchClauseInspection A notification callback created by self::createStreamContext() may throw HttpStreamException | ||
*/ | ||
private function request(HttpClientRequest $request, $context): HttpClientResponse | ||
{ | ||
try { | ||
$fp = fopen($request->getUrl(), 'r', context: $context); | ||
if (!$fp) { | ||
throw new HttpClientRequestException($request->getUrl()); | ||
} | ||
$result = stream_get_contents($fp); | ||
$options = stream_context_get_options($fp); | ||
fclose($fp); | ||
} catch (HttpStreamException $e) { | ||
throw new HttpClientRequestException($request->getUrl(), $e->getCode(), $e); | ||
} | ||
if ($result === false) { | ||
throw new HttpClientRequestException($request->getUrl()); | ||
} | ||
return new HttpClientResponse($request, $result, $options['ssl']['peer_certificate'] ?? null); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
<?php | ||
declare(strict_types = 1); | ||
|
||
namespace MichalSpacekCz\Http\Client; | ||
|
||
final class HttpClientRequest | ||
{ | ||
|
||
private ?string $userAgent = null; | ||
|
||
/** @var list<string> */ | ||
private array $headers = []; | ||
|
||
private ?bool $followLocation = null; | ||
|
||
private ?string $tlsServerName = null; | ||
|
||
private ?bool $tlsCaptureCertificate = null; | ||
|
||
|
||
public function __construct( | ||
private readonly string $url, | ||
) { | ||
} | ||
|
||
|
||
public function getUrl(): string | ||
{ | ||
return $this->url; | ||
} | ||
|
||
|
||
public function getUserAgent(): ?string | ||
{ | ||
return $this->userAgent; | ||
} | ||
|
||
|
||
public function setUserAgent(string $userAgent): self | ||
{ | ||
$this->userAgent = $userAgent; | ||
return $this; | ||
} | ||
|
||
|
||
public function addHeader(string $header, string $value): self | ||
{ | ||
$this->headers[] = "{$header}: {$value}"; | ||
return $this; | ||
} | ||
|
||
|
||
/** | ||
* @return list<string> | ||
*/ | ||
public function getHeaders(): array | ||
{ | ||
return $this->headers; | ||
} | ||
|
||
|
||
public function getFollowLocation(): ?bool | ||
{ | ||
return $this->followLocation; | ||
} | ||
|
||
|
||
public function setFollowLocation(bool $followLocation): self | ||
{ | ||
$this->followLocation = $followLocation; | ||
return $this; | ||
} | ||
|
||
|
||
public function getTlsServerName(): ?string | ||
{ | ||
return $this->tlsServerName; | ||
} | ||
|
||
|
||
public function setTlsServerName(string $tlsServerName): self | ||
{ | ||
$this->tlsServerName = $tlsServerName; | ||
return $this; | ||
} | ||
|
||
|
||
public function getTlsCaptureCertificate(): ?bool | ||
{ | ||
return $this->tlsCaptureCertificate; | ||
} | ||
|
||
|
||
public function setTlsCaptureCertificate(bool $tlsCaptureCertificate): self | ||
{ | ||
$this->tlsCaptureCertificate = $tlsCaptureCertificate; | ||
return $this; | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
<?php | ||
declare(strict_types = 1); | ||
|
||
namespace MichalSpacekCz\Http\Client; | ||
|
||
use MichalSpacekCz\Http\Exceptions\HttpClientTlsCertificateNotAvailableException; | ||
use MichalSpacekCz\Http\Exceptions\HttpClientTlsCertificateNotCapturedException; | ||
use OpenSSLCertificate; | ||
|
||
class HttpClientResponse | ||
{ | ||
|
||
public function __construct( | ||
private readonly HttpClientRequest $request, | ||
private readonly string $body, | ||
private readonly ?OpenSSLCertificate $tlsCertificate, | ||
) { | ||
} | ||
|
||
|
||
public function getBody(): string | ||
{ | ||
return $this->body; | ||
} | ||
|
||
|
||
/** | ||
* @throws HttpClientTlsCertificateNotAvailableException | ||
* @throws HttpClientTlsCertificateNotCapturedException | ||
*/ | ||
public function getTlsCertificate(): OpenSSLCertificate | ||
{ | ||
$scheme = parse_url($this->request->getUrl(), PHP_URL_SCHEME); | ||
if (!is_string($scheme) || strtolower($scheme) !== 'https') { | ||
throw new HttpClientTlsCertificateNotAvailableException($this->request->getUrl()); | ||
} | ||
if (!$this->request->getTlsCaptureCertificate() || !$this->tlsCertificate) { | ||
throw new HttpClientTlsCertificateNotCapturedException(); | ||
} | ||
return $this->tlsCertificate; | ||
} | ||
|
||
} |
Oops, something went wrong.