Call a Server-side Method on a Resource in a RESTful Way

后端 未结 8 1726
小蘑菇
小蘑菇 2020-12-02 03:14

Keep in mind I have a rudimentary understanding of REST. Let\'s say I have this URL:

http://api.animals.com/v1/dogs/1/

And now, I want to m

相关标签:
8条回答
  • 2020-12-02 03:48

    See my new answer -- it contradicts this one and explains REST and HTTP more clearly and accurately.

    Here's a recommendation that happens to be RESTful but is certainly not the only option. To start barking when the service receives the request:

    POST /v1/dogs/1/bark-schedule HTTP/1.1
    ...
    {"token": 12345, "next": 0, "frequency": 10}
    

    token is an arbitrary number that prevents redundant barks no matter how many times this request is sent.

    next indicates the time of the next bark; a value of 0 means 'ASAP'.

    Whenever you GET /v1/dogs/1/bark-schedule, you should get something like this, where t is the time of the last bark and u is t + 10 minutes:

    {"last": t, "next": u}

    I highly recommend that you use the same URL to request a bark that you use to find out about the dog's current barking state. It's not essential to REST, but it emphasizes the act of modifying the schedule.

    The appropriate status code is probably 205. I'm imagining a client that looks at the current schedule, POSTs to the same URL to change it, and is instructed by the service to give the schedule a second look to prove that it has been changed.

    Explanation

    REST

    Forget about HTTP for a moment. It's essential to understand that a resource is a function that takes time as input and returns a set containing identifiers and representations. Let's simplify that to: a resource is a set R of identifiers and representations; R can change -- members can be added, removed, or modified. (Though it's bad, unstable design to remove or modify identifiers.) We say an identifier that is an element of R identifies R, and that a representation that is an element of R represents R.

    Let's say R is a dog. You happen to identify R as /v1/dogs/1. (Meaning /v1/dogs/1 is a member of R.) That's just one of many ways you could identify R. You could also identify R as /v1/dogs/1/x-rays and as /v1/rufus.

    How do you represent R? Maybe with a photograph. Maybe with a set of X-rays. Or maybe with an indication of the date and time when R last barked. But remember that these are all representations of the same resource. /v1/dogs/1/x-rays is an identifier of the same resource that is represented by an answer to the question "when did R last bark?"

    HTTP

    Multiple representations of a resource aren't very useful if you can't refer to the one you want. That's why HTTP is useful: it lets you connect identifiers to representations. That is, it is a way for the service to receive a URL and decide which representation to serve to the client.

    At least, that's what GET does. PUT is basically the inverse of GET: you PUT a representation r at the URL if you wish for future GET requests to that URL to return r, with some possible translations like JSON to HTML.

    POST is a looser way of modifying a representation. Think of there being display logic and modification logic that are counterparts to each other -- both corresponding to the same URL. A POST request is a request for the modification logic to process the information and modify any representations (not just the representation located by the same URL) as the service sees fit. Pay attention to the third paragraph after 9.6 PUT: you're not replacing the thing at the URL with new content; you're asking the thing at the URL to process some information and intelligently respond in the form of informative representations.

    In our case, we ask the modification logic at /v1/dogs/1/bark-schedule (which is the counterpart to the display logic that tells us when it last barked and when it will next bark) to process our information and modify some representations accordingly. In response to future GETs, the display logic corresponding to the same URL will tell us that the dog is now barking as we wish.

    Think of the cron job as an implementation detail. HTTP deals in viewing and modifying representations. From now on, the service will tell the client when the dog last barked and when it will bark next. From the service's perspective, that is honest because those times correspond with past and planned cron jobs.

    0 讨论(0)
  • 2020-12-02 03:51

    Earlier revisions of some answers suggested you use RPC. You do not need to look to RPC as it is perfectly possible to do what you want whilst adhering to the REST constraints.

    Firstly, don't put action parameters in the URL. The URL defines what you are applying the action to, and query parameters are part of the URL. It should be thought of entirely as a noun. http://api.animals.com/v1/dogs/1/?action=bark is a different resource — a different noun — to http://api.animals.com/v1/dogs/1/. [n.b. Asker has removed the ?action=bark URI from the question.] For example, compare http://api.animals.com/v1/dogs/?id=1 to http://api.animals.com/v1/dogs/?id=2. Different resources, distinguished only by query string. So the action of your request, unless it corresponds directly to a bodyless existing method type (TRACE, OPTIONS, HEAD, GET, DELETE, etc) must be defined in the request body.

    Next, decide if the action is "idempotent", meaning that it can be repeated without adverse effect (see next paragraph for more explanaton). For example, setting a value to true can be repeated if the client is unsure that the desired effect happened. They send the request again and the value remains true. Adding 1 to a number is not idempotent. If the client sends the Add1 command, isn't sure it worked, and sends it again, did the server add one or two? Once you have determined that, you're in a better position to choose between PUT and POST for your method.

    Idempotent means a request can be repeated without changing the outcome. These effects do not include logging and other such server admin activity. Using your first and second examples, sending two emails to the same person does result in a different state than sending one email (the recipient has two in their inbox, which they might consider to be spam), so I would definitely use POST for that. If the barkCount in example 2 is intended to be seen by a user of your API or affects something that is client-visible, then it is also something that would make the request non-idempotent. If it is only to be viewed by you then it counts as server logging and should be ignored when determing idempotentcy.

    Lastly, determine if the action you want to perform can be expected to succeed immediately or not. BarkDog is a quickly completing action. RunMarathon is not. If your action is slow, consider returning a 202 Accepted, with a URL in the response body for a user to poll to see if the action is complete. Alternatively, have users POST to a list URL like /marathons-in-progress/ and then when the action is done, redirect them from the in progress ID URL to the /marathons-complete/ URL.
    For the specific cases #1 and #2, I would have the server host a queue, and the client post batches of addresses to it. The action would not be SendEmails, but something like AddToDispatchQueue. The server can then poll the queue to see if there are any email addresses waiting, and send emails if it finds any. It then updates the queue to indicate that the pending action has now been performed. You would have another URI showing the client the current state of the queue. To avoid double-sending of emails, the server could also keep a log of who it has sent this email to, and check each address against that to ensure it never sends two to the same address, even if you POST the same list twice to the queue.

    When choosing a URI for anything, try to think of it as a result, not an action. For example google.com/search?q=dogs shows the results of a search for the word "dogs". It does not necessarilly perform the search.

    Cases #3 and #4 from your list are also not idempotent actions. You suggest that the different suggested effects might affect the API design. In all four cases I would use the same API, as all four change the “world state.”

    0 讨论(0)
  • 2020-12-02 03:54

    POST is the HTTP method designed for

    Providing a block of data...to a data-handling process

    Server-side methods handling non-CRUD-mapped actions is what Roy Fielding intended with REST, so you're good there, and that's why POST was made to be non-idempotent. POST will handle most posting of data to server-side methods to process the information.

    That said, in your dog-barking scenario, if you want a server-side bark to be performed every 10 minutes, but for some reason need the trigger to originate from a client, PUT would serve the purpose better, because of its idempotence. Well, strictly by this scenario there's no apparent risk of multiple POST requests causing your dog to meow instead, but anyway that's the purpose of the two similar methods. My answer to a similar SO question may be useful for you.

    0 讨论(0)
  • 2020-12-02 03:57

    Most people use POST for this purpose. It is appropriate for performing "any unsafe or nonidempotent operation when no other HTTP method seems appropriate".

    APIs such as XMLRPC use POST to trigger actions that can run arbitrary code. The "action" is included in the POST data:

    POST /RPC2 HTTP/1.0
    User-Agent: Frontier/5.1.2 (WinNT)
    Host: betty.userland.com
    Content-Type: text/xml
    Content-length: 181
    
    <?xml version="1.0"?>
    <methodCall>
       <methodName>examples.getStateName</methodName>
       <params>
          <param>
             <value><i4>41</i4></value>
             </param>
          </params>
       </methodCall>
    

    The RPC is example is given to show that POST is the conventional choice of HTTP verbs for server-side methods. Here's Roy Fielding thoughts on POST -- he pretty much says it is RESTful to use the HTTP methods as specified.

    Note that RPC itself isn't very RESTful because it isn't resource oriented. But if you need statelessness, caching, or layering, it's not hard to make the appropriate transformations. See http://blog.perfectapi.com/2012/opinionated-rpc-apis-vs-restful-apis/ for an example.

    0 讨论(0)
  • 2020-12-02 03:58

    REST is a resource oriented standard, it is not action driven as a RPC would be.

    If you want your server to bark, you should look into different ideas like JSON-RPC, or into websockets communication.

    Every try to keep it RESTful will fail in my opinion: you can issue a POST with the action parameter, you are not creating any new resources but as you may have side effects, you are safer.

    0 讨论(0)
  • 2020-12-02 04:06

    Why aim for a RESTful design?

    The RESTful principles bring the features that make web sites easy (for a random human user to "surf" them) to the web services API design, so they are easy for a programmer to use. REST isn't good because it's REST, it's good because it's good. And it is good mostly because it is simple.

    The simplicity of plain HTTP (without SOAP envelopes and single-URI overloaded POST services), what some may call "lack of features", is actually its greatest strength. Right off the bat, HTTP asks you to have addressability and statelessness: the two basic design decisions that keep HTTP scalable up to today's mega-sites (and mega-services).

    But REST is not the silver bulltet: Sometimes an RPC-style ("Remote Procedure Call" - such as SOAP) may be appropriate, and sometimes other needs take precedence over the virtues of the Web. This is fine. What we don't really like is needless complexity. Too often a programmer or a company brings in RPC-style Services for a job that plain old HTTP could handle just fine. The effect is that HTTP is reduced to a transport protocol for an enormous XML payload that explains what's "really" going on (not the URI or the HTTP method give a clue about it). The resulting service is far too complex, impossible to debug, and won't work unless your clients have the exact setup as the developer intended.

    Same way a Java/C# code can be not object-oriented, just using HTTP does not make a design RESTful. One may be caught up in the rush of thinking about their services in terms of actions and remote methods that should be called. No wonder this will mostly end up in a RPC-Style service (or a REST-RPC-hybrid). The first step is to think differently. A RESTful design can be achieved in many ways, one way is to think of your application in terms of resources, not actions:

    0 讨论(0)
提交回复
热议问题