2

The following code taken from the aiohttp docs https://docs.aiohttp.org/en/stable/ does work:

from aiohttp import web

async def handle(request):
    name = request.match_info.get('name', "Anonymous")
    text = "Hello, " + name
    return web.Response(text=text)

app = web.Application()
app.add_routes([web.get('/', handle),
                web.get('/{name}', handle)])

if __name__ == '__main__':
  web.run_app(app)

enter image description here

But having the webserver hijack the main thread is not acceptable: the webserver should be on a separate non-main thread and subservient to the main backend application.

I can not determine how to run the webapp on a secondary thread. Here is what I have tried:

  1. It is not possible to run the snippet of code in ipython repl:

I tried to run it this way:

#if __name__ == '__main__':
web.run_app(app)

and am notified something about no current event loop

Traceback (most recent call last):
  File "/usr/local/lib/python3.8/site-packages/IPython/core/interactiveshell.py", line 3293, in run_code
    async def run_code(self, code_obj, result=None, *, async_=False):
  File "<ipython-input-8-344f41746659>", line 13, in <module>
    web.run_app(app)
  File "/usr/local/lib/python3.8/site-packages/aiohttp/web.py", line 393, in run_app
    def run_app(app: Union[Application, Awaitable[Application]], *,
  File "/usr/local/Cellar/python@3.8/3.8.1/Frameworks/Python.framework/Versions/3.8/lib/python3.8/asyncio/events.py", line 628, in get_event_loop
    def get_event_loop(self):
RuntimeError: There is no current event loop in thread 'Thread-11'.

So then .. what it can only be run in main? I'm missing something here..

I tried running in another standalone script but on a subservient thread:

def runWebapp():
  from aiohttp import web

  async def handle(request):
      name = request.match_info.get('name', "Anonymous")
      text = "Hello, " + name
      return web.Response(text=text)

  app = web.Application()
  app.add_routes([web.get('/', handle),
                  web.get('/{name}', handle)])
  web.run_app(app)

if __name__ == '__main__':
  from threading import Thread
  t = Thread(target=runWebapp)
  t.start()
  print('thread started let''s nap..')
  import time
  time.sleep(50)

But that gives basically the same error:

Exception in thread Thread-1:
Traceback (most recent call last):
  File "/usr/local/Cellar/python@3.8/3.8.1/Frameworks/Python.framework/Versions/3.8/lib/python3.8/threading.py", line 932, in _bootstrap_inner
    self.run()
  File "/usr/local/Cellar/python@3.8/3.8.1/Frameworks/Python.framework/Versions/3.8/lib/python3.8/threading.py", line 870, in run
    self._target(*self._args, **self._kwargs)
  File "/git/bluej/experiments/python/aio_thread.py", line 12, in runWebapp
    web.run_app(app)
  File "/usr/local/lib/python3.8/site-packages/aiohttp/web.py", line 409, in run_app
    loop = asyncio.get_event_loop()
  File "/usr/local/Cellar/python@3.8/3.8.1/Frameworks/Python.framework/Versions/3.8/lib/python3.8/asyncio/events.py", line 639, in get_event_loop
    raise RuntimeError('There is no current event loop in thread %r.'
RuntimeError: There is no current event loop in thread 'Thread-1'.

So how do I get this webapp off the main thread and make it play along with the other threads in my application

WestCoastProjects
  • 58,982
  • 91
  • 316
  • 560
  • Why are you trying to use asyncio/aiohttp *and* threads? Why are you importing inside of a function? All of this looks really....strange. – Jared Smith Mar 31 '20 at 21:02
  • @JaredSmith - I stated what the intent is: to get the web serving off of the main thread. Am I doing it correctly? Obviously not or this question would not be asked. Please provide guidance/ sample code that does it. Does that mean not importing inside a function? Does that not mean creating threads? Fine - but please show how it is done. – WestCoastProjects Mar 31 '20 at 21:08
  • The entire point of asyncio and libraries built atop it like aiohttp are to *aviod threads*. It's a non-blocking async event loop, so that your app can walk and chew gum at the same time without multithreading, and it's generally faster than threads for io-bound tasks. Either run a server in a separate thread, or run everything on the main thread with asyncio, but mixing the two is asking for trouble. – Jared Smith Mar 31 '20 at 21:10
  • Please show the code to do this: I am a strong preemptive multi-threaded programmer and this co-routines and asyncio are not clear yet. Can you point to code doing what is requested in this question? – WestCoastProjects Mar 31 '20 at 21:12
  • So you want code that runs a webserver on a separate thread? NP. Gimme a minute. – Jared Smith Mar 31 '20 at 21:15
  • Does this answer your question? [Running any web server event handler on a secondary thread](https://stackoverflow.com/questions/60992443/running-any-web-server-event-handler-on-a-secondary-thread) – aaron Feb 16 '22 at 06:17

1 Answers1

0

Here you go:

import http.server
import threading
import socketserver

PORT = 8000

Handler = http.server.SimpleHTTPRequestHandler

def serve_forever():
    with socketserver.TCPServer(("", PORT), Handler) as httpd:
        httpd.serve_forever()

if __name__ == "__main__":
    threading.Thread(target=serve_forever).start()
    while 1:
        x = input("enter a number")
        print("You entered {}".format(x))

N.B. this is a neat party trick, but not necessarily useful for production work: the documentation for the http.server module says in flaming red letters at the top of the doc page not to use it in production. But almost all python webserver frameworks operate as WSGI servers and aren't designed to work the way you seem to want them to: they generally expect to be run by something else like gunicorn or apache.

I strongly recommend if you need an HTTP server for e.g. monitoring a running app that you use asyncio instead and use coros for everything, but you can roll your own threading scenario as above if you really want to. You can see that you can still interact with the shell in the infinite input loop while you can also curl localhost:8000 to get an HTML page containing a directory listing.

Just pass in a non-default handler of your own creation to do other stuff like return app state in JSON format.

Jared Smith
  • 19,721
  • 5
  • 45
  • 83
  • 2
    How did we end up with the `SimpleHttpRequestHandler` instead of `aiohttp` ? We will be using the modern approach, plugins, and `websocket` support of `aiohttp`: not this. – WestCoastProjects Mar 31 '20 at 22:04
  • @javadba I thought that's what you wanted. Your aiohttp code that you posted in your question should work if you run it on the main thread. – Jared Smith Mar 31 '20 at 22:05
  • If you have a few lines of code to move around to make the OP code work I'll award. That's what I am looking for : but no "should"s. There is some detail - or possibly more than detail I am missing. – WestCoastProjects Mar 31 '20 at 22:06
  • I don't mind tweaking your OP code, but I really think the piece of the picture you're missing is WSGI: you seem to have this mental model of a web server being something you add to a running application. But nothing (I know of) in python is built that way. As I said, all the python web frameworks are meant to be plugged in to e.g. apache in a LAMP or similar stack and are basically glorified request handlers. It just isn't the JVM. I'm not saying you *can't* write that kind of program in python, but it may not be as easy as you'd expect. – Jared Smith Mar 31 '20 at 22:17
  • Yes that well describes my mental model. And I do end up surprised time and again that the `python` libs do not accommodate it. I'll upvote here for useful perspective and leave the question open to see if there were actually a way to get what I want. Having my backend app being run as a webapp is a cart before the horse. I'd have to extract that out into a separate web-only app and use some other message passing mechanism – WestCoastProjects Mar 31 '20 at 22:21
  • Did you copy and paste the lower code annotated by `subservient thread:` ? The top code does run but on the main thread . – WestCoastProjects Mar 31 '20 at 22:23
  • I like your thinking, and I really hope someone swoops in here and proves me wrong with something awesome, but I think I would have stumbled across it, because I *did* end up putting the cart before the horse with an application I wrote. – Jared Smith Mar 31 '20 at 22:23
  • Do you have any idea why I can't start the event loop on a manually created thread? What's so special about the `main` thread that can't happen on a different one? – WestCoastProjects Mar 31 '20 at 22:27
  • 1
    @javadba that is probably worth a question in it's own right, and I don't really know, but the answer may just be that as I said the whole point of async/await is to use higher-level concurrency constructs over threads so they didn't design it to play nice with manual threads. – Jared Smith Apr 01 '20 at 11:23