添加后台代理代码
This commit is contained in:
346
codes/agent/game/api/lib/phprs/Router.php
Normal file
346
codes/agent/game/api/lib/phprs/Router.php
Normal file
@@ -0,0 +1,346 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* $Id: Router.php 58820 2015-01-16 16:29:33Z caoyangmin $
|
||||
* @author caoyangmin(caoyangmin@baidu.com)
|
||||
* @brief Router
|
||||
*/
|
||||
namespace phprs;
|
||||
use phprs\util\HttpRouterEntries;
|
||||
use phprs\util\Verify;
|
||||
use phprs\util\AutoClassLoader;
|
||||
use phprs\util\Logger;
|
||||
use phprs\util\exceptions\NotFound;
|
||||
use phprs\util\ClassLoader;
|
||||
|
||||
/**
|
||||
* 结果保留在内存
|
||||
* @author caoym
|
||||
*/
|
||||
class BufferedRespond extends Response{
|
||||
/**
|
||||
* flush 不直接输出
|
||||
* @see \phprs\Response::flush()
|
||||
* @param string limit 现在输出的项目
|
||||
* @param callable $func 输出方法
|
||||
* @return void
|
||||
*/
|
||||
public function flush($limit=null, $func=null)
|
||||
{
|
||||
return ;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* restful api 路由
|
||||
* 加载API类文件, 通过API类的@annotation获取路由规则信息, 当请求到来时, 调用匹配的
|
||||
* API类方法.
|
||||
*
|
||||
* 通常每一个请求只对应到一个最严格匹配的API接口, 所谓"最严格匹配",比如:
|
||||
* API1 接口 => url: /apis/test
|
||||
* API2 接口 => url: /apis
|
||||
* 那么当请求"/apis/test/123/" 最严格匹配的接口是API1
|
||||
*
|
||||
* 如果需要让一个请求经过多个API调用, 比如有时候会需要一个统一验证的接口, 让所有请
|
||||
* 求先通过验证接口, 再调用其他接口. 此时可以通过Router的hooks属性, 设置一组hook实
|
||||
* 现. hook其实和普通的接口一样, 只是在hooks中指定后, 执行行为将有所不同: 请求会按
|
||||
* 优先级逐个经过hook, 只要匹配, hook的方法就会被调用, 直到最后调用普通的API
|
||||
*
|
||||
* 通过@return({"break", true})停止请求链路
|
||||
*
|
||||
* @author caoym
|
||||
*/
|
||||
class Router
|
||||
{
|
||||
private $class_loader; //用于确保反序列化时自动加载类文件
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function __construct( ){
|
||||
//AutoClassLoader确保序列化后自动加载类文件
|
||||
$this->class_loader = new AutoClassLoader();
|
||||
ClassLoader::addInclude($this->api_path);
|
||||
//TODO: 支持名字空间
|
||||
//TODO: 支持多路径多类名
|
||||
$this->load($this->api_path, $this->apis, $this->api_method);
|
||||
//允许通过接口访问api信息
|
||||
if($this->export_apis){
|
||||
$this->loadApi($this->routes, __DIR__.'/apis/ApiExporter.php', 'phprs\apis\ApiExporter');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取api文件列表
|
||||
* @return array
|
||||
*/
|
||||
public function getApiFiles(){
|
||||
return $this->class_loader->getClasses();
|
||||
}
|
||||
/**
|
||||
* 调用路由规则匹配的api
|
||||
* @param Request $request
|
||||
* @param Response $respond
|
||||
* @return void
|
||||
*/
|
||||
public function __invoke($request=null, &$respond=null){
|
||||
if($request === null){
|
||||
$request = new Request(null,$this->url_begin);
|
||||
}
|
||||
if($respond==null){
|
||||
$respond = new Response();
|
||||
}
|
||||
$request['$.router'] = $this;
|
||||
//先按配置的顺序调用hook
|
||||
foreach ($this->hook_routes as $hook){
|
||||
$res = new BufferedRespond();
|
||||
if(!$this->invokeRoute($hook, $request, $res)){
|
||||
continue;
|
||||
}
|
||||
$respond->append($res->getBuffer());
|
||||
$break = false;
|
||||
$respond->flush('break', function($var)use(&$break){$break = $var;});
|
||||
if($break){
|
||||
Logger::info("invoke break");
|
||||
$respond->flush();
|
||||
return;
|
||||
}
|
||||
}
|
||||
$res = new BufferedRespond();
|
||||
|
||||
Verify::isTrue($this->invokeRoute($this->routes, $request, $res), new NotFound());
|
||||
$respond->append($res->getBuffer());
|
||||
$respond->flush();
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param string|array $api_path
|
||||
* @param string $apis
|
||||
* @param string $api_method
|
||||
*/
|
||||
public function load($api_path, $apis=null , $api_method=null){
|
||||
if(is_string($api_path)){
|
||||
$api_paths = array($api_path);
|
||||
}else{
|
||||
$api_paths = $api_path;
|
||||
}
|
||||
Verify::isTrue(is_array($api_paths), 'invalid param');
|
||||
|
||||
foreach ($api_paths as $api_path){
|
||||
$this->loadRoutes($this->routes, $api_path, $apis, $api_method);
|
||||
foreach ($this->hooks as $hook) {
|
||||
$hook_route=array();
|
||||
$this->loadRoutes($hook_route, $api_path.'/hooks', $hook, null);
|
||||
$this->hook_routes[] = $hook_route;
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getRoutes(){
|
||||
return $this->routes;
|
||||
}
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getHooks(){
|
||||
return $this->hook_routes;
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用路由规则匹配的api
|
||||
* @param array $routes 路由规则
|
||||
* @param unknown $request
|
||||
* @param unknown $respond
|
||||
* @return boolean 是否有匹配的接口被调用
|
||||
*/
|
||||
private function invokeRoute($routes, $request, &$respond){
|
||||
$method = $request['$._SERVER.REQUEST_METHOD'];
|
||||
$path = $request['$.path'];
|
||||
$uri = $request['$._SERVER.REQUEST_URI'];
|
||||
list(,$params) = explode('?', $uri)+array( null,null );
|
||||
$params = is_null($params)?null:explode('&', $params);
|
||||
|
||||
Logger::debug("try to find route $method ".$uri);
|
||||
$match_path = array();
|
||||
if(isset($routes[$method])){
|
||||
if(($api = $routes[$method]->findByArray($path,$params,$match_path)) !== null){
|
||||
Logger::debug("invoke $uri => {$api->getClassName()}::{$api->getMethodName()}");
|
||||
$api($request, $respond);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if(!isset($routes['*'])){
|
||||
return false;
|
||||
}
|
||||
if(($api = $routes['*']->find($uri, $match_path)) === null){
|
||||
return false;
|
||||
}
|
||||
Logger::debug("invoke $uri => {$api->getClassName()}::{$api->getMethodName()}");
|
||||
$api($request, $respond);
|
||||
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* @param string $apis_dir
|
||||
* @param string $class
|
||||
* @param string $method
|
||||
* @return void
|
||||
*/
|
||||
public function addRoutes($apis_dir, $class=null, $method=null){
|
||||
$this->loadRoutes($this->routes, $apis_dir, $class, $method);
|
||||
}
|
||||
/**
|
||||
* 遍历API目录生成路由规则
|
||||
* @param object $factory
|
||||
* @param string $apis_dir
|
||||
* @param string $class
|
||||
* @param string $method
|
||||
* @param array $skipclass 需要跳过的类名
|
||||
* @return Router
|
||||
*/
|
||||
private function loadRoutes(&$routes, $apis_dir, $class, $method){
|
||||
Logger::info("attempt to load router $apis_dir");
|
||||
$dir = null;
|
||||
|
||||
if(is_dir($apis_dir) && $class === null){
|
||||
$apis_dir=$apis_dir.'/';
|
||||
Verify::isTrue(is_dir($apis_dir), "$apis_dir not a dir");
|
||||
$dir = @dir($apis_dir);
|
||||
Verify::isTrue($dir !== null, "open dir $apis_dir failed");
|
||||
$geteach = function ()use($dir){
|
||||
$name = $dir->read();
|
||||
if(!$name){
|
||||
return $name;
|
||||
}
|
||||
return $name;
|
||||
};
|
||||
}else{
|
||||
if(is_file($apis_dir)){
|
||||
$files = array($apis_dir);
|
||||
$apis_dir = '';
|
||||
}else{
|
||||
$apis_dir=$apis_dir.'/';
|
||||
if(is_array($class)){
|
||||
foreach ($class as &$v){
|
||||
$v .= '.php';
|
||||
}
|
||||
$files = $class;
|
||||
}else{
|
||||
$files = array($class.'.php');
|
||||
}
|
||||
}
|
||||
$geteach = function ()use(&$files){
|
||||
// 🚨 PHP8兼容性:each()函数替换,严格保持原有行为
|
||||
// ⚠️ 重要:必须保持数组指针移动行为与each()完全一致
|
||||
// ⚠️ 重要:必须保持文件加载顺序绝对不变
|
||||
$key = key($files);
|
||||
if($key === null) {
|
||||
return false; // ⚠️ 保持与each()相同的结束判断逻辑
|
||||
}
|
||||
$value = current($files);
|
||||
next($files); // ⚠️ 关键:保持指针移动与each()完全一致
|
||||
return $value; // ⚠️ 保持返回值格式与each()[1]完全一致
|
||||
};
|
||||
}
|
||||
while( !!($entry = $geteach()) ){
|
||||
$path = $apis_dir. str_replace('\\', '/', $entry);
|
||||
if(is_file($path) && substr_compare ($entry, '.php', strlen($entry)-4,4,true) ==0){
|
||||
$class_name = substr($entry, 0, strlen($entry)-4);
|
||||
$this->loadApi($routes, $path, $class_name, $method);
|
||||
}else{
|
||||
Logger::debug($path.' ignored');
|
||||
}
|
||||
}
|
||||
if($dir !== null){
|
||||
$dir->close();
|
||||
}
|
||||
Logger::info("load router $apis_dir ok");
|
||||
return $routes;
|
||||
}
|
||||
/**
|
||||
* 加载api类
|
||||
* @param array $routes
|
||||
* @param string $class_file
|
||||
* @param string $class_name
|
||||
* @param string $method
|
||||
* @return void
|
||||
*/
|
||||
private function loadApi(&$routes, $class_file, $class_name, $method=null){
|
||||
Verify::isTrue(is_file($class_file), $class_file.' is not an exist file');
|
||||
Logger::debug("attempt to load api: $class_name, $class_file");
|
||||
|
||||
$this->class_loader->addClass($class_name, $class_file);
|
||||
$api = null;
|
||||
if ($this->ignore_load_error){
|
||||
try {
|
||||
$api = $this->factory->create('phprs\\Container', array($class_name, $method), null, null);
|
||||
} catch (\Exception $e) {
|
||||
Logger::warning("load api: $class_name, $class_file failed with ".$e->getMessage());
|
||||
return ;
|
||||
}
|
||||
}else{
|
||||
$api = $this->factory->create('phprs\\Container', array($class_name, $method), null, null);
|
||||
}
|
||||
|
||||
foreach ($api->routes as $http_method=>$route){
|
||||
if(!isset($routes[$http_method])){
|
||||
$routes[$http_method] = new HttpRouterEntries();
|
||||
}
|
||||
$cur = $routes[$http_method];
|
||||
foreach ($route as $entry){
|
||||
list($uri,$invoke,$strict) = $entry;
|
||||
$realpath = preg_replace('/\/+/', '/', '/'.$uri);
|
||||
$strict = ($strict===null)?$this->default_strict_matching:$strict;
|
||||
Verify::isTrue($cur->insert($realpath, $invoke, $strict), "repeated path $realpath");
|
||||
Logger::debug("api: $http_method $realpath => $class_name::{$entry[1]->method_name} ok, strict:$strict");
|
||||
}
|
||||
}
|
||||
Logger::debug("load api: $class_name, $class_file ok");
|
||||
}
|
||||
private $routes=array();
|
||||
private $hook_routes=array();
|
||||
|
||||
|
||||
/** @inject("ioc_factory") */
|
||||
public $factory;
|
||||
/** @property */
|
||||
private $api_path='apis';
|
||||
/** @property */
|
||||
private $apis=null;
|
||||
/** @property */
|
||||
private $api_method=null;
|
||||
|
||||
private $api_root=null;
|
||||
/** @property
|
||||
* 指定hook的类名, 从优先级高到低
|
||||
* 如果一条规则匹配多个hook, 则优先级高的先执行, 再执行优先级低的
|
||||
*/
|
||||
private $hooks=array();
|
||||
/**
|
||||
* @property
|
||||
* 是否允许通过接口获取api信息
|
||||
*/
|
||||
private $export_apis=true;
|
||||
/**
|
||||
* 用于匹配路由的url偏移
|
||||
* @property
|
||||
*/
|
||||
public $url_begin=0;
|
||||
|
||||
/**
|
||||
* 指定路由规则默认情况下是否严格匹配path,如果@route中已经指定严格模式,则忽略此默认设置
|
||||
* 严格模式将只允许同级目录匹配,否则父目录和子目录也匹配。
|
||||
* 非严格匹配时
|
||||
* 路由"GET /a" 和请求"GET /a"、"GET /a/b"、"GET /a/b/c"等匹配
|
||||
* 严格匹配时
|
||||
* 路由"GET /a" 和请求"GET /a"匹配、和"GET /a/b"、"GET /a/b/c"等不匹配
|
||||
* @property
|
||||
*/
|
||||
public $default_strict_matching=false;
|
||||
/**
|
||||
* @property 忽略类加载时的错误,只是跳过出错的接口。否则抛出异常。
|
||||
*/
|
||||
public $ignore_load_error=true;
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user