Django 로컬 환경에서 WebSocket-WSS 프로토콜로 테스트 해보자!(트러블 슈팅)

승톨·2021년 3월 24일
1
post-thumbnail

개발 환경

  • python : 3.8
  • django : 3.1.7
  • channels : 2.4.0
  • channels-redis : 3.0.0
  • django-sslserver : 0.22

문제 상황

  • 현재 https로 서버를 띄우기 때문에 Websocket도 wss로 요청해야 하는 상황.
  • 브라우저에서 wss 프로토콜로 핸드쉐이킹 요청을 했으나, GET /rooms/xksk 404 에러가 계속 뜸.(저 url은 서버에 요청하는 주소)

트러블 슈팅

왜 ws 프로토콜로 요청했는데 http프로토콜 GET으로 요청하는지 몰라서, 검색해보니 Websocket 구동방식이, HTTP 요청을 upgrade해서 프로토콜을 스위칭해서 쓰는 방식인데, Get Method로 요청을 보내면 서버에서 스위칭을 시켜주는 것이라고 한다.

즉, GET 요청만 들어오고 스위칭이 동작하지 않아 Websocekt 연결이 안 된 것이다.

혹시 http로 통신해도 안 되려나 했더니, http는 너무 잘 된다. 따라서 wss 혹은 https의 문제라고 생각해서 이쪽을 파보기로했다.

구글링을 더 해보니, 내가 https를 위해 로컬에서 파이썬 서버를 실행할 때 쓰던 runsslserver 는 기본적으로 [wsgi.py] 파일을 읽어서 실행시키지, [asgi.py] 파일을 기본으로 실행시키지 않는다고 한다.

참고로 wsgi는 파이썬이 HTTP 요청을 처리하기 위한 전통적 동기 방식의 인터페이스이다.
asgi는 실시간으로 websocket을 통해 주고받는 요청 등의 비동기를 처리하기 위한 인터페이스라고 보면된다.
참고 : https://nitro04.blogspot.com/2020/01/django-python-asgi-wsgi-analysis-of.html

대신 runserver 로 돌리면 올바르게 [asgi.py] 을 읽어서 daphne를 사용한 Django channels 애플리케이션을 실행시켜준다고 한다.

해결책

그래서 runsslserver 로 HTTPS 요청을 위한 서버를 따로 두고(wsgi.py),
daphne 로 WSS 요청을 위한 서버를 따로 둬서(asgi.py) 각각 실행해보았다.

(터미널 창 2개를 따로 띄웠다.)

실행 경로는 runserver 를 실행하듯이 루트 디렉토리에서 실행하면 된다.

커맨드1

python manage.py runsslserver {본인의 public ip}:{실행 port} --certificate {본인이 만든 SSL cert파일} --key {본인이 만든 private key}

커맨드2

daphne -e ssl:{실행 port-보통 8001로 했음.}:privateKey={본인이 만든 private key}:certKey={본인이 만든 SSL cert파일} {asgi.py파일이 있는 폴더}.asgi:application

위의 crt 파일과 key파일은 둘다 동일한 파일을 사용하면 된다. crt, key 파일 만드는 법은 검색해보기 바란다.

asgi.py 파일이 있는 폴더는, 맨처음 장고 프로젝트를 만들 때 사용하는 폴더명이다.

나같은 경우는 아래와 같았다.

root directory

  • config 폴더
    • asgi.py
    • settings.py
    • 등등
  • user 폴더(app 폴더)

이렇게 서버를 띄운 다음에 클라이언트 쪽에서 wss://{xxx.xx.xx.xxx:8001}/{요청하는 url} 로 WebSocket을 만들면 된다.

예시

var loc = window.location;
var wsStart = 'ws://';
if (loc.protocol == 'https:') {
     wsStart = 'wss://'
}
// var endpoint = wsStart + 'your_ip_address:port_given_to_daphne_server' + '/ws/home';
// For above command, it look like this
var endpoint = wsStart + 'xxx.xx.xx.xxx:8001' + '/ws/home';
// Note the websocket port is 8001
var socket = new WebSocket(endpoint);

근데 이렇게 해서 요청해도 오류가 났다.

daphne쪽에서 Exception inside application: Django can only handle ASGI/HTTP connections, not websocket.

라는 오류가 나길래 한번 더 구글링해보니, asgi.py 파일의 설정 문제였다.

원래 아래와 같이 되어있던 코드를

import os
import django
from django.core.asgi import get_asgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings')

django.setup()
application = get_asgi_application()

이렇게 바꿨다.

import os
import django
from channels.routing import get_default_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings')

django.setup()
application = get_default_application()

바꾸고 daphne 서버를 재실행하고 소켓 요청하니까 성공!

마무리

처음에는 정보가 잘 안나오길래, nginx에 올려서 Let's encrypt로 퍼블릭 인증서를 받아서 하는 정석? 루트를 고민했으나 역시 구글링을 하니까 일단 해결됐다.

그치만 결국 웹서버를 띄워야하기 때문에 nginx+ssl+daphne 조합으로 갈 것 같다.

그래도 이 글을 읽는 분이 이 문제 때문에 디버깅하느라 시간을 쓰는걸 줄일 수 있는데 도움이 되면 좋겠다.🙏

p.s 참고로 간단하게 서버사이드에서 소켓 통신이 잘 됐는지 확인하려면 아래의 코드를 실행시켜보면 된다.
websockets 은 pip install로 받아야 하고, HTTP-WS 요청만 받는 코드이다.

from asgiref.sync import async_to_sync
import websockets

def test_url(url, data=""):
    conn = async_to_sync(websockets.connect)(url)
    async_to_sync(conn.send)(data)

test_url("ws://{본인의 호스트}:{본인의 포트}/{요청하는 url}")

참고 했던 글

profile
소프트웨어 엔지니어링을 연마하고자 합니다.

0개의 댓글