Web API 實作
精心設計的 RESTful Web API 定義了可供用戶端應用程式存取的資源、關係和導覽方案。 當您實施和部署 Web API 時,應考慮托管 Web API 的環境的實際需求以及 Web API 的構建方式,而不是僅僅關注資料的邏輯結構。 本指南著重於實施 Web API 的最佳做法,以及將其發佈以供用戶端應用程式使用。 有關 Web API 設計的詳細資訊,請參閱「Web API 設計」。
正在處理請求
在實作處理請求的程式碼時,請考慮以下幾點。
GET、PUT、DELETE、HEAD 和 PATCH 動作應該是等冪的
實作這些請求的程式碼不應該引入任何副作用。 對同一資源重複傳送相同的請求應該產生相同的狀態。 例如,向相同的 URI 傳送多個 DELETE 請求應該具有相同的效果,不過回應訊息中的 HTTP 狀態碼可能會有所不同。 第一次 DELETE 請求可能會傳回狀態碼 204 (沒有內容),而後續的 DELETE 請求可能會傳回狀態碼 404 (找不到)。
注意
Jonathan Oliver 的部落格文章「Idempotency Patterns」提供了冪等性的概述,以及它如何與資料管理作業相關聯。
建立新資源的 POST 動作不應該產生無關的副作用
如果 POST 請求旨在建立新資源,則該請求的效果應僅限於新資源 (如果涉及某種連結,則可能還包括任何直接相關的資源)。 例如,在一個電子商務系統中,建立新訂單的 POST 請求可能會調整庫存水準並生成帳單資訊,但它不應該修改與訂單無直接關聯的資訊,也不應對系統的整體狀態產生其他副作用。
避免執行過於頻繁的 POST、PUT 和 DELETE 作業
避免根據頻繁作業來設計您的 API。 每個請求都會帶來通訊協定、網路和計算負荷。 例如,執行 100 個較小的請求而不是一次較大的批次請求會在用戶端、網路和資源伺服器上帶來額外的負荷。 盡量對資源集合提供 HTTP 動詞支援,而不僅僅是對單一資源提供支援。
- 對集合傳送 GET 請求可以同時擷取多個資源。
- POST 請求可以包含多個新資源的詳細資訊,並將它們全部新增到同一集合中。
- PUT 請求可以替換集合中的整個資源集。
- DELETE 請求可以移除整個集合。
ASP.NET Web API 2 中包含的 Open Data Protocol (OData) 支援提供了批次請求的能力。 用戶端應用程式可以將幾個 Web API 請求打包在一起,並以單一的 HTTP 請求傳送到伺服器,然後接收包含每個請求回應的單一 HTTP 回應。 如需了解更多資訊,請參閱「在 Web API OData 服務中啟用批次處理」。
傳送回應時請遵循 HTTP 規範
Web API 必須傳回包含正確 HTTP 狀態碼的訊息,以便用戶端能夠判斷如何處理結果;包含適當的 HTTP 標頭,以使用戶端了解結果的性質;以及格式正確的主體,以便用戶端能夠解析結果。
例如,POST 作業應傳回狀態碼 201 (已建立),並且回應訊息應在 Location 標頭中包含新建立資源的 URI。
支援內容協商
回應訊息的主體可以包含多種格式的資料。 例如,HTTP GET 請求可以傳回 JSON 或 XML 格式的資料。 當用戶端提交請求時,它可以包含一個 Accept 標頭,指定它可以處理的資料格式。 這些格式會指定為媒體類型。 例如,發出 GET 請求以擷取影像的用戶端可以指定一個 Accept 標頭,列出用戶端可以處理的媒體類型,如 image/jpeg, image/gif, image/png
。 當 Web API 傳回結果時,應使用這些媒體類型中的一種來格式化資料,並在回應的 Content-Type 標頭中指定該格式。
如果用戶端未指定 Accept 標頭,則使用合適的預設格式來格式化回應主體。 例如,ASP.NET Web API 框架預設使用 JSON 作為文字資料的格式。
提供連結以支援 HATEOAS 式導覽和資源探索
HATEOAS 方法使用戶端能夠從初始起點導覽和探索資源。 這是透過使用包含 URI 的連結來實現的;當用戶端發出 HTTP GET 請求以獲取資源時,回應應包含 URI,以便用戶端應用程式能夠快速定位任何直接相關的資源。 例如,在支援電子商務解決方案的 Web API 中,客戶可能已經下了許多訂單。 當用戶端應用程式擷取客戶詳細資訊時,回應應包含連結,使用戶端應用程式能夠傳送 HTTP GET 請求來擷取這些訂單。 此外,HATEOAS 式連結應描述每個連結資源支援的其他作業 (如 POST、PUT、DELETE 等),以及執行每個請求的相應 URI。 這種方法在「API 設計」中有更詳細的描述。
目前沒有標準規範 HATEOAS 的實作,但以下範例說明了一種可能的方法。 在這個例子中,傳送 HTTP GET 請求以查找客戶詳細資訊會傳回一個回應,其中包含參考該客戶訂單的 HATEOAS 連結:
GET https://adventure-works.com/customers/2 HTTP/1.1
Accept: text/json
...
HTTP/1.1 200 OK
...
Content-Type: application/json; charset=utf-8
...
Content-Length: ...
{"CustomerID":2,"CustomerName":"Bert","Links":[
{"rel":"self",
"href":"https://adventure-works.com/customers/2",
"action":"GET",
"types":["text/xml","application/json"]},
{"rel":"self",
"href":"https://adventure-works.com/customers/2",
"action":"PUT",
"types":["application/x-www-form-urlencoded"]},
{"rel":"self",
"href":"https://adventure-works.com/customers/2",
"action":"DELETE",
"types":[]},
{"rel":"orders",
"href":"https://adventure-works.com/customers/2/orders",
"action":"GET",
"types":["text/xml","application/json"]},
{"rel":"orders",
"href":"https://adventure-works.com/customers/2/orders",
"action":"POST",
"types":["application/x-www-form-urlencoded"]}
]}
在這個例子中,客戶資料由以下程式碼片段中的 Customer
類別表示: HATEOAS 連結儲存在 Links
集合屬性中:
public class Customer
{
public int CustomerID { get; set; }
public string CustomerName { get; set; }
public List<Link> Links { get; set; }
...
}
public class Link
{
public string Rel { get; set; }
public string Href { get; set; }
public string Action { get; set; }
public string [] Types { get; set; }
}
HTTP GET 作業會從儲存體中擷取客戶資料並建構 Customer
物件,然後填入 Links
集合。 結果會格式化為 JSON 回應訊息。 每個連結都包括以下欄位:
- 連結所描述的物件與傳回物件之間的關係 (
Rel
)。 在這個例子中,self
表示連結是指向物件本身的參考 (類似於許多面向物件語言中的this
指標),而orders
是包含相關訂單資訊之集合的名稱。 - 連結描述物件的超連結 (
Href
),以 URI 形式表示。 - 可以傳送到該 URI 的 HTTP 請求類型 (
Action
)。 - 根據請求類型,應提供的 HTTP 請求中的資料格式 (
Types
),或可以在回應中傳回的資料格式。
範例 HTTP 回應中顯示的 HATEOAS 連結表明,用戶端應用程式可以執行以下作業:
- 向 URI
https://adventure-works.com/customers/2
傳送 HTTP GET 請求,以再次擷取客戶的詳細資訊。 資料能以 XML 或 JSON 格式傳回。 - 向 URI
https://adventure-works.com/customers/2
傳送 HTTP PUT 請求以修改客戶的詳細資訊。 新資料必須以 x-www-form-urlencoded 格式提供在請求訊息中。 - 向 URI
https://adventure-works.com/customers/2
傳送 HTTP DELETE 請求以刪除客戶。 請求不需要任何額外的資訊,也不會在回應訊息主體中傳回資料。 - 向 URI
https://adventure-works.com/customers/2/orders
傳送 HTTP GET 請求以查找該客戶的所有訂單。 資料能以 XML 或 JSON 格式傳回。 - 向 URI
https://adventure-works.com/customers/2/orders
傳送 HTTP POST 請求以為該客戶建立新訂單。 資料必須以 x-www-form-urlencoded 格式提供在請求訊息中。
處理例外狀況
如果作業擲回未攔截的例外狀況,請考慮以下幾點:
擷取例外狀況,並向用戶端傳回有意義的回應。
實作 HTTP 作業的程式碼應提供完整的例外狀況處理,而不是讓未攔截的例外狀況傳播到框架。 如果例外狀況使得作業無法成功完成,可以將例外狀況傳回在回應訊息中,但應包含對導致例外狀況錯誤的有意義描述。 例外狀況還應包含適當的 HTTP 狀態碼,而不只是對所有情況都傳回狀態碼 500。 例如,如果使用者請求導致資料庫更新違反約束 (例如嘗試刪除有未完成訂單的客戶),則應傳回狀態碼 409 (衝突) 以及一個訊息主體,說明衝突的原因。 如果其他條件使請求無法實現,您可以傳回狀態碼 400 (錯誤的請求)。 您可以在 W3C 網站上的狀態碼定義頁面找到完整的 HTTP 狀態碼清單。
程式碼範例會捕捉不同的條件並傳回適當的回應。
[HttpDelete]
[Route("customers/{id:int}")]
public IHttpActionResult DeleteCustomer(int id)
{
try
{
// Find the customer to be deleted in the repository
var customerToDelete = repository.GetCustomer(id);
// If there is no such customer, return an error response
// with status code 404 (Not Found)
if (customerToDelete == null)
{
return NotFound();
}
// Remove the customer from the repository
// The DeleteCustomer method returns true if the customer
// was successfully deleted
if (repository.DeleteCustomer(id))
{
// Return a response message with status code 204 (No Content)
// To indicate that the operation was successful
return StatusCode(HttpStatusCode.NoContent);
}
else
{
// Otherwise return a 400 (Bad Request) error response
return BadRequest(Strings.CustomerNotDeleted);
}
}
catch
{
// If an uncaught exception occurs, return an error response
// with status code 500 (Internal Server Error)
return InternalServerError();
}
}
提示
不要包含可能對試圖攻擊您 API 的攻擊者有用的資訊。
許多 Web 伺服器會在錯誤條件到達 Web API 之前先捕捉這些錯誤。 例如,如果您為網站配置了驗證,而使用者未提供正確的驗證資訊,則 Web 伺服器應回應狀態碼 401 (未授權)。 一旦用戶端通過驗證,您的程式碼可以進行自己的檢查,以驗證用戶端是否有權存取所請求的資源。 如果授權失敗,應傳回狀態碼 403 (禁止)。
以一致的方法處理例外狀況,並記錄有關錯誤的資訊。
若要以一致的方式處理例外狀況,請考慮在整個 Web API 中實施全域錯誤處理策略。 您還應該納入錯誤記錄,捕捉每個例外狀況的完整細節;只要這些錯誤記錄不對用戶端公開,就可以包含詳細資訊。
區分用戶端錯誤和伺服器端錯誤
HTTP 通訊協定會區分由用戶端應用程式引起的錯誤 (HTTP 4xx 狀態碼) 和由伺服器問題引起的錯誤 (HTTP 5xx 狀態碼)。 確保在任何錯誤回應訊息中都遵守此慣例。
最佳化用戶端資料存取
在分散式環境中,例如涉及 Web 伺服器和用戶端應用程式的情境中,其中主要關注來源之一是網路。 這可能成為相當大的瓶頸,尤其是當用戶端應用程式頻繁傳送請求或接收資料時。 因此,您應該盡量將網路上的流量降到最低。 在實作用於擷取和維護資料的程式碼時,請考慮以下幾點:
支援用戶端快取
HTTP 1.1 通訊協定透過使用 Cache-Control 標頭支援用戶端和中間伺服器的快取。 當用戶端應用程式向 Web API 傳送 HTTP GET 請求時,回應可以包含 Cache-Control 標頭,該標頭指示回應主體中的資料是否可以安全地由用戶端或請求經過的中間伺服器進行快取,以及在多長時間後這些資料應過期並被視為過時。
以下範例顯示了 HTTP GET 請求及其相應的回應,其中包括 Cache-Control 標頭:
GET https://adventure-works.com/orders/2 HTTP/1.1
HTTP/1.1 200 OK
...
Cache-Control: max-age=600, private
Content-Type: text/json; charset=utf-8
Content-Length: ...
{"orderID":2,"productID":4,"quantity":2,"orderValue":10.00}
在這個範例中,Cache-Control 標頭指定傳回的資料應在 600 秒後過期,並且僅適用於單一用戶端,不應儲存在其他用戶端使用的共用快取中 (它是私人的)。 Cache-Control 標頭可以指定為公開,而不是私人,這樣資料可以儲存在共用快取中;或者可以指定為不存放,在此情況下,用戶端不得快取資料。 以下程式碼範例顯示如何在回應訊息中建構 Cache-Control 標頭:
public class OrdersController : ApiController
{
...
[Route("api/orders/{id:int:min(0)}")]
[HttpGet]
public IHttpActionResult FindOrderByID(int id)
{
// Find the matching order
Order order = ...;
...
// Create a Cache-Control header for the response
var cacheControlHeader = new CacheControlHeaderValue();
cacheControlHeader.Private = true;
cacheControlHeader.MaxAge = new TimeSpan(0, 10, 0);
...
// Return a response message containing the order and the cache control header
OkResultWithCaching<Order> response = new OkResultWithCaching<Order>(order, this)
{
CacheControlHeader = cacheControlHeader
};
return response;
}
...
}
這段程式碼使用了名為 OkResultWithCaching
的自訂 IHttpActionResult
類別。 這個類別使控制器能夠設定快取標頭的內容:
public class OkResultWithCaching<T> : OkNegotiatedContentResult<T>
{
public OkResultWithCaching(T content, ApiController controller)
: base(content, controller) { }
public OkResultWithCaching(T content, IContentNegotiator contentNegotiator, HttpRequestMessage request, IEnumerable<MediaTypeFormatter> formatters)
: base(content, contentNegotiator, request, formatters) { }
public CacheControlHeaderValue CacheControlHeader { get; set; }
public EntityTagHeaderValue ETag { get; set; }
public override async Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
HttpResponseMessage response;
try
{
response = await base.ExecuteAsync(cancellationToken);
response.Headers.CacheControl = this.CacheControlHeader;
response.Headers.ETag = ETag;
}
catch (OperationCanceledException)
{
response = new HttpResponseMessage(HttpStatusCode.Conflict) {ReasonPhrase = "Operation was cancelled"};
}
return response;
}
}
注意
HTTP 通訊協定也為 Cache-Control 標頭定義了 no-cache 指示詞。 令人困惑的是,這個指示詞不代表「不進行快取」,而是「先向伺服器重新驗證快取資訊,然後再傳回」;仍然可以快取資料,但每次使用時都會檢查以確保其仍保持在最新狀態。
快取管理是用戶端應用程式或中繼伺服器的責任,但如果實作得當,它可以節省頻寬並提升效能,因為它能夠避免重新擷取最近已擷取的資料。
Cache-Control 標頭中的 max-age 值僅是一個指導原則,而不保證在指定時間內對應的資料不會改變。 Web API 應根據資料的預期波動性設定適當的 max-age 值。 當這段時間過期後,用戶端應該將該物件從快取中刪除。
注意
大多數現代網頁瀏覽器支援用戶端快取,方法是在請求中新增適當的 cache-control 標頭並檢查結果的標頭,如前所述。 然而,一些舊版瀏覽器不會快取來自包含查詢字串的 URL 的傳回值。 對於自訂用戶端應用程式來說,這通常不是問題,因為它們可以根據此處討論的通訊協定來實作自己的快取管理策略。
一些舊版 Proxy 也會出現相同的行為,可能不會快取基於包含查詢字串的 URL 的請求。 對於透過這種 Proxy 連接到 Web 伺服器的自訂用戶端應用程式來說,這可能會成為一個問題。
提供 ETags 以最佳化查詢處理
當用戶端應用程式擷取一個物件時,回應訊息也可以包含一個實體標記 (ETag) 。 ETag 是一個不透明的字串,用來表示資源的版本;每次資源發生變化時,ETag 也會隨之改變。 這個 ETag 應該作為資料的一部分,由用戶端應用程式進行快取。 以下程式碼範例顯示了如何將 ETag 作為回應的一部分新增到 HTTP GET 請求中。 這段程式碼會使用物件的 GetHashCode
方法來生成一個識別該物件的數值 (如果需要,您可以覆寫這個方法並使用如 MD5 之類的算法來生成自己的哈希值) :
public class OrdersController : ApiController
{
...
public IHttpActionResult FindOrderByID(int id)
{
// Find the matching order
Order order = ...;
...
var hashedOrder = order.GetHashCode();
string hashedOrderEtag = $"\"{hashedOrder}\"";
var eTag = new EntityTagHeaderValue(hashedOrderEtag);
// Return a response message containing the order and the cache control header
OkResultWithCaching<Order> response = new OkResultWithCaching<Order>(order, this)
{
...,
ETag = eTag
};
return response;
}
...
}
由 Web API 傳送的回應訊息如下所示:
HTTP/1.1 200 OK
...
Cache-Control: max-age=600, private
Content-Type: text/json; charset=utf-8
ETag: "2147483648"
Content-Length: ...
{"orderID":2,"productID":4,"quantity":2,"orderValue":10.00}
提示
出於安全考量,請勿允許敏感資料或通過經過驗證 (HTTPS) 連接傳回的資料被快取。
用戶端應用程式可以隨時發出後續的 GET 請求以擷取相同的資源。如果資源已經改變 (即擁有不同的 ETag),則應捨棄快取中的舊版本,並將新版本新增到快取中。 如果一個資源很大並且需要大量的頻寬來傳輸回用戶端,那麼重複請求以獲取相同的資料可能會讓效率變得低下。 為了解決這個問題,HTTP 通訊協定定義了以下程序來最佳化 GET 請求,您應該在 Web API 中支援這些程序:
用戶端會建構一個 GET 請求,其中包含了目前快取版本資源的 ETag,並將其放在 If-None-Match HTTP 標頭中:
GET https://adventure-works.com/orders/2 HTTP/1.1 If-None-Match: "2147483648"
Web API 中的 GET 作業會獲取請求資料的目前 ETag (上述範例中的步驟 2),並將其與 If-None-Match 標頭中的值進行比較。
如果請求資料的目前 ETag 與請求中提供的 ETag 相符,則表示資源未發生變更,Web API 應傳回一個 HTTP 回應,其中包含空的訊息主體和狀態碼 304 (未修改)。
如果請求資料的目前 ETag 與請求中提供的 ETag 不相符,則表示資料已經變更,Web API 應傳回一個 HTTP 回應,將新資料放在訊息主體中,並使用狀態碼 200 (成功)。
如果請求的資料已不存在,則 Web API 應傳回狀態碼為 404 (找不到) 的 HTTP 回應。
用戶端會使用狀態碼來維護快取。 如果資料未發生變化 (狀態碼為 304),則該物件可以繼續保持在快取中,用戶端應用程式應繼續使用這個版本的物件。 如果資料已發生變化 (狀態碼為 200),則應捨棄快取中的舊物件並插入新的物件。 如果資料已無法使用 (狀態碼為 404),則應將該物件從快取中移除。
注意
如果回應標頭包含 Cache-Control 標頭 no-store,則無論 HTTP 狀態碼為何,物件都應一律從快取中移除。
以下程式碼展示了如何擴充 FindOrderByID
方法以支援 If-None-Match 標頭。 請注意,如果省略 If-None-Match 標頭,則會一律擷取指定的訂單:
public class OrdersController : ApiController
{
[Route("api/orders/{id:int:min(0)}")]
[HttpGet]
public IHttpActionResult FindOrderByID(int id)
{
try
{
// Find the matching order
Order order = ...;
// If there is no such order then return NotFound
if (order == null)
{
return NotFound();
}
// Generate the ETag for the order
var hashedOrder = order.GetHashCode();
string hashedOrderEtag = $"\"{hashedOrder}\"";
// Create the Cache-Control and ETag headers for the response
IHttpActionResult response;
var cacheControlHeader = new CacheControlHeaderValue();
cacheControlHeader.Public = true;
cacheControlHeader.MaxAge = new TimeSpan(0, 10, 0);
var eTag = new EntityTagHeaderValue(hashedOrderEtag);
// Retrieve the If-None-Match header from the request (if it exists)
var nonMatchEtags = Request.Headers.IfNoneMatch;
// If there is an ETag in the If-None-Match header and
// this ETag matches that of the order just retrieved,
// then create a Not Modified response message
if (nonMatchEtags.Count > 0 &&
String.CompareOrdinal(nonMatchEtags.First().Tag, hashedOrderEtag) == 0)
{
response = new EmptyResultWithCaching()
{
StatusCode = HttpStatusCode.NotModified,
CacheControlHeader = cacheControlHeader,
ETag = eTag
};
}
// Otherwise create a response message that contains the order details
else
{
response = new OkResultWithCaching<Order>(order, this)
{
CacheControlHeader = cacheControlHeader,
ETag = eTag
};
}
return response;
}
catch
{
return InternalServerError();
}
}
...
}
這個範例包含了一個額外的自訂 IHttpActionResult
類別,名為 EmptyResultWithCaching
。 這個類別只作為一包裝函式,包裝了一個不包含回應主體的 HttpResponseMessage
物件:
public class EmptyResultWithCaching : IHttpActionResult
{
public CacheControlHeaderValue CacheControlHeader { get; set; }
public EntityTagHeaderValue ETag { get; set; }
public HttpStatusCode StatusCode { get; set; }
public Uri Location { get; set; }
public async Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
HttpResponseMessage response = new HttpResponseMessage(StatusCode);
response.Headers.CacheControl = this.CacheControlHeader;
response.Headers.ETag = this.ETag;
response.Headers.Location = this.Location;
return response;
}
}
提示
在這個範例中,資料的 ETag 是透過對從底層資料來源擷取到的資料進行哈希生成的。 如果 ETag 可以透過其他方式計算,那麼可以進一步最佳化程序,只有在資料變更時才需要從資料來源中提取資料。 這種方法尤其適用於資料量很大或存取資料來源可能會導致顯著延遲的情況 (例如,當資料來源是遠端資料庫時)。
使用 ETags 來支援樂觀並行存取
為了允許對先前快取資料的更新,HTTP 通訊協定支援樂觀並行存取策略。 如果用戶端應用程式在提取並快取了一個資源後,隨後傳送 PUT 或 DELETE 請求來修改或刪除該資源,它應該包含參考 ETag 的 If-Match 標頭。 Web API 可以利用這些資訊來判斷資源在擷取後是否已經由其他使用者更改,並向用戶端應用程式傳送適當的回應,如下所示:
用戶端會構建一個 PUT 請求,其中包含資源的新詳細資訊和目前快取版本的 ETag,並在 If-Match HTTP 標頭中參考該 ETag。 以下範例顯示了更新訂單的 PUT 請求:
PUT https://adventure-works.com/orders/1 HTTP/1.1 If-Match: "2282343857" Content-Type: application/x-www-form-urlencoded Content-Length: ... productID=3&quantity=5&orderValue=250
Web API 中的 PUT 作業會獲取請求資料的目前 ETag (上述範例中的步驟 1),並將其與 If-Match 標頭中的值進行比較。
如果請求資料的目前 ETag 與請求中提供的 ETag 相符,則表示資源未發生變化,Web API 應執行更新作業。如果成功,應傳回 HTTP 狀態碼 204 (無內容) 的訊息。 回應可以包含更新後資源的 Cache-Control 和 ETag 標頭。 回應應一律包含 Location 標頭,該標頭會參考新更新資源的 URI。
如果請求資料的目前 ETag 與請求中提供的 ETag 不相符,則表示資料在擷取後已由其他使用者更改,Web API 應傳回一個狀態碼為 412 (前置條件失敗) 的 HTTP 回應,並且回應的訊息主體應為空。
如果要更新的資源已不存在,則 Web API 應傳回狀態碼為 404 (找不到) 的 HTTP 回應。
用戶端會使用狀態碼和回應標頭來維護快取。 如果資料已經更新 (狀態碼為 204),則該物件可以繼續保留在快取中 (只要 Cache-Control 標頭未指定 `no-store`),但應更新 ETag。 如果其他使用者更改了資料 (狀態碼為 412) 或找不到資料 (狀態碼為 404),則應捨棄快取中的物件。
以下程式碼範例展示了 Orders 控制器的 PUT 作業實作:
public class OrdersController : ApiController
{
[HttpPut]
[Route("api/orders/{id:int}")]
public IHttpActionResult UpdateExistingOrder(int id, DTOOrder order)
{
try
{
var baseUri = Constants.GetUriFromConfig();
var orderToUpdate = this.ordersRepository.GetOrder(id);
if (orderToUpdate == null)
{
return NotFound();
}
var hashedOrder = orderToUpdate.GetHashCode();
string hashedOrderEtag = $"\"{hashedOrder}\"";
// Retrieve the If-Match header from the request (if it exists)
var matchEtags = Request.Headers.IfMatch;
// If there is an ETag in the If-Match header and
// this ETag matches that of the order just retrieved,
// or if there is no ETag, then update the Order
if (((matchEtags.Count > 0 &&
String.CompareOrdinal(matchEtags.First().Tag, hashedOrderEtag) == 0)) ||
matchEtags.Count == 0)
{
// Modify the order
orderToUpdate.OrderValue = order.OrderValue;
orderToUpdate.ProductID = order.ProductID;
orderToUpdate.Quantity = order.Quantity;
// Save the order back to the data store
// ...
// Create the No Content response with Cache-Control, ETag, and Location headers
var cacheControlHeader = new CacheControlHeaderValue();
cacheControlHeader.Private = true;
cacheControlHeader.MaxAge = new TimeSpan(0, 10, 0);
hashedOrder = order.GetHashCode();
hashedOrderEtag = $"\"{hashedOrder}\"";
var eTag = new EntityTagHeaderValue(hashedOrderEtag);
var location = new Uri($"{baseUri}/{Constants.ORDERS}/{id}");
var response = new EmptyResultWithCaching()
{
StatusCode = HttpStatusCode.NoContent,
CacheControlHeader = cacheControlHeader,
ETag = eTag,
Location = location
};
return response;
}
// Otherwise return a Precondition Failed response
return StatusCode(HttpStatusCode.PreconditionFailed);
}
catch
{
return InternalServerError();
}
}
...
}
提示
您可選擇是否使用 If-Match 標頭,如果省略此標頭,Web API 將一律嘗試更新指定的訂單,這可能會盲目覆寫其他使用者所做的更新。 為了避免由於更新遺失而產生問題,應一律提供 If-Match 標頭。
處理大型請求和回應
有時候,用戶端應用程式可能需要發出請求,傳送或接收數百萬位元組 (或更大) 的資料。 在傳輸這麼大量的資料時,可能會導致用戶端應用程式變得沒有回應。 當您需要處理包含大量資料的請求時,請考慮以下幾點:
最佳化涉及大型物件的請求和回應
某些資源可能是大型物件或包含大型欄位,例如圖形影像或其他類型的二進位資料。 Web API 應該支援串流,以便對這些資源進行最佳化的上傳和下載。
HTTP 通訊協定提供了區塊傳輸編碼機制,用於將大型資料物件串流傳輸回用戶端。 當用戶端傳送 HTTP GET 請求以獲取大型物件時,Web API 可以透過 HTTP 連接將回應分批傳送回來。 回應中的資料長度可能最初未知 (可能是動態生成的),因此託管 Web API 的伺服器應該在每個區塊中傳送回應訊息,並指定 Transfer-Encoding: Chunked
標頭,而不是 Content-Length 標頭。 用戶端應用程式可以逐個接收每個區塊,從而建置完整的回應。 當伺服器傳送回一個大小為零的最終區塊時,資料傳輸就會完成。
單一請求可能會產生龐大的物件,消耗相當多的資源。 如果在串流過程中,Web API 確定請求中的資料量超出了某些可接受的範圍,它可以中止作業並傳回狀態碼為 413 (請求實體過大) 的回應訊息。
您可以透過使用 HTTP 壓縮,將透過網路傳輸的大型物件大小降到最低。 這種方法有助於減少網路流量和相關的網路延遲,但代價是需要在用戶端和託管 Web API 的伺服器上進行額外的處理。 例如,預期接收壓縮資料的用戶端應用程式可以在請求中包含 Accept-Encoding: gzip
標頭 (也可以指定其他資料壓縮演算法)。 如果伺服器支援壓縮,它應該在 Content-Encoding: gzip
回應標頭中指定使用 gzip 格式,並將壓縮後的內容放在回應訊息的主體中。
您可以將編碼壓縮與串流結合使用;先壓縮資料,再進行串流,並在訊息標頭中指定 gzip 內容編碼和區塊傳輸編碼。 此外,請注意某些 Web 伺服器 (例如 Internet Information Server) 可以配置為自動壓縮 HTTP 回應,無論 Web API 是否對資料進行壓縮。
對於不支援非同步作業的用戶端,實施部分回應。
作為非同步串流的替代方案,用戶端應用程式可以明確請求大型物件的資料區塊,這種方式稱為部分回應。 用戶端應用程式傳送 HTTP HEAD 請求,以獲取有關該物件的資訊。 如果 Web API 支援部分回應,它應該對 HEAD 請求回應一個包含 Accept-Ranges
標頭和 Content-Length
標頭的回應訊息,這些標頭會指示物件的總大小,但回應訊息的主體應該是空的。 用戶端應用程式可以利用這些資訊來建構一系列 GET 請求,指定要接收的位元組範圍。 Web API 應該傳回一個 HTTP 狀態為 206 (部分內容) 的回應訊息,並包含 Content-Length 標頭,指明回應訊息主體中實際包含的資料量,以及 Content-Range 標頭,指示這些資料代表物件的哪一部分 (例如,從位元組 4000
到 8000
)。
HTTP HEAD 請求和部分回應在 API 設計中有更詳細的描述。
避免在用戶端應用程式中傳送不必要的 100-Continue 狀態訊息。
即將向伺服器傳送大量資料的用戶端應用程式,可能會先確認伺服器是否真的願意接受該請求。 在傳送資料之前,用戶端應用程式可以提交一個帶有 Expect: 100-Continue 標頭的 HTTP 請求,並包含 Content-Length 標頭以指示資料的大小,但訊息主體應為空。 如果伺服器願意處理該請求,它應該回應一個指定 HTTP 狀態 100 (繼續) 的訊息。 用戶端應用程式可以隨後繼續傳送完整的請求,包括訊息主體中的資料。
如果您使用網際網路資訊服務 (IIS) 托管服務,HTTP.sys 驅動程式會在將請求傳遞給您的 Web 應用程式之前,自動偵測並處理 Expect: 100-Continue 標頭。 這代表您在應用程式程式碼中不太可能看到這些標頭,您可以假設 IIS 已經篩選掉了它認為不適合或過大的訊息。
如果您使用 .NET Framework 開發用戶端應用程式,那麼所有的 POST 和 PUT 請求將預設先傳送帶有 Expect: 100-Continue 標頭的訊息。 與伺服器端一樣,這個程序由 .NET Framework 以透明的方式處理。 然而,這個程序會導致每個 POST 和 PUT 請求都往返伺服器兩次,即使對於小型請求也是如此。 如果您的應用程式不傳送大量資料的請求,則可以透過使用 ServicePointManager
類別來建立 ServicePoint
物件,在用戶端應用程式中停用這個功能。 ServicePoint
物件會根據標識伺服器上資源的 URI 配置和主機片段,來處理用戶端與伺服器建立的連線。 然後,您可以將 ServicePoint
物件的 Expect100Continue
屬性設為 False。 所有透過與 ServicePoint
物件的配置和主機片段相符的 URI 發出的後續 POST 和 PUT 請求,將不再包含 Expect: 100-Continue 標頭。 以下程式碼顯示了如何設定 ServicePoint
物件,使其設定所有傳送到配置為 http
和主機為 www.contoso.com
的 URI 的請求:
Uri uri = new Uri("https://www.contoso.com/");
ServicePoint sp = ServicePointManager.FindServicePoint(uri);
sp.Expect100Continue = false;
您也可以設定 ServicePointManager
類別的静态 Expect100Continue
屬性,以指定所有隨後建立的 ServicePoint 物件的預設值。
對於可能傳回大量物件的請求,支援分頁功能。
如果一個集合包含大量資源,對應 URI 發出 GET 請求可能會在託管 Web API 的伺服器上導致大量處理,影響效能,並產生大量網路流量,從而增加延遲。
為了處理這些情況,Web API 應該支援查詢字串,使用戶端應用程式能夠細化請求,或以更易於管理的離散區塊 (或頁面) 來提取資料。 以下程式碼顯示了 Orders
控制器中的 GetAllOrders
方法。 這個方法會擷取訂單的詳細資訊。 如果這個方法不加以限制,可能會傳回大量資料。 limit
和 offset
參數旨在將資料量縮減到更小的子集,在這種情況下,預設僅傳回前 10 個訂單:
public class OrdersController : ApiController
{
...
[Route("api/orders")]
[HttpGet]
public IEnumerable<Order> GetAllOrders(int limit=10, int offset=0)
{
// Find the number of orders specified by the limit parameter
// starting with the order specified by the offset parameter
var orders = ...
return orders;
}
...
}
用戶端應用程式可以透過以下 URI https://www.adventure-works.com/api/orders?limit=30&offset=50
發出請求,來擷取從位移 50 開始的 30 個訂單:
提示
避免允許用戶端應用程式指定會導致 URI 超過 2000 個字元的查詢字串。 許多 Web 用戶端和伺服器無法處理這麼長的 URI。
維持回應性、可擴展性和可用性
同一個 Web API 可能會被全球各地許多用戶端應用程式使用。 確保實施的 Web API 能夠在高負載下保持回應性、具有可擴展性以支援高度變化的工作負載,以及保證對執行業務關鍵作業的用戶端的可用性,這些都是非常重要的。 在確定如何滿足這些請求時,請考慮以下幾點:
為長時間執行的請求提供非同步支援
可能需要較長時間處理的請求,應該在不阻塞提交請求之用戶端的情況下進行。 Web API 可以執行一些初步檢查以驗證請求,啟動一個單獨的工作來執行工作,然後傳回一個 HTTP 狀態碼為 202 (已接受) 的回應訊息。 該工作可以作為 Web API 處理的一部分非同步執行,或者可以被卸載到背景工作中。
Web API 還應提供一種機制,將處理結果傳回給用戶端應用程式。 您可以透過提供輪詢機制,讓用戶端應用程式定期查詢處理是否完成並獲取結果,或者讓 Web API 在作業完成時傳送通知來實現這一點。
您可以使用以下方法,提供一個作為虛擬資源的輪詢 URI 來實現簡單的輪詢機制:
- 用戶端應用程式會向 Web API 傳送初始請求。
- Web API 將有關請求的資訊儲存在 Azure Table Storage 或 Microsoft Azure Cache 中的資料表中,並為此項目生成一個唯一索引鍵,其可能是全球唯一識別碼 (GUID) 的形式。 另一個選擇是透過 Azure Service Bus 傳送包含請求資訊和唯一索引鍵的訊息。
- Web API 將處理程序啟動為一個單獨的工作,或者使用像 Hangfire 這樣的程式庫來執行處理。 Web API 將工作的狀態記錄在資料表中,設為「執行中」。
- 如果使用 Azure Service Bus,訊息處理將與 API 分開進行,可能會使用 Azure Functions 或 AKS 來處理。
- Web API 會傳回一個 HTTP 狀態碼為 202 (已接受) 的回應訊息,並包含生成的唯一索引鍵的 URI,例如 /polling/{guid}。
- 當工作完成後,Web API 會將結果儲存在資料表中,並將工作的狀態設為「完成」。 請注意,如果工作失敗,Web API 也可以儲存有關失敗的資訊,並將狀態設為「失敗」。
- 請考慮套用重試技術來解決可能的暫時性失敗。
- 當工作執行時,用戶端可以繼續執行自己的處理程序。 它可以定期將請求傳送至稍早收到的 URI。
- URI 上的 Web API 查詢表中對應工作的狀態,並傳回包含此狀態 (執行中、完成或失敗) 的 HTTP 狀態碼 200 (正常) 的回應訊息。 如果工作已完成或失敗,回應訊息還可以包括處理結果,或任何有關失敗原因的可用資訊。
- 如果長時間執行的程序有較多中繼狀態,最好使用支援 saga 模式的程式庫,例如 NServiceBus 或 MassTransit。
實施通知的選項包括:
- 使用 通知中樞將非同步回應推送到用戶端應用程式。 有關詳細資訊,請參閱「使用 Azure 通知中樞向特定使用者傳送通知」。
- 使用 Comet 模型在用戶端和託管 Web API 的伺服器之間保留持續性網路連線,並使用此連線將訊息從伺服器推送回用戶端。 MSDN 雜誌文章「在 Microsoft .NET Framework 中建立簡單的 Comet 應用程式」介紹了一個解決方案範例。
- 使用 SignalR 透過永久網路連線,將資料從 Web 伺服器即時推送到用戶端。 SignalR 可作為 NuGet 套件用於 ASP.NET Web 應用程式。 您可以在 ASP.NET SignalR 網站上找到更多資訊。
確保每個請求都沒有狀態
每個請求都應該視為不可部分完成。 用戶端應用程式發出的一個請求與同一用戶端提交的任何後續請求之間不應存在相依性。 這種方法有助於擴展性;Web 服務的執行個體可以部署在多個伺服器上。 用戶端請求可以導向到這些執行個體中的任何一個,而且結果應始終相同。 它也會提高可用性;如果某個 Web 伺服器故障,即使伺服器在重啟過程中,請求也可以透過「Azure 流量管理員」路由到另一個執行個體,而不會對用戶端應用程式造成不良影響。
追蹤用戶端並實施節流以降低 DoS 攻擊的可能性
如果某個特定用戶端在特定時間內傳送大量請求,它可能會獨佔服務,進而影響其他用戶端的效能。 為了緩解這個問題,Web API 可以透過追蹤所有入站請求的 IP 地址或記錄每次經過驗證的存取,來監控來自用戶端應用程式的呼叫。 您可以使用這些資訊來限制資源存取。 如果用戶端超過了定義的限制,Web API 可以傳回一個狀態碼為 503 (服務無法使用) 的回應訊息,並包含 Retry-After 標頭,指明用戶端可以在何時傳送下一個請求而不會遭到拒絕。 這種策略有助於降低一組用戶端因造成系統停擺,而發動阻斷服務 (DoS) 攻擊的風險。
謹慎管理永久 HTTP 連線
HTTP 通訊協定支援永久 HTTP 連線,只要它們可用。 HTTP 1.0 規範新增了 Connection: Keep-Alive 標頭,使用戶端應用程式能夠告知伺服器,它可以使用相同的連線來傳送後續請求,而不是開啟新的連線。 如果用戶端在主機定義的時間內不重新使用該連線,連線會自動關閉。 這種行為是 HTTP 1.1 (如 Azure 服務所使用) 的預設設定,因此不需要在訊息中包含 Keep-Alive 標頭。
保持連接打開可以透過減少延遲和網路擁塞來提高回應性,但也可能對可擴展性造成不利影響,因為它會讓不必要的連線保持打開時間過長,限制其他同時執行用戶端的連線能力。 如果用戶端應用程式執行在行動裝置上,保持連線打開也可能影響電池壽命;如果應用程式僅偶爾向伺服器傳送請求,維持一個開啟的連線會導致電池消耗更快。 為了確保 HTTP 1.1 不會使連線成為為永久連線,用戶端可以在訊息中包含 Connection: Close 標頭,以覆寫預設行為。 同樣地,如果伺服器正在處理大量用戶端,它可以在回應訊息中包含 Connection: Close 標頭,這樣可以關閉連線並節省伺服器資源。
注意
永久 HTTP 連線是一個選擇性功能,用於減少與反覆建立通訊通道相關的網路負荷。 無論是 Web API 還是用戶端應用程式,都不應依賴於永久 HTTP 連線始終可用。 不要使用永久 HTTP 連線來實施 Comet 式通知系統;相反地,您應該在 TCP 層使用通訊端 (如果可用,則使用 Web Socket)。 最後,請注意,如果用戶端應用程式透過代理伺服器與伺服器通訊,Keep-Alive 標頭會受到限制;只有用戶端與 Proxy 之間的連線會永久存在,與伺服器的連線則不一定永久存在。
發佈和管理 Web API
若要讓 Web API 可供用戶端應用程式使用,必須將 Web API 部署到主機環境中。 這個環境通常是一個 Web 伺服器,但也可能是其他類型的主機處理序。 在發佈 Web API 時,應考慮以下幾點:
- 所有請求都必須經過驗證和授權,並且必須執行適當的存取控制層級。
- 商業 Web API 可能會受到有關回應時間的各種品質保證請求。 如果負載隨時間變化較大,確保主機環境具備可擴展性是非常重要的。
- 可能需要對請求進行計量,以便進行貨幣化。
- 可能需要調節流量流向 Web API,並對已用完配額的特定用戶端實施節流。
- 法規需求可能會請求對所有請求和回應進行記錄和稽核。
- 為了確保可用性,可能需要監控托管 Web API 伺服器的健康狀況,並在必要時重新啟動伺服器。
將這些問題與實作 Web API 的技術問題分開處理會非常有用。 因此,考慮建立一個作為單獨程序執行的外觀層,並將請求路由到 Web API。 該外觀層可以提供管理作業,並將經過驗證的請求轉發到 Web API。 使用外觀層還可以帶來許多功能上的優勢,包括:
- 作為多個 Web API 的整合點。
- 轉換訊息和翻譯通訊協定,以適應使用不同技術建置的用戶端。
- 快取請求和回應,以減少對托管 Web API 伺服器的負載。
測試 Web API
Web API 應該像其他軟體一樣進行全面測試。 您應該考慮建立單元測試來驗證功能。
Web API 的特性帶來了額外的需求,需要驗證其是否正常運作。 您應特別注意以下幾個方面:
測試所有路由,以確認它們是否叫用正確的作業。 特別注意是否意外傳回 HTTP 狀態碼 405 (不允許的方法),因為這可能表示路由與可以分派到該路由的 HTTP 方法 (GET、POST、PUT、DELETE) 不相符。
將 HTTP 請求傳送到不支援它們的路由,例如向特定資源提交 POST 請求 (POST請求應該只傳送到資源集合)。 在這些情況下,唯一有效的回應應該是狀態碼 405 (不允許)。
確認所有路由是否得到適當的保護,並經過適當的驗證和授權檢查。
注意
某些安全性層面 (如使用者驗證) 很可能是主機環境的責任,而不是 Web API 的責任,但仍然需要將安全測試納入部署過程中。
測試每個作業執行的例外狀況處理,並確認是否將適當且有意義的 HTTP 回應傳回給用戶端應用程式。
確認請求和回應訊息的格式是否正確。 例如,如果 HTTP POST 請求包含以 x-www-form-urlencoded 格式提交的新資源資料,請確認對應的作業是否正確剖析資料、建立資源,並傳回包含新資源詳細資訊的回應,包括正確的 Location 標頭。
確認回應訊息中的所有連結和 URI。 例如,HTTP POST 訊息應該會傳回新建立資源的 URI。 所有 HATEOAS 連結都應該有效。
確認每個作業都會針對不同的輸入組合傳回正確的狀態碼。 例如:
- 如果查詢成功,應傳回狀態碼 200 (成功)
- 如果找不到資源,作業應傳回 HTTP 狀態碼 404 (找不到)。
- 如果用戶端傳送的請求成功刪除了資源,狀態碼應為 204 (沒有內容)。
- 如果用戶端傳送的請求建立了一個新資源,狀態碼應為 201 (已建立)。
注意 5xx 範圍內的意外回應狀態碼。 這些訊息通常由主機伺服器報告,以表示它無法完成有效的請求。
測試用戶端應用程式可以指定的不同請求標頭組合,並確保 Web API 在回應訊息中傳回預期的資訊。
測試查詢字串。 如果作業可以接受選擇性參數 (例如分頁請求),則請測試不同的參數組合和順序。
確認非同步作業是否成功完成。 如果 Web API 支援用於傳回大型二進位物件 (如視訊或音訊) 的串流傳輸,請確保在資料串流傳輸過程中不會封鎖用戶端請求。 如果 Web API 針對長時間執行的資料修改作業實作輪詢,請確認這些作業在進行過程中能夠正確報告其狀態。
您還應該建立並執行效能測試,以檢查 Web API 在壓力下是否運作正常。 您可以使用 Visual Studio Ultimate 建置 Web 效能和負載測試專案。
使用 Azure API 管理
在 Azure 上,考慮使用 Azure API 管理來發佈和管理 Web API。 使用這項功能,您可以生成一個服務,作為一個或多個 Web API 的外觀層。 該服務本身是一個可擴展的 Web 服務,您可以透過 Azure 入口網站來建立和設定。 您可以使用此服務來發佈和管理 Web API,具體步驟如下:
將 Web API 部署到網站、Azure 雲端服務或 Azure 虛擬機器上。
將 API 管理服務連線至 Web API。 傳送到管理 API 的 URL 的請求會對應到 Web API 中的 URI。 相同的 API 管理服務可以將請求路由到多個 Web API。 這使您能夠將多個 Web API 匯總到單一的管理服務中。 同樣地,如果您需要限制或分割不同應用程式可用的功能,則可以從多個 API 管理服務中參考相同的 Web API。
注意
在作為 HTTP GET 請求回應一部分生成的 HATEOAS 連結中,URI 應該參考 API 管理服務的 URL,而不是託管 Web API 的 Web 伺服器的 URL。
對於每個 Web API,請指定該 Web API 公開的 HTTP 作業以及每個作業可以接受的任何選擇性參數。 您還可以設定 API 管理服務是否應該快取從 Web API 接收到的回應,以最佳化對相同資料的重複請求。 記錄每個作業可能生成的 HTTP 回應的詳細資訊。 這些資訊用於生成開發人員文件,因此準確和完整性非常重要。
您可以使用 Azure 入口網站提供的精靈手動定義作業,或者從包含 WADL 或 Swagger 格式定義的文件中匯入作業。
設定 API 管理服務與託管 Web API 的 Web 伺服器之間通訊的安全性設定。 API 管理服務目前支援基本認證、使用憑證的雙向認證,以及 Open Authorization (OAuth) 2.0 使用者授權。
建立產品。 產品是發佈單位;您會將之前連接到管理服務的 Web API 新增到該產品中。 產品發佈後,Web API 可供開發人員使用。
注意
在發佈產品之前,您還能定義可以存取該產品的使用者群組,並將使用者新增到這些群組中。 這使您可以控制哪些開發人員和應用程式可以使用該 Web API。 如果 Web API 需要經過核准,開發人員必須向產品管理員傳送請求才能獲得存取權。 管理員可以核准或拒絕開發人員的存取請求。 如果情況發生變化,也可以封鎖現有的開發人員。
為每個 Web API 設定原則。 原則管理著以下方面:是否允許跨網域呼叫、如何驗證用戶端、是否透明地在 XML 和 JSON 資料格式之間進行轉換、是否限制來自特定 IP 範圍的呼叫、使用配額,以及是否限制呼叫頻率。 原則可以全域套用至整個產品、產品中的單一 Web API,或 Web API 中的個別作業。
如需詳細資訊,請參閱「API 管理文件」。
提示
Azure 提供了 Azure 流量管理員,這使您可以實現故障轉移和負載均衡,並減少託管在不同地理位置的多個網站執行個體之間的延遲。 您可以將 Azure 流量管理員與 API 管理服務搭配使用;API 管理服務可以透過 Azure 流量管理員將請求路由到網站的執行個體。 如需詳細資訊,請參閱流量管理員路由方法。
在這種結構中,如果您為網站使用自訂 DNS 名稱,則應該為每個網站設定適當的 CNAME 記錄,以指向 Azure 流量管理員網站的 DNS 名稱。
支援用戶端開發人員
開發人員在建構用戶端應用程式時,通常需要有關如何存取 Web API 的資訊,以及描述 Web 服務和用戶端應用程式之間不同請求和回應的參數、資料類型、傳回類型和傳回碼的文件。
記錄 Web API 的 REST 作業
Azure API 管理服務包含一個開發人員入口網站,該網站描述了 Web API 所公開的 REST 作業。 當產品已經發佈後,它會出現在這個入口網站上。 開發人員可以使用這個入口網站申請存取權限;隨後管理員可以核准或拒絕該請求。 如果開發人員獲得核准,他們將獲得訂閱金鑰,用來驗證他們所開發用戶端應用程式的呼叫。 此金鑰必須在每次 Web API 呼叫時提供,否則請求將會遭到拒絕。
此入口網站也提供:
- 產品的文件應列出其所公開的作業、所需的參數,以及可能傳回的不同回應。 請注意,這些資訊是根據「使用 Microsoft Azure API 管理服務發佈 Web API」部分清單中,步驟 3 所提供的詳細資訊生成的。
- 程式碼片段,顯示了如何使用多種語言 (包括 JavaScript、C#、Java、Ruby、Python 和 PHP) 叫用作業。
- 開發人員主控台,可讓開發人員傳送 HTTP 請求以測試產品中的每個作業,並查看結果。
- 一個頁面,開發人員可以在這裡報告任何發現的問題。
Azure 入口網站可讓您自訂開發人員入口網站,改變樣式和版面配置,以符合組織的品牌形象
實作用戶端 SDK
建置一個叫用 REST 請求以存取 Web API 的用戶端應用程式需要編寫大量程式碼,包括建置每個請求並進行適當格式化、將請求傳送到託管 Web 服務的伺服器,以及剖析回應以確定請求是否成功或失敗,並提取任何傳回的資料。 為了使用戶端應用程式不受這些問題的影響,您可以提供一個 SDK,該 SDK 可封裝 REST 介面,並將這些低層次的細節抽象化,轉化為一組更具功能性的方法。 用戶端應用程式可使用這些方法,這些方法會透明地將呼叫轉換為 REST 請求,然後將回應轉換回方法傳回值。 這是一種常見的技術,許多服務 (包括 Azure SDK) 都會實施這種技術。
建立用戶端 SDK 是一項相當龐大的工作,因為它需要一致地實施並仔細測試。 然而,這個過程中的許多步驟都可以進行機械化處理,許多供應商提供的工具可以自動化完成這些工作。
監控 Web API
根據您發佈和部署 Web API 的方式,您可以直接監控 Web API,或透過分析通過 API 管理服務的流量來收集使用情況和健康資訊。
直接監控 Web API
如果您使用 ASP.NET Web API 範本 (無論是作為 Web API 專案還是 Azure 雲端服務中的 Web 角色) 和 Visual Studio 2013 來實施 Web API,您可以透過使用 ASP.NET Application Insights 來收集可用性、效能和使用方式資料。 Application Insights 是一個套件,能夠透明地追蹤和記錄 Web API 部署到雲端時的請求和回應資訊;一旦安裝並設定了該套件,您不需要修改 Web API 中的任何程式碼即可使用它。 當您將 Web API 部署到 Azure 網站時,系統會檢查所有流量,並收集以下統計資料:
- 伺服器回應時間。
- 伺服器請求的數量以及每個請求的詳細資訊。
- 按照平均回應時間排序的最慢請求。
- 任何失敗請求的詳細資訊。
- 由不同瀏覽器和使用者代理程式發起的工作階段數量。
- 最常查看的頁面 (主要適用於 Web 應用程式,而不是 Web API)。
- 存取 Web API 的不同使用者角色。
您可以在 Azure 入口網站中即時查看此資料。 您還可以建立 Web 測試來監控 Web API 的健康狀況。 Web 測試會定期向 Web API 中指定的 URI 傳送請求並擷取回應。 您可以指定成功回應的定義 (例如 HTTP 狀態碼 200),如果請求未傳回此回應,您可以設定警報以通知管理員。 如有必要,如果 Web API 失敗,管理員可以重新啟動託管 Web API 的伺服器。
如需詳細資訊,請參閱「Application Insights - 開始使用 ASP.NET」。
透過 API 管理服務監控 Web API
如果您透過 API 管理服務發佈了您的 Web API,Azure 入口網站上的 API 管理頁面會包含一個儀表板,讓您查看服務的整體效能。 [分析] 頁面可讓您深入了解產品的使用細節。 此頁面包含以下索引標籤:
- 使用情況。 此索引標籤提供提供有關 API 呼叫數量,以及一段時間內處理這些呼叫所用頻寬的相關資訊。 您可以依產品、API 和作業來篩選使用量詳細資料。
- 健康情況。 此索引標籤可讓您查看 API 請求的結果 (傳回的 HTTP 狀態碼)、快取策略的有效性、API 回應時間以及服務回應時間。 同樣地,您可以按照產品、API 和作業來篩選健康情況資料。
- 活動。 此索引標籤提供有關每個產品、Web API 和作業的成功呼叫數量、失敗呼叫數量、封鎖的呼叫數量、平均回應時間以及回應時間的文字摘要。 此頁面也會列出每個開發人員發出的呼叫數量。
- 速覽。 此索引標籤會顯示效能資料的摘要,包括負責進行最多 API 呼叫的開發人員,以及收到這些呼叫的產品、Web API 和作業。
您可以使用這些資訊來確定是否某個特定的 Web API 或作業造成了瓶頸,並在必要時擴展主機環境並增加更多伺服器。 您還可以確定是否有一個或多個應用程式使用了不成比例的資源,並採取適當的原則來設定配額和限制呼叫頻率。
注意
您可以更改已發佈產品的詳細資訊,這些更改會立即生效。 例如,您可以新增或移除 Web API 中的作業,而不需要重新發佈包含該 Web API 的產品。
下一步
- ASP.NET Web API OData 包含了使用 ASP.NET 實施 OData Web API 的範例和進一步資訊。
- 「在 Web API 和 Web API OData 中引入批次支援」描述了如何使用 OData 在 Web API 中實施批次作業。
- Jonathan Oliver 的部落格文章「Idempotency Patterns」提供了冪等性的概述,以及它如何與資料管理作業相關聯。
- W3C 網站上的狀態碼定義包含了所有 HTTP 狀態碼及其描述的完整清單。
- 「使用 WebJobs 執行背景工作」提供了有關使用 WebJobs 執行背景作業的資訊和範例。
- 「Azure 通知中樞通知使用者」中展示了如何使用 Azure 通知中樞向用戶端應用程式推送非同步回應。
- 「API 管理」說明如何發佈一個產品,以提供對 Web API 的受控和安全存取。
- 「Azure API 管理 REST API 參考」說明如何使用 API 管理 REST API 來建置自訂管理應用程式。
- 「Traffic Manager 路由方法」概述了如何使用 Azure 流量管理員在託管 Web API 網站的多個執行個體之間實現請求負載平衡。
- 「Application Insights - 開始使用 ASP.NET」提供了有關在 ASP.NET Web API 專案中安裝和設定 Application Insights 的詳細資訊。