-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[HttpClient] Streaming request changes in HttplugClient result in curl error CURLE_SEND_FAIL_REWIND #59489
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Thanks for your swift response! That change does not seem to fix the error though: Rewinding the request body works as expected. The error mentioned in this issue is happening at curl level:
It seems to be the internal curl stream that cannot be rewind properly. I've also found this debug information inside
(Note : the 401 is ntlm asking to reauthenticate which is handled by curl, the credentials are valid) |
OK, so basically we'd need to implement https://curl.se/libcurl/c/CURLOPT_SEEKFUNCTION.html but this isn't possible in PHP... 8000I'm wondering if this would help? --- a/src/Symfony/Component/HttpClient/Psr18Client.php
+++ b/src/Symfony/Component/HttpClient/Psr18Client.php
@@ -100,7 +100,7 @@ final class Psr18Client implements ClientInterface, RequestFactoryInterface, Str
$options = [
'headers' => $headers,
- 'body' => static fn (int $size) => $body->read($size),
+ 'body' => $body->detach(),
];
|
This change doesn't work either:
This dump info contains the previous request basically. I think you're right: when using either |
Before the #58856, the content was give as a string to curl. A string it easy to rewind so curl just did it. Not that we give it a read function, it cannot rewind anymore.
It's strange to me that both Looks like NTLM is a mess. :D
Actually, looking at the behavior we have here, I'd say this might be more performant than figuring out how we could rewind the stream, since apparently reusing connections requires sending a request, having it fail and have curl retry. |
If you run some bechmarks maybe and help us decide if we should make CURLOPT_FORBID_REUSE the default when auth_ntlm is used? |
It's a regular
I was playing with those things cause that was my initial hunch as well. The pasted example is not correct. It actually uses Content-Length with empty Transfer-Encoding.
Don't agree there, but it's also not the worst thing I've seen in Microsoft ... :D
Wow ... This is where things become weird: So my benchmark is using http-client 7.2.2.
The benchmark looks like this: use Symfony\Component\HttpClient\CurlHttpClient;
use Symfony\Component\HttpClient\HttplugClient;
use Symfony\Component\Stopwatch\Stopwatch;
$_ENV['SERVICE_ATTEMPTS'] = 20;
$client = $client = new HttplugClient(new CurlHttpClient([
'auth_ntlm' => [$_ENV['SERVICE_USER'], $_ENV['SERVICE_PASS']],
'extra' => [
'curl' => [
\CURLOPT_FORBID_REUSE => true,
]
]
]));
$request = $client->createRequest('POST', 'foo')
->withHeader('SOAPAction', '"doesnotexist"')
->withHeader('Content-Type', 'text/xml; charset="utf-8"')
->withBody($client->createStream(<<<EOXML
<xxx />
EOXML
));
dump('Sending '.$_ENV['SERVICE_ATTEMPTS'].' request...');
$stopWatch = new Stopwatch(true);
$stopWatch->start('request');
try {
for ($i=0; $i<$_ENV['SERVICE_ATTEMPTS']; $i++) {
dump('Attempt '.$i);
$response = $client->sendRequest($request);
$stopWatch->lap('request');
echo $response->getBody() . PHP_EOL;
}
} catch (\Exception $e) {
dump($e->getMessage());
dd($e->getTraceAsString());
}
$event = $stopWatch->stop('request');
dump('total duration (ms): '.$event->getDuration());
dump('AVG duration (ms): '.$event->getDuration()/$_ENV['SERVICE_ATTEMPTS']); The results are strange to me:
I've tried the same flow a couple of times on different period to make sure the results are representative and not too affected by the server response time. I also choose to send a non-existing soap method to get an instant soap error back, to make sure the server logic hasn't too much of an impact either. So I can only conclude that:
If streaming the body is soo much slower for the same request, wouldn't it make more sense to:
I've debugged the buffer size that is provided to the function and it is 65536 bytes. That is far beyond the size of my request payload. |
That's not possible : detach() returns a resource, not a PSR7 object. Thanks for the benchmarks! So let's set CURLOPT_FORBID_REUSE when auth_ntlm is in use. Up for a PR doing so? This makes me think: can you also benchmark with using $body->detach() instead of the current close? |
It's a seekable memory stream internally.
It's similar to
That is possible and would fix the error indeed. The performance overhead is something that is keeping me reluctant to choosing that solution though. It's consitently double as slow on this service ... What's your thought on that topic? |
If CURLOPT_FORBID_REUSE fixes the issue, I'd still do it, regardless of the perf issue. |
I submitted the fix in #59648 by:
|
Thanks. I've applied the patch on HttplugClient as well and it seems to work perfectly without any stream issues nor loss in performance. 👍 |
…NTLM connections (nicolas-grekas, ajgarlag) This PR was merged into the 7.2 branch. Discussion ---------- [HttpClient] Fix retrying requests with `Psr18Client` and NTLM connections | Q | A | ------------- | --- | Branch? | 7.2 | Bug fix? | yes | New feature? | no | Deprecations? | no | Issues | Fix #59489, Fix #59629 | License | MIT Commits ------- b647cdf Prevent empty request body stream in HttplugClient and Psr18Client f0239d8 [HttpClient] Fix retrying requests with Psr18Client and NTLM connections
Uh oh!
There was an error while loading. Please reload this page.
Symfony version(s) affected
7.2.2+
Description
Since this change in 7.2.2 that allows for streaming the HTTP request body with
CURLOPT_READFUNCTION
instead ofCURLOPT_POSTFIELDS
: bbdf0e0 (introduced by @nicolas-grekas)We are receiving: CURLE_SEND_FAIL_REWIND (65)
This happens on the second request. The first request gets handled as expected.
The server is private and cannot be shared.
Some details:
This happens on:
Our best guess is that using
CURLOPT_READFUNCTION
in combination with NTLM is resulting in this specific error insidecurl
.When we use this code-change on other endpoints, it works as expected.
We found following issues on the web which might or might not be related:
How to reproduce
As mentioned, the server is private and cannot be shared.
The configuration of the client is straight forward and looks like this:
ℹ️ The first request that is sent over the shared connection will succeed as usual. The second request will fail with the error
Unable to read stream contents: Necessary data rewind wasn't possible
The request is a SOAP request that looks like this:
which results in following curl options:
Let me know if I can add anything to this issue report to make it easier for you to reproduce or figure out what is going wrong.
Possible Solution
We are not sure what is causing this specific error. It might be an issue in
curl
instead of symfony/http-client.Following configuration got us around the actual issue, but seems like a bad idea performance-wise.
Another option around the problem would be to introduce a new
CurlHttpClient
option.Something like
$options['stream_request']
which has a default of true to use theCURLOPT_READFUNCTION
implementation, but can be set to false for using theCURLOPT_POSTFIELDS
logic.Additional Context
No response
The text was updated successfully, but these errors were encountered: