实验场景
- 前端域名
http://abc.nestealin.com/
配置如下
server { listen 80 ; listen 443 ssl; listen 8443 ssl; server_name abc.nestealin.com; charset utf-8; access_log logs/abc.nestealin.com.access.main.log main; error_log logs/abc.nestealin.com.error.log error; ssl_certificate /usr/local/nginx/conf/cert/server.cer; ssl_certificate_key /usr/local/nginx/conf/cert/server.key; ssl_session_timeout 5m; ssl_prefer_server_ciphers on; location / { # include firewall.conf; proxy_redirect off; 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-Real-Port $remote_port; proxy_set_header X-Forwarded-Proto $scheme; proxy_pass http://192.168.1.23:12345; } }
- 后端服务
http://192.168.1.23:12345/login
配置如下
server { listen 12345; charset utf-8; access_log logs/$http_host.access.main.log main; error_log logs/test.error.crit.log crit; location /login { add_header Content-Type 'application/json'; return 200 "uri: $uri"; } location / { return 302 http://abc.nestealin.com/login; } }
实验背景
- 场景1:
- 当正常请求域名根路径,后端服务会有一次让客户端302到
/login
的动作
- 当正常请求域名根路径,后端服务会有一次让客户端302到
正常请求,会给客户端返回302,然后再去请求 /login
路径.
- 浏览器请求如下
- curl 下查看跟随跳转
nestealin >> ~ # curl http://abc.nestealin.com -vL
* Trying 192.168.1.11...
* TCP_NODELAY set
* Connected to abc.nestealin.com (192.168.1.11) port 80 (#0)
> GET / HTTP/1.1
> Host: abc.nestealin.com
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 302 Moved Temporarily
< Server: nginx
< Date: Wed, 04 Aug 2021 15:27:36 GMT
< Content-Type: text/html
< Content-Length: 138
< Connection: keep-alive
< Keep-Alive: timeout=120
< Location: http://abc.nestealin.com/login
<
* Ignoring the response-body
* Connection #0 to host abc.nestealin.com left intact
* Issue another request to this URL: 'http://abc.nestealin.com/login'
* Found bundle for host abc.nestealin.com: 0x7fcb7dd15ee0 [can pipeline]
* Could pipeline, but not asked to!
* Re-using existing connection! (#0) with host abc.nestealin.com
* Connected to abc.nestealin.com (192.168.1.11) port 80 (#0)
> GET /login HTTP/1.1
> Host: abc.nestealin.com
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 200 OK
< Server: nginx
< Date: Wed, 04 Aug 2021 15:27:36 GMT
< Content-Type: application/json
< Content-Length: 11
< Connection: keep-alive
< Keep-Alive: timeout=120
<
* Connection #0 to host abc.nestealin.com left intact
uri: /login* Closing connection 0
由上可以看出:
- 客户端先请求了
http://abc.nestealin.com/
这个URL - 然后服务端响应了 302 状态码,并指定
Location: http://abc.nestealin.com/login
这个 URL 让客户端进行重定向请求 - 最终客户端通过请求
http://abc.nestealin.com/login
获取数据,得到状态码200
由此发现,当发升重定向时,存在客户端同一个域名需要请求两次的情况,”体验感受”没那么友好。
- 客户端先请求了
- 场景2:
- 在外网通过其他端口 (例如: 8443) 请求后端服务的根路径后,会重定向到默认端口的
/login
路径.
- 在外网通过其他端口 (例如: 8443) 请求后端服务的根路径后,会重定向到默认端口的
root@test >> ~ # curl https://abc.nestealin.com:8443 -IL
HTTP/1.1 302 Moved Temporarily
Server: nginx
Date: Wed, 04 Aug 2021 12:18:02 GMT
Content-Type: text/html
Content-Length: 138
Connection: keep-alive
Keep-Alive: timeout=120
Location: http://abc.nestealin.com/login
curl: (7) couldn't connect to host
- 但因为外网80端口禁用,所以访问失败。
解决方式
利用 error_page 指令实现重定向跟随
核心配置如下
location @handle_redirect { resolver 223.5.5.5; set $saved_redirect_location '$upstream_http_location'; proxy_pass $saved_redirect_location; } location / { proxy_pass http://192.168.1.23:12345; # 对 upstream 状态码检查,实现 error_page 错误重定向 proxy_intercept_errors on; # error_page 指令默认只检查了第一次后端返回的状态码,开启后可以跟随多次重定向。 recursive_error_pages on; # 根据状态码执行对应操作,以下为301、302、307状态码都会触发 error_page 301 302 307 = @handle_redirect; }
完整配置参考
server { listen 80 ; listen 443 ssl; listen 8443 ssl; server_name abc.nestealin.com; charset utf-8; access_log logs/abc.nestealin.com.access.main.log main; error_log logs/abc.nestealin.com.error.log error; ssl_certificate /usr/local/nginx/conf/cert/server.cer; ssl_certificate_key /usr/local/nginx/conf/cert/server.key; ssl_session_timeout 5m; ssl_prefer_server_ciphers on; location @handle_redirect { resolver 223.5.5.5; set $saved_redirect_location '$upstream_http_location'; # include firewall.conf; proxy_redirect off; 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-Real-Port $remote_port; proxy_set_header X-Forwarded-Proto $scheme; proxy_pass $saved_redirect_location; } location / { # include firewall.conf; proxy_redirect off; 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-Real-Port $remote_port; proxy_set_header X-Forwarded-Proto $scheme; proxy_pass http://192.168.7.83:12345; proxy_intercept_errors on; recursive_error_pages on; error_page 301 302 307 = @handle_redirect; } }
- 因为 error_page 捕获后是由 nginx 发起的重定向访问,所以若需透传客户端相关信息,那么也要在
handle_redirect
内部方法下增加响应透传头部,否则重定向后只会拿到 nginx 本机信息。
- 因为 error_page 捕获后是由 nginx 发起的重定向访问,所以若需透传客户端相关信息,那么也要在
实验效果
场景1:
只存在一次请求,直接获取200状态码
curl 侧观察,直接获取200状态码
nestealin >> ~ # curl https://abc.nestealin.com:8443/ -Lv * Trying 192.168.1.11... * TCP_NODELAY set * Connected to abc.nestealin.com (192.168.1.11) port 8443 (#0) * ALPN, offering h2 * ALPN, offering http/1.1 * successfully set certificate verify locations: * CAfile: /etc/ssl/cert.pem CApath: none * TLSv1.2 (OUT), TLS handshake, Client hello (1): * TLSv1.2 (IN), TLS handshake, Server hello (2): * TLSv1.2 (IN), TLS handshake, Certificate (11): * TLSv1.2 (IN), TLS handshake, Server key exchange (12): * TLSv1.2 (IN), TLS handshake, Server finished (14): * TLSv1.2 (OUT), TLS handshake, Client key exchange (16): * TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1): * TLSv1.2 (OUT), TLS handshake, Finished (20): * TLSv1.2 (IN), TLS change cipher, Change cipher spec (1): * TLSv1.2 (IN), TLS handshake, Finished (20): * SSL connection using TLSv1.2 / ECDHE-RSA-AES256-GCM-SHA384 * ALPN, server accepted to use h2 * Server certificate: * subject: CN=*.nestealin.com * start date: Jun 19 11:18:17 2021 GMT * expire date: Sep 17 11:18:16 2021 GMT * subjectAltName: host "abc.nestealin.com" matched cert's "*.nestealin.com" * issuer: C=US; O=Let's Encrypt; CN=R3 * SSL certificate verify ok. * Using HTTP2, server supports multi-use * Connection state changed (HTTP/2 confirmed) * Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0 * Using Stream ID: 1 (easy handle 0x7fe05200e800) > GET / HTTP/2 > Host: abc.nestealin.com:8443 > User-Agent: curl/7.64.1 > Accept: */* > * Connection state changed (MAX_CONCURRENT_STREAMS == 128)! < HTTP/2 200 < server: nginx < date: Wed, 04 Aug 2021 12:15:28 GMT < content-type: application/json < content-length: 11 < * Connection #0 to host abc.nestealin.com left intact uri: /login* Closing connection 0
场景2:
带端口访问后,同样在内部完成重定向,直接返回200给客户端
root@test >> ~ # curl https://abc.nestealin.com:8443 -vL * About to connect() to abc.nestealin.com port 8443 (#0) * Trying 219.11.74.12... connected * Connected to abc.nestealin.com (219.11.74.12) port 8443 (#0) * Initializing NSS with certpath: sql:/etc/pki/nssdb * CAfile: /etc/pki/tls/certs/ca-bundle.crt CApath: none * Server certificate: * subject: CN=*.nestealin.com * start date: Jun 19 11:18:17 2021 GMT * expire date: Sep 17 11:18:16 2021 GMT * common name: *.nestealin.com * issuer: CN=R3,O=Let's Encrypt,C=US > GET / HTTP/1.1 > User-Agent: curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.44 zlib/1.2.3 libidn/1.18 libssh2/1.4.2 > Host: abc.nestealin.com:8443 > Accept: */* > < HTTP/1.1 200 OK < Server: nginx < Date: Wed, 04 Aug 2021 12:08:28 GMT < Content-Type: application/json < Content-Length: 11 < Connection: keep-alive < Keep-Alive: timeout=120 < * Connection #0 to host abc.nestealin.com left intact * Closing connection #0 uri: /login
特别注意
在做内部重定向时,nginx需要对重定向域名做一次域名解析,一定务必保证ngxin可以解析该域名,否则会因无法解析直接返回502.
- curl 请求直接502
root@test >> ~ # curl https://abc.nestealin.com:8443 -vL * About to connect() to abc.nestealin.com port 8443 (#0) * Trying 219.11.74.18... connected * Connected to abc.nestealin.com (219.11.74.12) port 8443 (#0) * Initializing NSS with certpath: sql:/etc/pki/nssdb * CAfile: /etc/pki/tls/certs/ca-bundle.crt CApath: none * Server certificate: * subject: CN=*.nestealin.com * start date: Jun 19 11:18:17 2021 GMT * expire date: Sep 17 11:18:16 2021 GMT * common name: *.nestealin.com * issuer: CN=R3,O=Let's Encrypt,C=US > GET / HTTP/1.1 > User-Agent: curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.44 zlib/1.2.3 libidn/1.18 libssh2/1.4.2 > Host: abc.nestealin.com:8443 > Accept: */* > < HTTP/1.1 502 Bad Gateway < Server: nginx < Date: Wed, 04 Aug 2021 12:08:13 GMT < Content-Type: text/html; charset=utf-8 < Content-Length: 150 < Connection: keep-alive < Keep-Alive: timeout=120 < <html> <head><title>502 Bad Gateway</title></head> <body> <center><h1>502 Bad Gateway</h1></center> <hr><center>nginx</center> </body> </html> * Connection #0 to host abc.nestealin.com left intact * Closing connection #0
nginx错误日志提示如下:
2021/08/04 22:13:29 [error] 1490537#0: *4009172 no resolver defined to resolve abc.nestealin.com while sending to client, client: 192.168.1.3, server: test.neshomelab.com, request: "GET / HTTP/2.0", host: "abc.nestealin.com:8443"