Django, Twisted i WebSockety

Autor: nme · piątek, 15 Lipiec, 2011 · Komentarze: 2 · Tagi: django, twisted, websocket ·

Nigdy nie przypuszczałem, że pracowanie nad architekturą rozwiązań po stronie serwera sprawi mi tyle frajdy, a jednak :) Pod koniec lutego zaprezentowałem tutaj demo czata (kto sobie przetestował ten miał szczęście — obecnie demo jest wyłączone) ponieważ odkrywałem technologię long polling. Na jej poznawanie jest teraz trochę za późno (o jakieś 5 lat), ale warto było dowiedzieć się ile problemów ona stwarza. Postanowiłem teraz wybiec trochę do przodu i zaimplementować sobie obsługę WebSocketów.

To że powstał ten wpis — oznacza to, że się udało. Moje rozwiązanie okazało się być całkiem fajne i praktyczne. Zamierzam zatem przedstawić ewolucję architektury server-side z której korzystam.

Początki

Ten blog to nie Wordpress — to aplikacja w Django. Na serwerze nie ma Apache'a, jest Nginx — fenomenalny serwer www. Kiedy uruchamiałem wszystko pod koniec grudnia 2010 roku, architektura wyglądała tak:

architektura w oparciu o Nginxa

Stabilność i niesamowita wydajność przy niewielkim użyciu pamięci umożliwił nginx wraz z uwsgi serwującym aplikacje.

Ewolucja...

Aby mieć możliwość pracy z Comet musiałem wykorzystać Nginx Http Push Module. Byłem przy okazji ciekaw jak z tą techologią będzie współpracował haproxy. Ruch https został dodatkowo przepuszczony przez również zmodyfikowany stunnel (nałożona łata umożliwiająca http_x_forwarded_for). Po stronie aplikacji Django konieczne było dodanie własnego autorstwa middleware'a do obsługi http_x_forwared_for.

Long polling wymagał też lekkiego tuningu ustawień haproxy i serwera nginx.

Architektura w oparciu o Haproxy

Taka architektura ma jeszcze jeden niesamowicie praktyczny ficzer, który warto w tym momencie wymienić: jeśli jakaś aplikacja pracuje w trybie debugowania (w nginx zamiast uwsgi jako reverse proxy, a aplikacja jest uruchamiana komendą django-admin runserver), a zależy nam na prawdziwym adresie IP — dzięki middleware obsługującemu http_x_forwared_for nie będziemy widzieć 127.0.0.1 tylko prawdziwy adres IP. Było to dla mnie niezwykle przydatne podczas testowania uwierzytelniania oAuth.

Aplikacje działające na serwerze pracowały równie stabilnie i wydajnie, a całe rozwiązanie wymagało jednocześnie bardzo mało pamięci oraz umożliwiło bardziej elastyczne skanowanie.

Jako, że zamierzałem poznać bliżej WebSockety oraz postawiłem sobie za priorytet, aby działały one na tych samych adresach IP i portach (http i https) co sam serwer www (wykorzystanie http-alt jakoś nie wydaje mi się być właściwym rozwiązaniem). Z przeprowadzonych testów ustaliłem, że z już gotowych rozwiązań — taką funkcjonalność oferuje jedynie komercyjny Kaazing WebSocket Gateway.

Wykorzystanie komercyjnego serwera nie wchodziło w grę — bo przecież mogę sobie coś takiego samemu napisać. Konieczne było napisanie czegoś co stało by "od frontu", co byłoby świadome WebSocketów. Ponieważ haproxy niestety nie jest w stanie sobie z tym zadaniem na tą chwilę (i być może nigdy) poradzić — wymagana było wprowadzenie zmian. Najnowszy na tą chwilę draft WebSocketów — hixie 76 — uniemożliwia nawiązanie całego handshake'u przez serwer reverse-proxy. Należało zatem pójść o krok dalej.

Wykorzystanie Twisted

Konieczne było wykorzystanie serwera "event-driven".

Z pomocą przyszedł silnik Twisted oraz znaleziona na githubie biblioteka txWebSocket którą lekko poprawiłem. Tutaj zaczęła się prawdziwa frajda — zacząłem poznawać Twisted. Poszło szybko i sprawnie.

Zanim zabrałem się za WebSockety uruchomiłem reverse-proxy dla obecnych serwisów www (rozszerzając je o http_x_forwarded_for), skonfigurowałem sobie logowanie do poszczególnych facility, dodałem obsługę https i przygotowałem sobie skrypt startowy w oparciu o twistd.

Kiedy WebSockety zaczęły działać, zacząłem zastanawiać się w jaki sposób:

  • umożliwić dalszą komunikację z klientami WebSocketów
  • odseparować właściwe aplikacje od głównego serwera frontend'owego

Trochę poszukałem, poczytałem i znalazłem RabbitMQ — napisany w erlangu serwer implementujący protokół kolejkowania AMQP. To wystarczyło aby zamknąć krąg, który wygląda następująco:

Architektura w oparciu o Twisted

Kod minimalistycznej aplikacji (wykorzystującej przygotowaną bibliotekę umożliwiającą komunikację z klientami za pośrednictwem WebSocketów poprzez serwer frontendowy) jest krótki i bardzo czytelny — przedstawię go w następnym wpisie. Na tą chwilę aplikacje Twisted pracują w trzeciej warstwie (połączeniowej). Aby pracowały w warstwie czwartej (sesji) wykorzystując cookie sessionid ustawiane przez Django będę musiał wprowadzić kilka drobnych modyfikacji — myślę, że jeden wieczór wystarczy :) Aplikacje Twisted są również w stanie łączyć się do bazy za pośrednictwem Django ORM jeśli tylko jest zastosowany w nich model standalone (wystarczy z poziomu aplikacji ustawić odpowiednio DJANGO_SETTINGS_MODULE oraz odpowiednie ścieżki — bułka z masłem).

Najnowsza z przedstawionych architektur jest całkowicie zgodna z modelami zarówno dla klasycznego www (Django MTV) jak i WebSocketów (event-driven) — więc zalet jest wiele, a całość można sobie przygotować w zaledwie kilka wieczorów.

Komentarze

Ilość komentarzy do wpisu “Django, Twisted i WebSockety”: 2
  1. Wow.

    Fajnie jest zobaczyć zakrojony na tak szeroką skalę "eksperyment"

    Zastanawia mnie tylko sensowność ostatniego rozwiązania. Chodzi mi o stawianie nginx _za_ twisted. Wydaje mi się że wszystkie zalety nginx w takiej konfiguracji wyparują i nie ma już sensu z niego korzystać. Czy nie dało by się podłączyć aplikacji wprost przez reverse-proxy (bez uwsgi)?

    Z longpooling masz całkowitą rację, za późno już na wdrażanie tamtej technologii, ale z socketami znów jest odwrotny problem, ogon przestarzałych przeglądarek może zepsuć humor.

    Swoją drogą, coraz częściej umieszcza się aplikacje webowe za reverse-proxy, a po raz pierwszy widzę zainteresowanie adresem ip klienta.. Zawsze mnie to zastanawiało.

Zostaw komentarz