Nella prima parte dell’articolo abbiamo introdotto i WebSocket sviluppando una semplice applicazione composta da un client che invia dei messaggi di testo ad un server (WebSocket Server), il quale risponde inviando un testo ed un numero che rappresenta la lunghezza del messaggio ricevuto. Nel post precedente abbiamo descritto con maggior dettaglio il codice lato client demandando a questa seconda parte maggiori approfondimenti relativamente alla parte server il cui codice è composto dalla semplice classe WebSochetHandler.ashx, la quale implementa l’interfaccia IHttpHandler, ragion per cui è necessario scrivere del codice per il metodo ProcessRequest(HttpContext context), per l’elaborazione delle richieste Web HTTP, e la proprietà IsReusable che restituisce un valore che indica se sia possibile riciclare la stessa istanza di classe che implementa IHttpHandler per una successiva richiesta.
Per quanto concerne IsReusable, non dovendo memorizzare nulla tra una richiesta e l’altra, il codice è semplicemente:
public bool IsReusable
{
get
{
return true;
}
}
Mentre, per il metodo ProcessRequest(…) abbiamo :
public void ProcessRequest(HttpContext context)
{
if (context.IsWebSocketRequest)
{
context.AcceptWebSocketRequest(WebSocketRequestHandler);
}
else
{
context.Response.StatusCode = 400;
}
}
Utilizzando la proprietà IsWebSocketRequest verifichiamo che la richiesta entrante sia una richiesta WebSocket valida, in caso positivo demandiamo la gestione della richiesta al metodo WebSocketRequestHandler così definito:
public async Task WebSocketRequestHandler(WebSocketContext webSocketContext)
{
byte[] buffer = new byte[MAX_MESSAGE_SIZE];
WebSocket webSocket = webSocketContext.WebSocket;
while (webSocket.State == WebSocketState.Open)
{
WebSocketReceiveResult result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
switch (result.MessageType)
{
case WebSocketMessageType.Close:
await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closed.", CancellationToken.None);
break;
case WebSocketMessageType.Binary:
await webSocket.CloseAsync(WebSocketCloseStatus.InvalidMessageType, "Not supported.", CancellationToken.None);
break;
case WebSocketMessageType.Text:
int count = result.Count;
while (result.EndOfMessage == false)
{
if (count >= MAX_MESSAGE_SIZE)
{
string closeMessage = string.Format("Maximum message size: {0} bytes.", MAX_MESSAGE_SIZE);
await webSocket.CloseAsync(WebSocketCloseStatus.MessageTooBig, closeMessage, CancellationToken.None);
return;
}
result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer, count, buffer.Length), CancellationToken.None);
count += result.Count;
}
string receivedMessage = Encoding.UTF8.GetString(buffer, 0, count);
string outputMessage = "Text Length: " + receivedMessage.Length;
await webSocket.SendAsync(new ArraySegment<byte>(Encoding.UTF8.GetBytes(outputMessage)), WebSocketMessageType.Text, true, CancellationToken.None);
break;
}
}
}
In dettaglio, quando invochiamo il metodo WebSocketRequestHandler, ASP.NET passa un’istanza della classe WebSocketContext contenente informazioni quali l’URI utilizzato per stabilire la connessione (compresi i parametri della Query String) e la collezione dei cookies inviati. Per accedere ai metodi per la ricezione e l’invio dei dati è necessario accedere alla proprietà WebSocket . A questo punto, utilizziamo la proprietà State per verificare lo stato della connessione, rimanendo “in ciclo” finché la connessione rimane aperta. Per ricevere i dati utilizziamo il metodo ReceiveAsync che accetta due parametri: un ArraySegment in cui scrivere i dati ricevuti (buffer) ed un Cancellation Token, nel nostro caso CancellationToken.None. Il metodo ritorna un’istanza di WebSocketReceiveResult che rappresenta il risultato di una singola operazione ReceiveAsync. Tramite la proprietà MessageType della classe risultato verifichiamo il tipo di messaggio:
- Close: è stato inviato un close frame (vedi prima parte). A questo punto il server partecipa all’handshake di chiusura utilizzando il metodo CloseAsync. Trattandosi di una normale chiusura, ci limitiamo ad utilizzare la voce Normal dell’enumerativo WebSocketCloseStatus.
- Binary: non supportato dalla nostra applicazione, pertanto iniziamo il processo di chiusura della connessione e specifichiamo al client che il tipo dati inviato non è supportato (utilizziamo l’apposito valore dell’enumerativo).
- Text: tramite la proprietà EndOfMessage, entriamo in un loop finché non abbiamo letto il contenuto dell’intero del messaggio (o non si raggiunge la dimensione massima del messaggio). Una volta ricevuto il messaggio, utilizzando l’encoding opportuno (UTF8, per formati diversi è necessario utilizzare il formato binario) “leggiamo” la stringa ed inviamo un testo con l’indicazione della lunghezza della stringa ricevuta tramite il metodo SensAsync.
Il terzo ed ultimo post della serie dedicato alla “costruzione della lavagna” condivisa tra client Web/Windows utilizzando i WebSocket e l’oggetto HTML canvas.