下面是對restful從建立到速率控制的一個詳細流程介紹,裏面的步驟以及截圖儘量詳細,熟悉restful的盆友可能以爲過於繁瑣,新手不妨耐心仔細看一下。php
一.Api的建立html
1.複製一個frontend或者backend,將其重命名爲api放在同級目錄下git
2.而後刪除controllers和views文件夾,而後將api文件中的frontend替換爲api(好比命名空間,相關配置等),這點很是重要!!!web
3.打開cmd命令行,cd進入項目所在根目錄C:\wamp\www\advanced下,init 根據提示進行初始化設置,以後修改common\config\main-local.php文件中的數據庫相關配置。這個比較簡單。sql
4.須要修改common\config\bootstrap.php文件,對新建的應用增長alias別名,以下: 數據庫
Yii::setAlias('@api', dirname(dirname(__DIR__)) . '/api');apache
5.爲新建的api應用程序美化路由,爲了後續方便訪問,進行域名配置json
1)首先保證你的web服務器開啓rewrite規則,細節咱們就不說了,不過這是前提。bootstrap
2)給api目錄配置一個訪問地址:www.dsgn.com指向 api/web,不會的盆友請參考個人另外一篇博客,關於Apache的域名配置api
3)接着,在應用入口同級增長.htaccess文件就好,咱們以apache爲例
.htaccess文件內容:
Options +FollowSymLinks
IndexIgnore */*
RewriteEngine on
# if a directory or a file exists, use it directly
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
# otherwise forward it to index.php
RewriteRule . index.php
RewriteRule \.svn\/ /404.html
RewriteRule \.git\/ /404.html
6.用了便於演示說明,咱們新建一張數據表good表,字段本身隨意設置,並向其中插入幾條數據。
7.經過gii生成modules
注意:因爲剛纔配置的域名是訪問到api/web,如今gii生成器的地址我用localhost去訪問fronted/web/index.php
地址:http://localhost/advanced/frontend/web/index.php?r=gii
而後依次生成模塊 控制器 模型
8.同時在 api/config/main.php 中作以下配置,這一步也很重要
1)添加module的配置
'modules' => [
'v1' => [
'class' => 'api\modules\v1\Module',
],
],
2)修改controllerNamespace
'controllerNamespace' => 'api\controllers',
3)開啓urlManager,以下配置
'urlManager' => [
'enablePrettyUrl' => true,
'enableStrictParsing' => true,
'showScriptName' => false,
//爲Goods配置Url規則
'rules' => [
['class' => 'yii\rest\UrlRule', 'controller' => ['v1/good']],
],
],
4)刪除
'errorHandler' => [
'errorAction' => 'site/error',
],
9. 從新配置控制器。爲了實現restful風格的api,在yii2中,咱們須要對 api\modules\v1\controllers下的GoodController控制器進行一下改寫
<?php
namespace api\modules\v1\controllers;
use yii\rest\ActiveController;
class GoodController extends ActiveController
{
public $modelClass = 'api\models\Good';
}
說明:翻閱資料的時候,看到有人提出,在這個控制器裏面自定義方法,可是訪問的時候報404
緣由以下:RESTful是以資源爲中心而不是頁面,因此YII2給每一個資源只給了有限的幾個action,並且是以Action類的形式寫的,
它默認有create, delete, update, index, view等的一下方法
所建立的 API 包括:
GET /users: 逐頁列出全部用戶
POST /users: 建立一個新用戶
GET /users/123: 返回用戶 123 的詳細信息
PATCH /users/123 and PUT /users/123: 更新用戶123
DELETE /users/123: 刪除用戶123
1)框架默認的action都寫在yii\rest包下(XXXAction.php就表示相應的動詞操做,源代碼不多,你能夠直接看看),
2)若是這個資源的全部動詞都是默認的話是則它的controller裏除了modelClass是不用寫任何代碼的.
3)若是想要自定義動詞必須繼承rest包下對應的Action類,而後在controller重寫actions指向你的新action,從而實現重寫
打開文件api\modules\v1\controllers\UserController,參考代碼以下:
若是咱們想在本身定義不一樣的api接口的話?那麼咱們能夠經過配置實現,在main.php的主文件中:
'urlManager' => [
'enablePrettyUrl' => true,
'showScriptName' => false,
'rules' => [
[
'class' => 'yii\rest\UrlRule',
'controller' => ['v1/users'],
'pluralize' => false,
'extraPatterns' => [
'GET versions' => 'version',
'GET search/<id:\d+>' => 'search',
'POST newusers' => 'add'
],
],
],
],
"extraPatterns"這個屬性是額外模式配置
a)'GET versions' => 'version',表明獲取接口版本,例如/v1/users/versions ,對應的內聯操做actionVersion();
b)'GET searches/<id:\d+>' => 'search', 表明搜索一個指定id的用戶,例如/v1/users/searches/1,對應的內聯操做actionSearches();
c)'POST newusers' => 'add',表明添加一個用戶,例如/v1/users/newusers,對應的內聯操做actionAdd();
咱們只須要在控制器中實現這些方法,完成相應的邏輯業務,那麼各個須要的接口就完成了。
9.模擬請求操做
到這一步就能夠用postman來測試,不會安裝或者調試的盆友請網上搜索一下,固然也能夠用其餘測試工具來調試。
從上面截圖中能夠清楚的看到,GET 方式請求http://www.dsgn.com/v1/goods, 已經可以很方便的獲取咱們表中的數據了。
補充說明:1)Yii 將在末端使用的控制器的名稱自動變爲複數。這個要重點注意!
2)這個地方使用之因此使用goods這個複數,是由於在RESTful架構中,每一個網址表明一種資源(resource),因此網址中不能有動詞,只能有名詞,並且所用的名詞每每與數據庫的表格名對應。
通常來講,數據庫中的表都是同種記錄的"集合"(collection),因此API中的名詞也應該使用複數。
3)固然,用yii2的restful寫api,暫時能夠不用框架推薦的yii\rest\UrlRule,就能夠寫出通常路由的api了,主要由於yii\rest\UrlRule的路由簡略了不少,不利於直接識別api表明的意思。
例子:
使用yii\rest\UrlRule,訪問連接爲/profiles/3
使用通常URL規則'<controller:\w+>/<action:\w+>' => '<controller>/<action>', 訪問連接爲/profile/view?id=3
測試結果都一致。
httpx://api.yii.com/profile/view?id=1&expand=user
yii的restful要得到關聯表數據必需要帶一個expand參數代表須要使用的關聯數據
4)若是是用restful自帶的路由規則,要解決訪問存在的複數問題的話,須要添加'pluralize' => false這個條件,具體以下:
'rules' => [
['class' => 'yii\rest\UrlRule',
'controller' => ['v1/good'],
'pluralize' => false
],
],
到此爲止,用http://www.dsgn.com/v1/good就能成功測試訪問了。
可是,有個問題出現了,用postman測試的數據,能夠看到是json格式的,那是由於postman測試工具能夠自動轉化成json美化格式,
實際中咱們用瀏覽器訪問該url看到以下,(這跟是否用複數訪問不要緊)
解決方法是,在GoodController控制器下重寫behaviors函數,具體作法是加這樣一段代碼:
public function behaviors()
{
$behaviors=parent::behaviors();
$behaviors['contentNegotiator']['formats'] = [
'application/json'=>Response::FORMAT_JSON
];
return $behaviors;
}
說明:Yii2 RESTful支持JSON和XML格式,若是想指定返回數據的格式,須要配置yii\filters\ContentNegotiator::formats屬性
如今從新去訪問http://www.dsgn.com/v1/good就能夠看到以json格式解析出來了。O(∩_∩)O~~~
補充說明:
API的目錄結構有2種方式:
以前的建立測試都是在方式1的狀況下進行的,其實這兩種方式的差別並不大,如今對方式2進行補充說明:
打開api\config\main.php中,有以下配置:
2、認證受權
1.須要設置認證的緣由:和Web應用不一樣,RESTful APIs 一般是無狀態的,也就意味着不該使用sessions 或 cookies,所以每一個請求應附帶某種受權憑證,由於用戶受權狀態可能沒經過sessions 或 cookies維護,
經常使用的作法是每一個請求都發送一個祕密的access token來認證用戶,因爲access token能夠惟一識別和認證用戶, API 請求應經過HTTPS來防止man-in-the-middle (MitM) 中間人攻擊.
2.準備工做:首先,要建立一個user表,能夠用yii2自帶的migrate建立,不會的請查閱相關資料。或者用sql語句建表,以下
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`username` varchar(20) NOT NULL DEFAULT '' COMMENT '用戶名',
`password_hash` varchar(100) NOT NULL DEFAULT '' COMMENT '密碼',
`password_reset_token` varchar(50) NOT NULL DEFAULT '' COMMENT '密碼token',
`email` varchar(20) NOT NULL DEFAULT '' COMMENT '郵箱',
`auth_key` varchar(50) NOT NULL DEFAULT '',
`status` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '狀態',
`created_at` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '建立時間',
`updated_at` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '更新時間',
`access_token` varchar(50) NOT NULL DEFAULT '' COMMENT 'restful請求token',
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`),
UNIQUE KEY `access-token` (`access-token`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
插入一條或者幾條數據,以便後期測試
3.配置user應用組件(不是必要的,可是推薦配置):
說明:1) 設置yii\web\User::enableSession屬性爲false(由於RESTful APIs爲無狀態的,當yii\web\User::enableSession爲false,請求中的用戶認證狀態就不能經過session來保持)
2) 設置yii\web\User::loginUrl屬性爲null(顯示一個HTTP 403 錯誤而不是跳轉到登陸界面)
3) 'identityClass' => 'api\models\User',
指定認證的model類,如今是在api\models\User這個類中,那麼咱們須要在api\models\User這個類中實現,yii\web\IdentityInterface這個類中的全部定義的接口方法
'user' => [
'identityClass' => 'api\models\User',
'enableAutoLogin' => true,
'enableSession'=>false,
'identityCookie' => ['name' => '_identity-api', 'httpOnly' => true],
],
4.爲控制器配置authenticator行爲指定認證方式
<?php
namespace api\modules\v1\controllers;
use yii\rest\ActiveController;
use yii\helpers\ArrayHelper;
use yii\filters\auth\QueryParamAuth;
class UserController extends ActiveController
{
public $modelClass = 'api\models\User';
public function behaviors() {
$behaviors = parent::behaviors();
$behaviors['authenticator'] = [
'class' => QueryParamAuth::className(),
];
$behaviors['contentNegotiator']['formats'] = [
'application/json'=> Response::FORMAT_JSON
];
return $behaviors;
}
}
說明:behaviors()這個函數的是定義這個控制器類的行爲,也就是每一次訪問這個控制器的方法,
都會執行這個behaviors中定義的各類行爲,認證也是這個流程,咱們訪問一個api接口時,
就會執行yii\filters\auth\QueryParamAuth的這個文件的authenticate()這個方法
5.在api/config/main.php中
咱們須要在配置文件中保證,步驟3配置user組件中曾提到過,這裏做爲補充說明。
'user' => [
'identityClass' => 'api\models\User',
],
這裏是指定認證的model類,如今是在api\models\User這個類中,那麼咱們須要在api\models\User這個類中實現yii\web\IdentityInterface這個類中的全部定義的接口方法,在api/models/User.php中代碼以下:
<?php
namespace api\models;
use yii\db\ActiveRecord;
use yii\web\IdentityInterface;
class User extends ActiveRecord implements IdentityInterface{
public static function tableName()
{
return 'user';
}
public function rules()
{
return [
[['username', 'auth_key', 'password_hash', 'email', 'created_at', 'updated_at'], 'required'],
[['status', 'created_at', 'updated_at', 'allowance', 'allowance_updated_at'], 'integer'],
[['username', 'password_hash', 'password_reset_token', 'email'], 'string', 'max' => 255],
[['auth_key'], 'string', 'max' => 32],
[['access_token'], 'string', 'max' => 60],
[['username'], 'unique'],
[['email'], 'unique'],
[['password_reset_token'], 'unique'],
];
}
public function attributeLabels()
{
return [
'id' => 'ID',
'username' => 'Username',
'auth_key' => 'Auth Key',
'password_hash' => 'Password Hash',
'password_reset_token' => 'Password Reset Token',
'email' => 'Email',
'status' => 'Status',
'created_at' => 'Created At',
'updated_at' => 'Updated At',
'access_token' => 'Access Token',
'allowance' => 'Allowance',
'allowance_updated_at' => 'Allowance Updated At',
];
}
//實現接口裏的所有方法
public static function findIdentity($id)
{
return static::findOne(['id' => $id, 'status' => self::STATUS_ACTIVE]);
}
public static function findIdentityByAccessToken($token, $type = null)
{
return static::findOne(['access_token' => $token]); //數據庫user表中的字段access_token
}
//這個就是咱們進行yii\filters\auth\QueryParamAuth調用認證的函數,下面會說到。
public function loginByAccessToken($accessToken, $type) {
//查詢數據庫中有沒有存在這個token
return static::findIdentityByAccessToken($accessToken, $type);
}
public static function findByUsername($username)
{
return static::findOne(['username' => $username, 'status' => self::STATUS_ACTIVE]);
}
public function getId()
{
return $this->getPrimaryKey();
}
public function getAuthKey()
{
return $this->auth_key;
}
public function validateAuthKey($authKey)
{
return $this->getAuthKey() === $authKey;
}
這樣一來,整個認證的過程就完成了。
如今對數據庫user表中的access_token和訪問時需攜帶的參數access-token進行簡單說明:
1.user表中的參數access_token做用:
在實現IdentityInterface接口類,重寫findIdentityByAccessToken()方法中,根據access_token校驗用戶身份,獲取用戶信息
2.url訪問時,經過攜帶的參數access-token做用:
利用該參數進行認證。此參數實際上是對yii\filters\auth\QueryParanAuth中的屬性$tokenParam設置的值。
細心的盆友會發現,這實際上是UserController中行爲中配置authenticator行爲對應的認證類。
$tokenParam這個屬性是設置url附帶的token的參數key,咱們能夠在behaviors()這個函數中配置修改:
更改以後url附帶的參數key就應該是token.
4.如此一來,咱們先經過postman模擬不帶access-token請求看結果
提示401 咱們沒有權限訪問!
5.咱們在請求的連接上攜帶正確的access-token,認證經過後,控制器會再繼續執行其餘檢查(頻率限制、操做權限等),才能夠返回正確的用戶信息。
若是攜帶access-token在user表中找不到與該字段對應的值,那麼結果跟步驟4中顯示的仍是同樣。這裏訪問http://www.dsgn.com/v1/user?access-token=123456,進行測試:
若是此處要指定哪些字段放在結果中,可使用fields或者expand參數,如:http://www.dsgn.com/v1/user?fields=id,username&access-token=123456,這樣GET的結果就只顯示包含id,username這2個字段的結果了
6.受權:在用戶認證經過後,你可能想檢查他是否有足夠的權限來訪問請求資源的這個動做, 那麼就在api/modules/controllers下對應的控制器中重寫函數checkAccess
public function checkAccess($action, $model = null, $params = [])
{
}
3、速率控制
說明:爲防止濫用,能夠增長速率限制。例如,限制每一個用戶的API的使用是在60秒內最多5次的API調用,
若是一個用戶同一個時間段內太多的請求被接收,將返回響應狀態代碼 429 (這意味着過多的請求)。
1.在咱們啓用了速率限制後,Yii會自動使用yii\filters\RateLimiter爲yii\rest\Controller配置一個行爲過濾器來執行速率限制檢查。
若是速度超出限制,該速率限制器將拋出一個yii\web\TooManyRequestsHttpException。
2.給user表添加2個字段
`allowance` int(10) unsigned NOT NULL DEFAULT '0' COMMENT 'restful剩餘的容許的請求數',
`allowance_updated_at` int(10) unsigned NOT NULL DEFAULT '0' COMMENT 'restful請求的UNIX時間戳數',
3.在api相應的控制器下,behaviors函數下添加如下代碼:
$behaviors['rateLimiter'] = [
'class' => RateLimiter::className(),
'enableRateLimitHeaders' => true,
];
3.在user表中使用兩列來記錄容差和時間戳信息。爲了提升性能,能夠考慮使用緩存或NoSQL存儲這些信息。修改api/models/User.php,改動或者加入如下代碼:
use yii\filters\RateLimitInterface;
class User extends ActiveRecord implements IdentityInterface,RateLimitInterface
//限制同一接口某時間段過多的請求,須要實現yii\filters\RateLimitInterface接口的所有方法====================
// 返回某一時間容許請求的最大數量,好比設置60秒內最多5次請求(小數量方便咱們模擬測試)
public function getRateLimit($request, $action){
return [5, 60];
}
// 回剩餘的容許的請求和相應的UNIX時間戳數 當最後一次速率限制檢查時
public function loadAllowance($request, $action){
return [$this->allowance, $this->allowance_updated_at];
}
// 保存容許剩餘的請求數和當前的UNIX時間戳
public function saveAllowance($request, $action, $allowance, $timestamp){
$this->allowance = $allowance;
$this->allowance_updated_at = $timestamp;
$this->save();
}
4.經過postman請求http://www.dsgn.com/v1/user?access-token=12345,經過測試結果發現,60秒內調用超過5次API接口,咱們會獲得狀態爲429太多請求的異常信息。
注意:如下紅框裏面的頭信息內容,能夠經過在api/models/User.php中,經過設置禁用掉
5.錯誤處理
Yii的REST框架的HTTP狀態代碼可參考以下:
200: OK。一切正常。
201: 響應 POST 請求時成功建立一個資源。Location header 包含的URL指向新建立的資源。
204: 該請求被成功處理,響應不包含正文內容 (相似 DELETE 請求)。
304: 資源沒有被修改。可使用緩存的版本。
400: 錯誤的請求。可能經過用戶方面的多種緣由引發的,例如在請求體內有無效的JSON 數據,無效的操做參數,等等。
401: 驗證失敗。
403: 已經通過身份驗證的用戶不容許訪問指定的 API 末端。
404: 所請求的資源不存在。
405: 不被容許的方法。 請檢查 Allow header 容許的HTTP方法。
415: 不支持的媒體類型。 所請求的內容類型或版本號是無效的。
422: 數據驗證失敗 (例如,響應一個 POST 請求)。 請檢查響應體內詳細的錯誤消息。
429: 請求過多。 因爲限速請求被拒絕。
500: 內部服務器錯誤。 這多是因爲內部程序錯誤引發的。