mrniko / netty-socketio

Socket.IO server implemented on Java. Realtime java framework
Apache License 2.0
6.82k stars 1.65k forks source link

connect to netty-socket with websocket namespace failed #760

Open stoneLee81 opened 4 years ago

stoneLee81 commented 4 years ago

When I was using websocket transport to connect netty-socket server side, it seems to have bug, below is my demo code and excpeitons:

js code: io.connect(domain+"/im/user?appid="+clientid+"&userid="+userid+"&invitation=Y&ipaddr="+ipaddr+"&referer="+referer+"&userAgent="+SIP.Utils.getUserAgent(), {"transports": ["websocket"]});

threw below exception: java.lang.IllegalStateException: null at com.corundumstudio.socketio.protocol.PacketType.valueOf(PacketType.java:48) ~[netty-socketio-1.7.17.jar:?] at com.corundumstudio.socketio.protocol.PacketDecoder.readType(PacketDecoder.java:86) ~[netty-socketio-1.7.17.jar:?] at com.corundumstudio.socketio.protocol.PacketDecoder.decode(PacketDecoder.java:160) ~[netty-socketio-1.7.17.jar:?] at com.corundumstudio.socketio.protocol.PacketDecoder.decodePackets(PacketDecoder.java:142) ~[netty-socketio-1.7.17.jar:?] at com.corundumstudio.socketio.handler.InPacketHandler.channelRead0(InPacketHandler.java:65) [netty-socketio-1.7.17.jar:?] at com.corundumstudio.socketio.handler.InPacketHandler.channelRead0(InPacketHandler.java:36) [netty-socketio-1.7.17.jar:?] at io.netty.channel.SimpleChannelInboundHandler.channelRead(SimpleChannelInboundHandler.java:105) [netty-all-4.1.32.Final.jar:4.1.32.Final] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362) [netty-all-4.1.32.Final.jar:4.1.32.Final] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348) [netty-all-4.1.32.Final.jar:4.1.32.Final] at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340) [netty-all-4.1.32.Final.jar:4.1.32.Final] at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:301) [netty-all-4.1.32.Final.jar:4.1.32.Final] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362) [netty-all-4.1.32.Final.jar:4.1.32.Final] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348) [netty-all-4.1.32.Final.jar:4.1.32.Final] at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340) [netty-all-4.1.32.Final.jar:4.1.32.Final] at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1434) [netty-all-4.1.32.Final.jar:4.1.32.Final] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362) [netty-all-4.1.32.Final.jar:4.1.32.Final] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348) [netty-all-4.1.32.Final.jar:4.1.32.Final] at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:965) [netty-all-4.1.32.Final.jar:4.1.32.Final] at com.corundumstudio.socketio.transport.WebSocketTransport.channelRead(WebSocketTransport.java:95) [netty-socketio-1.7.17.jar:?] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362) [netty-all-4.1.32.Final.jar:4.1.32.Final] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348) [netty-all-4.1.32.Final.jar:4.1.32.Final] at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340) [netty-all-4.1.32.Final.jar:4.1.32.Final] at io.netty.handler.codec.MessageToMessageDecoder.channelRead(MessageToMessageDecoder.java:102) [netty-all-4.1.32.Final.jar:4.1.32.Final] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362) [netty-all-4.1.32.Final.jar:4.1.32.Final] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348) [netty-all-4.1.32.Final.jar:4.1.32.Final] at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340) [netty-all-4.1.32.Final.jar:4.1.32.Final] at io.netty.handler.codec.MessageToMessageDecoder.channelRead(MessageToMessageDecoder.java:102) [netty-all-4.1.32.Final.jar:4.1.32.Final] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362) [netty-all-4.1.32.Final.jar:4.1.32.Final] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348) [netty-all-4.1.32.Final.jar:4.1.32.Final] at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340) [netty-all-4.1.32.Final.jar:4.1.32.Final] at com.corundumstudio.socketio.transport.PollingTransport.channelRead(PollingTransport.java:109) [netty-socketio-1.7.17.jar:?] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362) [netty-all-4.1.32.Final.jar:4.1.32.Final] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348) [netty-all-4.1.32.Final.jar:4.1.32.Final] at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340) [netty-all-4.1.32.Final.jar:4.1.32.Final] at com.corundumstudio.socketio.handler.AuthorizeHandler.channelRead(AuthorizeHandler.java:137) [netty-socketio-1.7.17.jar:?] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362) [netty-all-4.1.32.Final.jar:4.1.32.Final] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348) [netty-all-4.1.32.Final.jar:4.1.32.Final] at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340) [netty-all-4.1.32.Final.jar:4.1.32.Final] at io.netty.channel.SimpleChannelInboundHandler.channelRead(SimpleChannelInboundHandler.java:108) [netty-all-4.1.32.Final.jar:4.1.32.Final] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362) [netty-all-4.1.32.Final.jar:4.1.32.Final] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348) [netty-all-4.1.32.Final.jar:4.1.32.Final] at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340) [netty-all-4.1.32.Final.jar:4.1.32.Final] at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:323) [netty-all-4.1.32.Final.jar:4.1.32.Final] at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:297) [netty-all-4.1.32.Final.jar:4.1.32.Final] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362) [netty-all-4.1.32.Final.jar:4.1.32.Final] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348) [netty-all-4.1.32.Final.jar:4.1.32.Final] at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340) [netty-all-4.1.32.Final.jar:4.1.32.Final] at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1434) [netty-all-4.1.32.Final.jar:4.1.32.Final] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362) [netty-all-4.1.32.Final.jar:4.1.32.Final] at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348) [netty-all-4.1.32.Final.jar:4.1.32.Final] at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:965) [netty-all-4.1.32.Final.jar:4.1.32.Final] at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:163) [netty-all-4.1.32.Final.jar:4.1.32.Final] at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:656) [netty-all-4.1.32.Final.jar:4.1.32.Final] at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:591) [netty-all-4.1.32.Final.jar:4.1.32.Final] at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:508) [netty-all-4.1.32.Final.jar:4.1.32.Final] at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:470) [netty-all-4.1.32.Final.jar:4.1.32.Final] at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:909) [netty-all-4.1.32.Final.jar:4.1.32.Final] at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) [netty-all-4.1.32.Final.jar:4.1.32.Final] at java.lang.Thread.run(Thread.java:748) [?:1.8.0_181]

DoubleDD commented 4 years ago

下面的代码导致了你这个错误 com.corundumstudio.socketio.protocol.PacketDecoder#readNamespace

    private String readNamespace(ByteBuf frame){
        /**
         * namespace post request with url queryString, like
         *  /message?a=1,
         *  /message,
         */
        int endIndex = frame.bytesBefore((byte)'?');
        if(endIndex > 0){
            return readString(frame,endIndex);
        }
        endIndex = frame.bytesBefore((byte)',');
        if(endIndex > 0){
            return readString(frame,endIndex);
        }
        return readString(frame);
    }

当你的namespace包含queryString时,它会只会读取问号前面的部分,不会处理问号后面的部分。

原因分析

例如:

io.connect(domain+"/im/user?appid=clientid&userid=userid“, {"transports": ["websocket"]});

这段代码将会产生一个websocket连接,并且在连接建立成功后client会发送一个数据包到server端, 这个数据包的内容将会是这样的:

40/im/user?appid=clientid&userid=userid

这个数据包分为三部分: 第一部分:4 第一位为主类型,OPEN(0), CLOSE(1), PING(2), PONG(3), MESSAGE(4), UPGRADE(5), NOOP(6) 第二部分:0 第二位为次类型,CONNECT(0, true), DISCONNECT(1, true), EVENT(2, true), ACK(3, true), ERROR(4, true), BINARY_EVENT(5, true), BINARY_ACK(6, true) 第三部分: /im/user?appid=clientid&userid=userid 表示data 服务端在接收到这段数据包后,通过while循环来逐个字节解析数据包PacketDecoder#readNamespace这个方法将会从这段数据包中解析出namespace,这个方法的参数的内容为:/im/user?appid=clientid&userid=userid,对于这个参数,该方法只读取了/im/user这几个字符,导致frame参数里面的内容还剩下?appid=clientid&userid=userid没有处理,而这个frame则来源于InPacketHandler类中的channelRead0方法,代码如下

 @Override
    protected void channelRead0(io.netty.channel.ChannelHandlerContext ctx, PacketsMessage message)
                throws Exception {
        ByteBuf content = message.getContent();
        ClientHead client = message.getClient();

        if (log.isTraceEnabled()) {
            log.trace("In message: {} sessionId: {}", content.toString(CharsetUtil.UTF_8), client.getSessionId());
        }
        while (content.isReadable()) {
            try {
                Packet packet = decoder.decodePackets(content, client);// frame 来源于这里的 content
              ......
    }

frame里面还剩有?appid=clientid&userid=userid这部分内容时,content.isReadable()的值为 true,那么while循环体就会继续执行,最终它会执行到 com.corundumstudio.socketio.protocol.PacketDecoder#readType这个方法

    private PacketType readType(ByteBuf buffer) {   //buffer=?appid=clientid&userid=userid
        int typeId = buffer.readByte() & 0xF; 
        return PacketType.valueOf(typeId);
    }

buffer.readByte() 表示读取一个字节,这里是? 在ascii码表中,?=63。所以,typeId = 63 & 0xF = 15。 再看下PacketType.valueOf方法:

    public static PacketType valueOf(int value) { // value=15
        for (PacketType type : VALUES) {
            if (type.getValue() == value && !type.inner) {
                return type;
            }
        }
        throw new IllegalStateException();
    }
public enum PacketType {

    OPEN(0), CLOSE(1), PING(2), PONG(3), MESSAGE(4), UPGRADE(5), NOOP(6),

    CONNECT(0, true), DISCONNECT(1, true), EVENT(2, true), ACK(3, true), ERROR(4, true), BINARY_EVENT(5, true), BINARY_ACK(6, true);
    ......

所以在PacketType.valueOf方法中符合条件的值是:0,1,2,3,4,5,6,而我们这里是15,自然就会抛出IllegalStateException异常,也就是你所看到的异常。

解决方法

第一种方法:

修改readNamespace源码

    private String readNamespace(ByteBuf frame) {
        /*********************** modify start *************************************/
        ByteBuf buffer = frame.slice();
        // skip this frame
        frame.readerIndex(frame.readerIndex() + frame.readableBytes());
        /*********************** modify end *************************************/

        /**
         * namespace post request with url queryString, like
         *  /message?a=1,
         *  /message,
         */
        int endIndex = buffer.bytesBefore((byte) '?');
        if (endIndex > 0) {
            return readString(buffer, endIndex);
        }
        endIndex = buffer.bytesBefore((byte) ',');
        if (endIndex > 0) {
            return readString(buffer, endIndex);
        }
        return readString(buffer);
    }

第二种方法:

放弃使用 namespace

langxiaojunzi commented 4 years ago

非常感谢,用你的方法解决了这个问题

Mr-Luo-csc commented 4 years ago

pretty nice

soulmz commented 3 years ago

@DoubleDD 我去修改源码,但是发现有点难扩展。有没有具体的修改的方式。1.7.18

DoubleDD commented 3 years ago

@DoubleDD 我去修改源码,但是发现有点难扩展。有没有具体的修改的方式。1.7.18

解决方案一里面的代码贴过去就能用,但是不知道你说的扩展是指的什么,我这边解决这个问题的时候目标很明确,就是要干掉那个报错,并没有做其他的扩展,如果要扩展的话还要看下具体的需求而定。

soulmz commented 3 years ago

@DoubleDD 我去修改源码,但是发现有点难扩展。有没有具体的修改的方式。1.7.18

解决方案一里面的代码贴过去就能用,但是不知道你说的扩展是指的什么,我这边解决这个问题的时候目标很明确,就是要干掉那个报错,并没有做其他的扩展,如果要扩展的话还要看下具体的需求而定。

@DoubleDD 嗯嗯, 我这边已解决了。感谢