Initial commit
This commit is contained in:
commit
e02e43217c
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
/var/
|
||||||
|
/vendor/
|
||||||
|
/.idea/
|
47
README.md
Normal file
47
README.md
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
# `curl-http2`
|
||||||
|
|
||||||
|
## What is this?
|
||||||
|
|
||||||
|
A `curl` wrapper for PHP that uses `HTTP/2` to send requests over a single TCP connection.
|
||||||
|
|
||||||
|
Provided are:
|
||||||
|
- The main class `JoopSchilder\Http2\Http2`
|
||||||
|
- A simple class for requests supporting `curl` options
|
||||||
|
- A simple class for responses
|
||||||
|
|
||||||
|
## What is this not?
|
||||||
|
|
||||||
|
A production-ready library.
|
||||||
|
I mean, you _are_ of course allowed to use it if you think it's useful.
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
There is some example code in `bin/app.php`.
|
||||||
|
In it's most basic form, usage of this library might look like this:
|
||||||
|
```php
|
||||||
|
use JoopSchilder\Http2\Http2;
|
||||||
|
use JoopSchilder\Http2\Response;
|
||||||
|
|
||||||
|
$http2 = new Http2();
|
||||||
|
|
||||||
|
// Might also be an implementation of the ResponseHandler interface
|
||||||
|
$http2->onResponse(function(Response $response) {
|
||||||
|
var_dump($response);
|
||||||
|
});
|
||||||
|
|
||||||
|
// This creates a request with sensible defaults
|
||||||
|
$request = $http2->createRequest('https://www.twitter.com/');
|
||||||
|
$request->setOptions([CURLOPT_USERAGENT => 'AppleTV6,2/11.1']);
|
||||||
|
$http2->addRequest($request);
|
||||||
|
|
||||||
|
// Requests are not executed until this method is called
|
||||||
|
$http2->execute();
|
||||||
|
```
|
||||||
|
It's a good idea to use a different instance of `Http2` for every host you plan to make a request to.
|
||||||
|
|
||||||
|
## What's next?
|
||||||
|
|
||||||
|
- Add more control to dynamically add requests to an `Http2` instance
|
||||||
|
- Make an `Http2` instance coupled to a domain (as this is its intended use)
|
||||||
|
- Use [`PSR-7`](https://www.php-fig.org/psr/psr-7/) HTTP message interfaces
|
||||||
|
- Add a factory for requests
|
37
bin/app.php
Executable file
37
bin/app.php
Executable file
@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use JoopSchilder\Http2\Http2;
|
||||||
|
use JoopSchilder\Http2\Response;
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../vendor/autoload.php';
|
||||||
|
|
||||||
|
$http2 = new Http2();
|
||||||
|
$http2->onResponse(function (Response $response) {
|
||||||
|
$statusLine = substr($response->getHeader(), 0, strpos($response->getHeader(), "\r"));
|
||||||
|
$statusLine = str_pad($statusLine, 14, ' ', STR_PAD_RIGHT);
|
||||||
|
print("$statusLine {$response->getOriginalUrl()}\n");
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add urls
|
||||||
|
$urls = [
|
||||||
|
'https://twitter.com/survivetheark',
|
||||||
|
'https://twitter.com/missingpeople',
|
||||||
|
'https://twitter.com/elainecrowley',
|
||||||
|
'https://twitter.com/2020comms',
|
||||||
|
'https://twitter.com/goal',
|
||||||
|
'https://twitter.com/cydarmedical',
|
||||||
|
'https://twitter.com/cloakzy',
|
||||||
|
'https://twitter.com/cllrandrewkelly',
|
||||||
|
'https://twitter.com/youranonnews',
|
||||||
|
'https://twitter.com/trickyjabs',
|
||||||
|
];
|
||||||
|
foreach ($urls as $url) {
|
||||||
|
$http2->addRequest($http2->createRequest($url));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute the requests
|
||||||
|
print("Note: it might take some time to establish the TCP connection.\n");
|
||||||
|
print("The benefits of HTTP/2 become more clear if you send more requests to the same server.\n\n");
|
||||||
|
$startTime = microtime(true);
|
||||||
|
$http2->execute();
|
||||||
|
printf("Requests: %d, time(s): %.3f\n", count($urls), microtime(true) - $startTime);
|
19
composer.json
Normal file
19
composer.json
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"name": "joopschilder/php-http2",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Joop Schilder",
|
||||||
|
"email": "jnmschilder@protonmail.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"JoopSchilder\\Http2\\": [
|
||||||
|
"src"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"ext-curl": "*"
|
||||||
|
}
|
||||||
|
}
|
19
composer.lock
generated
Normal file
19
composer.lock
generated
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"_readme": [
|
||||||
|
"This file locks the dependencies of your project to a known state",
|
||||||
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
|
"This file is @generated automatically"
|
||||||
|
],
|
||||||
|
"content-hash": "5b1e133742e667bcab8f73ed237d3adc",
|
||||||
|
"packages": [],
|
||||||
|
"packages-dev": [],
|
||||||
|
"aliases": [],
|
||||||
|
"minimum-stability": "stable",
|
||||||
|
"stability-flags": [],
|
||||||
|
"prefer-stable": false,
|
||||||
|
"prefer-lowest": false,
|
||||||
|
"platform": {
|
||||||
|
"ext-curl": "*"
|
||||||
|
},
|
||||||
|
"platform-dev": []
|
||||||
|
}
|
117
src/Http2.php
Normal file
117
src/Http2.php
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace JoopSchilder\Http2;
|
||||||
|
|
||||||
|
use RuntimeException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class Http2
|
||||||
|
*/
|
||||||
|
class Http2
|
||||||
|
{
|
||||||
|
/** @var resource */
|
||||||
|
private $multihandle;
|
||||||
|
|
||||||
|
/** @var callable */
|
||||||
|
private $responseHandler;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Http2 constructor.
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->multihandle = curl_multi_init();
|
||||||
|
curl_multi_setopt($this->multihandle, CURLMOPT_PIPELINING, CURLPIPE_MULTIPLEX);
|
||||||
|
curl_multi_setopt($this->multihandle, CURLMOPT_MAX_HOST_CONNECTIONS, 1);
|
||||||
|
curl_multi_setopt($this->multihandle, CURLMOPT_MAX_TOTAL_CONNECTIONS, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Http2 destructor.
|
||||||
|
*/
|
||||||
|
public function __destruct()
|
||||||
|
{
|
||||||
|
if (is_resource($this->multihandle)) {
|
||||||
|
curl_multi_close($this->multihandle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $url
|
||||||
|
* @param bool $priorKnowledge
|
||||||
|
* @return Request
|
||||||
|
*/
|
||||||
|
public function createRequest(string $url, bool $priorKnowledge = true): Request
|
||||||
|
{
|
||||||
|
return (new Request($url))->setOptions([
|
||||||
|
CURLOPT_AUTOREFERER => 1,
|
||||||
|
CURLOPT_FOLLOWLOCATION => 1,
|
||||||
|
CURLOPT_HEADER => 1,
|
||||||
|
CURLOPT_HTTP_VERSION => ($priorKnowledge ? CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE : CURL_HTTP_VERSION_2),
|
||||||
|
CURLOPT_MAXREDIRS => 5,
|
||||||
|
CURLOPT_PIPEWAIT => 1,
|
||||||
|
CURLOPT_RETURNTRANSFER => 1,
|
||||||
|
CURLOPT_SSL_VERIFYPEER => 0,
|
||||||
|
CURLOPT_TIMEOUT => 10,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Request $request
|
||||||
|
*/
|
||||||
|
public function addRequest(Request $request): void
|
||||||
|
{
|
||||||
|
$handle = curl_init($request->getUri());
|
||||||
|
curl_setopt_array($handle, $request->getOptions());
|
||||||
|
curl_multi_add_handle($this->multihandle, $handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param callable $responseHandler
|
||||||
|
*/
|
||||||
|
public function onResponse(callable $responseHandler): void
|
||||||
|
{
|
||||||
|
$this->responseHandler = $responseHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function execute(): void
|
||||||
|
{
|
||||||
|
$this->guardNoResponseHandler();
|
||||||
|
do {
|
||||||
|
curl_multi_exec($this->multihandle, $stillRunning);
|
||||||
|
while (false !== ($message = curl_multi_info_read($this->multihandle))) {
|
||||||
|
$handle = $message['handle'];
|
||||||
|
$content = curl_multi_getcontent($handle);
|
||||||
|
|
||||||
|
$header = substr($content, 0, curl_getinfo($handle, CURLINFO_HEADER_SIZE));
|
||||||
|
$content = str_replace($header, '', $content);
|
||||||
|
$url = curl_getinfo($handle, CURLINFO_EFFECTIVE_URL);
|
||||||
|
call_user_func($this->responseHandler, new Response($url, $header, $content));
|
||||||
|
curl_multi_remove_handle($this->multihandle, $handle);
|
||||||
|
curl_close($handle);
|
||||||
|
}
|
||||||
|
usleep(5);
|
||||||
|
} while ($stillRunning > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private function guardNoResponseHandler(): void
|
||||||
|
{
|
||||||
|
if (!is_callable($this->responseHandler)) {
|
||||||
|
throw new RuntimeException('No valid response handler found');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
57
src/Request.php
Normal file
57
src/Request.php
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace JoopSchilder\Http2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class Request
|
||||||
|
*/
|
||||||
|
class Request
|
||||||
|
{
|
||||||
|
/** @var string */
|
||||||
|
private $uri;
|
||||||
|
|
||||||
|
/** @var array */
|
||||||
|
private $options;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request constructor.
|
||||||
|
* @param string $uri
|
||||||
|
*/
|
||||||
|
public function __construct(string $uri)
|
||||||
|
{
|
||||||
|
$this->uri = $uri;
|
||||||
|
$this->options = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getUri(): string
|
||||||
|
{
|
||||||
|
return $this->uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getOptions(): array
|
||||||
|
{
|
||||||
|
return $this->options;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $options
|
||||||
|
* @return Request
|
||||||
|
*/
|
||||||
|
public function setOptions(array $options)
|
||||||
|
{
|
||||||
|
$this->options = array_replace($this->options, $options);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
60
src/Response.php
Normal file
60
src/Response.php
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace JoopSchilder\Http2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class Response
|
||||||
|
*/
|
||||||
|
class Response
|
||||||
|
{
|
||||||
|
/** @var string */
|
||||||
|
private $originalUrl;
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
private $header;
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
private $content;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response constructor.
|
||||||
|
* @param string $originalUrl
|
||||||
|
* @param string $header
|
||||||
|
* @param string $content
|
||||||
|
*/
|
||||||
|
public function __construct(string $originalUrl, string $header, string $content)
|
||||||
|
{
|
||||||
|
$this->originalUrl = trim($originalUrl);
|
||||||
|
$this->header = trim($header);
|
||||||
|
$this->content = trim($content);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getOriginalUrl(): string
|
||||||
|
{
|
||||||
|
return $this->originalUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getHeader(): string
|
||||||
|
{
|
||||||
|
return $this->header;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getContent(): string
|
||||||
|
{
|
||||||
|
return $this->content;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
16
src/ResponseHandler.php
Normal file
16
src/ResponseHandler.php
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace JoopSchilder\Http2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface ResponseHandler
|
||||||
|
*/
|
||||||
|
interface ResponseHandler
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param Response $response
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
function __invoke(Response $response): void;
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user