我正致力于构建RESTful Web服务。我已经阅读了为每种机制使用HTTP的原则,只要它会占用你,而且大多数时候,就像获取资源一样,它运行得很好。
但是当我需要发布某种新条目时,为了清晰和健壮,无论客户端做什么,我都想提供新条目可能失败的特定验证错误。此外,存在特定错误,例如,用于创建新用户的数据完全有效,但可以采用昵称或电子邮件地址。简单地返回409 Conflict
并不能详细说明昵称或电子邮件地址的详细信息。
因此,解决这个问题并不是火箭科学:记录一堆特定的错误代码并返回一个有错误的对象:
{ errors: [4, 8, 42] }
这意味着在请求失败的情况下,我没有返回资源或其密钥,正如我可能期望的那样,REST哲学。同样,当我返回许多资源时,我必须以某种方式在数组中构造它们。
所以我的问题是:如果我将信封标准化以用于每个请求,我是否仍然会提供一个表现良好的RESTful Web服务,例如,总是像{{1}这样的对象}?
我之前已经构建了使用此类型的RPC样式的Web服务,但我不想做出几乎是REST"的东西。如果能够成为REST的任何一点,我希望尽可能保持良好的行为。
如果答案是"地狱没有",我认为它可能是,我想听听它是否至少正确地解决了验证问题,对此有什么好的参考解决问题的方法可能是,因为我发现的大多数指南都只详细说明了这些简单的案例。
答案 0 :(得分:24)
我会说“好吧!” (与此处有人说“地狱不”)的信封相反!总有一些额外的信息需要从某些端点发送。例如,分页,errorMessages,debugMessages。 facebook如何做到这一点的例子:
Response from get friends request
{
"data": [
{
"id": "68370",
"name": "Magnus"
},
{
"id": "726497",
"name": "Leon"
},
{
"id": "57034",
"name": "Gonçalo"
}
],
"paging": {
"next": "https://graph.facebook.com/v2.1/723783051/friends?fields=id,name&limit=5000&offset=5000&__after_id=enc_AeyGEGXHV9pJmWq2OQeWtQ2ImrJmkezZrs6z1WXXdz14Rhr2nstGCSLs0e5ErhDbJyQ"
},
"summary": {
"total_count": 200
}
}
在这里,我们分配了下一个请求获取下一个用户块的链接以及一个摘要,其中包含可以获取的朋友总数。然而,他们并不总是发送这个信封,有时数据可以直接在身体的根部。始终以相同的方式发送数据使客户端更容易解析数据,因为它们可以对所有端点执行相同的操作。客户端如何处理信封响应的一个小例子:
public class Response<T> {
public T data;
public Paging paging;
public Summary summary;
}
public class Paging {
public String next;
}
public class Summary {
public int totalCount;
}
public class WebRequest {
public Response<List<User>> getFriends() {
String json = FacebookApi.getFriends();
Response<List<User>> response = Parser.parse(json);
return response;
}
}
然后,只需将List更改为端点返回的数据,即可将此Response对象用于所有端点。
答案 1 :(得分:15)
HTTP 你的信封。通过返回4 **错误代码,您正在做正确的事。
话虽如此,在响应中设置描述性主体并没有错 - 事实上HTTP RFC中的,大多数HTTP错误代码都主张您确实返回错误原因的描述发生了。参见403,例如:
如果请求方法不是HEAD并且服务器希望公开为什么请求尚未完成,那么<strong>应该描述实体拒绝的原因。
所以你可以继续使用响应的主体来更详细地描述错误。如果您不确定要使用的特定HTTP错误响应(例如多个错误),并且您知道用户不应该重复请求,我通常会回到使用400
答案 2 :(得分:3)
我认为像REST中的许多特定情况一样,这取决于您。我在网上看一些例子。例如,当您访问WWW中不存在的网页或URL时,您通常会获得404页面和HTML页面,该页面通常具有超媒体到某些资源。这个超媒体是服务认为你想要的东西,或者可能是主页{bookmark url}。在机器到机器中,REST可能没有使用HTML作为媒体类型,但您仍然可以返回一个资源:1)提供有关错误的详细信息; 2)为有效资源提供超媒体
409是一个错误代码,你在野外WWW中看不到太多,所以你有点自己。我使用404作为并行并返回错误资源,就像你正在做的那样,并且还将超媒体返回到导致409首先出现的资源。这样,如果他们想要创造引起冲突的东西,他们就可以得到它。
我们确实标准化了什么错误资源,以便客户端知道如何使用错误。这当然是通过遵循资源中的rel来记录的。
在您的“昵称或电子邮件地址”的特定情况下,我可以看到使用400或409,因为这只是资源的一条信息。
此外,我们没有1个信封。我们使用http://stateless.co/hal_specification.html,资源是他们要求的或错误。
HTH
答案 3 :(得分:2)
如果通过“我为每个请求标准化了一个信封”,那么你的意思就是每个请求,而不仅仅是你描述的那个请求,我会说不要这样做。在REST中,我们尝试使用HTTP juts,就像Web使用它一样,而不是像SOAP一样构建一个全新的专有协议。这种方法使REST简单易用。如果你有兴趣,我会在这里提出更多相关的想法:
http://theamiableapi.com/2012/03/04/rest-and-the-art-of-protocol-design/
这就是说,可以使用HTTP错误代码返回详细的错误描述。你第一直觉,返回409和其他错误代码听起来对我很好。 409比通用400更好的原因是客户端代码中的错误处理路径更清晰。一些不相关的错误可能导致400,所以如果你使用400,你将需要检查是否有一个实体返回,它是什么格式等。
答案 4 :(得分:0)
由于需要封装每个WebApi操作的开销,我过去常常反对包含响应的想法。
然后我stumbled upon this article以完整的方式做到这一点,不需要任何额外的努力,它只是工作
public class WrappingHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var response = await base.SendAsync(request, cancellationToken);
return BuildApiResponse(request, response);
}
private static HttpResponseMessage BuildApiResponse(HttpRequestMessage request, HttpResponseMessage response)
{
object content;
string errorMessage = null;
if (response.TryGetContentValue(out content) && !response.IsSuccessStatusCode)
{
HttpError error = content as HttpError;
if (error != null)
{
content = null;
errorMessage = error.Message;
#if DEBUG
errorMessage = string.Concat(errorMessage, error.ExceptionMessage, error.StackTrace);
#endif
}
}
var newResponse = request.CreateResponse(response.StatusCode, new ApiResponse(response.StatusCode, content, errorMessage));
foreach (var header in response.Headers)
{
newResponse.Headers.Add(header.Key, header.Value);
}
return newResponse;
}
}
自定义包装类 [DataContract]
public class ApiResponse
{
[DataMember]
public string Version { get { return "1.2.3"; } }
[DataMember]
public int StatusCode { get; set; }
[DataMember(EmitDefaultValue = false)]
public string ErrorMessage { get; set; }
[DataMember(EmitDefaultValue = false)]
public object Result { get; set; }
public ApiResponse(HttpStatusCode statusCode, object result = null, string errorMessage = null)
{
StatusCode = (int)statusCode;
Result = result;
ErrorMessage = errorMessage;
}
}
在App_Start中的WebApiConfig.cs中
config.MessageHandlers.Add(new WrappingHandler());