0

I'm writing a Java websocket server and it now receives the opening handshake message from the client (in Chrome Version 57.0.2987.133 (64-bit)) and responds to complete the handshake, both shown below.

Received: GET / HTTP/1.1
Received: Host: localhost:6789
Received: Connection: Upgrade
Received: Pragma: no-cache
Received: Cache-Control: no-cache
Received: Upgrade: websocket
Received: Origin: http://localhost:8080
Received: Sec-WebSocket-Version: 13
Received: User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36
Received: Accept-Encoding: gzip, deflate, sdch, br
Received: Accept-Language: en-US,en;q=0.8
Received: Sec-WebSocket-Key: L1IiUSGijbGmTpthWsebOg==
Received: Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits

Sending: HTTP/1.1 101 Switching Protocols
Sending: Upgrade: websocket
Sending: Connection: Upgrade
Sending: Sec-WebSocket-Accept: L5HXnDJGDMYbWr5gRcQMOwKNf3Q=
Sending: Accept-Encoding: gzip, deflate
Sending: Sec-WebSocket-Extensions: permessage-deflate; client_no_context_takeover; server_no_context_takeover

Now, the client can send messages and it does so without a problem and my code successfully uncompresses them using java.util.zip.Deflater and... if my server responds with header bytes 0x81 (fin, no compression, text) and 0x5 then the bytes for 'hello' (as an example) then the websocket client in javascript on Chrome is entirely happy but when I try to compress the response, the client always closes the connection citing error code 1002 and the text 'Websocket protocol error'.

DEFLATE

    public void sendMessageDeflated(String rxMessage, OutputStream streamOut) {

    System.out.println("Message back to client is: " + rxMessage);

    // And then compress the response and send it out.
    Deflater compressor = new Deflater(Deflater.DEFLATED);
    try {
        int headerLength = 2;
        byte unzippedMsg[] = rxMessage.getBytes("UTF-8");
        compressor.setInput(unzippedMsg);
        compressor.finish();
        byte zippedMsg[] = new byte[2048];  // Nasty constant but will have to do for now.
        int toCompressLength = unzippedMsg.length;
        int compLength = compressor.deflate(zippedMsg, headerLength, zippedMsg.length - headerLength);
        compressor.end();

        zippedMsg[0] = (byte)0xC1; // FIN bit, compression plus opcode for TEXT MESSAGE
        zippedMsg[1] = (byte)((byte)0x00 | (byte)compLength); // No mask on return data.

        streamOut.write(zippedMsg, 0, compLength + headerLength);

    } catch ( IOException ioEx ) {
        // TBD
        System.out.println("IOException: " + ioEx.toString());
    } catch ( Exception ex ) {
        // TBD
        System.out.println("IOException: " + ex.toString());
    }
}

GZIP

    public void sendMessageGZipped(String rxMessage, OutputStream streamOut) {
    // Do something with the message here...

    System.out.println("Message back to client is: " + rxMessage);

    // And then compress the response and send it out.
    try {
        int headerLength = 2;
        byte unzippedMsg[] = rxMessage.getBytes("UTF-8");
        int toCompressLength = unzippedMsg.length;

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        GZIPOutputStream gzipOut = new GZIPOutputStream(baos);
        gzipOut.write(unzippedMsg, 0, toCompressLength);
        gzipOut.close();
        byte[] payload = baos.toByteArray();

        byte header[] = new byte[32];
        header[0] = (byte)0xC1; // FIN bit plus opcode for TEXT MESSAGE
        header[1] = (byte)((byte)0x00 | (byte)payload.length); // No mask on return data.

        streamOut.write(header, 0, 2);
        streamOut.write(payload);

    } catch ( IOException ioEx ) {
        // TBD
        System.out.println("IOException: " + ioEx.toString());
    } catch ( Exception ex ) {
        // TBD
        System.out.println("IOException: " + ex.toString());
    }
}

I've tried switching to the opcode for binary thinking that perhaps compressed text = binary but that didn't work. I really can't see what I've done wrong or missed out. This implementation does not include a sliding compression window that spans messages. I think the response headers make that clear. Help gratefully accepted.

PhilD66
  • 11
  • 3

1 Answers1

1

I solved this eventually by building the client-side websocket code and seeing the exception thrown when it tried to Inflate the message: "invalid stored block lengths". That led me to this post: Java decompressing array of bytes which talks about Deflate being able to compress with zlib wrapper or without. So change one line in processMessage2 from the example code above to this...

        Deflater compressor = new Deflater(Deflater.DEFLATED, true);

...which sets the 'nowrap' to true.

In its headers the Chrome browser client claims to support gzip. If I get that working I'll come back here and post the answer to that too.

Community
  • 1
  • 1
PhilD66
  • 11
  • 3