1. 程式人生 > >【Yii系列】處理請求

【Yii系列】處理請求

入口 實現 官方 cookie this sender att 只需要 ota

緣起

這一章是Yii系列的第三章,前兩章給大夥講解了Yii2.0的安裝與Yii2.0的基本框架及基礎概念,傳送門:

【Yii2.0的安裝與調試】:http://www.cnblogs.com/riverdubu/p/6439680.html

【Yii2.0基礎框架】:http://www.cnblogs.com/riverdubu/p/6607373.html

相信學習過上兩章的內容,你們是不是對Yii有個大致的了解了呢,Yii2.0基礎框架這一章很重要,不僅僅是因為它的長度,而是裏面講解了整個Yii2.0的基礎概念,以及如何基於MVC模型構建的它的整套框架,只是浮於表面的一些基礎只是,如果大家對更深層次的Yii的底層感興趣,可以持續關註我的博客,後面我會對於Yii底層的一些關鍵組件和代碼做更加詳細的講解。

接上一章的話,我們這章帶大夥了解下一個用戶的請求從發起到收到響應的整個過程是如何實現,這對於統籌全局至關重要,如果說上一章是基礎,是打根基和搭架子的一章,那麽這一章就是給你將整個房子建起來啦,會把所有的門窗房間規劃好,讓你能夠輕松自如的在房子裏面穿梭。

運行機制

首先,我們來看下整個請求的運行機制

技術分享

用戶發送請求給入口腳本,入口腳本加載配置,運行application,application會創建一個request組件去處理這次用戶請求,Request組件會去路由裏面查找用戶想要請求的那個Controller,找到Controller後實例化它,調用對應的action執行操作,action會調用對應model層的函數進行數據處理,處理完成之後返回給對應的action,action會將數據格式化或者不格式化渲染View,為其提供填充所需要的數據,渲染完成的結果會返回給response組件發送給用戶瀏覽器。

這裏面有幾個關鍵的組件,request和response我們先不用去管他們,這是application會自動搞定的,我們要關心關心這個被稱為路由的東西。

路由

有幾個關鍵概念是需要大家理解下的,引導路由。

當入口腳本在調用 yii\web\Application::run() 方法時,它進行的第一個操作就是解析輸入的請求,然後實例化對應的Controller處理這個請求。 該過程就被稱為引導路由(routing)

我們之前可能看過類似於下面的請求URL

http://服務器IP/index.php?r=post/view&id=100

這邊的r後面的就是後面會被實例化的Controller去處理這次請求,這是標準的寫法,但是,為了讓URL看上去更pretty一些,比方說下面的URL

http://服務器IP/post/view?id=100

這樣是不是能夠更清晰的看出是哪個業務單元的邏輯,這樣的寫法需要通過配置應用主體Components中一個叫urlManager的配置項來完成路由的解析。

這兩種寫法之間通過配置urlManager裏面的enablePrettyUrl屬性來實現切換,true的話是下面一種寫法,false的話是上面一種寫法。

引導路由包含兩步,第一步,請求會被解析成對應的路由和請求參數,第二步,一個路由對應的Controller的action會被實例化去處理這個請求。

當你使用pretty方式去解析你的URL時,urlManager會在當前的規則中尋找你想要找的規則,如果找不到,會拋出一個 yii\web\NotFoundHttpException,這就是大名鼎鼎的404啦。

一旦找到對應的路由規則,URL會被拆分成很多部分,就像上面的post/view一樣,從前往後可能分別對應了一個module,一個controller,一個action。application回一個個去嘗試的,如果沒有找到對應的action去執行這個請求,還是會像上面一樣,拋出大名鼎鼎的404。

我們這邊對基礎的路由不做解析,想要了解的朋友可以去官網詳細的查看下。我們來具體的來看下pretty的路由規則。

為了能夠使用pretty路由解析,我們得在應用主體,也就是上一章的web.php中配置一下。

技術分享
[
    ‘components‘ => [
        ‘urlManager‘ => [
            ‘enablePrettyUrl‘ => true,
            ‘showScriptName‘ => false,
            ‘enableStrictParsing‘ => false,
            ‘rules‘ => [
                // ...
            ],
        ],
    ],
]
技術分享

具體的規則就要配置在rules,這裏建議一下,這個rules配置在一個文件中,然後這邊require一下即可。

這裏面有幾個參數

enablePrettyUrl:和上面的意思一樣,打開pretty url規則的,這個必須得打開,要不然的話默認的路由解析會是基礎路由解析。

showScriptName:是否將入口腳本寫入用戶請求的鏈接,如果為true,請求鏈接為:/index.php/post/100,如果為false,請求鏈接為:/post/100

enableStrictParsing:是否需要按照rules裏的規則嚴格解析,如果為true,請求的鏈接必須符合rules裏面的規則,如果不滿足,拋出404,如果為false,請求鏈接可以是rules規則的一部分。

url rules

下面我們就來詳細的講一講需要配置的這個rules文件。

我們先來看個例子

[
    ‘posts‘ => ‘post/index‘, 
    ‘post/<id:\d+>‘ => ‘post/view‘,
]

這邊定義了兩條URL rules

第一條將URL中的posts對應到post/index這條路由;

第二條規則匹配一個正則式post/(\d+)對應到post/view這條路由,id是它的參數。

當URL被規則解析,他後面的參數會被收錄進application的request組件,後面我們會說到這個request組件。在我看來,處女座的人比較滿意的一個url是這樣的:http://example.com/psot/action?a=1&b=2,這是比較常規的

我這邊給出的一個例子適用於大多數情況

‘v1/<module:\w+>/<controller:\w+>/<action:\w+>‘ => ‘api/v1/<module>/<controller>/<action>‘, //模塊相關規則

前面部分是用戶訪問時的鏈接格式,後面部分是項目代碼中對應的action位置。

通過正則式去解析路由,這回大大減少規則數量,大大提升urlManager的性能。

Request

講完了路由,我們再來看下一個請求是如何被應用主體接受和反饋的吧。

首先,我們來看下在應用中,用戶的請求是被實例化成啥的。

一個應用的請求是用 yii\web\Request 對象來表示的,該對象提供了諸如 請求參數(譯者註:通常是GET參數或者POST參數)、HTTP頭、cookies等信息。

調用方式

Yii::$app->request

$request->get()這段代碼等價於$_GET,之前和大夥說過,不能直接使用$_GET這個值,這使你更容易編寫測試用例,因為你可以偽造數據來創建一個模擬請求組件。

你可以通過 Yii::$app->request->method 表達式來獲取當前請求使用的HTTP方法。

技術分享
$request = Yii::$app->request;

if ($request->isAjax) { /* 該請求是一個 AJAX 請求 */ }
if ($request->isGet)  { /* 請求方法是 GET */ }
if ($request->isPost) { /* 請求方法是 POST */ }
if ($request->isPut)  { /* 請求方法是 PUT */ }
技術分享

我這邊一般的是使用一個Web基類去獲取請求的body和head

$requestBody = $this->getParam(‘body‘);
$requestHeader = $this->getParam(‘header‘);

Web基類

技術分享
<?php
namespace ext\controller;

use Yii;
use yii\web\Controller;

class Web extends Controller
{
    public function init()
    {
        parent::init();
        $this->setDefaultCrumbs();
    }

    public function beforeaction($action)
    {
        return parent::beforeaction($action);
    }

    ......

    /**
     * 獲取參數
     * @param null $key
     * @param null $val
     * @return array|mixed|null
     */
    public function getParam($key = null, $val = null)
    {

        $request = Yii::$app->request;

        $data = [];
        /**
         * 如果是GET請求
         */
        if ($request->getIsGet()) {
            $data = $request->get(‘request‘);
        } elseif ($request->getIsPost()) {
            if($request->contentType == ‘application/json‘)
            {
                $rawBody = $request->rawBody;
                if($rawBody){
                    $requestData = json_decode($rawBody,true);
                    $data = $requestData[‘request‘];
                    unset($requestData);
                }
            }else{
                $data = $request->post(‘request‘);
            }
        } elseif ($request->getIsDelete()) {
            $data = $request->get(‘request‘);
        }

        if (isset($data[$key])) {
            if (is_null($data[$key])) {
                return $val;
            } else {
                return $data[$key];
            }
        } elseif (is_null($key)) {
            return $data;
        } else {
            return $val;
        }
    }
}
技術分享

詳細的代碼待我完成Yii系列最佳時間貢獻到githun供大夥下載研究呢。

Response

看完了請求的引導,我們來看下我們如何反饋給用戶吧。

Yii是通過response這個組件來反饋給用戶我們想要給到他的信息的。

Response對象包含的信息有HTTP狀態碼,HTTP頭和主體內容等, 網頁應用開發的最終目的本質上就是根據不同的請求構建這些響應對象。

響應中首當其沖的應該就是狀態碼了,也就是我們之前提到的404啊,503啊,301啊,這些都是服務器的狀態碼。

Yii提供了一些常量去定義這些狀態碼

技術分享
yii\web\BadRequestHttpException:狀態碼 400。
yii\web\ConflictHttpException:狀態碼 409。
yii\web\ForbiddenHttpException:狀態碼 403。
yii\web\GoneHttpException:狀態碼 410。
yii\web\MethodNotAllowedHttpException:狀態碼 405。
yii\web\NotAcceptableHttpException:狀態碼 406。
yii\web\NotFoundHttpException:狀態碼 404。
yii\web\ServerErrorHttpException:狀態碼 500。
yii\web\TooManyRequestsHttpException:狀態碼 429。
yii\web\UnauthorizedHttpException:狀態碼 401。
yii\web\UnsupportedMediaTypeHttpException:狀態碼 415。
技術分享

yii\web\Response::statusCode 狀態碼默認為200, 如果需要指定請求失敗,可拋出對應的上訴的HTTP異常。

如果想拋出的異常不在如上列表中,可創建一個yii\web\HttpException異常, 帶上狀態碼拋出,如下:

throw new \yii\web\HttpException(402);

可在 response 組件中操控yii\web\Response::headers來發送HTTP頭部信息, 例如:

技術分享
$headers = Yii::$app->response->headers;

// 增加一個 Pragma 頭,已存在的Pragma 頭不會被覆蓋。
$headers->add(‘Pragma‘, ‘no-cache‘);

// 設置一個Pragma 頭. 任何已存在的Pragma 頭都會被丟棄
$headers->set(‘Pragma‘, ‘no-cache‘);

// 刪除Pragma 頭並返回刪除的Pragma 頭的值到數組
$values = $headers->remove(‘Pragma‘);
技術分享

響應主體

大多是響應應有一個主體存放你想要顯示給終端用戶的內容。

如果已有格式化好的主體字符串,可賦值到響應的yii\web\Response::$content屬性, 例如:

$response = Yii::$app->response;
$response->format = \yii\web\Response::FORMAT_JSON;
$response->data = [‘message‘ => ‘hello world‘];

Yii可以使用如下的幾種格式

HTML: 通過 yii\web\HtmlResponseFormatter 來實現.
XML: 通過 yii\web\XmlResponseFormatter來實現.
JSON: 通過 yii\web\JsonResponseFormatter來實現.
JSONP: 通過 yii\web\JsonResponseFormatter來實現.
RAW: use this format if you want to send the response directly without applying any formatting.

一般的話,我們不會這麽麻煩的去操作,而是直接和前端商量好,我們會以哪種形式返回給你,然後直接在action裏面直接返回數據給用戶。

Web基類中對應的方法:

技術分享
public function sendSuccess($message = null, $data = [], $code = "0")
    {
        return $this->formatJson([
                ‘response‘ => [
                    ‘header‘ => [
                        ‘code‘ => $code,
                        ‘msg‘ => $message
                    ],
                    ‘body‘ => $data ?: []
                ]
            ]
        );
    }

    public function sendError($message = null, $code = "1")
    {
        return $this->formatJson([
                ‘response‘ => [
                    ‘header‘ => [
                        ‘code‘ => (string)$code,
                        ‘msg‘ => $message ? $message : ‘未知錯誤‘
                    ],
                ]
            ]
        );
    }
技術分享

session

除了Request和Response這兩個基本概念,還有個概念比較重要,如果你想讓你用戶的數據在你後端構成一個map,你可以使用session這個組件,相信我,這個組件絕對能夠滿足大部分用戶登錄系統,用他來保存用戶的數據非常方便,每次用戶來請求,只需要將sessionId傳給你,你就可以獲取到這個用戶的狀態,雖然方便,但不能完全保證安全,如果sessionId被截取了,這也是相當蛋疼的事。這就要看系統的健壯性了,用戶的敏感信息是否會在前端有權限拿到,如果要拿敏感信息,你的系統又當如何。

這裏不多說,會在安全那一章和大夥好好嘮嘮後臺應用中提高安全系數的一些做法呢。

下面直接上session相關的代碼,session的操作也很簡單,key-value模型。

技術分享
$session = Yii::$app->session;

// 檢查session是否開啟 
if ($session->isActive) ...

// 開啟session
$session->open();

// 關閉session
$session->close();

// 銷毀session中所有已註冊的數據
$session->destroy();
技術分享

比較重要的一個方法是設置session存在的時間:

$session->setTimeout(...)

session數據的存儲和提取

技術分享
$session = Yii::$app->session;

// 獲取session中的變量值,以下用法是相同的:
$language = $session->get(‘language‘);
$language = $session[‘language‘];
$language = isset($_SESSION[‘language‘]) ? $_SESSION[‘language‘] : null;

// 設置一個session變量,以下用法是相同的:
$session->set(‘language‘, ‘en-US‘);
$session[‘language‘] = ‘en-US‘;
$_SESSION[‘language‘] = ‘en-US‘;

// 刪除一個session變量,以下用法是相同的:
$session->remove(‘language‘);
unset($session[‘language‘]);
unset($_SESSION[‘language‘]);

// 檢查session變量是否已存在,以下用法是相同的:
if ($session->has(‘language‘)) ...
if (isset($session[‘language‘])) ...
if (isset($_SESSION[‘language‘])) ...

// 遍歷所有session變量,以下用法是相同的:
foreach ($session as $name => $value) ...
foreach ($_SESSION as $name => $value) ...
技術分享

Flash數據是一種特別的session數據,它一旦在某個請求中設置後, 只會在下次請求中有效,然後該數據就會自動被刪除。喜歡的朋友可以去官方文當中詳查。

關於cookie,後端使用的較少,前端用的時候經常用它來存儲一些數據。這裏不多說,感興趣的朋友可以去官方文當中詳查。

至此,所有的關於用戶的請求Yii是如何處理的就講完啦,包括從URL解析,Request和Response組件,Session組件這一整套內容,其實,官網將錯誤處理,日誌系統歸類到這章,我感覺這兩章對於開發者來講還是比較重要的,所以,我下面會分兩個小章來詳細講講這兩個系統。^_^

【Yii系列】處理請求