starter - usando el cliente html5 con un servidor en java
spring-boot-starter-actuator maven (6)
El cliente HTML5 reduce el esfuerzo de los programadores al proporcionar al cliente un cliente de websocket html5. Será beneficioso para muchos programadores aprender a usar este cliente html5 websocket con servidor en java.
Quiero crear un ejemplo de cliente HTML5 que se comunique con un servidor Java, pero no puedo encontrar la manera de hacerlo. ¿Alguien puede arrojar luz sobre eso?
Referencia: demo html5 cliente / servidor con c ++
Encontré una demostración en http://java.dzone.com/articles/creating-websocket-chat pero no funciona para mí ...
Utilice las solicitudes jQuery ajax desde el lado del cliente y los servicios de descanso en el lado del servidor.
Aquí sobre la creación de módulo war con Rest Service
artículo 1 (Servicio de descanso)
aquí sobre jQuery ajax
Para escribir el servidor de socket Java, todo lo que necesita es crear el programa principal con
try
{
final ServerSocket ss = new ServerSocket(8001);
while (true)
{
final Socket s = ss.accept();
// @todo s.getInputStream();
}
}
catch (final IOException ex)
{
//
}
es la cascada principal de la parte del servidor
Estás ejecutando GlassFish. Los sockets web no están habilitados por defecto en él. Para habilitarlos, debes ejecutar el siguiente comando de una sola línea en tu dominio:
asadmin set configs.config.server-config.network-config.protocols.protocol.http-listener-1.http.websockets-support-enabled=true
HttpServlet.init(...)
contenedor servlet llama al método HttpServlet.init(...)
para indicar a un servlet que el servlet se está poniendo en servicio. * Entonces, su mensaje de registro allí no representa la verdad.
Intenta leer este blog. Cubre cómo lograr tu trabajo usando el marco de primavera. Se debe agregar soporte completo pronto, si no se ha agregado aún.
http://keaplogik.blogspot.com.au/2012/05/atmosphere-websockets-comet-with-spring.html?m=1
También sugiero consultar las notas de la versión de primavera.
Implementé un ejemplo simple del lado del servidor de Java que podemos ver. Estoy empezando creando un ServerSocket que escucha una conexión en el puerto 2005
public class WebsocketServer {
public static final int MASK_SIZE = 4;
public static final int SINGLE_FRAME_UNMASKED = 0x81;
private ServerSocket serverSocket;
private Socket socket;
public WebsocketServer() throws IOException {
serverSocket = new ServerSocket(2005);
connect();
}
private void connect() throws IOException {
System.out.println("Listening");
socket = serverSocket.accept();
System.out.println("Got connection");
if(handshake()) {
listenerThread();
}
}
Tal como se define en el estándar RFC para el protocolo de websocket , cuando un cliente se conecta a través de un websocket, se debe realizar un handshake . Así que echemos un vistazo al método handshake (), es bastante feo, así que caminaremos paso a paso a través de él: la primera parte lee el saludo del cliente.
private boolean handshake() throws IOException {
PrintWriter out = new PrintWriter(socket.getOutputStream());
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
//This hashmap will be used to store the information given to the server in the handshake
HashMap<String, String> keys = new HashMap<>();
String str;
//Reading client handshake, handshake ends with CRLF which is again specified in the RFC, so we keep on reading until we hit ""...
while (!(str = in.readLine()).equals("")) {
//Split the string and store it in our hashmap
String[] s = str.split(": ");
System.out.println(str);
if (s.length == 2) {
keys.put(s[0], s[1]);
}
}
El saludo del cliente se parece a esto (esto es lo que me dio Chrome, versión 22.0.1229.94 m), de acuerdo con el RFC - sección 1.2.
GET / HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Host: localhost:2005
Origin: null
Sec-WebSocket-Key: PyvrecP0EoFwVnHwC72ecA==
Sec-WebSocket-Version: 13
Sec-WebSocket-Extensions: x-webkit-deflate-frame
Ahora podemos usar el mapa de teclas para crear una respuesta correspondiente en el proceso de diálogo. Citando de la RFC:
Para demostrar que se recibió el apretón de manos, el servidor debe tomar dos piezas de información y combinarlas para formar una respuesta. La primera información proviene de | Sec-WebSocket-Key | campo de encabezado en el protocolo de enlace del cliente. Para este campo de encabezado, el servidor debe tomar el valor y concatenarlo con el identificador único global, "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" en forma de cadena, que es poco probable que sea utilizado por los puntos finales de la red que no entienden el Protocolo de WebSocket. A continuación, se devuelve un hash SHA-1 (160 bits), codificado en base64, de esta concatenación en el protocolo de enlace del servidor.
¡Entonces eso es lo que tenemos que hacer! concatenar Sec-WebSocket-Key con una cadena mágica, hash it con la función SHA-1 hash y Base64-encode it. Esto es lo que hace el siguiente feo.
String hash;
try {
hash = new BASE64Encoder().encode(MessageDigest.getInstance("SHA-1").digest((keys.get("Sec-WebSocket-Key") + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").getBytes()));
} catch (NoSuchAlgorithmException ex) {
ex.printStackTrace();
return false;
}
A continuación, devolvemos la respuesta esperada con el hash recién creado en el campo "Sec-WebSocket-Accept".
//Write handshake response
out.write("HTTP/1.1 101 Switching Protocols/r/n"
+ "Upgrade: websocket/r/n"
+ "Connection: Upgrade/r/n"
+ "Sec-WebSocket-Accept: " + hash + "/r/n"
+ "/r/n");
out.flush();
return true;
}
Ahora hemos establecido una conexión websocket exitosa entre el cliente y el servidor. ¿Y ahora qué? ¿Cómo hacemos que hablen entre ellos? Podemos comenzar enviando un mensaje del servidor al cliente. ¡NÓTESE BIEN! Lo hacemos a partir de ahora, ya no hablemos con el cliente con HTTP. Ahora debemos comunicar el envío de bytes puros e interpretar los bytes entrantes. ¿Entonces como hacemos esto?
Un mensaje del servidor debe estar en un formato determinado llamado "marcos", como se especifica en el RFC - sección 5.6. Al enviar un mensaje desde el servidor, el RFC establece que el primer byte debe especificar qué tipo de marco es. Un byte con valor 0x81 le dice al cliente que estamos enviando un "mensaje de texto sin máscara de fotograma único", que básicamente es un mensaje de texto. El byte secundario debe representar la longitud del mensaje. A continuación están los datos o la carga útil. Bueno, está bien ... ¡Implementémoslo!
public void sendMessage(byte[] msg) throws IOException {
System.out.println("Sending to client");
ByteArrayOutputStream baos = new ByteArrayOutputStream();
BufferedOutputStream os = new BufferedOutputStream(socket.getOutputStream());
//first byte is kind of frame
baos.write(SINGLE_FRAME_UNMASKED);
//Next byte is length of payload
baos.write(msg.length);
//Then goes the message
baos.write(msg);
baos.flush();
baos.close();
//This function only prints the byte representation of the frame in hex to console
convertAndPrint(baos.toByteArray());
//Send the frame to the client
os.write(baos.toByteArray(), 0, baos.size());
os.flush();
}
Entonces, para enviar un mensaje al cliente, simplemente llamamos a sendMessage ("¡Hola, cliente!", GetBytes ()).
Eso no fue muy difícil? ¿Qué hay de recibir mensajes del cliente? Bueno, es un poco más complicado, ¡pero espera!
El frame sendt del cliente está casi estructurado de la misma manera que el frame sendt del servidor. El primer byte es el tipo de mensaje y el segundo byte es la longitud de la carga. Entonces hay una diferencia: los siguientes cuatro bytes representan una máscara . ¿Qué es una máscara y por qué los mensajes del cliente están enmascarados, pero los mensajes de los servidores no? Desde el RFC - sección 5.1, podemos ver que:
... un cliente DEBE enmascarar todos los marcos que envía al servidor ... Un servidor NO DEBE enmascarar ningún marco que envíe al cliente.
Entonces, la respuesta fácil es: simplemente TENEMOS que hacerlo. Bueno, ¿por qué tenemos que hacerlo, puedes preguntar? ¿No te dije que leyeras el RFC?
Continuando, después de la máscara de cuatro bytes en el marco, la carga útil enmascarada sigue. Y una cosa más, el cliente debe establecer el noveno bit más a la izquierda en el cuadro en 1, para decirle al servidor que el mensaje está enmascarado (Vea el marco ASCII-art ordenado en el RFC - sección 5.2). El noveno bit más a la izquierda corresponde a nuestro bit más a la izquierda en nuestro segundo byte, pero bueno, ¡ese es nuestro byte de longitud de carga útil! Esto significa que todos los mensajes de nuestro cliente tendrán un byte de longitud de carga igual a 0b10000000 = 0x80 + la longitud real de la carga útil. Entonces, para conocer la longitud real de la carga útil, debemos restar 0x80, o 128, o 0b10000000 (o cualquier otro sistema numérico que prefiera) del byte de longitud de carga, nuestro segundo byte en el marco.
Wow, está bien ... eso suena complicado ... Para usted, "TLDR" -guys, summary: reste 0x80 del segundo byte para obtener la longitud de la carga útil ...
public String reiceveMessage() throws IOException {
//Read the first two bytes of the message, the frame type byte - and the payload length byte
byte[] buf = readBytes(2);
System.out.println("Headers:");
//Print them in nice hex to console
convertAndPrint(buf);
//And it with 00001111 to get four lower bits only, which is the opcode
int opcode = buf[0] & 0x0F;
//Opcode 8 is close connection
if (opcode == 8) {
//Client want to close connection!
System.out.println("Client closed!");
socket.close();
System.exit(0);
return null;
}
//Else I just assume it''s a single framed text message (opcode 1)
else {
final int payloadSize = getSizeOfPayload(buf[1]);
System.out.println("Payloadsize: " + payloadSize);
//Read the mask, which is 4 bytes, and than the payload
buf = readBytes(MASK_SIZE + payloadSize);
System.out.println("Payload:");
convertAndPrint(buf);
//method continues below!
Ahora que hemos leído todo el mensaje, es hora de desenmascararlo para que podamos darle sentido a la carga útil. Para desenmascararlo, hice un método que toma la máscara y la carga como argumentos y devuelvo la carga decodificada. Entonces la llamada se hace con:
buf = unMask(Arrays.copyOfRange(buf, 0, 4), Arrays.copyOfRange(buf, 4, buf.length));
String message = new String(buf);
return message;
}
}
Ahora el método unMask es bastante dulce y pequeño
private byte[] unMask(byte[] mask, byte[] data) {
for (int i = 0; i < data.length; i++) {
data[i] = (byte) (data[i] ^ mask[i % mask.length]);
}
return data;
}
Lo mismo ocurre con getSizeOfPayload:
private int getSizeOfPayload(byte b) {
//Must subtract 0x80 from (unsigned) masked frames
return ((b & 0xFF) - 0x80);
}
¡Eso es todo! Ahora debería poder comunicarse en ambas direcciones usando sockets puros. Agregaré la clase completa de Java en aras de la integridad. Es capaz de recibir y enviar mensajes con un cliente usando websockets.
package javaapplication5;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.HashMap;
import sun.misc.BASE64Encoder;
/**
*
* @author
* Anders
*/
public class WebsocketServer {
public static final int MASK_SIZE = 4;
public static final int SINGLE_FRAME_UNMASKED = 0x81;
private ServerSocket serverSocket;
private Socket socket;
public WebsocketServer() throws IOException {
serverSocket = new ServerSocket(2005);
connect();
}
private void connect() throws IOException {
System.out.println("Listening");
socket = serverSocket.accept();
System.out.println("Got connection");
if(handshake()) {
listenerThread();
}
}
private boolean handshake() throws IOException {
PrintWriter out = new PrintWriter(socket.getOutputStream());
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
HashMap<String, String> keys = new HashMap<>();
String str;
//Reading client handshake
while (!(str = in.readLine()).equals("")) {
String[] s = str.split(": ");
System.out.println();
System.out.println(str);
if (s.length == 2) {
keys.put(s[0], s[1]);
}
}
//Do what you want with the keys here, we will just use "Sec-WebSocket-Key"
String hash;
try {
hash = new BASE64Encoder().encode(MessageDigest.getInstance("SHA-1").digest((keys.get("Sec-WebSocket-Key") + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").getBytes()));
} catch (NoSuchAlgorithmException ex) {
ex.printStackTrace();
return false;
}
//Write handshake response
out.write("HTTP/1.1 101 Switching Protocols/r/n"
+ "Upgrade: websocket/r/n"
+ "Connection: Upgrade/r/n"
+ "Sec-WebSocket-Accept: " + hash + "/r/n"
+ "/r/n");
out.flush();
return true;
}
private byte[] readBytes(int numOfBytes) throws IOException {
byte[] b = new byte[numOfBytes];
socket.getInputStream().read(b);
return b;
}
public void sendMessage(byte[] msg) throws IOException {
System.out.println("Sending to client");
ByteArrayOutputStream baos = new ByteArrayOutputStream();
BufferedOutputStream os = new BufferedOutputStream(socket.getOutputStream());
baos.write(SINGLE_FRAME_UNMASKED);
baos.write(msg.length);
baos.write(msg);
baos.flush();
baos.close();
convertAndPrint(baos.toByteArray());
os.write(baos.toByteArray(), 0, baos.size());
os.flush();
}
public void listenerThread() {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
try {
while (true) {
System.out.println("Recieved from client: " + reiceveMessage());
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
});
t.start();
}
public String reiceveMessage() throws IOException {
byte[] buf = readBytes(2);
System.out.println("Headers:");
convertAndPrint(buf);
int opcode = buf[0] & 0x0F;
if (opcode == 8) {
//Client want to close connection!
System.out.println("Client closed!");
socket.close();
System.exit(0);
return null;
} else {
final int payloadSize = getSizeOfPayload(buf[1]);
System.out.println("Payloadsize: " + payloadSize);
buf = readBytes(MASK_SIZE + payloadSize);
System.out.println("Payload:");
convertAndPrint(buf);
buf = unMask(Arrays.copyOfRange(buf, 0, 4), Arrays.copyOfRange(buf, 4, buf.length));
String message = new String(buf);
return message;
}
}
private int getSizeOfPayload(byte b) {
//Must subtract 0x80 from masked frames
return ((b & 0xFF) - 0x80);
}
private byte[] unMask(byte[] mask, byte[] data) {
for (int i = 0; i < data.length; i++) {
data[i] = (byte) (data[i] ^ mask[i % mask.length]);
}
return data;
}
private void convertAndPrint(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02X ", b));
}
System.out.println(sb.toString());
}
public static void main(String[] args) throws IOException, InterruptedException, NoSuchAlgorithmException {
WebsocketServer j = new WebsocketServer();
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
while (true) {
System.out.println("Write something to the client!");
j.sendMessage(br.readLine().getBytes());
}
}
}
Y un cliente simple en html:
<!DOCTYPE HTML>
<html>
<body>
<button type="button" onclick="connect();">Connect</button>
<button type="button" onclick="connection.close()">Close</button>
<form>
<input type="text" id="msg" />
<button type="button" onclick="sayHello();">Say Hello!</button>
<script>
var connection;
function connect() {
console.log("connection");
connection = new WebSocket("ws://localhost:2005/");
// Log errors
connection.onerror = function (error) {
console.log(''WebSocket Error '');
console.log(error);
};
// Log messages from the server
connection.onmessage = function (e) {
console.log(''Server: '' + e.data);
alert("Server said: " + e.data);
};
connection.onopen = function (e) {
console.log("Connection open...");
}
connection.onclose = function (e) {
console.log("Connection closed...");
}
}
function sayHello() {
connection.send(document.getElementById("msg").value);
}
function close() {
console.log("Closing...");
connection.close();
}
</script>
</body>
</html>
Espero que esto aclare algo, y que arrojé algo de luz sobre él :)
También puede lograr eso utilizando el marco existente como: jWebsocket
Este es el mismo código anterior solo le permite recibir mensajes del cliente que están por encima de los 126 bytes. Muchos códigos fuente de socket web no han descubierto la fragmentación.
// Modified code from Anders, - Christopher Price
package GoodExample;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.HashMap;
import sun.misc.BASE64Encoder;
public class JfragWS {
public static final int MASK_SIZE = 4;
public static final int SINGLE_FRAME_UNMASKED = 0x81;
private ServerSocket serverSocket;
private Socket socket;
public JfragWS() throws IOException {
serverSocket = new ServerSocket(1337);
connect();
}
private void connect() throws IOException {
System.out.println("Listening");
socket = serverSocket.accept();
System.out.println("Got connection");
if(handshake()) {
listenerThread();
}
}
private boolean handshake() throws IOException {
PrintWriter out = new PrintWriter(socket.getOutputStream());
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
HashMap<String, String> keys = new HashMap<>();
String str;
//Reading client handshake
while (!(str = in.readLine()).equals("")) {
String[] s = str.split(": ");
System.out.println();
System.out.println(str);
if (s.length == 2) {
keys.put(s[0], s[1]);
}
}
//Do what you want with the keys here, we will just use "Sec-WebSocket-Key"
String hash;
try {
hash = new BASE64Encoder().encode(MessageDigest.getInstance("SHA-1").digest((keys.get("Sec-WebSocket-Key") + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").getBytes()));
} catch (NoSuchAlgorithmException ex) {
ex.printStackTrace();
return false;
}
//Write handshake response
out.write("HTTP/1.1 101 Switching Protocols/r/n"
+ "Upgrade: websocket/r/n"
+ "Connection: Upgrade/r/n"
+ "Sec-WebSocket-Accept: " + hash + "/r/n"
+ "Origin: http://face2fame.com/r/n"
+ "/r/n");
out.flush();
return true;
}
private byte[] readBytes(int numOfBytes) throws IOException {
byte[] b = new byte[numOfBytes];
socket.getInputStream().read(b);
return b;
}
public void sendMessage(byte[] msg) throws IOException {
System.out.println("Sending to client");
ByteArrayOutputStream baos = new ByteArrayOutputStream();
BufferedOutputStream os = new BufferedOutputStream(socket.getOutputStream());
baos.write(SINGLE_FRAME_UNMASKED);
baos.write(msg.length);
baos.write(msg);
baos.flush();
baos.close();
convertAndPrint(baos.toByteArray());
os.write(baos.toByteArray(), 0, baos.size());
os.flush();
}
public void listenerThread() {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
try {
while (true) {
System.out.println("Recieved from client: " + reiceveMessage());
System.out.println("Enter data to send");
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
});
t.start();
}
public String reiceveMessage() throws IOException {
String EasyBytes = null;
byte[] buf = readBytes(2); // our initial header
convertAndPrint(buf);
//System.exit(0);
EasyBytes = (String.format("%02X ", buf[1]));
int payloadadder = 0;
if (EasyBytes.contains("FE")){ // Indicates extended message
byte[] buf2 = readBytes(1);
int a = (buf2[0] & 0xff) + 1; // if byte is zero there is one extra fragment so add 1!
System.out.println("Number of extra bytes" + a);
payloadadder = 2; // account for original header size
byte[] adder = null;
//String MagnificentString = "";
for (int x = 0; x < a; x++){
if(x==0){
adder = readBytes(1);
//MagnificentString += String.format("%02X ", adder[0]);
payloadadder += ((adder[0] & 0xFF) - 0x80);}
if(x==1){
payloadadder = (buf[1] & 0xFF) + (adder[0] & 0xFF);
}
if(x>1){
payloadadder = (Integer.parseInt((String.format("%02X", buf2[0]) + String.format("%02X", adder[0])), 16));
//System.out.println(String.format("%02X", buf2[0]) + String.format("%02X", adder[0]));
}
}
System.out.println("Overflow in byte/s " + payloadadder);
//System.out.println("Our Hex String " + MagnificentString);
//System.exit(0);
}
//convertAndPrint(buf);
//dont use this byte[] buf2 = readBytes(4);
System.out.println("Headers:");
//convertAndPrint(buf2);// Check out the byte sizes
int opcode = buf[0] & 0x0F;
if (opcode == 8) {
//Client want to close connection!
System.out.println("Client closed!");
socket.close();
System.exit(0);
return null;
} else {
int payloadSize = 0;
if (payloadadder <= 0){
payloadSize = getSizeOfPayload(buf[1]);}
else {
payloadSize = getSizeOfPayload(buf[1]) + payloadadder;
}
// if (extendedsize>=126){
//payloadSize = extendedsize;}
System.out.println("Payloadsize: " + payloadSize);
buf = readBytes(MASK_SIZE + payloadSize);
System.out.println("Payload:");
convertAndPrint(buf);
buf = unMask(Arrays.copyOfRange(buf, 0, 4), Arrays.copyOfRange(buf, 4, buf.length));
String message = new String(buf);
return message;
}
}
private int getSizeOfPayload(byte b) {
//Must subtract 0x80 from masked frames
int a = b & 0xff;
//System.out.println("PAYLOAD SIZE INT" + a);
return ((b & 0xFF) - 0x80);
}
private byte[] unMask(byte[] mask, byte[] data) {
for (int i = 0; i < data.length; i++) {
data[i] = (byte) (data[i] ^ mask[i % mask.length]);
}
return data;
}
private boolean convertAndPrintHeader(byte[] bytes) {
StringBuilder sb = new StringBuilder();
String CaryOverDetection = new String();
// We must test byte 2 specifically for this. In the next step we add length bytes perhaps?
//for(int i = 0; i < bytes.length; i++) {
//}
for (byte b : bytes) {
CaryOverDetection = (String.format("%02X ", b));
if (CaryOverDetection.contains("FE")){
return false;
}
sb.append(String.format("%02X ", b));
}
System.out.println(sb.toString());
return true;
}
private void convertAndPrint(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02X ", b));
}
System.out.println(sb.toString());
}
public static void main(String[] args) throws IOException, InterruptedException, NoSuchAlgorithmException {
JfragWS j = new JfragWS();
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
while (true) {
System.out.println("Write something to the client!");
j.sendMessage(br.readLine().getBytes());
}
}
}