源碼分析之WebSocketHandshake

一、基礎知識

1、英語補習

Algotithm 算法;   verify  驗證;  split 裂解。

2、使用websocket交互流程

客戶端與服務端連接成功之前,使用的通信協議是 HTTP。連接成功後,使用的纔是 WebSocket。

3、規定   RFC6455 對客戶端握手的規定,原文錨點鏈接爲 看這裏

The opening handshake is intended to be compatible with HTTP-based server-side software and intermediaries, so that a single port can be used by both HTTP clients talking to that server and WebSocket clients talking to that server.  To this end, the WebSocket client's handshake is an HTTP Upgrade request:

GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13

In compliance with [RFC2616], header fields in the handshake may be sent by the client in any order, so the order in which different header fields are received is not significant.

二、源碼分析

1、 將ArrayList<String> 轉成 Map<String,String>

    private Map<String, String> getHeaders(ArrayList<String> headers) {
        Map<String, String> headerMap = new HashMap();

        for(int i = 1; i < headers.size(); ++i) {
            String headerPre = (String)headers.get(i);
            String[] header = headerPre.split(":");
            headerMap.put(header[0].toLowerCase(), header[1]);
        }

        return headerMap;
    }

該段代碼核心是使用String.split(":"); 將String[]中的字符串以: 爲分界線裂解成 key,value 格式並存放在HashMap<String,String>中。

2、驗證傳入的

sec-websocket-accept對應的值是不是我們希望的。
    private void verifyWebSocketKey(String key, String accept) throws NoSuchAlgorithmException, HandshakeFailedException {
        byte[] sha1Bytes = this.sha1(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
        String encodedSha1Bytes = Base64.encodeBytes(sha1Bytes).trim();
        if (!encodedSha1Bytes.equals(accept.trim())) {
            throw new HandshakeFailedException();
        }
    }

3、shal算法

 

    private byte[] sha1(String input) throws NoSuchAlgorithmException {
        MessageDigest mDigest = MessageDigest.getInstance("SHA1");
        byte[] result = mDigest.digest(input.getBytes());
        return result;
    }

4、發送握手請求

   private void sendHandshakeRequest(String key) throws IOException {
        try {
            String path = "/mqtt";
            URI srvUri = new URI(this.uri);
            if (srvUri.getRawPath() != null && !srvUri.getRawPath().isEmpty()) {
                path = srvUri.getRawPath();
                if (srvUri.getRawQuery() != null && !srvUri.getRawQuery().isEmpty()) {
                    path = path + "?" + srvUri.getRawQuery();
                }
            }

            PrintWriter pw = new PrintWriter(this.output);
            pw.print("GET " + path + " HTTP/1.1" + "\r\n");
            if (this.port != 80 && this.port != 443) {
                pw.print("Host: " + this.host + ":" + this.port + "\r\n");
            } else {
                pw.print("Host: " + this.host + "\r\n");
            }

            pw.print("Upgrade: websocket\r\n");
            pw.print("Connection: Upgrade\r\n");
            pw.print("Sec-WebSocket-Key: " + key + "\r\n");
            pw.print("Sec-WebSocket-Protocol: mqtt\r\n");
            pw.print("Sec-WebSocket-Version: 13\r\n");
            if (this.customWebSocketHeaders != null) {
                Set keys = this.customWebSocketHeaders.keySet();
                Iterator i = keys.iterator();

                while(i.hasNext()) {
                    String k = (String)i.next();
                    String value = this.customWebSocketHeaders.getProperty(k);
                    pw.print(k + ": " + value + "\r\n");
                }
            }

            String userInfo = srvUri.getUserInfo();
            if (userInfo != null) {
                pw.print("Authorization: Basic " + Base64.encode(userInfo) + "\r\n");
            }

            pw.print("\r\n");
            pw.flush();
        } catch (URISyntaxException var9) {
            throw new IllegalStateException(var9.getMessage());
        }
    }

5、接受到握手請求的迴應

private void receiveHandshakeResponse(String key) throws IOException {
        BufferedReader in = new BufferedReader(new InputStreamReader(this.input));
        ArrayList<String> responseLines = new ArrayList();
        String line = in.readLine();
        if (line == null) {
            throw new IOException("WebSocket Response header: Invalid response from Server, It may not support WebSockets.");
        } else {
            while(!line.equals("")) {
                responseLines.add(line);
                line = in.readLine();
            }

            Map<String, String> headerMap = this.getHeaders(responseLines);
            String connectionHeader = (String)headerMap.get("connection");
            if (connectionHeader != null && !connectionHeader.equalsIgnoreCase("upgrade")) {
                String upgradeHeader = (String)headerMap.get("upgrade");
                if (upgradeHeader != null && upgradeHeader.toLowerCase().contains("websocket")) {
                    String secWebsocketProtocolHeader = (String)headerMap.get("sec-websocket-protocol");
                    if (secWebsocketProtocolHeader == null) {
                        throw new IOException("WebSocket Response header: empty sec-websocket-protocol");
                    } else if (!headerMap.containsKey("sec-websocket-accept")) {
                        throw new IOException("WebSocket Response header: Missing Sec-WebSocket-Accept");
                    } else {
                        try {
                            this.verifyWebSocketKey(key, (String)headerMap.get("sec-websocket-accept"));
                        } catch (NoSuchAlgorithmException var10) {
                            throw new IOException(var10.getMessage());
                        } catch (HandshakeFailedException var11) {
                            throw new IOException("WebSocket Response header: Incorrect Sec-WebSocket-Key");
                        }
                    }
                } else {
                    throw new IOException("WebSocket Response header: Incorrect upgrade.");
                }
            } else {
                throw new IOException("WebSocket Response header: Incorrect connection header");
            }
        }
    }

6、核心代碼

    public void execute() throws IOException {
        byte[] key = new byte[16];
        System.arraycopy(UUID.randomUUID().toString().getBytes(), 0, key, 0, 16);
        String b64Key = Base64.encodeBytes(key);
        this.sendHandshakeRequest(b64Key);
        this.receiveHandshakeResponse(b64Key);
    }

 7、構造函數

    public WebSocketHandshake(InputStream input, OutputStream output, String uri, String host, int port, Properties customWebSocketHeaders) {
        this.input = input;
        this.output = output;
        this.uri = uri;
        this.host = host;
        this.port = port;
        this.customWebSocketHeaders = customWebSocketHeaders;
    }

 8、域或靜態變量

    private static final String ACCEPT_SALT = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
    private static final String SHA1_PROTOCOL = "SHA1";
    private static final String HTTP_HEADER_SEC_WEBSOCKET_ACCEPT = "sec-websocket-accept";
    private static final String HTTP_HEADER_UPGRADE = "upgrade";
    private static final String HTTP_HEADER_UPGRADE_WEBSOCKET = "websocket";
    private static final String EMPTY = "";
    private static final String LINE_SEPARATOR = "\r\n";
    private static final String HTTP_HEADER_CONNECTION = "connection";
    private static final String HTTP_HEADER_CONNECTION_VALUE = "upgrade";
    private static final String HTTP_HEADER_SEC_WEBSOCKET_PROTOCOL = "sec-websocket-protocol";
    // 先於類內部和包內使用
    InputStream input;
    OutputStream output;
    String uri;
    String host;
    int port;
    Properties customWebSocketHeaders;

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章