開篇語與本主題無關,我非常尊敬的一個導師好幾天沒有見到人,今天聽說原來是病了,人也出現了。在此祝願他身體康健,長命百歲!
使用ASP.NET MVC構建RESTful服務時,想到一個問題:在使用POST,PUT,DELETE方法發送請求時伺服器端如何回傳響應?如果在操作過程中發生了異常情況,如何通知用戶端?
帶著這個問題,嘗試著構建了一個ActionResult的衍生類別:
namespace System.Web.Mvc{ public class HttpStatusResult : ActionResult { public HttpStatusResult() : this(HttpStatusCode.OK) { } public HttpStatusResult(HttpStatusCode statusCode) : this(statusCode, "") { } public HttpStatusResult(HttpStatusCode statusCode, string content) { this.StatusCode = statusCode; this.Content = content; } public string Content { get; set; } public HttpStatusCode StatusCode { get; set; } public override void ExecuteResult(ControllerContext context) { context.HttpContext.Response.StatusCode = (int)StatusCode; context.HttpContext.Response.ContentType = "text/xml, charset=\"utf-8\""; if (string.IsNullOrEmpty(context.HttpContext.Request.Headers["Accept-Encoding"]) == false) { string acceptEncoding = context.HttpContext.Request.Headers["Accept-Encoding"].ToLower(); if (acceptEncoding.Contains("gzip")) { context.HttpContext.Response.Filter = new GZipStream(context.HttpContext.Response.Filter, CompressionMode.Compress); context.HttpContext.Response.AppendHeader("Content-Encoding", "gzip"); } } using (var writer = XmlWriter.Create(context.HttpContext.Response.Output)) { this.ResponseContent.WriteTo(writer); } } private XmlDocument ResponseContent { get { XmlDocument xml = new XmlDocument(); xml.LoadXml(string.Format("<?xml version=\"1.0\"?><response xmlns=\"http://schema.cleversoft.com/webutil/httpstatusresult/1.0/\"><content>{0}</content></response>", this.Content)); return xml; } } }}
使用這個類,可以向請求的用戶端返回一個Http Status資訊,如果操作正常,則視情況使用200,202,204。情況是什嗎?據HTTP/1.1定義:
POST:
The action performed by the POST method might not result in a resource that can be identified by a URI. In this case, either 200 (OK) or 204 (No Content) is the appropriate response status, depending on whether or not the response includes an entity that describes the result.
If a resource has been created on the origin server, the response SHOULD be 201 (Created) and contain an entity which describes the status of the request and refers to the new resource, and a Location header.
PUT:
If a new resource is created, the origin server MUST inform the user agent via the 201 (Created) response. If an existing resource is modified, either the 200 (OK) or 204 (No Content) response codes SHOULD be sent to indicate successful completion of the request.
DELETE:
A successful response SHOULD be 200 (OK) if the response includes an entity describing the status, 202 (Accepted) if the action has not yet been enacted, or 204 (No Content) if the action has been enacted but the response does not include an entity.
總而言之,看你真正做了什麼操作,返回一個與之匹配的HTTP Status Code。在此過程中也有可能遇到需要使用3XX系統的情況,不過我要做的服務中還沒有這麼複雜的規劃,據以前的學習發現,發現Google的服務中3XX用的比較多,整的比較複雜。
上面這些只僅僅是發送POST,PUT,DELETE成功的響應,如果僅是為了這個原因,似乎就沒有必要去做HttpStatusResult類型了。自已去實現RESTful服務,異常處理肯定是必須要考慮的內容。在我現在的設想中,如果一些操作發生了伺服器端的錯誤,那就得使用HttpStatusResult類型來向用戶端傳遞錯誤資訊,即HttpStatusResult.Content屬性。使用方法如下:
return new HttpStatusResult(System.Net.HttpStatusCode.InternalServerError, "Exception Message.");
在發送異常時我使用了InternalServerError(500)。
問題來了,使用了500以後,用戶端並不知道這是你“處心積慮”的發出來了,它只知道伺服器端出錯了,然後在WebRequest.GetResponse()時直接拋出異常了,而且沒有返回WebResponse,那我們使用Content返回的異常資訊豈不成了笑話了?所幸,M$也沒有這麼笨,大家可以仔細看下WebException的定義就會發現,原來使用WebException.Response就可以獲得剛才伺服器返回的響應,當然這時WebException.Status的狀態必須不能為ConnectFailure(值為2),這個Response屬性也必須不可為空(廢話),接下來,你就使用XmlReader從ResponseStream中拿資料吧,資料結構的定義可以視個人喜好定義:)。這裡比較推薦的方法是在響應的xml中定義一個NameSpace,為什麼nia?因為這樣你就可以確定這是你自已,而不是他人發來的響應。
最後,轉一下Http Status Code定義:
100 Continue
101 Switching Protocols
102 Processing
200 OK
201 Created
202 Accepted
203 Non-Authoritative Information
204 No Content
205 Reset Content
206 Partial Content
207 Multi-Status
226 IM Used
300 Multiple Choices
301 Moved Permanently
302 Found
303 See Other
304 Not Modified
305 Use Proxy
306 (Unused)
307 Temporary Redirect
400 Bad Request
401 Unauthorized
402 Payment Required
403 Forbidden
404 Not Found
405 Method Not Allowed
406 Not Acceptable
407 Proxy Authentication Required
408 Request Timeout
409 Conflict
410 Gone
411 Length Required
412 Precondition Failed
413 Request Entity Too Large
414 Request-URI Too Long
415 Unsupported Media Type
416 Requested Range Not Satisfiable
417 Expectation Failed
418 I'm a teapot
422 Unprocessable Entity
423 Locked
424 Failed Dependency
425 (Unordered Collection)
426 Upgrade Required
500 Internal Server Error
501 Not Implemented
502 Bad Gateway
503 Service Unavailable
504 Gateway Timeout
505 HTTP Version Not Supported
506 Variant Also Negotiates
507 Insufficient Storage
510 Not Extended