开篇语与本主题无关,我非常尊敬的一个导师好几天没有见到人,今天听说原来是病了,人也出现了。在此祝愿他身体康健,长命百岁!
使用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