Python Forum
Wsgiref with asyncio ?
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Wsgiref with asyncio ?
#1
I'm wanting to relate the asyncio module with wsgiref to work with asynchronous requests could anyone give me an idea of a functional implementation ?

import asyncio
from wsgiref.simple_server import make_server

def hello_world_app(environ, start_response):
    status = "200 OK"  # HTTP Status
    headers = [("Content-type", "text/plain; charset=utf-8")]  # HTTP Headers
    start_response(status, headers)

    # The returned object is going to be printed
    return [b"Hello World"]

with make_server("", 8000, hello_world_app) as httpd:
    print("Serving on port 8000...")

    # Serve until process is killed
    httpd.serve_forever()
likes this post
Reply
#2
I personally can't.

I found that discussion and it seems if you want to implement some asynchronous behaviour it won't work just using asyncio. That lib is a single-threaded module to make async calls. They added that run_in_executor method that takes concurrent.futures.executor object as a parameter but don't have time to figure out how to use it with just wsgi.

If you want to build a web app try some framework like Flask or a module like aiohttp, Falcon, etc. They support and handle async requests.
"As they say in Mexico 'dosvidaniya'. That makes two vidaniyas."
https://freedns.afraid.org
Reply
#3
I'm actually working on developing a web framework in python, that's why I asked and see the implementation possibilities, but thanks for the goodwill.
Reply
#4
Maybe this could give you some directions.

Note that the syntax for making a coroutine is async def/await now. That one there was the old way. Using a decorator...

You are awaiting instead of yield from
"As they say in Mexico 'dosvidaniya'. That makes two vidaniyas."
https://freedns.afraid.org
Reply
#5
(Oct-28-2022, 02:59 PM)wavic Wrote: Maybe this could give you some directions.

Note that the syntax for making a coroutine is async def/await now. That one there was the old way. Using a decorator...

You are awaiting instead of yield from

I managed to define a simple and very logical implementation, but I come across the following error:

# Native Module : wsgiref.simple_server -> ( https://docs.python.org/3/library/wsgiref.html#module-wsgiref )
from wsgiref.simple_server import make_server
# Native Module : asyncio — Asynchronous I/O -> https://docs.python.org/3/library/asyncio.html#module-asyncio
from asyncio import get_event_loop


# Function -> define coroutine, to run the function asynchronous
async def maincoro(environ, start_response):

    status = '200 OK'  # HTTP Status
    headers = [('Content-type', 'text/plain; charset=utf-8')]  # HTTP Headers
    start_response(status, headers)

    # The returned object is going to be printed
    return [str('Test of requisition http ?').encode('utf-8')]

# Function -> prepare a loop to work with asynchronous events ( request list ) :
loop = get_event_loop()
# Function -> run the loop until finished :
coro = loop.run_until_complete(maincoro(environ, start_response))

with make_server('', 8000, coro) as httpd:
    print('Serving on port 8000...')
    # Serve until process is killed
    httpd.serve_forever()
Error :
(wse) joe@debian:~/PycharmProjects/quark$ python trycode.py
/home/joe/PycharmProjects/quark/trycode.py:31: DeprecationWarning: There is no current event loop
  loop = get_event_loop()
Traceback (most recent call last):
  File "/home/joe/PycharmProjects/quark/trycode.py", line 33, in <module>
    coro = loop.run_until_complete(maincoro(environ, start_response))
NameError: name 'environ' is not defined
Reply
#6
I am not familiar with the plain WSGI at all. Never used directly. So I can only guess how it works.

But first something about asycnio.

run_until_complete returns the result of a future object ( maincoro ) - in that case it will be [str('Test of requisition http ?').encode('utf-8')]. Which is a list BTW. The returned result will be the returned one from the coroutine ( maincoro ) object. It does not return a callable function which you can use into the with statement.

I think make_server runs the server and waits for a request. Then when happens it calls the hander ( maincoro is your handler ) and its returned object is sent back. Instead of giving it a function to execute on a request, you are giving it [str('Test of requisition http ?').encode('utf-8')] .

The error is because maincoro is executed before the with statement which takes the request and provides the environ as an argument to maincoro. The environ object doesn't exist yet at the execution of maincoro.

I guess you are not quite familiar how asyncio and the event loop work.

asyncio is not a small library. I barely touch it sometimes. When I need some async calls. Most of the time I use 3rd party libraries to do web requests for example. Lke aiohttp or httpx... One may call them frameworks. Anyway.

During figuring out for myself what is going on with such a low-level library as WSGI I came across two I think useful web pages. First is how to make a framework using WSGI. Perhaps it explains a lot of its machinery. I don't have time to dig into it.

The second one is about asynchronous WSGI called ASGI which I think you can use for your purposes. Making an asynchronous framework using a low-level library. At least you will be able to see how they wrap WSGI in order to work with asyncio.

Apologize my English Wink
"As they say in Mexico 'dosvidaniya'. That makes two vidaniyas."
https://freedns.afraid.org
Reply
#7
(Oct-31-2022, 02:50 AM)wavic Wrote: I am not familiar with the plain WSGI at all. Never used directly. So I can only guess how it works.

But first something about asycnio.

run_until_complete returns the result of a future object ( maincoro ) - in that case it will be [str('Test of requisition http ?').encode('utf-8')]. Which is a list BTW. The returned result will be the returned one from the coroutine ( maincoro ) object. It does not return a callable function which you can use into the with statement.

I think make_server runs the server and waits for a request. Then when happens it calls the hander ( maincoro is your handler ) and its returned object is sent back. Instead of giving it a function to execute on a request, you are giving it [str('Test of requisition http ?').encode('utf-8')] .

The error is because maincoro is executed before the with statement which takes the request and provides the environ as an argument to maincoro. The environ object doesn't exist yet at the execution of maincoro.

I guess you are not quite familiar how asyncio and the event loop work.

asyncio is not a small library. I barely touch it sometimes. When I need some async calls. Most of the time I use 3rd party libraries to do web requests for example. Lke aiohttp or httpx... One may call them frameworks. Anyway.

During figuring out for myself what is going on with such a low-level library as WSGI I came across two I think useful web pages. First is how to make a framework using WSGI. Perhaps it explains a lot of its machinery. I don't have time to dig into it.

The second one is about asynchronous WSGI called ASGI which I think you can use for your purposes. Making an asynchronous framework using a low-level library. At least you will be able to see how they wrap WSGI in order to work with asyncio.

Apologize my English Wink

The output I found was to define a simple intermediate function (maininter) with parameters (environ, start_response) and that is triggered when the http request happens, so that the object (environ) is available to be able to manipulate it.

In the (maininter) function, notice that I try to capture event loop via ( get_event_loop() ) but I get the message ( Deprecation Warning: There is no current event loop ) ? Imagining in getting the event loop ( http request ) I define the loop to run until it ends through the method ( run_until_complete() ), triggering the asynchronous function (maincoro) with passing parameters (environ, start_response) so that it executes the code without problems, follow the code below:

# Native Module : asyncio — Asynchronous I/O -> https://docs.python.org/3/library/asyncio.html#module-asyncio
from asyncio import get_event_loop
# Native Module : wsgiref.simple_server -> ( https://docs.python.org/3/library/wsgiref.html#module-wsgiref )
from wsgiref.simple_server import make_server
# Native Module :
import inspect, threading


def maininter(environ, start_response):

    # Function -> capture the loop
    loop = get_event_loop()
    # Function -> run until it terminate the loop and trigger the coroutine :
    return loop.run_until_complete(maincoro(environ, start_response, loop))

# Function -> convert function to coroutine and be able to run concurrently ( asynchronous )
async def maincoro(environ, start_response, loop):

    iscoro = inspect.iscoroutinefunction(maincoro)
    thread = threading.active_count()

    status = '200 OK'  # HTTP Status
    headers = [('Content-type', 'text/plain; charset=utf-8')]  # HTTP Headers
    start_response(status, headers)

    # The returned object is going to be printed
    return [str(f'It is asynchronous request http ? ( Response: {iscoro} ), ( Thread Number : {thread} ), ( Loop : {loop} )').encode('utf-8')]

# step 1º -> start the test server
# step 2º -> trigger function of the main coroutine via http request
# step 3º -> capture the function loop in execution

with make_server('', 8000, maininter) as httpd:

    print(f'Serving on port 8000...')
    # Serve until process is killed
    httpd.serve_forever()
( ☉☉ ) The code works, but I still have doubts?

• Why this message ( Deprecation Warning: There is no current event loop ) ?
• Does the code really work asynchronously to answer http requests?
Reply
#8
You can print a message for every request and after that you await asyncio.sleep(15) both in maincoro. Run the whole thing and refresh the "web page" two-three times. If you get the message on every refresh it is asynchronous.

Consider looking at ASGI. You will end up building one or similar anyway. Just to build on top of your framework. Unless you want to do something completely different. Smile

Do not use time.sleep. It is blocking. Async.sleep is not
"As they say in Mexico 'dosvidaniya'. That makes two vidaniyas."
https://freedns.afraid.org
Reply
#9
(Oct-31-2022, 03:47 PM)wavic Wrote: You can print a message for every request and after that you await asyncio.sleep(15) both in maincoro. Run the whole thing and refresh the "web page" two-three times. If you get the message on every refresh it is asynchronous.

Consider looking at ASGI. You will end up building one or similar anyway. Just to build on top of your framework. Unless you want to do something completely different. Smile

Do not use time.sleep. It is blocking. Async.sleep is not

I believe that, to find out if the function really works asynchronously, it captures the id of the secondary thread, but how do I do that, as it seems to be dynamic?
Reply


Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020