Django Channels with websockets and http

I have a large Django application, that is WSGI based, but it also has websockets (for notifications) implemented by using Django Channels.

This application also offers users to download some files.
Up until now, those files were only smaller reports (<2MB).
Plan is to enable download of some other stuff, that can be in GBs in size.

Implementing that in WSGI fashion is a futile effort, as it freezes the whole application for the user, making the UX bleak.

I want to use django channels http protocol for creating this download feature.

I don’t have a lot of experience using Django and its libraries, but the channels documentation, or to be hones in any other part of the internet,
I was unable to find how to make http and websocket protocols coexist, i.e how to create asgi.py that will support both asgi http urlpatterns, and websocket urlpatterns

Example in the official documentation,
http is set to get_asgi_application(),
and websocket has an actual urlrouter.

I see that AsyncHttpConsumer exists,
so I believe this should be possible,
but I’m unable to figure it out.

I won’t paste my lame attempts here that didn’t work, but basically just removing get_asgi_application() and replacing it with URLRouter didn’t work,
and I don’t have a better idea.

Again, what I’m surprised by is that I’m unable to find any example of this on the internet, as I would expect it to be a common use case, not necessarily download, but any other async stuff over channels.

Any code pointers / examples / suggestions on how to have urlpatterns for both http and websocket protocol in Django Channels would be highly appreciated

Why do you want to try and implement http over a websocket? I’m not sure I see any benefit to that.

Quite the opposite. I wouldn’t expect to ever see it used. One of the benefits of websockets is that it’s a persistent connection - as such, there’s no need to present things like headers or cookies or session ids with each websocket frame.

If these are actual files existing on a file system, and not data being dynamically generated at the time of the request, Django shouldn’t be handling this at all - your webserver should be serving these.

If they are being dynamically generated, then you could implement your own “file copy” protocol within your consumer, matched with the appropriate JavaScript in the client.

Thanks for your thoughts Ken!

I’m not looking to implement http over a websocket,
I’m looking to have them side by side, where websockets would handle the notification part of the application, and http would handle async download of large files.
Even disregarding the download part,
having async http would enable me to use async django querying methods, and increase the amount of users I could serve simultaneously.
What would be the use of http protocol in Channels in your opinion?

To the second point,
files are dynamically generated.
I have to download the file from google storage/S3, and then stream it to the user,
due to the fact that it’s not possible to limit the number of times file can be downloaded,
and just setting an expiry time could potentially be abused

Ok, I get you now - just a little confusion on terminology on my part. (Unfortunately easy to do with the name “Channels” being a bit overloaded here.)

Without having tried this myself, I’m guessing you tried something like this:

application = ProtocolTypeRouter({
    # Attempt to attach a consumer for http requests
    "http": URLRouter([path("something/", MyHttpConsumer.as_asgi())]),
    # WebSocket chat handler
    "websocket": AllowedHostsOriginValidator(
        AuthMiddlewareStack(
            URLRouter([
                path("chat/admin/", AdminChatConsumer.as_asgi()),
                path("chat/", PublicChatConsumer.as_asgi()),
            ])
        )
    ),
})

Is that correct? If so, what were the results?

It’s basically what I would expect to have work, with possibly some other minor changes necessary - at least that would be the direction I’d be working in.

There’s another change as part of this as well. My (very uncertain) understanding of this is that get_asgi_application() causes some other initialization to occur, which doesn’t happen if that call is removed. I fix this by adding the following two lines immediately after my import django line and before the from channels.auth import ... line:

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'my_project.settings')
django.setup()

Nginx is capable of proxying an S3 bucket (Using NGINX as an Object Storage Gateway - NGINX). This gives you the additional ability to add your own security layer to the access of the objects. (Nginx is an amazingly powerful and flexible server - it is capable of doing a lot more than just forwarding http and websocket requests.)