service1, * "/api?arg1=x" => service2, * 查询"/api?arg1=x&arg2=y" 找到service2 */ class HttpRouterEntries{ function __construct(){ $this->routes = new Tree(); } /** * * @param string $q * @param string& $matched_path * @return found object */ function find($q,&$matched_path=null){ list($path,$param) = explode('?', $q)+array( null,null ); $paths = self::stringToPath($path); $params = is_null($param)?null:explode('&', $param); return $this->findByArray($paths, $params, $matched_path); } /** * 查找url对应的实体 * @param string $paths * @param string $params * @param array $matched_path 已匹配的路径 * @return mixed|null */ function findByArray($paths,$params, &$matched_path =null){ $paths = array_filter($paths,function ($i){return $i!== '/' &&!empty($i);}); array_unshift($paths, '/'); $paths[] = Tree::$end; //最后总是插入Tree::$end表示路径结束 $route=null; $valid_path=null;//最深一个不为空的路径 $visited = 0; $geted=0; $walked_path=array(); //找到匹配的最深路径,/a/b/c匹配的路径可以是/,/a,/a/c,/a/b/c,但最深的是/a/b/c $this->routes->visit($paths,function ($node,$value)use(&$valid_path,&$route,&$visited,&$geted,&$walked_path){ if(!is_null($value)) { $route=$value; if(!($route instanceof \phprs\util\Tree)) { $valid_path=$value; } $geted = $visited; } $walked_path[]=$node; $visited ++; return true; }); if(is_null($route)){ return null; } if($matched_path !==null){ $matched_path = $walked_path; } //如果匹配完整的路径, 则还需要匹配querystring参数 if(count($paths) == $geted+1){ if($route instanceof \phprs\util\Tree){ //条件要求有querystring,所以请求也需要有querystring if(empty($params)){ return $valid_path; } $found = $this->findParams($route, $params); return is_null($found)?$valid_path:$found ; }else{ return $route; } }else{//如果不匹配全路径,则只能处理没有querystring的条件 if($route instanceof \phprs\util\Tree) { return $valid_path; } return $route; } return null; } /** * 查找querystring对应的实体 * querystring 参数匹配条件在tree中的存储方式如下 * * 1.c=xxx&f=xxx&g=xxx * 2.c=xxx&e=xxx&f-xxx * 3.b=xxx&c=xxx * (经过排序的数据结构) * c=xxx -> f=xxx -> g=xxx * \->e=xxx->f=xxx * b=xxx ->d=xxx * 当收到请求 b=xxx&d=xxx&a=xxx&c=xxx时 * 先拆成数组再排序=> a=xxx,b=xxx,c=xxx,d=xxx * 先找 a=xxx(根据当前的数据结构,只可能是有一个或者没有,不可能存在多个),没找到则跳过 * 找b=xxx,有,则在b=xxx节点下找c=xxx,没找到则跳过 * 在b=xxx节点下找d=xxx,找到,且 b=xxx ->d=xxx是完整的规则、请求的params也已处理完,则命中此规则 * @param Tree $root 根节点 * @param $params 需要查找的节点名 * @return mixed|null */ private function findParams($root,$params){ //排序,使参数与顺序无关 sort($params,SORT_STRING); $params = array_filter($params,function ($i){return !empty($i);}); $find_step = array(); $matching = null; foreach ($params as $param){ $find_step[]=$param; $node = $root->findNode($find_step); if($node === null) { array_pop($find_step);//换一个接着找 }else{ if(array_key_exists('value', $node)){ $matching = $node['value']; } } } return $matching; } /** * 增加一条规则 * @param $query string 请求的url形式 * @param $e mixed * @param $strict boolean 是否严格匹配 * @returnboolean 是否插入成功 */ public function insert($query,$e,$strict=false){ list($path, $param) = explode('?', $query)+array( null, null ); $path = str_replace('\\', '/', $path); $paths = explode('/', $path); $route=null; $params = null; if($param !==null && $param !== ""){ $params = explode('&', $param); } if($strict || !empty($params)){ $paths[]=Tree::$end; //对于严格匹配的路由规则(有querystring的路由,path部分也需要严格匹配),在路径末尾加入end,偏于匹配 } return $this->insertByArray($paths,$params,$e); } /** * 增加一条路由规则 * @param array $paths * @param array $params * @param mixed $e * @return boolean */ public function insertByArray($paths,$params,$e){ //生成树形的路由表,便于快速查找, // 如 规则 // "/b/a?z=1&y=2" => service1 // "/b/a?z=1&x=2" => service2 // 对应的 规则结构是 // path -> a+ // |-b+ // param -> |-x+ // |-z => service2 // |-y+ // |-z => service1 // // // $paths = array_filter($paths, function ($i){return !empty($i);}); array_unshift($paths, '/'); if(empty($paths)){ $paths = array(''); } if( $params !== null){ //排序,使参数与顺序无关 sort($params, SORT_STRING); $params = array_filter($params,function ($i){return !empty($i);}); $node = $this->routes->findNode($paths, true, true); if($node && $node['value'] instanceof \phprs\util\Tree){ $route = $node['value']; return $route->insert($params, $e,false); }else{ $route = new Tree(); $route->insert($params, $e,false); return $this->routes->insert($paths, $route,false); } }else{ return $this->routes->insert($paths, $e ,false); } } /** * 树打平输出成数组 * @return array */ public function export(){ $ori = $this->routes->export(); $res = array(); foreach ($ori as $v){ list($path, $value) = $v; if($value instanceof \phprs\util\Tree){ $querys = $value->export(); //提取querystring $path_str = self::pathToString($path); foreach ($querys as $query){ list($param, $value) = $query; $res[]=array($path_str.'?'.implode('&',$param), $value); } }else{ $res[]=array(self::pathToString($path), $value); } } return $res; } /** * 字符串路转径数组路 * @param string $path * @return array */ static public function stringToPath($path){ $path = str_replace('\\', '/', $path); $paths = explode('/', $path); $paths = array_filter($paths,function ($i){return !empty($i);}); return array_values($paths); } /** * 数组路径转字符串路径 * @param array $path * @return string 用/连接的字符串 */ static public function pathToString($path){ $append=function (&$str, $v){ //连续的/忽略 if(strlen($str) !==0 && substr_compare($str, '/', strlen($str)-1) ===0 && $v==='/'){ return; } //两个项目间加/ if(strlen($str) !==0 && substr_compare($str, '/', strlen($str)-1) !==0 && $v!=='/'){ $str =$str.'/'.$v; }else{ $str.=$v; } }; $str = ''; foreach ($path as $i){ $append($str, $i); } return $str; } private $routes; }