php请求分析与处理

作者: hopevow | 来源:发表于2016-10-27 17:46 被阅读133次

在一个web应用中,都有着一个从请求到响应的过程,那么在这之间,服务器端可以怎样进行一些相应的处理呢,今天借鉴YII2的源码来仔细分析下这其中的一些细节。

请求方法

*如果存在 X_HTTP_METHOD_OVERRIDE HTTP 头时,以该 HTTP 头所指定的方法作为请求方法, 如 X-HTTP-Method-Override: PUT 表示该请求所要执行的是 PUT方法;
如果 X_HTTP_METHOD_OVERRIDE 不存在,则以 REQUEST_METHOD 的值作为当前请求的方法。 如果连 REQUEST_METHOD 也不存在,则视该请求是一个 GET 请求。

代码实现如下:

if (isset($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'])) {
    $requestmethod =  strtoupper($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']);
} else {
    $requestmethod = isset($_SERVER['REQUEST_METHOD']) ? strtoupper($_SERVER['REQUEST_METHOD']) : 'GET';
}

这样,在请求方法成功的收集到了变量requestmethod中。

这里在特别介绍几个函数,可以用来判断非HTTP协议所规定的请求类型:

function isAjax() {
  return (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest');
}

function isPjax() {
  return isAjax && !empty($_SERVER['HTTP_X_PJAX']);
}

function getFlash() {
  return isset($_SERVER['HTTP_USER_AGENT']) && (stripos($_SERVER['HTTP_USER_AGENT'], 'Shockwave') ! == false || stripos($_SERVER['HTTP_USER_AGENT'] !== false);
}
请求参数

在上面的代码中,将所有的请求参数划分为两类, 一类是包含在 URL 中的,称为查询参数(Query Parameter),或 GET 参数。 另一类是包含在请求体中的,需要根据请求体的内容类型(Content Type)进行解析,称为 POST 参数。
我们来看一下YII2中是如何解析请求参数的:

function get($name = null, $defaultValue = null) {
    if ($name = null) {
        return getQueryParams();
    } else {
        return getQueryParams($name, $defaultValue);
    }
}

function getQueryParams() {
    return _GET;
}

function getQueryParam($name, $defaultValue = null) {
    $params = getQueryParams();
    return isset($params[$name]) ? $params[$name] : $defaultValue;
}



function post($name = null, $defaultValue = null) {
    if ($name == null) {
        return getBodyParams();
    } else {
        return getBodyParam($name, $defaultValue);
    }
}

function getBodyParam($name, $defaultValue = null) {
    $params = getBodyParams();
    return isset($params[$name]) ? $params[$name] : $defaultValue;
}

function getBodyParams() {
    $parsers = [
        "*" => 'Parseall',
    ];
    var_dump($_SERVER);
    $contentType = getContentType();
    if (($pos = strpos($contentType, ';')) !== false) {
        $contentType = substr($contentType, 0, $pos);
    }

    if (isset($parsers[$contentType])) {
        $parser = new $parsers[$contentType]();
        if (!($parser instanceof RequestParser)) {
            throw new Exception('wrong');
        }

        $_bodyParams = $parser->parse(getRawBody(), $contentType);
    } elseif(isset($parsers['*'])) {
    
        $parser = new $parsers['*']();
        if (!($parser instanceof RequestParser)) {
            throw new Exception('wrong');
        }

        $_bodyParams = $parser->parse(getRawBody(), $contentType);
    } elseif ($requestmethod === 'POST') {
        $_bodyParams = $_POST;
        
    } else {
        $_bodyParams = [];
        mb_parse_str(getRawBody(), $_bodyParams);
    }
    return $_bodyParams;
}

function getContentType() {
    if (isset($_SERVER['CONTENT_TYPE'])) {
        return $_SERVER['CONTENT_TYPE'];
    } elseif (isset($_SERVER['HTTP_CONTENT_TYPE'])) {
        return $_SERVER['HTTP_CONTENT_TYPE'];
    }
    return null;
}


function getRawBody() {
    return file_get_contents('php://input');
}

class RequestParser {

}

class Parseall extends RequestParser {
    function parse($rawBody, $contentType = '') {
        return $rawBody;
    }
}

这是简化后的逻辑代码,这里而用到了只读流php://input:

  • php://input 是个只读流,用于获取请求体
  • php://input 是返回整个 HTTP 请求中,除去 HTTP 头部的全部原始内容, 而不管是什么 Content Type(或称为编码方式)。 相比较之下, $_POST 只支持 application/x-www-form-urlencoded 和 multipart/form-data-encoded 两种 ContentType。其中前一种就是简单的 HTML 表单以 method="post" 提交时的形式, 后一种主要是用于上传文档。因此,对于诸如 application/json 等 Content Type,这往往是在 AJAX 场景下使用, 那么使用 $_POST 得到的是空的内容,这时就必须使用php://input
  • 相比较于 $HTTP_RAW_POST_DATA , php://input 无需额外地在 php.ini 中 激活 always-populate-raw-post-data ,而且对于内存的压力也比较小
  • 当编码方式为 multipart/form-data-encoded 时, php://input 是无效的。这种情况一般为上传文档。 这种情况可以使用传统的 $_FILES 。
请求头

*同样的我们以YII2的获取请求头信息的方法来进行分析 *

function getHeaders() {
    if (function_exists('getallheaders')) {
        $headers = getallheaders();
    } elseif (function_exists('http_get_request_headers')) {
        $headers = http_get_request_headers();
    } else {
        foreach($_SERVER as $name => $value) {
            if (strncmp($name, 'HTTP_', 5) === 0) {
                $name = str_replace(' ', '-',ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))));
                $headers[$name] = $value;
            }
        }
    }
    return $headers;
}

这个方法根据不同的PHP环境,采用有效的方法来获取请求头部。

  • getallheaders() ,这个方法仅在将 PHP 作为 Apache 的一个模块运行时有效。
  • http_get_request_headers() ,要求 PHP 启用 HTTP 扩展。
  • $SERVER 数组的方法,需要遍历整个数组,并将所有以 HTTP* 元素加入到集合中去。 并且,要将所有 HTTP_HEADER_NAME 转换成 Header-Name 的形式。
框架的路由解析

*前面铺垫了这么多,干脆把YII2的路由解析也帖出来 ,让我们对此有一个更深层次的理解 *
YII2中可以定义相对应的路由规则,这里我们只介绍如何相应的逻辑

function init() {
    //路由配置
    $pattern = 'post/<action:\w+>/<id:\d+>';
    //路由规则
    $route = 'post/<action>';
    //默认配置
    $defaults = ['id' => 100];

    $pattern = trim($pattern, '/');
    $pattern = '/' . $pattern . '/';

    $route = trim($route, '/');
    if (strpos($route, '<') !== false && preg_match_all('/<(\w+)>/', $route, $matches)) {
        foreach($matches[1] as $name) {
            $_routeParams[$name] = "<$name>";
        }
    }


    $tr = [
        '.' => '\\.',
        '*' => '\\*',
        '$' => '\\$',
        '[' => '\\[',
        ']' => '\\]',
        '(' => '\\(',
        '}' => '\\(',
    ];
    $tr2 = [];

    if (preg_match_all('/<(\w+):?([^>]+)?>/', $pattern, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER)) {
        foreach($matches as $match) {
            $name = $match[1][0];
            $patt = isset($match[2][0]) ? $match[2][0] : '[^\/]+';
            if (array_key_exists($name, $defaults)) {
                $length = strlen($match[0][0]);
                $offset = $match[0][1];
        
                if ($offset > 1 && $pattern[$offset - 1] === '/' && $pattern[$offset + $length] === '/') {
                    
                    $tr["/<$name>"] = "(/(?P<$name>$patt))?";
                } else {
                    $tr["<$name>"] = "(?P<$name>$patt)?";
                }
            } else {
                $tr["<$name>"] = "(?P<$name>$patt)";
            }

            if (isset($_routeParams[$name])) {
                $tr2["<$name>"] = "(?P<$name>$patt)";
            } else {
                $_paramRules[$name] = $patt === '[^\/]+' ? '' : "#^$patt$#u";
            }
        }
    }
    
    $_template = preg_replace('/<(\w+):?([^>]+)?>/', '<$1>', $pattern);
    
    $pattern = '#^' . trim(strtr($_template, $tr), '/') . '$#u';
    if (!empty($_routeParams)) {
        $_routeRule = '#^' . strtr($route, $tr2) . '$#u';
    }
    return [$pattern, $_routeRule, $_routeParams, $defaults, $_paramRules, $_template];
}```
这个的功能其实就是根据参数生成标准的正则:#^post/(?P<action>\w+)(/(?P<id>\d+))?$#u
***为了方便,源码已经被改成过程式代码***
这里已经到了路由解析,所以干脆带下YII2的路由构建思想 :

function createUrl($manager, $route, $params) {
$_route = 'post/<action>';

list($pattern, $_routeRule, $_routeParams, $defaults, $_paramRules, $_template) = init();

$tr = [];

if ($route !== $_route) {
    if ($_routeRule !== null && preg_match($_routeRule, $route, $matches)) {
        foreach($_routeParams as $name => $token) {
            if (isset($defaults[$name]) && strcmp($defaults[$name], $matches[$name]) === 0) {
                $tr[$token] = '';
            } else {
                $tr[$token] = $matches[$name];
            }
        }
    } else {
        return false;
    }
}


foreach($defaults as $name => $value) {
    
    if (isset($_routeParams[$name])) {
        continue;
    }

    if(!isset($params[$name])) {
        return false;
    } elseif(strcmp($params[$name], $value) === 0) {
        unset($params[$name]);
        if (isset($_paramRules[$name])) {
            $tr["<$name>"] = '';
        }
    } elseif (!isset($_paramRules[$name])) {
        return false;
    }
}
var_dump($_paramRules);
foreach( $_paramRules as $name => $rule) {
    if(isset($params[$name]) && !is_array($params[$name]) && ($rule === '' || preg_match($rule, $params[$name]))) {
        $tr["<$name>"] = $params[$name];
        unset($params[$name]);
    } elseif (!isset($defaults[$name]) ||isset($params[$name])) {
        return false;
    }
}
$url = trim(strtr($_template, $tr), '/');
echo $url;

}

其实整体的流程还是很好理解的,我们要先定义好规则,如
['post/<action>:\w+><id:\d+> => 'post/<action>'],
默认id为100
这样在init中的三个参数:
$pattern = 'post/<action:\w+>/<id:\d+>';
$route = 'post/<action>';
$defaults = ['id' => 100];
经过init函数后,会得到
[$pattern, $_routeRule, $_routeParams, $defaults, $_paramRules, $_template]六个参数,这些都是后面需要用到的。
![Paste_Image.png](http:https://img.haomeiwen.com/i3079704/c1acdc070b7ec275.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
其实构造url就是判断传入的参数是否合乎规则,是否有默认值,如果 有的话传入的参数 值是不是等于默认值,如果 是的话要对其进行省略操作。

构造url的反向当然就是解析URL了,这里我们也来把其改造成我们方便测试的方法

function parseUrl($manager, $request) {
$route = 'post/<action>';
extract(init());
$suffix = '.html';
$pathinfo = getpath();
$suffix = (string)($suffix === null ? '' : $suffix);

if ($suffix !== '' && $pathinfo !== '') {
    $n = strlen($suffix);
    if(substr_compare($pathinfo, $suffix, -$n , $n) === 0) {
        $pathinfo = substr($pathinfo, 0, -$n);
        if ($pathinfo === '') {
            return false;
        }
    } else {
        return false;
    }
}

if (!preg_match($pattern, $pathinfo, $matches)) {
    return false;
}

foreach($defaults as $name => $value) {
    if (!isset($matches) || $matches[$name] === '') {
        $matches[$name] = $value;
    }
}

$params = $defaults;
$tr = [];

foreach($matches as $name => $value) {
    if (isset($_routeParams[$name])) {
        $tr[$_routeParams[$name]] = $value;
        unset($params[$name]);
    } elseif (isset($_paramRules[$name])) {
        $params[$name] = $value;
    }
}
if ($_routeRule !== null) {
    $_route = strtr($route, $tr);
} else {
    $_route = $route;
}

return [$_route, $params];

}

在parseurl中,先获取pathinfo(*URL *中入口脚本之后、查询参数 *? *号之前的全部内容,即为 *PATH_INFO*),然后对其进行正则匹配,再查看 $routeParams,$_paramRules中有没有相对的参数 ,构造出路由和参数

相关文章

  • php请求分析与处理

    在一个web应用中,都有着一个从请求到响应的过程,那么在这之间,服务器端可以怎样进行一些相应的处理呢,今天借鉴YI...

  • 记一次使用Zookeeper C API导致的内存泄漏

    现象 线上 nginx + php-fpm来实时处理请求, php处理请求时需加载我们写的扩展; 发现每次请求处理...

  • 理解PHP与Node的IO处理机制

    以下分析了php和node如何处理一个以及多个Http请求以及代码中的IO操作处理机制。 PHP的实现:基本流程:...

  • php-fpm是干嘛的

    nginx本身不能处理PHP,它只是个web服务器,当接收到请求后,如果是php请求,则发给php解释器处理,并把...

  • LNMP的搭建

    LNMP就是 Nginx服务本身不能处理PHP的请求,那么当用户发起PHP动态请求, Nginx又是如何进行处理的...

  • php请求处理

    GET/POST请求 _GET 可用于收集提交 HTML 表单 (method="get") 之后的表单数据。 $...

  • 【再读lara】生命周期 ~ 请求实例化

    public\index.php Illuminate\Http\Request.php 处理请求 Illumin...

  • PHP生命周期

    PHP的执行分三个阶段:处理请求的开始阶段、处理请求阶段、请求之后的结束阶段。 处理请求的开始阶段: 1 模块初始...

  • PHP-FPM

    1.什么是PHP-FPM nginx本身不能处理PHP,它只是个web服务器,当接收到请求后,如果是php请求,则...

  • centos7 安装php5.6.30 nginx

    关于php-fpm nginx本身不能处理PHP,它只是个web服务器,当接收到请求后,如果是php请求,则发给p...

网友评论

    本文标题:php请求分析与处理

    本文链接:https://www.haomeiwen.com/subject/efgjuttx.html