function run()
{
if ($this->blacklisted($this->hive['IP'])) {
// Spammer detected
$this->error(403);
}
if (!$this->hive['ROUTES']) {
// No routes defined
user_error(self::E_Routes, E_USER_ERROR);
}
// Match specific routes first
$paths = [];
foreach ($keys = array_keys($this->hive['ROUTES']) as $key) {
$path = preg_replace('/@\\w+/', '*@', $key);
if (substr($path, -1) != '*') {
$path .= '+';
}
$paths[] = $path;
}
$vals = array_values($this->hive['ROUTES']);
array_multisort($paths, SORT_DESC, $keys, $vals);
$this->hive['ROUTES'] = array_combine($keys, $vals);
// Convert to BASE-relative URL
$req = $this->rel(urldecode($this->hive['PATH']));
if ($cors = isset($this->hive['HEADERS']['Origin']) && $this->hive['CORS']['origin']) {
$cors = $this->hive['CORS'];
header('Access-Control-Allow-Origin: ' . $cors['origin']);
header('Access-Control-Allow-Credentials: ' . ($cors['credentials'] ? 'true' : 'false'));
}
$allowed = [];
foreach ($this->hive['ROUTES'] as $pattern => $routes) {
if (!($args = $this->mask($pattern, $req))) {
continue;
}
ksort($args);
$route = NULL;
$ptr = $this->hive['CLI'] ? self::REQ_CLI : $this->hive['AJAX'] + 1;
if (isset($routes[$ptr][$this->hive['VERB']]) || isset($routes[$ptr = 0])) {
$route = $routes[$ptr];
}
if (!$route) {
continue;
}
if ($this->hive['VERB'] != 'OPTIONS' && isset($route[$this->hive['VERB']])) {
if ($this->hive['VERB'] == 'GET' && preg_match('/.+\\/$/', $this->hive['PATH'])) {
$this->reroute(substr($this->hive['PATH'], 0, -1) . ($this->hive['QUERY'] ? '?' . $this->hive['QUERY'] : ''));
}
list($handler, $ttl, $kbps, $alias) = $route[$this->hive['VERB']];
// Capture values of route pattern tokens
$this->hive['PARAMS'] = $args;
// Save matching route
$this->hive['ALIAS'] = $alias;
$this->hive['PATTERN'] = $pattern;
if ($cors && $cors['expose']) {
header('Access-Control-Expose-Headers: ' . (is_array($cors['expose']) ? implode(',', $cors['expose']) : $cors['expose']));
}
if (is_string($handler)) {
// Replace route pattern tokens in handler if any
$handler = preg_replace_callback('/({)?@(\\w+\\b)(?(1)})/', function ($id) use($args) {
$pid = count($id) > 2 ? 2 : 1;
return isset($args[$id[$pid]]) ? $args[$id[$pid]] : $id[0];
}, $handler);
if (preg_match('/(.+)\\h*(?:->|::)/', $handler, $match) && !class_exists($match[1])) {
$this->error(404);
}
}
// Process request
$result = NULL;
$body = '';
$now = microtime(TRUE);
if (preg_match('/GET|HEAD/', $this->hive['VERB']) && $ttl) {
// Only GET and HEAD requests are cacheable
$headers = $this->hive['HEADERS'];
$cache = Cache::instance();
$cached = $cache->exists($hash = $this->hash($this->hive['VERB'] . ' ' . $this->hive['URI']) . '.url', $data);
if ($cached) {
if (isset($headers['If-Modified-Since']) && strtotime($headers['If-Modified-Since']) + $ttl > $now) {
$this->status(304);
die;
}
// Retrieve from cache backend
list($headers, $body, $result) = $data;
if (!$this->hive['CLI']) {
array_walk($headers, 'header');
}
$this->expire($cached[0] + $ttl - $now);
} else {
// Expire HTTP client-cached page
$this->expire($ttl);
}
} else {
$this->expire(0);
}
if (!strlen($body)) {
if (!$this->hive['RAW'] && !$this->hive['BODY']) {
$this->hive['BODY'] = file_get_contents('php://input');
}
ob_start();
// Call route handler
$result = $this->call($handler, [$this, $args], 'beforeroute,afterroute');
$body = ob_get_clean();
if (isset($cache) && !error_get_last()) {
// Save to cache backend
$cache->set($hash, [preg_grep('/Set-Cookie\\:/', headers_list(), PREG_GREP_INVERT), $body, $result], $ttl);
}
}
$this->hive['RESPONSE'] = $body;
if (!$this->hive['QUIET']) {
if ($kbps) {
$ctr = 0;
foreach (str_split($body, 1024) as $part) {
// Throttle output
$ctr++;
if ($ctr / $kbps > ($elapsed = microtime(TRUE) - $now) && !connection_aborted()) {
usleep(1000000.0 * ($ctr / $kbps - $elapsed));
}
echo $part;
}
} else {
echo $body;
}
}
return $result;
}
$allowed = array_merge($allowed, array_keys($route));
}
if (!$allowed) {
// URL doesn't match any route
$this->error(404);
} elseif (!$this->hive['CLI']) {
// Unhandled HTTP method
header('Allow: ' . implode(',', array_unique($allowed)));
if ($cors) {
header('Access-Control-Allow-Methods: OPTIONS,' . implode(',', $allowed));
if ($cors['headers']) {
header('Access-Control-Allow-Headers: ' . (is_array($cors['headers']) ? implode(',', $cors['headers']) : $cors['headers']));
}
if ($cors['ttl'] > 0) {
header('Access-Control-Max-Age: ' . $cors['ttl']);
}
}
if ($this->hive['VERB'] != 'OPTIONS') {
$this->error(405);
}
}
return FALSE;
}