简体   繁体   English

JS WebSocket 和 Java 之间的握手

[英]Handshake between JS WebSocket and Java

I tried to implement the WebSocket Protocol and connecting a JavaScript WebSocket with a Java WebSocket Server.我尝试实现 WebSocket 协议并将 JavaScript WebSocket 与 Java WebSocket 服务器连接。

The JavaScript part is pretty straight forward and works as expected. JavaScript 部分非常简单,并且按预期工作。 I wrote the Java server myself and read the rfc 6455 page 7 for the correct handshake response.我自己编写了 Java 服务器并阅读了rfc 6455 第 7 页以获得正确的握手响应。 So the server generates the correct response and sends it.所以服务器生成正确的响应并发送它。 I wrote a Java client dummy to make sure it gets send.我写了一个 Java 客户端虚拟机来确保它被发送。

But the problem is that the JavaScript / Browser seems not to receive the handshake response and kills the request after some seconds (but does not close the tcp socket).但问题是 JavaScript / 浏览器似乎没有收到握手响应并在几秒钟后终止请求(但没有关闭 tcp 套接字)。

Here is the handshake:这是握手:

Client客户

GET / HTTP/1.1
Host: 127.0.0.1:4455
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:52.0) Gecko/20100101 Firefox/52.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: de,en-US;q=0.7,en;q=0.3
Accept-Encoding: gzip, deflate
Sec-WebSocket-Version: 13
Origin: http://localhost
Sec-WebSocket-Extensions: permessage-deflate
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Connection: keep-alive, Upgrade
Pragma: no-cache
Cache-Control: no-cache
Upgrade: websocket

Server服务器

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat

HTML JavaScript HTML JavaScript

<!DOCTYPE HTML>
<html>
    <head>
        <meta charset="utf-8">
        <title>Socket testing - Client</title>
    </head>
    <body>
        <div id="container"></div>
        <script type="text/javascript">

            var socket = new WebSocket('ws://127.0.0.1:4455');

            socket.addEventListener("error",function(e){
                console.log("an error ocurred: ",e)
            })
            socket.addEventListener("close",function(e){
                console.log("the connection was closed: ",e)
            })
            socket.addEventListener("open",function(e){
                console.log("the connection was opened: ",e)
            })
            socket.addEventListener("message",function(e){
                console.log("recieved a message: ",e)
            })

        </script>
    </body>
</html>

Java (excerpt) Java(摘录)

public class SocketHandlerWebSocketLevel extends SocketHandler {

    private HashMap<String, String> connectionHeaders;
    private InputStreamReader stringReader;
    private OutputStreamWriter stringWriter;

    public SocketHandlerWebSocketLevel(Socket socket) {
        super(socket);
        connectionHeaders = new HashMap<String, String>();

        try {
            stringReader = new InputStreamReader(s.getInputStream());
        } catch (IOException e) {
            e.printStackTrace();
            close();
            print("could not get the input stream");
            return;
        }

        try {
            stringWriter = new OutputStreamWriter(s.getOutputStream());
        } catch (IOException e) {
            e.printStackTrace();
            close();
            print("could not get the output stream");
            return;
        }
    }

    @Override
    public void run() {

        print("Started handler");
        char b;
        String buffer = "";
        try {
            mainLoop: while (true) {
                while (stringReader.ready() || buffer.length() == 0) {
                    if ((b = (char) stringReader.read()) != -1) {
                        buffer += b;
                    } else {
                        break mainLoop;
                    }

                }
                gotMessage(buffer);
                buffer = "";
            }
        } catch (IOException e) {
            close();
            print("connection was killed remotly, could not read the next byte");
            return;
        }

        close();
        print("connection was closed remotely, stopped Handler, closed socked");
    }

    private void gotMessage(String message) {
        if (connectionHeaders.size() == 0) {
            connectionHeaders = parseHttpHeader(message);
            handshakeResponse();
        } else {
            print(message);
        }
    }

    private void handshakeResponse() {
        /* 
           taken from: https://tools.ietf.org/html/rfc6455#page-7
           For this header field, the server has to take the value (as present
           in the header field, e.g., the base64-encoded [RFC4648] version minus
           any leading and trailing whitespace) and concatenate this with the
           Globally Unique Identifier (GUID, [RFC4122]) "258EAFA5-E914-47DA-
           95CA-C5AB0DC85B11" in string form, which is unlikely to be used by
           network endpoints that do not understand the WebSocket Protocol.  A
           SHA-1 hash (160 bits) [FIPS.180-3], base64-encoded (see Section 4 of
           [RFC4648]), of this concatenation is then returned in the server's
           handshake.

           Concretely, if as in the example above, the |Sec-WebSocket-Key|
           header field had the value "dGhlIHNhbXBsZSBub25jZQ==", the server
           would concatenate the string "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
           to form the string "dGhlIHNhbXBsZSBub25jZQ==258EAFA5-E914-47DA-95CA-
           C5AB0DC85B11".  The server would then take the SHA-1 hash of this,
           giving the value 0xb3 0x7a 0x4f 0x2c 0xc0 0x62 0x4f 0x16 0x90 0xf6
           0x46 0x06 0xcf 0x38 0x59 0x45 0xb2 0xbe 0xc4 0xea.  This value is
           then base64-encoded (see Section 4 of [RFC4648]), to give the value
           "s3pPLMBiTxaQ9kYGzzhZRbK+xOo=".  This value would then be echoed in
           the |Sec-WebSocket-Accept| header field.
        */

        String secWebSocketKey, secWebSocketAccept, GUID, template, merged, toSend;
        secWebSocketKey = connectionHeaders.get("Sec-WebSocket-Key");
        GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
        template = "HTTP/1.1 101 Switching Protocols\nUpgrade: websocket\nConnection: Upgrade\nSec-WebSocket-Accept: %s\nSec-WebSocket-Protocol: chat\n";

        // combine secWebSocketKey and the GUID
        merged = secWebSocketKey + GUID;
        print("merged: " + merged);

        // convert to byte[]
        byte[] asBytes = merged.getBytes();
        print("asBytes: " + Arrays.toString(asBytes));

        // SHA-1 hash
        byte[] sha1 = SHA1Hash(asBytes);
        print("sha1: " + Arrays.toString(sha1));

        // base64 encode
        byte[] base64 = base64Encode(sha1);
        print("base64: " + Arrays.toString(base64));

        // reconvert to string to put it into the template
        secWebSocketAccept = new String(base64);

        toSend = String.format(template, secWebSocketAccept);
        print(toSend);

        try {
            stringWriter.write(toSend, 0, toSend.length());
            stringWriter.flush();
        } catch (IOException e) {
            print("hanshake sending failed!");
        }

    }

    private HashMap<String, String> parseHttpHeader(String h) {
        HashMap<String, String> fields = new HashMap<String, String>();

        String[] rows = h.split("\n");
        if (rows.length > 1) {
            fields.put("Prototcol", rows[0]);
            Pattern pattern = Pattern.compile("^([^:]+): (.+)$");
            for (int i = 1; i < rows.length; i++) {
                Matcher matcher = pattern.matcher(rows[i]);
                while (matcher.find()) {
                    if (matcher.groupCount() == 2) {
                        fields.put(matcher.group(1), matcher.group(2));
                    }
                }
            }
        }
        return fields;
    }

    private byte[] SHA1Hash(byte[] bytes) {
        MessageDigest md;

        try {
            md = MessageDigest.getInstance("SHA-1");
        } catch (NoSuchAlgorithmException e) {
            return null;
        }

        md.update(bytes);
        return md.digest();
    }

    private byte[] base64Encode(byte[] bytes) {
        byte[] encodedBytes = Base64.getEncoder().encode(bytes);
        return encodedBytes;
    }

Where might be my error?我的错误可能在哪里? What could be missing, maybe a "message end" symbol?可能缺少什么,也许是“消息结束”符号?

Note that I don't want to use a framework.请注意,我不想使用框架。

The solution was simple.解决办法很简单。 I just used Wireshark to debug this whole thing: I just forgot the carriage return.我只是用 Wireshark 来调试这整个事情:我只是忘记了回车。

The correct string in the Java class would be: Java 类中的正确字符串是:

template = "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: %s\r\nSec-WebSocket-Protocol: chat\r\n\r\n";

Until this modification the browser is not able to interpret it as HTTP data in the TCP package.在此修改之前,浏览器无法将其解释为 TCP 包中的 HTTP 数据。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM