Skip to content

Latest commit

 

History

History
147 lines (117 loc) · 5.48 KB

15. Django Async.md

File metadata and controls

147 lines (117 loc) · 5.48 KB

Introduzione

Finora abbiamo interagito con il server per mezzo di richieste HTTP sincrone e richieste HTTP asincrone attese con AJAX.

Il modello prevede in entrambi i casi una richiesta seguita da una risposta. Quali sono gli svantaggi del protocollo HTTP?

  • Ogni richiesta si apre alla richiesta e chiude alla risposta, no flusso continuo.
  • Overhead prestazione nel caso di interrogazioni continue.
  • Stateless

Per il momento possiamo implementare un flusso mediante polling temporizzato. Quale periodo? grande overhead, difficile da scegliere.

Websockets

Protocollo basato su TCP che risolve il problema consentendo di instaurare un canale full-duplex: il server può inviare al client dati. Designatore del protocollo: ws. Versione sicura: wss. Abilita all'utilizzo di ASGI lato server.

Client-side

var ws = new WebSocket("ws://site.com/endpoint")
ws.onmessage = function(evt) {  // Callback function on message receive
	// ...
}

#Vedi Java EDT - Event Dispatcher Thread. Thread periodico che accoda gli eventi e li consuma periodicamente.

Sul canale WS vengono scambiati frame, ovvero pezzi di informazione che contengono sia dati che informazioni necessari per interpretarle. Supporto sia a dati binari (eg. foto) che dati testuali.

Server-side

Lato server possono esistere più consumatori (Consumer), che rimane in ascolto di potenziali client. Il client apre la connessione verso il consumatore. Una volta instaurata la connessione possono essere scambiati dati con le funzioni send e receive.

WSGI non è sempre peggio di ASGI. Sono equivalenti (se non meglio) nel caso in cui si eseguano operazioni CPU-intensive. Ma ASGI è vincente quando si ha a che fare con oggetti awaitable (eg. Future). Questo accade tipicamente con l'accesso ai database. Il tempo di accesso al DB ha tipicamente molta latenza.

Inoltre, il multi-threading potrebbe avvenire a livello di webserver. Il server WSGI potrebbe fare interleaving di processi all'interno del core, con processi single-threaded.

#Vedi https://docs.djangoproject.com/en/5.0/howto/deployment/wsgi/

Come distinguere richieste HTTP e richieste WS.

Configurazione

Dipendenze:

  • wsgiref
  • Daphne
  • Channels

Dopo aver installato Channels, aggiungi channels tra le applicazioni installate; aggiungi ASGI_APPLICATION = 'chat.asgi.application'. #Hint: crea un modulo diverso da quello di default e impostalo con os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'chat.settings')

Il logging cambierà in starting ASGI server.

#Vedi https://channels.readthedocs.io/en/stable/deploying.html

Protocol routing, che indirizzi le richieste verso il server HTTP o verso il consumer in base al protocollo usato:

from django.core.asgi import get_asgi_application
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.auth import AuthMiddlewareStack

from .routing import ws_urlspattern  # User defined

application = ProtocolTypeRouter(
	{
		'http': get_asgi_application(),
		'wsgi': AuthMiddlewareStack(URLRouter(ws_urlpatterns))
	} 
)

Il router ci permette di stabilire come i diversi protocolli debbano essere serviti. Il middleware di autenticazione permette di accedere alle variabili di sessione, quali cookie, user, etc. come facciamo nelle view, l'accesso avviene mediante la variabile scope.

Il file routing.py da noi creato conterrà la regola:

ws_urlpatterns = [
	path("ws/chatws/", WSConsumerChat.as_asgi()),
]

Websocket channels

Abilitano all'utilizzo di un paradigma publisher-subscriber su uno stesso endpoint. Il protocollo WS chiama channels questo meccanismo. Le stanze appaiono nell'URL path.

Regole

Un gruppo viene creato quando almeno un client è sottoscritto. Un gruppo viene distrutto quando nessuno ascolta. Quando un messaggio arriva su un gruppo, il messaggio viene inviato a tutti i consumatori sottoscritti.

Channel layers

L'implementazione dei canali è trasparente, non differisce il comportamento esibito dipendentemente dall'implementazione scelta.

Il package channel mette a disposizione due tipi di canali:

  • in-memory channels per lo sviluppo
  • redis channels production-ready

Configurazione:

CHANNEL_LAYERS = {
    "default": {
        "BACKEND": "channels.layers.InMemoryChannelLayer"
    }
}

Caso studio

Naive Chat Async Consumer

Creiamo un consumer ereditando da AsyncWebsocketConsumer:

class ChatConsumer(AsyncWebsocketConsumer):
	async def connect(self):
		await self.accept() # Accettazione sempre, operazione deferita nel tempo
		await self.send("Connesso!")  # Non mi blocco sull'attesa della risposta

	# Due tipi di dato: testuale o binario
	async def receive(self, text_data=None, bytes_data=None):
		...

Channels consumer

#Ricorda channel name si a url path paramter: path("ws/chatws/<str:room>/", WSConsumerChatChannels.as_asgi())

class ChannelChatConsumer(AyncWebsocketConsumer):
	async def connect(self):
		gname = self.scope['url_router']['kwargs']['group']
		...
		self.channel_layer.group_add(
			gname,
			self.channel_name
		)
		await self.accept()

	async def disconnect(self):
		self.channe_layer.group_discard(
			gname,
			self.channel_name
		)

	async def receive(self):
		...
		self.channel_layer.group_send(
			gname,
			{
				'type': 'chatroom_message',
				'msg': message,
				...
			}
		)

	async def chatroom_message(self, event):
		...

#Nota : che i messaggi json arrivano in formato testuale #Nota : group add e discard non distruggono e creano il gruppo, eliminano aggiungono ed eliminano un listener.