1. The background

In corporate business, websockets are used to communicate messages between clients and servers, which are developed in C and use the libWebSockets library. Due to the upgrade of system architecture, a new signaling service module is established and developed using Java language as a new Websocket server.

2. Problems and analysis

The signaling server serves as a Websocket service. The Web client has no problem connecting to the signaling server, but the Linux SDK always fails to establish a Websocket connection with the signaling server. Linux SDK webSocket client also uses libWebSockets library, which carries the following Connection when initiating a websocket Connection request:

Connection: Upgrade,close
Copy the code

Where Upgrade means to Upgrade HTTP to websocket. Close is used when HTTP fails to be upgraded to websocket.

In practice, many servers don’t support this (older servers support it because they use the libWebSockets library as well), such as Spring WebSocket, which contains close in the request header, returns the following and disconnects.

Connection: close
Copy the code

The attached:

Case discussion on Github:

https://github.com/warmcat/libwebsockets/issues/1435
Copy the code

Libwebsockets library

https://github.com/warmcat/libwebsockets/commit/bc394b0680ba4b0a1789d549f9464ad9ae6425a5#diff-277c228a17322dfa446081eac59cbd2d
Copy the code

3. Solutions

3.1 train of thought

Since the Connection contains close, consider removing the close when the request is received. The following schemes were tried, but none was feasible:

  • In WebSocketConfigurer implementation class registered HttpSessionHandshakeInterceptor subclasses, in beforeHandshake method to deal with the head.
  • Using an interceptor, the header is processed in the interceptor.

The Connection header of the service response is always close, and then the Connection is closed.

3.2 Breakpoint Debugging

Desperate, I started debugging the breakpoint step by step and found that there are methods in Tomcat’s built-in Http11Processor classes prepareRequest() and prepareResponse(). There’s code in the prepareResponse() method:

keepAlive
close



prepareRequest()

If the Request Connection header contains close, keepAlive is set to false, which causes a close value to be added to the Response Connection header in the prepareResponse() method. And debugging found that the prepareRequest() of the Http11Processor class executes first in the interceptor and also in the beforeHandshake() method, so modifying the Request Header in the interceptor and beforeHandshake() method doesn’t work.

3.3 Overriding the MimeHeaders class

From the previous debugging analysis, we know why close was added to the Response Connection header. By reading the code, we can see that the request Connection value comes from these two lines:

MimeHeaders headers = request.getMimeHeaders();

// Check connection header
MessageBytes connectionValueMB = headers.getValue(Constants.CONNECTION);
Copy the code

The MimeHeaders getValue() method is overwritten to create a package with the same name and a class with the same name. According to the JVM class loading mechanism, the class files in the project directory will be loaded first, and the main code is overwritten as follows:

package org.apache.tomcat.util.http;

public MessageBytes getValue(String name) {
    for(int i = 0; i < this.count; ++i) {
        if (this.headers[i].getName().equalsIgnoreCase(name)) {
            returngetHeader(i); }}returnnull; } private MessageBytes getHeader(int I) {if (this.headers[i].getName().equalsIgnoreCase("connection")) {
        String originValue = this.headers[i].getValue().getString();
        if ((originValue.contains("close") || originValue.contains("Close"))
                && (originValue.contains("upgrade") || originValue.contains("Upgrade"))) {
            MessageBytes messageBytes = MessageBytes.newInstance();
            messageBytes.setString(convertConnectionHeader(originValue));
            returnmessageBytes; }}return this.headers[i].getValue();
}

public static String convertConnectionHeader(String oldValue) {
    String[] array = oldValue.split(",");
    Set<String> headerSet = new HashSet<>();
    for (int i = 0; i < array.length; i++) {
        headerSet.add(array[i].trim());
    }
    headerSet.remove("close");
    headerSet.remove("Close");
    return StringUtil.concat(",", headerSet.stream().toArray());
}
Copy the code