46

I'm building a RESTful API that supports queuing long-running tasks for eventual handling.

The typical workflow for this API would be:

  1. User fills in form
  2. Client posts data to API
  3. API returns 202 Accepted
  4. Client redirects user to a unique URL for that request (/results/{request_id})
  5. ~eventually~
  6. Client visits URL again, and sees the results on that page.

My trouble is on step 6. Any time a user visits the page, I file a request to my API (GET /api/results/{request_id}). Ideally, the task will have been completed by now, and I'd return a 200 OK with the results of their task.

But users are pushy, and I expect many overzealous refreshes, when the result is not yet finished processing.

What is my best option for a status code to indicate that:

  • this request exists,
  • it's not done yet,
  • but it also hasn't failed.

I don't expect a single code to communicate all of that, but I'd like something that lets me pass metadata instead of having the client expect content.

It could make sense to return a 202, since that would have no other meaning here: it's a GET request, so nothing is possibly being "accepted." Would that be a reasonable choice?

The obvious alternative to all this -- which functions, but defeats one purpose of status codes -- would be to always include the metadata:

200 OK

{
    status: "complete",
    data: {
        foo: "123"
    }
}

...or...

200 OK

{
    status: "pending"
}

Then client-side, I would (sigh) switch on response.data.status to determine whether the request was completed.

Is this what I should be doing? Or is there a better alternative? This just feels so Web 1.0 to me.

  • 1
    Isn't 1xx codes made exactly for that purpose? – Andy Apr 19 '16 at 18:04
  • @Andy I was looking at 102, but that's for WebDAV stuff. Beyond that, no... They're mostly for in-transit communications. Useful in switching to Web Sockets and such. – Matthew Haugen Apr 19 '16 at 18:16
  • What kind of delay are you talking? 10 seconds? Or 6 hours? If the delays are short and generally within the same browser visit, you might do long polling or web sockets rather than periodic polling. – GrandmasterB Apr 19 '16 at 19:37
  • @GrandmasterB It's hours, potentially. I'm not responsible for the job processing itself, so I don't have a really good estimate, but it'll be a while. Otherwise, I'd just leave the first POST request open. The main issue with long polling or web sockets is that the user might close the browser and come back. I could open them again at that time (and that's what I do), but it seems cleaner to have a single API to call before I open those sockets, since it's an edge-case to have that problem arise. – Matthew Haugen Apr 19 '16 at 20:05
45

HTTP 202 Accepted (HTTP/1.1)

You are looking for HTTP 202 Accepted status. See RFC 2616:

The request has been accepted for processing, but the processing has not been completed.

HTTP 102 Processing (WebDAV)

RFC 2518 suggests using HTTP 102 Processing:

The 102 (Processing) status code is an interim response used to inform the client that the server has accepted the complete request, but has not yet completed it.

but it has a caveat:

The server MUST send a final response after the request has been completed.

I'm not sure how to interpret the last sentence. Should the server avoid sending anything during the processing, and respond only after the completion? Or it only forces to end the response only when the processing terminates? This could be useful if you want to report progress. Send HTTP 102 and flush response byte by byte (or line by line).

For instance, for a long but linear process, you can send one hundred dots, flushing after each character. If the client side (such as a JavaScript application) knows that it should expect exactly 100 characters, it can match it with a progress bar to show to the user.

Another example concerns a process which consists of several non-linear steps. After each step, you can flush a log message which would eventually be displayed to the user, so that the end user could know how the process is going.

Issues with progressive flushing

Note that while this technique has its merits, I wouldn't recommend it. One of the reasons is that it forces the connection to remain open, which could hurt in terms of service availability and doesn't scale well.

A better approach is to respond with HTTP 202 Accepted and either let the user to get back to you later to determine whether the processing ended (for instance by calling repeatedly a given URI such as /process/result which would respond with HTTP 404 Not Found or HTTP 409 Conflict until the process finishes and the result is ready), or notify the user when the processing is done if you're able to call the client back for instance through a message queue service (example) or WebSockets.

Practical example

Imagine a web service which converts videos. The entry point is:

POST /video/convert

which takes a video file from the HTTP request and does some magic with it. Let's imagine that the magic is CPU-intensive, so it cannot be done in real-time during the transfer of the request. This means that once the file is transferred, the server will respond with a HTTP 202 Accepted with some JSON content, meaning “Yes, I got your video, and I'm working on it; it will be ready somewhere in the future and will be available through the ID 123.”

The client has a possibility to subscribe to a message queue to be notified when the processing finishes. Once it is finished, the client can download the processed video by going to:

GET /video/download/123

which leads to an HTTP 200.

What happens if the client queries this URI before receiving the notification? Well, the server will respond with HTTP 404 since, indeed, the video doesn't exist yet. It may be currently prepared. It may never been requested. It may exist some time in the past and be removed later. All that matters is that the resulting video is not available.

Now, what if the client cares not only about the final video, but also about the progress (which would be even more important if there is no message queue service or any similar mechanism)?

In this case, you can use another endpoint:

GET /video/status/123

which would result a response similar to this:

HTTP 200
{
    "id": 123,
    "status": "queued",
    "priority": 2,
    "progress-percent": 0,
    "submitted-utc-time": "2016-04-19T13:59:22"
}

Doing the request over and over will show the progress until it's:

HTTP 200
{
    "id": 123,
    "status": "done",
    "progress-percent": 100,
    "submitted-utc-time": "2016-04-19T13:59:22"
}

It is crucial to make a difference between those three types of requests:

  • POST /video/convert queues a task. It should be called only once: calling it again would queue an additional task.
  • GET /video/download/123 concerns the result of the operation: the resource is the video. The processing—that is what happened under the hood to prepare the actual result prior to request and independently to the request—is irrelevant here. It can be called once or several times.
  • GET /video/status/123 concerns the processing per se. It doesn't queue anything. It doesn't care about the resulting video. The resource is the processing itself. It can be called once or several times.
  • 1
    Does a 202 make sense in response to a GET, though? That's certainly the correct choice for the initial POST, which is why I'm using it. But it seems semantically suspicious for a GET to say "accepted" when it didn't accept anything from that particular request. – Matthew Haugen Apr 19 '16 at 18:21
  • 2
    @MainMa Like I said, I POST up the job to be queued, then I GET the results, potentially after the client has closed the session. A 404 is something I've considered as well, but it seems wrong, since the request is found, it just hasn't been completed. That would indicate to me that the queued job was not found, which is a very different situation. – Matthew Haugen Apr 19 '16 at 19:06
  • 1
    @MatthewHaugen: when you do the GET part, don't think about it as a incomplete request, but as a request to get the result of the operation. For instance, if I tell you to convert a video and it takes you five minutes to do it, requesting for a converted video two minutes later should result in HTTP 404, because the video is simply not there yet. Requesting for the progress of the operation itself, on the other hand, will probably result in an HTTP 200 containing the number of bytes converted, the speed, etc. – Arseni Mourzenko Apr 19 '16 at 19:11
  • 5
    HTTP Status Code for Resource not yet available suggests returning a 409 conflict response ("The request could not be completed due to a conflict with the current state of the resource. "), rather than a 404 response, in the case that a resource doesn't exist because it is in the middle of being generated. – Brian Apr 20 '16 at 13:22
  • 1
    @Brian Your comment would make a reasonable answer to this question. Although I would then respond with "[t]his code is only allowed in situations where it is expected that the user might be able to resolve the conflict and resubmit the request," which isn't strictly true in my case, but that seems less wrong than "not found." A part of me is leaning toward a 409 with a Retry-After header pinned on. The only issue is that it seems weird to return a 409 for a GET, but I can live with that weirdness--it's unlikely to be otherwise-defined in the future. – Matthew Haugen Apr 20 '16 at 16:15
5

The obvious alternative to all this -- which functions, but defeats one purpose of status codes -- would be to always include the metadata:

This is the correct way to go. The state a resources is in with regard to domain specific log (aka business logic) is a matter for the content type of the resource's representation.

There are two difference concepts being conflated here which are actually different. One is the status of the state transfer between client and server of a resource, and the other is the state of the resource itself in what ever context the business domain undertands the different states of that resource to be. The latter is nothing to do with HTTP status codes.

Remember the HTTP status codes correspond to the state transfer between client and server of the resource being dealt with, independently to any details of that resource. When you GET a resource your client is asking the server for a representation of a resource in the current state it is in. That could be a picture of a bird, it could be a Word document, it could be the current outside temp. The HTTP protocol doesn't care. The HTTP status code corresponds to the result of that request. Did the POST from the client to the server transfer a resource to the server, where the server then gave it a URL that the client can view? Yes? Then that is a 201 Created response.

The resource could be a airline booking that is currently in the 'to be reviewed' state. Or it could be product purchase order that is in the 'approved' state. Those states are domain specific and not what the HTTP protocol is about. The HTTP protocol deals with the transfer of resources between client and server.

The point of REST and HTTP is that the protocols don't concern itself with the details of the resources. This is on purpose, it doesn't concern itself with the domain specific issues so that it can be used without having to know anything about the domain specific issues. You don't reinterpret what the HTTP status codes mean in each different context (an airline booking system, an imagine processing system, a video security system etc).

The domain specific stuff is for the client and server to figure out between themselves based on the Content Type of the resource. The HTTP protocol is agnostic to this.

As for how the client figures out that the Request resource has changed state, polling is your best bet as it keeps control in at the client and doesn't assume unbroken connection. Particularly if it is going to be potentially hours until the state changes. Even if you said to hell with REST you are just going to keep the connection open, keeping it open for hours and assuming nothing will go wrong would be a bad idea. What if the user closes the client or the network goes out. If the granularity is hours the client can just request the state every few minutes until the Request changes from 'pending' to 'done'.

Hope that helps clarify things

  • "Did the POST from the client to the server transfer a resource to the server, where the server then gave it a URL that the client can view? Yes? Then that is a 201 Created response." 202 Accepted is also acceptable as a response to this if the server cannot act immediately to process the resource, which is what the OP is doing. – Andy Apr 21 '16 at 22:24
  • 1
    The thing is the server is acting immediately. It creates the resource with a URL immediately. It is just the state of the resource is "Pending" (or something). That is a business domain state. As far as the HTTP Protocol is concerned the server acted as soon as it created the resource and gave the client the URL of the resource. You can GET that resource. The POST request itself is not pending. This is what I mean by keeping the two different conceptual domains separate. If the client was sending a fire and forget POST request not acted upon for hours then 202 would be applicable. – Cormac Mulhall Apr 22 '16 at 9:13
  • No one cares if the url exists but you can't get the data the resource represents because it's still being processed. Might as well NOT create the url until it can be used to get the video. – Andy Apr 22 '16 at 23:01
  • The resource is created, it is just in the state "pending". That is in itself relevant data. At some point in the future the server may change the resources state to "completed" (or "failed") but that is a different concept to the HTTP domain specific task of "create the resource". Pending can be a perfectly valid state for a "Request" resource to be, and the client obviously wants to know the server has created the resource in that state since it moves from asking the server to create the resource to know polling it to find out if the state changed. – Cormac Mulhall Apr 24 '16 at 0:06
3

I found the suggestions from this blog reasonable: REST and long-running jobs.

To summarize:

  1. Server returns code "202 Accepted" with the "Location" header set to a URI for client to check the status, e.g. "/queue/12345".
  2. Until the processing finishes, server responds to status queries with "200 OK" and some response data showing job status.
  3. After the processing finishes, server responds to status queries with "303 See Other" and "Location" containing URI to the final result.
2

HTTP Status Code for Resource not yet available suggests returning a 409 conflict response, rather than a 404 response, in the case that a resource doesn't exist because it is in the middle of being generated.

From the w3 spec:

10.4.10 409 Conflict

The request could not be completed due to a conflict with the current state of the resource. This code is only allowed in situations where it is expected that the user might be able to resolve the conflict and resubmit the request. The response body SHOULD include enough

information for the user to recognize the source of the conflict. Ideally, the response entity would include enough information for the user or user agent to fix the problem; however, that might not be possible and is not required.

Conflicts are most likely to occur in response to a PUT request. For example, if versioning were being used and the entity being PUT included changes to a resource which conflict with those made by an earlier (third-party) request, the server might use the 409 response to indicate that it can't complete the request. In this case, the response entity would likely contain a list of the differences between the two versions in a format defined by the response Content-Type.

This is slightly awkward, since the 409 code is "only allowed in situations where it is expected that the user might be able to resolve the conflict and resubmit the request." I suggest the response body includes a message (possibly in some a response format matching the rest of your API) like, "This resource is currently being generated. It was initiated at [TIME] and is estimated to complete at [TIME]. Please try again later."

Note that I would only suggest the 409 approach if it is highly likely that the user who is requesting the resource is also the user who initiated generation of that resource. Users not involved with the generation of the resource would find a 404 error less confusing.

  • Seems like a stretch f what 409 is really meant for, which is in response to a put. – Andy Apr 21 '16 at 0:07
  • @Andy: True, but so is every other alternative. E.g., 202 is really meant to be a response to the request which initiated processing, not the the request that requested the results of the processing. Really, the most spec-compliant response is 404, since the resource was not found (because it didn't exist yet). There's nothing stopping the API from providing the relevant api data within the 404 response. Mind you, 4xx/5xx responses tend to be annoying to consume; some languages will fire an exception rather than just providing a different status code. – Brian Apr 21 '16 at 14:39
  • 2
    No, especially the last few paragraphs of MainMa's answer. Separate end points to check the status of the request and to get the video itself. The status is not the same resource as the video and should be addressable on its own. – Andy Apr 21 '16 at 22:21

protected by gnat Nov 16 '18 at 21:40

Thank you for your interest in this question. Because it has attracted low-quality or spam answers that had to be removed, posting an answer now requires 10 reputation on this site (the association bonus does not count).

Would you like to answer one of these unanswered questions instead?

Not the answer you're looking for? Browse other questions tagged or ask your own question.