django / channels

Developer-friendly asynchrony for Django
https://channels.readthedocs.io
BSD 3-Clause "New" or "Revised" License
6.11k stars 800 forks source link

404 and 301 Redirect Loops when using runserver #1075

Closed justinttl closed 6 years ago

justinttl commented 6 years ago

I was just trying to integrate channels into an existing django/DRF project and I have been running into an issue where my original urls paths are breaking in runserver. My root index view leads to 301 infinite redirects loop and my other views like /rfs/ results in a 404.

The app works when I remove "channels" from installed_apps The app also works when I run the asgi application directly through daphne and serve the static separately.

I am using django auth backend with login_required for all my views. I am fairly new to django and would love some help on this!

my_app/urls.py

urlpatterns = [
    path('users/', include('django.contrib.auth.urls')),
    path('admin/', admin.site.urls),
    path('api/', include('api.urls', namespace='api')),
    url(r'^.*', index, name='index'),
]

my_app/routing.py

from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
from django.urls import path

from api.consumer import EchoConsumer

application = ProtocolTypeRouter({
    'websocket': AuthMiddlewareStack(
            URLRouter([
                path('', EchoConsumer)
            ])
    )
})

Settings

# Application definition
INSTALLED_APPS = [
    'channels',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'django.contrib.postgres',
    'django_extensions',
    'rest_framework',
    'rfs.apps.RfsConfig',
    'api.apps.ApiConfig',
    'import_export',
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

Versions

channels==2.1.1
daphne==2.1.2
andrewgodwin commented 6 years ago

Hm, nothing looks wrong there. When you say it works when you run through daphne, do you mean that it:

justinttl commented 6 years ago

Hi Andrew,

Yea, it works when I run the asgi server thru daphne daphne -p 8000 my_app.asgi:application

But I obviously loose the hot reloading feature of runserver.

channels is definitely overloading the runserver command since the output is different....but I have noticed that the output is different when running thru daphne vs running thru runserver

console output of runserver:

Starting ASGI/Channels version 2.1.1 development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
2018-06-10 01:20:06,572 - INFO - server - HTTP/2 support not enabled (install the http2 and tls Twisted extras)
2018-06-10 01:20:06,573 - INFO - server - Configuring endpoint tcp:port=8000:interface=127.0.0.1
2018-06-10 01:20:06,577 - INFO - server - Listening on TCP address 127.0.0.1:8000
Not Found: /rfs/
[2018/06/10 01:20:11] HTTP GET /rfs/ 404 [0.04, 127.0.0.1:52800]
[2018/06/10 01:20:20] WebSocket HANDSHAKING / [127.0.0.1:52870]
Received Connection
[2018/06/10 01:20:20] WebSocket CONNECT / [127.0.0.1:52870]
Received {'message': 'Hello World'}

out from running daphne directly

2018-06-10 01:25:04,561 INFO     Starting server at tcp:port=8000:interface=127.0.0.1
2018-06-10 01:25:04,562 INFO     HTTP/2 support not enabled (install the http2 and tls Twisted extras)
2018-06-10 01:25:04,562 INFO     Configuring endpoint tcp:port=8000:interface=127.0.0.1
2018-06-10 01:25:04,563 INFO     Listening on TCP address 127.0.0.1:8000
127.0.0.1:55224 - - [10/Jun/2018:01:25:17] "GET /rfs/" 200 29851
127.0.0.1:55342 - - [10/Jun/2018:01:25:24] "WSCONNECTING /" - -
2018-06-10 01:25:24,900 ERROR    Received Connection
127.0.0.1:55342 - - [10/Jun/2018:01:25:24] "WSCONNECT /" - -
2018-06-10 01:25:32,429 ERROR    Received {'message': 'Hello World'}

The error is just my logging level

andrewgodwin commented 6 years ago

Well, that is very strange. The two do have different output, but runserver just uses daphne under the surface, so they shouldn't actually be different.

Do you have anything else installed that might be fiddling with runserver? Did you change something in your asgi.py from the default?

justinttl commented 6 years ago

I simply follow the tutorial

asgi.py

import django
import os
from channels.routing import get_default_application

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "my_app.settings")
django.setup()
application = get_default_application()

settings.py

# ASGI
ASGI_APPLICATION = 'my_app.routing.application'

# Application definition
INSTALLED_APPS = [
    'channels',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'django.contrib.postgres',
    'django_extensions',
    'rest_framework',
    'rfs.apps.RfsConfig',
    'api.apps.ApiConfig',
    'import_export',
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

# WSGI for waitress
WSGI_APPLICATION = 'my_app.wsgi.application'

Also, running nginx proxy passing to daphne works as well. It seems like it is only runserver that is somehow breaking for me.

Additionally, when I run python manage.py show_urlrs from django extension, the proper routes are there! I have also tried disabling django-extension but that didn't resolve it.

/api/locations/search   api.views.LocationViewSet   api:location-search
/api/locations/search\.<format>/    api.views.LocationViewSet   api:location-search
/api/locations/validate_address api.views.LocationViewSet   api:location-validate-address
/api/locations/validate_address\.<format>/  api.views.LocationViewSet   api:location-validate-address
/api/quicklinks api.views.QuickLinkViewSet  api:quicklink-list
/api/quicklinks\.<format>/  api.views.QuickLinkViewSet  api:quicklink-list
/api/work_orders    api.views.WorkOrderViewSet  api:work_orders-list
/api/work_orders/<pk>   api.views.WorkOrderViewSet  api:work_orders-detail
/api/work_orders/<pk>/submit    api.views.WorkOrderViewSet  api:work_orders-submit
/api/work_orders/<pk>/submit\.<format>/ api.views.WorkOrderViewSet  api:work_orders-submit
/api/work_orders/<pk>\.<format>/    api.views.WorkOrderViewSet  api:work_orders-detail
....

This might be an important detail, but I am running the django app through pycharm's remote interpreter feature. I am technically running in debug mode on a remote server. I have a SSH port forwarding tunnel setup to tunnel 8000 back to localhost. I don't see that being an issue though since it clearly works running daphne on 8000

andrewgodwin commented 6 years ago

Could you try running it locally without the PyCharm remote interpreter stuff and see if it still breaks? This works just great for me locally and pretty much everyone else who runs the tutorial, so we need to work out what's different for you :)

justinttl commented 6 years ago

Sorry that took a while, I needed to remove some view since they had dependencies I couldn't build locally in a windows environment.

I tried setting up a 3.6 env with

channels==2.1.1
daphne==2.1.2
Django==2.0.6
django-extensions==2.0.7
djangorestframework==3.8.2
Twisted==18.4.0

Still doesn't work, getting redirect loops and 404s. I tried running the tutorial chat app in that venv and it works... Very confused as to what my app has that causes routes to break when I activate channels.

andrewgodwin commented 6 years ago

Hmm, if the tutorial works then it's definitely something in the app. Have you tried removing anything that might add middleware or url mutation?

justinttl commented 6 years ago

My apps are pretty standard and middlewares are the same as tutorials. From django extension's show url command, all my urls are shown properly too so django view handler side is definitely recognizing the routes. Seems like a problem with how ASGI passes to the view handler? Is there a place I can break point to look into the internals of how url are passed from ASGI handler?

andrewgodwin commented 6 years ago

Yup, you can get the raw ASGI scope as request.scope, if I remember right. See what that looks like.

justinttl commented 6 years ago

Where should I breakpoint to intercept the scope? Due to the broken routes, it is not even hitting any of my views.

andrewgodwin commented 6 years ago

Hm, you can break inside of one of the routers if you want. That should do it.

justinttl commented 6 years ago

I found the cause of the issue!

In my project, I have used the setting FORCE_SCRIPT_NAME and I think that breaks channels somehow.

justinttl commented 6 years ago
DEPLOYED_ROUTE = "http://127.0.0.1:8000/"
FORCE_SCRIPT_NAME = urlparse(DEPLOYED_ROUTE).path

I mainly use that setting for making my app deployable under any route prefix when nginx proxy passes

andrewgodwin commented 6 years ago

Ah, channels probably handles slashes on the script name differently.

justinttl commented 6 years ago

Being different than django default behavior is probably not intended right?

andrewgodwin commented 6 years ago

No, it's not. If you could work out what the difference is (as in which combination of trailing slashes works, and which does not), then I can fix it.

andrewgodwin commented 6 years ago

Will have to close this for inactivity soon unless there's actionable changes to make!

justinttl commented 6 years ago

Hi Andrew,

I will verify today! Sorry for the delay

justinttl commented 6 years ago

Hi Andrew,

I will close this for now until I have time to investigate further. Will make a more explicit issue then.

Thanks again!