RabbitMQ, Flask/fastapi and websockets - python

My system architecture looks very similar to the figure posted in the question here. The primary difference between my implementation and the posted question is that I'll be using fastapi/flask for the web-server (in python) and rabbitmq for messaging.
My high level pseudo code (using fastAPI) is as follows:
from fastapi import APIRouter
from starlette.responses import Response
router = APIRouter()
#router.post("/users/{message}")
async def provide_suggestions(message: str, response: Response):
uuid = generate_uuid(message)
message_dict = {"uuid": uuid, "data": message}.
result = await post_message_to_rabbit_mq(message_dict)
response.status_code = SOME_VALID_HTTP_RESPONSE_CODE # what would this be?
Question 1: What would the HTTP response code be? Basically, the web server needs to notify the client to come back after a certain period of time and check for result (and return suggestions then).
Once the web server posts message via rabbitmq, the workers would generate relevant suggestions based on the message (by looking up a database). This message along with the uuid would be posted back in another rabbitmq message queue. Now the web server becomes a consumer.
Question 2: Assuming the webserver is registered as a consumer for the message queue on the egress path, would the webserver get the data on a separate thread for the message queue?
Question 3: Instead of waiting for another HTTP request from the client to send the suggestions, can the client and the server communicate asynchronously via web-sockets?

To answer your questions:
1: According to REST standards, status code 202 seems to do it here:
HTTP Status 202 indicates that request has been accepted for
processing, but the processing has not been completed. This status
code is useful when the actual operation is asynchronous in nature.
2: You would want a different process within the service to consume from the queue and update the local server database. This would generally not be a part of your fastapi webserver, but a seperate process. Your fastapi webserver could then query the local database every so often, or you could have a seperate endpoint on the webserver than can be called by this process when the database has been updated.
3: If you have client utilities that can deal with the websocket connection, then yes. See fastapi's documentation on it here. Otherwise it might be better to return status code 202 on the first request and have the client query the webserver every few seconds. Another option is to use a callback url, but that depends on the client's situation.

Related

Initiating a Kafka consumer on server (Flask) and returning response

I have a Flask API, which when hit by a client, will subscribe to a Kafka topic and start consuming (polling) data from the topic and write it to a database.
Currently the consumer function runs an infinite loop to poll the data and write it to the database. But the problem here is, the client doesn't receive a success response here. The further client processes happens after the server timeout.
But the objective is, client has to receive the response right after the consumer is initialized and is ready to consume.
How to implement it in a proper way?
Edit:
I came across this thread - Flask end response and continue processing
Seems that using Python's Thread Library won't work here according to the comments on the top voted answer.
And the Accepted Answer suggested to use WSGI middleware that adds a hook to the close method of the response iterator. But is it advisable in this case where the consumer will be running for an infinite amount of time?
Is there an alternative to these?

How to send partial status of request to frontend by django python?

Suppose, I have sent a post request from react to Django rest API and that request is time taking. I want to get how many percentages it has been processed and send to the frontend without sending the real response?
There are two broad ways to approach this.
(which I would recommend to start with): Break the request up. The initial request doesn't start the work, it sends a message to an async task queue (such as Celery) to do the work. The response to the initial request is the ID of the Celery task that was spawned. The frontend now can use that request ID to poll the backend periodically to check if the task is finished and grab the results when they are ready.
Websockets, wherein the connection to the backend is kept open across many requests, and either side can initiate sending data. I wouldn't recommend this to start with, since its not really how Django is built, but with a higher level of investment it will give an even smoother experience.

Pending requests with Python's SimpleHTTPServer

I'm making an anonymous chat application, similar to Omegle. My method of approach instead of using sockets is to use a REST API, but to add a bit of a twist. When a user makes a request, such as POST /search (find a match), the request is held by the server until another user sends a POST /search. Once two people have done this, both requests are responded to which lets the client know to switch to a chat page. This is also done with a pending GET /events request, which is only responded to by the server if there's any new events to be sent.
This works very well in theory with the flow of this application; however, since I'm using SimpleHTTPServer - which is a very basic library - requests are not handling asynchronously. This means that if I block one request until information requirements are fulfilled, no other requests can be accepted. For this kind of project I really don't want to take the time to learn an entirely new library/sub-language for asynchronous requests handling, so I'm trying to figure out how I can do this.
def waitForMatch(client):
# if no other clients available to match:
if not pendingQueue:
# client added to pending queue
pendingQueue.append(client)
client["pending"] = True
while client["pending"]:
time.sleep(1)
# break out and let the other client's waitForMatch call create the chatsession
return
# now there's a client available to match
otherClient = pendingQueue.pop()
otherClient["pending"] = False
# chat session created
createChatSession(otherClient, client)
This is the code I currently have, which won't work with non-async requests.

Flask request waiting for asynchronous background job

I have an HTTP API using Flask and in one particular operation clients use it to retrieve information obtained from a 3rd party API. The retrieval is done with a celery task. Usually, my approach would be to accept the client request for that information and return a 303 See Other response with an URI that can be polled for the response as the background job is finished.
However, some clients require the operation to be done in a single request. They don't want to poll or follow redirects, which means I have to run the background job synchronously, hold on to the connection until it's finished, and return the result in the same response. I'm aware of Flask streaming, but how to do such long-pooling with Flask?
Tornado would do the trick.
Flask is not designed for asynchronization. A Flask instance processes one request at a time in one thread. Therefore, when you hold the connection, it will not proceed to next request.

Python Webserver: How to serve requests asynchronously

I need to create a python middleware that will do the following:
a) Accept http get/post requests from multiple clients.
b) Modify and Dispatch these requests to a backend remote application (via socket communication). I do not have any control over this remote application.
c) Receive processed results from backend application and return these results back to the requesting clients.
Now the clients are expecting a synchronous request/response scenario. But the backend application is not returning the results synchronously. That is, some requests take much longer to process than others. Hence,
Client 1 : send http request C1 --> get response R1
Client 2 : send http request C2 --> get response R2
Client 3 : send http request C3 --> get response R3
Python middleware receives them in some order: C2, C3, C1. Dispatches them in this order to backend (as non-http messages). Backend responds with results in mixed order R1, R3, R2. Python middleware should package these responses back into http response objects and send the response back to the relevant client.
Is there any sample code to program this sort of behavior. There seem to be something like 20 different web frameworks for python and I'm confused as to which one would be best for this scenario (would prefer something as lightweight as possible ... I would consider Django too heavy ... I tried bottle, but I am not sure how to go about programming that for this scenario).
================================================
Update (based on discussions below): Requests have a request id. Responses have a response id (which should match the request id that they correspond to). There is only one socket connection between the middleware and the remote backend application. While we can maintain a {request_id : ip_address} dictionary, the issue is how to construct a HTTP response object to the correct client. I assume, threading might solve this problem where each thread maintains its own response object.
Screw frameworks. This exactly the kind of task for asyncore. This module allows event-based network programming: given a set of sockets, it calls back given handlers when data is ready on any of them. That way, threads are not necessary just to dumbly wait for data on one socket to arrive and painfully pass it to another thread. You would have to implement the http handling yourself, but examples can be found on that. Alternatively, you could use the async feature of uwsgi, which would allow your application to be integrated with an existing webserver, but that does not integrate with asyncore by default --- though it wouldn't be hard to make it work. Depends on specific needs.
Quoting your comment:
The middleware uses a single persistent socket connection to the backend. All requests from middleware are forwarded via this single socket. Clients do send a request id along with their requests. Response id should match the request id. So the question remains: How does the middleware (web server) keep track of which request id belonged to which client? I mean, is there any way for a cgi script in middleware to create a db of tuples like and once a response id matches, then send a http response to clientip:clienttcpport ?
Is there any special reason for doing all this processing in a middleware? You should be able to do all this in a decorator, or somewhere else, if more appropriate.
Anyway, you need to maintain a global concurrent dictionary (extend dict and protect it using threading.Lock). Upon a new request, store the given request-id as key, and associate it to the respective client (sender). Whenever your backend responds, retrieve the client from this dictionary, and remove the entry so it doesn't accumulate forever.
UPDATE: someone already extended the dictionary for you - check this answer.
Ultimately your going from the synchronous http request-response protocol from your clients to an asynchronous queuing/messaging protocol with your backend. So you've two choices (1) either make requests wait until the backend has no outstanding work, then process one (2) write something that marries the backend responses with their associated request (using a dictionary of request or something)
One way might be to run your server in one thread while dealing with your backend in another (see... Run Python HTTPServer in Background and Continue Script Execution) or maybe look at aiohttp (https://docs.aiohttp.org/en/v0.12.0/web.html)

Categories