Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HTTP client #237

Merged
merged 7 commits into from
Sep 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 8 additions & 19 deletions site/app/CompanyInfo/CompanyRegisterAres.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@

use MichalSpacekCz\CompanyInfo\Exceptions\CompanyInfoException;
use MichalSpacekCz\CompanyInfo\Exceptions\CompanyNotFoundException;
use MichalSpacekCz\Http\Client\HttpClient;
use MichalSpacekCz\Http\Client\HttpClientRequest;
use MichalSpacekCz\Http\Exceptions\HttpClientRequestException;
use Nette\Http\IResponse;
use Nette\Schema\Expect;
use Nette\Schema\Processor;
Expand All @@ -23,6 +26,7 @@ class CompanyRegisterAres implements CompanyRegister

public function __construct(
private readonly Processor $schemaProcessor,
private readonly HttpClient $httpClient,
) {
}

Expand Down Expand Up @@ -86,31 +90,16 @@ public function getDetails(string $companyId): CompanyInfoDetails


/**
* @throws CompanyInfoException
* @throws CompanyNotFoundException
*/
private function fetch(string $companyId): string
{
$url = "https://ares.gov.cz/ekonomicke-subjekty-v-be/rest/ekonomicke-subjekty/{$companyId}";
$context = stream_context_create();
$setResult = stream_context_set_params($context, [
'notification' => function (int $notificationCode, int $severity, ?string $message, int $messageCode) {
if ($severity === STREAM_NOTIFY_SEVERITY_ERR) {
throw new CompanyNotFoundException($messageCode !== IResponse::S404_NotFound ? $messageCode : null);
}
},
'options' => [
'http' => ['ignore_errors' => true], // To suppress PHP Warning: [...] HTTP/1.0 500 Internal Server Error
],
]);
if (!$setResult) {
throw new CompanyInfoException("Can't set stream context params to get contents from {$url}");
}
$result = file_get_contents($url, false, $context);
if (!$result) {
throw new CompanyInfoException("Can't get result from {$url}");
try {
return $this->httpClient->get(new HttpClientRequest($url))->getBody();
} catch (HttpClientRequestException $e) {
throw new CompanyNotFoundException($e->getCode() !== IResponse::S404_NotFound ? $e->getCode() : null, $e);
}
return $result;
}


Expand Down
17 changes: 13 additions & 4 deletions site/app/CompanyInfo/CompanyRegisterRegisterUz.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@

use MichalSpacekCz\CompanyInfo\Exceptions\CompanyInfoException;
use MichalSpacekCz\CompanyInfo\Exceptions\CompanyNotFoundException;
use MichalSpacekCz\Http\Client\HttpClient;
use MichalSpacekCz\Http\Client\HttpClientRequest;
use MichalSpacekCz\Http\Exceptions\HttpClientRequestException;
use Nette\Http\IResponse;
use Nette\Utils\Json;
use Nette\Utils\JsonException;
Expand All @@ -23,6 +26,12 @@ class CompanyRegisterRegisterUz implements CompanyRegister
private const COUNTRY_CODE = 'sk';


public function __construct(
private readonly HttpClient $httpClient,
) {
}


public function getCountry(): string
{
return 'sk';
Expand Down Expand Up @@ -71,10 +80,10 @@ private function call(string $method, ?array $parameters = null): stdClass
} else {
$query = '';
}
$content = file_get_contents("https://www.registeruz.sk/cruz-public/api/{$method}{$query}");
if (!$content) {
$lastError = error_get_last();
throw new CompanyInfoException($lastError ? $lastError['message'] : '', IResponse::S500_InternalServerError);
try {
$content = $this->httpClient->get(new HttpClientRequest("https://www.registeruz.sk/cruz-public/api/{$method}{$query}"))->getBody();
} catch (HttpClientRequestException $e) {
throw new CompanyInfoException(code: IResponse::S500_InternalServerError, previous: $e);
}
try {
$data = Json::decode($content);
Expand Down
13 changes: 3 additions & 10 deletions site/app/Form/UpcKeysSsidFormFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

namespace MichalSpacekCz\Form;

use MichalSpacekCz\UpcKeys\Technicolor;
use MichalSpacekCz\UpcKeys\UpcKeys;

class UpcKeysSsidFormFactory
Expand All @@ -12,16 +11,14 @@ class UpcKeysSsidFormFactory
public function __construct(
private readonly UnprotectedFormFactory $factory,
private readonly UpcKeys $upcKeys,
private readonly Technicolor $technicolor,
) {
}


/**
* @param callable(string): void $onSuccess
* @param callable(): void $onError
*/
public function create(callable $onSuccess, callable $onError, ?string $ssid): UiForm
public function create(callable $onSuccess, ?string $ssid): UiForm
{
$form = $this->factory->create();
$form->addText('ssid', 'SSID:')
Expand All @@ -33,14 +30,10 @@ public function create(callable $onSuccess, callable $onError, ?string $ssid): U
$form->addSubmit('submit', 'Get keys')
->setHtmlId('submit')
->setHtmlAttribute('data-alt', 'Wait…');
$form->onSuccess[] = function (UiForm $form) use ($onSuccess, $onError): void {
$form->onSuccess[] = function (UiForm $form) use ($onSuccess): void {
$values = $form->getFormValues();
$ssid = strtoupper(trim($values->ssid));
if (!$this->technicolor->saveKeys($ssid)) {
$onError();
} else {
$onSuccess($ssid);
}
$onSuccess($ssid);
};
return $form;
}
Expand Down
113 changes: 113 additions & 0 deletions site/app/Http/Client/HttpClient.php
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);
}

}
100 changes: 100 additions & 0 deletions site/app/Http/Client/HttpClientRequest.php
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;
}

}
43 changes: 43 additions & 0 deletions site/app/Http/Client/HttpClientResponse.php
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;
}

}
Loading