-
Notifications
You must be signed in to change notification settings - Fork 102
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add the basic coneg response headers.
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
Showing
6 changed files
with
438 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.