Aiohttp: How to switch from aiohttp.wsgi to aiohttp.web.Application?

Created on 9 May 2017  路  13Comments  路  Source: aio-libs/aiohttp

Long story short

I'm planing to run an aiohttp production server using:

Client Request ----> Nginx (Reverse-Proxy) + Gunicorn
                        |
                        \                           
                         `-> App. Server I.   127.0.0.1:8081

I'm getting all sorts of errors, but one that I can't figure out is this:

  File "/Users/yad/pySites/Slog/venv/lib/python3.5/site-packages/aiohttp/worker.py", line 65, in make_handler
    "aiohttp.wsgi is not supported anymore, "
RuntimeError: aiohttp.wsgi is not supported anymore, consider to switch to aiohttp.web.Application

Expected behavior + Actual behavior

According to the documents, running the gunicorn with this line should be fine:

gunicorn aiohttp-server:main --bind localhost:3000 --worker-class aiohttp.GunicornWebWorker

Any suggestions?

I tried following the documentation for these details

outdated

Most helpful comment

you can use aiohttp 1.3 version, it supports wsgi.

wsgi is not supported by aiohttp 2.0

All 13 comments

you can use aiohttp 1.3 version, it supports wsgi.

wsgi is not supported by aiohttp 2.0

Thanks @fafhrd91! Is it in the plan for the new version or it's fully dropped?

Do you have any recommendation for deployment? Like a guide or so?

I'm having a hard time understanding how I can run the aiohttp apps with Gunicorn and WSGI.

Currently I have:

aiohttp-server.py >> file
contnet:

import json
import asyncio
import logging
from aiohttp import web
from functools import wraps

# imports...

class Rest:

    @asyncio.coroutine
    def index(self, request):
        return web.Response(body=b"Hello, world")

    @rest_handler
    @asyncio.coroutine
    def log(self, request):

        #... doing some more work here:
       print("request arrived")
        return ""
def main():
    rest = Rest()

    app = web.Application()
    app.router.add_route('GET', '/', rest.index)
    app.router.add_route('POST', '/log', rest.log)

    loop = asyncio.get_event_loop()
    handler = app.make_handler()
    f = loop.create_server(handler, '0.0.0.0', 3000)
    srv = loop.run_until_complete(f)
    log.info('serving on %r', srv.sockets[0].getsockname())
    try:
        loop.run_forever()
    except KeyboardInterrupt:
        pass
    finally:
        log.info('\nBye')
        loop.run_until_complete(handler.finish_connections(1.0))
        srv.close()
        loop.run_until_complete(srv.wait_closed())
        loop.run_until_complete(app.finish())
    loop.close()


if __name__ == '__main__':
    main()

now when I run:

gunicorn aiohttp-server:main --bind localhost:3000 --worker-class aiohttp.GunicornWebWorker

I get this:

[2017-05-08 17:14:59 -0700] [81259] [INFO] Starting gunicorn 19.7.1
[2017-05-08 17:14:59 -0700] [81259] [INFO] Listening at: http://127.0.0.1:3000 (81259)
[2017-05-08 17:14:59 -0700] [81259] [INFO] Using worker: aiohttp.GunicornWebWorker
[2017-05-08 17:14:59 -0700] [81259] [INFO] Unhandled exception in main loop
Traceback (most recent call last):
  File "/Users/yad/pySites/Slog/venv/lib/python3.5/site-packages/gunicorn/arbiter.py", line 202, in run
    self.manage_workers()
  File "/Users/name/pySites/MyProject/venv/lib/python3.5/site-packages/gunicorn/arbiter.py", line 544, in manage_workers
    self.spawn_workers()
  File "/Users/name/pySites/MyProject/venv/lib/python3.5/site-packages/gunicorn/arbiter.py", line 611, in spawn_workers
    self.spawn_worker()
  File "/Users/name/pySites/MyProject/venv/lib/python3.5/site-packages/gunicorn/arbiter.py", line 564, in spawn_worker
    self.cfg, self.log)
TypeError: 'str' object is not callable

aiohttp.readthedocs.io/en/1.3.5/

But I'd suggest to use aiohttp.web framework instead of wsgi

Oh, sorry. You are using aiohttp.web

So, to use gunicorn you need point it to application object and you don't need any of run_ methods. Gunicorn worker does it for you

Check "Start gunicorn" section

Thanks again @fafhrd91 for the help, the issue was the unicorn worker name:
i used this at firstaiohttp.GunicornWebWorker, but it should be aiohttp.worker.GunicornWebWorker for the 1.3 version

Now, I'm getting this error:

2017-05-08 18:37:28 -0700] [82090] [INFO] Starting gunicorn 19.7.1
[2017-05-08 18:37:28 -0700] [82090] [INFO] Listening at: http://127.0.0.1:3000 (82090)
[2017-05-08 18:37:28 -0700] [82090] [INFO] Using worker: aiohttp.worker.GunicornWebWorker
[2017-05-08 18:37:28 -0700] [82097] [INFO] Booting worker with pid: 82097
INFO:root:serving on ('0.0.0.0', 3000)
[2017-05-08 18:37:58 -0700] [82090] [CRITICAL] WORKER TIMEOUT (pid:82097)
INFO:root:
Bye
[2017-05-08 18:37:58 -0700] [82097] [INFO] Worker exiting (pid: 82097)
Exception ignored in: <bound method BaseEventLoop.__del__ of <_UnixSelectorEventLoop running=False closed=True debug=False>>
Traceback (most recent call last):
  File "/usr/local/Cellar/python3/3.5.2_1/Frameworks/Python.framework/Versions/3.5/lib/python3.5/asyncio/base_events.py", line 431, in __del__
  File "/usr/local/Cellar/python3/3.5.2_1/Frameworks/Python.framework/Versions/3.5/lib/python3.5/asyncio/unix_events.py", line 58, in close
  File "/usr/local/Cellar/python3/3.5.2_1/Frameworks/Python.framework/Versions/3.5/lib/python3.5/asyncio/unix_events.py", line 139, in remove_signal_handler
  File "/usr/local/Cellar/python3/3.5.2_1/Frameworks/Python.framework/Versions/3.5/lib/python3.5/signal.py", line 47, in signal
TypeError: signal handler must be signal.SIG_IGN, signal.SIG_DFL, or a callable object
[2017-05-08 18:37:58 -0700] [82122] [INFO] Booting worker with pid: 82122
INFO:root:serving on ('0.0.0.0', 3000)
[2017-05-08 18:38:28 -0700] [82090] [CRITICAL] WORKER TIMEOUT (pid:82122)
INFO:root:
Bye
[2017-05-08 18:38:28 -0700] [82122] [INFO] Worker exiting (pid: 82122)
Exception ignored in: <bound method BaseEventLoop.__del__ of <_UnixSelectorEventLoop running=False closed=True debug=False>>
Traceback (most recent call last):

I will look into this and might bother you later in another issue :)

could you post your code?

thank you very much!

aiohttp-server.py

import json
import asyncio
import logging
from aiohttp import web
from functools import wraps

from pymongo import MongoClient
import os
# Mongo setup:
client = MongoClient(os.getenv('MongoDBURL'))
db = client['logs']

logging.basicConfig(level=logging.INFO)
log = logging.getLogger()


def rest_handler(handler_func):
    """
    Allows handlers to return dict.
    Errors are handled and put in json format.
    """
    @wraps(handler_func)
    def wrapper(self, request):
        error_code = None
        try:
            res = yield from handler_func(self, request)
            result = dict(status='OK', result=res)
        except web.HTTPClientError as e:
            log.warning('Http error: %r %r', e.status_code, e.reason,
                        exc_info=True)
            error_code = e.status_code
            result = dict(error_code=error_code,
                          error_reason=e.reason,
                          status='FAILED')
        except Exception as e:
            log.warning('Server error', exc_info=True)
            error_code = 500
            result = dict(error_code=error_code,
                          error_reason='Unhandled exception',
                          status='FAILED')

        assert isinstance(result, dict)
        body = json.dumps(result).encode('utf-8')
        result = web.Response(body=body)
        result.headers['Content-Type'] = 'application/json'
        if error_code:
            result.set_status(error_code)
        return result

    return wrapper

class Rest:

    @asyncio.coroutine
    def index(self, request):
        return web.Response(body=b"Hello, world")

    @rest_handler
    @asyncio.coroutine
    def log(self, request):

        data = yield from request.post()


        # Get the Log Type to write to proper Mongo Collection / DB

        collection = db[dict(data)['col_name']]
        data_inserted = json.loads(json.dumps(dict(data)))
        # Data Inserted to Database here:
        response = collection.insert_one(data_inserted)

        # print(response.inserted_id)
        log.info('Message reveived %r', data_inserted)
        return ""


# def main():
rest = Rest()

app = web.Application()
app.router.add_route('GET', '/', rest.index)
app.router.add_route('POST', '/log', rest.log)

loop = asyncio.get_event_loop()
handler = app.make_handler()
f = loop.create_server(handler, '0.0.0.0', 3000)
srv = loop.run_until_complete(f)
log.info('serving on %r', srv.sockets[0].getsockname())
try:
    loop.run_forever()
except KeyboardInterrupt:
    pass
finally:
    log.info('\nBye')
    loop.run_until_complete(handler.finish_connections(1.0))
    srv.close()
    loop.run_until_complete(srv.wait_closed())
    loop.run_until_complete(app.finish())
    loop.close()


# if __name__ == '__main__':
#     main()


Virtual Environment:

  • Python 3.5.2
  • gunicorn version_info': (19, 7, 1)

Then I run this:
gunicorn aiohttp-server:app --bind localhost:3000 --worker-class aiohttp.worker.GunicornWebWorker

and I get the above error:

Could it be: the Port issue localhost to 0.0.0.0 ?

I have been following this guide to deploy the AIOHTTP + WSGI + Gunicorn, and that might be an issue as well. Since I wasn't sure how to connect the Gunicorn to NginX directly.

you don need to run loop in your script gunicorn worker does it.

here is slightly modified script, you can read mongo.

import json
import asyncio
import logging
from aiohttp import web
from functools import wraps

import os

logging.basicConfig(level=logging.INFO)
log = logging.getLogger()


def rest_handler(handler_func):
    """
    Allows handlers to return dict.
    Errors are handled and put in json format.
    """
    @wraps(handler_func)
    def wrapper(self, request):
        error_code = None
        try:
            res = yield from handler_func(self, request)
            result = dict(status='OK', result=res)
        except web.HTTPClientError as e:
            log.warning('Http error: %r %r', e.status_code, e.reason,
                        exc_info=True)
            error_code = e.status_code
            result = dict(error_code=error_code,
                          error_reason=e.reason,
                          status='FAILED')
        except Exception as e:
            log.warning('Server error', exc_info=True)
            error_code = 500
            result = dict(error_code=error_code,
                          error_reason='Unhandled exception',
                          status='FAILED')

        assert isinstance(result, dict)
        body = json.dumps(result).encode('utf-8')
        result = web.Response(body=body)
        result.headers['Content-Type'] = 'application/json'
        if error_code:
            result.set_status(error_code)
        return result

    return wrapper


class Rest:

    @asyncio.coroutine
    def index(self, request):
        return web.Response(body=b"Hello, world")

    @rest_handler
    @asyncio.coroutine
    def log(self, request):

        data = yield from request.post()


        # Get the Log Type to write to proper Mongo Collection / DB

        collection = db[dict(data)['col_name']]
        data_inserted = json.loads(json.dumps(dict(data)))
        # Data Inserted to Database here:
        response = collection.insert_one(data_inserted)

        # print(response.inserted_id)
        log.info('Message reveived %r', data_inserted)
        return ""


# def main():
rest = Rest()

app = web.Application()
app.router.add_route('GET', '/', rest.index)
app.router.add_route('POST', '/log', rest.log)

your gunicorn command will work:
gunicorn aiohttp-server:app --bind localhost:3000 --worker-class aiohttp.worker.GunicornWebWorker

That worked like a charm buddy! Thanks for the tips, your framework is awesome :)

thanks!

This thread has been automatically locked since there has not been
any recent activity after it was closed. Please open a [new issue] for
related bugs.

If you feel like there's important points made in this discussion,
please include those exceprts into that [new issue].

Was this page helpful?
0 / 5 - 0 ratings