Switched Xrpc client to Guzzle.
This commit is contained in:
parent
d3bdd8f3a2
commit
676e3fb217
3 changed files with 316 additions and 115 deletions
|
@ -1,7 +1,6 @@
|
|||
{
|
||||
"require": {
|
||||
"php": ">=8.4",
|
||||
"ext-curl": "*",
|
||||
"ext-mbstring": "*",
|
||||
"flashwave/index": "^0.2501",
|
||||
"flashii/rpcii": "~4.0",
|
||||
|
@ -13,7 +12,8 @@
|
|||
"nesbot/carbon": "~3.8",
|
||||
"vlucas/phpdotenv": "~5.6",
|
||||
"filp/whoops": "~2.17",
|
||||
"phpseclib/phpseclib": "~3.0"
|
||||
"phpseclib/phpseclib": "~3.0",
|
||||
"guzzlehttp/guzzle": "~7.0"
|
||||
},
|
||||
"autoload": {
|
||||
"classmap": [
|
||||
|
|
288
composer.lock
generated
288
composer.lock
generated
|
@ -4,7 +4,7 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "42e27bedc2ac1251eca61bf7df4f0342",
|
||||
"content-hash": "1045c8f605203fae19361d659fb64c54",
|
||||
"packages": [
|
||||
{
|
||||
"name": "carbonphp/carbon-doctrine-types",
|
||||
|
@ -655,6 +655,215 @@
|
|||
],
|
||||
"time": "2024-07-20T21:45:45+00:00"
|
||||
},
|
||||
{
|
||||
"name": "guzzlehttp/guzzle",
|
||||
"version": "7.9.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/guzzle/guzzle.git",
|
||||
"reference": "d281ed313b989f213357e3be1a179f02196ac99b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/d281ed313b989f213357e3be1a179f02196ac99b",
|
||||
"reference": "d281ed313b989f213357e3be1a179f02196ac99b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-json": "*",
|
||||
"guzzlehttp/promises": "^1.5.3 || ^2.0.3",
|
||||
"guzzlehttp/psr7": "^2.7.0",
|
||||
"php": "^7.2.5 || ^8.0",
|
||||
"psr/http-client": "^1.0",
|
||||
"symfony/deprecation-contracts": "^2.2 || ^3.0"
|
||||
},
|
||||
"provide": {
|
||||
"psr/http-client-implementation": "1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"bamarni/composer-bin-plugin": "^1.8.2",
|
||||
"ext-curl": "*",
|
||||
"guzzle/client-integration-tests": "3.0.2",
|
||||
"php-http/message-factory": "^1.1",
|
||||
"phpunit/phpunit": "^8.5.39 || ^9.6.20",
|
||||
"psr/log": "^1.1 || ^2.0 || ^3.0"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-curl": "Required for CURL handler support",
|
||||
"ext-intl": "Required for Internationalized Domain Name (IDN) support",
|
||||
"psr/log": "Required for using the Log middleware"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"bamarni-bin": {
|
||||
"bin-links": true,
|
||||
"forward-command": false
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"src/functions_include.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"GuzzleHttp\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Graham Campbell",
|
||||
"email": "hello@gjcampbell.co.uk",
|
||||
"homepage": "https://github.com/GrahamCampbell"
|
||||
},
|
||||
{
|
||||
"name": "Michael Dowling",
|
||||
"email": "mtdowling@gmail.com",
|
||||
"homepage": "https://github.com/mtdowling"
|
||||
},
|
||||
{
|
||||
"name": "Jeremy Lindblom",
|
||||
"email": "jeremeamia@gmail.com",
|
||||
"homepage": "https://github.com/jeremeamia"
|
||||
},
|
||||
{
|
||||
"name": "George Mponos",
|
||||
"email": "gmponos@gmail.com",
|
||||
"homepage": "https://github.com/gmponos"
|
||||
},
|
||||
{
|
||||
"name": "Tobias Nyholm",
|
||||
"email": "tobias.nyholm@gmail.com",
|
||||
"homepage": "https://github.com/Nyholm"
|
||||
},
|
||||
{
|
||||
"name": "Márk Sági-Kazár",
|
||||
"email": "mark.sagikazar@gmail.com",
|
||||
"homepage": "https://github.com/sagikazarmark"
|
||||
},
|
||||
{
|
||||
"name": "Tobias Schultze",
|
||||
"email": "webmaster@tubo-world.de",
|
||||
"homepage": "https://github.com/Tobion"
|
||||
}
|
||||
],
|
||||
"description": "Guzzle is a PHP HTTP client library",
|
||||
"keywords": [
|
||||
"client",
|
||||
"curl",
|
||||
"framework",
|
||||
"http",
|
||||
"http client",
|
||||
"psr-18",
|
||||
"psr-7",
|
||||
"rest",
|
||||
"web service"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/guzzle/guzzle/issues",
|
||||
"source": "https://github.com/guzzle/guzzle/tree/7.9.2"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/GrahamCampbell",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/Nyholm",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-07-24T11:22:20+00:00"
|
||||
},
|
||||
{
|
||||
"name": "guzzlehttp/promises",
|
||||
"version": "2.0.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/guzzle/promises.git",
|
||||
"reference": "f9c436286ab2892c7db7be8c8da4ef61ccf7b455"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/guzzle/promises/zipball/f9c436286ab2892c7db7be8c8da4ef61ccf7b455",
|
||||
"reference": "f9c436286ab2892c7db7be8c8da4ef61ccf7b455",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.2.5 || ^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"bamarni/composer-bin-plugin": "^1.8.2",
|
||||
"phpunit/phpunit": "^8.5.39 || ^9.6.20"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"bamarni-bin": {
|
||||
"bin-links": true,
|
||||
"forward-command": false
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"GuzzleHttp\\Promise\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Graham Campbell",
|
||||
"email": "hello@gjcampbell.co.uk",
|
||||
"homepage": "https://github.com/GrahamCampbell"
|
||||
},
|
||||
{
|
||||
"name": "Michael Dowling",
|
||||
"email": "mtdowling@gmail.com",
|
||||
"homepage": "https://github.com/mtdowling"
|
||||
},
|
||||
{
|
||||
"name": "Tobias Nyholm",
|
||||
"email": "tobias.nyholm@gmail.com",
|
||||
"homepage": "https://github.com/Nyholm"
|
||||
},
|
||||
{
|
||||
"name": "Tobias Schultze",
|
||||
"email": "webmaster@tubo-world.de",
|
||||
"homepage": "https://github.com/Tobion"
|
||||
}
|
||||
],
|
||||
"description": "Guzzle promises library",
|
||||
"keywords": [
|
||||
"promise"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/guzzle/promises/issues",
|
||||
"source": "https://github.com/guzzle/promises/tree/2.0.4"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/GrahamCampbell",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/Nyholm",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-10-17T10:06:22+00:00"
|
||||
},
|
||||
{
|
||||
"name": "guzzlehttp/psr7",
|
||||
"version": "2.7.0",
|
||||
|
@ -1512,6 +1721,58 @@
|
|||
},
|
||||
"time": "2019-01-08T18:20:26+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/http-client",
|
||||
"version": "1.0.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/php-fig/http-client.git",
|
||||
"reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90",
|
||||
"reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.0 || ^8.0",
|
||||
"psr/http-message": "^1.0 || ^2.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.0.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Psr\\Http\\Client\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "PHP-FIG",
|
||||
"homepage": "https://www.php-fig.org/"
|
||||
}
|
||||
],
|
||||
"description": "Common interface for HTTP clients",
|
||||
"homepage": "https://github.com/php-fig/http-client",
|
||||
"keywords": [
|
||||
"http",
|
||||
"http-client",
|
||||
"psr",
|
||||
"psr-18"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/php-fig/http-client"
|
||||
},
|
||||
"time": "2023-09-23T14:17:50+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/http-factory",
|
||||
"version": "1.1.0",
|
||||
|
@ -2237,16 +2498,16 @@
|
|||
},
|
||||
{
|
||||
"name": "symfony/mime",
|
||||
"version": "v7.2.3",
|
||||
"version": "v7.2.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/mime.git",
|
||||
"reference": "2fc3b4bd67e4747e45195bc4c98bea4628476204"
|
||||
"reference": "87ca22046b78c3feaff04b337f33b38510fd686b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/mime/zipball/2fc3b4bd67e4747e45195bc4c98bea4628476204",
|
||||
"reference": "2fc3b4bd67e4747e45195bc4c98bea4628476204",
|
||||
"url": "https://api.github.com/repos/symfony/mime/zipball/87ca22046b78c3feaff04b337f33b38510fd686b",
|
||||
"reference": "87ca22046b78c3feaff04b337f33b38510fd686b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -2301,7 +2562,7 @@
|
|||
"mime-type"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/mime/tree/v7.2.3"
|
||||
"source": "https://github.com/symfony/mime/tree/v7.2.4"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
@ -2317,7 +2578,7 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-01-27T11:08:17+00:00"
|
||||
"time": "2025-02-19T08:51:20+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/options-resolver",
|
||||
|
@ -2950,16 +3211,16 @@
|
|||
},
|
||||
{
|
||||
"name": "symfony/translation",
|
||||
"version": "v7.2.2",
|
||||
"version": "v7.2.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/translation.git",
|
||||
"reference": "e2674a30132b7cc4d74540d6c2573aa363f05923"
|
||||
"reference": "283856e6981286cc0d800b53bd5703e8e363f05a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/translation/zipball/e2674a30132b7cc4d74540d6c2573aa363f05923",
|
||||
"reference": "e2674a30132b7cc4d74540d6c2573aa363f05923",
|
||||
"url": "https://api.github.com/repos/symfony/translation/zipball/283856e6981286cc0d800b53bd5703e8e363f05a",
|
||||
"reference": "283856e6981286cc0d800b53bd5703e8e363f05a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -3025,7 +3286,7 @@
|
|||
"description": "Provides tools to internationalize your application",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/translation/tree/v7.2.2"
|
||||
"source": "https://github.com/symfony/translation/tree/v7.2.4"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
@ -3041,7 +3302,7 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-12-07T08:18:10+00:00"
|
||||
"time": "2025-02-13T10:27:23+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/translation-contracts",
|
||||
|
@ -3420,7 +3681,6 @@
|
|||
"prefer-lowest": false,
|
||||
"platform": {
|
||||
"php": ">=8.4",
|
||||
"ext-curl": "*",
|
||||
"ext-mbstring": "*"
|
||||
},
|
||||
"platform-dev": {},
|
||||
|
|
|
@ -1,36 +1,28 @@
|
|||
<?php
|
||||
namespace Misuzu\ATProto;
|
||||
|
||||
use CurlHandle;
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
use UnexpectedValueException;
|
||||
use GuzzleHttp\Client as HttpClient;
|
||||
use GuzzleHttp\Exception\RequestException;
|
||||
use GuzzleHttp\Psr7\Request as HttpRequest;
|
||||
use Misuzu\Misuzu;
|
||||
|
||||
class XrpcClient {
|
||||
private readonly CurlHandle $handle;
|
||||
private readonly string $userAgent;
|
||||
private readonly HttpClient $httpClient;
|
||||
|
||||
public function __construct(
|
||||
private string $service
|
||||
) {
|
||||
public function __construct(string $service) {
|
||||
if(!filter_var($service, FILTER_VALIDATE_URL))
|
||||
throw new InvalidArgumentException('$service must be a valid URL');
|
||||
|
||||
$handle = curl_init();
|
||||
if($handle === false)
|
||||
throw new RuntimeException('Failed to initialise cURL.');
|
||||
|
||||
$version = curl_version();
|
||||
if(!is_array($version) || !array_key_exists('version', $version) || !is_string($version['version']))
|
||||
throw new RuntimeException('Failed to retrieve cURL version info.');
|
||||
|
||||
$this->handle = $handle;
|
||||
$this->userAgent = sprintf('Misuzu/%s cURL/%s', Misuzu::version(), $version['version']);
|
||||
}
|
||||
|
||||
public function __destruct() {
|
||||
curl_close($this->handle);
|
||||
$this->httpClient = new HttpClient([
|
||||
'allow_redirects' => true,
|
||||
'base_uri' => sprintf('%s/xrpc/', rtrim($service, '/')),
|
||||
'timeout' => 10,
|
||||
'headers' => [
|
||||
'User-Agent' => sprintf('Misuzu/%s', Misuzu::version()),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -46,98 +38,47 @@ class XrpcClient {
|
|||
array $params,
|
||||
array|object|string|null $data
|
||||
): object {
|
||||
$url = sprintf('%s/xrpc/%s', $this->service, $nsid);
|
||||
if(!empty($params))
|
||||
$url = sprintf('%s?%s', $url, http_build_query($params, encoding_type: PHP_QUERY_RFC3986));
|
||||
$request = new HttpRequest($method, $nsid, $headers);
|
||||
$options = [
|
||||
'query' => $params,
|
||||
];
|
||||
|
||||
$headers = (function($original) {
|
||||
$sanitised = [];
|
||||
foreach($original as $name => $value) {
|
||||
$name = strtolower($name);
|
||||
if(!array_key_exists($name, $sanitised))
|
||||
$sanitised[$name] = $value;
|
||||
if(is_object($data) || is_array($data))
|
||||
$options['json'] = $data;
|
||||
elseif(is_string($data))
|
||||
$options['body'] = $data;
|
||||
|
||||
try {
|
||||
$response = $this->httpClient->send($request, $options);
|
||||
$statusCode = $response->getStatusCode();
|
||||
} catch(RequestException $ex) {
|
||||
$response = $ex->getResponse();
|
||||
$statusCode = $ex->getCode();
|
||||
}
|
||||
|
||||
return $sanitised;
|
||||
})($headers);
|
||||
|
||||
if(is_object($data) || is_array($data)) {
|
||||
$headers['content-type'] = 'application/json';
|
||||
$data = json_encode($data);
|
||||
}
|
||||
|
||||
curl_reset($this->handle);
|
||||
curl_setopt_array($this->handle, [
|
||||
CURLOPT_AUTOREFERER => true,
|
||||
CURLOPT_CUSTOMREQUEST => $method,
|
||||
CURLOPT_FAILONERROR => false,
|
||||
CURLOPT_FOLLOWLOCATION => true,
|
||||
CURLOPT_HEADER => true,
|
||||
CURLOPT_HTTPHEADER => (function($headers) {
|
||||
$lines = [];
|
||||
foreach($headers as $name => $value)
|
||||
$lines[] = sprintf('%s: %s', $name, $value);
|
||||
return $lines;
|
||||
})($headers),
|
||||
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_TCP_FASTOPEN => true,
|
||||
CURLOPT_CONNECTTIMEOUT => 5,
|
||||
CURLOPT_MAXREDIRS => 5,
|
||||
CURLOPT_POSTFIELDS => $data,
|
||||
CURLOPT_PROTOCOLS => CURLPROTO_HTTPS,
|
||||
CURLOPT_TIMEOUT => 10,
|
||||
CURLOPT_URL => $url,
|
||||
CURLOPT_USERAGENT => $this->userAgent,
|
||||
]);
|
||||
|
||||
$response = curl_exec($this->handle);
|
||||
if($response === false)
|
||||
throw new RuntimeException(curl_error($this->handle), curl_errno($this->handle));
|
||||
if($response === true)
|
||||
throw new UnexpectedValueException('Request executed successfully but cURL returned no data.');
|
||||
|
||||
$parts = explode("\r\n\r\n", $response, 2);
|
||||
|
||||
$headerLines = array_reverse(explode("\r\n", $parts[0]));
|
||||
$statusLine = array_pop($headerLines);
|
||||
if(!is_string($statusLine))
|
||||
throw new RuntimeException('Unable to read status header.');
|
||||
|
||||
$statusLineScan = sscanf($statusLine, 'HTTP/%s %d %[^\t\r\n]');
|
||||
if($statusLineScan === null)
|
||||
throw new RuntimeException('Failed to decode HTTP status line.');
|
||||
[$_, $statusCode, $statusLine] = $statusLineScan;
|
||||
|
||||
$statusCode = (int)$statusCode;
|
||||
|
||||
$headers = (function(array $raw) {
|
||||
$headers = [];
|
||||
while(($headerLine = array_pop($headerLines)) !== null) {
|
||||
$headerLine = trim((string)$headerLine);
|
||||
if($headerLine === '')
|
||||
continue;
|
||||
|
||||
$headerParts = explode(':', $headerLine, 2);
|
||||
$headers[strtolower(trim($headerParts[0]))] = count($headerParts) > 1 ? trim($headerParts[1]) : '';
|
||||
}
|
||||
foreach($raw as $name => $value)
|
||||
$headers[strtolower($name)] = implode(', ', $value);
|
||||
return $headers;
|
||||
})($response->getHeaders());
|
||||
|
||||
$body = (string)$response->getBody();
|
||||
if(array_key_exists('content-type', $headers) && str_starts_with($headers['content-type'], 'application/json'))
|
||||
$data = json_decode($parts[1]);
|
||||
else
|
||||
$data = $parts[1];
|
||||
$body = json_decode($body);
|
||||
|
||||
if($statusCode !== 200) {
|
||||
if(is_object($data)
|
||||
&& isset($data->error) && is_string($data->error)
|
||||
&& isset($data->message) && is_string($data->message))
|
||||
throw new RuntimeException(sprintf('%s: %s', $data->error, $data->message), $statusCode);
|
||||
if(is_object($body)
|
||||
&& isset($body->error) && is_string($body->error)
|
||||
&& isset($body->message) && is_string($body->message))
|
||||
throw new RuntimeException(sprintf('%s: %s', $body->error, $body->message), $statusCode);
|
||||
|
||||
throw new RuntimeException(sprintf('HTTP %03d', $statusCode), $statusCode);
|
||||
}
|
||||
|
||||
return (object)[
|
||||
'headers' => $headers,
|
||||
'data' => $data,
|
||||
'data' => $body,
|
||||
];
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue