Search code examples
javawebsocketserverjettyjava-websocket

Websocket(Jetty) : How to handle the binary data on server side which is coming in chunks


I need to setup a websocket server which can receive audio data sent by the client. I am using Jetty for this.

My handler code :

{
    @OnWebSocketClose
    public void onClose(int statusCode, String reason) {
    }

    @OnWebSocketError
    public void onError(Throwable t) {
    }

    @OnWebSocketConnect
    public void onConnect(Session session) {

    }

    @OnWebSocketMessage
    public void onMessage(String message) {
    }

    @OnWebSocketMessage
    public void onMessage(bytes [] b) {
    }

    @OnWebSocketMessage
    public void inputStream(InputStream is) {
    }
}

Since the audio file is quite large, the client is sending those in chunks. Now for every chunk, the onMessage(bytes [] b) {} method is getting invoked.

On the server side, I need to add these chunks and process the audio. How can I do that?

Also what is the difference between onMessage(bytes [] b) {} and onMessage(InputStream is) {} methods?


Solution

  • According to the onMessage javadoc, both onMessage(byte[] b) and onMessage(InputStream is) will receive the whole message:

    if the method is handling binary messages:

    • byte[] or ByteBuffer to receive the whole message
    • InputStream to receive the whole message as a blocking stream

    So, if you use one of these methods, Jetty will automatically reassemble the chunks and deliver the whole message to your method (as a byte[] array or an InputStream).

    The maximum size of the binary message, you can receive this way, is set with the setMaxBinaryMessageSize

    Also note, that you may only have one of these methods defined at your Handler class at once:

    Each websocket endpoint may only have one message handling method for each of the native websocket message formats: text, binary and pong.

    If you want to process the data as it goes, you should use the following method signature instead:

    @OnMessage
    public void processUpload(byte[] b, boolean last, Session session) {
        // process partial data here, which check on last to see if these is more on the way
    }
    

    and manually buffer your data (in memory or disk file e.t.c.)

    The client, in turn, should use one of sendPartialBytes methods, to send the data chunk by chunk.


    If what you observe is the "whole message" method (i.e. onMessage(byte[] b) being invoked for an each data chunk, as send by the client, this indicates that the client is not utilizing the protocol capabilities to send chunked messages, and does instead split the input data and then sends its parts as independent WS messages, effectively creating it's own application level protocol for transmitting the data in chunks.

    In this case, you must either update the client (if that is an option), to make it use a regular WS protocol capabilities, or implement the same "application level protocol" the client is using.

    Below is a super-simple endpoint example, which will buffer the input data until the WS socket is closed (this implies that client will close the connection once it has sent all the chunks).

    @ServerEndpoint("/data")
    public static class Handler {
    
        private ByteArrayOutputStream buffer = new ByteArrayOutputStream();
    
        @OnMessage
        public void onMessage(byte[] message) throws IOException {
            buffer.write(message);
        }
    
        @OnClose
        public void onClose(Session session) throws IOException {
            System.out.println(
                buffer.toByteArray().length
            );
        }
    
    }
    

    This implementation also implies that a default ServerEndpointConfig.Configurator is used, so that there is exactly one Endpoint instance per connection (as documented here).

    A more complex implementation might want to reuse the socket to send multiple files, and specify a mechanism to denote the start and end of each transmission (e.g. with a specially formatted message), but it all depends on how your client is implemented.