Nginx 配置WSS 解析與實(shí)戰(zhàn)
1. 幾個(gè)關(guān)鍵概念
1.1 WebSocket
WebSocket 協(xié)議是 html5 的一種通信協(xié)議,該協(xié)議兼容我們常用的瀏覽器。例如:Chrome、Firefox、IE 等。它可以使客戶(hù)端和服務(wù)端雙向數(shù)據(jù)傳輸更加簡(jiǎn)單快捷,并且在TCP連接進(jìn)行一次握手后,就可以持久性連接,同時(shí)允許服務(wù)端對(duì)客戶(hù)端推送數(shù)據(jù)。外加傳統(tǒng)模式的協(xié)議一般HTTP輕輕可能會(huì)包含較長(zhǎng)的頭部,但真正有效的可能只有小部分,從而占用了很多資源和寬帶。因此WebSocket協(xié)議不僅可以實(shí)時(shí)通訊,支持?jǐn)U展;也可以壓縮節(jié)省服務(wù)資源和寬帶。 關(guān)于WebSocket的深入理解,可以看這篇文章:Html5 WebSocket 。
WS 協(xié)議 和 WSS 協(xié)議兩個(gè)均是 WebSocket 協(xié)議的 SCHEM,兩者一個(gè)是非安全的,一個(gè)是安全的。也是統(tǒng)一的資源標(biāo)志符。就好比 HTTP協(xié)議 和 HTTPS協(xié)議的差別。非安全的沒(méi)有證書(shū),安全的需要 SSL 證書(shū)。其中 WSS 表示在 TLS 之上的 WebSocket。
WS 一般默認(rèn)是 80 端口,而 WSS 默認(rèn)是 443 端口,大多數(shù)網(wǎng)站用的就是 80 和 443 端口。

1.2 SSL
SSL(Secure Socket Layer,安全套接層) 簡(jiǎn)單來(lái)說(shuō)是一種加密技術(shù), 通過(guò)它, 我們可以在通信的雙方上建立一個(gè)安全的通信鏈路, 因此數(shù)據(jù)交互的雙方可以安全地通信, 而不需要擔(dān)心數(shù)據(jù)被竊取. 關(guān)于 SSL 的深入知識(shí), 可以看這篇文章: SSL/TLS協(xié)議運(yùn)行機(jī)制的概述。
1.3 WSS
WSS 是 Web Socket Secure 的簡(jiǎn)稱(chēng), 它是 WebSocket 的加密版本. 我們知道 WebSocket 中的數(shù)據(jù)是不加密的, 但是不加密的數(shù)據(jù)很容易被別有用心的人竊取, 因此為了保護(hù)數(shù)據(jù)安全, 人們將 WebSocket 與 SSL 結(jié)合, 實(shí)現(xiàn)了安全的 WebSocket 通信, 即 WebSocket Secure。所以說(shuō) WSS 是使用 SSL 進(jìn)行加密了的 WebSocket 通信技術(shù)。
1.3 HTTPS
其實(shí) HTTPS 和 WSS 類(lèi)似, HTTP 之于 HTTPS 就像 WebSocket 之于 WebSocket Secure。
HTTP 協(xié)議本身也是明文傳輸, 因此為了數(shù)據(jù)的安全性, 人們利用 SSL 作為加密通道, 在 SSL 之上傳遞 HTTP 數(shù)據(jù), 因此 SSL 加密通道上運(yùn)行的 HTTP 協(xié)議就被稱(chēng)為 HTTPS 了。
1.4 總結(jié)
SSL 是基礎(chǔ), 在 SSL 上運(yùn)行 WebSocket 協(xié)議就是 WSS; 在 SSL 上運(yùn)行 HTTP 協(xié)議就是 HTTPS。
2. SSL單向/雙向認(rèn)證
2.1 單向認(rèn)證 SSL 協(xié)議的具體過(guò)程
- 客戶(hù)端的瀏覽器向服務(wù)器傳送客戶(hù)端 SSL 協(xié)議的版本號(hào),加密算法的種類(lèi),產(chǎn)生的隨機(jī)數(shù),以及其他服務(wù)器和客戶(hù)端之間通訊所需要的各種信息。
- 服務(wù)器向客戶(hù)端傳送 SSL 協(xié)議的版本號(hào),加密算法的種類(lèi),隨機(jī)數(shù)以及其他相關(guān)信息,同時(shí)服務(wù)器還將向客戶(hù)端傳送自己的證書(shū)。
- 客戶(hù)利用服務(wù)器傳過(guò)來(lái)的信息驗(yàn)證服務(wù)器的合法性,服務(wù)器的合法性包括:證書(shū)是否過(guò)期,發(fā)行服務(wù)器證書(shū)的 CA 是否可靠,發(fā)行者證書(shū)的公鑰能否正確解開(kāi)服務(wù)器證書(shū)的“發(fā)行者的數(shù)字簽名”,服務(wù)器證書(shū)上的域名是否和服務(wù)器的實(shí)際域名相匹配。如果合法性驗(yàn)證沒(méi)有通過(guò), 通訊將斷開(kāi);如果合法性驗(yàn)證通過(guò),將繼續(xù)進(jìn)行第四步。
- 用戶(hù)端隨機(jī)產(chǎn)生一個(gè)用于后面通訊的“對(duì)稱(chēng)密碼”,然后用服務(wù)器的公鑰(服務(wù)器的公鑰從步驟②中的服務(wù)器的證書(shū)中獲得)對(duì)其加密,然后將加密后的“預(yù)主密碼”傳給服務(wù)器。
- 如果服務(wù)器要求客戶(hù)的身份認(rèn)證(在握手過(guò)程中為可選),用戶(hù)可以建立一個(gè)隨機(jī)數(shù)然后對(duì)其進(jìn)行數(shù)據(jù)簽名,將這個(gè)含有簽名的隨機(jī)數(shù)和客戶(hù)自己的證書(shū)以及加密過(guò)的“預(yù)主密碼”一起傳給服務(wù)器。
- 如果服務(wù)器要求客戶(hù)的身份認(rèn)證,服務(wù)器必須檢驗(yàn)客戶(hù)證書(shū)和簽名隨機(jī)數(shù)的合法性,具體的合法性驗(yàn)證過(guò)程包括:客戶(hù)的證書(shū)使用日期是否有效,為客戶(hù)提供證書(shū)的 CA 是否可靠,發(fā)行CA 的公鑰能否正確解開(kāi)客戶(hù)證書(shū)的發(fā)行 CA 的數(shù)字簽名,檢查客戶(hù)的證書(shū)是否在證書(shū)廢止列表(CRL)中。檢驗(yàn)如果沒(méi)有通過(guò),通訊立刻中斷;如果驗(yàn)證通過(guò),服務(wù)器將用自己的私鑰解開(kāi)加密的“預(yù)主密碼 ”,然后執(zhí)行一系列步驟來(lái)產(chǎn)生主通訊密碼(客戶(hù)端也將通過(guò)同樣的方法產(chǎn)生相同的主通訊密碼)。
- 服務(wù)器和客戶(hù)端用相同的主密碼即“通話(huà)密碼”,一個(gè)對(duì)稱(chēng)密鑰用于 SSL 協(xié)議的安全數(shù)據(jù)通訊的加解密通訊。同時(shí)在 SSL 通訊過(guò)程中還要完成數(shù)據(jù)通訊的完整性,防止數(shù)據(jù)通訊中的任何變化。
- 客戶(hù)端向服務(wù)器端發(fā)出信息,指明后面的數(shù)據(jù)通訊將使用的步驟⑦中的主密碼為對(duì)稱(chēng)密鑰,同時(shí)通知服務(wù)器客戶(hù)端的握手過(guò)程結(jié)束。
- 服務(wù)器向客戶(hù)端發(fā)出信息,指明后面的數(shù)據(jù)通訊將使用的步驟⑦中的主密碼為對(duì)稱(chēng)密鑰,同時(shí)通知客戶(hù)端服務(wù)器端的握手過(guò)程結(jié)束。
- SSL 的握手部分結(jié)束,SSL 安全通道的數(shù)據(jù)通訊開(kāi)始,客戶(hù)和服務(wù)器開(kāi)始使用相同的對(duì)稱(chēng)密鑰進(jìn)行數(shù)據(jù)通訊,同時(shí)進(jìn)行通訊完整性的檢驗(yàn)。
2.2 雙向認(rèn)證 SSL 協(xié)議的具體過(guò)程
- 瀏覽器發(fā)送一個(gè)連接請(qǐng)求給安全服務(wù)器。
- 服務(wù)器將自己的證書(shū),以及同證書(shū)相關(guān)的信息發(fā)送給客戶(hù)瀏覽器。
- 客戶(hù)瀏覽器檢查服務(wù)器送過(guò)來(lái)的證書(shū)是否是由自己信賴(lài)的 CA 中心所簽發(fā)的。如果是,就繼續(xù)執(zhí)行協(xié)議;如果不是,客戶(hù)瀏覽器就給客戶(hù)一個(gè)警告消息:警告客戶(hù)這個(gè)證書(shū)不是可以信賴(lài)的,詢(xún)問(wèn)客戶(hù)是否需要繼續(xù)。
- 接著客戶(hù)瀏覽器比較證書(shū)里的消息,例如域名和公鑰,與服務(wù)器剛剛發(fā)送的相關(guān)消息是否一致,如果是一致的,客戶(hù)瀏覽器認(rèn)可這個(gè)服務(wù)器的合法身份。
- 服務(wù)器要求客戶(hù)發(fā)送客戶(hù)自己的證書(shū)。收到后,服務(wù)器驗(yàn)證客戶(hù)的證書(shū),如果沒(méi)有通過(guò)驗(yàn)證,拒絕連接;如果通過(guò)驗(yàn)證,服務(wù)器獲得用戶(hù)的公鑰。
- 客戶(hù)瀏覽器告訴服務(wù)器自己所能夠支持的通訊對(duì)稱(chēng)密碼方案。
- 服務(wù)器從客戶(hù)發(fā)送過(guò)來(lái)的密碼方案中,選擇一種加密程度最高的密碼方案,用客戶(hù)的公鑰加過(guò)密后通知瀏覽器。
- 瀏覽器針對(duì)這個(gè)密碼方案,選擇一個(gè)通話(huà)密鑰,接著用服務(wù)器的公鑰加過(guò)密后發(fā)送給服務(wù)器。
- 服務(wù)器接收到瀏覽器送過(guò)來(lái)的消息,用自己的私鑰解密,獲得通話(huà)密鑰。
- 服務(wù)器、瀏覽器接下來(lái)的通訊都是用對(duì)稱(chēng)密碼方案,對(duì)稱(chēng)密鑰是加過(guò)密的。
上面所述的是雙向認(rèn)證 SSL 協(xié)議的具體通訊過(guò)程,這種情況要求服務(wù)器和用戶(hù)雙方都有證書(shū)。
單向認(rèn)證 SSL 協(xié)議不需要客戶(hù)擁有 CA 證書(shū),具體的過(guò)程相對(duì)于上面的步驟,只需將服務(wù)器端驗(yàn)證客戶(hù)證書(shū)的過(guò)程去掉,以及在協(xié)商對(duì)稱(chēng)密碼方案,對(duì)稱(chēng)通話(huà)密鑰時(shí),服務(wù)器發(fā)送給客戶(hù)的是沒(méi)有加過(guò)密的 (這并不影響 SSL 過(guò)程的安全性)密碼方案。這樣,雙方具體的通訊內(nèi)容,就是加過(guò)密的數(shù)據(jù),如果有第三方攻擊,獲得的只是加密的數(shù)據(jù),第三方要獲得有用的信息,就需要對(duì)加密 的數(shù)據(jù)進(jìn)行解密,這時(shí)候的安全就依賴(lài)于密碼方案的安全。而幸運(yùn)的是,目前所用的密碼方案,只要通訊密鑰長(zhǎng)度足夠的長(zhǎng),就足夠的安全。這也是我們強(qiáng)調(diào)要求使 用 128 位加密通訊的原因。
3. 證書(shū)格式說(shuō)明
在使用openssl自己生成證書(shū)的時(shí)候,會(huì)發(fā)現(xiàn)網(wǎng)上很多例子生成的證書(shū)格式都不同,同一篇文章里也會(huì)有很多種格式.
所以就需要了解下 不同格式有什么區(qū)別和聯(lián)系
參考博客http://blog.csdn.net/justinjing0612/article/details/7770301
參考博客http://www.cnblogs.com/lzjsky/archive/2010/11/14/1877143.html
- der,cer文件一般是二進(jìn)制格式的,只放證書(shū),不含私鑰
- crt文件可能是二進(jìn)制的,也可能是文本格式的,應(yīng)該以文本格式居多,功能同der/cer
- pem文件一般是文本格式的,可以放證書(shū)或者私鑰,或者兩者都有
- pem如果只含私鑰的話(huà),一般用.key擴(kuò)展名,而且可以有密碼保護(hù)
- pfx,p12文件是二進(jìn)制格式,同時(shí)含私鑰和證書(shū),通常有保護(hù)密碼
4. 使用openssl生成證書(shū)
因?yàn)槲业南到y(tǒng)是Mac,這里記錄的是Mac申請(qǐng)自簽名的SSL證書(shū),其他系統(tǒng)也類(lèi)似,這里不再贅述。
- 桌面創(chuàng)建"SSL"文件夾,用來(lái)存放申請(qǐng)證書(shū)過(guò)程中的所有文件。
- 打開(kāi)"終端"并進(jìn)入到"桌面->SSL"。
- 管理員權(quán)限(命令:sudo su)。
這個(gè)命令超級(jí)重要!重要!重要!沒(méi)有在管理員權(quán)限下執(zhí)行下面的操作,即使你可以完成前面幾步,也會(huì)在最后一步卡住報(bào)錯(cuò)!
sudo su
- 創(chuàng)建rootCA.key(命令:openssl genrsa -des3 -out rootCA.key 2048)。
創(chuàng)建時(shí)需要輸入一個(gè)密碼,這個(gè)密碼要記住,下一步有用。
openssl genrsa -des3 -out rootCA.key 2048

- 使用rootCA.key創(chuàng)建rootCA.pem(命令:openssl req -x509 -new -nodes -key rootCA.key -sha256 -days 3650 -out rootCA.pem)
創(chuàng)建時(shí)需要輸入rootCA.key的密碼。
其他信息可以隨意填寫(xiě)。
openssl req -x509 -new -nodes -key rootCA.key -sha256 -days 3650 -out rootCA.pem

- 雙擊rootCA.pem證書(shū)。在鑰匙串中雙擊,并修改為"始終信任"該證書(shū)

- 創(chuàng)建v3.ext文件(命令:touch v3.ext)
touch v3.ext
8、編輯v3.ext文件(命令:sudo vim v3.ext)
sudo vim v3.ext
將下列文字復(fù)制粘貼進(jìn)去
注意最后一行,(DNS.1 = xxxxxxx)
這一行可以輸入域名或IP。
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage=digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName=@alt_names
[alt_names]
DNS.1 = 192.168.0.3

- 創(chuàng)建server.csr和server.key(命令:openssl req -new -sha256 -nodes -out server.csr -newkey rsa:2048 -keyout server.key)
這里要注意倒數(shù)第二項(xiàng),必須填自己的IP地址或域名,如果IP為動(dòng)態(tài)獲取的,建議先改成固定IP。
openssl req -new -sha256 -nodes -out server.csr -newkey rsa:2048 -keyout server.key

- 創(chuàng)建server.crt(命令:openssl x509 -req -in server.csr -CA [rootCA.pem路徑] -CAkey [rootCA.key路徑] -CAcreateserial -out server.crt -days 500 -sha256 -extfile v3.ext)
去掉中括號(hào),把對(duì)應(yīng)文件路徑寫(xiě)進(jìn)去。
如果你報(bào)了“Error opening CA Certificate”這個(gè)錯(cuò)誤,請(qǐng)先檢查路徑是否正確,如果正確請(qǐng)檢查是否執(zhí)行了第三步,我就卡在這卡了一晚上~
如果一切正常會(huì)讓你輸入密碼,輸入完成后就會(huì)生成server.crt??!
openssl x509 -req -in server.csr -CA [rootCA.pem路徑] -CAkey [rootCA.key路徑] -CAcreateserial -out server.crt -days 500 -sha256 -extfile v3.ext

- 最后查看SSL文件夾

5. Nginx配置 WSS
修改 nginx.conf配置文件
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
upstream websocket {
server 192.168.0.3:8888;
}
server {
listen 8888;
server_name test.com;
ssl on;
ssl_certificate /usr/local/etc/nginx/ZC_chain.crt;
ssl_certificate_key /usr/local/etc/nginx/ZC_key.key;
#ssl_client_certificate /usr/local/etc/nginx/ZC_chain.crt;
ssl_session_timeout 20m;
ssl_verify_client off;
location / {
proxy_pass http://websocket;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
}
}
128.190.0.3:8888是真正的服務(wù)端地址,nginx所在域名是test.com,代理的端口號(hào)是8888,所以前端訪(fǎng)問(wèn)的時(shí)候這樣配置:
WEBSOCKET_URL: 'wss://test.com:8888',
檢查nginx.conf正確性:
nginx -t
重新加載配置文件:
nginx -s reload
6. WebSocketServer(基于SpringBoot)
基于SpringBoot整合WebSocket實(shí)現(xiàn)前后端互推消息,該代碼可以用于ws協(xié)議以及wss協(xié)議進(jìn)行測(cè)試。
- 首先創(chuàng)建一個(gè)springboot項(xiàng)目,網(wǎng)上教程很多,很簡(jiǎn)單,最終的目錄結(jié)構(gòu)如下:

- 項(xiàng)目的pom.xml如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>webSocket</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>webSocket</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.10</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
- application.properties中配置端口號(hào)。
server.port=8888
- 配置類(lèi)
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
@Configuration
public class WebSocketConfig {
/**
* ServerEndpointExporter 作用
*
* 這個(gè)Bean會(huì)自動(dòng)注冊(cè)使用@ServerEndpoint注解聲明的websocket endpoint
*
* @return
*/
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
- 核心類(lèi)
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
@ServerEndpoint("/webSocket/{sid}")
@Component
public class WebSocketServer {
//靜態(tài)變量,用來(lái)記錄當(dāng)前在線(xiàn)連接數(shù)。應(yīng)該把它設(shè)計(jì)成線(xiàn)程安全的。
private static AtomicInteger onlineNum = new AtomicInteger();
//concurrent包的線(xiàn)程安全Set,用來(lái)存放每個(gè)客戶(hù)端對(duì)應(yīng)的WebSocketServer對(duì)象。
private static ConcurrentHashMap<String, Session> sessionPools = new ConcurrentHashMap<>();
//發(fā)送消息
public void sendMessage(Session session, String message) throws IOException {
if(session != null){
synchronized (session) {
// System.out.println("發(fā)送數(shù)據(jù):" + message);
session.getBasicRemote().sendText(message);
}
}
}
//給指定用戶(hù)發(fā)送信息
public void sendInfo(String userName, String message){
Session session = sessionPools.get(userName);
try {
sendMessage(session, message);
}catch (Exception e){
e.printStackTrace();
}
}
//建立連接成功調(diào)用
@OnOpen
public void onOpen(Session session, @PathParam(value = "sid") String userName){
sessionPools.put(userName, session);
addOnlineCount();
System.out.println(userName + "加入webSocket!當(dāng)前人數(shù)為" + onlineNum);
try {
sendMessage(session, "歡迎" + userName + "加入連接!");
} catch (IOException e) {
e.printStackTrace();
}
}
//關(guān)閉連接時(shí)調(diào)用
@OnClose
public void onClose(@PathParam(value = "sid") String userName){
sessionPools.remove(userName);
subOnlineCount();
System.out.println(userName + "斷開(kāi)webSocket連接!當(dāng)前人數(shù)為" + onlineNum);
}
//收到客戶(hù)端信息
@OnMessage
public void onMessage(String message) throws IOException{
message = "客戶(hù)端:" + message + ",已收到";
System.out.println(message);
for (Session session: sessionPools.values()) {
try {
sendMessage(session, message);
} catch(Exception e){
e.printStackTrace();
continue;
}
}
}
//錯(cuò)誤時(shí)調(diào)用
@OnError
public void onError(Session session, Throwable throwable){
System.out.println("發(fā)生錯(cuò)誤");
throwable.printStackTrace();
}
public static void addOnlineCount(){
onlineNum.incrementAndGet();
}
public static void subOnlineCount() {
onlineNum.decrementAndGet();
}
}
- 在Controller中跳轉(zhuǎn)頁(yè)面
package com.example.controller;
import com.example.service.WebSocketServer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
@Controller
public class SocketController {
@Autowired
private WebSocketServer webSocketServer;
@RequestMapping("/index")
public String index() {
return "index";
}
@GetMapping("/webSocket")
public ModelAndView socket() {
ModelAndView mav = new ModelAndView("/webSocket");
//mav.addObject("userId", userId);
return mav;
}
}
- 前端代碼在webSocket.html中:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>WebSocket</title>
</head>
<body>
<h3>hello socket</h3>
<p>【userId】:<div><input id="userId" name="userId" type="text" value="10"></div>
<p>【toUserId】:<div><input id="toUserId" name="toUserId" type="text" value="20"></div>
<p>【toUserId】:<div><input id="contentText" name="contentText" type="text" value="hello websocket"></div>
<p>操作:<div><a onclick="openSocket()">開(kāi)啟socket</a></div>
<p>【操作】:<div><a onclick="sendMessage()">發(fā)送消息</a></div>
</body>
<script>
var socket;
function openSocket() {
if(typeof(WebSocket) == "undefined") {
console.log("您的瀏覽器不支持WebSocket");
}else{
console.log("您的瀏覽器支持WebSocket");
//實(shí)現(xiàn)化WebSocket對(duì)象,指定要連接的服務(wù)器地址與端口 建立連接
var userId = document.getElementById('userId').value;
var socketUrl="wss://192.168.0.3:8888/webSocket/"+userId;
console.log(socketUrl);
if(socket!=null){
socket.close();
socket=null;
}
socket = new WebSocket(socketUrl);
//打開(kāi)事件
socket.onopen = function() {
console.log("websocket已打開(kāi)");
//socket.send("這是來(lái)自客戶(hù)端的消息" + location.href + new Date());
};
//獲得消息事件
socket.onmessage = function(msg) {
var serverMsg = "收到服務(wù)端信息:" + msg.data;
console.log(serverMsg);
//發(fā)現(xiàn)消息進(jìn)入 開(kāi)始處理前端觸發(fā)邏輯
};
//關(guān)閉事件
socket.onclose = function() {
console.log("websocket已關(guān)閉");
};
//發(fā)生了錯(cuò)誤事件
socket.onerror = function() {
console.log("websocket發(fā)生了錯(cuò)誤");
}
}
}
function sendMessage() {
if(typeof(WebSocket) == "undefined") {
console.log("您的瀏覽器不支持WebSocket");
}else {
// console.log("您的瀏覽器支持WebSocket");
var toUserId = document.getElementById('toUserId').value;
var contentText = document.getElementById('contentText').value;
var msg = '{"toUserId":"'+toUserId+'","contentText":"'+contentText+'"}';
console.log(msg);
socket.send(msg);
}
}
</script>
</html>
7. WSS + WebSocketClient
- maven依賴(lài)
備注:maven倉(cāng)庫(kù)官方(https://mvnrepository.com/)
<!-- WebSocket start-->
<dependency>
<groupId>org.java-websocket</groupId>
<artifactId>Java-WebSocket</artifactId>
<version>1.3.0</version>
</dependency>
<!-- WebSocket end-->
這個(gè)ws客戶(hù)端對(duì)wss支持不好,源碼中的wss client 還要使用證書(shū)。可以修改為不驗(yàn)證證書(shū),具體看工具類(lèi)代碼。
- 工具類(lèi)
package com.example.wss;
import java.net.URI;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Map;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import org.java_websocket.client.DefaultSSLWebSocketClientFactory;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.drafts.Draft;
/**
* <p>
* WSS工具類(lèi)
* </p>
*
* @author: hejianhui
* @create: 2020-08-23 00:27
* @see MyWssUtil
* @since JDK1.8
*/
abstract class MyWssUtil extends WebSocketClient {
public MyWssUtil(URI serverURI) {
super(serverURI);
if (serverURI.toString().contains("wss://"))
trustAllHosts(this);
}
public MyWssUtil(URI serverURI, Draft draft) {
super(serverURI, draft);
if (serverURI.toString().contains("wss://"))
trustAllHosts(this);
}
public MyWssUtil(URI serverURI, Draft draft, Map<String, String> headers, int connecttimeout) {
super(serverURI, draft, headers, connecttimeout);
if (serverURI.toString().contains("wss://"))
trustAllHosts(this);
}
final static HostnameVerifier DO_NOT_VERIFY = new HostnameVerifier() {
public boolean verify(String hostname, SSLSession session) {
return true;
}
};
static void trustAllHosts(MyWssUtil appClient) {
System.out.println("start...");
TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return new java.security.cert.X509Certificate[]{};
}
@Override
public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
// TODO Auto-generated method stub
}
@Override
public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
// TODO Auto-generated method stub
}
}};
try {
SSLContext sc = SSLContext.getInstance("TLS");
sc.init(null, trustAllCerts, new java.security.SecureRandom());
appClient.setWebSocketFactory(new DefaultSSLWebSocketClientFactory(sc));
} catch (Exception e) {
e.printStackTrace();
}
}
}
- 使用工具進(jìn)行wss協(xié)議的接口請(qǐng)求
package com.example.wss;
import org.java_websocket.WebSocket;
import org.java_websocket.drafts.Draft;
import org.java_websocket.drafts.Draft_17;
import org.java_websocket.handshake.ServerHandshake;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* <p>
* 進(jìn)行wss協(xié)議的接口請(qǐng)求測(cè)試
* </p>
*
* @author: hejianhui
* @create: 2020-08-23 00:30
* @see WssTest
* @since JDK1.8
*/
public class WssTest {
public static List<String> result = new ArrayList<>();
public static String initmsg = "{'lat':'118.817891','lng':'31.931724','speed':'0','distance':'0'}";
public static void main(String[] args) throws URISyntaxException {
Map<String, String> headers = new HashMap<>();
// 根據(jù)服務(wù)端具體進(jìn)行配置
// headers.put("Sec-WebSocket-Extensions", "permessage-deflate; client_max_window_bits");
// headers.put("Sec-WebSocket-Key", "");
// headers.put("Sec-WebSocket-Protocol", "x-access-token, ");
// headers.put("Sec-WebSocket-Version", "13");
// 以下請(qǐng)求頭可以不用傳
// headers.put("Connection", "Upgrade");
// headers.put("Upgrade", "websocket");
// headers.put("Accept-Encoding", "gzip, deflate, br");
// headers.put("Accept-Language", "zh-CN,zh;q=0.9");
// headers.put("Cache-Control", "no-cache");
// headers.put("Host", "test.com");
// headers.put("Origin", "https://test.com");
// headers.put("Pragma", "no-cache");
// headers.put("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.117 Safari/537.36");
Draft draft = new Draft_17();
draft.setParseMode(WebSocket.Role.CLIENT);
new MyWssUtil(new URI("wss://****************填寫(xiě)自己的url******************")
, draft
, headers
, 10
) {
@Override
public void onClose(int arg0, String arg1, boolean arg2) {
System.out.println(String.format("onClose:【%s】【%s】【%s】", arg0, arg1, arg2));
}
@Override
public void onError(Exception arg0) {
System.out.println(String.format("onError:%s", arg0));
}
@Override
public void onMessage(String arg0) {
if (!arg0.equals("pong")) {
result.add(arg0);
System.out.println(String.format("onMessage:%s", arg0));
}
this.send(arg0);
}
@Override
public void onOpen(ServerHandshake arg0) {
System.out.println(String.format("onOpen:%s", arg0));
this.send(initmsg);
}
}.connect();
}
}
完整代碼地址:https://github.com/org-hejianhui/websocket
部分圖片來(lái)源于網(wǎng)絡(luò),版權(quán)歸原作者,侵刪。
以上謝謝大家,求贊求贊求贊!
?? 大佬們隨手關(guān)注下我的wx公眾號(hào)【一角錢(qián)小助手】和 掘金專(zhuān)欄【一角錢(qián)】 更多干貨等你來(lái)~~