Nginx反向代理问题
# 1. nginx代理问题
# 1.1 nginx反代到nginx
问题: 使用域名可以访问,使用IP报404
解决: 在location段proxy_pass参数上面加上一行:
proxy_set_header Host xxx.com;
# 1.2 nginx反代到项目
问题: location匹配到规则之后,无法从一个完整的url跳到另一个完整的url
解决: 在nginx配置文件的http段添加一行参数:
underscores_in_headers on;
# 1.3. nginx反代配置错误页面
问题: 无法捕捉到proxy_pass后的状态码(4xx、5xx)进行处理
解决: #在server中location段proxy_pass参数上面添加一行
proxy_intercept_errors on;
#在server中添加error_page匹配:
error_page 403 404 500 502 503 504 /error.html;
location = /error.html {
root html/serverError;
}
2
3
4
# 1.4 nginx反代到阿里云
问题: nginx反代proxy_pass到函数计算服务时,由于传入的header中host是前端域名,不是函数计算绑定的域名,而且会把upstream中server域名解析为IP,阿里云无法识别。
解决: #在server中location段中修改proxy_set_header参数,手动设置header host,传入函数计算的域名
proxy_set_header Host xxx.com;
# 1.5 nginx支持websocket代理,并保持连接超过60s不中断
# 1.5.1 支持websocket代理
在location段添加:
proxy_set_header Upgrade "websocket";
proxy_set_header Connection "Upgrade";
2
**原理:**关键部分在于HTTP的请求中多了如下头部:
Upgrade: websocket
Connection: Upgrade
2
这两个字段表示请求服务器升级协议为WebSocket。服务器处理完请求后,响应如下报文:
// 状态码为101
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: upgrade
2
3
4
如果添加未生效,可以看下nginx http段是否有以下配置:
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
2
3
4
# 1.5.2 保持连接超过60s不中断
利用nginx代理websocket的时候,发现客户端和服务器握手成功后,如果在60s时间内没有数据交互,连接就会自动断开,为了保持长连接,可以采取来两种方式.
第一种方式:
同样是在location段添加:
proxy_read_timeout 600s
proxy_send_timeout 600s
2
两个超时参数含义:
proxy_read_timeout语法:默认值 60s 上下文 http server location 说明 该指令设置与代理服务器的读超时时间。它决定了nginx会等待多长时间来获得请求的响应。 这个时间不是获得整个response的时间,而是两次reading操作的时间。
proxy_send_timeout语法:默认值 60s 上下文 http server location 说明 这个指定设置了发送请求给upstream服务器的超时时间。超时设置不是为了整个发送期间,而是在两次write操作期间。 如果超时后,upstream没有收到新的数据,nginx会关闭连接
如果在10分钟之内没有数据交互的话,websocket连接就会自动断开,所以这种方式还是有点问题,如果我页面停留时间超过十分钟而且又没有数据交互的话,连接还是会断开的,所以需要同时结合第二种方法.
第二种方式:
在nginx延长超时时间的基础上,前端在超时时间内发心跳包,刷新再读时间,前端具体实现见如下代码(此处代码包含了前端整个websocket的实现过程,其中加粗重点标注了发心跳包的内容):
// websocket连接
var websocket_connected_count = 0;
var onclose_connected_count = 0;
function newWebSocket(){
var websocket = null;
// 判断当前环境是否支持websocket
if(window.WebSocket){
if(!websocket){
var ws_url ="wss://"+domain+"/updatewebsocket";
websocket = new WebSocket(ws_url);
}
}else{
Tip("not support websocket");
}
// 连接成功建立的回调方法
websocket.onopen = function(e){
heartCheck.reset().start(); // 成功建立连接后,重置心跳检测
Tip("connected successfully")
}
// 连接发生错误,连接错误时会继续尝试发起连接(尝试5次)
websocket.onerror = function() {
console.log("onerror连接发生错误")
websocket_connected_count++;
if(websocket_connected_count <= 5){
newWebSocket()
}
}
// 接受到消息的回调方法
websocket.onmessage = function(e){
console.log("接受到消息了")
heartCheck.reset().start(); // 如果获取到消息,说明连接是正常的,重置心跳检测
var message = e.data;
if(message){
//执行接收到消息的操作,一般是刷新UI
}
}
// 接受到服务端关闭连接时的回调方法
websocket.onclose = function(){
Tip("onclose断开连接");
}
// 监听窗口事件,当窗口关闭时,主动断开websocket连接,防止连接没断开就关闭窗口,server端报错
window.onbeforeunload = function(){
websocket.close();
}
// 心跳检测, 每隔一段时间检测连接状态,如果处于连接中,就向server端主动发送消息,来重置server端与客户端的最大连接时间,如果已经断开了,发起重连。
var heartCheck = {
timeout: 55000, // 9分钟发一次心跳,比server端设置的连接时间稍微小一点,在接近断开的情况下以通信的方式去重置连接时间。
serverTimeoutObj: null,
reset: function(){
clearTimeout(this.timeoutObj);
clearTimeout(this.serverTimeoutObj);
return this;
},
start: function(){
var self = this;
this.serverTimeoutObj = setInterval(function(){
if(websocket.readyState == 1){
console.log("连接状态,发送消息保持连接");
websocket.send("ping");
heartCheck.reset().start(); // 如果获取到消息,说明连接是正常的,重置心跳检测
}else{
console.log("断开状态,尝试重连");
newWebSocket();
}
}, this.timeout)
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# 1.6 nginx 透传header到多级应用
背景
调用链路:浏览器 (https://xxx:30001) --> nginx (反代到http://serviceName) --> A(http://serverName) --> B(http://serverName)--> C (https://xxx:30002) --> 回调nginx --> D
问题
- B在调用C时,header里只有host地址,没有端口
- C 在回调nginx时,应使用https协议,然而却使用了http协议,回调失败。
分析
- 客户端访问nginx的端口信息没有传递到C
- C 应用代码中根据B传递的header信息判断回调协议,默认走了http
- 综上,需要在nginx header中增加端口和协议信息
实现
server {
listen 30001 ssl;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_certificate /etc/nginx/cert/cert.pem;
ssl_certificate_key /etc/nginx/cert/key.pem;
server_name _;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
location /healthz {
access_log off;
return 200;
}
location / {
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_set_header X-Forwarded-Proto $scheme; //透传协议
proxy_set_header X-Forwarded-Port $server_port; // 透传端口
proxy_pass https://B:30013;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
另外,如果存在多级代理会重写header配置时,可以自定义header头来实现
proxy_set_header My-Forwarded-Proto $scheme;
proxy_set_header My-Forwarded-Port $server_port;
2