源码分析之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;
发布了85 篇原创文章 · 获赞 21 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/weixin_41670928/article/details/103632456