天天看點

SpringCloud工作筆記048---RESTful API 中 HTTP 狀态碼的定義_以及把RESTFul版本号_放到http協定header中_以及RestFul設計時的兩個誤區

RESTful架構有一些典型的設計誤區。

​最常見的一種設計錯誤,就是URI包含動詞。​因為"資源"表示一種實體,是以應該是名詞,URI不應該有動詞,動詞應該放在HTTP協定中。

舉例來說,某個URI是/posts/show/1,其中show是動詞,這個URI就設計錯了,正确的寫法應該是/posts/1,然後用GET方法表示show。

如果某些動作是HTTP動詞表示不了的,你就應該把動作做成一種資源。比如網上彙款,從賬戶1向賬戶2彙款500元,錯誤的URI是:

  POST /accounts/1/transfer/500/to/2

正确的寫法是把動詞transfer改成名詞transaction,資源不能是動詞,但是可以是一種服務:

  POST /transaction HTTP/1.1

  Host: 127.0.0.1

  

  from=1&to=2&amount=500.00

​另一個設計誤區,就是在URI中加入版本号​:

  http://www.example.com/app/1.0/foo

  http://www.example.com/app/1.1/foo

  http://www.example.com/app/2.0/foo

因為不同的版本,可以了解成同一種資源的不同表現形式,是以應該采用同一個URI。版本号可以在HTTP請求頭資訊的Accept字段中進行區分(參見​​Versioning REST Services​​):

  Accept: vnd.example-com.foo+json; version=1.0

  Accept: vnd.example-com.foo+json; version=1.1

  Accept: vnd.example-com.foo+json; version=2.0

-------------------------

懶人直接看 200、400、401、403、404、500 就可以了。

其中 2XX/3XX 其實都是請求成功,但是結果不同。4XX 是請求出錯,5XX 是伺服器處理出現錯誤。

200 這個最容易了解,就是正确的請求傳回正确的結果,如果不想細分正确的請求結果都可以直接傳回200。

201 表示資源被正确的建立。比如說,我們 POST 使用者名、密碼正确建立了一個使用者就可以傳回 201。

202 請求是正确的,但是結果正在進行中,沒法傳回對應的結果。比如說,我們請求一個需要大量計算的結果,但是并沒有計算結束時,可以傳回這個,這時候用戶端可以通過輪詢等機制繼續請求。

203 請求的代理伺服器修改了源伺服器傳回的 200 中的内容,一般用不到。比如說,我們通過代理伺服器向伺服器 A 請求使用者資訊,伺服器 A 正常響應,但代理伺服器命中了緩存并傳回了自己的緩存内容,這時候它傳回 203 告訴我們這部分資訊不一定是最新的,我們可以自行判斷并處理。

204 請求正确,但是沒有需要傳回的内容。比如說,我們請求删除某個使用者,删除成功可以傳回 204。

205 類似 204,但是要求請求者重置視圖,一般也用不到。比如說,我們請求删除某個使用者,伺服器傳回 205 的話,我們就重新整理現在的使用者清單。

206 請求成功,但根據請求頭隻傳回了部分内容。比如說,我們下載下傳一部片,共有 10 部分,我們把請求也分成了 10 次(防止一次請求過大),這時候伺服器就可以傳回 206 并在其頭部告訴我們這是哪一部分,然後再根據這個資訊進行拼裝。

300 請求成功,但結果有多種選擇。比如說,我們下載下傳一部片,伺服器有 avi、mp4 等格式,這時候可以傳回 300,并在 body 裡告知有哪些格式,然後使用者可以根據這些格式再次請求。

301 請求成功,但是資源被永久轉移。比如說,我們要下載下傳葫蘆娃,但是由于舊的存儲服務商漲價了,現在要使用新的存儲服務了,要去新位址下載下傳,這時候可以傳回 301,并在 header 的 Location 中告知新的位址,以後也應當到這個位址下載下傳。

302 請求成功,但是資源被臨時轉移了。和 301 不同的是,除非是 HEAD 請求,否則新位址的資訊應當在 body 中傳回,并且資源隻是臨時轉移,以後不應當通過新位址來下載下傳。

303 類似 302,但要求使用 GET 來通路新的位址來擷取資源。

304 請求的資源并沒有被修改過。比如說,我們發送請求想看看 5.20 後的情侶資訊,伺服器查詢沒有新的情侶資訊産生,這時候可以傳回 304,然後用戶端可以繼續用舊的資料。

305 請求的資源必須通過代理通路。比如說,我們想請求伺服器 A 上新的 iPhone 的資訊,但是需要通過代理伺服器才能通路,如果直接請求了伺服器 A,沒有經過代理伺服器,這時候伺服器 A 就可以傳回 305 進而告訴我們應當通路代理伺服器。 306 不用了。

307 類似 302,但要求使用原有的請求方式來通過新位址擷取資源。

308 類似 301,但要求使用原有的請求方式來通過新位址擷取資源。

400 請求出現錯誤,比如請求頭不對等,所有不想明确區分的用戶端請求出錯都可以傳回 400。

401 沒有提供認證資訊。比如說,請求的時候沒有帶上 Token 等。

402 為将來的需要所保留的狀态碼。

403 請求的資源不允許通路。比如說,你使用普通使用者的 Token 去請求管理者才能通路的資源。

404 請求的内容不存在。

405 請求的方法不允許使用。比如說,伺服器隻實作了 PATCH 了局部更新資源,并沒有實作 PUT 來替換資源,而我們使用了 PUT,這時候伺服器可以傳回 405 來告知并沒有實作對 PUT 的相關處理。

406 請求的資源并不符合要求。比如說,我們 header 裡請求 JSON 格式的資料,但是伺服器隻有 XML 格式的資料,這時候可以傳回 406 告知。

407 類似 401,但是要求必須去同代理伺服器進行認證。

408 用戶端請求逾時。我們想 POST 建立一個使用者,雖然建立了連接配接,但是網絡不好,伺服器在規定時間内沒有得到我們的請求資訊,這時候伺服器可以傳回 408 告訴我們逾時了。然後我們可以重新發送請求。

409 請求沖突。比如說,伺服器要求不同使用者不能重名,伺服器已經有了一個名叫小偉的使用者,這時候我們又想建立一個名叫小偉的使用者,伺服器可以傳回 409,告訴我們沖突了,也可以在 body 中明确告知是什麼沖突了。

410 請求資源曾經存在,但現在不存在了。比如說,我們下載下傳葫蘆娃,但是因為版權被删了,下載下傳不了了,這時候伺服器傳回 410,告訴我們洗洗早點睡。

411 沒有提供請求資源的長度。比如說,我們下載下傳葫蘆娃,伺服器隻允許我們分部分下載下傳,我們如果不告訴伺服器我們要下載下傳哪部分,伺服器就傳回 411 警告我們。

412 請求的資源不符合請求頭中的 IF-* 的某些條件。比如說,我們下載下傳葫蘆娃,然後在請求頭告知伺服器要 5.20 後更新過的,伺服器沒有,于是傳回了 412。

413 請求體過大。比如說,伺服器要求上傳檔案不能超過 5M,但是我們 POST 了 10M,這時候就傳回 413。

414 請求的 URI 太長了。比如說,我們提供了太多的 Query 參數,以至于超過了伺服器的限制,這時候可以傳回 414。

415 不支援的媒體類型。比如說,我們上傳了一張七娃的 GIF 動圖,而伺服器隻允許你上傳 PNG 圖檔,這時候就傳回 415。 416 請求的區間無效。比如說,我們分部分下載下傳時請求葫蘆娃的 10 分鐘到 12 分鐘的内容,但是這部葫蘆娃隻有 1 分鐘的内容,這時候就傳回 416。

417 預期錯誤。指伺服器沒法滿足我們在請求頭裡的 Expect 相關的資訊。

418 我是個茶壺。這是一個愚人節的玩笑,這個狀态碼就是用來搞笑的。

500 伺服器錯誤。沒法明确定義的伺服器錯誤都可以傳回這個。

501 請求還沒有被實作。比如說,我們請求一個接口來自動拒絕項目經理的要求,但是這個接口隻是美好的想象,并沒有被實作,這時候可以傳回 501。

502 網關錯誤。比如說,我們向伺服器 A 請求下載下傳葫蘆娃,但是 A 其實隻是一個代理伺服器,他得向 B 請求葫蘆娃,但是不知道為啥 B 不理他或者給他錯誤,這時候哦可以 A 傳回 502 用來表示 B 這家夥傲嬌了。

503 服務暫時不可用。比如說,伺服器正好在更新代碼重新開機。

504 類似 502,但是這時候是 B 不理 A,逾時了 。

505 請求的 HTTP 版本不支援。比如說,現在強行根據 HTTP 1000 來請求。

-------------------------------------------------

HTTP無狀态協定(超文本傳輸協定,屬于文本協定)

HTTP無狀态協定是指協定對于事務處理沒有記憶能力。缺少狀态意味着如果後續處理需要前面的資訊,則它必須重傳,這樣可能導緻每次連接配接傳送的資料量增大。另一方面,在伺服器不需要先前資訊時它的應答就較快。



HTTP請求頭和相應頭

參考:http://tools.jb51.net/table/http_header

相應頭和請求頭對照(部分)


Request
Response
Accept:浏覽器能夠處理的内容類型
Accept-Ranges:表明伺服器是否支援指定範圍請求及哪種類型的分段請求
Accept-Charset:浏覽器能夠顯示的字元集
Accept-Encoding:浏覽器能夠處理的壓縮編碼
Accept-Language:浏覽器目前設定的語言
Connection:浏覽器與伺服器連接配接類型,表示是否要持續連接配接
 
Cookie:把儲存在該請求域名的所有cookies發送給伺服器
Set-Cookie:設定HTTP cookie
Host:指定請求伺服器的域名或端口号
Server:伺服器軟體名稱
Referer:先前請求的網頁
 
User-Agent:使用者代理
Vary:告訴下遊代理是使用緩存響應還是從原始伺服器請求
 
Location:重定向到新的資源
 
Refresh:應用于重定向或一個新的資源被創造
Date:請求發送的時間
Date:原始伺服器發出消息的時間
 
Expires:相應過期的時間
 
Age:從原始伺服器到代理緩存形成的估算時間(以秒計,非負)
Cache-Control:指定請求和相應遵循哪種緩存機制。
Cache-Control:告訴所有的緩存機制是否可以緩存以及用哪種方式緩存



RESTful(英文:Representational State Transfer,簡稱REST表現層狀态轉換)

一種軟體架構風格,設計風格而不是标準,隻是提供了一組設計原則和限制條件。它主要用于用戶端和伺服器互動類的軟體。基于這個風格設計的軟體可以更簡潔,更有層次,更易于實作緩存等機制。

Web 應用程式最重要的 REST 原則是,用戶端和伺服器之間的互動在請求之間是無狀态的。從用戶端到伺服器的每個請求都必須包含了解請求所必需的資訊。如果伺服器在請求之間的任何時間點重新開機,用戶端不會得到通知。此外,無狀态請求可以由任何可用伺服器回答,這十分适合雲計算之類的環境。用戶端可以緩存資料以改進性能。

HTTP狀态碼

HTTP狀态碼分類 
1** 資訊,伺服器收到請求,需要請求者繼續執行操作
2** 成功,操作被成功接收并處理
3** 重定向,需要進一步的操作以完成請求
4** 用戶端錯誤,請求包含文法錯誤或無法完成請求
5** 伺服器錯誤,伺服器在處理請求的過程中發生了錯
HTTP狀态碼清單
100 Continue    繼續。用戶端應繼續其請求
101 Switching Protocols 切換協定。伺服器根據用戶端的請求切換協定。隻能切換到更進階的協定,例如,切換到HTTP的新版本協定
200 OK  請求成功。一般用于GET與POST請求
201 Created 已建立。成功請求并建立了新的資源
202 Accepted    已接受。已經接受請求,但未處理完成
203 Non-Authoritative Information   非授權資訊。請求成功。但傳回的meta資訊不在原始的伺服器,而是一個副本
204 No Content  無内容。伺服器成功處理,但未傳回内容。在未更新網頁的情況下,可確定浏覽器繼續顯示目前文檔
205 Reset Content   重置内容。伺服器處理成功,使用者終端(例如:浏覽器)應重置文檔視圖。可通過此傳回碼清除浏覽器的表單域
206 Partial Content 部分内容。伺服器成功處理了部分GET請求
300 Multiple Choices    多種選擇。請求的資源可包括多個位置,相應可傳回一個資源特征與位址的清單用于使用者終端(例如:浏覽器)選擇
301 Moved Permanently   永久移動。請求的資源已被永久的移動到新URI,傳回資訊會包括新的URI,浏覽器會自動定向到新URI。今後任何新的請求都應使用新的URI代替
302 Found   臨時移動。與301類似。但資源隻是臨時被移動。用戶端應繼續使用原有URI
303 See Other   檢視其它位址。與301類似。使用GET和POST請求檢視
304 Not Modified    未修改。所請求的資源未修改,伺服器傳回此狀态碼時,不會傳回任何資源。用戶端通常會緩存通路過的資源,通過提供一個頭資訊指出用戶端希望隻傳回在指定日期之後修改的資源
305 Use Proxy   使用代理。所請求的資源必須通過代理通路
306 Unused  已經被廢棄的HTTP狀态碼
307 Temporary Redirect  臨時重定向。與302類似。使用GET請求重定向
400 Bad Request 用戶端請求的文法錯誤,伺服器無法了解
401 Unauthorized    請求要求使用者的身份認證
402 Payment Required    保留,将來使用
403 Forbidden   伺服器了解請求用戶端的請求,但是拒絕執行此請求
404 Not Found   伺服器無法根據用戶端的請求找到資源(網頁)。通過此代碼,網站設計人員可設定"您所請求的資源無法找到"的個性頁面
405 Method Not Allowed  用戶端請求中的方法被禁止
406 Not Acceptable  伺服器無法根據用戶端請求的内容特性完成請求
407 Proxy Authentication Required   請求要求代理的身份認證,與401類似,但請求者應當使用代理進行授權
408 Request Time-out    伺服器等待用戶端發送的請求時間過長,逾時
409 Conflict    伺服器完成用戶端的PUT請求是可能傳回此代碼,伺服器處理請求時發生了沖突
410 Gone    用戶端請求的資源已經不存在。410不同于404,如果資源以前有現在被永久删除了可使用410代碼,網站設計人員可通過301代碼指定資源的新位置
411 Length Required 伺服器無法處理用戶端發送的不帶Content-Length的請求資訊
412 Precondition Failed 用戶端請求資訊的先決條件錯誤
413 Request Entity Too Large    由于請求的實體過大,伺服器無法處理,是以拒絕請求。為防止用戶端的連續請求,伺服器可能會關閉連接配接。如果隻是伺服器暫時無法處理,則會包含一個Retry-After的響應資訊
414 Request-URI Too Large   請求的URI過長(URI通常為網址),伺服器無法處理
415 Unsupported Media Type  伺服器無法處理請求附帶的媒體格式
416 Requested range not satisfiable 用戶端請求的範圍無效
417 Expectation Failed  伺服器無法滿足Expect的請求頭資訊
500 Internal Server Error   伺服器内部錯誤,無法完成請求
501 Not Implemented 伺服器不支援請求的功能,無法完成請求
502 Bad Gateway 充當網關或代理的伺服器,從遠端伺服器接收到了一個無效的請求
503 Service Unavailable 由于超載或系統維護,伺服器暫時的無法處理用戶端的請求。延時的長度可包含在伺服器的Retry-After頭資訊中
504 Gateway Time-out    充當網關或代理的伺服器,未及時從遠端伺服器擷取請求
505 HTTP Version not supported  伺服器不支援請求的HTTP協定的版本,無法完成處理

---------------------      

-----------------------------------------------

​​http://www.ruanyifeng.com/blog/2011/09/restful​​ 阮一峰認為,版本号應該放在 HTTP 請求頭中 這個認為,API 版本号應該區分

而且 github API,也是 URI 中帶有版本号的,難道說 github 的 API 設計的不好?

是以我懷疑 阮一峰這個問題上搞錯了。

能分享一些實際的設計 API 的經驗嗎?謝謝

---------------------------

REST URI設計:版本号放在http header中,rewrite配置

REST API新版本上線後,舊版本要繼續線上,是以要做多版本并行。

伺服器代碼目錄

api.example.com/

                          0.1/

                               controller

                               model

                               htdocs/index.php

                          0.2/

                               controller

                               model

                               htdocs/index.php

之前做的URI是這樣的:

curl http://api.example.com/0.2/users/1      

web server需要做rewrite,把各個版本的請求路由到 {v}/www/index.php。

版本号的格式為:11.11.11,即([0-9]+\.)+[0-9]+

這時候apache這麼配:

DocumentRoot "/api.example.com/"
    ServerName api.example.com
    RewriteEngine On
    RewriteRule ^/(([0-9]+\.)+[0-9]+)/ /$1/www/index.php      

更多的了解REST以後,覺得把版本号、access_token放在header中更符合資源的概念。

參考:​​http://www.ruanyifeng.com/blog/2011/09/restful.html​​

URI改成這樣:

curl -H 'Accept:application/json; version=0.2' http://api.example.com/users/1      

這個時候需要web server從header中解析到版本号,然後路由。

這個時候apache這麼配:

DocumentRoot "/api.example.com/"
    ServerName api.example.com
    RewriteEngine On
    RewriteCond  %{HTTP_ACCEPT}  version=(([0-9]+\.)+[0-9]+) 
    RewriteRule ^(.+)$ - [env=v:%1]
    RewriteCond  %{HTTP_ACCEPT}  version=(([0-9]+\.)+[0-9]+)
    RewriteRule  .*   /%{ENV:v}/htdocs/index.php
    #RewriteLogLevel 9
    #RewriteLog logs/api.example.com-rewrite_log      

在網上查了半天,才試出來apache rewrite從header中取變量。

nginx 這麼配:

SpringCloud工作筆記048---RESTful API 中 HTTP 狀态碼的定義_以及把RESTFul版本号_放到http協定header中_以及RestFul設計時的兩個誤區
server {
    listen       8080;
    server_name  api.example.com;
    root html/api;
    #access_log   logs/api.example.com/access.log combined buffer=32k;
    access_log   logs/api.example.com/trunk/access.log combined;
    error_log    logs/api.example.com/trunk/error.log;

    location = /robots.txt {
        expires 1d;
    }
    location = /favicon.ico {
        expires 1d;
    }

    location / {
        rewrite ^/$ /docs.php last;
        set $api_version "enabled";
        if ($uri ~ "((([0-9]+\.)+[0-9]+)|trunk)/docs/.*") {
            set $api_version $1;
            rewrite ^/((([0-9]+\.)+[0-9]+)|trunk)/docs/$ /$api_version/htdocs/docs/index.html break;
            rewrite ^/((([0-9]+\.)+[0-9]+)|trunk)/docs/(.*)$ /$api_version/htdocs/docs/$4 break;
        }
        if ($http_accept ~ "application/json; version=((([0-9]+\.)+[0-9]+)|trunk)") {
            set $api_version $1;
        }
        rewrite .* /$api_version/htdocs/index.php last;
    }

    location ~ \.php$ {
        fastcgi_pass   unix:/home/lnmp/php/var/run/php-fpm.sock;
        fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
        include        fastcgi_params;
    }

    error_page  404              /404.html;
    location = /404.html {
        root   html;
    }

    error_page  500 502 503 504  /50x.html;
    location = /50x.html {
        root   html;
    }
}      
SpringCloud工作筆記048---RESTful API 中 HTTP 狀态碼的定義_以及把RESTFul版本号_放到http協定header中_以及RestFul設計時的兩個誤區

注意:nginx if中的rewrite不能直接使用$1,而要先set。

參考資料:

how to use a variable inside a nginx “if” regular expression

​​http://stackoverflow.com/questions/5859848/how-to-use-a-variable-inside-a-nginx-if-regular-expression​​

apache [env=ps:http] %{ENV:ps}

​​http://www.askapache.com/htaccess/http-https-rewriterule-redirect.html​​

apache rewrite 變量 %{}

​​http://httpd.apache.org/docs/current/mod/mod_rewrite.html#RewriteCond​​

apache rewrite %N $N

​​http://httpd.apache.org/docs/current/mod/mod_rewrite.html#RewriteRule​​

apache [E=VAR:VAL] [env

​​http://httpd.apache.org/docs/2.2/rewrite/flags.html#flag_e​​