implement TLS/socket client

This commit is contained in:
yggverse 2024-04-03 02:11:07 +03:00
parent 0973828a82
commit edf0234056
3 changed files with 287 additions and 1 deletions

View File

@ -5,7 +5,57 @@ PHP 8 Library for Gemini Protocol
## Usage
```
composer require yggverse/gemini:dev-main
composer require yggverse/gemini
```
## Client
PHP interface for Gemini protocol queries by TLS socket connection
### Request
```
$request = new \Yggverse\Gemini\Client\Request(
'gemini://betahowto.duckdns.org:1965/archive'
);
```
#### Request::setHost
#### Request::getHost
#### Request::setPort
#### Request::getPort
#### Request::setPath
#### Request::getPath
#### Request::setQuery
#### Request::getQuery
#### Request::getResponse
Execute requested URL and return raw response
```
var_dump(
$request->getResponse()
);
```
### Response
This class provides additional features for the raw response operations
```
$response = new \Yggverse\Gemini\Client\Response(
$request->getResponse()
);
```
#### Response::getCode
#### Response::getMeta
#### Response::getBody
```
var_dump(
$response->getBody()
);
```
## DokuWiki

178
src/Client/Request.php Normal file
View File

@ -0,0 +1,178 @@
<?php
declare(strict_types=1);
namespace Yggverse\Gemini\Client;
class Request
{
private string $_host;
private int $_port;
private string $_path;
private string $_query;
public function __construct(string $url)
{
if ($host = parse_url($url, PHP_URL_HOST))
{
$this->setHost(
$host
);
}
else
{
throw new Exception(); // @TODO
}
if ($port = parse_url($url, PHP_URL_PORT))
{
$this->setPort(
$port
);
}
else
{
$this->setPort(
1965
);
}
if ($path = parse_url($url, PHP_URL_PATH))
{
$this->setPath(
$path
);
}
else
{
$this->setPath(
''
);
}
if ($query = parse_url($url, PHP_URL_QUERY))
{
$this->setQuery(
$query
);
}
else
{
$this->setQuery(
''
);
}
}
public function setHost(string $value): void
{
$this->_host = $value;
}
public function getHost(): string
{
return $this->_host;
}
public function setPort(int $value): void
{
$this->_port = $value;
}
public function getPort(): int
{
return $this->_port;
}
public function setPath(string $value): void
{
$this->_path = $value;
}
public function getPath(): string
{
return $this->_path;
}
public function setQuery(string $value): void
{
$this->_query = $value;
}
public function getQuery(): string
{
return $this->_query;
}
public function getResponse(
int $timeout = 30, // socket timeout, useful for offline resources
?int $limit = null, // content length, null for unlimited
int $chunk = 1024, // chunk size, it's better change it later to 1 @TODO
int &$length = 0, // current response length, do not change without special needs
?int &$code = null, // error code for debug
?string &$message = null, // error message for debug
string &$response = '' // response init, also returning by this method
): ?string
{
$connection = stream_socket_client(
sprintf(
'tls://%s:%d',
$this->_host,
$this->_port
),
$code,
$message,
$timeout,
STREAM_CLIENT_CONNECT,
stream_context_create(
[
'ssl' =>
[
'verify_peer' => false,
'verify_peer_name' => false
]
]
)
);
if (!is_resource($connection))
{
return null;
}
fwrite(
$connection,
sprintf(
"gemini://%s:%d%s%s\r\n",
$this->_host,
$this->_port,
$this->_path,
$this->_query
)
);
while ($part = fgets($connection, $chunk))
{
$length = $length + mb_strlen(
$part
);
if ($limit && $length > $limit)
{
break;
}
$response .= $part;
}
fclose(
$connection
);
return $response;
}
}

58
src/Client/Response.php Normal file
View File

@ -0,0 +1,58 @@
<?php
declare(strict_types=1);
namespace Yggverse\Gemini\Client;
class Response
{
private ?int $_code = null;
private ?string $_meta = null;
private ?string $_body = null;
public function __construct(string $data)
{
$match = [];
preg_match(
'/(?<status>\d{2})\s(?<meta>.*)\r\n(?<body>.*)/su',
$data,
$match
);
if (isset($match['code']))
{
$code = (int) $match['code'];
if ($code >= 10 && $code <= 69)
{
$this->_code = $match['code'];
}
}
if (isset($match['meta']) && mb_strlen($match['meta']) <= 1024)
{
$this->_meta = (string) $match['meta'];
}
if (isset($match['body']))
{
$this->_body = (string) $match['body'];
}
}
public function getCode(): ?int
{
return $this->_code;
}
public function getMeta(): ?string
{
return $this->_meta;
}
public function getBody(): ?string
{
return $this->_body;
}
}