Écrire un serveur WebSocket en Java

Introduction

Cet exemple montre comment créer un serveur d'API WebSocket API utilisant Java d'Oracle.

Bien que d'autres languages exécutés côté serveur peuvent être utilisés pour créer un serveur de WebSocket, cet exemple utilise Java d'Oracle pour simplifier le code en exemple.

Ce serveur respecte la RFC 6455, dont il prend uniquement en charge les connexions depuis Chrome 16, Firefox 11, IE 10 et au-delà.

Premiers pas

WebSockets communique via une connexion TCP (Transmission Control Protocol). La classe Java ServerSocket est située dans le paquet java.net.

ServerSocket

Constructeur :

java
ServerSocket(int port)

Lors de l'instanciation de la classe ServerSocket, celle-ci est liée au numéro de port renseigné par le paramètre port.

Voici comment implémenter ce que nous venons d'apprendre :

java
import java.net.ServerSocket;
import java.net.Socket;

public class Server{
    public static void main(String[] args){
        ServerSocket server = new ServerSocket(80);

        System.out.println("Démarrage du serveur sur 127.0.0.1:80.\r\nAttente d’une connexion...");

        Socket client = server.accept();

        System.out.println("Un client s’est connecté.");
    }
}

Socket

Méthodes :

  • java.net.Socket getInputStream() Renvoie un flux d'entrée pour ce socket.
  • java.net.Socket getOutputStream() Renvoie un flux sortant pour ce socket.

OutputStream

Méthode :

java
write(byte[] b, int off, int len)

En débutant à partir de la position off, écrit len octets du tableau d'octets fourni.

InputStream

Méthodes :

java
read(byte[] b, int off, int len)

Reads up to len bytes of data from the input stream into an array of bytes.

Lit jusqu'à len octets de données depuis source d'entrée dans un tableau d'octets.

Développons notre exemple.

java
Socket client = server.accept();

System.out.println("Un client s’est connecté.");

InputStream in = client.getInputStream();

OutputStream out = client.getOutputStream();

new Scanner(in, "UTF-8").useDelimiter("\\r\\n\\r\\n").next();

Établissement d'une liaison (handshaking)

Quand un client se connecte à un serveur, il envoit une requête GET pour passer à une connexion WebSocket à partir d'une simple connexion HTTP. Ceci est appelé l'établissement d'une liaison.

java
import java.util.Scanner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

//translate bytes of request to string
String data = new Scanner(in,"UTF-8").useDelimiter("\\r\\n\\r\\n").next();

Matcher get = Pattern.compile("^GET").matcher(data);

if (get.find()) {

} else {

}

Créer une réponse est plus facile que de comprendre pourquoi vous devez le faire comme ça.

Vous devez,

  1. obtenir la valeur de la requête d'entête Sec-WebSocket-Key sans aucun espacement;
  2. la lier avec « 258EAFA5-E914-47DA-95CA-C5AB0DC85B11 »;
  3. en calculer les codes SHA-1 et Base64;
  4. renvoyer le résultat comme valeur de l'entête de réponse Sec-WebSocket-Accept qui sera une partie d'une réponse HTTP.
java
if (get.find()) {
    Matcher match = Pattern.compile("Sec-WebSocket-Key: (.*)").matcher(data);
    match.find();
    byte[] response = ("HTTP/1.1 101 Switching Protocols\r\n"
            + "Connection: Upgrade\r\n"
            + "Upgrade: websocket\r\n"
            + "Sec-WebSocket-Accept: "
            + DatatypeConverter
            .printBase64Binary(
                    MessageDigest
                    .getInstance("SHA-1")
                    .digest((match.group(1) + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11")
                            .getBytes("UTF-8")))
            + "\r\n\r\n")
            .getBytes("UTF-8");

    out.write(response, 0, response.length);
}

Décoder les messages

Après l'établissement réussie d'une liaison, le client peut transmettre des messages au serveur, ils seront désormais encodés.

Si nous envoyons « abcdef », nous obtenons :

129 134 167 225 225 210 198 131 130 182 194 135

- 129:

FIN (est-ce la totalité du message ?) RSV1 RSV2 RSV3 Opcode
1 0 0 0 0x1=0001

FIN : votre message peut être transmis en plusieurs morceaux, mais restons simple pour l'instant. Opcode 0x1 signifie que ceci est un texte. Liste exhaustive des Opcodes

- 134:

Si le second octet moins 128 est entre 0 et 125, alors il s'agit de la longueur du message. Si c'est 126, les deux octets suivants (entier non signé de 16-bits), si c'est 127, les huit octets suivants (entier non signé de 64-bis, dont le poid ford doit être 0) sont la longueur.

Note : Je peux prendre 128 car le premier bit est toujours 1.

- 167, 225, 225 et 210 sont les octets de la clef à décoder. Cela change en permanence.

- Les octets encodés restants constituent le message.

Algorithme de décodage

octet décodé = octet encodé XOR (position de l'octet ET LOGIQUE 0x3)th octet de la clef

Exemple en Java :

java
byte[] decoded = new byte[6];
byte[] encoded = new byte[] {198, 131, 130, 182, 194, 135};
byte[] key = byte[4] {167, 225, 225, 210};

for (int i = 0; i < encoded.length; i++) {
    decoded[i] = (byte)(encoded[i] ^ key[i & 0x3]);
}

Voir aussi