Skip to content

Commit

Permalink
Add the basic coneg response headers.
Browse files Browse the repository at this point in the history
See discussion at #39
Added the basic coneg headers as per 2295 and 2616
Implemented the headers in the abstract Accept class for combined implementation for all the accept headers.
Added Accept-Language to the mix
Adde Content-Language to the response headers.
Headers now include:
	The Extended Vary:
	Expires: for backward compatibility with HTTP/1.0
	Cache-Control: Not to confuse HTTP/1.1 about our intentions with Expires.
  • Loading branch information
nickl- committed Jun 24, 2012
1 parent 4d25277 commit 76cecc6
Show file tree
Hide file tree
Showing 6 changed files with 438 additions and 13 deletions.
269 changes: 269 additions & 0 deletions library/Respect/Rest/Router_1.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,269 @@
<?php

namespace Respect\Rest;

use Exception;
use ReflectionClass;
use ReflectionMethod;
use RuntimeException;
use InvalidArgumentException;
use Respect\Rest\Routes;
use Respect\Rest\Routes\AbstractRoute;
use Respect\Rest\Exception\MethodNotAllowed;

/**
* @method \Respect\Rest\Routes\AbstractRoute get(string $path, $routeTarget)
* @method \Respect\Rest\Routes\AbstractRoute post(string $path, $routeTarget)
* @method \Respect\Rest\Routes\AbstractRoute put(string $path, $routeTarget)
* @method \Respect\Rest\Routes\AbstractRoute head(string $path, $routeTarget)
* @method \Respect\Rest\Routes\AbstractRoute delete(string $path, $routeTarget)
* @method \Respect\Rest\Routes\AbstractRoute any(string $path, $routeTarget)
*/
class Router
{

public $isAutoDispatched = true;
public $methodOverriding = false;
protected $globalRoutines = array();
protected $routes = array();
protected $virtualHost = '';

/** Cleans up an return an array of extracted parameters */
public static function cleanUpParams($params)
{
return array_values(
array_filter(
$params, function($param) {
return $param !== '';
}
)
);
}

public function __call($method, $args)
{
if (count($args) < 2)
throw new InvalidArgumentException('Any route binding must at least 2 arguments');

list ($path, $routeTarget) = $args;

if (is_callable($routeTarget)) //closures, func names, callbacks
return $this->callbackRoute($method, $path, $routeTarget);
elseif ($routeTarget instanceof Routable) //direct instances
return $this->instanceRoute($method, $path, $routeTarget);
elseif (!is_string($routeTarget)) //static returns the argument itself
return $this->staticRoute($method, $path, $routeTarget);
elseif (is_string($routeTarget) && !class_exists($routeTarget))
return $this->staticRoute($method, $path, $routeTarget);
else
if (!isset($args[2])) //raw classnames
return $this->classRoute($method, $path, $routeTarget);
elseif (is_callable($args[2])) //classnames as factories
return $this->factoryRoute($method, $path, $routeTarget, $args[2]);
else //classnames with constructor arguments
return $this->classRoute($method, $path, $routeTarget, $args[2]);
}

public function __construct($virtualHost=null)
{
$this->virtualHost = $virtualHost;
}

public function __destruct()
{
if (!$this->isAutoDispatched || !isset($_SERVER['SERVER_PROTOCOL']))
return;

echo $this;
}

public function __toString()
{
return $this->run();
}

/** Applies a routine to every route */
public function always($routineName, $routineParameter)
{
$routineClass = 'Respect\\Rest\\Routines\\' . $routineName;
$routineInstance = new $routineClass($routineParameter);
$this->globalRoutines[] = $routineInstance;

foreach ($this->routes as $route)
$route->appendRoutine($routineInstance);

return $this;
}

/** Appends a pre-built route to the dispatcher */
public function appendRoute(AbstractRoute $route)
{
$this->routes[] = $route;

foreach ($this->globalRoutines as $routine)
$route->appendRoutine($routine);
}

/** Creates and returns a callback-based route */
public function callbackRoute($method, $path, $callback)
{
$route = new Routes\Callback($method, $path, $callback);
$this->appendRoute($route);
return $route;
}

/** Creates and returns a class-based route */
public function classRoute($method, $path, $class, array $arguments=array())
{
$route = new Routes\ClassName($method, $path, $class, $arguments);
$this->appendRoute($route);
return $route;
}

/** Dispatch the current route with a standard Request */
public function dispatch($method=null, $uri=null)
{
return $this->dispatchRequest(new Request($method, $uri));
}

/** Dispatch the current route with a custom Request */
public function dispatchRequest(Request $request=null)
{
$this->isAutoDispatched = false;
if (!$request)
$request = new Request;

if ($this->methodOverriding && isset($_REQUEST['_method']) && $request->method == 'POST')
$request->method = strtoupper($_REQUEST['_method']);

if ($request->method === 'OPTIONS' && $request->uri === '*') {
$allowedMethods = array();

foreach ($this->routes as $route)
$allowedMethods[] = $route->method;

if ($allowedMethods)
header('Allow: '.implode(', ', $allowedMethods));

return $request;
}

usort($this->routes, function($a, $b) {
$a = $a->pattern;
$b = $b->pattern;

if (0 === stripos($a, $b) || $a == AbstractRoute::CATCHALL_IDENTIFIER)
return 1;
elseif (0 === stripos($b, $a) || $b == AbstractRoute::CATCHALL_IDENTIFIER)
return -1;
elseif (substr_count($a, '/') < substr_count($b, '/'))
return 1;

return substr_count($a, AbstractRoute::PARAM_IDENTIFIER)
< substr_count($b, AbstractRoute::PARAM_IDENTIFIER) ? -1 : 1;
}
);

if ($this->virtualHost)
$request->uri =
preg_replace('#^' . preg_quote($this->virtualHost) . '#', '', $request->uri);

$matchedByPath = array();
$allowedMethods = array();
$paramsByPath = new \SplObjectStorage;

foreach ($this->routes as $route)
if ($this->matchRoute($request, $route, $params)) {

$paramsByPath[$route] = $params;

$matchedByPath[] = $route;
$allowedMethods[] = $route->method;
}

if ($request->method === 'OPTIONS' && $allowedMethods) {
header('Allow: '.implode(', ', $allowedMethods));
return $request;
}

if (!$matchedByPath)
header('HTTP/1.1 404');

foreach ($matchedByPath as $route)
if (0 !== stripos($request->method, '__')
&& ($route->method === $request->method
|| $route->method === 'ANY'
|| ($route->method === 'GET' && $request->method === 'HEAD')))
if ($route->matchRoutines($request, $tempParams = $paramsByPath[$route]))
return $this->configureRequest($request, $route, static::cleanUpParams($tempParams));
else
$badRequest = true;

if ($matchedByPath && !isset($badRequest))
header('HTTP/1.1 405');

if ($matchedByPath && $allowedMethods)
header('Allow: '.implode(', ', $allowedMethods));

$request->route = null;
return $request;
}

/** Dispatches and get response with default request parameters */
public function run(Request $request=null)
{
$route = $this->dispatchRequest($request);
if (!$route || (isset($request->method) && $request->method === 'HEAD'))
return null;

$response = $route->response();
if (is_resource($response)) {
fpassthru($response);
return '';
}
return (string) $response;

}

/** Creates and returns an factory-based route */
public function factoryRoute($method, $path, $className, $factory)
{
$route = new Routes\Factory($method, $path, $className, $factory);
$this->appendRoute($route);
return $route;
}

/** Creates and returns an instance-based route */
public function instanceRoute($method, $path, $instance)
{
$route = new Routes\Instance($method, $path, $instance);
$this->appendRoute($route);
return $route;
}

/** Creates and returns a static route */
public function staticRoute($method, $path, $instance)
{
$route = new Routes\StaticValue($method, $path, $instance);
$this->appendRoute($route);
return $route;
}

/** Configures a request for a specific route with specific parameters */
protected function configureRequest(Request $request, AbstractRoute $route, array $params=array())
{
$request->route = $route;
$request->params = $params;
return $request;
}

/** Returns true if the passed route matches the passed request */
protected function matchRoute(Request $request, AbstractRoute $route, &$params=array())
{
if ($route->match($request, $params)) {
$request->route = $route;
return true;
}
}

}
28 changes: 26 additions & 2 deletions library/Respect/Rest/Routines/AbstractAccept.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,36 @@ protected function negotiate(Request $request)
arsort($acceptList);
foreach ($acceptList as $requested => $quality)
foreach ($this->callbacksPerMimeType as $provided => $callback)
if ($this->compareItens($requested, $provided))
return $this->negotiated[$request] = $callback;
if (false !== ($accepted = $this->compareItens($requested, $provided))) {
$this->negotiated[$request] = $callback;
return $this->responseHeaders($accepted);
}

return false;
}

private function responseHeaders($negotiated) {
$header_type = preg_replace(
array(
'/(^.*)(?=\w*$)/U', // select namespace to strip
'/(?!^)([A-Z]+)/' // select camels to add -
),
array('','-$1'), get_class($this));

$content_header = 'Content-Type';

if (false !== strpos($header_type, '-'))
$content_header = str_replace('Accept', 'Content', $header_type);

header("$content_header: $negotiated"); // RFC 2616
header("Vary: negotiate,".strtolower($header_type)); // RFC 2616/2295
header("Content-Location: {$_SERVER['REQUEST_URI']}"); // RFC 2616
header('Expires: Thu, 01 Jan 1980 00:00:00 GMT'); // RFC 2295
header('Cache-Control: max-age=86400'); // RFC 2295

return true;
}

public function by(Request $request, $params)
{
$unsyncedParams = $request->params;
Expand Down
Loading

0 comments on commit 76cecc6

Please sign in to comment.