using html5 client with a server in java

14,423

Solution 1

I've implemented a simple java server side example which we can take a look at. I'm starting off by creating a ServerSocket which listens for a connection on port 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();
    }
}

As defined in the RFC standard for the websocket protocol, when a client connects through a websocket, a handshake must be done. So let's take a look at the handshake() method, it's pretty ugly so will walk stepwise through it: The first part reads the client handshake.

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]);
        }
    }

The client handshake looks something like this (this is what chrome gave me, version 22.0.1229.94 m), according to the RFC - section 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

Now we can use the keys-map to create a corresponding response in the handshake process. Quoting from the RFC:

To prove that the handshake was received, the server has to take two pieces of information and combine them to form a response. The first piece of information comes from the |Sec-WebSocket-Key| header field in the client handshake. For this header field, the server has to take the value and concatenate this with the Globally Unique Identifier, "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" in string form, which is unlikely to be used by network endpoints that do not understand the WebSocket Protocol. A SHA-1 hash (160 bits) , base64-encoded, of this concatenation is then returned in the server's handshake.

So that's what we have to do! concatenate Sec-WebSocket-Key with a magic string, hash it with the SHA-1 hash function, and Base64-encode it. This is what the next ugly one-liner does.

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;
}

Then we just return the expected response with the newly hash created into the "Sec-WebSocket-Accept" field.

    //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;

}

We have now established a successfully websocket connection between the client and the server. So, what now? How do we make them talk to each other? We can start by sending a message from server to client. NB! We do from this point on, not talk to the client with HTTP anymore. Now we must communicate sending pure bytes, and interpret incoming bytes. So how do we do this?

A message from the server have to be in a certain format called "frames", as spesified in the RFC - section 5.6. When sending a message from the server, the RFC states that the first byte must specify what kind of frame it is. A byte with value 0x81 tells the client that we are sending a "single-frame unmasked text message", which basically is - a text message. The suqsequent byte must represent the length of the message. Following this is the data, or payload. Well, okay... let's implement that!

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();
}

So to send a message to the client, we simply call sendMessage("Hello, client!".getBytes()).

That wasn't too hard? What about recieving messages from the client? Well, it's a little more complicated, but hang in there!

The frame sendt from the client is almost structured the same way as the frame sendt from the server. First byte is type of message, and second byte is payload length. Then there is a difference: the next four bytes represents a mask. What's a mask, and why is messages from the client masked, but the servers messages not? From the RFC - section 5.1, we can see that:

...a client MUST mask all frames that it sends to the server... A server MUST NOT mask any frames that it sends to the client.

So the easy answer is: we just HAVE to. Well why do we have to, you may ask? Didn't I tell you to read the RFC?

Moving on, after the four byte mask in the frame, the masked payload is following on. And one more thing, the client must set the 9th leftmost bit in the frame to 1, to tell the server that the message is masked (Check out the neat ASCII-art frame in the RFC - section 5.2). The 9th leftmost bit corresponds to our leftmost bit in our second byte, but hey, that's our payload length byte! This means that all messages from our client will have a payload length byte equal 0b10000000 = 0x80 + the actual payload length. So to find out the real payload length, we must subtract 0x80, or 128, or 0b10000000 (or any other number system you may prefer) from the payload length byte, our second byte in the frame.

Wow, okay.. that sounds complicated... For you "TLDR"-guys, summary: subtract 0x80 from the second byte to get the payload length...

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!

Now that we have read the whole message, it's time to unmask it so we can make some sense of the payload. To unmask it I've made a method which takes the mask, and the payload as arguments, and return the decoded payload. So the call is done with:

    buf = unMask(Arrays.copyOfRange(buf, 0, 4), Arrays.copyOfRange(buf, 4, buf.length));
    String message = new String(buf);
    return message;
    }
}

Now the unMask method is rather sweet and tiny

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;
}

Same goes for getSizeOfPayload:

private int getSizeOfPayload(byte b) {
    //Must subtract 0x80 from (unsigned) masked frames
    return ((b & 0xFF) - 0x80);
}

That's all! You should now be able to communicate in both directions using pure sockets. I will add the complete Java-class for the sake of completeness. It is capable of recieveing and sending messages with a client using 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());
    }
    }
}

And a simple client in 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>

Hope that this will clear something up, and that I threw some light on it :)

Solution 2

Use jQuery ajax requests from client side, and rest services on server side.
Here about creating of war module with Rest Service

article 1 (Rest Service)

here abour jQuery ajax

article 2 (jQuery Ajax)

To write Java socket server, all that you need is create main program with

  try
  {
     final ServerSocket ss = new ServerSocket(8001);

     while (true)
     {
        final Socket s = ss.accept();
        // @todo s.getInputStream();
     }
  }
  catch (final IOException ex)
  {
     //
  }

it's main cascade of server part

Solution 3

Try reading this blog. It covers how to achieve your work using spring framework. Full support should be added soon, if not already added.

http://keaplogik.blogspot.com.au/2012/05/atmosphere-websockets-comet-with-spring.html?m=1

I'd also suggest checking the spring release notes.

Share:
14,423
Hemant Metalia
Author by

Hemant Metalia

Great communication skills and rich domain knowledge with comprehensive understanding and practical knowledge of various languages, operating systems and databases. Reliable as a fully contributing, responsible and accountable member of task/project teams with highly creative, logical and analytical approach. Focused and hardworking and team oriented; with proven capability to meet coordinate multiple projects. Currently working as, a Technical architect at Publicis Sapient. About 11+ years of total experience working in various roles in software development. I have mainly worked in Java related frameworks and development platforms but I also have some experience of working in C, C++, javascript, angularJS projects. I am fast learner so new technology is not a barrier for me, I can adopt any technology, I consider myself as a professional programmer and given enough money can work on any programming language. Specialties: Application Development for Java platforms i.e. J2EE, Spring, Web Services, Cloud Computing and Research &amp; Development etc. Communicate with me on : Linked In

Updated on July 28, 2022

Comments

  • Hemant Metalia
    Hemant Metalia almost 2 years

    HTML5 client reduce prograamers effort by providing client in html5 websocket client. It will be beneficial to many programmers to learn how to use this html5 websocket client with server in java.

    i want to create an Example of HTML5 client communicating with a java server, but i am not able to find out the way how to do it. can anyone throw a light on it ?

    Reference : demo html5 client/server with c++

    I have found a demo on http://java.dzone.com/articles/creating-websocket-chat but its not working for me..