![URL 中没有尾部斜杠会导致 nginx 尝试从错误位置加载 js](https://linux22.com/image/761861/URL%20%E4%B8%AD%E6%B2%A1%E6%9C%89%E5%B0%BE%E9%83%A8%E6%96%9C%E6%9D%A0%E4%BC%9A%E5%AF%BC%E8%87%B4%20nginx%20%E5%B0%9D%E8%AF%95%E4%BB%8E%E9%94%99%E8%AF%AF%E4%BD%8D%E7%BD%AE%E5%8A%A0%E8%BD%BD%20js.png)
我有一个类似于所见的 nginx api 网关配置设置这里。该应用程序在“www.foo.com/bar/app/”上提供服务,但我们还希望用户能够访问该网站而不包括尾部斜杠。
目前,当我访问没有尾部斜杠的 URL 时,服务器会抛出 500 错误。深入研究日志显示以下内容:
2021/01/05 22:43:39 [notice] 22#22: *4 rewritten data: "/", args: "", client: 172.29.0.1, server: , request: "GET /bar/app HTTP/1.1", host: "localhost:8080"
2021/01/05 22:43:39 [error] 22#22: *4 open() "/etc/nginx/html/nap/js/compiled/app.js" failed (2: No such file or directory), client: 172.29.0.1, server: , request: "GET /bar/js/compiled/app.js HTTP/1.1", host: "localhost:8080", referrer: "http://localhost:8080/bar/app"
然而,当 URL 带有尾部斜杠时,日志将显示以下序列:
2021/01/05 22:33:55 [notice] 22#22: *1 rewritten data: "//", args: "", client: 172.29.0.1, server: , request: "GET /bar/app/ HTTP/1.1", host: "localhost:8080"
2021/01/05 22:33:55 [notice] 22#22: *1 "^/bar/app(.*)$" matches "/bar/app/js/compiled/app.js", client: 172.29.0.1, server: , request: "GET /bar/app/js/compiled/app.js HTTP/1.1", host: "localhost:8080", referrer: "http://localhost:8080/bar/app/"
目前的API定义+策略:
location /bar/app {
set $upstream bar_frontend_app_main;
rewrite ^/bar/app(.*)$ /_bar_frontend_app_service$1 last;
}
location /_bar_frontend_app_service {
internal;
set $api_name "Frontend API";
rewrite ^/_bar_frontend_app_service(.*)$ /$1 break;
proxy_pass http://$upstream; # Proxy the rewritten URI
}
完整配置如下:
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
# Added to troubleshoot trailing slash frontend issue
log_format upstream_logging '[$time_local] $remote_addr - $remote_user - $server_name to: '
'$upstream: $request upstream_response_time $upstream_response_time '
'msec $msec request_time $request_time';
access_log /var/log/nginx/access.log main;
sendfile on;
keepalive_timeout 65;
include /etc/nginx/api_gateway.conf; # All API gateway configuration
include /etc/nginx/conf.d/*.conf; # Regular web traffic
}
# configuration file /etc/nginx/mime.types:
types {
...
}
# configuration file /etc/nginx/api_gateway.conf:
log_format api_main '$remote_addr - $remote_user [$time_local] "$request"'
'$status $body_bytes_sent "$http_referer" "$http_user_agent"'
'"$http_x_forwarded_for" "$api_name"';
include api_backends.conf;
server {
set $api_name -; # Start with an undefined API name, each API will update this value
access_log /var/log/nginx/api_access.log api_main; # Each API may also log to a separate file
listen 80;
# API definitions, one per file
include api_conf.d/*.conf;
# Error responses
error_page 404 = @400; # Invalid paths are treated as bad requests
proxy_intercept_errors on; # Do not send backend errors to the client
include api_json_errors.conf; # API client friendly JSON error responses
default_type application/json; # If no content-type then assume JSON
proxy_http_version 1.1;
proxy_set_header Connection "";
rewrite_log on;
}
# configuration file /etc/nginx/api_backends.conf:
upstream foo_api_main {
keepalive 32;
zone foo_service 64k;
server foo-server:5000;
}
upstream foobar_api_main {
keepalive 32;
zone foobar_service 64k;
server foobar:5000;
}
upstream bar_frontend_app_main {
keepalive 32;
zone bar_frontend_app_service 64k;
server bar-frontend:80;
}
# configuration file /etc/nginx/api_conf.d/foo_api.conf:
# API definition
#
location /api/hello/foo {
set $upstream foo_api_main;
rewrite ^/api/hello/foo/(.*)$ /_foo_service$1 last;
}
# This is used when app is running through MAF
location /hello/foo {
set $upstream foo_api_main;
rewrite ^/hello/foo/(.*)$ /_foo_service$1 last;
}
# Policy section
#
location /_foo_service {
internal;
set $api_name "foo backend API";
rewrite ^/_foo_service(.*)$ /api/$1 break; # Remove /_foo_service prefix
proxy_pass http://$upstream; # Proxy the rewritten URI
}
# configuration file /etc/nginx/api_conf.d/bar_frontend.conf:
location /bar/app {
set $upstream bar_frontend_app_main;
rewrite ^/bar/app(.*)$ /_bar_frontend_app_service$1 last;
}
# Policy section
#
location /_bar_frontend_app_service {
internal;
set $api_name "Frontend API";
rewrite ^/_bar_frontend_app_service(.*)$ /$1 break;
proxy_pass http://$upstream; # Proxy the rewritten URI
}
# configuration file /etc/nginx/api_conf.d/foobar_api.conf:
# API definition
#
location /api/hello/foobar {
set $upstream foobar_api_main;
rewrite ^/api/hello/foobar/(.*)$ /_foobar_service$1 last;
}
# This is used when app is running through MAF
location /hello/foobar {
set $upstream foobar_api_main;
rewrite ^/hello/foobar/(.*)$ /_foobar_service$1 last;
}
# Policy section
location /_foobar_service {
internal;
set $api_name "foobar backend API";
rewrite ^/_foobar_service(.*)$ /$1 break; # Remove /_foobar_service prefix
proxy_pass http://$upstream; # Proxy the rewritten URI
}
# configuration file /etc/nginx/api_json_errors.conf:
error_page 400 = @400;
location @400 { return 400 '{"status":400,"message":"Bad request"}\n'; }
error_page 401 = @401;
location @401 { return 401 '{"status":401,"message":"Unauthorized"}\n'; }
error_page 403 = @403;
location @403 { return 403 '{"status":403,"message":"Forbidden"}\n'; }
error_page 404 = @404;
location @404 { return 404 '{"status":404,"message":"Resource not found"}\n'; }
error_page 405 = @405;
location @405 { return 405 '{"status":405,"message":"Method not allowed"}\n'; }
error_page 408 = @408;
location @408 { return 408 '{"status":408,"message":"Request timeout"}\n'; }
error_page 413 = @413;
location @413 { return 413 '{"status":413,"message":"Payload too large"}\n'; }
error_page 414 = @414;
location @414 { return 414 '{"status":414,"message":"Request URI too large"}\n'; }
error_page 415 = @415;
location @415 { return 415 '{"status":415,"message":"Unsupported media type"}\n'; }
error_page 426 = @426;
location @426 { return 426 '{"status":426,"message":"HTTP request was sent to HTTPS port"}\n'; }
error_page 429 = @429;
location @429 { return 429 '{"status":429,"message":"API rate limit exceeded"}\n'; }
error_page 495 = @495;
location @495 { return 495 '{"status":495,"message":"Client certificate authentication error"}\n'; }
error_page 496 = @496;
location @496 { return 496 '{"status":496,"message":"Client certificate not presented"}\n'; }
error_page 497 = @497;
location @497 { return 497 '{"status":497,"message":"HTTP request was sent to mutual TLS port"}\n'; }
error_page 500 = @500;
location @500 { return 500 '{"status":500,"message":"Server error"}\n'; }
error_page 501 = @501;
location @501 { return 501 '{"status":501,"message":"Not implemented"}\n'; }
error_page 502 = @502;
location @502 { return 502 '{"status":502,"message":"Bad gateway"}\n'; }