专注于 JetBrains IDEA 全家桶,永久激活,教程
持续更新 PyCharm,IDEA,WebStorm,PhpStorm,DataGrip,RubyMine,CLion,AppCode 永久激活教程

WebSocket踩坑-头部Connection="close"

1. 背景

在公司业务中,客户端和服务器之间通过websocket进行消息传递,服务器是用C开发的,并且使用了libwebsockets库。
由于系统架构升级,新建立信令服务模块,使用Java语言开发,作为新的websocket服务器。

2. 问题及分析

信令服务器作为websocket服务,web客户端连接服务器没有问题,但是Linux sdk总是无法和信令服务器建立websocket连接。Linux sdk的websocket客户端也使用了libwebsockets库,该库在发起websocket连接请求是携带的Connection如下:

Connection: Upgrade,close

其中Upgrade表示要将http升级为websocket。作者称其中close是用于将http升级成websocket失败时对应的处理,描述如下:

89_1.png

但实际上许多服务器并不支持这样(旧服务器支持因为也使用了libwebsockets库),比如Spring websocket在request头部中包含close时,会返回如下,并断开连接

Connection: close

附:

github上case讨论:

https://github.com/warmcat/libwebsockets/issues/1435

libwebsockets库修复:

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

3. 解决方案

3.1 思路

由于时Connection中包含close引起的,所以思考在收到请求时去掉close
尝试了以下几种方案,均不可行:

  • 在WebSocketConfigurer实现类中注册了HttpSessionHandshakeInterceptor子类,在beforeHandshake方法中处理头部。
  • 使用拦截器,在拦截器中处理头部。

可以说测试到近乎崩溃,明明修改了request的Connection header,但是发现服务response的Connection header值总是close,然后直接关闭了连接。

3.2 断点调试

走投无路,开始打断点一步一步调试,发现在tomcat内置的Http11Processor类中有prepareRequest()prepareResponse()方法。在prepareResponse()方法中有这么一段代码:

89_2.pngkeepAlive值为false时,则在Connection header中添加 close,这时候已经看到曙光了,接下来就要寻找keepalive什么时候会被修改成false。
然后在 prepareRequest()中发现这么一段代码

89_3.png

如果request Connection头中包含close,就会将keepAlive置为false,这使得在prepareResponse()方法中给response Connection header添加了close值。并且调试发现,Http11Processor类的prepareRequest()在拦截器以及beforeHandshake()方法都更先执行,所以在拦截器beforeHandshake()方法中修改request header都无效。

3.3 重写MimeHeaders类

根据前面的调试分析,已经知道了response Connection header中添加close的原因,通过阅读代码可知,request Connection value的值来源于这两行:

MimeHeaders headers = request.getMimeHeaders();

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

那么,只需要重写MimeHeaders.getValue()方法即可,建立同名package,再建立同名class,根据JVM类加载机制,会优先加载工程目录下的类文件,重写主要代码如下:

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)) {
            return getHeader(i);
        }
    }

    return null;
}

// 重写header方法
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));
            return messageBytes;
        }
    }

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

文章永久链接:https://tech.souyunku.com/36644

未经允许不得转载:搜云库技术团队 » WebSocket踩坑-头部Connection="close"

JetBrains 全家桶,激活、破解、教程

提供 JetBrains 全家桶激活码、注册码、破解补丁下载及详细激活教程,支持 IntelliJ IDEA、PyCharm、WebStorm 等工具的永久激活。无论是破解教程,还是最新激活码,均可免费获得,帮助开发者解决常见激活问题,确保轻松破解并快速使用 JetBrains 软件。获取免费的破解补丁和激活码,快速解决激活难题,全面覆盖 2024/2025 版本!

联系我们联系我们