php-http / curl-client Goto Github PK
View Code? Open in Web Editor NEWcURL client
Home Page: http://httplug.io
License: MIT License
cURL client
Home Page: http://httplug.io
License: MIT License
Hello. I try to install php-http/curl-client
but it fails
composer require php-http/curl-client
Using version ^0.3.0 for php-http/curl-client
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
Your requirements could not be resolved to an installable set of packages.
Problem 1
- Installation request for php-http/curl-client ^0.3.0 -> satisfiable by php-http/curl-client[v0.3.0].
- php-http/curl-client v0.3.0 requires php-http/message-factory ^0.4@dev -> no matching package found.
Potential causes:
- A typo in the package name
- The package is not available in a stable-enough version according to your minimum-stability setting
see <https://groups.google.com/d/topic/composer-dev/_g3ASeIFlrc/discussion> for more details.
Read <https://getcomposer.org/doc/articles/troubleshooting.md> for further common problems.
Installation failed, reverting ./composer.json to its original content.
Document usage of this package.
in #71 we switched from travis-ci to github actions. scrutinizer starts to fail, but looking at the last travis-ci builds, scrutinizer did not happen for a while. the shields on the README that rely on scrutinizer also don't work.
is it ok if I remove the coverage flag and scrutinizer shields?
Super dummy script that confirms this behaviour:
curl.php
<?php
require_once "vendor/autoload.php";
use GuzzleHttp\Promise;
use GuzzleHttp\Psr7\Request;
$pluginClient = new \Http\Client\Common\PluginClient(
new \Http\Client\Curl\Client(\Http\Discovery\MessageFactoryDiscovery::find(), \Http\Discovery\StreamFactoryDiscovery::find())
);
$iteration = 1;
$exit = 0;
while (TRUE) {
if ($iteration > 20) {
echo('Passed' . PHP_EOL);
break;
}
// Initiate each request but do not block
$promises = [
$pluginClient->sendAsyncRequest(new Request('GET', 'http://httpbin.org/status/500')),
$pluginClient->sendAsyncRequest(new Request('GET', 'http://httpbin.org/status/404')),
$pluginClient->sendAsyncRequest(new Request('GET', 'http://httpbin.org/status/404')),
$pluginClient->sendAsyncRequest(new Request('GET', 'http://httpbin.org/status/404')),
$pluginClient->sendAsyncRequest(new Request('GET', 'http://httpbin.org/status/404')),
$pluginClient->sendAsyncRequest(new Request('GET', 'http://httpbin.org/status/404')),
$pluginClient->sendAsyncRequest(new Request('GET', 'http://httpbin.org/status/404')),
$pluginClient->sendAsyncRequest(new Request('GET', 'http://httpbin.org/status/200')),
];
try {
// Wait for the requests to complete, even if some of them fail
$results = Promise\settle($promises)->wait();
foreach ($results as $result) {
if ($result['state'] === Promise\Promise::REJECTED && is_string($result['reason'])) {
printf("Iteration: %d. %s\n", $iteration, $result['reason']);
$exit = 1;
break 2;
}
}
$iteration++;
}
catch (\Exception $e) {
printf("Exception should not be thrown. %s\n", get_class($e));
$exit = 1;
break;
}
}
exit($exit);
testrunner.sh
#!/usr/bin/env bash
COUNTER=1;
while true; do
php curl.php
if [ $? -eq 1 ]; then
echo Failed after: $COUNTER restarts.;
break
fi
let COUNTER=COUNTER+1
sleep 1
done
Outputs:
[email protected]:/var/www/html $ ./testrunner.sh
Passed
Passed
Passed
Passed
Passed
Passed
Passed
Passed
Passed
Iteration: 1. Invoking the wait callback did not resolve the promise
Failed after: 10 restarts.
[email protected]:/var/www/html $ ./testrunner.sh
Iteration: 1. Invoking the wait callback did not resolve the promise
Failed after: 1 restarts.
PHP version:
PHP 7.1.17 (cli) (built: Apr 27 2018 07:21:42) ( NTS ) Copyright (c) 1997-2018 The PHP Group Zend Engine v3.1.0, Copyright (c) 1998-2018 Zend Technologies with Zend OPcache v7.1.17, Copyright (c) 1999-2018, by Zend Technologies with Xdebug v2.6.0, Copyright (c) 2002-2018, by Derick Rethans
I have tested this with enabled and disabled xDebug.
As you can see the tester either fails in the first iteration or it does not fail at all.
When consuming API's the response body ofter includes additional information on 4XX
and 5XX
errors. Currently both sendRequest()
and wait()
throw RequestException
which does not allow accessing the response. This would be resolved by throwing HttpException
instead.
Although I am not sure if sendRequest()
actually ever throws RequestException
even though code is there. Documentation says clients always return response and ErrorPlugin
should be used to throw exceptions.
Different clients seem to be behaving differently. For example React adapter returns response with 404 status instead of throwing.
https://gist.github.com/tuupola/9862b11cc8fb6f7131545fffea900ee5
Repository:
CS:
@api
makes sense here@joelwurtz SocketClient might also be affected by the repository issues, haven't checked though.
given the following snippet
#!/usr/bin/env php
<?php
require_once "vendor/autoload.php";
use Http\Client\Curl\Client;
use Http\Discovery\MessageFactoryDiscovery;
use Http\Discovery\StreamFactoryDiscovery;
$messageFactory = MessageFactoryDiscovery::find();
$client = new Client($messageFactory, StreamFactoryDiscovery::find());
$client->sendAsyncRequest(
$messageFactory->createRequest('GET', 'https://httpstat.us/200?sleep=3000')
)->wait(true)->getBody()->getContents();
the service"
https://httpstat.us/200?sleep=3000
simulate a slow request waiting for 3 seconds.
Where running the script, my application consume A LOT of CPU while waiting for the request
$ time ./index.php
real 0m3.190s
user 0m1.638s
sys 0m1.523s
Here CPU is used 1.638s + 1.523s = 3.161s to process this 3.190s script
when applying this durty patch
diff --git a/src/MultiRunner.php b/src/MultiRunner.php
index 545f39a..5872804 100644
--- a/src/MultiRunner.php
+++ b/src/MultiRunner.php
@@ -82,6 +82,7 @@ class MultiRunner
public function wait(PromiseCore $targetCore = null)
{
do {
+ usleep(1);
$status = curl_multi_exec($this->multiHandle, $active);
$info = curl_multi_info_read($this->multiHandle);
if (false !== $info) {
I get
$ time ./index.php
real 0m3.163s
user 0m0.234s
sys 0m0.211s
now, the CPU is used 0.445s to process the same request.
I would like to add the ability to handle a 100-continue status from the server as I just hit a bug where I would get the following response:
HTTP/1.1 100 Continue
HTTP/1.1 200 OK
Date: Tue, 08 Mar 2016 10:34:43 GMT
Server: Apache/2.2.22 (Debian)
X-Powered-By: PHP/5.4.45-0+deb7u2
etc
It gets the initial header line and then throws an exception parsing HTTP/1.1 200 OK
as a header due to the missing colon.
I fixed it by setting an empty "Expect" header. Somewhere an "Expect 100-continue" was being added to my request (I wasn't explicitly setting this).
Could we check the initial status line for this status, and then discard the first 2 lines and resume parsing the actual response?
I'm using the curl-client to run SOAP requests from Phpro/SoapClient.
It includes a debug method that brings up request and response headers, so we can inspect what's going on. I also instantiated a PluginClient with a LoggerPlugin, just to be on the safe side. I'm debugging a supposedly missing Content-Length
header my server is complaining about.
A request I'm running reports on those two types of logs the header Content-Length: 1150
, while the body actually has 1596 chars. After a lot of digging around, I found that those loggers refer to headers inside the RequestInterface
object, while the Content-Length
header is silently changed inside Http\Client\Curl\Client::createHeaders()
- but only passed as cURL options, and not upwards as well.
For the sanity of other developers, this value should also be changed on the request object. I'm not doing a PR as I don't even know if that object is supposed to be modified, or the best way to do it around here.
From https://github.com/php-http/httplug-quickstart/pull/1#issuecomment-312874509
Currently Guzzle automatically calls the queue in the shutdown handler or can be called manually. The queue is responsible for waiting on Promises. In our cURL client the only solution right now is to manually do that for every promise.
@mekras can you tell me why 0.4 message-factory is required? Ever since the 0.3 release we didn't have a single commit.
I also noticed that both the common psr7 implementations are required in the project. Why is that?
MultiRunner class uses function curl_strerror, which is not supported by HHVM.
HTTP status code should be passed as integer to ResponseInterface::withStatus()
, because string can lead to an error in some implementations (in [Slim](https://github.com/slimphp/Slim/blob/48fb2ce25255ad73f0c322f8914d289d8e55e923/Slim/Http
/Response.php#L206) for example).
I'm sending a request method PUT
Fatal error: Out of memory (allocated 801898496) (tried to allocate 800528777 bytes) in php-http\curl-client\src\Client.php on line 183
why CURLOPT_POSTFIELDS ?
sample code
$stream = new Stream($file_path, 'rb');
$request = new Request('http://localhost/test.php', 'PUT', $stream);
$response = $client->sendRequest($request);
Description PSR-7 Streams written
Attempting to represent the body of a message as a string can easily consume more memory than intended because the body must be stored completely in memory. Attempting to store the body of a request or response in memory would preclude the use of that implementation from being able to work with large message bodies.
but Client.php reading full body in variable (in memory).
what to do?
I'm trying to create a small library usign php-http
.
Here the composer.json
{
"require": {
"php-http/message": "^1.2",
"php-http/client-common": "^1.1",
"php-http/discovery": "^0.8.0",
"puli/composer-plugin": "^1.0@beta,>=1.0.0-beta9"
},
"require-dev": {
"php-http/curl-client": "^1.4"
},
"minimum-stability": "beta"
}
I run composer install
and all vendors are installed
I've then created a simple demo.php
file with this code:
include_once('vendor/autoload.php');
use Http\Discovery\HttpClientDiscovery;
use Http\Discovery\MessageFactoryDiscovery;
$httpClient = HttpClientDiscovery::find();
echo get_class($httpClient);
but I get
PHP Catchable fatal error: Argument 1 passed to Http\Client\Curl\Client::__construct() must be an instance of Http\Message\MessageFactory, none given, called in /var/www/demo-library/vendor/php-http/discovery/src/HttpClientDiscovery.php on line 23 and defined in /var/www/demo-library/vendor/php-http/curl-client/src/Client.php on line 72
That seems correct because Http\Client\Curl\Client
is declared as
public function __construct(
MessageFactory $messageFactory,
StreamFactory $streamFactory,
array $options = []
) {
$this->messageFactory = $messageFactory;
$this->streamFactory = $streamFactory;
$this->options = $options;
}
Am I doing something wrong ?
As decided in php-http/httplug#98
http://docs.php-http.org/en/latest/clients/curl-client.html
I just followed steps on the page.
And there are 2 packages now in my composer.json
:
"require": {
"php-http/curl-client": "^2.0",
"php-http/message": "^1.7"
}
http://docs.php-http.org/en/latest/clients/curl-client.html#usage
I've copied one of these code snippets and I noticed there is an incompatibility.
use Http\Client\Curl\Client;
use Http\Message\MessageFactory\DiactorosMessageFactory;
use Http\Message\StreamFactory\DiactorosStreamFactory;
$client = new Client(new DiactorosMessageFactory(), new DiactorosStreamFactory());
php-http/curl-client
package is working with Psr\Http\Message\...
interfaces. But php-http/message
package implements Http\Message\...
interfaces.
When I changed the version "php-http/curl-client": "^2.0"
to "^1.7"
, the incompatibility is solved. Curl client works with Http\Message\...
interfaces on ^1.7
version.
So, what is standard?
Which versions of php-http
packages work compatible with each other?
Like we did in our other clients and adapters.
What do you think?
Not sure which project to file this under, but curl-client seems the most appropriate as this behavior does not occur with guzzle6-adapter.
Basically, if the Stopwatch plugin is attached to the client (as it would be in a default configuration running under Symfony with httplug-bundle), the CurlClient ignores the reject handler on any request.
Test script:
use Http\Client\Curl\Client;
use Http\Client\Common\PluginClient;
use Http\Client\Common\Plugin\ErrorPlugin;
use Http\Client\Common\Plugin\StopwatchPlugin;
use GuzzleHttp\Psr7\Request;
use Symfony\Component\Stopwatch\Stopwatch;
$clients = [
'normal_client' => new PluginClient(new Client(), [
new ErrorPlugin()
]),
'stopwatch_client' => new PluginClient(new Client(), [
new ErrorPlugin(),
new StopwatchPlugin(new Stopwatch()),
]),
];
$urls = [
'http://www.google.com/',
'http://localhost:8000/asdfasdfasddf',
'http://www.nosuch.domain/foo'
];
$promises = [];
foreach ($clients as $clientName => $client) {
foreach ($urls as $url) {
$promises[] = $client->sendAsyncRequest(new Request('GET', $url))
->then(
function ($response) use ($clientName, $url) {
echo "[$clientName] $url SUCCESS " . $response->getStatusCode() . ' ' . $response->getReasonPhrase() . "\n";
}, function ($e) use ($clientName, $url) {
echo "[$clientName] $url ERROR " . $e->getMessage() . "\n";
}
);
}
}
foreach ($promises as $promise) {
try {
$promise->wait();
} catch (\Exception $e) {
}
}
Expected output:
[normal_client] http://www.nosuch.domain/foo ERROR Could not resolve host: www.nosuch.domain
[normal_client] http://localhost:8000/asdfasdfasddf ERROR Not Found
[normal_client] http://www.google.com/ SUCCESS 200 OK
[stopwatch_client] http://www.nosuch.domain/foo ERROR Could not resolve host: www.nosuch.domain
[stopwatch_client] http://localhost:8000/asdfasdfasddf ERROR Not Found
[stopwatch_client] http://www.google.com/ SUCCESS 200 OK
Actual output:
[normal_client] http://www.nosuch.domain/foo ERROR Could not resolve host: www.nosuch.domain
[normal_client] http://localhost:8000/asdfasdfasddf ERROR Not Found
[normal_client] http://www.google.com/ SUCCESS 200 OK
[stopwatch_client] http://www.google.com/ SUCCESS 200 OK
If you swap out Curl\Client for Guzzle6\Client, the expected output (with slightly different error messages) is seen.
This implementation used to be a demo case for the Httplug interfaces and PSR-18. Meanwhile, guzzle and buzz both implement PSR-18 and symfony also provides an HTTP client that comes with a PSR-18 wrapper.
There are a couple of problems with this client: #55 and #59
Without wanting to devalue the effort put into this, i want to raise the question if we still need to maintain this, or better focus energy on making guzzle | buzz | symfony/http-client as good as possible?
Use ResponseBuilder instead.
This will drop PHP5 and force us to release a new major version.
This will require some work, since the curl functions no longer return resources.
When using and async requests CurlPromise
seems to ignore the ErrorPlugin
and throws the original exception instead. For example in case of 404
response ErrorPlugin
is supposed to thrown ClientErrorException
.
use Http\Client\Curl\Client as Curl;
use Http\Message\MessageFactory\DiactorosMessageFactory;
use Http\Message\StreamFactory\DiactorosStreamFactory;
use Http\Client\Common\Plugin\ErrorPlugin;
use Http\Client\Common\PluginClient;
use Psr\Http\Message\ResponseInterface;
$nosuch = (new DiactorosMessageFactory)->createRequest("GET", "http://google.com/nosuch");
$curl = new Curl(new DiactorosMessageFactory(), new DiactorosStreamFactory());
$client = new PluginClient($curl, [new ErrorPlugin]);
$promise = $client
->sendAsyncRequest($nosuch)
->then(function (ResponseInterface $response) {
print date("H:s") . " got foo\n";
return $response;
});
try {
$promise->wait();
} catch (\Exception $exception) {
print "async: " . get_class($exception) . "\n";
}
try {
$client->sendRequest($nosuch);
} catch (\Exception $exception) {
print "sync: " . get_class($exception) . "\n";
}
/*
async: Http\Client\Exception\RequestException
sync: Http\Client\Common\Exception\ClientErrorException
*/
It appears CURLOPT_FOLLOWLOCATION
is hardcoded to false here for some strange reason.
Obviously this makes it impossible to directly interact with an entire category of HTTP responses (3xx) in any way with this library.
Was this done intentionally or is it simply an accidental addition? If the latter I can submit a PR to fix.
Note: I realise I'm possibly missing something - is there a way to set this setting at the request stage?
In src/PromiseCore.php:79
, there are 2 assertions as string:
assert('is_resource($handle)');
assert('get_resource_type($handle) === "curl"');
This behavior is deprecated in PHP 7.2 (see http://php.net/manual/en/function.assert.php).
PHP version: 7.4.1
Description
How to reproduce
Possible Solution
Additional context
I don't know if this is a normal behaviour but if I send a large body (a file > 1MB) the CURLOPT_POSTFIELDS option is not used but the CURLOPT_READFUNCTION instead and in the createHeaders
method the content-length
header is set to 0.
Doing this, my server doest not reveive data, all is empty.
So 2 possible scenario :
content-length
should be set with the body size if present.If I set the content-length header with the body, my server receive all the data and have an expected behaviour, I put this for my test :
if ('content-length' === $header) {
$values = [0];
if (array_key_exists(CURLOPT_POSTFIELDS, $options)) {
$values = [strlen($options[CURLOPT_POSTFIELDS])];
} else {
$body = $request->getBody();
$bodySize = $body->getSize();
if ($bodySize > 0) {
$values = [(string) $bodySize];
}
}
}
but maybe the CURLOPT_INFILESIZE
is a better option
PHP message: PHP Fatal error: Uncaught TypeError: Return value of Http\Client\Curl\PromiseCore::getException() must implement interface Throwable, instance of Nyholm\Psr7\Response returned in /var/www/releases/20191015093018/vendor/php-http/curl-client/src/PromiseCore.php:194
Stack trace:
#0 /var/www/releases/20191015093018/vendor/php-http/curl-client/src/CurlPromise.php(103): Http\Client\Curl\PromiseCore->getException()
#1 /var/www/releases/20191015093018/vendor/sentry/sentry/src/Transport/HttpTransport.php(110): Http\Client\Curl\CurlPromise->wait()
#2 /var/www/releases/20191015093018/vendor/sentry/sentry/src/Client.php(117): Sentry\Transport\HttpTransport->send(Object(Sentry\Event))
#3 /var/www/releases/20191015093018/vendor/sentry/sentry/src/Client.php(103): Sentry\Client->captureEvent(Array, Object(Sentry\State\Scope))
Just found some examples using the curl_multi_init
the perform async requests here: https://github.com/stil/curl-easy/
The request queue is defined in the file: https://github.com/stil/curl-easy/blob/master/src/cURL/RequestsQueue.php
I think that the idea is here but we can't use the whole logic here. In cURL sync processing and async processing is something different and maybe must be handled in different objects...
If the curl_reset
function is used in the client, we must ensure that there is no workaround with async mode (see #1).
I'm not sure where this gets swallowed, but I'm trying following:
use Http\Client\Curl\Client;
use Http\Client\HttpClient;
use Http\Message\MessageFactory\DiactorosMessageFactory;
use Http\Message\StreamFactory\DiactorosStreamFactory;
use Zend\Diactoros\Request;
$client = new Client(new DiactorosMessageFactory(), new DiactorosStreamFactory());
$url = 'http://localhost:8085/api/v2/messages?start=0&limit=50';
$response = $this->client->sendRequest(
(new Request($url))->withAddedHeader('Accept', 'application/json')
);
var_dump((string) $response->getBody()); // 404 page not found
file_get_contents($url); // the JSON I wanted
This is while interacting with MailHog.
While debugging, it seems like the curl options are as following:
$options = [
42 => true,
19913 => true,
52 => false,
84 => 2,
10002 => 'http://localhost:8025/api/v2/messages?start=0&limit=50',
11036 => '',
10023 => [
0 => 'Host: localhost:8025',
],
];
Translated in curl options constants, that would be:
$options = [
CURLOPT_HEADER => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_HTTP_VERSION => 2,
CURLOPT_URL => 'http://localhost:8025/api/v2/messages?start=0&limit=50',
CURLOPT_CUSTOMREQUEST => '',
CURLOPT_HTTPHEADER => [
0 => 'Host: localhost:8025',
],
];
Does anybody have a clue on why this behavior may happen? Note that requests seem to go to port 80.
First of all: thanks for your amazing job! <3
Would you mind releasing a new version in order to have php-httplug ^2.0
support in a stable release?
Thanks a lot!
As there is no conventional way to configure client, the best way to setup CurlHttpCliebnt is to use PHP cURL constants as options keys.
Chaining promises appears to be either broken or unsupported. The documentation claims general Promise/A+ compliance, so I'm assuming the former. Regardless, I noticed that the standard ErrorPlugin was not behaving properly when curl-client was used as the client implementation. Execution of the plugin stack simply stopped when ErrorPlugin threw an exception.
Here's an example of the issue:
use Http\Client\Common\PluginClient;
use Http\Client\Common\Plugin\ErrorPlugin;
use Http\Client\Curl\Client;
use Zend\Diactoros\Request;
use Zend\Diactoros\Response\TextResponse;
$client = new PluginClient(new Client(), [new ErrorPlugin()]);
$promise = $client->sendAsyncRequest(new Request('http://localhost:10000/bogus.php', 'GET'))->then(null, function () {
return new TextResponse('test');
});
// Results in ClientErrorException, which should be averted by the onRejected callback above.
var_dump((string) $promise->wait()->getBody());
After digging through the code, it looks like the root problem is that all promises in a chain share the same PromiseCore object. PromiseCore simply executes all onFulfilled callbacks from all promises in the order they were created when a valid HTTP response is received, or all onRejected callbacks if the request was unsuccessful. As such, all promises that share that core return the same response or throw the same exception. This approach has a number of problems:
Point 4 also leads to a far more insidious error that can occur when issuing multiple asynchronous requests at once. This might technically qualify as a separate issue since it stems from the handling of curl_multi_info_read, but I'm mentioning it here since it's a consequence of exceptions thrown from onFulfilled callbacks. Exceptions triggered during the resolution of one response can be thrown when requesting the resolution of a completely different response.
use Http\Client\Common\PluginClient;
use Http\Client\Common\Plugin\ErrorPlugin;
use Http\Client\Curl\Client;
use Zend\Diactoros\Request;
$client = new PluginClient(new Client(), [new ErrorPlugin()]);
$bad = $client->sendAsyncRequest(new Request('http://localhost:10000/bogus.php', 'GET'));
$good = $client->sendAsyncRequest(new Request('http://localhost:10000/server.php', 'GET'));
// Results in ClientErrorException from failure of $bad request.
var_dump((string) $good->wait()->getBody());
I'm presently working on a fix, but I wanted to make the issue known.
During sendRequest the Client checks for the existence of a curl handle in the handle
property.
If there is one, it calls curl_reset
on it and continues with that handle.
The only way to destroy a curl handle is with the destructor of the Client.
The problem with this is how curl_reset
works.
This function is only resetting the options, but not the underlying socket.
From the curl manual
It does not change the following information kept in the handle: live connections, the Session ID cache, the DNS cache, the cookies and shares.
In long running scripts this leads to more and more open sockets in CLOSE_WAIT
status.
Depending on the remote servers.
Until all allowed file descriptors are in use.
The workaround is to construct a new Client object for every request, so the destructor gets called when a request is finished.
A better way would be to explicitely close the handle with curl_close
after each request and use a new one for the next request.
I'd recommend relying on guzzle/promise to fix all these issues as the implementation there is correct.
See also https://promisesaplus.com/
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.