php請求分析與處理

在一個web應用中攻臀,都有著一個從請求到響應的過程焕数,那么在這之間,服務器端可以怎樣進行一些相應的處理呢刨啸,今天借鑒YII2的源碼來仔細分析下這其中的一些細節(jié)堡赔。

請求方法

*如果存在 X_HTTP_METHOD_OVERRIDE HTTP 頭時,以該 HTTP 頭所指定的方法作為請求方法设联, 如 X-HTTP-Method-Override: PUT 表示該請求所要執(zhí)行的是 PUT方法善已;
如果 X_HTTP_METHOD_OVERRIDE 不存在,則以 REQUEST_METHOD 的值作為當前請求的方法离例。 如果連 REQUEST_METHOD 也不存在换团,則視該請求是一個 GET 請求艘包。

代碼實現(xiàn)如下:

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中磷醋。

這里在特別介紹幾個函數(shù)邓线,可以用來判斷非HTTP協(xié)議所規(guī)定的請求類型:

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);
}
請求參數(shù)

在上面的代碼中器联,將所有的請求參數(shù)劃分為兩類拨拓, 一類是包含在 URL 中的渣磷,稱為查詢參數(shù)(Query Parameter)授瘦,或 GET 參數(shù)提完。 另一類是包含在請求體中的,需要根據(jù)請求體的內(nèi)容類型(Content Type)進行解析逐样,稱為 POST 參數(shù)官研。
我們來看一下YII2中是如何解析請求參數(shù)的:

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 頭部的全部原始內(nèi)容浇垦, 而不管是什么 Content Type(或稱為編碼方式)男韧。 相比較之下此虑, $_POST 只支持 application/x-www-form-urlencoded 和 multipart/form-data-encoded 兩種 ContentType口锭。其中前一種就是簡單的 HTML 表單以 method="post" 提交時的形式鹃操, 后一種主要是用于上傳文檔荆隘。因此,對于諸如 application/json 等 Content Type莫其,這往往是在 AJAX 場景下使用乱陡, 那么使用 $_POST 得到的是空的內(nèi)容憨颠,這時就必須使用php://input 积锅。
  • 相比較于 $HTTP_RAW_POST_DATA 适篙, php://input 無需額外地在 php.ini 中 激活 always-populate-raw-post-data 箫爷,而且對于內(nèi)存的壓力也比較小
  • 當編碼方式為 multipart/form-data-encoded 時, php://input 是無效的衩婚。這種情況一般為上傳文檔非春。 這種情況可以使用傳統(tǒng)的 $_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;
}

這個方法根據(jù)不同的PHP環(huán)境敬矩,采用有效的方法來獲取請求頭部弧岳。

  • getallheaders() 业踏,這個方法僅在將 PHP 作為 Apache 的一個模塊運行時有效勤家。
  • http_get_request_headers() ,要求 PHP 啟用 HTTP 擴展热幔。
  • $SERVER 數(shù)組的方法绎巨,需要遍歷整個數(shù)組蠕啄,并將所有以 HTTP* 元素加入到集合中去歼跟。 并且哈街,要將所有 HTTP_HEADER_NAME 轉(zhuǎn)換成 Header-Name 的形式。
框架的路由解析

*前面鋪墊了這么多撼港,干脆把YII2的路由解析也帖出來 帝牡,讓我們對此有一個更深層次的理解 *
YII2中可以定義相對應的路由規(guī)則靶溜,這里我們只介紹如何相應的邏輯

function init() {
    //路由配置
    $pattern = 'post/<action:\w+>/<id:\d+>';
    //路由規(guī)則
    $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];
}```
這個的功能其實就是根據(jù)參數(shù)生成標準的正則:#^post/(?P<action>\w+)(/(?P<id>\d+))?$#u
***為了方便个扰,源碼已經(jīng)被改成過程式代碼***
這里已經(jīng)到了路由解析递宅,所以干脆帶下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;

}

其實整體的流程還是很好理解的,我們要先定義好規(guī)則俐填,如
['post/<action>:\w+><id:\d+> => 'post/<action>'],
默認id為100
這樣在init中的三個參數(shù):
$pattern = 'post/<action:\w+>/<id:\d+>';
$route = 'post/<action>';
$defaults = ['id' => 100];
經(jīng)過init函數(shù)后英融,會得到
[$pattern, $_routeRule, $_routeParams, $defaults, $_paramRules, $_template]六個參數(shù)驶悟,這些都是后面需要用到的。
![Paste_Image.png](http://upload-images.jianshu.io/upload_images/3079704-c1acdc070b7ec275.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
其實構造url就是判斷傳入的參數(shù)是否合乎規(guī)則给涕,是否有默認值够庙,如果 有的話傳入的參數(shù) 值是不是等于默認值,如果 是的話要對其進行省略操作耘眨。

構造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 *中入口腳本之后纯趋、查詢參數(shù) *? *號之前的全部內(nèi)容纯命,即為 *PATH_INFO*),然后對其進行正則匹配亿汞,再查看 $routeParams,$_paramRules中有沒有相對的參數(shù) 疗我,構造出路由和參數(shù)
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末碍粥,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子黑毅,更是在濱河造成了極大的恐慌嚼摩,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件矿瘦,死亡現(xiàn)場離奇詭異枕面,居然都是意外死亡,警方通過查閱死者的電腦和手機缚去,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進店門潮秘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人易结,你說我怎么就攤上這事枕荞。” “怎么了搞动?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵箩溃,是天一觀的道長股冗。 經(jīng)常有香客問我穆律,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任买优,我火速辦了婚禮湘纵,結果婚禮上,老公的妹妹穿的比我還像新娘汇歹。我一直安慰自己硝皂,他們只是感情好,可當我...
    茶點故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布咪奖。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天饿悬,我揣著相機與錄音弟劲,去河邊找鬼庸追。 笑死咱娶,一個胖子當著我的面吹牛喻喳,可吹牛的內(nèi)容都是我干的蹦哼。 我是一名探鬼主播局劲,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼赘理,長吁一口氣:“原來是場噩夢啊……” “哼嫂沉!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤胆数,失蹤者是張志新(化名)和其女友劉穎必尼,沒想到半個月后骂租,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體互站,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了我擂。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,841評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡缓艳,死狀恐怖校摩,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情阶淘,我是刑警寧澤衙吩,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站溪窒,受9級特大地震影響坤塞,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜澈蚌,卻給世界環(huán)境...
    茶點故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一摹芙、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧宛瞄,春花似錦浮禾、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蝴簇。三九已至,卻和暖如春匆帚,著一層夾襖步出監(jiān)牢的瞬間熬词,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工吸重, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留互拾,地道東北人。 一個月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓晤锹,卻偏偏與公主長得像摩幔,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子鞭铆,可洞房花燭夜當晚...
    茶點故事閱讀 44,781評論 2 354

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理或衡,服務發(fā)現(xiàn),斷路器车遂,智...
    卡卡羅2017閱讀 134,656評論 18 139
  • 路由 路由(routing)就是通過互聯(lián)的網(wǎng)絡把信息從源地址傳輸?shù)侥康牡刂返幕顒臃舛稀B酚砂l(fā)生在OSI網(wǎng)絡參考模型中的...
    Dearmadman閱讀 2,856評論 2 9
  • AJAX 原生js操作ajax 1.創(chuàng)建XMLHttpRequest對象 var xhr = new XMLHtt...
    碧玉含香閱讀 3,201評論 0 7
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,116評論 25 707
  • pyplot 教程 原文:Pyplot tutorial 譯者:飛龍 協(xié)議:CC BY-NC-SA 4.0 mat...
    布客飛龍閱讀 39,893評論 5 58