在我的REST API中,我应该使用信封吗?如果我在一个地方使用它,我应该总是使用它吗?

时间:2012-04-03 07:45:01

标签: rest

我正致力于构建RESTful Web服务。我已经阅读了为每种机制使用HTTP的原则,只要它会占用你,而且大多数时候,就像获取资源一样,它运行得很好。

但是当我需要发布某种新条目时,为了清晰和健壮,无论客户端做什么,我都想提供新条目可能失败的特定验证错误。此外,存在特定错误,例如,用于创建新用户的数据完全有效,但可以采用昵称或电子邮件地址。简单地返回409 Conflict并不能详细说明昵称或电子邮件地址的详细信息。

因此,解决这个问题并不是火箭科学:记录一堆特定的错误代码并返回一个有错误的对象:

{ errors: [4, 8, 42] }

这意味着在请求失败的情况下,我没有返回资源或其密钥,正如我可能期望的那样,REST哲学。同样,当我返回许多资源时,我必须以某种方式在数组中构造它们。

所以我的问题是:如果我将信封标准化以用于每个请求,我是否仍然会提供一个表现良好的RESTful Web服务,例如,总是像{{1}这样的对象}?

我之前已经构建了使用此类型的RPC样式的Web服务,但我不想做出几乎是REST"的东西。如果能够成为REST的任何一点,我希望尽可能保持良好的行为。

如果答案是"地狱没有",我认为它可能是,我想听听它是否至少正确地解决了验证问题,对此有什么好的参考解决问题的方法可能是,因为我发现的大多数指南都只详细说明了这些简单的案例。

5 个答案:

答案 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());