Files
django-discord/doc/main.tex
T

399 lines
20 KiB
TeX
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
\documentclass[12pt,a4paper]{article}
% Polskie znaki i kodowanie
\usepackage[utf8]{inputenc}
\usepackage[T1]{fontenc}
\usepackage[polish]{babel}
% Marginesy
\usepackage{geometry}
\geometry{a4paper, margin=2.5cm}
% Zawijanie tekstu i mikrotypografia
\usepackage{microtype}
\usepackage[hyphens]{url}
\usepackage{xurl} % pozwala łamać URL-e w dowolnym miejscu
\emergencystretch=3em % zapobiega overfull hbox w uzasadnionych przypadkach
% \setlength{\parfillskip}{0pt plus 1fil} % naturalny fill na końcu akapitu
\setlength{\headheight}{13.59999pt}
% Grafika, tabele i kolory
\usepackage{graphicx}
\usepackage{float}
\usepackage{xcolor}
\usepackage{booktabs}
% Drzewo katalogów
\usepackage{dirtree}
% Zaawansowane nagłówki i stopki
\usepackage{fancyhdr}
\pagestyle{fancy}
\fancyhf{}
\fancyhead[L]{\small\textbf{Technologie Webowe}}
\fancyhead[R]{\small\textit{Discord Clone - Dokumentacja}}
\fancyfoot[C]{\thepage}
\renewcommand{\headrulewidth}{0.4pt}
\renewcommand{\footrulewidth}{0.4pt}
% Podświetlanie składni (Kod źródłowy)
\usepackage{listings}
\definecolor{codegreen}{rgb}{0,0.6,0}
\definecolor{codegray}{rgb}{0.5,0.5,0.5}
\definecolor{codepurple}{rgb}{0.58,0,0.82}
\definecolor{backcolour}{rgb}{0.95,0.95,0.92}
\lstdefinestyle{mystyle}{
backgroundcolor=\color{backcolour},
commentstyle=\color{codegreen},
keywordstyle=\color{magenta},
numberstyle=\tiny\color{codegray},
stringstyle=\color{codepurple},
basicstyle=\ttfamily\footnotesize,
breakatwhitespace=false, % łam linie również w środku tokenu (nie tylko przy białych znakach)
breaklines=true, % włącz automatyczne zawijanie długich linii
breakautoindent=true, % zachowaj wcięcie po złamaniu
postbreak=\mbox{\textcolor{codegray}{$\hookrightarrow$}\space}, % strzałka przy złamaniu
columns=flexible, % elastyczne kolumny zapobiegają overfull w listingach
captionpos=b,
keepspaces=true,
numbers=left,
numbersep=5pt,
showspaces=false,
showstringspaces=false,
showtabs=false,
tabsize=2,
extendedchars=true,
inputencoding=utf8,
literate={ą}{{\k{a}}}1
{ć}{{\'c}}1
{ę}{{\k{e}}}1
{ł}{{\l{}}}1
{ń}{{\'n}}1
{ó}{{\'o}}1
{ś}{{\'s}}1
{ź}{{\'z}}1
{ż}{{\.z}}1
{Ą}{{\k{A}}}1
{Ć}{{\'C}}1
{Ę}{{\k{E}}}1
{Ł}{{\L{}}}1
{Ń}{{\'N}}1
{Ó}{{\'O}}1
{Ś}{{\'S}}1
{Ź}{{\'Z}}1
{Ż}{{\.Z}}1
}
\lstset{style=mystyle}
% Konfiguracja linków
\usepackage{hyperref}
\hypersetup{
colorlinks=true,
linkcolor=blue!70!black,
filecolor=magenta,
urlcolor=blue!70!black,
pdftitle={Discord Clone - Dokumentacja},
pdfpagemode=FullScreen,
breaklinks=true, % pozwól na zawijanie linków
}
\begin{document}
% Globalne ustawienie luźniejszego składania tekstu zapobiega overfull hbox
\sloppy
\begin{titlepage}
\centering
\vspace*{3cm}
{\scshape\Large Przedmiot: Aplikacje internetowe w Django \par}
\vspace{1.5cm}
{\huge\bfseries Dokumentacja Projektowa\par}
\vspace{0.5cm}
{\Large\itshape Discord Clone: Aplikacja do komunikacji w czasie rzeczywistym\par}
\vspace{2cm}
\vfill
{\large \today\par}
\end{titlepage}
\newpage
\tableofcontents
\newpage
\section{Wstęp i cel projektu}
Prezentowany projekt \textbf{,,Discord Clone''} to nowoczesna aplikacja webowa stworzona w celu dostarczenia funkcjonalności umożliwiających wieloosobową komunikację tekstową w czasie rzeczywistym, analogiczną do popularnej platformy Discord.
Głównym celem edukacyjnym i technologicznym projektu jest zintegrowanie klasycznego frameworka opartego na protokole HTTP z zaawansowanymi mechanizmami asynchronicznymi i protokołem \textbf{WebSocket}, z zachowaniem optymalnej wydajności i uproszczonej infrastruktury.
\subsection{Kluczowe funkcjonalności}
\begin{itemize}
\item \textbf{Autoryzacja:} Rejestracja i logowanie. Dostęp do głównej funkcjonalności jest chroniony.
\item \textbf{Zarządzanie Kanałami:} Użytkownicy z odpowiednimi uprawnieniami mogą tworzyć tematyczne kanały grupowe (tekstowe i głosowe).
\item \textbf{Role i uprawnienia:} Podział na Administratorów, Moderatorów oraz Użytkowników z dedykowanymi przywilejami (np. usuwanie obcych wiadomości przez moderatora).
\item \textbf{Czat Real-Time:} Natychmiastowe przesyłanie wiadomości bez konieczności odświeżania okna przeglądarki.
\item \textbf{Kanały Głosowe (Voice Channels):} Wieloużytkownikowe rozmowy audio w czasie rzeczywistym realizowane w architekturze peer-to-peer (P2P) z wykorzystaniem WebRTC, wspierane przez globalną usługe Cloudflare Realtime TURN do NAT-traversalu.
\end{itemize}
\newpage
\section{Architektura systemu i wykorzystane technologie}
Aplikacja została zaprojektowana w oparciu o architekturę trójwarstwową, w której wyeliminowano konieczność stosowania osobnego serwera kolejkowego (np. Redis) na rzecz wbudowanych mechanizmów bazy PostgreSQL.
\subsection{Warstwa Prezentacji (Klient / Frontend)}
Użytkownik komunikuje się z systemem za pośrednictwem przeglądarki internetowej. Warstwa ta renderowana jest przy pomocy \textbf{szablonów HTML/CSS (Bootstrap)}. Lekki skrypt \textbf{JavaScript} nawiązuje i utrzymuje ciągłe połączenie WebSocket z serwerem, asynchronicznie odbierając powiadomienia o nowych wiadomościach. W przypadku połączeń głosowych, frontend odpowiada za przechwytywanie dźwięku z mikrofonu (Web Audio API), realizację nawiązania połączeń WebRTC P2P (Full Mesh) oraz dynamiczną obsługę odtwarzania strumieni audio od innych użytkowników bez przerywania nawigacji dzięki technologii AJAX/Fetch (SPA Shell).
\subsection{Warstwa Aplikacji (Backend)}
Główny trzon projektu oparty jest o język \textbf{Python} oraz framework \textbf{Django 4.2}.
Ruch sieciowy jest zarządzany przez serwer \textbf{Daphne (ASGI)}, który:
\begin{itemize}
\item \textbf{Zwykłe żądania HTTP} (np. logowanie, ładowanie widoków) przekazuje do synchronicznych widoków Django.
\item \textbf{Żądania WebSocket} routuje do asynchronicznych consumerów zdefiniowanych w \textbf{Django Channels}.
\end{itemize}
W kontekście kanałów głosowych backend pełni funkcję serwera sygnalizacyjnego (signaling server) za pośrednictwem protokołu WebSocket, pośrednicząc w wymianie ofert SDP (Session Description Protocol) i kandydatów ICE między klientami. Dodatkowo, backend komunikuje się z interfejsem API Cloudflare, pobierając krótkotrwałe, bezpieczne dane uwierzytelniające dla serwerów TURN (ważne przez 24 godziny).
\subsection{Warstwa Danych}
Zastosowano zunifikowaną bazę danych \textbf{PostgreSQL}. Standardowe dane (użytkownicy, historia wiadomości) są obsługiwane przez mechanizm Django ORM. Dodatkowo, dzięki zastosowaniu pluginu \texttt{channels-postgres}, architektura w czasie rzeczywistym (pub/sub) wykorzystuje natywne zapytania \texttt{LISTEN/NOTIFY} z PostgreSQL, co pozwala rozgłaszać eventy WebSockets pomiędzy klientami. W bazie danych PostgreSQL przechowywany jest również stan aktywności użytkowników w kanałach głosowych za pomocą pola \texttt{current\_voice\_channel} w profilu użytkownika (\texttt{UserProfile}). Umożliwia to natychmiastowe renderowanie listy uczestników kanału głosowego bezpośrednio pod jego nazwą na liście kanałów podczas ładowania strony.
\newpage
\section{Środowisko i instalacja}
Do uruchomienia projektu w celach deweloperskich i testowych zastosowano konteneryzację, co eliminuje problem zależności systemowych.
\subsection{Wymagania systemowe}
\begin{itemize}
\item \textbf{Docker} (Docker Engine)
\item \textbf{Docker Compose}
\item Połączenie z internetem (do pobrania obrazów oraz weryfikacji API Cloudflare)
\end{itemize}
\subsection{Kroki instalacji}
\begin{enumerate}
\item \textbf{Pobranie kodu:} Sklonuj lub pobierz i rozpakuj repozytorium projektu w wybranym folderze.
\item \textbf{Konfiguracja zmiennych środowiskowych:} Utwórz plik \texttt{.env} w katalogu głównym projektu na wzór udostępnionych szablonów. Wprowadź tam wygenerowane w panelu Cloudflare dane uwierzytelniające dla usługi Cloudflare Realtime TURN: \texttt{CLOUDFLARE\_ACCOUNT\_ID}, \texttt{CLOUDFLARE\_API\_TOKEN} oraz \texttt{CLOUDFLARE\_TURN\_KEY\_ID}. Umożliwi to serwerowi dynamiczne generowanie tokenów ICE dla przeglądarek.
\item \textbf{Budowanie kontenerów:} Uruchom terminal w głównym katalogu (tam, gdzie znajduje się plik \texttt{docker-compose.yml}) i wykonaj polecenie:
\begin{lstlisting}[language=bash, style=mystyle, numbers=none]
docker-compose up --build
\end{lstlisting}
\item \textbf{Uruchomienie bazy:} Kontenery wykonają automatycznie proces migracji struktury danych (\texttt{manage.py migrate}). Zostanie także wygenerowane konto administratora.
\item Aplikacja docelowo jest dostępna pod adresem: \url{http://localhost:8000}.
\end{enumerate}
\textbf{Domyślne dane dostępowe administratora:}
\begin{itemize}
\item Login: \texttt{admin}
\item Hasło: \texttt{admin}
\end{itemize}
\newpage
\section{Struktura repozytorium}
Projekt zorganizowany jest wokół aplikacji modułowych typowych dla frameworka Django. Poniższe drzewo prezentuje kluczowe pliki z punktu widzenia architektonicznego.
\vspace{0.5cm}
% Szerokość minipage dopasowana do pozostałej przestrzeni kolumny dirtree
\dirtree{%
.1 / (Katalog głowny projektu).
.2 core/.
.3 settings.py \dots\dots\dots\dots\begin{minipage}[t]{6cm}\raggedright Konfiguracja główna (baza danych, channels)\end{minipage}.
.3 asgi.py \dots\dots\dots\dots\dots\begin{minipage}[t]{6cm}\raggedright Punkt wejścia dla serwera Daphne i routing Channels\end{minipage}.
.3 urls.py \dots\dots\dots\dots\dots\begin{minipage}[t]{6cm}\raggedright Główny routing HTTP\end{minipage}.
.2 chat/.
.3 models.py \dots\dots\dots\dots\begin{minipage}[t]{6cm}\raggedright Definicje tabel DB (Użytkownicy, Kanały, Wiadomości)\end{minipage}.
.3 consumers.py \dots\dots\begin{minipage}[t]{6cm}\raggedright Asynchroniczna obsługa WebSocketów (czat, sygnalizacja WebRTC)\end{minipage}.
.3 routing.py \dots\dots\dots\dots\begin{minipage}[t]{6cm}\raggedright Definicje ścieżek URL dla połączeń ws://\end{minipage}.
.3 views.py \dots\dots\dots\dots\begin{minipage}[t]{6cm}\raggedright Widoki synchroniczne oraz endpoint generowania tokenów TURN\end{minipage}.
.2 docker-compose.yml \dots\dots\begin{minipage}[t]{6cm}\raggedright Orkiestracja kontenerów (PostgreSQL + aplikacja)\end{minipage}.
.2 Dockerfile \dots\dots\dots\dots\begin{minipage}[t]{6cm}\raggedright Instrukcja budowania środowiska bazowego Python\end{minipage}.
.2 requirements.txt \dots\dots\begin{minipage}[t]{6cm}\raggedright Zależności (Django, channels-postgres)\end{minipage}.
}
\newpage
\section{Kluczowe fragmenty implementacji}
W tej sekcji przedstawiono wybrane fragmenty kodu źródłowego odpowiadające za najważniejsze mechanizmy realizujące komunikację w czasie rzeczywistym.
\subsection{Consumer obsługujący WebSocket (chat/consumers.py)}
Poniższy kod ukazuje w jaki sposób framework Channels obsługuje podłączanie się użytkowników do specyficznej dla kanału ,,grupy'' oraz metodykę odbierania wiadomości tekstowych z przeglądarki i rozsyłania ich w czasie rzeczywistym.
\begin{lstlisting}[language=Python, caption={Przykład implementacji klasy \texttt{AsyncWebsocketConsumer}}]
import json
from channels.generic.websocket import AsyncWebsocketConsumer
class ChatConsumer(AsyncWebsocketConsumer):
async def connect(self):
self.room_name = self.scope['url_route']['kwargs']['room_name']
self.room_group_name = f"chat_{self.room_name}"
# Dodanie klienta do grupy nasluchujacej
await self.channel_layer.group_add(
self.room_group_name,
self.channel_name
)
await self.accept()
async def disconnect(self, close_code):
# Odlaczenie od grupy przy zamknieciu strony
await self.channel_layer.group_discard(
self.room_group_name,
self.channel_name
)
async def receive(self, text_data):
text_data_json = json.loads(text_data)
message = text_data_json['message']
# Rozeslanie otrzymanej wiadomosci do calej grupy
await self.channel_layer.group_send(
self.room_group_name,
{
'type': 'chat_message',
'message': message
}
)
# Funkcja wywolywana, gdy warstwa kanałów wysyla typ 'chat_message'
async def chat_message(self, event):
message = event['message']
await self.send(text_data=json.dumps({
'message': message
}))
\end{lstlisting}
\subsection{Generowanie tokenów TURN (chat/views.py)}
Poniższy kod prezentuje bezpieczne pobieranie danych uwierzytelniających (tokenów ICE) z API Cloudflare TURN za pomocą synchronicznego widoku Django. W przypadku braku konfiguracji kluczy Cloudflare zastosowano automatyczną ścieżkę awaryjną (fallback) do publicznych serwerów STUN.
\begin{lstlisting}[language=Python, caption={Pobieranie tokenów ICE Cloudflare Realtime TURN}]
import urllib.request
import urllib.error
import json
from django.conf import settings
from django.http import JsonResponse
from django.contrib.auth.decorators import login_required
@login_required
def voice_credentials_view(request):
api_token = getattr(settings, 'CLOUDFLARE_API_TOKEN', '')
turn_key_id = getattr(settings, 'CLOUDFLARE_TURN_KEY_ID', '')
if not api_token or not turn_key_id:
return JsonResponse({
"iceServers": [{"urls": ["stun:stun.cloudflare.com:3478"]}]
})
url = (
f"https://rtc.live.cloudflare.com/v1/turn/keys/"
f"{turn_key_id}/credentials/generate-ice-servers"
)
headers = {
"Authorization": f"Bearer {api_token}",
"Content-Type": "application/json"
}
body = json.dumps({"ttl": 86400}).encode("utf-8")
req = urllib.request.Request(url, data=body, headers=headers, method="POST")
try:
with urllib.request.urlopen(req, timeout=5) as response:
return JsonResponse(json.loads(response.read().decode()))
except urllib.error.URLError:
return JsonResponse({
"iceServers": [{"urls": ["stun:stun.cloudflare.com:3478"]}]
})
\end{lstlisting}
\subsection{Sygnalizacja WebRTC (chat/consumers.py)}
Aby nawiązać połączenie P2P, klienci muszą wymienić oferty SDP i kandydatów ICE. Poniższy fragment klasy \texttt{VoiceSignalingConsumer} realizuje to zadanie, automatycznie aktualizując status obecności użytkownika w kanale głosowym w bazie danych oraz rozsyłając sygnały do właściwych odbiorców docelowych.
\begin{lstlisting}[language=Python, caption={Klasa \texttt{VoiceSignalingConsumer} obsługująca sygnalizację WebRTC}]
from channels.generic.websocket import AsyncWebsocketConsumer
class VoiceSignalingConsumer(AsyncWebsocketConsumer):
async def connect(self):
self.channel_id = self.scope['url_route']['kwargs']['channel_id']
self.group_name = f'voice_{self.channel_id}'
self.user = self.scope['user']
await self.channel_layer.group_add(self.group_name, self.channel_name)
await self.accept()
await self.set_voice_channel(self.channel_id)
await self.channel_layer.group_send(
'global_updates',
{
'type': 'voice_presence_change',
'user_id': self.user.id,
'username': self.user.username,
'channel_id': int(self.channel_id),
'action': 'joined',
}
)
async def receive(self, text_data):
data = json.loads(text_data)
if data.get('action') == 'signal':
await self.channel_layer.group_send(
self.group_name,
{
'type': 'voice_signal_relay',
'sender_id': self.user.id,
'target_id': data.get('target_id'),
'data': data.get('data'),
}
)
async def voice_signal_relay(self, event):
if event['target_id'] == self.user.id:
await self.send(text_data=json.dumps(event))
\end{lstlisting}
\newpage
\section{Prezentacja interfejsu graficznego}
Kolejne ekrany prezentują wygląd aplikacji z perspektywy klienta webowego. Widoki zostały przygotowane w oparciu o framework Bootstrap, co zapewnia responsywność interfejsu na różnych urządzeniach.
\subsection{Dostęp i Autoryzacja}
\begin{figure}[H]
\centering
% Odkomentuj poniższą linię, aby wstawić prawdziwy plik graficzny
\includegraphics[width=0.7\textwidth]{images/login_screen.png}
\caption{Ekran logowania -- Wymagany dla wszystkich użytkowników przed przystąpieniem do konwersacji.}
\label{fig:login}
\end{figure}
\begin{figure}[H]
\centering
\includegraphics[width=0.7\textwidth]{images/register_screen.png}
\caption{Ekran rejestracji -- Umożliwia utworzenie nowego profilu.}
\label{fig:register}
\end{figure}
\subsection{Główny panel komunikacyjny}
\begin{figure}[H]
\centering
\includegraphics[width=0.7\textwidth]{images/chat_interface.png}
\caption{Główne okno aplikacji przypominające interfejs platformy Discord. Po lewej stronie widoczna jest lista dostępnych kanałów tematycznych, z prawej okno wybranego czatu z historią wiadomości.}
\label{fig:main_chat}
\end{figure}
\begin{figure}[H]
\centering
\includegraphics[width=0.7\textwidth]{images/admin_panel.png}
\caption{Panel Administracyjny Uprawnień -- Służy do moderowania i edycji uprawnień użytkowników.}
\label{fig:admin_panel}
\end{figure}
\newpage
\section{Podsumowanie}
Projekt Discord Clone stanowi kompletną, chociaż z konieczności uproszczoną na potrzeby ćwiczeniowe, wersję nowoczesnego komunikatora grupowego. Użycie architektury ASGI (Daphne, Django Channels) przełamuje ograniczenia tradycyjnego, synchronicznego modelu HTTP/WSGI i pozwala na obsługę setek równoczesnych połączeń w czasie rzeczywistym.
\vspace{0.3cm}
\textbf{Najważniejsze osiągnięcia technologiczne zastosowane w projekcie:}
\begin{enumerate}
\item \textbf{Integracja \texttt{channels-postgres}:} Rezygnacja z klasycznego wykorzystywania zewnętrznych rozwiązań cachingowych (takich jak np. Redis) pozwala znacznie uprościć infrastrukturę serwerową oraz obniżyć zasoby wykorzystywane przez kontenery Dockera. Komunikaty WebSocket skutecznie wykorzystują wewnętrzne wyzwalacze PostgreSQL.
\item \textbf{Podział uprawnień:} Prawidłowe zdefiniowanie ról wewnątrz systemu uniemożliwia użytkownikom ze standardowymi uprawnieniami modyfikowanie i kasowanie wiadomości innych użytkowników, chroniąc system przed nadużyciami.
\item \textbf{Prostota środowiska deweloperskiego:} Plik konfiguracyjny \texttt{docker-compose.yml} sprowadza proces instalacji środowiska uruchomieniowego do jednej komendy terminala, rozwiązując odwieczny problem niezgodności wersji paczek pomiędzy maszynami programistów.
\item \textbf{Komunikacja głosowa WebRTC z Cloudflare Realtime TURN:} Zaimplementowanie kanałów głosowych w modelu P2P Full Mesh, przy jednoczesnym wyeliminowaniu kosztów stałych dzięki darmowemu pakietowi Cloudflare Realtime (do 1000 GB egressu/miesiąc) i dynamicznemu generowaniu krótkoterminowych tokenów sesyjnych.
\end{enumerate}
Projekt daje doskonałą podstawę do wdrażania kolejnych funkcji w przyszłości, takich jak udostępnianie ekranu (screen sharing), grupowe rozmowy wideo czy natywne powiadomienia typu Push Notifications.
\end{document}