Merge branch '2.1-http' into 2.1

This commit is contained in:
Jose Lorenzo Rodriguez 2012-01-21 15:51:34 -04:30
commit a1daaf5960
8 changed files with 943 additions and 49 deletions

View file

@ -95,7 +95,8 @@ class RequestHandlerComponent extends Component {
* @param array $settings Array of settings.
*/
public function __construct(ComponentCollection $collection, $settings = array()) {
parent::__construct($collection, $settings);
$default = array('checkHttpCache' => true);
parent::__construct($collection, $settings + $default);
$this->addInputType('xml', array(array($this, 'convertXml')));
$Controller = $collection->getController();
@ -240,6 +241,22 @@ class RequestHandlerComponent extends Component {
$this->_stop();
}
/**
* Checks if the response can be considered different according to the request
* headers, and the caching response headers. If it was not modified, then the
* render process is skipped. And the client will get a blank response with a
* "304 Not Modified" header.
*
* @params Controller $controller
* @return boolean false if the render process should be aborted
**/
public function beforeRender($controller) {
$shouldCheck = $this->settings['checkHttpCache'];
if ($shouldCheck && $this->response->checkNotModified($this->request)) {
return false;
}
}
/**
* Returns true if the current HTTP request is Ajax, false otherwise
*
@ -704,4 +721,5 @@ class RequestHandlerComponent extends Component {
}
$this->_inputTypeMap[$type] = $handler;
}
}

View file

@ -896,7 +896,12 @@ class Controller extends Object implements CakeEventListener {
* @link http://book.cakephp.org/2.0/en/controllers.html#Controller::render
*/
public function render($view = null, $layout = null) {
$this->getEventManager()->dispatch(new CakeEvent('Controller.beforeRender', $this));
$event = new CakeEvent('Controller.beforeRender', $this);
$this->getEventManager()->dispatch($event);
if ($event->isStopped()) {
$this->autoRender = false;
return $this->response;
}
$viewClass = $this->viewClass;
if ($this->viewClass != 'View') {

View file

@ -311,6 +311,14 @@ class CakeResponse {
*/
protected $_charset = 'UTF-8';
/**
* Holds all the cache directives that will be converted
* into headers when sending the request
*
* @var string
*/
protected $_cacheDirectives = array();
/**
* Class constructor
*
@ -348,14 +356,43 @@ class CakeResponse {
$codeMessage = $this->_statusCodes[$this->_status];
$this->_sendHeader("{$this->_protocol} {$this->_status} {$codeMessage}");
$this->_sendHeader('Content-Type', "{$this->_contentType}; charset={$this->_charset}");
$this->_setContent();
$this->_setContentLength();
$this->_setContentType();
foreach ($this->_headers as $header => $value) {
$this->_sendHeader($header, $value);
}
$this->_sendContent($this->_body);
}
/**
* Formats the Content-Type header based on the configured contentType and charset
* the charset will only be set in the header if the response is of type text/*
*
* @return void
*/
protected function _setContentType() {
if (in_array($this->_status, array(304, 204))) {
return;
}
if (strpos($this->_contentType, 'text/') === 0) {
$this->header('Content-Type', "{$this->_contentType}; charset={$this->_charset}");
} else {
$this->header('Content-Type', "{$this->_contentType}");
}
}
/**
* Sets the response body to an empty text if the status code is 204 or 304
*
* @return void
*/
protected function _setContent() {
if (in_array($this->_status, array(304, 204))) {
$this->body('');
}
}
/**
* Calculates the correct Content-Length and sets it as a header in the response
* Will not set the value if already set or if the output is compressed.
@ -363,13 +400,17 @@ class CakeResponse {
* @return void
*/
protected function _setContentLength() {
$shouldSetLength = empty($this->_headers['Content-Length']) && !in_array($this->_status, range(301, 307));
$shouldSetLength = !isset($this->_headers['Content-Length']) && !in_array($this->_status, range(301, 307));
if (isset($this->_headers['Content-Length']) && $this->_headers['Content-Length'] === false) {
unset($this->_headers['Content-Length']);
return;
}
if ($shouldSetLength && !$this->outputCompressed()) {
$offset = ob_get_level() ? ob_get_length() : 0;
if (ini_get('mbstring.func_overload') & 2 && function_exists('mb_strlen')) {
$this->_headers['Content-Length'] = $offset + mb_strlen($this->_body, '8bit');
$this->length($offset + mb_strlen($this->_body, '8bit'));
} else {
$this->_headers['Content-Length'] = $offset + strlen($this->_body);
$this->length($this->_headers['Content-Length'] = $offset + strlen($this->_body));
}
}
}
@ -625,8 +666,7 @@ class CakeResponse {
$this->header(array(
'Expires' => 'Mon, 26 Jul 1997 05:00:00 GMT',
'Last-Modified' => gmdate("D, d M Y H:i:s") . " GMT",
'Cache-Control' => 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0',
'Pragma' => 'no-cache'
'Cache-Control' => 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0'
));
}
@ -642,12 +682,272 @@ class CakeResponse {
$time = strtotime($time);
}
$this->header(array(
'Date' => gmdate("D, j M Y G:i:s ", time()) . 'GMT',
'Last-Modified' => gmdate("D, j M Y G:i:s ", $since) . 'GMT',
'Expires' => gmdate("D, j M Y H:i:s", $time) . " GMT",
'Cache-Control' => 'public, max-age=' . ($time - time()),
'Pragma' => 'cache'
'Date' => gmdate("D, j M Y G:i:s ", time()) . 'GMT'
));
$this->modified($since);
$this->expires($time);
$this->sharable(true);
$this->maxAge($time - time());
}
/**
* Sets whether a response is eligible to be cached by intermediate proxies
* This method controls the `public` or `private` directive in the Cache-Control
* header
*
* @param boolean $public if set to true, the Cache-Control header will be set as public
* if set to false, the response will be set to private
* if no value is provided, it will return whether the response is sharable or not
* @param int $time time in seconds after which the response should no longer be considered fresh
* @return boolean
*/
public function sharable($public = null, $time = null) {
if ($public === null) {
$public = array_key_exists('public', $this->_cacheDirectives);
$private = array_key_exists('private', $this->_cacheDirectives);
$noCache = array_key_exists('no-cache', $this->_cacheDirectives);
if (!$public && !$private && !$noCache) {
return null;
}
$sharable = $public || ! ($private || $noCache);
return $sharable;
}
if ($public) {
$this->_cacheDirectives['public'] = true;
unset($this->_cacheDirectives['private']);
$this->sharedMaxAge($time);
} else {
$this->_cacheDirectives['private'] = true;
unset($this->_cacheDirectives['public']);
$this->maxAge($time);
}
if ($time == null) {
$this->_setCacheControl();
}
return (bool) $public;
}
/**
* Sets the Cache-Control s-maxage directive.
* The max-age is the number of seconds after which the response should no longer be considered
* a good candidate to be fetched from a shared cache (like in a proxy server).
* If called with no parameters, this function will return the current max-age value if any
*
* @param int $seconds if null, the method will return the current s-maxage value
* @return int
*/
public function sharedMaxAge($seconds = null) {
if ($seconds !== null) {
$this->_cacheDirectives['s-maxage'] = $seconds;
$this->_setCacheControl();
}
if (isset($this->_cacheDirectives['s-maxage'])) {
return $this->_cacheDirectives['s-maxage'];
}
return null;
}
/**
* Sets the Cache-Control max-age directive.
* The max-age is the number of seconds after which the response should no longer be considered
* a good candidate to be fetched from the local (client) cache.
* If called with no parameters, this function will return the current max-age value if any
*
* @param int $seconds if null, the method will return the current max-age value
* @return int
*/
public function maxAge($seconds = null) {
if ($seconds !== null) {
$this->_cacheDirectives['max-age'] = $seconds;
$this->_setCacheControl();
}
if (isset($this->_cacheDirectives['max-age'])) {
return $this->_cacheDirectives['max-age'];
}
return null;
}
/**
* Sets the Cache-Control must-revalidate directive.
* must-revalidate indicates that the response should not be served
* stale by a cache under any cirumstance without first revalidating
* with the origin.
* If called with no parameters, this function will return wheter must-revalidate is present.
*
* @param int $seconds if null, the method will return the current
* must-revalidate value
* @return boolean
*/
public function mustRevalidate($enable = null) {
if ($enable !== null) {
if ($enable) {
$this->_cacheDirectives['must-revalidate'] = true;
} else {
unset($this->_cacheDirectives['must-revalidate']);
}
$this->_setCacheControl();
}
return array_key_exists('must-revalidate', $this->_cacheDirectives);
}
/**
* Helper method to generate a valid Cache-Control header from the options set
* in other methods
*
* @return void
*/
protected function _setCacheControl() {
$control = '';
foreach ($this->_cacheDirectives as $key => $val) {
$control .= $val === true ? $key : sprintf('%s=%s', $key, $val);
$control .= ', ';
}
$control = rtrim($control, ', ');
$this->header('Cache-Control', $control);
}
/**
* Sets the Expires header for the response by taking an expiration time
* If called with no parameters it will return the current Expires value
*
* ## Examples:
*
* `$response->expires('now')` Will Expire the response cache now
* `$response->expires(new DateTime('+1 day'))` Will set the expiration in next 24 hours
* `$response->expires()` Will return the current expiration header value
*
* @param string|DateTime $time
* @return string
*/
public function expires($time = null) {
if ($time !== null) {
$date = $this->_getUTCDate($time);
$this->_headers['Expires'] = $date->format('D, j M Y H:i:s') . ' GMT';
}
if (isset($this->_headers['Expires'])) {
return $this->_headers['Expires'];
}
return null;
}
/**
* Sets the Last-Modified header for the response by taking an modification time
* If called with no parameters it will return the current Last-Modified value
*
* ## Examples:
*
* `$response->modified('now')` Will set the Last-Modified to the current time
* `$response->modified(new DateTime('+1 day'))` Will set the modification date in the past 24 hours
* `$response->modified()` Will return the current Last-Modified header value
*
* @param string|DateTime $time
* @return string
*/
public function modified($time = null) {
if ($time !== null) {
$date = $this->_getUTCDate($time);
$this->_headers['Last-Modified'] = $date->format('D, j M Y H:i:s') . ' GMT';
}
if (isset($this->_headers['Last-Modified'])) {
return $this->_headers['Last-Modified'];
}
return null;
}
/**
* Sets the response as Not Modified by removing any body contents
* setting the status code to "304 Not Modified" and removing all
* conflicting headers
*
* @return void
**/
public function notModified() {
$this->statusCode(304);
$this->body('');
$remove = array(
'Allow',
'Content-Encoding',
'Content-Language',
'Content-Length',
'Content-MD5',
'Content-Type',
'Last-Modified'
);
foreach ($remove as $header) {
unset($this->_headers[$header]);
}
}
/**
* Sets the Vary header for the response, if an array is passed,
* values will be imploded into a comma separated string. If no
* parameters are passed, then an array with the current Vary header
* value is returned
*
* @param string|array $cacheVariances a single Vary string or a array
* containig the list for variances.
* @return array
**/
public function vary($cacheVariances = null) {
if ($cacheVariances !== null) {
$cacheVariances = (array) $cacheVariances;
$this->_headers['Vary'] = implode(', ', $cacheVariances);
}
if (isset($this->_headers['Vary'])) {
return explode(', ', $this->_headers['Vary']);
}
return null;
}
/**
* Sets the response Etag, Etags are a strong indicative that a response
* can be cached by a HTTP client. A bad way of generaing Etags is
* creating a hash of the response output, instead generate a unique
* hash of the unique components that identifies a request, such as a
* modification time, a resource Id, and anything else you consider it
* makes it unique.
*
* Second parameter is used to instuct clients that the content has
* changed, but sematicallly, it can be used as the same thing. Think
* for instance of a page with a hit counter, two different page views
* are equivalent, but they differ by a few bytes. This leaves off to
* the Client the decision of using or not the cached page.
*
* If no parameters are passed, current Etag header is returned.
*
* @param string $hash the unique has that identifies this resposnse
* @param boolean $weak whether the response is semantically the same as
* other with th same hash or not
* @return string
**/
public function etag($tag = null, $weak = false) {
if ($tag !== null) {
$this->_headers['Etag'] = sprintf('%s"%s"', ($weak) ? 'W/' : null, $tag);
}
if (isset($this->_headers['Etag'])) {
return $this->_headers['Etag'];
}
return null;
}
/**
* Returns a DateTime object initialized at the $time param and using UTC
* as timezone
*
* @param string|int|DateTime $time
* @return DateTime
*/
protected function _getUTCDate($time = null) {
if ($time instanceof DateTime) {
$result = clone $time;
} else if (is_integer($time)) {
$result = new DateTime(date('Y-m-d H:i:s', $time));
} else {
$result = new DateTime($time);
}
$result->setTimeZone(new DateTimeZone('UTC'));
return $result;
}
/**
@ -683,6 +983,68 @@ class CakeResponse {
$this->header('Content-Disposition', 'attachment; filename="' . $filename . '"');
}
/**
* Sets the protocol to be used when sending the response. Defaults to HTTP/1.1
* If called with no arguments, it will return the current configured protocol
*
* @return string protocol to be used for sending response
*/
public function protocol($protocol = null) {
if ($protocol !== null) {
$this->_protocol = $protocol;
}
return $this->_protocol;
}
/**
* Sets the Content-Length header for the response
* If called with no arguments returns the last Content-Length set
*
* @return int
*/
public function length($bytes = null) {
if ($bytes !== null ) {
$this->_headers['Content-Length'] = $bytes;
}
if (isset($this->_headers['Content-Length'])) {
return $this->_headers['Content-Length'];
}
return null;
}
/**
* Checks whether a response has not been modified according to the 'If-None-Match'
* (Etags) and 'If-Modified-Since' (last modification date) request
* headers headers. If the response is detected to be not modified, it
* is marked as so accordingly so the client can be informed of that.
*
* In order to mark a response as not modified, you need to set at least
* the Last-Modified response header or a response etag to be compared
* with the request itself
*
* @return boolean whether the response was marked as not modified or
* not
**/
public function checkNotModified(CakeRequest $request) {
$etags = preg_split('/\s*,\s*/', $request->header('If-None-Match'), null, PREG_SPLIT_NO_EMPTY);
$modifiedSince = $request->header('If-Modified-Since');
if ($responseTag = $this->etag()) {
$etagMatches = in_array('*', $etags) || in_array($responseTag, $etags);
}
if ($modifiedSince) {
$timeMatches = strtotime($this->modified()) == strtotime($modifiedSince);
}
$checks = compact('etagMatches', 'timeMatches');
if (empty($checks)) {
return false;
}
$notModified = !in_array(false, $checks, true);
if ($notModified) {
$this->notModified();
}
return $notModified;
}
/**
* String conversion. Fetches the response body as a string.
* Does *not* send headers.

View file

@ -823,4 +823,60 @@ class RequestHandlerComponentTest extends CakeTestCase {
public function testAddInputTypeException() {
$this->RequestHandler->addInputType('csv', array('I am not callable'));
}
/**
* Test checkNotModified method
*
* @return void
**/
public function testCheckNotModifiedByEtagStar() {
$_SERVER['HTTP_IF_NONE_MATCH'] = '*';
$RequestHandler = $this->getMock('RequestHandlerComponent', array('_stop'), array(&$this->Controller->Components));
$RequestHandler->response = $this->getMock('CakeResponse', array('notModified'));
$RequestHandler->response->etag('something');
$RequestHandler->response->expects($this->once())->method('notModified');
$this->assertFalse($RequestHandler->beforeRender($this->Controller));
}
/**
* Test checkNotModified method
*
* @return void
**/
public function testCheckNotModifiedByEtagExact() {
$_SERVER['HTTP_IF_NONE_MATCH'] = 'W/"something", "other"';
$RequestHandler = $this->getMock('RequestHandlerComponent', array('_stop'), array(&$this->Controller->Components));
$RequestHandler->response = $this->getMock('CakeResponse', array('notModified'));
$RequestHandler->response->etag('something', true);
$RequestHandler->response->expects($this->once())->method('notModified');
$this->assertFalse($RequestHandler->beforeRender($this->Controller));
}
/**
* Test checkNotModified method
*
* @return void
**/
public function testCheckNotModifiedByEtagAndTime() {
$_SERVER['HTTP_IF_NONE_MATCH'] = 'W/"something", "other"';
$_SERVER['HTTP_IF_MODIFIED_SINCE'] = '2012-01-01 00:00:00';
$RequestHandler = $this->getMock('RequestHandlerComponent', array('_stop'), array(&$this->Controller->Components));
$RequestHandler->response = $this->getMock('CakeResponse', array('notModified'));
$RequestHandler->response->etag('something', true);
$RequestHandler->response->modified('2012-01-01 00:00:00');
$RequestHandler->response->expects($this->once())->method('notModified');
$this->assertFalse($RequestHandler->beforeRender($this->Controller));
}
/**
* Test checkNotModified method
*
* @return void
**/
public function testCheckNotModifiedNoInfo() {
$RequestHandler = $this->getMock('RequestHandlerComponent', array('_stop'), array(&$this->Controller->Components));
$RequestHandler->response = $this->getMock('CakeResponse', array('notModified'));
$RequestHandler->response->expects($this->never())->method('notModified');
$this->assertNull($RequestHandler->beforeRender($this->Controller));
}
}

View file

@ -359,6 +359,14 @@ class TestComponent extends Object {
}
}
class Test2Component extends TestComponent {
public function beforeRender($controller) {
return false;
}
}
/**
* AnotherTestController class
*
@ -685,6 +693,21 @@ class ControllerTest extends CakeTestCase {
App::build();
}
/**
* test that a component beforeRender can change the controller view class.
*
* @return void
*/
public function testComponentCancelRender() {
$Controller = new Controller($this->getMock('CakeRequest'), new CakeResponse());
$Controller->uses = array();
$Controller->components = array('Test2');
$Controller->constructClasses();
$result = $Controller->render('index');
$this->assertInstanceOf('CakeResponse', $result);
}
/**
* testToBeInheritedGuardmethods method
*

View file

@ -17,6 +17,7 @@
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
*/
App::uses('CakeResponse', 'Network');
App::uses('CakeRequest', 'Network');
class CakeResponseTest extends CakeTestCase {
@ -182,11 +183,13 @@ class CakeResponseTest extends CakeTestCase {
$response->expects($this->at(0))
->method('_sendHeader')->with('HTTP/1.1 200 OK');
$response->expects($this->at(1))
->method('_sendHeader')->with('Content-Type', 'text/html; charset=UTF-8');
$response->expects($this->at(2))
->method('_sendHeader')->with('Content-Language', 'es');
$response->expects($this->at(3))
$response->expects($this->at(2))
->method('_sendHeader')->with('WWW-Authenticate', 'Negotiate');
$response->expects($this->at(3))
->method('_sendHeader')->with('Content-Length', 17);
$response->expects($this->at(4))
->method('_sendHeader')->with('Content-Type', 'text/html; charset=UTF-8');
$response->send();
}
@ -202,7 +205,9 @@ class CakeResponseTest extends CakeTestCase {
$response->expects($this->at(0))
->method('_sendHeader')->with('HTTP/1.1 200 OK');
$response->expects($this->at(1))
->method('_sendHeader')->with('Content-Type', 'audio/mpeg; charset=UTF-8');
->method('_sendHeader')->with('Content-Length', 17);
$response->expects($this->at(2))
->method('_sendHeader')->with('Content-Type', 'audio/mpeg');
$response->send();
}
@ -218,7 +223,9 @@ class CakeResponseTest extends CakeTestCase {
$response->expects($this->at(0))
->method('_sendHeader')->with('HTTP/1.1 200 OK');
$response->expects($this->at(1))
->method('_sendHeader')->with('Content-Type', 'audio/mpeg; charset=UTF-8');
->method('_sendHeader')->with('Content-Length', 17);
$response->expects($this->at(2))
->method('_sendHeader')->with('Content-Type', 'audio/mpeg');
$response->send();
}
@ -232,9 +239,9 @@ class CakeResponseTest extends CakeTestCase {
$response->expects($this->at(0))
->method('_sendHeader')->with('HTTP/1.1 302 Found');
$response->expects($this->at(1))
->method('_sendHeader')->with('Content-Type', 'text/html; charset=UTF-8');
$response->expects($this->at(2))
->method('_sendHeader')->with('Location', 'http://www.example.com');
$response->expects($this->at(2))
->method('_sendHeader')->with('Content-Type', 'text/html; charset=UTF-8');
$response->send();
}
@ -247,8 +254,7 @@ class CakeResponseTest extends CakeTestCase {
$expected = array(
'Expires' => 'Mon, 26 Jul 1997 05:00:00 GMT',
'Last-Modified' => gmdate("D, d M Y H:i:s") . " GMT",
'Cache-Control' => 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0',
'Pragma' => 'no-cache'
'Cache-Control' => 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0'
);
$response->disableCache();
$this->assertEquals($response->header(), $expected);
@ -261,13 +267,13 @@ class CakeResponseTest extends CakeTestCase {
public function testCache() {
$response = new CakeResponse();
$since = time();
$time = '+1 day';
$time = new DateTime('+1 day', new DateTimeZone('UTC'));
$response->expires('+1 day');
$expected = array(
'Date' => gmdate("D, j M Y G:i:s ", $since) . 'GMT',
'Last-Modified' => gmdate("D, j M Y G:i:s ", $since) . 'GMT',
'Expires' => gmdate("D, j M Y H:i:s", strtotime($time)) . " GMT",
'Cache-Control' => 'public, max-age=' . (strtotime($time) - time()),
'Pragma' => 'cache'
'Last-Modified' => gmdate("D, j M Y H:i:s ", $since) . 'GMT',
'Expires' => $time->format('D, j M Y H:i:s') . ' GMT',
'Cache-Control' => 'public, max-age=' . ($time->format('U') - time())
);
$response->cache($since);
$this->assertEquals($response->header(), $expected);
@ -277,10 +283,9 @@ class CakeResponseTest extends CakeTestCase {
$time = '+5 day';
$expected = array(
'Date' => gmdate("D, j M Y G:i:s ", $since) . 'GMT',
'Last-Modified' => gmdate("D, j M Y G:i:s ", $since) . 'GMT',
'Last-Modified' => gmdate("D, j M Y H:i:s ", $since) . 'GMT',
'Expires' => gmdate("D, j M Y H:i:s", strtotime($time)) . " GMT",
'Cache-Control' => 'public, max-age=' . (strtotime($time) - time()),
'Pragma' => 'cache'
'Cache-Control' => 'public, max-age=' . (strtotime($time) - time())
);
$response->cache($since, $time);
$this->assertEquals($response->header(), $expected);
@ -290,10 +295,9 @@ class CakeResponseTest extends CakeTestCase {
$time = time();
$expected = array(
'Date' => gmdate("D, j M Y G:i:s ", $since) . 'GMT',
'Last-Modified' => gmdate("D, j M Y G:i:s ", $since) . 'GMT',
'Last-Modified' => gmdate("D, j M Y H:i:s ", $since) . 'GMT',
'Expires' => gmdate("D, j M Y H:i:s", $time) . " GMT",
'Cache-Control' => 'public, max-age=0',
'Pragma' => 'cache'
'Cache-Control' => 'public, max-age=0'
);
$response->cache($since, $time);
$this->assertEquals($response->header(), $expected);
@ -439,9 +443,9 @@ class CakeResponseTest extends CakeTestCase {
$response->expects($this->once())->method('_sendContent')->with('the response body');
$response->expects($this->at(0))
->method('_sendHeader')->with('HTTP/1.1 200 OK');
$response->expects($this->at(1))
->method('_sendHeader')->with('Content-Type', 'text/html; charset=UTF-8');
$response->expects($this->at(2))
->method('_sendHeader')->with('Content-Type', 'text/html; charset=UTF-8');
$response->expects($this->at(1))
->method('_sendHeader')->with('Content-Length', strlen('the response body'));
$response->send();
@ -451,9 +455,9 @@ class CakeResponseTest extends CakeTestCase {
$response->expects($this->once())->method('_sendContent')->with($body);
$response->expects($this->at(0))
->method('_sendHeader')->with('HTTP/1.1 200 OK');
$response->expects($this->at(1))
->method('_sendHeader')->with('Content-Type', 'text/html; charset=UTF-8');
$response->expects($this->at(2))
->method('_sendHeader')->with('Content-Type', 'text/html; charset=UTF-8');
$response->expects($this->at(1))
->method('_sendHeader')->with('Content-Length', 116);
$response->send();
@ -471,7 +475,7 @@ class CakeResponseTest extends CakeTestCase {
$response->header('Content-Length', 1);
$response->expects($this->never())->method('outputCompressed');
$response->expects($this->once())->method('_sendContent')->with($body);
$response->expects($this->at(2))
$response->expects($this->at(1))
->method('_sendHeader')->with('Content-Length', 1);
$response->send();
@ -494,10 +498,427 @@ class CakeResponseTest extends CakeTestCase {
$response->expects($this->at(0))
->method('_sendHeader')->with('HTTP/1.1 200 OK');
$response->expects($this->at(1))
->method('_sendHeader')->with('Content-Type', 'text/html; charset=UTF-8');
$response->expects($this->at(2))
->method('_sendHeader')->with('Content-Length', strlen($goofyOutput) + 116);
$response->expects($this->at(2))
->method('_sendHeader')->with('Content-Type', 'text/html; charset=UTF-8');
$response->send();
ob_end_clean();
}
/**
* Tests getting/setting the protocol
*
* @return void
*/
public function testProtocol() {
$response = $this->getMock('CakeResponse', array('_sendHeader', '_sendContent'));
$response->protocol('HTTP/1.0');
$this->assertEquals('HTTP/1.0', $response->protocol());
$response->expects($this->at(0))
->method('_sendHeader')->with('HTTP/1.0 200 OK');
$response->send();
}
/**
* Tests getting/setting the Content-Length
*
* @return void
*/
public function testLength() {
$response = $this->getMock('CakeResponse', array('_sendHeader', '_sendContent'));
$response->length(100);
$this->assertEquals(100, $response->length());
$response->expects($this->at(1))
->method('_sendHeader')->with('Content-Length', 100);
$response->send();
$response = $this->getMock('CakeResponse', array('_sendHeader', '_sendContent'));
$response->length(false);
$this->assertFalse($response->length());
$response->expects($this->exactly(2))
->method('_sendHeader');
$response->send();
}
/**
* Tests that the response body is unset if the status code is 304 or 204
*
* @return void
*/
public function testUnmodifiedContent() {
$response = $this->getMock('CakeResponse', array('_sendHeader', '_sendContent'));
$response->body('This is a body');
$response->statusCode(204);
$response->expects($this->once())
->method('_sendContent')->with('');
$response->send();
$this->assertFalse(array_key_exists('Content-Type', $response->header()));
$response = $this->getMock('CakeResponse', array('_sendHeader', '_sendContent'));
$response->body('This is a body');
$response->statusCode(304);
$response->expects($this->once())
->method('_sendContent')->with('');
$response->send();
$response = $this->getMock('CakeResponse', array('_sendHeader', '_sendContent'));
$response->body('This is a body');
$response->statusCode(200);
$response->expects($this->once())
->method('_sendContent')->with('This is a body');
$response->send();
}
/**
* Tests setting the expiration date
*
* @return void
*/
public function testExpires() {
$response = $this->getMock('CakeResponse', array('_sendHeader', '_sendContent'));
$now = new DateTime('now', new DateTimeZone('America/Los_Angeles'));
$response->expires($now);
$now->setTimeZone(new DateTimeZone('UTC'));
$this->assertEquals($now->format('D, j M Y H:i:s') . ' GMT', $response->expires());
$response->expects($this->at(1))
->method('_sendHeader')->with('Expires', $now->format('D, j M Y H:i:s') . ' GMT');
$response->send();
$response = $this->getMock('CakeResponse', array('_sendHeader', '_sendContent'));
$now = time();
$response->expires($now);
$this->assertEquals(gmdate('D, j M Y H:i:s', $now) . ' GMT', $response->expires());
$response->expects($this->at(1))
->method('_sendHeader')->with('Expires', gmdate('D, j M Y H:i:s', $now) . ' GMT');
$response->send();
$response = $this->getMock('CakeResponse', array('_sendHeader', '_sendContent'));
$time = new DateTime('+1 day', new DateTimeZone('UTC'));
$response->expires('+1 day');
$this->assertEquals($time->format('D, j M Y H:i:s') . ' GMT', $response->expires());
$response->expects($this->at(1))
->method('_sendHeader')->with('Expires', $time->format('D, j M Y H:i:s') . ' GMT');
$response->send();
}
/**
* Tests setting the modification date
*
* @return void
*/
public function testModified() {
$response = $this->getMock('CakeResponse', array('_sendHeader', '_sendContent'));
$now = new DateTime('now', new DateTimeZone('America/Los_Angeles'));
$response->modified($now);
$now->setTimeZone(new DateTimeZone('UTC'));
$this->assertEquals($now->format('D, j M Y H:i:s') . ' GMT', $response->modified());
$response->expects($this->at(1))
->method('_sendHeader')->with('Last-Modified', $now->format('D, j M Y H:i:s') . ' GMT');
$response->send();
$response = $this->getMock('CakeResponse', array('_sendHeader', '_sendContent'));
$now = time();
$response->modified($now);
$this->assertEquals(gmdate('D, j M Y H:i:s', $now) . ' GMT', $response->modified());
$response->expects($this->at(1))
->method('_sendHeader')->with('Last-Modified', gmdate('D, j M Y H:i:s', $now) . ' GMT');
$response->send();
$response = $this->getMock('CakeResponse', array('_sendHeader', '_sendContent'));
$time = new DateTime('+1 day', new DateTimeZone('UTC'));
$response->modified('+1 day');
$this->assertEquals($time->format('D, j M Y H:i:s') . ' GMT', $response->modified());
$response->expects($this->at(1))
->method('_sendHeader')->with('Last-Modified', $time->format('D, j M Y H:i:s') . ' GMT');
$response->send();
}
/**
* Tests setting of public/private Cache-Control directives
*
* @return void
*/
public function testSharable() {
$response = $this->getMock('CakeResponse', array('_sendHeader', '_sendContent'));
$this->assertNull($response->sharable());
$response->sharable(true);
$headers = $response->header();
$this->assertEquals('public', $headers['Cache-Control']);
$response->expects($this->at(1))
->method('_sendHeader')->with('Cache-Control', 'public');
$response->send();
$response = $this->getMock('CakeResponse', array('_sendHeader', '_sendContent'));
$response->sharable(false);
$headers = $response->header();
$this->assertEquals('private', $headers['Cache-Control']);
$response->expects($this->at(1))
->method('_sendHeader')->with('Cache-Control', 'private');
$response->send();
$response = $this->getMock('CakeResponse', array('_sendHeader', '_sendContent'));
$response->sharable(true);
$headers = $response->header();
$this->assertEquals('public', $headers['Cache-Control']);
$response->sharable(false);
$headers = $response->header();
$this->assertEquals('private', $headers['Cache-Control']);
$response->expects($this->at(1))
->method('_sendHeader')->with('Cache-Control', 'private');
$response->send();
$this->assertFalse($response->sharable());
$response->sharable(true);
$this->assertTrue($response->sharable());
$response = new CakeResponse;
$response->sharable(true, 3600);
$headers = $response->header();
$this->assertEquals('public, s-maxage=3600', $headers['Cache-Control']);
$response = new CakeResponse;
$response->sharable(false, 3600);
$headers = $response->header();
$this->assertEquals('private, max-age=3600', $headers['Cache-Control']);
$response->send();
}
/**
* Tests setting of max-age Cache-Control directive
*
* @return void
*/
public function testMaxAge() {
$response = $this->getMock('CakeResponse', array('_sendHeader', '_sendContent'));
$this->assertNull($response->maxAge());
$response->maxAge(3600);
$this->assertEquals(3600, $response->maxAge());
$headers = $response->header();
$this->assertEquals('max-age=3600', $headers['Cache-Control']);
$response->expects($this->at(1))
->method('_sendHeader')->with('Cache-Control', 'max-age=3600');
$response->send();
$response = $this->getMock('CakeResponse', array('_sendHeader', '_sendContent'));
$response->maxAge(3600);
$response->sharable(false);
$headers = $response->header();
$this->assertEquals('max-age=3600, private', $headers['Cache-Control']);
$response->expects($this->at(1))
->method('_sendHeader')->with('Cache-Control', 'max-age=3600, private');
$response->send();
}
/**
* Tests setting of s-maxage Cache-Control directive
*
* @return void
*/
public function testSharedMaxAge() {
$response = $this->getMock('CakeResponse', array('_sendHeader', '_sendContent'));
$this->assertNull($response->maxAge());
$response->sharedMaxAge(3600);
$this->assertEquals(3600, $response->sharedMaxAge());
$headers = $response->header();
$this->assertEquals('s-maxage=3600', $headers['Cache-Control']);
$response->expects($this->at(1))
->method('_sendHeader')->with('Cache-Control', 's-maxage=3600');
$response->send();
$response = $this->getMock('CakeResponse', array('_sendHeader', '_sendContent'));
$response->sharedMaxAge(3600);
$response->sharable(true);
$headers = $response->header();
$this->assertEquals('s-maxage=3600, public', $headers['Cache-Control']);
$response->expects($this->at(1))
->method('_sendHeader')->with('Cache-Control', 's-maxage=3600, public');
$response->send();
}
/**
* Tests setting of must-revalidate Cache-Control directive
*
* @return void
*/
public function testMustRevalidate() {
$response = $this->getMock('CakeResponse', array('_sendHeader', '_sendContent'));
$this->assertFalse($response->mustRevalidate());
$response->mustRevalidate(true);
$this->assertTrue($response->mustRevalidate());
$headers = $response->header();
$this->assertEquals('must-revalidate', $headers['Cache-Control']);
$response->expects($this->at(1))
->method('_sendHeader')->with('Cache-Control', 'must-revalidate');
$response->send();
$response->mustRevalidate(false);
$this->assertFalse($response->mustRevalidate());
$response = $this->getMock('CakeResponse', array('_sendHeader', '_sendContent'));
$response->sharedMaxAge(3600);
$response->mustRevalidate(true);
$headers = $response->header();
$this->assertEquals('s-maxage=3600, must-revalidate', $headers['Cache-Control']);
$response->expects($this->at(1))
->method('_sendHeader')->with('Cache-Control', 's-maxage=3600, must-revalidate');
$response->send();
}
/**
* Tests getting/setting the Vary header
*
* @return void
*/
public function testVary() {
$response = $this->getMock('CakeResponse', array('_sendHeader', '_sendContent'));
$response->vary('Accept-encoding');
$this->assertEquals(array('Accept-encoding'), $response->vary());
$response->expects($this->at(1))
->method('_sendHeader')->with('Vary', 'Accept-encoding');
$response->send();
$response = $this->getMock('CakeResponse', array('_sendHeader', '_sendContent'));
$response->vary(array('Accept-language', 'Accept-encoding'));
$response->expects($this->at(1))
->method('_sendHeader')->with('Vary', 'Accept-language, Accept-encoding');
$response->send();
$this->assertEquals(array('Accept-language', 'Accept-encoding'), $response->vary());
}
/**
* Tests getting/setting the Etag header
*
* @return void
*/
public function testEtag() {
$response = $this->getMock('CakeResponse', array('_sendHeader', '_sendContent'));
$response->etag('something');
$this->assertEquals('"something"', $response->etag());
$response->expects($this->at(1))
->method('_sendHeader')->with('Etag', '"something"');
$response->send();
$response = $this->getMock('CakeResponse', array('_sendHeader', '_sendContent'));
$response->etag('something', true);
$this->assertEquals('W/"something"', $response->etag());
$response->expects($this->at(1))
->method('_sendHeader')->with('Etag', 'W/"something"');
$response->send();
}
/**
* Tests that the response is able to be marked as not modified
*
* @return void
*/
public function testNotModified() {
$response = $this->getMock('CakeResponse', array('_sendHeader', '_sendContent'));
$response->body('something');
$response->statusCode(200);
$response->length(100);
$response->modified('now');
$response->notModified();
$this->assertEmpty($response->header());
$this->assertEmpty($response->body());
$this->assertEquals(304, $response->statusCode());
}
/**
* Test checkNotModified method
*
* @return void
**/
public function testCheckNotModifiedByEtagStar() {
$_SERVER['HTTP_IF_NONE_MATCH'] = '*';
$response = $this->getMock('CakeResponse', array('notModified'));
$response->etag('something');
$response->expects($this->once())->method('notModified');
$response->checkNotModified(new CakeRequest);
}
/**
* Test checkNotModified method
*
* @return void
**/
public function testCheckNotModifiedByEtagExact() {
$_SERVER['HTTP_IF_NONE_MATCH'] = 'W/"something", "other"';
$response = $this->getMock('CakeResponse', array('notModified'));
$response->etag('something', true);
$response->expects($this->once())->method('notModified');
$this->assertTrue($response->checkNotModified(new CakeRequest));
}
/**
* Test checkNotModified method
*
* @return void
**/
public function testCheckNotModifiedByEtagAndTime() {
$_SERVER['HTTP_IF_NONE_MATCH'] = 'W/"something", "other"';
$_SERVER['HTTP_IF_MODIFIED_SINCE'] = '2012-01-01 00:00:00';
$response = $this->getMock('CakeResponse', array('notModified'));
$response->etag('something', true);
$response->modified('2012-01-01 00:00:00');
$response->expects($this->once())->method('notModified');
$this->assertTrue($response->checkNotModified(new CakeRequest));
}
/**
* Test checkNotModified method
*
* @return void
**/
public function testCheckNotModifiedByEtagAndTimeMismatch() {
$_SERVER['HTTP_IF_NONE_MATCH'] = 'W/"something", "other"';
$_SERVER['HTTP_IF_MODIFIED_SINCE'] = '2012-01-01 00:00:00';
$response = $this->getMock('CakeResponse', array('notModified'));
$response->etag('something', true);
$response->modified('2012-01-01 00:00:01');
$response->expects($this->never())->method('notModified');
$this->assertFalse($response->checkNotModified(new CakeRequest));
}
/**
* Test checkNotModified method
*
* @return void
**/
public function testCheckNotModifiedByEtagMismatch() {
$_SERVER['HTTP_IF_NONE_MATCH'] = 'W/"something-else", "other"';
$_SERVER['HTTP_IF_MODIFIED_SINCE'] = '2012-01-01 00:00:00';
$response = $this->getMock('CakeResponse', array('notModified'));
$response->etag('something', true);
$response->modified('2012-01-01 00:00:00');
$response->expects($this->never())->method('notModified');
$this->assertFalse($response->checkNotModified(new CakeRequest));
}
/**
* Test checkNotModified method
*
* @return void
**/
public function testCheckNotModifiedByTime() {
$_SERVER['HTTP_IF_MODIFIED_SINCE'] = '2012-01-01 00:00:00';
$response = $this->getMock('CakeResponse', array('notModified'));
$response->modified('2012-01-01 00:00:00');
$response->expects($this->once())->method('notModified');
$this->assertTrue($response->checkNotModified(new CakeRequest));
}
/**
* Test checkNotModified method
*
* @return void
**/
public function testCheckNotModifiedNoHints() {
$_SERVER['HTTP_IF_NONE_MATCH'] = 'W/"something", "other"';
$_SERVER['HTTP_IF_MODIFIED_SINCE'] = '2012-01-01 00:00:00';
$response = $this->getMock('CakeResponse', array('notModified'));
$response->expects($this->never())->method('notModified');
$this->assertFalse($response->checkNotModified(new CakeRequest));
}
}

View file

@ -48,51 +48,59 @@ class RedirectRouteTestCase extends CakeTestCase {
$route->stop = false;
$route->response = $this->getMock('CakeResponse', array('_sendHeader'));
$result = $route->parse('/home');
$this->assertEquals($route->response->header(), array('Location' => Router::url('/posts', true)));
$header = $route->response->header();
$this->assertEquals($header['Location'], Router::url('/posts', true));
$route = new RedirectRoute('/home', array('controller' => 'posts', 'action' => 'index'));
$route->stop = false;
$route->response = $this->getMock('CakeResponse', array('_sendHeader'));
$result = $route->parse('/home');
$this->assertEquals($route->response->header(), array('Location' => Router::url('/posts', true)));
$header = $route->response->header();
$this->assertEquals($header['Location'], Router::url('/posts', true));
$this->assertEquals($route->response->statusCode(), 301);
$route = new RedirectRoute('/google', 'http://google.com');
$route->stop = false;
$route->response = $this->getMock('CakeResponse', array('_sendHeader'));
$result = $route->parse('/google');
$this->assertEquals($route->response->header(), array('Location' => 'http://google.com'));
$header = $route->response->header();
$this->assertEquals($header['Location'], 'http://google.com');
$route = new RedirectRoute('/posts/*', array('controller' => 'posts', 'action' => 'view'), array('status' => 302));
$route->stop = false;
$route->response = $this->getMock('CakeResponse', array('_sendHeader'));
$result = $route->parse('/posts/2');
$this->assertEquals($route->response->header(), array('Location' => Router::url('/posts/view', true)));
$header = $route->response->header();
$this->assertEquals($header['Location'], Router::url('/posts/view', true));
$this->assertEquals($route->response->statusCode(), 302);
$route = new RedirectRoute('/posts/*', array('controller' => 'posts', 'action' => 'view'), array('persist' => true));
$route->stop = false;
$route->response = $this->getMock('CakeResponse', array('_sendHeader'));
$result = $route->parse('/posts/2');
$this->assertEquals($route->response->header(), array('Location' => Router::url('/posts/view/2', true)));
$header = $route->response->header();
$this->assertEquals($header['Location'], Router::url('/posts/view/2', true));
$route = new RedirectRoute('/posts/*', '/test', array('persist' => true));
$route->stop = false;
$route->response = $this->getMock('CakeResponse', array('_sendHeader'));
$result = $route->parse('/posts/2');
$this->assertEquals($route->response->header(), array('Location' => Router::url('/test', true)));
$header = $route->response->header();
$this->assertEquals($header['Location'], Router::url('/test', true));
$route = new RedirectRoute('/my_controllers/:action/*', array('controller' => 'tags', 'action' => 'add'), array('persist' => true));
$route->stop = false;
$route->response = $this->getMock('CakeResponse', array('_sendHeader'));
$result = $route->parse('/my_controllers/do_something/passme/named:param');
$this->assertEquals($route->response->header(), array('Location' => Router::url('/tags/add/passme/named:param', true)));
$header = $route->response->header();
$this->assertEquals($header['Location'], Router::url('/tags/add/passme/named:param', true));
$route = new RedirectRoute('/my_controllers/:action/*', array('controller' => 'tags', 'action' => 'add'));
$route->stop = false;
$route->response = $this->getMock('CakeResponse', array('_sendHeader'));
$result = $route->parse('/my_controllers/do_something/passme/named:param');
$this->assertEquals($route->response->header(), array('Location' => Router::url('/tags/add', true)));
$header = $route->response->header();
$this->assertEquals($header['Location'], Router::url('/tags/add', true));
}
}

View file

@ -2519,7 +2519,8 @@ class RouterTest extends CakeTestCase {
$this->assertEquals(Router::$routes[0]->options['status'], 302);
Router::parse('/blog');
$this->assertEquals(Router::$routes[0]->response->header(), array('Location' => Router::url('/posts', true)));
$header = Router::$routes[0]->response->header();
$this->assertEquals($header['Location'], Router::url('/posts', true));
$this->assertEquals(Router::$routes[0]->response->statusCode(), 302);
Router::$routes[0]->response = $this->getMock('CakeResponse', array('_sendHeader'));