- 更新: 增加 https://httpbin.org/headers 作为API示例 - 2018-02-07
webpack devServer.proxy 配置参数与 http-proxy-middleware 一样,可用来解决本地开发过程中与后端服务器的跨域问题。当通过代理访问托管在Google或其他共享主机的一个HTTPS API时,不是CONNRESET就是提示404 Not Found,而浏览器直接访问是没有问题的。
比如:
$ curl 'https://httpbin.org/headers'
{
"headers": {
"Accept": "*/*",
"Connection": "close",
"Host": "httpbin.org",
"User-Agent": "curl/7.47.0"
}
}
如果本地webpack配置为:
proxy: {
'/api': {
target: "https://httpbin.org/",
pathRewrite: {'^/api' : ''},
secure: true
}
}
那么会提示:
[HPM] PROXY ERROR: ECONNRESET. localhost -> https://httpbin.org/headers
或者
[HPM] Error occurred while trying to proxy request /api from localhost:3000 to https://httpbin.org/headers (EPROTO) (https://nodejs.org/api/errors.html#errors_common_system_errors)
抓包看到没有完成握手过程
查看TLS握手内容发现SNI的值为localhost,而不是目标域名。SNI是HTTPS支持虚拟主机的一个特性,在握手阶段就能知道目标证书的信息,进而使用相应的证书,这样就能在一个IP上使用多个证书。如果使用stunnel建立SSL连接。将HTTPS转为明文HTTP协议又会怎样呢?是404 Not Found。
proxy: {
'/api': {
target: "http://httpbin.org/",
pathRewrite: {'^/api' : ''}
}
}
$ curl 'http://localhost:3000/api/headers' -I
HTTP/1.1 404 Not Found
查看文档,有个changeOrigin: true
参数。
如果后端服务托管在虚拟主机的时候,也就是一个IP对应多个域名,需要通过域名区分服务,那么参数changeOrigin
必须为true(默认为false),这样才会传递给后端正确的Host头,虚拟主机才能正确回应。否则http-proxy-middleware会原封不动将本地HTTP请求发往后端,包括Host: localhost
而不是Host: httpbin.org
,只有正确的Host才能使用虚拟主机,不然会返回404 Not Found。
比如要将http://localhost:8080/api/*
都转由https://httpbin.org/*
处理,正确的配置为:
proxy: {
"/api": {
target: "https://httpbin.org/",
pathRewrite: {'^/api' : ''}
changeOrigin: true,
secure: true
}
}
使用curl测试
$ curl http://localhost:3000/api/headers
{
"headers": {
"Accept": "*/*",
"Connection": "close",
"Host": "httpbin.org",
"User-Agent": "curl/7.47.0"
}
}
option.target: url string to be parsed with the url module
:后端服务器地址
option.changeOrigin: true/false, Default: false - changes the origin of the host header to the target URL
:更改HTTP头Host字段为目标服务器
option.secure: true/false, if you want to verify the SSL Certs
:是否验证SSL证书
参数changeOrigin: true
解决了两种使用虚拟主机代理错误的情况,一种是HTTPS握手失败,另一种是404 Not Found。这样代理就不再是盲转发,而是作为一个客户端去请求源服务器,然后把结果返回本地,避免了错误。
SSL 握手过程可用openssl查看,如果不指定SNI,没有证书发送到客户端。
$ openssl s_client -connect httpbin.org:443
CONNECTED(00000003)
140200928761496:error:14077438:SSL routines:SSL23_GET_SERVER_HELLO:tlsv1 alert internal error:s23_clnt.c:769:
---
no peer certificate available
---
No client certificate CA names sent
---
SSL handshake has read 7 bytes and written 305 bytes
---
New, (NONE), Cipher is (NONE)
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
SSL-Session:
Protocol : TLSv1.2
Cipher : 0000
Session-ID:
Session-ID-ctx:
Master-Key:
Key-Arg : None
PSK identity: None
PSK identity hint: None
SRP username: None
Start Time: 1517966624
Timeout : 300 (sec)
Verify return code: 0 (ok)
---
提示
no peer certificate available
No client certificate CA names sent
只有指定servername
参数才能获取证书。这个问题在单证书环境是不存在的,比如本地单主机单证书。
-servername host - Set TLS extension servername in ClientHello
$ openssl s_client -connect httpbin.org:443 -servername httpbin.org
CONNECTED(00000003)
depth=2 O = Digital Signature Trust Co., CN = DST Root CA X3
verify return:1
depth=1 C = US, O = Let's Encrypt, CN = Let's Encrypt Authority X3
verify return:1
depth=0 CN = httpbin.org
verify return:1
---
Certificate chain
0 s:/CN=httpbin.org
i:/C=US/O=Let's Encrypt/CN=Let's Encrypt Authority X3
1 s:/C=US/O=Let's Encrypt/CN=Let's Encrypt Authority X3
i:/O=Digital Signature Trust Co./CN=DST Root CA X3
---
Server certificate
-----BEGIN CERTIFICATE-----
.........................
-----END CERTIFICATE-----
subject=/CN=httpbin.org
issuer=/C=US/O=Let's Encrypt/CN=Let's Encrypt Authority X3
---
No client certificate CA names sent
Peer signing digest: SHA512
Server Temp Key: ECDH, P-256, 256 bits
---
SSL handshake has read 2976 bytes and written 451 bytes
---
New, TLSv1/SSLv3, Cipher is ECDHE-RSA-AES128-GCM-SHA256
Server public key is 2048 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
SSL-Session:
Protocol : TLSv1.2
Cipher : ECDHE-RSA-AES128-GCM-SHA256
Session-ID: F5C728655E77907DC31AEF32239FFE8733A9F86FBA47C9A2B015A262812E16F4
Session-ID-ctx:
Master-Key: 9E48E87D46D270D3F56922A624D003EF3A79774AC0220A85C523DD77A7263F0328E6A3646B37A1B6F5C73A52F1CCB4FC
Key-Arg : None
PSK identity: None
PSK identity hint: None
SRP username: None
Start Time: 1517966742
Timeout : 300 (sec)
Verify return code: 0 (ok)
---
这种情况在使用CDN的情况下很常见,因为CDN同时使用多个TLS证书,需要通过SNI判断发送哪个证书。