Polski

Dogłębna analiza Tornado, frameworka webowego i biblioteki do programowania asynchronicznego w Pythonie. Naucz się tworzyć skalowalne, wydajne aplikacje.

Dokumentacja Tornado: Kompleksowy przewodnik dla programistów na całym świecie

Tornado to framework webowy i biblioteka do programowania asynchronicznego w Pythonie, pierwotnie opracowana w FriendFeed. Jest szczególnie dobrze przystosowana do long-polling, WebSockets i innych aplikacji wymagających długotrwałego połączenia z każdym użytkownikiem. Jej nieblokujące wejście/wyjście sieciowe czyni ją niezwykle skalowalną i potężnym narzędziem do tworzenia wysokowydajnych aplikacji internetowych. Ten kompleksowy przewodnik przeprowadzi Cię przez podstawowe koncepcje Tornado i dostarczy praktycznych przykładów, abyś mógł zacząć.

Czym jest Tornado?

W swej istocie Tornado to framework webowy i biblioteka do programowania asynchronicznego. W przeciwieństwie do tradycyjnych synchronicznych frameworków webowych, Tornado używa jednowątkowej architektury opartej na pętli zdarzeń. Oznacza to, że może obsługiwać wiele jednoczesnych połączeń bez konieczności tworzenia wątku na połączenie, co czyni go bardziej wydajnym i skalowalnym.

Kluczowe cechy Tornado:

Konfiguracja środowiska Tornado

Zanim zagłębisz się w rozwój z Tornado, musisz skonfigurować swoje środowisko. Oto przewodnik krok po kroku:

  1. Zainstaluj Pythona: Upewnij się, że masz zainstalowanego Pythona w wersji 3.6 lub nowszej. Możesz go pobrać z oficjalnej strony Pythona (python.org).
  2. Utwórz środowisko wirtualne (zalecane): Użyj venv lub virtualenv, aby utworzyć izolowane środowisko dla swojego projektu:
    python3 -m venv myenv
    source myenv/bin/activate  # Na Linux/macOS
    myenv\Scripts\activate  # Na Windows
  3. Zainstaluj Tornado: Zainstaluj Tornado za pomocą pip:
    pip install tornado

Twoja pierwsza aplikacja Tornado

Stwórzmy prostą aplikację "Hello, World!" z Tornado. Utwórz plik o nazwie app.py i dodaj następujący kod:

import tornado.ioloop
import tornado.web

class MainHandler(tornado.web.RequestHandler):
 def get(self):
  self.write("Hello, World!")

def make_app():
 return tornado.web.Application([
  (r"/", MainHandler),
 ])

if __name__ == "__main__":
 app = make_app()
 app.listen(8888)
 tornado.ioloop.IOLoop.current().start()

Teraz uruchom aplikację z terminala:

python app.py

Otwórz przeglądarkę internetową i przejdź do http://localhost:8888. Powinieneś zobaczyć komunikat "Hello, World!".

Wyjaśnienie:

Request Handlery i Routing

Request handlery są podstawą aplikacji webowych Tornado. Definiują one, jak obsługiwać przychodzące żądania HTTP na podstawie adresu URL. Routing mapuje adresy URL na określone request handlery.

Definiowanie Request Handlerów:

Aby utworzyć request handler, dziedzicz po tornado.web.RequestHandler i zaimplementuj odpowiednie metody HTTP (get, post, put, delete, itp.).

class MyHandler(tornado.web.RequestHandler):
 def get(self):
  self.write("To jest żądanie GET.")

 def post(self):
  data = self.request.body.decode('utf-8')
  self.write(f"Otrzymano dane POST: {data}")

Routing:

Routing jest konfigurowany podczas tworzenia tornado.web.Application. Podajesz listę krotek, gdzie każda krotka zawiera wzorzec URL i odpowiadający mu request handler.

app = tornado.web.Application([
 (r"/", MainHandler),
 (r"/myhandler", MyHandler),
])

Wzorce URL:

Wzorce URL to wyrażenia regularne. Możesz używać grup wyrażeń regularnych do przechwytywania części adresu URL i przekazywania ich jako argumenty do metod request handlera.

class UserHandler(tornado.web.RequestHandler):
 def get(self, user_id):
  self.write(f"ID użytkownika: {user_id}")

app = tornado.web.Application([
 (r"/user/([0-9]+)", UserHandler),
])

W tym przykładzie /user/([0-9]+) pasuje do adresów URL takich jak /user/123. Część ([0-9]+) przechwytuje jedną lub więcej cyfr i przekazuje je jako argument user_id do metody get handlera UserHandler.

Szablony

Tornado zawiera prosty i wydajny silnik szablonów. Szablony są używane do dynamicznego generowania HTML, oddzielając logikę prezentacji od logiki aplikacji.

Tworzenie szablonów:

Szablony są zazwyczaj przechowywane w osobnych plikach (np. index.html). Oto prosty przykład:

<!DOCTYPE html>
<html>
<head>
 <title>Moja strona internetowa</title>
</head>
<body>
 <h1>Witaj, {{ name }}!</h1>
 <p>Dzisiaj jest {{ today }}.</p>
</body>
</html>

{{ name }} oraz {{ today }} to symbole zastępcze, które zostaną zastąpione rzeczywistymi wartościami podczas renderowania szablonu.

Renderowanie szablonów:

Aby wyrenderować szablon, użyj metody render() w swoim request handlerze:

class TemplateHandler(tornado.web.RequestHandler):
 def get(self):
  name = "Jan Kowalski"
  today = "2023-10-27"
  self.render("index.html", name=name, today=today)

Upewnij się, że ustawienie template_path jest poprawnie skonfigurowane w ustawieniach aplikacji. Domyślnie Tornado szuka szablonów w katalogu o nazwie templates w tym samym katalogu, co plik aplikacji.

app = tornado.web.Application([
 (r"/template", TemplateHandler),
], template_path="templates")

Składnia szablonów:

Szablony Tornado obsługują różne funkcje, w tym:

Operacje asynchroniczne

Siła Tornado leży w jego możliwościach asynchronicznych. Operacje asynchroniczne pozwalają Twojej aplikacji na wykonywanie nieblokującego I/O, co poprawia wydajność i skalowalność. Jest to szczególnie przydatne w zadaniach, które wymagają oczekiwania na zasoby zewnętrzne, takie jak zapytania do bazy danych czy żądania sieciowe.

@tornado.gen.coroutine:

Dekorator @tornado.gen.coroutine pozwala pisać kod asynchroniczny z użyciem słowa kluczowego yield. Dzięki temu kod asynchroniczny wygląda i zachowuje się bardziej jak kod synchroniczny, co poprawia czytelność i łatwość konserwacji.

import tornado.gen
import tornado.httpclient

class AsyncHandler(tornado.web.RequestHandler):
 @tornado.gen.coroutine
 def get(self):
  http_client = tornado.httpclient.AsyncHTTPClient()
  response = yield http_client.fetch("http://example.com")
  self.write(response.body.decode('utf-8'))

W tym przykładzie http_client.fetch() jest operacją asynchroniczną, która zwraca obiekt Future. Słowo kluczowe yield zawiesza wykonanie korutyny do czasu rozwiązania obiektu Future. Gdy Future zostanie rozwiązany, korutyna wznawia działanie, a treść odpowiedzi jest zapisywana do klienta.

tornado.concurrent.Future:

Future reprezentuje wynik operacji asynchronicznej, która może jeszcze nie być dostępna. Możesz używać obiektów Future do łączenia operacji asynchronicznych i obsługi błędów.

tornado.ioloop.IOLoop:

IOLoop to serce asynchronicznego silnika Tornado. Monitoruje deskryptory plików i gniazda w poszukiwaniu zdarzeń i przekazuje je do odpowiednich handlerów. Zazwyczaj nie musisz bezpośrednio wchodzić w interakcje z IOLoop, ale ważne jest, aby rozumieć jego rolę w obsłudze operacji asynchronicznych.

WebSockets

Tornado zapewnia doskonałe wsparcie dla WebSockets, umożliwiając komunikację w czasie rzeczywistym między serwerem a klientami. WebSockets są idealne dla aplikacji wymagających dwukierunkowej komunikacji o niskim opóźnieniu, takich jak aplikacje czatowe, gry online i pulpity nawigacyjne w czasie rzeczywistym.

Tworzenie Handlera WebSocket:

Aby utworzyć handler WebSocket, dziedzicz po tornado.websocket.WebSocketHandler i zaimplementuj następujące metody:

import tornado.websocket

class WebSocketHandler(tornado.websocket.WebSocketHandler):
 def open(self):
  print("WebSocket otwarty")

 def on_message(self, message):
  self.write_message(f"Wysłałeś: {message}")

 def on_close(self):
  print("WebSocket zamknięty")

 def check_origin(self, origin):
  return True # Włącz połączenia WebSocket z innych domen

Integracja WebSockets z Twoją aplikacją:

Dodaj handler WebSocket do konfiguracji routingu swojej aplikacji:

app = tornado.web.Application([
 (r"/ws", WebSocketHandler),
])

Implementacja po stronie klienta:

Po stronie klienta możesz użyć JavaScriptu, aby nawiązać połączenie WebSocket i wysyłać/odbierać wiadomości:

const websocket = new WebSocket("ws://localhost:8888/ws");

websocket.onopen = () => {
 console.log("Połączenie WebSocket nawiązane");
 websocket.send("Witaj od klienta!");
};

websocket.onmessage = (event) => {
 console.log("Otrzymano wiadomość:", event.data);
};

websocket.onclose = () => {
 console.log("Połączenie WebSocket zamknięte");
};

Uwierzytelnianie i bezpieczeństwo

Bezpieczeństwo jest kluczowym aspektem tworzenia aplikacji internetowych. Tornado oferuje kilka funkcji, które pomogą Ci zabezpieczyć aplikacje, w tym uwierzytelnianie, autoryzację i ochronę przed powszechnymi podatnościami internetowymi.

Uwierzytelnianie:

Uwierzytelnianie to proces weryfikacji tożsamości użytkownika. Tornado zapewnia wbudowane wsparcie dla różnych schematów uwierzytelniania, w tym:

Autoryzacja:

Autoryzacja to proces określania, czy użytkownik ma uprawnienia do dostępu do określonego zasobu. Możesz zaimplementować logikę autoryzacji w swoich request handlerach, aby ograniczyć dostęp na podstawie ról lub uprawnień użytkownika.

Najlepsze praktyki bezpieczeństwa:

Wdrażanie (Deployment)

Wdrażanie aplikacji Tornado obejmuje kilka kroków, w tym konfigurację serwera WWW, ustawienie menedżera procesów i optymalizację wydajności.

Serwer WWW:

Możesz wdrożyć Tornado za serwerem WWW, takim jak Nginx lub Apache. Serwer WWW działa jako odwrotne proxy, przekazując przychodzące żądania do aplikacji Tornado.

Menedżer procesów:

Menedżer procesów, taki jak Supervisor lub systemd, może być używany do zarządzania procesem Tornado, zapewniając jego automatyczne ponowne uruchomienie w przypadku awarii.

Optymalizacja wydajności:

Internacjonalizacja (i18n) i Lokalizacja (l10n)

Podczas tworzenia aplikacji dla globalnej publiczności ważne jest uwzględnienie internacjonalizacji (i18n) i lokalizacji (l10n). i18n to proces projektowania aplikacji w taki sposób, aby można ją było dostosować do różnych języków i regionów bez zmian inżynierskich. l10n to proces adaptacji zinternacjonalizowanej aplikacji do określonego języka lub regionu poprzez dodanie komponentów specyficznych dla danego locale i przetłumaczenie tekstu.

Tornado a i18n/l10n

Samo Tornado nie ma wbudowanych bibliotek i18n/l10n. Można jednak łatwo zintegrować standardowe biblioteki Pythona, takie jak `gettext`, lub bardziej zaawansowane frameworki, takie jak Babel, aby obsłużyć i18n/l10n w aplikacji Tornado.

Przykład z użyciem `gettext`:

1. **Skonfiguruj swoje locale:** Utwórz katalogi dla każdego języka, który chcesz wspierać, zawierające katalogi wiadomości (zazwyczaj pliki `.mo`).

locales/
 en/LC_MESSAGES/messages.mo
 fr/LC_MESSAGES/messages.mo
 de/LC_MESSAGES/messages.mo

2. **Wyodrębnij ciągi znaków do tłumaczenia:** Użyj narzędzia takiego jak `xgettext`, aby wyodrębnić ciągi znaków do tłumaczenia z kodu Pythona do pliku `.po` (Portable Object). Ten plik będzie zawierał oryginalne ciągi znaków i miejsce na tłumaczenia.

xgettext -d messages -o locales/messages.po your_tornado_app.py

3. **Przetłumacz ciągi znaków:** Przetłumacz ciągi znaków w plikach `.po` dla każdego języka.

4. **Skompiluj tłumaczenia:** Skompiluj pliki `.po` do plików `.mo` (Machine Object), które są używane przez `gettext` w czasie wykonania.

msgfmt locales/fr/LC_MESSAGES/messages.po -o locales/fr/LC_MESSAGES/messages.mo

5. **Zintegruj z aplikacją Tornado:**

import gettext
import locale
import os
import tornado.web

class BaseHandler(tornado.web.RequestHandler):
 def initialize(self):
  try:
  locale.setlocale(locale.LC_ALL, self.get_user_locale().code)
  except locale.Error:
  # Obsłuż przypadki, gdy locale nie jest wspierane przez system
  print(f"Locale {self.get_user_locale().code} nie jest wspierane")

  translation = gettext.translation('messages', 'locales', languages=[self.get_user_locale().code])
  translation.install()
  self._ = translation.gettext

 def get_current_user_locale(self):
  # Logika do określenia locale użytkownika (np. z nagłówka Accept-Language, ustawień użytkownika itp.)
  # To jest uproszczony przykład - będziesz potrzebować bardziej rozbudowanego rozwiązania
  accept_language = self.request.headers.get('Accept-Language', 'en')
  return tornado.locale.get(accept_language.split(',')[0].split(';')[0])

class MainHandler(BaseHandler):
 def get(self):
  self.render("index.html", _=self._)

settings = {
 "template_path": os.path.join(os.path.dirname(__file__), "templates"),
}

app = tornado.web.Application([
 (r"/", MainHandler),
], **settings)

6. **Zmodyfikuj swoje szablony:** Użyj funkcji `_()` (powiązanej z `gettext.gettext`), aby oznaczyć ciągi znaków do tłumaczenia w szablonach.

<h1>{{ _("Witaj na naszej stronie!") }}</h1>
<p>{{ _("To jest przetłumaczony akapit.") }}</p>

Ważne kwestie dla globalnej publiczności:

Tematy zaawansowane

Niestandardowe strony błędów:

Możesz dostosować strony błędów, które Tornado wyświetla w przypadku wystąpienia błędu. Pozwala to na zapewnienie bardziej przyjaznego dla użytkownika doświadczenia i dołączenie informacji debugowania.

Ustawienia niestandardowe:

Możesz zdefiniować niestandardowe ustawienia w konfiguracji aplikacji i uzyskiwać do nich dostęp w swoich request handlerach. Jest to przydatne do przechowywania parametrów specyficznych dla aplikacji, takich jak ciągi połączeń do bazy danych lub klucze API.

Testowanie:

Dokładnie testuj swoje aplikacje Tornado, aby upewnić się, że działają poprawnie i bezpiecznie. Używaj testów jednostkowych, integracyjnych i end-to-end, aby objąć wszystkie aspekty swojej aplikacji.

Podsumowanie

Tornado to potężny i wszechstronny framework webowy, który doskonale nadaje się do tworzenia skalowalnych, wysokowydajnych aplikacji internetowych. Jego asynchroniczna architektura, wsparcie dla WebSockets i łatwe w użyciu API czynią go popularnym wyborem wśród programistów na całym świecie. Postępując zgodnie z wytycznymi i przykładami w tym kompleksowym przewodniku, możesz zacząć tworzyć własne aplikacje Tornado i korzystać z jego wielu funkcji.

Pamiętaj, aby zapoznać się z oficjalną dokumentacją Tornado w celu uzyskania najbardziej aktualnych informacji i najlepszych praktyk. Miłego kodowania!