本文将介绍下浏览器访问https站点的完整过程(以https://www.baidu.com为例
) ,包括直接访问和通过代理访问的区别。
1. 直接访问 HTTPS 站点(不经过代理)
当浏览器直接访问 https://www.baidu.com
时,整个过程如下:
(1) DNS 解析
-
浏览器首先解析
www.baidu.com
的 IP 地址。 -
如果本地缓存中没有对应的 IP 地址,浏览器会向 DNS 服务器发送查询请求。
(2) 建立 TCP 连接
-
浏览器通过解析到的 IP 地址和 HTTPS 默认端口(443),与目标服务器建立 TCP 连接。
-
这是一个普通的 TCP 连接,尚未涉及加密。
(3) TLS/SSL 握手
-
在 TCP 连接建立后,浏览器会发起 TLS/SSL 握手。
-
TLS/SSL 握手的主要目的是协商加密算法、验证服务器身份,并交换密钥。
-
握手过程:
-
浏览器发送
ClientHello
消息,包含支持的 TLS 版本、加密算法列表和一个随机数。 -
服务器返回
ServerHello
消息,选择 TLS 版本、加密算法,并发送服务器的随机数和证书。 -
浏览器验证服务器的证书(确保证书是由受信任的证书颁发机构签发的)。
-
浏览器生成一个预主密钥(Pre-Master Secret),并使用服务器的公钥加密后发送给服务器。
-
服务器使用私钥解密预主密钥。
-
浏览器和服务器使用预主密钥和随机数生成对称加密密钥(Session Key)。
-
握手完成,后续通信使用对称加密密钥加密。
-
(4) 发送 HTTP 请求
-
TLS/SSL 握手完成后,浏览器会通过加密的通道发送 HTTP 请求。
-
例如:
复制
GET / HTTP/1.1 Host: www.baidu.com Connection: keep-alive
(5) 接收 HTTP 响应
-
服务器通过加密的通道返回 HTTP 响应。
-
例如:
复制
HTTP/1.1 200 OK Content-Type: text/html Content-Length: 1234<html>...</html>
(6) 关闭连接
-
如果 HTTP 头中包含
Connection: close
,浏览器会关闭 TCP 连接。 -
否则,连接会保持打开状态,以便复用(HTTP Keep-Alive)。
2. 通过代理访问 HTTPS 站点
当浏览器通过代理服务器访问 https://www.baidu.com
时,整个过程如下:
(1) DNS 解析
-
浏览器解析代理服务器的 IP 地址(而不是
www.baidu.com
的 IP 地址)。
(2) 建立 TCP 连接
-
浏览器与代理服务器建立 TCP 连接。
(3) 发送 CONNECT
请求
-
浏览器向代理服务器发送
CONNECT
请求,请求与目标服务器建立隧道。 -
例如:
复制
CONNECT www.baidu.com:443 HTTP/1.1 Host: www.baidu.com:443 Proxy-Connection: Keep-Alive
(4) 代理服务器建立 TCP 连接
-
代理服务器解析
www.baidu.com
的 IP 地址,并与目标服务器建立 TCP 连接。
(5) 代理服务器响应 CONNECT
请求
-
如果代理服务器成功与目标服务器建立 TCP 连接,它会向浏览器返回
HTTP/1.1 200 Connection Established
响应。 -
例如:
复制
HTTP/1.1 200 Connection Established Proxy-agent: tistar-mitmproxy
(6) TLS/SSL 握手
-
浏览器通过代理服务器与目标服务器进行 TLS/SSL 握手。
-
代理服务器只是中转数据,不参与 TLS/SSL 握手。
(7) 发送 HTTP 请求
-
TLS/SSL 握手完成后,浏览器通过加密的通道发送 HTTP 请求。
-
例如:
复制
GET / HTTP/1.1 Host: www.baidu.com Connection: keep-alive
(8) 接收 HTTP 响应
-
目标服务器通过加密的通道返回 HTTP 响应。
-
代理服务器将响应转发给浏览器。
(9) 关闭连接
-
如果 HTTP 头中包含
Connection: close
,浏览器会关闭与代理服务器的 TCP 连接。 -
否则,连接会保持打开状态,以便复用。
3. 直接访问与代理访问的区别
步骤 | 直接访问 HTTPS 站点 | 通过代理访问 HTTPS 站点 |
---|---|---|
DNS 解析 | 解析目标服务器(如 www.baidu.com ) | 解析代理服务器 |
TCP 连接 | 直接与目标服务器建立 TCP 连接 | 与代理服务器建立 TCP 连接 |
TLS/SSL 握手 | 直接与目标服务器进行 TLS/SSL 握手 | 通过代理服务器与目标服务器握手 |
HTTP 请求 | 直接发送加密的 HTTP 请求 | 通过代理服务器发送加密的 HTTP 请求 |
数据中转 | 无 | 代理服务器中转数据 |
4. 总结
-
无论是直接访问还是通过代理访问 HTTPS 站点,浏览器都需要先建立 TCP 连接,然后进行 TLS/SSL 握手。
-
直接访问时,浏览器直接与目标服务器通信。
-
通过代理访问时,浏览器与代理服务器通信,代理服务器再与目标服务器通信。
-
你的代码直接断开连接是因为未处理 TLS/SSL 握手,而 HTTPS 服务器期望的是一个加密的连接。
5. 示例
如果与https站点建立tcp连接后,没有进行TLS/SSL握手,直接发送数据,则服务器会断开本次连接。原因是:
-
服务器期望的是一个 TLS/SSL 握手,但你的代码发送了一个明文 HTTP 请求。
-
服务器检测到不符合预期的数据,因此直接断开连接。
如下面的代码:
var net = require('net')let port = 443;
let host = "www.baidu.com"
let url = 'https://www.baidu.com/';var client = net.connect(port, host, function(){console.log("==> 连接到服务器")const line1 = `GET ${url} HTTP/1.1\r\n`;const head = `Host: ${host}\r\nConnection: keep-alive\r\n`;const blankLine = "\r\n";client.write(`${line1}${head}${blankLine}`)
})//客户端收到服务端数据的事件
client.on('data',function(resData){console.log('==> 客户端:收到服务端响应数据为'+resData.toString())
})// 收到数据后不需要保持连接,则断开
client.on('end',function(){console.log('==> 断开与服务器的连接')})
client.on('error', function(err) {console.error('==> 客户端出错:', err);
});
执行以上代码,不会收到服务端的数据,而是会直接断开连接。输出如下:
==> 连接到服务器
==> 断开与服务器的连接
问题原因
直接使用 net.connect
连接到 www.baidu.com:443
,但没有处理 TLS/SSL 握手。因此,服务器期望的是一个 TLS/SSL 加密的连接,但代码没有提供加密层,导致服务器直接断开连接。
具体问题
-
未进行 TLS/SSL 握手:
-
你的代码直接发送了一个明文 HTTP 请求(
GET / HTTP/1.1
),但服务器期望的是一个 TLS/SSL 握手。 -
服务器检测到不符合预期的数据,因此直接断开连接。
-
-
net.connect
不支持 TLS/SSL:-
net.connect
只能建立普通的 TCP 连接,无法处理 TLS/SSL 加密。 -
你需要使用
tls.connect
来处理 TLS/SSL 握手。
-
如果要使上面的程序可以与服务器正常进行数据交互,就需要解决TLS/SSL握手的问题,可以将nodejs 的 net 模块修改为 tls 模块,修改后的代码如下:
var tls = require('tls')let port = 443;
let host = "www.baidu.com"
let url = 'https://www.baidu.com/';var client = tls.connect(port, host, function(){console.log("==> 连接到服务器")const line1 = `GET ${url} HTTP/1.1\r\n`;const head = `Host: ${host}\r\nConnection: keep-alive\r\n`;const blankLine = "\r\n";client.write(`${line1}${head}${blankLine}`)
})//客户端收到服务端数据的事件
client.on('data',function(resData){console.log('==> 客户端:收到服务端响应数据为'+resData.toString())
})// 收到数据后不需要保持连接,则断开
client.on('end',function(){console.log('==> 断开与服务器的连接')})
client.on('error', function(err) {console.error('==> 客户端出错:', err);
});
关键点
-
使用
tls.connect
:-
tls.connect
会处理 TLS/SSL 握手,确保与 HTTPS 服务器的通信是加密的。
-
-
发送 HTTP 请求:
-
在 TLS/SSL 握手完成后,发送明文 HTTP 请求(如
GET / HTTP/1.1
)。
-
-
rejectUnauthorized: false
:-
忽略证书验证错误(仅用于测试,生产环境中应验证证书)。
-