簡體   English   中英

JS WebSocket 和 Java 之間的握手

[英]Handshake between JS WebSocket and Java

我嘗試實現 WebSocket 協議並將 JavaScript WebSocket 與 Java WebSocket 服務器連接。

JavaScript 部分非常簡單,並且按預期工作。 我自己編寫了 Java 服務器並閱讀了rfc 6455 第 7 頁以獲得正確的握手響應。 所以服務器生成正確的響應並發送它。 我寫了一個 Java 客戶端虛擬機來確保它被發送。

但問題是 JavaScript / 瀏覽器似乎沒有收到握手響應並在幾秒鍾后終止請求(但沒有關閉 tcp 套接字)。

這是握手:

客戶

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

服務器

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

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(摘錄)

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

我的錯誤可能在哪里? 可能缺少什么,也許是“消息結束”符號?

請注意,我不想使用框架。

解決辦法很簡單。 我只是用 Wireshark 來調試這整個事情:我只是忘記了回車。

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";

在此修改之前,瀏覽器無法將其解釋為 TCP 包中的 HTTP 數據。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM