I work with internal AngularJS SPA, based on REST API and problem is some REST requests proceeds for very a long time (~5-10 seconds).
It is ridiciolus, but there is nothing to do with it – server need to get a lot of data from different sources and math it together before response.
Nice solution is to show request status to user, and here we come to the WebSockets.
Example of request status showing to user
- Request sended
- Request accepted
- Getting data from DB1
- Getting data from DB2
- Mathing data
- Preparing response
But there is a bunch of problems.
It is unclear how to get which API user send an request to notify its status. Possible solution is to have dictionary “API client”-“Websocket connection”, but it can blows up on every reconnection and cause memory leaks. Isn`t it?
Maybe solution is to change whole REST API to Websockets. But it looks like a lot of work here and still I`ll need to reinvent thing like caching.
It looks like a mess from any angle. What is the better solution here?
It is better to provide both things, REST and Websocket, so web pages can use the websocket and automated clients with no websocket support and still use the API.
You can process those request asynchronously, and use the REST async pattern.
REST:
- Client sends request to API.
- API gets the request and:
- Creates an unique id for this processing task.
- Records the details of this task in a data store.
- Sends a message to a message bus requesting the task with that id to be processed.
- Returns
HTTP 202 Accepted
with a Location
header to a queue endpoint /queue/<taskid>
that provides the status of the task.
- Client gets the response, and can poll for the state of such processing on the endpoint indicated in the
Location
header in a regular basis.
- If the task is still not processed, the queue endpoint returns
HTTP 202 Accepted
.
- A background worker at the other side of the message bus, picks the message and:
- Goes to the data store to get the details of that task id.
- Do the processing.
- When done update the status of task with that id.
- At this point the queue endpoint returns
HTTP 303 See Other
with a Location
header that redirects the client to the location of the processing result.
Websocket:
- A websocket connection is established at the beginning of the session.
- The websocket server translates the incoming messages to subscribe operations in the message bus, and subscription messages to messages in the websocket. Examples:
- If you are using Redis.
- If you are using RabbitMQ.
- On REST step 3, rather than poll on time intervals, use the websocket to send an application defined message to subscribe to the changes on such task id. Make sure you can either know how to get the task id from the
Location
header, or just provide only the task id in another header.
- The websocket server should indicate to the service bus that such connection is subscribed to a topic that identifies changes in that task id.
- When the background worker completes the data processing, after updating the status of the task, also issue a message to the service bus with the topic that identifies that task id.
- The websocket server will get the message and then send it to the client.
- The client can then request to the queue endpoint to find out the location of the processing outcome. You can send that location via websocket as well, but in my experience is sometimes difficult because the websocket server could be a simple Windows service that has no knowledge about URLs or resources location.