前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Nginx Websocket 配置

Nginx Websocket 配置

作者头像
Se7en258
发布2021-05-18 11:02:59
5.2K0
发布2021-05-18 11:02:59
举报
文章被收录于专栏:Se7en的架构笔记

Websocket 简介

WebSocket 是一种基于 TCP 连接的全双工通信的协议,其工作在应用层,建立连接的时候通过复用 Http 握手通道,完成 Http 协议的切换升级,即切换到 WebSocket 协议,协议切换成功后,将不再需要客户端发起请求,服务端就可以直接主动向客户端发送数据,实现双向通信。

和 Http 相比,WebSocket有以下优点:

  • WebSocket 是双向通信协议,可以双向发送或接受信息。HTTP是单向的,只能由客户端发起请求时,服务器才能响应,服务器不能主动向客户端发送数据。
  • WebSocket 可以和 HTTP Server 共享相同端口。
  • WebSocket 协议可以更好的支持二进制,可以直接传送二进制数据。
  • 同时WebSocket协议的头部非常小,服务器发到客户端的数据包的包头,只有2~10个字节(取决于数据包的长度),客户端发送服务端的包头稍微大一点,因为其要进行掩码加密,所以还要加上4个字节的掩码。总得来说,头部不超过14个字节。
  • 支持扩展,用户可以扩展协议实现自己的子协议。

Websocket 建立过程

客户端: 申请协议升级

首先由客户端发起协议升级请求, 根据WebSocket协议规范, 请求头必须包含如下的内容:

代码语言:javascript
复制
GET / HTTP/1.1
Host: localhost:8080
Origin: http://127.0.0.1:3000
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: w4v7O6xFTi36lq3RNcgctw==
  • 请求行: 请求方法必须是GET, HTTP版本至少是1.1。
  • 请求必须含有Host。
  • 如果请求来自浏览器客户端, 必须包含Origin。
  • 请求必须含有 Connection, 其值必须含有 "Upgrade" 记号。
  • 请求必须含有 Upgrade, 其值必须含有 "websocket" 关键字。
  • 请求必须含有 Sec-Websocket-Version, 其值必须是 13。
  • 请求必须含有 Sec-Websocket-Key, 用于提供基本的防护, 比如无意的连接。

服务器: 响应协议升级

服务器返回的响应头必须包含如下的内容:

代码语言:javascript
复制
HTTP/1.1 101 Switching Protocols
Connection:Upgrade
Upgrade: websocket
Sec-WebSocket-Accept: Oy4NRAQ13jhfONC7bP8dTKb4PTU=
  • 响应行: HTTP/1.1 101 Switching Protocols。
  • 响应必须含有 Upgrade, 其值为 "weboscket"。
  • 响应必须含有 Connection, 其值为 "Upgrade"。
  • 响应必须含有 Sec-Websocket-Accept, 根据请求首部的 Sec-Websocket-key计算出来。。

Sec-WebSocket-Key/Accept的计算

Sec-WebSocket-Key 值由一个随机生成的16字节的随机数通过 base64(见 RFC4648 的第四章)编码得到的。例如, 随机选择的16个字节为:

代码语言:javascript
复制
// 十六进制 数字1~16
0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0a 0x0b 0x0c 0x0d 0x0e 0x0f 0x10

测试代码如下:

代码语言:javascript
复制
const list = Array.from({ length: 16 }, (v, index) => ++index)
const key = Buffer.from(list)
console.log(key.toString('base64'))
// AQIDBAUGBwgJCgsMDQ4PEA==

而 Sec-WebSocket-Accept 值的计算方式为:

将 Sec-Websocket-Key 的值和 258EAFA5-E914-47DA-95CA-C5AB0DC85B11 拼接 通过 SHA1 计算出摘要, 并转成 base64 字符串。

此处不需要纠结神奇字符串 258EAFA5-E914-47DA-95CA-C5AB0DC85B11, 它就是一个 GUID, 没准儿是写 RFC 的时候随机生成的。

测试代码如下:

代码语言:javascript
复制
const crypto = require('crypto')

function hashWebSocketKey (key) {
  const GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'

  return crypto.createHash('sha1')
    .update(key + GUID)
    .digest('base64')
}

console.log(hashWebSocketKey('w4v7O6xFTi36lq3RNcgctw=='))
// Oy4NRAQ13jhfONC7bP8dTKb4PTU=

Sec-WebSocket-Key的作用

前面简单提到他的作用为: 提供基础的防护, 减少恶意连接, 进一步阐述如下:

  • Key 可以避免服务器收到非法的 WebSocket 连接, 比如 Http 请求连接到 Websocket, 此时服务端可以直接拒绝。
  • Key 可以用来初步确保服务器认识 ws 协议, 但也不能排除有的 Http服务器只处理 Sec-WebSocket-Key, 并不实现ws协议。
  • Key可以避免反向代理缓存。
  • 在浏览器中发起 ajax 请求, Sec-Websocket-Key 以及相关 header 是被禁止的, 这样可以避免客户端发送 ajax 请求时, 意外请求协议升级。
  • 最终需要强调的是: Sec-WebSocket-Key/Accept 并不是用来保证数据的安全性, 因为其计算/转换公式都是公开的, 而且非常简单, 最主要的作用是预防一些意外的情况。

后端服务

安装 node.js

代码语言:javascript
复制
wget https://nodejs.org/dist/v14.16.0/node-v14.16.0-linux-x64.tar.xz
tar -xJvf node-v14.16.0-linux-x64.tar.xz
ln -s /root/node-v14.16.0-linux-x64/bin/node /usr/local/bin/node
ln -s /root/node-v14.16.0-linux-x64/bin/npm /usr/local/bin/npm

安装依赖

代码语言:javascript
复制
npm install ws

后端 websocket 服务部署

本次实验后端服务 Http 和 Websocket 使用相同的 80 和 443 端口。在实际应用中有个好处,如果原先是提供的是 Http 服务,后来新增了 Websocket 服务,不需要暴露新的端口,也不需要修改防火墙规则。

把 WebSocketServer 和 Http 绑定到同一个端口的关键代码是先获取创建的 http.Server 的引用,再根据 http.Server 创建 WebSocketServer。

不管是 Http 还是 Websocket,客户端发送的都是标准的 Http 请求,都会先将请求交给 http.Server 处理。WebSocketServer 会首先判断请求是不是 Websocket 请求,如果是,它将处理该请求,如果不是,该请求仍由 http.Server 处理。

服务器代码:

代码语言:javascript
复制
// app.js 文件
// 导入相关模块
const WebSocket = require('ws');
const  http = require('http');

// 使用 http 模块创建的 http.Server
httpserver = http.createServer(function (request, response) {
    // 发送 HTTP 头部
    // HTTP 状态值: 200 : OK
    // 内容类型: text/plain
    response.writeHead(200, {'Content-Type': 'text/plain'});

    // 发送响应数据 "Hello World"
    response.end('Http Message: Hello World\n');
}).listen(80); // 监听 80 端口, 根据 http.Server 创建 WebSocketServer


//创建 WebSocketServer
const WebSocketServer = WebSocket.Server;
const wss = new WebSocketServer({
    server: httpserver //根据 http.Server 创建 WebSocketServer
});

wss.on('connection', function (ws) {
    ws.send("Websocket Send: Hello World")  //客户端连接成功后立即向客户端发送一条消息
    console.log(`WebSocket connection()`);
    ws.on('message', function (message) {  //收到客户端的消息
        console.log(`Websocket Received: ${message}`);
    })
});

console.log('WebSocket and Http Server started at port 80...');

启动后端服务

代码语言:javascript
复制
[root@ws1 ws-http-server]# node app.js 
WebSocket and Http Server started at port 80...

验证

分别使用客户端验证 Http 和 Websocket 服务,后端服务器的地址为 192.168.1.141:

  • 当客户端未发起协议升级请求时,使用 Http 服务响应客户端。
  • 当客户端发起协议升级请求时,Websocket 会复用 Http 的握手通道,升级完成后,后续数据交换使用 Websocket。
测试 Http 连接
代码语言:javascript
复制
# curl -i http://192.168.1.141                                                            
HTTP/1.1 200 OK
Content-Type: text/plain
Date: Thu, 25 Mar 2021 08:00:40 GMT
Connection: keep-alive
Keep-Alive: timeout=5
Transfer-Encoding: chunked

Http Message: Hello World
测试 Websocket 连接
代码语言:javascript
复制
# 方式一:使用 wscat(客户端 npm install wscat 安装)
# wscat --connect ws://192.168.1.141                                                          
Connected (press CTRL+C to quit)
< Websocket Send: Hello World #接收到服务器的消息
> send hello #向服务器发送消息

# 方式二:使用 curl
curl -i \
     --header "Upgrade: websocket" \
     --header "Sec-WebSocket-Key: AQIDBAUGBwgJCgsMDQ4PEA==" \
     --header "Sec-WebSocket-Version: 13" \
     --header "Connection: upgrade" \ #直接访问后端服务的 Websocket 需要带上该头部
  http://192.168.1.141 

抓包查看交互报文,可以看到 Websocket 复用了 HTTP 的握手通道, 客户端通过 HTTP 请求与 WebSocket 服务器协商升级协议, 协议升级完成后, 后续的数据交换则遵照 WebSocket协议。

在后端服务器上抓包:

代码语言:javascript
复制
tcpdump -i any host 192.168.1.141 and port 80 -nn -w ws.pcap

通过 Wireshark 软件打开查看:

Nginx 配置

生成自签名证书

https 证书我们都在 CA 站点申请,并由 CA 机构颁发,本次实验使用 openssl 生成自签名 https 证书。

代码语言:javascript
复制
[root@nginx-plus1 certs]# openssl req -newkey rsa:2048 -nodes -keyout server.key -x509 -days 365 -out server.crt   

# 以下信息自行添加,可以随意
Generating a 2048 bit RSA private key
....................+++
......+++
writing new private key to 'server.key'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [XX]:CN
State or Province Name (full name) []:Shanghai
Locality Name (eg, city) [Default City]:Shanghai
Organization Name (eg, company) [Default Company Ltd]:Ect
Organizational Unit Name (eg, section) []:Ect
Common Name (eg, your name or your server's hostname) []:chengzw
Email Address []:chengzw258@163.com

Nginx 配置文件

Nginx 监听 80 端口用于 Http 和 ws 服务,监听 443 端口用于 Https 和 wss 服务。wss 就是加密的 ws 服务。

代码语言:javascript
复制
events{}
http {
    upstream websocket {
        server 192.168.1.141:80;  #后端服务器地址
    }
 
    server {
      listen 443 ssl;
      # ssl 相关配置
      ssl_protocols TLSv1 TLSv1.1 TLSv1.2 SSLv3 SSlv2;
      ssl_certificate_key /usr/local/nginx/certs/server.key;
      ssl_certificate /usr/local/nginx/certs/server.crt;
      location / {
       proxy_pass http://websocket;
       # 添加 WebSocket 协议升级 Http 头部
       proxy_set_header Upgrade $http_upgrade;
       proxy_set_header Connection "upgrade";
      }
   }
    server {
        listen 80;
        location / {
            proxy_pass http://websocket;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
        }
    }
}

启动 Nginx 服务

代码语言:javascript
复制
/usr/local/nginx/sbin/nginx  #根据自己安装 nginx 的路径

验证

测试 Http & Https 连接
代码语言:javascript
复制
# Http 连接
# curl -i http://192.168.1.134                                                                        
HTTP/1.1 200 OK
Server: nginx/1.14.2
Date: Thu, 25 Mar 2021 08:16:59 GMT
Content-Type: text/plain
Transfer-Encoding: chunked
Connection: keep-alive

Http Message: Hello World

# Https 连接
# curl -i https://192.168.1.134 -k                                                                    
HTTP/1.1 200 OK
Server: nginx/1.14.2
Date: Thu, 25 Mar 2021 08:17:07 GMT
Content-Type: text/plain
Transfer-Encoding: chunked
Connection: keep-alive

Http Message: Hello World
测试 ws & wss 连接
代码语言:javascript
复制
# 方式一:使用wscat
# ws 连接
# wscat --connect ws://192.168.1.134                                                              
Connected (press CTRL+C to quit)
< Websocket Send: Hello World
> send hello

# wss 连接,由于是自签名证书需要 -n 参数,表示不检验证书                                                                                                                            # wscat --connect wss://192.168.1.134  -n                                                      
Connected (press CTRL+C to quit)
< Websocket Send: Hello World
> send hello

# 方式二:使用curl
# ws 连接
curl -i \                                                                                       
     --header "Upgrade: websocket" \
     --header "Sec-WebSocket-Key: MlRAR6bQZi07587UD4H8oA==" \
     --header "Sec-WebSocket-Version: 13" \
  http://192.168.1.134
HTTP/1.1 101 Switching Protocols
Server: nginx/1.14.2
Date: Thu, 25 Mar 2021 08:18:48 GMT
Connection: upgrade
Upgrade: websocket
Sec-WebSocket-Accept: iURIl3uIT+tsPMmZ0x1IVH7EL98=

# wss 连接,由于是自签名证书需要 -k 参数,表示不检验证书   
curl -i \                                                                                       
     --header "Upgrade: websocket" \
     --header "Sec-WebSocket-Key: MlRAR6bQZi07587UD4H8oA==" \
     --header "Sec-WebSocket-Version: 13" \
  https://192.168.1.134 -k
HTTP/1.1 101 Switching Protocols
Server: nginx/1.14.2
Date: Thu, 25 Mar 2021 08:20:20 GMT
Connection: upgrade
Upgrade: websocket
Sec-WebSocket-Accept: iURIl3uIT+tsPMmZ0x1IVH7EL98=

参考链接

  • https://juejin.cn/post/6844903850667671560
  • https://www.liaoxuefeng.com/wiki/1022910821149312/1103327377678688
  • https://www.nginx.com/blog/websocket-nginx/
  • https://segmentfault.com/a/1190000022075295
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-03-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Se7en的架构笔记 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Websocket 简介
  • Websocket 建立过程
    • 客户端: 申请协议升级
      • 服务器: 响应协议升级
      • Sec-WebSocket-Key/Accept的计算
      • Sec-WebSocket-Key的作用
      • 后端服务
        • 安装 node.js
          • 安装依赖
            • 后端 websocket 服务部署
              • 启动后端服务
                • 验证
                  • 测试 Http 连接
                  • 测试 Websocket 连接
              • Nginx 配置
                • 生成自签名证书
                  • Nginx 配置文件
                    • 启动 Nginx 服务
                      • 验证
                        • 测试 Http & Https 连接
                        • 测试 ws & wss 连接
                    • 参考链接
                    领券
                    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档