mirror of
https://codeberg.org/reflect/reflect-client-php.git
synced 2025-09-13 17:43:42 +02:00
feat: add HTTP client (#1)
* feat: add HTTP client * feat(doc): update readme * fix(doc): missed a rename
This commit is contained in:
parent
0330436caa
commit
05866d02f8
5 changed files with 101 additions and 56 deletions
18
README.md
18
README.md
|
@ -1,15 +1,15 @@
|
||||||
# Reflect API UNIX socket client for PHP
|
# Reflect API client for PHP
|
||||||
|
|
||||||
Make requests to endpoints created with the [Reflect API framework](https://github.com/victorwesterlund/reflect) running the same machine.
|
Make requests to an API built using the Reflect API framework over HTTP or UNIX sockets. This program comes with both an extendable/instantiable class that you can integrate with existing PHP code, or as a stand-alone CLI which can be piped into other programs.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
Make a request with `SocketClient->call()`. It will return the response as an array of length 2.
|
Make a request with `Client->call()`. It will return the response as an array of length 2.
|
||||||
- The first value is the HTTP-equivalent response code.
|
- The first value is the HTTP-equivalent response code.
|
||||||
- The second value is the response body
|
- The second value is the response body
|
||||||
|
|
||||||
```php
|
```php
|
||||||
$client = new Reflect\SocketClient("/path/to/socket");
|
$client = new Reflect\Client("<API URL or path to UNIX socket>");
|
||||||
|
|
||||||
$client->call("foo", Method::GET); // (array) [200, "bar"]
|
$client->call("foo", Method::GET); // (array) [200, "bar"]
|
||||||
$client->call("foo", Method::POST, [
|
$client->call("foo", Method::POST, [
|
||||||
|
@ -26,7 +26,7 @@ Requires PHP 8.1 or newer, and of course an instance of the [Reflect socket serv
|
||||||
1. **Install with composer**
|
1. **Install with composer**
|
||||||
|
|
||||||
```
|
```
|
||||||
composer require reflect/socket-server
|
composer require reflect/client
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **Initialize the class**
|
2. **Initialize the class**
|
||||||
|
@ -34,7 +34,7 @@ Requires PHP 8.1 or newer, and of course an instance of the [Reflect socket serv
|
||||||
```php
|
```php
|
||||||
require_once "/vendor/autoload.php";
|
require_once "/vendor/autoload.php";
|
||||||
|
|
||||||
$client = new Reflect\SocketClient("/path/to/socket");
|
$client = new Reflect\Client("<API URL or path to UNIX socket>");
|
||||||
```
|
```
|
||||||
|
|
||||||
3. **Make API request**
|
3. **Make API request**
|
||||||
|
@ -66,7 +66,7 @@ You can also run this from the command line with
|
||||||
php client <socket_file> <endpoint> <http_method> [payload]
|
php client <socket_file> <endpoint> <http_method> [payload]
|
||||||
```
|
```
|
||||||
|
|
||||||
and it will return a serialized JSON array with the same structure as described in the `SocketClient->call()` return.
|
and it will return a serialized JSON array with the same structure as described in the `Client->call()` return.
|
||||||
|
|
||||||
*Example*
|
*Example*
|
||||||
```sh
|
```sh
|
||||||
|
@ -80,12 +80,12 @@ Requires PHP CLI 8.1 or greater, and of course an instance of the [Reflect socke
|
||||||
1. **Clone repo**
|
1. **Clone repo**
|
||||||
|
|
||||||
```
|
```
|
||||||
git clone https://github.com/victorwesterlund/reflect-socket-client-php
|
git clone https://github.com/victorwesterlund/reflect-client-php
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **Run from command line**
|
2. **Run from command line**
|
||||||
|
|
||||||
```
|
```
|
||||||
cd reflect-socket-client-php
|
cd reflect-client-php
|
||||||
php client <socket_file> <endpoint> <http_method> [payload]
|
php client <socket_file> <endpoint> <http_method> [payload]
|
||||||
```
|
```
|
||||||
|
|
4
client
4
client
|
@ -4,7 +4,7 @@
|
||||||
die("Must be run from command line\n");
|
die("Must be run from command line\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
require_once __DIR__ . "/src/SocketClient.php";
|
require_once __DIR__ . "/src/Client.php";
|
||||||
|
|
||||||
// Require 3 to 4 arguments
|
// Require 3 to 4 arguments
|
||||||
if ($argc < 4 || $argc > 4) {
|
if ($argc < 4 || $argc > 4) {
|
||||||
|
@ -13,7 +13,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// Connect to the socket server
|
// Connect to the socket server
|
||||||
$client = new SocketClient($argv[1]);
|
$client = new Client($argv[1], ConType::AF_UNIX);
|
||||||
|
|
||||||
// Get endpoint, method, and optional payload
|
// Get endpoint, method, and optional payload
|
||||||
$args = $argv;
|
$args = $argv;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "reflect/socket-client",
|
"name": "reflect/client",
|
||||||
"description": "Extendable PHP interface for communicating with Reflect API over UNIX sockets",
|
"description": "Extendable PHP interface for communicating with Reflect API over HTTP or UNIX sockets",
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"license": "GPL-2.0-only",
|
"license": "GPL-2.0-only",
|
||||||
"authors": [
|
"authors": [
|
||||||
|
|
88
src/Client.php
Normal file
88
src/Client.php
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
// Allowed HTTP verbs
|
||||||
|
enum Method: string {
|
||||||
|
case GET = "GET";
|
||||||
|
case POST = "POST";
|
||||||
|
case PUT = "PUT";
|
||||||
|
case DELETE = "DELETE";
|
||||||
|
case PATCH = "PATCH";
|
||||||
|
case OPTIONS = "OPTIONS";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Supported connection methods
|
||||||
|
enum ConType {
|
||||||
|
case HTTP;
|
||||||
|
case AF_UNIX;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Client {
|
||||||
|
public function __construct(string $endpoint, ConType $con = null) {
|
||||||
|
$this->_con = $con ?: $this::resolve_contype($endpoint);
|
||||||
|
$this->_endpoint = $endpoint;
|
||||||
|
|
||||||
|
// Initialize socket properties
|
||||||
|
if ($this->_con === ConType::AF_UNIX) {
|
||||||
|
$this->socket = socket_create(AF_UNIX, SOCK_STREAM, 0);
|
||||||
|
$conn = socket_connect($this->socket, $this->_endpoint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve connection type from endpoint string.
|
||||||
|
// If the string is a valid URL we will treat it as HTTP otherwise we will
|
||||||
|
// assume it's a path on disk to a UNIX socket file.
|
||||||
|
private static function resolve_contype(string $endpoint): ConType {
|
||||||
|
return filter_var($endpoint, FILTER_VALIDATE_URL)
|
||||||
|
? ConType::HTTP
|
||||||
|
: ConType::AF_UNIX;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make request and return response over HTTP
|
||||||
|
private function http_call(string $endpoint, Method $method, array $payload = null): array {
|
||||||
|
$data = stream_context_create([
|
||||||
|
"http" => [
|
||||||
|
"header" => "Content-Type: application/json",
|
||||||
|
"method" => $method->value,
|
||||||
|
"ignore_errors" => true,
|
||||||
|
"content" => !empty($payload) ? json_encode($payload) : ""
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$resp = file_get_contents($this->_endpoint . $endpoint, false, $data);
|
||||||
|
|
||||||
|
// Get HTTP response code from $http_response_header which materializes out of
|
||||||
|
// thin air after file_get_contents(). The first header line and second word will
|
||||||
|
// contain the status code.
|
||||||
|
$resp_code = (int) explode(" ", $http_response_header[0])[1];
|
||||||
|
|
||||||
|
return [$resp_code, $resp];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make request and return response over socket
|
||||||
|
private function socket_txn(string $payload): string {
|
||||||
|
$tx = socket_write($this->socket, $payload, strlen($payload));
|
||||||
|
$rx = socket_read($this->socket, 2024);
|
||||||
|
|
||||||
|
if (!$tx || !$rx) {
|
||||||
|
throw new Error("Failed to complete transaction");
|
||||||
|
}
|
||||||
|
|
||||||
|
return $rx;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create HTTP-like JSON with ["<endpoint>","<method>","[payload]"] and return
|
||||||
|
// respone from endpoint as ["<http_status_code", "<json_encoded_response_body>"]
|
||||||
|
public function call(string $endpoint, Method $method, array $payload = null): array {
|
||||||
|
// Call endpoint over UNIX socket
|
||||||
|
if ($this->_con === ConType::AF_UNIX) {
|
||||||
|
return json_decode($this->socket_txn(json_encode([
|
||||||
|
$endpoint,
|
||||||
|
$method->value,
|
||||||
|
$payload
|
||||||
|
])));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call endpoint over HTTP
|
||||||
|
return $this->http_call(...func_get_args());
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,43 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
// Allowed HTTP verbs
|
|
||||||
enum Method: string {
|
|
||||||
case GET = "GET";
|
|
||||||
case POST = "POST";
|
|
||||||
case PUT = "PUT";
|
|
||||||
case DELETE = "DELETE";
|
|
||||||
case PATCH = "PATCH";
|
|
||||||
case OPTIONS = "OPTIONS";
|
|
||||||
}
|
|
||||||
|
|
||||||
class SocketClient {
|
|
||||||
public function __construct(string $path) {
|
|
||||||
$this->socket = socket_create(AF_UNIX, SOCK_STREAM, 0);
|
|
||||||
$conn = socket_connect($this->socket, $path);
|
|
||||||
|
|
||||||
if (!$conn) {
|
|
||||||
throw new Error("Unable to connect to socket");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make request and return response
|
|
||||||
private function txn(string $payload): string {
|
|
||||||
$tx = socket_write($this->socket, $payload, strlen($payload));
|
|
||||||
$rx = socket_read($this->socket, 2024);
|
|
||||||
|
|
||||||
if (!$tx || !$rx) {
|
|
||||||
throw new Error("Failed to complete transaction");
|
|
||||||
}
|
|
||||||
|
|
||||||
return $rx;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create HTTP-like JSON with ["<endpoint>","<method>","[payload]"]
|
|
||||||
public function call(string $endpoint, Method $method, array $payload = null): mixed {
|
|
||||||
return json_decode($this->txn(json_encode([
|
|
||||||
$endpoint,
|
|
||||||
$method->value,
|
|
||||||
$payload
|
|
||||||
])));
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Add table
Reference in a new issue