mliezun / caddy-snake

Caddy plugin to serve Python apps
MIT License
68 stars 2 forks source link

ASGI support for FastAPI #5

Closed nickchomey closed 2 months ago

nickchomey commented 3 months ago

This is fantastic! But I'm wondering if there's any sort of ASGI support, in order to run FastAPI apps?

Or am I misunderstanding how this all works and there's another approach for this?

mliezun commented 3 months ago

Hi @nickchomey!

Caddy snake doesn't have ASGI support yet. But this project might be useful to run ASGI apps with a WSGI server: https://github.com/abersheeran/a2wsgi.

I tested with a simple FastAPI app and seems to work:

example_fastapi.py

from fastapi import FastAPI
from a2wsgi import ASGIMiddleware

app = FastAPI()

@app.get("/app")
async def root():
    return {"message": "Hello World"}

app = ASGIMiddleware(app)

Caddyfile

{
    http_port 9080
    https_port 9443
    log {
        level info
    }
}
localhost:9080 {
    route /app {
        python "example_fastapi:app"
    }
}

I think this is just a workaround for now.

Thanks for your comments and for filing the issue. And hope this helps. Im gonna start working on ASGI support as soon as possible

nickchomey commented 3 months ago

Now that I think about it some more (it's been a little while since I've worked with Python, FastAPI etc...), perhaps this is a non-issue...

FastAPI is, among other things, meant for running a long-running async server. You start it once and it receives requests, processes them and returns the result.

As such, surely we can just use Caddy as a reverse proxy without any WSGI/ASGI stuff...

Even still, FastAPI itself has documentation about how to use Gunicorn (WSGI) in front of Uvicorn (ASGI). https://fastapi.tiangolo.com/deployment/server-workers/

Perhaps that could be used here somehow?

One side/clarifying question: it looks like what this plugin does is allow you to start a new python process upon each request that hits Caddy. If so, under what scenarios would that be desirable as opposed to just reverse proxying to a long-running server (like FastAPI)? Surely there's extra overhead to bootstrapping it all for each request

Or, again, perhaps I'm misunderstanding/forgetting something fundamental here...

Thanks!

mliezun commented 3 months ago

This plugin is meant to avoid adding an extra layer of reverse proxy. What the plugin is doing is embedding the Python interpreter inside Caddy. It doesn't create a new process on each request. The requests are all handled within Caddy.

To make it clear, when you configure a Caddyfile like this:

route /app {
    python "example_fastapi:app"
}

What happens internally is that Caddy imports the FastAPI app using the Python C API and is initialized on startup, the FastAPI server runs inside Caddy from then on.

On each request to /app, Caddy passes the data directly to FastAPI "in-memory" without creating a new process.

nickchomey commented 3 months ago

Ah, very cool! Clearly I was, indeed, missing something fundamental!

And, now that I think about it more, what I was describing in my previous comment was wrong. I think Uvicorn and/or gunicorn are the long-running servers, hosting the fastapi app.

So,this plugin replaces uvicorn/gunicorn rather than fastapi. Sort of in the same sense that the popular new Frankenphp caddy module replaces php-fpm and calls the php application code directly.

Is that correct?

(sorry again, it's been a little while since I've worked with Python and just stumbled upon this as I start to get into Caddy)

mliezun commented 3 months ago

Yep, exactly like that.

It serves the same purpose as gunicorn/uvicorn.

And indeed this plugin works similarly as Frankenphp replacing the php-fpm calls.

nickchomey commented 3 months ago

Fantastic! Thanks for your patience and explanations. I look forward to using this when I get back to incorporating Python into my application architecture! Embedding languages right into Caddy seems to be the future!

mliezun commented 3 months ago

Thank you!

I'm here to help, don't hesitate in asking other questions if further clarification is needed.

mliezun commented 2 months ago

ASGI HTTP support is out!

There's a new directive to use it: module_asgi.

You can import your apps as follows:

route /app {
  python {
    module_asgi "example_fastapi:app"
  }
}

This doesn't require any external dependencies and should work out of the box.

Noting here that it only supports HTTP for now, Websockets is not supported yet.