1
0
mirror of https://github.com/guzzle/guzzle.git synced 2025-01-17 21:38:16 +01:00
guzzle/tests/Handler/CurlFactoryTest.php
Graham Campbell f5aa695156
Robust handling of responses with invalid headers (#2872)
* Robust handling of responses with invalid headers

Co-Authored-By: Tim Düsterhus <209270+TimWolla@users.noreply.github.com>

* Cleanup

Co-authored-by: Tim Düsterhus <209270+TimWolla@users.noreply.github.com>
2021-03-21 18:40:38 +01:00

902 lines
33 KiB
PHP

<?php
namespace GuzzleHttp\Test\Handler;
use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Handler;
use GuzzleHttp\Handler\CurlFactory;
use GuzzleHttp\Handler\EasyHandle;
use GuzzleHttp\Promise as P;
use GuzzleHttp\Psr7;
use GuzzleHttp\Tests\Helpers;
use GuzzleHttp\Tests\Server;
use GuzzleHttp\TransferStats;
use PHPUnit\Framework\TestCase;
use Psr\Http\Message\ResponseInterface;
/**
* @covers \GuzzleHttp\Handler\CurlFactory
*/
class CurlFactoryTest extends TestCase
{
public static function setUpBeforeClass(): void
{
$_SERVER['curl_test'] = true;
unset($_SERVER['_curl']);
}
public static function tearDownAfterClass(): void
{
unset($_SERVER['_curl'], $_SERVER['curl_test']);
}
public function testCreatesCurlHandle()
{
Server::flush();
Server::enqueue([
new Psr7\Response(200, [
'Foo' => 'Bar',
'Baz' => 'bam',
'Content-Length' => 2,
], 'hi')
]);
$stream = Psr7\Utils::streamFor();
$request = new Psr7\Request('PUT', Server::$url, [
'Hi' => ' 123',
'Content-Length' => '7'
], 'testing');
$f = new Handler\CurlFactory(3);
$result = $f->create($request, ['sink' => $stream]);
self::assertInstanceOf(EasyHandle::class, $result);
if (\PHP_VERSION_ID >= 80000) {
self::assertInstanceOf(\CurlHandle::class, $result->handle);
} else {
self::assertIsResource($result->handle);
}
self::assertIsArray($result->headers);
self::assertSame($stream, $result->sink);
\curl_close($result->handle);
self::assertSame('PUT', $_SERVER['_curl'][\CURLOPT_CUSTOMREQUEST]);
self::assertSame(
'http://127.0.0.1:8126/',
$_SERVER['_curl'][\CURLOPT_URL]
);
// Sends via post fields when the request is small enough
self::assertSame('testing', $_SERVER['_curl'][\CURLOPT_POSTFIELDS]);
self::assertEquals(0, $_SERVER['_curl'][\CURLOPT_RETURNTRANSFER]);
self::assertEquals(0, $_SERVER['_curl'][\CURLOPT_HEADER]);
self::assertSame(150, $_SERVER['_curl'][\CURLOPT_CONNECTTIMEOUT]);
self::assertInstanceOf('Closure', $_SERVER['_curl'][\CURLOPT_HEADERFUNCTION]);
if (\defined('CURLOPT_PROTOCOLS')) {
self::assertSame(
\CURLPROTO_HTTP | \CURLPROTO_HTTPS,
$_SERVER['_curl'][\CURLOPT_PROTOCOLS]
);
}
self::assertContains('Expect:', $_SERVER['_curl'][\CURLOPT_HTTPHEADER]);
self::assertContains('Accept:', $_SERVER['_curl'][\CURLOPT_HTTPHEADER]);
self::assertContains('Content-Type:', $_SERVER['_curl'][\CURLOPT_HTTPHEADER]);
self::assertContains('Hi: 123', $_SERVER['_curl'][\CURLOPT_HTTPHEADER]);
self::assertContains('Host: 127.0.0.1:8126', $_SERVER['_curl'][\CURLOPT_HTTPHEADER]);
}
public function testSendsHeadRequests()
{
Server::flush();
Server::enqueue([new Psr7\Response()]);
$a = new Handler\CurlMultiHandler();
$response = $a(new Psr7\Request('HEAD', Server::$url), []);
$response->wait();
self::assertTrue($_SERVER['_curl'][\CURLOPT_NOBODY]);
$checks = [\CURLOPT_READFUNCTION, \CURLOPT_FILE, \CURLOPT_INFILE];
foreach ($checks as $check) {
self::assertArrayNotHasKey($check, $_SERVER['_curl']);
}
self::assertEquals('HEAD', Server::received()[0]->getMethod());
}
public function testCanAddCustomCurlOptions()
{
Server::flush();
Server::enqueue([new Psr7\Response()]);
$a = new Handler\CurlMultiHandler();
$req = new Psr7\Request('GET', Server::$url);
$a($req, ['curl' => [\CURLOPT_LOW_SPEED_LIMIT => 10]]);
self::assertEquals(10, $_SERVER['_curl'][\CURLOPT_LOW_SPEED_LIMIT]);
}
public function testCanChangeCurlOptions()
{
Server::flush();
Server::enqueue([new Psr7\Response()]);
$a = new Handler\CurlMultiHandler();
$req = new Psr7\Request('GET', Server::$url);
$a($req, ['curl' => [\CURLOPT_HTTP_VERSION => \CURL_HTTP_VERSION_1_0]]);
self::assertEquals(\CURL_HTTP_VERSION_1_0, $_SERVER['_curl'][\CURLOPT_HTTP_VERSION]);
}
public function testValidatesVerify()
{
$f = new Handler\CurlFactory(3);
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('SSL CA bundle not found: /does/not/exist');
$f->create(new Psr7\Request('GET', Server::$url), ['verify' => '/does/not/exist']);
}
public function testCanSetVerifyToFile()
{
$f = new Handler\CurlFactory(3);
$f->create(new Psr7\Request('GET', 'http://foo.com'), ['verify' => __FILE__]);
self::assertEquals(__FILE__, $_SERVER['_curl'][\CURLOPT_CAINFO]);
self::assertEquals(2, $_SERVER['_curl'][\CURLOPT_SSL_VERIFYHOST]);
self::assertTrue($_SERVER['_curl'][\CURLOPT_SSL_VERIFYPEER]);
}
public function testCanSetVerifyToDir()
{
$f = new Handler\CurlFactory(3);
$f->create(new Psr7\Request('GET', 'http://foo.com'), ['verify' => __DIR__]);
self::assertEquals(__DIR__, $_SERVER['_curl'][\CURLOPT_CAPATH]);
self::assertEquals(2, $_SERVER['_curl'][\CURLOPT_SSL_VERIFYHOST]);
self::assertTrue($_SERVER['_curl'][\CURLOPT_SSL_VERIFYPEER]);
}
public function testAddsVerifyAsTrue()
{
$f = new Handler\CurlFactory(3);
$f->create(new Psr7\Request('GET', Server::$url), ['verify' => true]);
self::assertEquals(2, $_SERVER['_curl'][\CURLOPT_SSL_VERIFYHOST]);
self::assertTrue($_SERVER['_curl'][\CURLOPT_SSL_VERIFYPEER]);
self::assertArrayNotHasKey(\CURLOPT_CAINFO, $_SERVER['_curl']);
}
public function testCanDisableVerify()
{
$f = new Handler\CurlFactory(3);
$f->create(new Psr7\Request('GET', Server::$url), ['verify' => false]);
self::assertEquals(0, $_SERVER['_curl'][\CURLOPT_SSL_VERIFYHOST]);
self::assertFalse($_SERVER['_curl'][\CURLOPT_SSL_VERIFYPEER]);
}
public function testAddsProxy()
{
$f = new Handler\CurlFactory(3);
$f->create(new Psr7\Request('GET', Server::$url), ['proxy' => 'http://bar.com']);
self::assertEquals('http://bar.com', $_SERVER['_curl'][\CURLOPT_PROXY]);
}
public function testAddsViaScheme()
{
$f = new Handler\CurlFactory(3);
$f->create(new Psr7\Request('GET', Server::$url), [
'proxy' => ['http' => 'http://bar.com', 'https' => 'https://t'],
]);
self::assertEquals('http://bar.com', $_SERVER['_curl'][\CURLOPT_PROXY]);
$this->checkNoProxyForHost('http://test.test.com', ['test.test.com'], false);
$this->checkNoProxyForHost('http://test.test.com', ['.test.com'], false);
$this->checkNoProxyForHost('http://test.test.com', ['*.test.com'], true);
$this->checkNoProxyForHost('http://test.test.com', ['*'], false);
$this->checkNoProxyForHost('http://127.0.0.1', ['127.0.0.*'], true);
}
private function checkNoProxyForHost($url, $noProxy, $assertUseProxy)
{
$f = new Handler\CurlFactory(3);
$f->create(new Psr7\Request('GET', $url), [
'proxy' => [
'http' => 'http://bar.com',
'https' => 'https://t',
'no' => $noProxy
],
]);
if ($assertUseProxy) {
self::assertArrayHasKey(\CURLOPT_PROXY, $_SERVER['_curl']);
} else {
self::assertArrayNotHasKey(\CURLOPT_PROXY, $_SERVER['_curl']);
}
}
public function testUsesProxy()
{
Server::flush();
Server::enqueue([
new Psr7\Response(200, [
'Foo' => 'Bar',
'Baz' => 'bam',
'Content-Length' => 2,
], 'hi')
]);
$handler = new Handler\CurlMultiHandler();
$request = new Psr7\Request('GET', 'http://www.example.com', [], null, '1.0');
$promise = $handler($request, [
'proxy' => Server::$url
]);
$response = $promise->wait();
self::assertSame(200, $response->getStatusCode());
self::assertSame('Bar', $response->getHeaderLine('Foo'));
self::assertSame('2', $response->getHeaderLine('Content-Length'));
self::assertSame('hi', (string) $response->getBody());
}
public function testValidatesSslKey()
{
$f = new Handler\CurlFactory(3);
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('SSL private key not found: /does/not/exist');
$f->create(new Psr7\Request('GET', Server::$url), ['ssl_key' => '/does/not/exist']);
}
public function testAddsSslKey()
{
$f = new Handler\CurlFactory(3);
$f->create(new Psr7\Request('GET', Server::$url), ['ssl_key' => __FILE__]);
self::assertEquals(__FILE__, $_SERVER['_curl'][\CURLOPT_SSLKEY]);
}
public function testAddsSslKeyWithPassword()
{
$f = new Handler\CurlFactory(3);
$f->create(new Psr7\Request('GET', Server::$url), ['ssl_key' => [__FILE__, 'test']]);
self::assertEquals(__FILE__, $_SERVER['_curl'][\CURLOPT_SSLKEY]);
self::assertEquals('test', $_SERVER['_curl'][\CURLOPT_SSLKEYPASSWD]);
}
public function testAddsSslKeyWhenUsingArraySyntaxButNoPassword()
{
$f = new Handler\CurlFactory(3);
$f->create(new Psr7\Request('GET', Server::$url), ['ssl_key' => [__FILE__]]);
self::assertEquals(__FILE__, $_SERVER['_curl'][\CURLOPT_SSLKEY]);
}
public function testValidatesCert()
{
$f = new Handler\CurlFactory(3);
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('SSL certificate not found: /does/not/exist');
$f->create(new Psr7\Request('GET', Server::$url), ['cert' => '/does/not/exist']);
}
public function testAddsCert()
{
$f = new Handler\CurlFactory(3);
$f->create(new Psr7\Request('GET', Server::$url), ['cert' => __FILE__]);
self::assertEquals(__FILE__, $_SERVER['_curl'][\CURLOPT_SSLCERT]);
}
public function testAddsCertWithPassword()
{
$f = new Handler\CurlFactory(3);
$f->create(new Psr7\Request('GET', Server::$url), ['cert' => [__FILE__, 'test']]);
self::assertEquals(__FILE__, $_SERVER['_curl'][\CURLOPT_SSLCERT]);
self::assertEquals('test', $_SERVER['_curl'][\CURLOPT_SSLCERTPASSWD]);
}
public function testAddsDerCert()
{
$certFile = tempnam(sys_get_temp_dir(), "mock_test_cert");
rename($certFile, $certFile .= '.der');
try {
$f = new Handler\CurlFactory(3);
$f->create(new Psr7\Request('GET', Server::$url), ['cert' => $certFile]);
self::assertArrayHasKey(\CURLOPT_SSLCERTTYPE, $_SERVER['_curl']);
self::assertEquals('DER', $_SERVER['_curl'][\CURLOPT_SSLCERTTYPE]);
} finally {
@\unlink($certFile);
}
}
public function testAddsP12Cert()
{
$certFile = tempnam(sys_get_temp_dir(), "mock_test_cert");
rename($certFile, $certFile .= '.p12');
try {
$f = new Handler\CurlFactory(3);
$f->create(new Psr7\Request('GET', Server::$url), ['cert' => $certFile]);
self::assertArrayHasKey(\CURLOPT_SSLCERTTYPE, $_SERVER['_curl']);
self::assertEquals('P12', $_SERVER['_curl'][\CURLOPT_SSLCERTTYPE]);
} finally {
@\unlink($certFile);
}
}
public function testValidatesProgress()
{
$f = new Handler\CurlFactory(3);
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('progress client option must be callable');
$f->create(new Psr7\Request('GET', Server::$url), ['progress' => 'foo']);
}
public function testEmitsDebugInfoToStream()
{
$res = \fopen('php://temp', 'r+');
Server::flush();
Server::enqueue([new Psr7\Response()]);
$a = new Handler\CurlMultiHandler();
$response = $a(new Psr7\Request('HEAD', Server::$url), ['debug' => $res]);
$response->wait();
\rewind($res);
$output = \str_replace("\r", '', \stream_get_contents($res));
self::assertStringContainsString("> HEAD / HTTP/1.1", $output);
self::assertStringContainsString("< HTTP/1.1 200", $output);
\fclose($res);
}
public function testEmitsProgressToFunction()
{
Server::flush();
Server::enqueue([new Psr7\Response()]);
$a = new Handler\CurlMultiHandler();
$called = [];
$request = new Psr7\Request('HEAD', Server::$url);
$response = $a($request, [
'progress' => static function (...$args) use (&$called) {
$called[] = $args;
},
]);
$response->wait();
self::assertNotEmpty($called);
foreach ($called as $call) {
self::assertCount(4, $call);
}
}
private function addDecodeResponse($withEncoding = true)
{
$content = \gzencode('test');
$headers = ['Content-Length' => \strlen($content)];
if ($withEncoding) {
$headers['Content-Encoding'] = 'gzip';
}
$response = new Psr7\Response(200, $headers, $content);
Server::flush();
Server::enqueue([$response]);
return $content;
}
public function testDecodesGzippedResponses()
{
$this->addDecodeResponse();
$handler = new Handler\CurlMultiHandler();
$request = new Psr7\Request('GET', Server::$url);
$response = $handler($request, ['decode_content' => true]);
$response = $response->wait();
self::assertEquals('test', (string) $response->getBody());
self::assertEquals('', $_SERVER['_curl'][\CURLOPT_ENCODING]);
$sent = Server::received()[0];
self::assertFalse($sent->hasHeader('Accept-Encoding'));
}
public function testReportsOriginalSizeAndContentEncodingAfterDecoding()
{
$this->addDecodeResponse();
$handler = new Handler\CurlMultiHandler();
$request = new Psr7\Request('GET', Server::$url);
$response = $handler($request, ['decode_content' => true]);
$response = $response->wait();
self::assertSame(
'gzip',
$response->getHeaderLine('x-encoded-content-encoding')
);
self::assertSame(
\strlen(\gzencode('test')),
(int) $response->getHeaderLine('x-encoded-content-length')
);
}
public function testDecodesGzippedResponsesWithHeader()
{
$this->addDecodeResponse();
$handler = new Handler\CurlMultiHandler();
$request = new Psr7\Request('GET', Server::$url, ['Accept-Encoding' => 'gzip']);
$response = $handler($request, ['decode_content' => true]);
$response = $response->wait();
self::assertEquals('gzip', $_SERVER['_curl'][\CURLOPT_ENCODING]);
$sent = Server::received()[0];
self::assertEquals('gzip', $sent->getHeaderLine('Accept-Encoding'));
self::assertEquals('test', (string) $response->getBody());
self::assertFalse($response->hasHeader('content-encoding'));
self::assertTrue(
!$response->hasHeader('content-length') ||
$response->getHeaderLine('content-length') == $response->getBody()->getSize()
);
}
/**
* https://github.com/guzzle/guzzle/issues/2799
*/
public function testDecodesGzippedResponsesWithHeaderForHeadRequest()
{
$this->addDecodeResponse();
$handler = new Handler\CurlMultiHandler();
$request = new Psr7\Request('HEAD', Server::$url, ['Accept-Encoding' => 'gzip']);
$response = $handler($request, ['decode_content' => true]);
$response = $response->wait();
self::assertEquals('gzip', $_SERVER['_curl'][\CURLOPT_ENCODING]);
$sent = Server::received()[0];
self::assertEquals('gzip', $sent->getHeaderLine('Accept-Encoding'));
// Verify that the content-length matches the encoded size.
self::assertTrue(
!$response->hasHeader('content-length') ||
$response->getHeaderLine('content-length') == \strlen(\gzencode('test'))
);
}
public function testDoesNotForceDecode()
{
$content = $this->addDecodeResponse();
$handler = new Handler\CurlMultiHandler();
$request = new Psr7\Request('GET', Server::$url);
$response = $handler($request, ['decode_content' => false]);
$response = $response->wait();
$sent = Server::received()[0];
self::assertFalse($sent->hasHeader('Accept-Encoding'));
self::assertEquals($content, (string) $response->getBody());
}
public function testProtocolVersion()
{
Server::flush();
Server::enqueue([new Psr7\Response()]);
$a = new Handler\CurlMultiHandler();
$request = new Psr7\Request('GET', Server::$url, [], null, '1.0');
$a($request, []);
self::assertEquals(\CURL_HTTP_VERSION_1_0, $_SERVER['_curl'][\CURLOPT_HTTP_VERSION]);
}
public function testSavesToStream()
{
$stream = \fopen('php://memory', 'r+');
$this->addDecodeResponse();
$handler = new Handler\CurlMultiHandler();
$request = new Psr7\Request('GET', Server::$url);
$response = $handler($request, [
'decode_content' => true,
'sink' => $stream,
]);
$response->wait();
\rewind($stream);
self::assertEquals('test', \stream_get_contents($stream));
}
public function testSavesToGuzzleStream()
{
$stream = Psr7\Utils::streamFor();
$this->addDecodeResponse();
$handler = new Handler\CurlMultiHandler();
$request = new Psr7\Request('GET', Server::$url);
$response = $handler($request, [
'decode_content' => true,
'sink' => $stream,
]);
$response->wait();
self::assertEquals('test', (string) $stream);
}
public function testSavesToFileOnDisk()
{
$tmpfile = \tempnam(\sys_get_temp_dir(), 'testfile');
$this->addDecodeResponse();
$handler = new Handler\CurlMultiHandler();
$request = new Psr7\Request('GET', Server::$url);
$response = $handler($request, [
'decode_content' => true,
'sink' => $tmpfile,
]);
$response->wait();
self::assertStringEqualsFile($tmpfile, 'test');
@\unlink($tmpfile);
}
public function testDoesNotAddMultipleContentLengthHeaders()
{
$this->addDecodeResponse();
$handler = new Handler\CurlMultiHandler();
$request = new Psr7\Request('PUT', Server::$url, ['Content-Length' => 3], 'foo');
$response = $handler($request, []);
$response->wait();
$sent = Server::received()[0];
self::assertEquals(3, $sent->getHeaderLine('Content-Length'));
self::assertFalse($sent->hasHeader('Transfer-Encoding'));
self::assertEquals('foo', (string) $sent->getBody());
}
public function testSendsPostWithNoBodyOrDefaultContentType()
{
Server::flush();
Server::enqueue([new Psr7\Response()]);
$handler = new Handler\CurlMultiHandler();
$request = new Psr7\Request('POST', Server::$url);
$response = $handler($request, []);
$response->wait();
$received = Server::received()[0];
self::assertEquals('POST', $received->getMethod());
self::assertFalse($received->hasHeader('content-type'));
self::assertSame('0', $received->getHeaderLine('content-length'));
}
public function testFailsWhenCannotRewindRetryAfterNoResponse()
{
$factory = new Handler\CurlFactory(1);
$stream = Psr7\Utils::streamFor('abc');
$stream->read(1);
$stream = new Psr7\NoSeekStream($stream);
$request = new Psr7\Request('PUT', Server::$url, [], $stream);
$fn = static function ($request, $options) use (&$fn, $factory) {
$easy = $factory->create($request, $options);
return Handler\CurlFactory::finish($fn, $easy, $factory);
};
$this->expectException(RequestException::class);
$this->expectExceptionMessage('but attempting to rewind the request body failed');
$fn($request, [])->wait();
}
public function testRetriesWhenBodyCanBeRewound()
{
$callHandler = $called = false;
$fn = static function ($r, $options) use (&$callHandler) {
$callHandler = true;
return P\Create::promiseFor(new Psr7\Response());
};
$bd = Psr7\FnStream::decorate(Psr7\Utils::streamFor('test'), [
'tell' => static function () {
return 1;
},
'rewind' => static function () use (&$called) {
$called = true;
}
]);
$factory = new Handler\CurlFactory(1);
$req = new Psr7\Request('PUT', Server::$url, [], $bd);
$easy = $factory->create($req, []);
$res = Handler\CurlFactory::finish($fn, $easy, $factory);
$res = $res->wait();
self::assertTrue($callHandler);
self::assertTrue($called);
self::assertEquals('200', $res->getStatusCode());
}
public function testFailsWhenRetryMoreThanThreeTimes()
{
$factory = new Handler\CurlFactory(1);
$call = 0;
$fn = static function ($request, $options) use (&$mock, &$call, $factory) {
$call++;
$easy = $factory->create($request, $options);
return Handler\CurlFactory::finish($mock, $easy, $factory);
};
$mock = new Handler\MockHandler([$fn, $fn, $fn]);
$p = $mock(new Psr7\Request('PUT', Server::$url, [], 'test'), []);
$p->wait(false);
self::assertEquals(3, $call);
$this->expectException(RequestException::class);
$this->expectExceptionMessage('The cURL request was retried 3 times');
$p->wait(true);
}
public function testHandles100Continue()
{
Server::flush();
Server::enqueue([
new Psr7\Response(200, ['Test' => 'Hello', 'Content-Length' => 4], 'test'),
]);
$request = new Psr7\Request('PUT', Server::$url, [
'Expect' => '100-Continue'
], 'test');
$handler = new Handler\CurlMultiHandler();
$response = $handler($request, [])->wait();
self::assertSame(200, $response->getStatusCode());
self::assertSame('OK', $response->getReasonPhrase());
self::assertSame('Hello', $response->getHeaderLine('Test'));
self::assertSame('4', $response->getHeaderLine('Content-Length'));
self::assertSame('test', (string) $response->getBody());
}
public function testCreatesConnectException()
{
$m = new \ReflectionMethod(CurlFactory::class, 'finishError');
$m->setAccessible(true);
$factory = new Handler\CurlFactory(1);
$easy = $factory->create(new Psr7\Request('GET', Server::$url), []);
$easy->errno = \CURLE_COULDNT_CONNECT;
$response = $m->invoke(
null,
static function () {
},
$easy,
$factory
);
$this->expectException(ConnectException::class);
$response->wait();
}
public function testAddsTimeouts()
{
$f = new Handler\CurlFactory(3);
$f->create(new Psr7\Request('GET', Server::$url), [
'timeout' => 0.1,
'connect_timeout' => 0.2
]);
self::assertEquals(100, $_SERVER['_curl'][\CURLOPT_TIMEOUT_MS]);
self::assertEquals(200, $_SERVER['_curl'][\CURLOPT_CONNECTTIMEOUT_MS]);
}
public function testAddsStreamingBody()
{
$f = new Handler\CurlFactory(3);
$bd = Psr7\FnStream::decorate(Psr7\Utils::streamFor('foo'), [
'getSize' => static function () {
return null;
}
]);
$request = new Psr7\Request('PUT', Server::$url, [], $bd);
$f->create($request, []);
self::assertEquals(1, $_SERVER['_curl'][\CURLOPT_UPLOAD]);
self::assertIsCallable($_SERVER['_curl'][\CURLOPT_READFUNCTION]);
}
public function testEnsuresDirExistsBeforeThrowingWarning()
{
$f = new Handler\CurlFactory(3);
$this->expectException(\RuntimeException::class);
$this->expectExceptionMessage('Directory /does/not/exist/so does not exist for sink value of /does/not/exist/so/error.txt');
$f->create(new Psr7\Request('GET', Server::$url), [
'sink' => '/does/not/exist/so/error.txt'
]);
}
public function testClosesIdleHandles()
{
$f = new Handler\CurlFactory(3);
$req = new Psr7\Request('GET', Server::$url);
$easy = $f->create($req, []);
$h1 = $easy->handle;
$f->release($easy);
self::assertCount(1, Helpers::readObjectAttribute($f, 'handles'));
$easy = $f->create($req, []);
self::assertSame($easy->handle, $h1);
$easy2 = $f->create($req, []);
$easy3 = $f->create($req, []);
$easy4 = $f->create($req, []);
$f->release($easy);
self::assertCount(1, Helpers::readObjectAttribute($f, 'handles'));
$f->release($easy2);
self::assertCount(2, Helpers::readObjectAttribute($f, 'handles'));
$f->release($easy3);
self::assertCount(3, Helpers::readObjectAttribute($f, 'handles'));
$f->release($easy4);
self::assertCount(3, Helpers::readObjectAttribute($f, 'handles'));
}
public function testRejectsPromiseWhenCreateResponseFails()
{
Server::flush();
Server::enqueueRaw(999, "Incorrect", ['X-Foo' => 'bar'], 'abc 123');
$req = new Psr7\Request('GET', Server::$url);
$handler = new Handler\CurlHandler();
$promise = $handler($req, []);
$this->expectException(\GuzzleHttp\Exception\RequestException::class);
$this->expectExceptionMessage('An error was encountered while creating the response');
$promise->wait();
}
public function testEnsuresOnHeadersIsCallable()
{
$req = new Psr7\Request('GET', Server::$url);
$handler = new Handler\CurlHandler();
$this->expectException(\InvalidArgumentException::class);
$handler($req, ['on_headers' => 'error!']);
}
public function testRejectsPromiseWhenOnHeadersFails()
{
Server::flush();
Server::enqueue([
new Psr7\Response(200, ['X-Foo' => 'bar'], 'abc 123')
]);
$req = new Psr7\Request('GET', Server::$url);
$handler = new Handler\CurlHandler();
$promise = $handler($req, [
'on_headers' => static function () {
throw new \Exception('test');
}
]);
$this->expectException(RequestException::class);
$this->expectExceptionMessage('An error was encountered during the on_headers event');
$promise->wait();
}
public function testSuccessfullyCallsOnHeadersBeforeWritingToSink()
{
Server::flush();
Server::enqueue([
new Psr7\Response(200, ['X-Foo' => 'bar'], 'abc 123')
]);
$req = new Psr7\Request('GET', Server::$url);
$got = null;
$stream = Psr7\Utils::streamFor();
$stream = Psr7\FnStream::decorate($stream, [
'write' => static function ($data) use ($stream, &$got) {
self::assertNotNull($got);
return $stream->write($data);
}
]);
$handler = new Handler\CurlHandler();
$promise = $handler($req, [
'sink' => $stream,
'on_headers' => static function (ResponseInterface $res) use (&$got) {
$got = $res;
self::assertEquals('bar', $res->getHeaderLine('X-Foo'));
}
]);
$response = $promise->wait();
self::assertSame(200, $response->getStatusCode());
self::assertSame('bar', $response->getHeaderLine('X-Foo'));
self::assertSame('abc 123', (string) $response->getBody());
}
public function testInvokesOnStatsOnSuccess()
{
Server::flush();
Server::enqueue([new Psr7\Response(200)]);
$req = new Psr7\Request('GET', Server::$url);
$gotStats = null;
$handler = new Handler\CurlHandler();
$promise = $handler($req, [
'on_stats' => static function (TransferStats $stats) use (&$gotStats) {
$gotStats = $stats;
}
]);
$response = $promise->wait();
self::assertSame(200, $response->getStatusCode());
self::assertSame(200, $gotStats->getResponse()->getStatusCode());
self::assertSame(
Server::$url,
(string) $gotStats->getEffectiveUri()
);
self::assertSame(
Server::$url,
(string) $gotStats->getRequest()->getUri()
);
self::assertGreaterThan(0, $gotStats->getTransferTime());
self::assertArrayHasKey('appconnect_time', $gotStats->getHandlerStats());
}
public function testInvokesOnStatsOnError()
{
$req = new Psr7\Request('GET', 'http://127.0.0.1:123');
$gotStats = null;
$handler = new Handler\CurlHandler();
$promise = $handler($req, [
'connect_timeout' => 0.001,
'timeout' => 0.001,
'on_stats' => static function (TransferStats $stats) use (&$gotStats) {
$gotStats = $stats;
}
]);
$promise->wait(false);
self::assertFalse($gotStats->hasResponse());
self::assertSame(
'http://127.0.0.1:123',
(string) $gotStats->getEffectiveUri()
);
self::assertSame(
'http://127.0.0.1:123',
(string) $gotStats->getRequest()->getUri()
);
self::assertIsFloat($gotStats->getTransferTime());
self::assertIsInt($gotStats->getHandlerErrorData());
self::assertArrayHasKey('appconnect_time', $gotStats->getHandlerStats());
}
public function testRewindsBodyIfPossible()
{
$body = Psr7\Utils::streamFor(\str_repeat('x', 1024 * 1024 * 2));
$body->seek(1024 * 1024);
self::assertSame(1024 * 1024, $body->tell());
$req = new Psr7\Request('POST', 'https://www.example.com', [
'Content-Length' => 1024 * 1024 * 2,
], $body);
$factory = new CurlFactory(1);
$factory->create($req, []);
self::assertSame(0, $body->tell());
}
public function testDoesNotRewindUnseekableBody()
{
$body = Psr7\Utils::streamFor(\str_repeat('x', 1024 * 1024 * 2));
$body->seek(1024 * 1024);
$body = new Psr7\NoSeekStream($body);
self::assertSame(1024 * 1024, $body->tell());
$req = new Psr7\Request('POST', 'https://www.example.com', [
'Content-Length' => 1024 * 1024,
], $body);
$factory = new CurlFactory(1);
$factory->create($req, []);
self::assertSame(1024 * 1024, $body->tell());
}
public function testRelease()
{
$factory = new CurlFactory(1);
$easyHandle = new EasyHandle();
$easyHandle->handle = \curl_init();
self::assertEmpty($factory->release($easyHandle));
}
/**
* https://github.com/guzzle/guzzle/issues/2735
*/
public function testBodyEofOnWindows()
{
$expectedLength = 4097;
Server::flush();
Server::enqueue([
new Psr7\Response(200, [
'Content-Length' => $expectedLength,
], \str_repeat('x', $expectedLength))
]);
$handler = new Handler\CurlMultiHandler();
$request = new Psr7\Request('GET', Server::$url);
$promise = $handler($request, []);
$response = $promise->wait();
$body = $response->getBody();
$actualLength = 0;
while (!$body->eof()) {
$chunk = $body->read(4096);
$actualLength += \strlen($chunk);
}
self::assertSame($expectedLength, $actualLength);
}
public function testHandlesGarbageHttpServerGracefully()
{
$a = new Handler\CurlMultiHandler();
$this->expectException(RequestException::class);
$this->expectExceptionMessage('cURL error 1: Received HTTP/0.9 when not allowed');
$a(new Psr7\Request('GET', Server::$url . 'guzzle-server/garbage'), [])->wait();
}
public function testHandlesInvalidStatusCodeGracefully()
{
$a = new Handler\CurlMultiHandler();
$this->expectException(RequestException::class);
$this->expectExceptionMessage('An error was encountered while creating the response');
$a(new Psr7\Request('GET', Server::$url . 'guzzle-server/bad-status'), [])->wait();
}
}