Skip to content

Commit

Permalink
Merge 29c2977 into 9f59bff
Browse files Browse the repository at this point in the history
  • Loading branch information
Bilge committed Oct 28, 2017
2 parents 9f59bff + 29c2977 commit 1ff63db
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 51 deletions.
6 changes: 3 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
],
"license": "LGPL-3.0",
"require": {
"scriptfusion/porter": ">=3,<4",
"connectors/ssl": "^1",
"zendframework/zend-uri": "^2"
"scriptfusion/porter": "4.0.x-dev",
"connectors/ssl": "^1.1",
"zendframework/zend-uri": "^2.2"
},
"require-dev": {
"phpunit/phpunit": "^4.8",
Expand Down
76 changes: 39 additions & 37 deletions src/Net/Http/HttpConnector.php
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
<?php
namespace ScriptFUSION\Porter\Net\Http;

use ScriptFUSION\Porter\Connector\CachingConnector;
use ScriptFUSION\Porter\Connector\ConnectionContext;
use ScriptFUSION\Porter\Connector\Connector;
use ScriptFUSION\Porter\Net\UrlBuilder;
use ScriptFUSION\Porter\Options\EncapsulatedOptions;

Expand All @@ -11,7 +12,7 @@
* Enhanced error reporting is achieved by ignoring HTTP error codes in the wrapper, instead throwing
* HttpServerException which includes the body of the response in the error message.
*/
class HttpConnector extends CachingConnector
class HttpConnector implements Connector
{
/** @var HttpOptions */
private $options;
Expand All @@ -24,14 +25,13 @@ class HttpConnector extends CachingConnector

public function __construct(HttpOptions $options = null)
{
parent::__construct();

$this->options = $options ?: new HttpOptions;
}

/**
* {@inheritdoc}
*
* @param ConnectionContext $context Runtime connection settings and methods.
* @param string $source Source.
* @param EncapsulatedOptions|null $options Optional. Options.
*
Expand All @@ -41,46 +41,48 @@ public function __construct(HttpOptions $options = null)
* @throws HttpConnectionException Failed to connect to source.
* @throws HttpServerException Server sent an error code.
*/
public function fetchFreshData($source, EncapsulatedOptions $options = null)
public function fetch(ConnectionContext $context, $source, EncapsulatedOptions $options = null)
{
if ($options && !$options instanceof HttpOptions) {
throw new \InvalidArgumentException('Options must be an instance of HttpOptions.');
}

if (false === $response = @file_get_contents(
$this->getOrCreateUrlBuilder()->buildUrl(
$source,
$options ? $options->getQueryParameters() : [],
$this->getBaseUrl()
),
false,
stream_context_create([
'http' =>
// Instruct PHP to ignore HTTP error codes so Porter can handle them.
['ignore_errors' => true]
+ ($options ? $options->extractHttpContextOptions() : [])
+ $this->options->extractHttpContextOptions()
,
'ssl' =>
($options ? $options->getSslOptions()->extractSslContextOptions() : [])
+ $this->options->getSslOptions()->extractSslContextOptions()
,
])
)) {
$error = error_get_last();
throw new HttpConnectionException($error['message'], $error['type']);
}
$url = $this->getOrCreateUrlBuilder()->buildUrl(
$source,
$options ? $options->getQueryParameters() : [],
$this->getBaseUrl()
);

$code = explode(' ', $http_response_header[0], 3)[1];
if ($code < 200 || $code >= 400) {
throw new HttpServerException(
"HTTP server responded with error: \"$http_response_header[0]\".\n\n$response",
$code,
$response
);
}
$streamContext = stream_context_create([
'http' =>
// Instruct PHP to ignore HTTP error codes so Porter can handle them instead.
['ignore_errors' => true]
+ ($options ? $options->extractHttpContextOptions() : [])
+ $this->options->extractHttpContextOptions()
,
'ssl' =>
($options ? $options->getSslOptions()->extractSslContextOptions() : [])
+ $this->options->getSslOptions()->extractSslContextOptions()
,
]);

return $context->retry(function () use ($url, $streamContext) {
if (false === $response = @file_get_contents($url, false, $streamContext)) {
$error = error_get_last();
throw new HttpConnectionException($error['message'], $error['type']);
}

$code = explode(' ', $http_response_header[0], 3)[1];
if ($code < 200 || $code >= 400) {
throw new HttpServerException(
"HTTP server responded with error: \"$http_response_header[0]\".\n\n$response",
$code,
$response
);
}

return $response;
return $response;
});
}

/**
Expand Down
26 changes: 26 additions & 0 deletions test/FixtureFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php
namespace ScriptFUSIONTest;

use ScriptFUSION\Porter\Connector\ConnectionContext;
use ScriptFUSION\StaticClass;

final class FixtureFactory
{
use StaticClass;

/**
* Builds ConnectionContexts with sane defaults for testing.
*
* @return ConnectionContext
*/
public static function createConnectionContext()
{
return new ConnectionContext(
false,
function () {
// Intentionally empty.
},
1
);
}
}
29 changes: 19 additions & 10 deletions test/Functional/Porter/Net/Http/HttpConnectorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
use ScriptFUSION\Porter\Net\Http\HttpServerException;
use ScriptFUSION\Porter\Specification\ImportSpecification;
use ScriptFUSION\Retry\ExceptionHandler\ExponentialBackoffExceptionHandler;
use ScriptFUSION\Retry\FailingTooHardException;
use ScriptFUSIONTest\FixtureFactory;
use Symfony\Component\Process\Process;

final class HttpConnectorTest extends \PHPUnit_Framework_TestCase
Expand Down Expand Up @@ -65,23 +67,28 @@ public function testSslConnectionToLocalWebserver()

public function testConnectionTimeout()
{
$this->setExpectedException(HttpConnectionException::class);
try {
$this->fetch();

$this->fetch();
self::fail('Expected FailingTooHardException exception.');
} catch (FailingTooHardException $exception) {
self::assertInstanceOf(HttpConnectionException::class, $exception->getPrevious());
}
}

public function testErrorResponse()
{
$server = $this->startServer('404');

$this->setExpectedExceptionRegExp(HttpServerException::class, '[^foo\z]m');

try {
$this->fetch();
} catch (HttpServerException $exception) {
$this->assertSame('foo', $exception->getBody());

throw $exception;
self::fail('Expected FailingTooHardException exception.');
} catch (FailingTooHardException $exception) {
/** @var HttpServerException $innerException */
self::assertInstanceOf(HttpServerException::class, $innerException = $exception->getPrevious());
self::assertSame('foo', $innerException->getBody());
self::assertStringEndsWith("\n\nfoo", $innerException->getMessage());
} finally {
$this->stopServer($server);
}
Expand Down Expand Up @@ -149,12 +156,12 @@ private function fetch(Connector $connector = null)
{
$connector = $connector ?: $this->connector;

return $connector->fetch('http://' . self::HOST . self::URI);
return $connector->fetch(FixtureFactory::createConnectionContext(), 'http://' . self::HOST . self::URI);
}

private function fetchViaSsl(Connector $connector)
{
return $connector->fetch('https://' . self::SSL_HOST);
return $connector->fetch(FixtureFactory::createConnectionContext(), 'https://' . self::SSL_HOST);
}

/**
Expand All @@ -168,7 +175,9 @@ private static function waitForHttpServer(\Closure $serverInvoker)
ImportSpecification::DEFAULT_FETCH_ATTEMPTS,
$serverInvoker,
function (\Exception $exception) {
if (!$exception instanceof HttpConnectionException) {
if (!$exception instanceof FailingTooHardException
|| !$exception->getPrevious() instanceof HttpConnectionException
) {
return false;
}

Expand Down
7 changes: 6 additions & 1 deletion test/Unit/Porter/Net/Http/HttpConnectorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use ScriptFUSION\Porter\Net\Http\HttpConnector;
use ScriptFUSION\Porter\Net\Http\HttpOptions;
use ScriptFUSION\Porter\Options\EncapsulatedOptions;
use ScriptFUSIONTest\FixtureFactory;

final class HttpConnectorTest extends \PHPUnit_Framework_TestCase
{
Expand All @@ -19,7 +20,11 @@ public function testInvalidOptionsType()
{
$this->setExpectedException(\InvalidArgumentException::class);

(new HttpConnector)->fetch('foo', \Mockery::mock(EncapsulatedOptions::class));
(new HttpConnector)->fetch(
FixtureFactory::createConnectionContext(),
'foo',
\Mockery::mock(EncapsulatedOptions::class)
);
}

public function testBaseUrl()
Expand Down

0 comments on commit 1ff63db

Please sign in to comment.