腾讯云CDN支持WebSocket

1. WebSocket简介

Websocket是用于服务端主动向客户端推送消息的技术。传统的HTTP/HTTPS只能由客户端向服务端发起请求,服务端对请求一一响应。在需要获取服务端状态变化的场景下,如:提交的后台任务是否执行成功,只能通过客户端轮询向服务端发起请求,不仅效率低,还浪费资源(HTTP1.0下每次轮询都需要经过TCP三次握手重新建立连接)。而WebSocket的出现较好的解决了这个问题,在TCP首次建立完连接之后,该连接不自动关闭,在有效期内客户端可以继续向服务端发送消息,服务端也能主动给客户端发送消息。


2. 腾讯云CDN对WebSocket的支持

腾讯云CDN依靠全球广泛部署的CDN节点,高效的网络存储优化方案和精准的调度策略,有效提升下载速度、降低响应时间,提供流畅的用户体验。腾讯云CDN节点自研服务器在提供静态资源访问的能力下,同时支持WebSocket访问,兼容动态资源的极速上云服务。本文将介绍如果验证腾讯云CDN节点支持WebSocket。


2.1 配置源站支持WebSocket

由于WebSocket属于动态资源,不适用于缓存服务,所有请求必定回源,所以首先需要源站支持WebSocket。以下以Python+Nginx为例介绍如何配置代理支持WebSocket。

  • 2.1.1 Websocket服务端

服务端示例代码:

#!/usr/bin/python
#coding=utf8
"""
# Created Time : 2020-05-02 11:07:02

# File Name: websocket-server.py
# Description:

"""
import socket
import base64
import hashlib
import struct
from threading import Thread
 
 
def parse_headers(data):
    headers = {}
    method = url = protocol = ''
    raw_headers = data.split('\r\n\r\n')[0].split('\r\n')
    for i in range(0, len(raw_headers)):
        if i == 0:
            elements = raw_headers[i].split(' ')
            if len(elements) == 3:
                method, url, protocol = elements
        else:
            k, v = raw_headers[i].split(':', 1)
            headers[k] = v.strip()
    print headers
    return method, url, protocol, headers

def calc_sign(data):
    message = data + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'   #不要问为什么是这个字符串,rfc6455中定义的!
    return base64.b64encode(hashlib.sha1(message).digest())
    
def encode(data):
    token = b"\x81"
    length = len(data)
    if length < 126:
        token += struct.pack("B", length)
    elif length <= 0xFFFF:
        token += struct.pack("!BH", 126, length)
    else:
        token += struct.pack("!BQ", 127, length)
 
    return token + data

def decode(raw_data):
    payload_len = ord(raw_data[1]) & 127
    if payload_len == 126:
        extend_payload_len = raw_data[2:4]
        mask = raw_data[4:8]
        decoded = raw_data[8:]
    elif payload_len == 127:
        extend_payload_len = raw_data[2:10]
        mask = raw_data[10:14]
        decoded = raw_data[14:]
    else:
        extend_payload_len = None
        mask = raw_data[2:6]
        decoded = raw_data[6:]
 
    data = bytearray()
    for i in range(len(decoded)):
        chunk = ord(decoded[i]) ^ ord(mask[i % 4])
        data.append(chunk)
    return str(data)

def handle_request(conn):
    data = conn.recv(1024)
    print data
    method, url, protocol, headers = parse_headers(data)
    response = "HTTP/1.1 101 Switching Protocols\r\n" \
                   "Upgrade:websocket\r\n" \
                   "Connection:Upgrade\r\n" \
                   "Sec-WebSocket-Accept:%s\r\n" \
                   "WebSocket-Location:ws://%s%s\r\n\r\n" % (calc_sign(headers['Sec-WebSocket-Key']), headers['Host'], url)
    conn.send(response)
 
    while True:
        try:
            raw_data = conn.recv(1024)
        except Exception as e:
            raw_data = None
        if not raw_data:
            break
        data = decode(raw_data)
        print('recv data', data)
        if not conn.send(encode(data)):
            break
 
 
def run():
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    sock.bind(('0.0.0.0', 8888))
    sock.listen(10)
    
    while True:
        conn, address = sock.accept()
        print('new client %s:%s' % address)
        Thread(target=handle_request, args=(conn,)).start()
 
    sock.close()
 
if __name__ == '__main__':
    run()

该服务通过socket响应客户端连接,打印出每个连接的头部,读取客户端发送的消息,并将其发回给客户端。注意websocket通信发送的不是原始消息,需要经过编码,如encode,decode函数。


  • 2.1.2 Websocket客户端

客户端同样需要编解码,也可以直接利用现有工具简化测试步骤:https://pypi.org/project/websocket_client/


  • 2.1.3 本地测试

分两次分别发送两次消息,123和456。

请求的抓包HTTP头部内容如下:

tcpdump抓包
HTTP请求+响应头部

可以看到服务端总共只收到了一个TCP建立连接请求,一组HTTP头部,两组消息共用一个连接。websocket实际上发送和响应的也是标准的http头部格式,只是额外带上了鉴权头部。


  • 2.1.4 配置Nginx代理WebSocket

一般我们会在最终的服务前面挂一个代理服务支持路由+负载均衡等,如Nginx等。Nginx需要启用额外配置支持Websocket,修改Nginx配置文件配置如下:

worker_processes  1;

events {
    worker_connections  1024;
}


http {
    include       mime.types;
    default_type  application/octet-stream;
    resolver 119.29.29.29;

    # websocket
    map $http_upgrade $connection_upgrade {
        default upgrade;
        '' close;
    }
    upstream websocket {
        server 127.0.0.1:8888;
    }

    sendfile        on;

    keepalive_timeout  65;

    server {
        listen       80;
        server_name  _;
        root /usr/local/nginx/nginx/http/;
        access_log  logs/all-http.log;

            # websocket
        location ~ ^/websocket {
            proxy_pass http://websocket;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For
            $proxy_add_x_forwarded_for;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection $connection_upgrade;
        }

    }
}

重启nginx服务:

nginx -s reload

客户端重新请求80端口即可。

wsdump.py ws://127.0.0.1:80/websocket


  • 2.1.5 测试CDN支持WebSocket

在腾讯云CDN控制台域名配置页面将域名源站设置为支持websocket的源站,待配置生效后,直接通过WebSocket协议访问。

CDN访问

可以看到腾讯云CDN确实无需特殊配置即可支持WebSocket访问并透传源站。有一点在实际使用过程中需要注意的是节点默认支持10s的保活时间,10s内如果没有消息传递,将默认关闭连接。

admin
admin管理员

上一篇:Apache Tomcat AJP协议漏洞分析(CVE-2020-1938)
下一篇:MySQL 8.0 New Feature:NOWAIT and SKIP LOCKED

留言评论

暂无留言