我正在努力决定如何设计restful url。我完全赞成使用带有名词而不是动词的url的restful方法,我不知道如何做到这一点。

我们正在创建一个服务来实现一个金融计算器。计算器接受一堆参数,我们将通过CSV文件上传这些参数。用例包括:

上传新参数 获取最新参数 获取给定业务日期的参数 激活一组参数 验证一组参数

我收集的restful方法将有以下类型的url:

/parameters
/parameters/12-23-2009

你可以通过以下方法实现前三个用例:

在POST请求中包含参数文件 第一个URL的GET 第二个URL的GET

但是在没有动词的情况下如何完成第4和第5个用例呢?你不需要这样的url吗:

/parameters/ID/activate
/parameters/ID/validate

??


当前回答

我建议使用以下Meta资源和方法。

使参数激活和/或验证它们:

> PUT /parameters/<id>/meta HTTP/1.1
> Host: example.com
> Content-Type: application/json
> Connection: close
>
> {'active': true, 'require-valid': true}
>
< HTTP/1.1 200 OK
< Connection: close
<

检查参数是否激活且有效:

> GET /parameters/<id>/meta HTTP/1.1
> Host: example.com
> Connection: close
>
< HTTP/1.1 200 OK
< Content-Type: application/json
< Connection: close
<
< {
<     'active': true,
<     'require-valid': true,
<     'valid': {'status': false, 'reason': '...'}
< }
<

其他回答

好的URI设计的一般原则:

Don't use query parameters to alter state Don't use mixed-case paths if you can help it; lowercase is best Don't use implementation-specific extensions in your URIs (.php, .py, .pl, etc.) Don't fall into RPC with your URIs Do limit your URI space as much as possible Do keep path segments short Do prefer either /resource or /resource/; create 301 redirects from the one you don't use Do use query parameters for sub-selection of a resource; i.e. pagination, search queries Do move stuff out of the URI that should be in an HTTP header or a body

(注意:我没有说“rest式URI设计”;uri在REST中本质上是不透明的。)

HTTP方法选择的一般原则:

Don't ever use GET to alter state; this is a great way to have the Googlebot ruin your day Don't use PUT unless you are updating an entire resource Don't use PUT unless you can also legitimately do a GET on the same URI Don't use POST to retrieve information that is long-lived or that might be reasonable to cache Don't perform an operation that is not idempotent with PUT Do use GET for as much as possible Do use POST in preference to PUT when in doubt Do use POST whenever you have to do something that feels RPC-like Do use PUT for classes of resources that are larger or hierarchical Do use DELETE in preference to POST to remove resources Do use GET for things like calculations, unless your input is large, in which case use POST

使用HTTP设计web服务的一般原则:

Don't put metadata in the body of a response that should be in a header Don't put metadata in a separate resource unless including it would create significant overhead Do use the appropriate status code 201 Created after creating a resource; resource must exist at the time the response is sent 202 Accepted after performing an operation successfully or creating a resource asynchronously 400 Bad Request when someone does an operation on data that's clearly bogus; for your application this could be a validation error; generally reserve 500 for uncaught exceptions 401 Unauthorized when someone accesses your API either without supplying a necessary Authorization header or when the credentials within the Authorization are invalid; don't use this response code if you aren't expecting credentials via an Authorization header. 403 Forbidden when someone accesses your API in a way that might be malicious or if they aren't authorized 405 Method Not Allowed when someone uses POST when they should have used PUT, etc 413 Request Entity Too Large when someone attempts to send you an unacceptably large file 418 I'm a teapot when attempting to brew coffee with a teapot Do use caching headers whenever you can ETag headers are good when you can easily reduce a resource to a hash value Last-Modified should indicate to you that keeping around a timestamp of when resources are updated is a good idea Cache-Control and Expires should be given sensible values Do everything you can to honor caching headers in a request (If-None-Modified, If-Modified-Since) Do use redirects when they make sense, but these should be rare for a web service

关于你的具体问题,第4和第5点应该用POST。这些操作属于上面的“类rpc”准则。对于#5,记住POST不一定要使用Content-Type: application/x-www-form-urlencoded。这也可以是一个JSON或CSV有效负载。

编辑:URI确实会阻止GET请求保持幂等性。


然而,对于验证,使用HTTP状态代码来通知请求的有效性(创建一个新的或修改一个现有的“参数”)将适合Restful模型。

如果提交的数据无效,并且在重新提交请求之前必须修改请求,则返回400坏请求状态码(HTTP/1.1状态码)。

不过,这依赖于在提交时进行验证,而不是像在用例中那样推迟。其他答案都有适合该场景的解决方案。

我建议使用以下Meta资源和方法。

使参数激活和/或验证它们:

> PUT /parameters/<id>/meta HTTP/1.1
> Host: example.com
> Content-Type: application/json
> Connection: close
>
> {'active': true, 'require-valid': true}
>
< HTTP/1.1 200 OK
< Connection: close
<

检查参数是否激活且有效:

> GET /parameters/<id>/meta HTTP/1.1
> Host: example.com
> Connection: close
>
< HTTP/1.1 200 OK
< Content-Type: application/json
< Connection: close
<
< {
<     'active': true,
<     'require-valid': true,
<     'valid': {'status': false, 'reason': '...'}
< }
<

也许是这样的:

PUT /parameters/activation HTTP/1.1
Content-Type: application/json; encoding=UTF-8
Content-Length: 18

{ "active": true }

我有点难过地看到,10多年过去了,还没有答案真正说明OP中要求的东西如何在REST架构中设计,因此我觉得现在有必要这样做。

首先,什么是REST?首字母缩写REST或REST代表“具象状态传输”,并定义了以某种表示格式交换资源状态。表示格式适用于协商的媒体类型。在application/html的情况下,表示格式可能是在浏览器中呈现的html格式的文本内容流,可能是在应用一些样式表格式将某些元素定位到某些位置之后。

REST原则上是我们都知道的可浏览Web的泛化,尽管它针对的是各种应用程序,而不仅仅是浏览器。因此,从设计上讲,适用于Web的相同概念也适用于REST体系结构。像如何以“RESTful”方式实现某些内容这样的问题解决了如何在Web页面上实现某些内容,然后将相同的概念应用到应用程序层的问题。

基于Web的计算器通常从一些“页面”开始,允许您在将输入的数据发送到服务器之前输入一些值进行计算。在HTML中,这通常是通过HTML <form>元素来实现的,它告诉客户端要设置的可用参数、要发送请求的目标位置以及在发送输入数据时应用的表示格式。它可以是这样的:

<html>
  <head>
    ...
  </head>
  <body>
    <form action="/../someResource" method="post" enctype="application/x-www-form-urlencoded">
      <label for="firstNumber">First number:</label>
      <input type="number" id="firstNumber" name="firstNumber"/>

      <label for="secondNumber">Second number:</label>
      <input type="number" id="secondNumber" name="secondNumber"/>

      <input type="submit" value="Add numbers"/>
    </form>
  </body>
</html>

The sample above i.e. states that there are two input fields that can be filled out either by the user or by some other automata, and that upon invoking the submit input element the browser takes care of formatting the input data into a application/x-www-form-urlencoded representation format that is sent to the mentioned target location via the specified HTTP request method, POST in this case. If we enter 1 into the firstNumber input field and 2 into the secondNumber input field, the browser will generate a representation of firstNumber=1&secondNumber=2 and send this as the body payload of the actual request to the target resource.

因此,发送给服务器的原始HTTP请求可能是这样的:

POST /../someResource
Host: www.acme.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 28
Accept: application/html

firstNumber=1&secondNumber=2

服务器可以执行计算,并使用包含计算结果的进一步HTML页面进行响应,因为请求表明客户端理解这种格式。

正如Breton已经指出的,没有所谓的“RESTful”URL或URI。URI/URL是它自己的东西,不应该向客户端/用户传递任何含义。在上面的计算器示例中,用户根本不感兴趣的是将数据发送到哪里,它只感兴趣的是在触发提交输入字段时发送请求。服务器应该已经提供了执行任务所需的所有信息。

A browser also might not be aware of that the request is actually feeding a calculator with some input parameters, it could as well be some kind of an order form that returns just the next form representation to continue the ordering process or some totally different kind of resource. It simply performs what the HTML spec demands in such a case and it couldn't care less what the server is actually doing. This concept enables a browser to use the same representation format to do all kind of things such as ordering some stuff from your preferred online shop, chatting with your best friends, signing into an online account and so on.

The affordance of certain elements, such in the submit input field case that is usually rendered as button, defines what you should to with it. In the case of a button or a link it basically tells you to click it. Other elements may convey different affordances. Such an affordance can also be expressed via link-relations as i.e. with preload annotated links that basically tell a client that it can already load the content of the linked resource in the background as the user will most likely grab this content next. Such link relations should of course be standardized or follow the extension mechanism for relation types as defined by Web linking.

These are the fundamental concept that are used on the Web and that should also be used in a REST architecture. According to "Uncle Bob" Robert C. Martin an architecture is about intent and the intention behind the REST architecture is the decoupling of clients from servers to allow servers to evolve freely in future without having to fear them breaking clients. This unfortunately requires a lot of discipline as it is so easy to introduce coupling or to add quick-fix solutions to get the job done and move on. As Jim Webber pointed out in a REST architecture you, as a service provider, should attempt to design an domain application protocol similar to a text based computer game of the 70s that clients will follow through until they reached the end of a process.

What plenty of so-called "REST" APIs unfortunately do in reality is everything but that. You see the exchange of mostly JSON based data that is specified in an API specific external documentation that is usually hard to dynamically integrate on the fly. The format how a request needs to look like are also hardcoded into the external documentation which lead to plenty of implementation interpreting URIs to return predefined typs instead of using some common representation format that is negotiated upfront. This prevents servers from changing as clients now expect to receive a certain data format (note not representation format!) for predefined URIs. This custom data format exchange furthermore prevents clients from interacting with other APIs as the "data format" is usually tide to a specific API. We know this concept from the past from RPC technologies such as Corba, RMI or SOAP which we condemn as somehow evil, even though Peppol moved to it again by replacing AS2 with AS4 as default transfer protocol as of recently.

In regards to the actual question asked, sending data as csv file is nothing different than using application/x-www-form-urlencoded representation or similar stuff. Jim Webber made it clear that after all HTTP is just a transport protocol whose application domain is the transfer of documents over the Web. Client and server should at least both support text/csv as defined in RFC 7111. This CSV file could be generated as a consequence of processing a media type that defines form elements, a target element or attribute to send the request to as well as the HTTP method to perform the upload of the configuration.

有一些媒体类型支持表单,如HTML, HAL forms, halform, ion或Hydra。不过,我目前还不知道有一种媒体类型可以自动将输入数据直接编码为文本/csv,因此可能需要在IANA的媒体类型注册表中定义和注册。

The upload and download of the complete parameter set shouldn't be an issue I guess. As mentioned before, the target URI is not of relevance as a client will just use the URI to retrieve new content to process. Filtering by business date should also not be to difficult. Here the server should however the client with all the possibilities the client simply can chose from. In recent years GraphQL and RestQL evolved which introduce an SQL like language that can be targeted at a certain endpoint to get a filtered response. However, in a true REST sense this violates the idea behind REST as a) GraphQL i.e. only uses a single endpoint which somehow prevents optimal usage of caching and b) requires the knowledge of available fields upfrong, which may lead to introducing a coupling of clients to the base data model of the resource.

Activating or deactivating certain configuration parameters is simply a matter of triggering the hypermedia controls that provide this affordance. In HTML forms this could be a simple checkbox or a multi-line selection in a list or that kind. Depending on the form and what method it defines it could then potentially send the whole configuration via PUT or be smart about the changes done and only perform a partial update via PATCH. The latter one requires basically a calculaton of the change representation to the one updated and feed the server with the required steps to tranform the current representation into the desired one. According to the PATH specification this has to be done within a transaction so that either all or none of the steps are applied.

HTTP允许并鼓励服务器在应用更改之前预先验证接收到的请求。对于PUT,规范声明:

An origin server SHOULD verify that the PUT representation is consistent with any constraints the server has for the target resource that cannot or will not be changed by the PUT. This is particularly important when the origin server uses internal configuration information related to the URI in order to set the values for representation metadata on GET responses. When a PUT representation is inconsistent with the target resource, the origin server SHOULD either make them consistent, by transforming the representation or changing the resource configuration, or respond with an appropriate error message containing sufficient information to explain why the representation is unsuitable. The 409 (Conflict) or 415 (Unsupported Media Type) status codes are suggested, with the latter being specific to constraints on Content-Type values. For example, if the target resource is configured to always have a Content-Type of "text/html" and the representation being PUT has a Content-Type of "image/jpeg", the origin server ought to do one of: a. reconfigure the target resource to reflect the new media type; b. transform the PUT representation to a format consistent with that of the resource before saving it as the new resource state; or, c. reject the request with a 415 (Unsupported Media Type) response indicating that the target resource is limited to "text/html", perhaps including a link to a different resource that would be a suitable target for the new representation. HTTP does not define exactly how a PUT method affects the state of an origin server beyond what can be expressed by the intent of the user agent request and the semantics of the origin server response. ...

To sum this post up, you should either use an existing media type that allows you to teach a client about the required or supported input parameters, the target location to send the request to, the operation to use as well as the media-type the request has to be formatted in, or define your own one that you register with IANA. The latter might be necessary if you want to convert the input to text/csv and then upload the CSV representation to the server. The validation should occur before the changes are applied to the resource. The actual URI should not be of relevance to clients other than to determine where to send the request to and as such can be freely chosen by you, the service implementor. By following these steps you pretty much gain the freedom to change your server side at any time and clients will not break as a consequence if they support the used media-types.