Guzzle changes

Taking advantage of RequestFactory LSB
New tests to make sure Guzzle returns what's expected
This commit is contained in:
Chris Boden 2012-05-08 12:46:21 -04:00
parent 13009cf673
commit ef6e777f31
5 changed files with 83 additions and 218 deletions

View File

@ -2,7 +2,7 @@
<phpunit <phpunit
forceCoversAnnotation="true" forceCoversAnnotation="true"
mapTestClassNameToCoveredClassName="true" mapTestClassNameToCoveredClassName="true"
bootstrap="tests/bootstrap.php" bootstrap="vendor/autoload.php"
colors="true" colors="true"
backupGlobals="false" backupGlobals="false"
backupStaticAttributes="false" backupStaticAttributes="false"

View File

@ -1,223 +1,18 @@
<?php <?php
namespace Ratchet\Component\WebSocket\Guzzle\Http\Message; namespace Ratchet\Component\WebSocket\Guzzle\Http\Message;
use Guzzle\Common\Collection; use Guzzle\Http\Message\RequestFactory as GuzzleRequestFactory;
use Guzzle\Http\EntityBody; use Guzzle\Http\EntityBody;
use Guzzle\Http\QueryString;
use Guzzle\Http\Url;
use Guzzle\Http\Message\RequestFactoryInterface;
/**
* Default HTTP request factory used to create the default
* Guzzle\Http\Message\Request and Guzzle\Http\Message\EntityEnclosingRequest
* objects.
*/
class RequestFactory implements RequestFactoryInterface
{
/**
* @var Standard request headers
*/
protected static $requestHeaders = array(
'accept', 'accept-charset', 'accept-encoding', 'accept-language',
'authorization', 'cache-control', 'connection', 'cookie',
'content-length', 'content-type', 'date', 'expect', 'from', 'host',
'if-match', 'if-modified-since', 'if-none-match', 'if-range',
'if-unmodified-since', 'max-forwards', 'pragma', 'proxy-authorization',
'range', 'referer', 'te', 'transfer-encoding', 'upgrade', 'user-agent',
'via', 'warning'
);
/**
* @var RequestFactory Singleton instance of the default request factory
*/
protected static $instance;
/**
* @var string Class to instantiate for GET, HEAD, and DELETE requests
*/
protected $requestClass = 'Guzzle\\Http\\Message\\EntityEnclosingRequest';
/**
* @var string Class to instantiate for POST and PUT requests
*/
protected $entityEnclosingRequestClass = 'Guzzle\\Http\\Message\\EntityEnclosingRequest';
/**
* Get a cached instance of the default request factory
*
* @return RequestFactory
*/
public static function getInstance()
{
// @codeCoverageIgnoreStart
if (!self::$instance) {
self::$instance = new self();
}
// @codeCoverageIgnoreEnd
return self::$instance;
}
class RequestFactory extends GuzzleRequestFactory {
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function parseMessage($message) public function create($method, $url, $headers = null, $body = null) {
{ $c = $this->entityEnclosingRequestClass;
if (!$message) {
return false;
}
$headers = new Collection();
$scheme = $host = $body = $method = $user = $pass = $query = $port = $version = $protocol = '';
$path = '/';
// Inspired by https://github.com/kriswallsmith/Buzz/blob/message-interfaces/lib/Buzz/Message/Parser/Parser.php#L16
$lines = preg_split('/(\\r?\\n)/', $message, -1, PREG_SPLIT_DELIM_CAPTURE);
for ($i = 0, $c = count($lines); $i < $c; $i += 2) {
$line = $lines[$i];
// If two line breaks were encountered, then this is the body
if (empty($line)) {
$body = implode('', array_slice($lines, $i + 2));
break;
}
// Parse message headers
$matches = array();
if (!$method && preg_match('#^(?P<method>[A-Za-z]+)\s+(?P<path>/.*)\s+(?P<protocol>\w+)/(?P<version>\d\.\d)\s*$#i', $line, $matches)) {
$method = strtoupper($matches['method']);
$protocol = strtoupper($matches['protocol']);
$path = $matches['path'];
$version = $matches['version'];
$scheme = 'http';
} else if (strpos($line, ':')) {
list($key, $value) = explode(':', $line, 2);
$key = trim($key);
// Normalize standard HTTP headers
if (in_array(strtolower($key), self::$requestHeaders)) {
$key = str_replace(' ', '-', ucwords(str_replace('-', ' ', $key)));
}
// Headers are case insensitive
$headers->add($key, trim($value));
}
}
// Check for the Host header
if (isset($headers['Host'])) {
$host = $headers['Host'];
}
if (strpos($host, ':')) {
list($host, $port) = array_map('trim', explode(':', $host));
if ($port == 443) {
$scheme = 'https';
}
} else {
$port = '';
}
// Check for basic authorization
$auth = isset($headers['Authorization']) ? $headers['Authorization'] : '';
if ($auth) {
list($type, $data) = explode(' ', $auth);
if (strtolower($type) == 'basic') {
$data = base64_decode($data);
list($user, $pass) = explode(':', $data);
}
}
// Check if a query is present
$qpos = strpos($path, '?');
if ($qpos) {
$query = substr($path, $qpos);
$path = substr($path, 0, $qpos);
}
return array(
'method' => $method,
'protocol' => $protocol,
'protocol_version' => $version,
'parts' => array(
'scheme' => $scheme,
'host' => $host,
'port' => $port,
'user' => $user,
'pass' => $pass,
'path' => $path,
'query' => $query
),
'headers' => $headers->getAll(),
'body' => $body
);
}
/**
* {@inheritdoc}
*/
public function fromMessage($message)
{
$parsed = $this->parseMessage($message);
if (!$parsed) {
return false;
}
$request = $this->fromParts($parsed['method'], $parsed['parts'],
$parsed['headers'], $parsed['body'], $parsed['protocol'],
$parsed['protocol_version']);
// EntityEnclosingRequest adds an "Expect: 100-Continue" header when
// using a raw request body for PUT or POST requests. This factory
// method should accurately reflect the message, so here we are
// removing the Expect header if one was not supplied in the message.
if (!isset($parsed['headers']['Expect'])) {
$request->removeHeader('Expect');
}
return $request;
}
/**
* {@inheritdoc}
*/
public function fromParts($method, array $parts, $headers = null, $body = null, $protocol = 'HTTP', $protocolVersion = '1.1')
{
return $this->create($method, Url::buildUrl($parts, true), $headers, $body)
->setProtocolVersion($protocolVersion);
}
/**
* {@inheritdoc}
*/
public function create($method, $url, $headers = null, $body = null)
{
if ($method != 'POST' && $method != 'PUT' && $method != 'PATCH') {
$c = $this->requestClass;
$request = new $c($method, $url, $headers); $request = new $c($method, $url, $headers);
if ($body) { if ($body) {
$request->setBody(EntityBody::factory($body)); $request->setBody(EntityBody::factory($body));
} }
} else {
$c = $this->entityEnclosingRequestClass;
$request = new $c($method, $url, $headers);
if ($body) {
if ($method == 'POST' && (is_array($body) || $body instanceof Collection)) {
$request->addPostFields($body);
} else if (is_resource($body) || $body instanceof EntityBody) {
$request->setBody($body, (string) $request->getHeader('Content-Type'));
} else {
$request->setBody((string) $body, (string) $request->getHeader('Content-Type'));
}
}
// Fix chunked transfers based on the passed headers
if (isset($headers['Transfer-Encoding']) && $headers['Transfer-Encoding'] == 'chunked') {
$request->removeHeader('Content-Length')
->setHeader('Transfer-Encoding', 'chunked');
}
}
return $request; return $request;
} }

View File

@ -0,0 +1,67 @@
<?php
namespace Ratchet\Tests\Component\WebSocket\Guzzle\Http\Message;
use Ratchet\Component\WebSocket\Guzzle\Http\Message\RequestFactory;
/**
* @covers Ratchet\Component\WebSocket\Guzzle\Http\Message\RequestFactory
*/
class RequestFactoryTest extends \PHPUnit_Framework_TestCase {
protected $factory;
public function setUp() {
$this->factory = RequestFactory::getInstance();
}
public function testMessageProvider() {
return array(
'status' => 'GET / HTTP/1.1'
, 'headers' => array(
'Upgrade' => 'WebSocket'
, 'Connection' => 'Upgrade'
, 'Host' => 'localhost:8000'
, 'Sec-WebSocket-Key1' => '> b3lU Z0 fh f 3+83394 6 (zG4'
, 'Sec-WebSocket-Key2' => ',3Z0X0677 dV-d [159 Z*4'
)
, 'body' => "123456\r\n\r\n"
);
}
public function combineMessage($status, array $headers, $body = '') {
$message = $status . "\r\n";
foreach ($headers as $key => $val) {
$message .= "{$key}: {$val}\r\n";
}
$message .= "\r\n{$body}";
return $message;
}
public function testExpectedDataFromGuzzleHeaders() {
$parts = $this->testMessageProvider();
$message = $this->combineMessage($parts['status'], $parts['headers'], $parts['body']);
$object = $this->factory->fromMessage($message);
foreach ($parts['headers'] as $key => $val) {
$this->assertEquals($val, $object->getHeader($key, true));
}
}
public function testExpectedDataFromNonGuzzleHeaders() {
$parts = $this->testMessageProvider();
$message = $this->combineMessage($parts['status'], $parts['headers'], $parts['body']);
$object = $this->factory->fromMessage($message);
$this->assertNull($object->getHeader('Nope', true));
$this->assertNull($object->getHeader('Nope'));
}
public function testExpectedDataFromNonGuzzleBody() {
$parts = $this->testMessageProvider();
$message = $this->combineMessage($parts['status'], $parts['headers'], $parts['body']);
$object = $this->factory->fromMessage($message);
$this->assertEquals($parts['body'], (string)$object->getBody());
}
}

View File

@ -125,4 +125,12 @@ class RFC6455Test extends \PHPUnit_Framework_TestCase {
$this->_version->handshake($request); $this->_version->handshake($request);
} }
} }
public function testNewMessage() {
$this->assertInstanceOf('\\Ratchet\\Component\\WebSocket\\Version\\RFC6455\\Message', $this->_version->newMessage());
}
public function testNewFrame() {
$this->assertInstanceOf('\\Ratchet\\Component\\WebSocket\\Version\\RFC6455\\Frame', $this->_version->newFrame());
}
} }

View File

@ -1,5 +0,0 @@
<?php
error_reporting(E_ALL | E_STRICT);
require_once dirname(__DIR__) . '/vendor/autoload.php';