Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CONTRIB: easily-integrated minimal http server example #8385

Closed
1 task done
davidmcnabnz opened this issue Apr 26, 2024 · 1 comment
Closed
1 task done

CONTRIB: easily-integrated minimal http server example #8385

davidmcnabnz opened this issue Apr 26, 2024 · 1 comment

Comments

@davidmcnabnz
Copy link

Is your feature request related to a problem?

Most of the server examples in the documentation are based on a somewhat restrictive pattern of running an HTTP[S] server largely in isolation, with no easy way to have it interacting with other components.

This leaves a huge gap for real world usage. In countless cases, developers want to implement a web server, but also connect it to other clients and/or servers and/or data sources.

Developers usually don't want to be caught in a pattern where the web server is the "star attraction", and difficult to connect to other elements. They'd rather have the web server running in and around numerous elements, and able to freely exchange data and control with them.

I've written an example (see section below) for how to make aiohttp easier to integrate with other asyncio elements.

Describe the solution you'd like

I recommend the documentation be updated to include an example, such as what I've provided below, of how to run an HTTP server in a way that makes it very simple to integrate with other program elements running in the asyncio environment.

This will help other developers to incorporate aiohttp-based servers in with their other work, much more easily and quickly, than digging through the aiohttp code to figure out how to adapt it to their patterns.

Describe alternatives you've considered

Here is my implementation of a very minimal server. Feel free to use it verbatim, or with adaptations, in the aiohttp documentation. The focus for this demo is making it very trivial to integrate with other asyncio elements, and support a broader range of programming patterns.

import aiohttp, aiohttp.web

async def runLocalNanoServer(
        *,
        port,
        handler,
        userData=None,
        client_max_size=65536,
):
    """
    Given a listen port, and a single handler coroutine function, run an absolutely
    minimal web server serving from that port

    :param port: TCP port to listen on
    :param handler: a coroutine which gets called with (req, ctx) when
    a request is received
    :param userData: optional, an object to pass to the handler to help it discern from
    different concurrent executions
    :return: nothing
    """
    # first set up and configure the server
    ctx = {
        'user': userData,
        'port': port,
    }

    # ensure that inbound requests result in a call to the handler function,
    # passing both the aiohttp Request, as well as a 'context' dict, containing
    # all the objects involved in creating/running this server, as well as whatever userData
    # object was passed in
    def wrapHandler(handler):
        async def call_handler(req, *args, **kw):
            result = await handler(req, ctx)
            return result
        return call_handler

    # process starts with creating an abstract 'Application' object, and adding
    # routes to handle the request methods and URLs
    app = ctx['app'] = aiohttp.web.Application(client_max_size=client_max_size)

    # wrap the given handler function, then assign it into the app as 2 routes,
    # one for no URI, other for full wildcard URLs
    hdlrWrapped = wrapHandler(handler)
    app.router.add_route('*', '', wrapHandler(handler))
    app.router.add_route('*', '/{tail:.*}', wrapHandler(handler))

    # build the server and get it ready, deliberately hardwired to localhost to avoid
    # presenting an attack surface
    runner = ctx['runner'] = aiohttp.web.AppRunner(app)
    await runner.setup()
    site = ctx['site'] = aiohttp.web.TCPSite(runner, '127.0.0.1', port, ssl_context=None)
    await site.start()
    server = ctx['server'] = runner.server

    # now run it by launching the low-level asyncio server run method
    outcome = await site._server.serve_forever()
    return

async def runNanoServer():
    """
    Now, an equally minimal example of launching/running this minimal server
    """
    # ensure each hit returns different data
    seq = [0]

    # absolutely minimal handler
    async def hdlr(req, ctx):
        # when this handler is called, it has access to all the internals, plus the original
        # userData. It's up to the handler to dive into the Request object to get everything it needs.
        # Based on this, it's possible to put objects and/or callable methods into the 'userData' value
        # of the 'ctx' object we receive here
        resp = f"seq={seq[0]}"
        seq[0] += 1
        return aiohttp.web.Response(text=resp)

    # now run the server
    await runLocalNanoServer(
        port=1234,
        handler=hdlr,
    )
def main():
    asyncio.run(runNanoServer())
    print("main finished")

if __name__ == '__main__':
    main()

Related component

Server

Additional context

No response

Code of Conduct

  • I agree to follow the aio-libs Code of Conduct
@Dreamsorcerer
Copy link
Member

Dreamsorcerer commented Apr 26, 2024

I'm not very clear on what this is trying to achieve. I don't see any integration, just some almost monkey-patching of handlers to add an extra parameter. I'd highly discourage that approach; if you need those things in every handler, just add it to the Application object (https://docs.aiohttp.org/en/stable/web_advanced.html#data-sharing-aka-no-singletons-please).

I see 2 ways that aiohttp should be run. The first is with the web app being the main runner of the application, and any other components being initialised off of it. This is probably the right approach 95-99% of the time. Documentation to help users run these components correctly is at: https://docs.aiohttp.org/en/stable/web_advanced.html#complex-applications

The second approach is for more complex applications where the web app is a smaller component (e.g. homeassistant, which is basically an OS), in which case the runners should be used directly, and the user now needs to handle correct shutdown logic etc. themselves. This is documented at: https://docs.aiohttp.org/en/stable/web_advanced.html#application-runners

@Dreamsorcerer Dreamsorcerer closed this as not planned Won't fix, can't repro, duplicate, stale Apr 29, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants