Nginx 别名由于 try_files $uri 别名错误而中断
Nginx alias breaks due to try_files $uri alias bug
我有一个版本化的 Symfony API 实例,我想按以下方式配置它:
- api.com/api/v1 -> /srv/api-v1/public/index.php
- api.com/api/v2 -> /srv/api-v2/public/index.php
我尝试使用 nginx 位置和别名来解决这个问题,因为它是 Symfony,我们使用 try_files (as recommended) 在默认为 index.php
之前检查实际文件.
问题
似乎有一个 known nginx bug 用 alias
和 try_files
.
打破了 $uri
变量
我怎样才能绕过这个错误来达到我想要的结果?
nginx 配置文件
server {
listen 443 http2;
listen [::]:443 http2;
server_name api.com;
root /srv/default/public/; # default root when no version
location /api/v1 {
alias /srv/api-v1/public/;
try_files $uri /index.php$is_args$args;
}
location /api/v2 {
alias /srv/api-v2/public/;
try_files $uri /index.php$is_args$args;
}
location ~ ^/index\.php(/|$) {
include /etc/nginx/fastcgi.conf;
fastcgi_pass unix:/run/php-fpm-php7.2.socket;
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
internal;
fastcgi_read_timeout 300;
}
}
已尝试修复
根据 this hacky fix 我创建了以下内容,它确实有效但生成了一个巨大的配置文件,并不理想:
upstream v1 {
server 127.0.0.1;
}
upstream v2 {
server 127.0.0.1;
}
server {
listen 443 http2;
listen [::]:443 http2;
server_name api.com;
location /api/v1 {
proxy_pass http://v1;
}
location /api/v2 {
proxy_pass http://v2;
}
}
server {
server_name v1;
root /srv/api-v1/public/;
location / {
try_files $uri /index.php$is_args$args;
}
location ~ ^/index\.php(/|$) {
include /etc/nginx/fastcgi.conf;
fastcgi_pass unix:/run/php-fpm-php7.2.socket;
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
internal;
}
}
server {
server_name v2;
root /srv/api-v2/public/;
location / {
try_files $uri /index.php$is_args$args;
}
location ~ ^/index\.php(/|$) {
include /etc/nginx/fastcgi.conf;
fastcgi_pass unix:/run/php-fpm-php7.2.socket;
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
internal;
}
}
存在另一种解决方法,当 alias
指令与 try_files
指令结合使用时可以使用(参见 答案的示例)。您可以尝试以下配置吗?
server {
listen 443 http2;
listen [::]:443 http2;
server_name api.com;
root /srv/default/public/; # default root when no version
location ~ ^/api/v1(?<v1route>/.*)? {
alias /srv/api-v1/public;
try_files $v1route /api/v1/index.php$is_args$args;
location ~ ^/api/v1/index\.php$ {
internal;
include /etc/nginx/fastcgi.conf;
fastcgi_param SCRIPT_FILENAME /srv/api-v1/public/index.php;
fastcgi_read_timeout 300;
fastcgi_pass unix:/run/php-fpm-php7.2.socket;
}
}
location ~ ^/api/v2(?<v2route>/.*)? {
alias /srv/api-v2/public;
try_files $v2route /api/v2/index.php$is_args$args;
location ~ ^/api/v2/index\.php$ {
internal;
include /etc/nginx/fastcgi.conf;
fastcgi_param SCRIPT_FILENAME /srv/api-v2/public/index.php;
fastcgi_read_timeout 300;
fastcgi_pass unix:/run/php-fpm-php7.2.socket;
}
}
}
临时更新(待补充说明)
OP 问了一个额外的问题:
There's one slight issue with the defaults Symfony expects. The following three server variables $_SERVER['DOCUMENT_URI']
, $_SERVER['SCRIPT_NAME']
and $_SERVER['PHP_SELF']
equal /api/v1/index.php
but default Symfony nginx generates /index.php
, is there a way to tweak that in the above?
我不认为这是不正确的 Symfony 行为的原因。虽然当然可以调整此变量,但最可能的原因是 $_SERVER['REQUEST_URI']
值不正确。使用上面的配置,它将等于 /api/v1/some/path
但很可能 Symfony 期望只是 /some/path
而不是。这是您可以尝试覆盖该变量的配置:
map $request_uri $api_ver {
~^/api/v([12])/? ;
}
map $request_uri $api_route {
~^/api/v[12](/[^?]*)?(?:$|\?) ;
}
server {
listen 443 http2;
listen [::]:443 http2;
server_name api.com;
root /srv/default/public; # default root when no version
location ~ ^/api/v[12]/? {
alias /srv/api-v$api_ver/public;
try_files $api_route /api/v$api_ver/index.php$is_args$args;
location ~ ^/api/v[12]/index\.php$ {
internal;
include /etc/nginx/fastcgi.conf;
fastcgi_param REQUEST_URI $api_route$is_args$args;
fastcgi_param SCRIPT_FILENAME /srv/api-v$api_ver/public/index.php;
fastcgi_read_timeout 300;
fastcgi_pass unix:/run/php-fpm-php7.2.socket;
}
}
}
我认为这应该可以解决您的问题,但如果您真的想要调整 $_SERVER['DOCUMENT_URI']
、$_SERVER['SCRIPT_NAME']
和 $_SERVER['PHP_SELF']
,您可以向嵌套位置添加另外两行:
location ~ ^/api/v[12]/index\.php$ {
internal;
include /etc/nginx/fastcgi.conf;
fastcgi_param REQUEST_URI $api_route$is_args$args;
fastcgi_param DOCUMENT_URI /index.php;
fastcgi_param SCRIPT_NAME /index.php;
fastcgi_param SCRIPT_FILENAME /srv/api-v$api_ver/public/index.php;
fastcgi_read_timeout 300;
fastcgi_pass unix:/run/php-fpm-php7.2.socket;
}
但我认为正确的 Symfony 行为不需要它。
更新 2
要防止 $_SERVER['REQUEST_URI']
变量为空字符串,您有以下选项:
将请求从 /api/v1
或 /api/v2
重定向到 /api/v1/
或 /api/v2/
(对我来说似乎是最好的):
map $request_uri $api_ver {
~^/api/v([12])/ ;
}
map $request_uri $api_route {
~^/api/v[12](/[^?]*)(?:$|\?) ;
}
server {
...
location ~ ^/api/v[12]$ {
return 301 https://$host$uri/$is_args$args;
}
location ~ ^/api/v[12]/ {
alias /srv/api-v$api_ver/public;
try_files $api_route /api/v$api_ver/index.php$is_args$args;
location ~ ^/api/v[12]/index\.php$ {
internal;
include /etc/nginx/fastcgi.conf;
fastcgi_param REQUEST_URI $api_route$is_args$args;
fastcgi_param SCRIPT_FILENAME /srv/api-v$api_ver/public/index.php;
fastcgi_read_timeout 300;
fastcgi_pass unix:/run/php-fpm-php7.2.socket;
}
}
}
将尾部斜杠明确添加到确切的 /api/v1
或 /api/v2
请求中:
map $request_uri $api_ver {
~^/api/v([12])/? ;
}
map $request_uri $api_route {
~^/api/v[12](?:/([^?]*))?(?:$|\?) /;
}
server {
...
location ~ ^/api/v[12]/? {
alias /srv/api-v$api_ver/public;
try_files $api_route /api/v$api_ver/index.php$is_args$args;
location ~ ^/api/v[12]/index\.php$ {
internal;
include /etc/nginx/fastcgi.conf;
fastcgi_param REQUEST_URI $api_route$is_args$args;
fastcgi_param SCRIPT_FILENAME /srv/api-v$api_ver/public/index.php;
fastcgi_read_timeout 300;
fastcgi_pass unix:/run/php-fpm-php7.2.socket;
}
}
}
如果 $_SERVER['REQUEST_URI']
变量值应该以 /api
字符串作为前缀,您可以尝试 fastcgi_param REQUEST_URI /api$api_route$is_args$args;
而不是 fastcgi_param REQUEST_URI $api_route$is_args$args;
。
如果您想调整 $_SERVER['DOCUMENT_URI']
、$_SERVER['SCRIPT_NAME']
和 $_SERVER['PHP_SELF']
变量,请添加
fastcgi_param DOCUMENT_URI /index.php;
fastcgi_param SCRIPT_NAME /index.php;
行到嵌套位置。
在“hacky fix”中,您缺少 proxy_pass
指令的结尾 /
。
这是一个可以轻松测试您的实施的回购协议:https://github.com/MelwinKfr/nginx-workaround97
如果您想了解有关尾部斜线的更多信息,请参阅 this thread。
如果您觉得 'hacky fix' 没问题,您可以让它更干净一些。只需将重复的配置放入其他文件即可。然后你可以使用 include
指令导入另一个文件。
/etc/nginx/some_other_file:
location / {
try_files $uri /index.php$is_args$args;
}
location ~ ^/index\.php(/|$) {
include /etc/nginx/fastcgi.conf;
fastcgi_pass unix:/run/php-fpm-php7.2.socket;
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
internal;
}
upstream v1 {
server 127.0.0.1;
}
upstream v2 {
server 127.0.0.1;
}
server {
listen 443 http2;
listen [::]:443 http2;
server_name api.com;
location /api/v1 {
proxy_pass http://v1;
}
location /api/v2 {
proxy_pass http://v2;
}
}
server {
server_name v1;
root /srv/api-v1/public/;
include some_other_file;
}
server {
server_name v2;
root /srv/api-v2/public/;
include some_other_file;
}
我有一个版本化的 Symfony API 实例,我想按以下方式配置它:
- api.com/api/v1 -> /srv/api-v1/public/index.php
- api.com/api/v2 -> /srv/api-v2/public/index.php
我尝试使用 nginx 位置和别名来解决这个问题,因为它是 Symfony,我们使用 try_files (as recommended) 在默认为 index.php
之前检查实际文件.
问题
似乎有一个 known nginx bug 用 alias
和 try_files
.
$uri
变量
我怎样才能绕过这个错误来达到我想要的结果?
nginx 配置文件
server {
listen 443 http2;
listen [::]:443 http2;
server_name api.com;
root /srv/default/public/; # default root when no version
location /api/v1 {
alias /srv/api-v1/public/;
try_files $uri /index.php$is_args$args;
}
location /api/v2 {
alias /srv/api-v2/public/;
try_files $uri /index.php$is_args$args;
}
location ~ ^/index\.php(/|$) {
include /etc/nginx/fastcgi.conf;
fastcgi_pass unix:/run/php-fpm-php7.2.socket;
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
internal;
fastcgi_read_timeout 300;
}
}
已尝试修复
根据 this hacky fix 我创建了以下内容,它确实有效但生成了一个巨大的配置文件,并不理想:
upstream v1 {
server 127.0.0.1;
}
upstream v2 {
server 127.0.0.1;
}
server {
listen 443 http2;
listen [::]:443 http2;
server_name api.com;
location /api/v1 {
proxy_pass http://v1;
}
location /api/v2 {
proxy_pass http://v2;
}
}
server {
server_name v1;
root /srv/api-v1/public/;
location / {
try_files $uri /index.php$is_args$args;
}
location ~ ^/index\.php(/|$) {
include /etc/nginx/fastcgi.conf;
fastcgi_pass unix:/run/php-fpm-php7.2.socket;
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
internal;
}
}
server {
server_name v2;
root /srv/api-v2/public/;
location / {
try_files $uri /index.php$is_args$args;
}
location ~ ^/index\.php(/|$) {
include /etc/nginx/fastcgi.conf;
fastcgi_pass unix:/run/php-fpm-php7.2.socket;
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
internal;
}
}
存在另一种解决方法,当 alias
指令与 try_files
指令结合使用时可以使用(参见
server {
listen 443 http2;
listen [::]:443 http2;
server_name api.com;
root /srv/default/public/; # default root when no version
location ~ ^/api/v1(?<v1route>/.*)? {
alias /srv/api-v1/public;
try_files $v1route /api/v1/index.php$is_args$args;
location ~ ^/api/v1/index\.php$ {
internal;
include /etc/nginx/fastcgi.conf;
fastcgi_param SCRIPT_FILENAME /srv/api-v1/public/index.php;
fastcgi_read_timeout 300;
fastcgi_pass unix:/run/php-fpm-php7.2.socket;
}
}
location ~ ^/api/v2(?<v2route>/.*)? {
alias /srv/api-v2/public;
try_files $v2route /api/v2/index.php$is_args$args;
location ~ ^/api/v2/index\.php$ {
internal;
include /etc/nginx/fastcgi.conf;
fastcgi_param SCRIPT_FILENAME /srv/api-v2/public/index.php;
fastcgi_read_timeout 300;
fastcgi_pass unix:/run/php-fpm-php7.2.socket;
}
}
}
临时更新(待补充说明)
OP 问了一个额外的问题:
There's one slight issue with the defaults Symfony expects. The following three server variables
$_SERVER['DOCUMENT_URI']
,$_SERVER['SCRIPT_NAME']
and$_SERVER['PHP_SELF']
equal/api/v1/index.php
but default Symfony nginx generates/index.php
, is there a way to tweak that in the above?
我不认为这是不正确的 Symfony 行为的原因。虽然当然可以调整此变量,但最可能的原因是 $_SERVER['REQUEST_URI']
值不正确。使用上面的配置,它将等于 /api/v1/some/path
但很可能 Symfony 期望只是 /some/path
而不是。这是您可以尝试覆盖该变量的配置:
map $request_uri $api_ver {
~^/api/v([12])/? ;
}
map $request_uri $api_route {
~^/api/v[12](/[^?]*)?(?:$|\?) ;
}
server {
listen 443 http2;
listen [::]:443 http2;
server_name api.com;
root /srv/default/public; # default root when no version
location ~ ^/api/v[12]/? {
alias /srv/api-v$api_ver/public;
try_files $api_route /api/v$api_ver/index.php$is_args$args;
location ~ ^/api/v[12]/index\.php$ {
internal;
include /etc/nginx/fastcgi.conf;
fastcgi_param REQUEST_URI $api_route$is_args$args;
fastcgi_param SCRIPT_FILENAME /srv/api-v$api_ver/public/index.php;
fastcgi_read_timeout 300;
fastcgi_pass unix:/run/php-fpm-php7.2.socket;
}
}
}
我认为这应该可以解决您的问题,但如果您真的想要调整 $_SERVER['DOCUMENT_URI']
、$_SERVER['SCRIPT_NAME']
和 $_SERVER['PHP_SELF']
,您可以向嵌套位置添加另外两行:
location ~ ^/api/v[12]/index\.php$ {
internal;
include /etc/nginx/fastcgi.conf;
fastcgi_param REQUEST_URI $api_route$is_args$args;
fastcgi_param DOCUMENT_URI /index.php;
fastcgi_param SCRIPT_NAME /index.php;
fastcgi_param SCRIPT_FILENAME /srv/api-v$api_ver/public/index.php;
fastcgi_read_timeout 300;
fastcgi_pass unix:/run/php-fpm-php7.2.socket;
}
但我认为正确的 Symfony 行为不需要它。
更新 2
要防止 $_SERVER['REQUEST_URI']
变量为空字符串,您有以下选项:
将请求从
/api/v1
或/api/v2
重定向到/api/v1/
或/api/v2/
(对我来说似乎是最好的):map $request_uri $api_ver { ~^/api/v([12])/ ; } map $request_uri $api_route { ~^/api/v[12](/[^?]*)(?:$|\?) ; } server { ... location ~ ^/api/v[12]$ { return 301 https://$host$uri/$is_args$args; } location ~ ^/api/v[12]/ { alias /srv/api-v$api_ver/public; try_files $api_route /api/v$api_ver/index.php$is_args$args; location ~ ^/api/v[12]/index\.php$ { internal; include /etc/nginx/fastcgi.conf; fastcgi_param REQUEST_URI $api_route$is_args$args; fastcgi_param SCRIPT_FILENAME /srv/api-v$api_ver/public/index.php; fastcgi_read_timeout 300; fastcgi_pass unix:/run/php-fpm-php7.2.socket; } } }
将尾部斜杠明确添加到确切的
/api/v1
或/api/v2
请求中:map $request_uri $api_ver { ~^/api/v([12])/? ; } map $request_uri $api_route { ~^/api/v[12](?:/([^?]*))?(?:$|\?) /; } server { ... location ~ ^/api/v[12]/? { alias /srv/api-v$api_ver/public; try_files $api_route /api/v$api_ver/index.php$is_args$args; location ~ ^/api/v[12]/index\.php$ { internal; include /etc/nginx/fastcgi.conf; fastcgi_param REQUEST_URI $api_route$is_args$args; fastcgi_param SCRIPT_FILENAME /srv/api-v$api_ver/public/index.php; fastcgi_read_timeout 300; fastcgi_pass unix:/run/php-fpm-php7.2.socket; } } }
如果 $_SERVER['REQUEST_URI']
变量值应该以 /api
字符串作为前缀,您可以尝试 fastcgi_param REQUEST_URI /api$api_route$is_args$args;
而不是 fastcgi_param REQUEST_URI $api_route$is_args$args;
。
如果您想调整 $_SERVER['DOCUMENT_URI']
、$_SERVER['SCRIPT_NAME']
和 $_SERVER['PHP_SELF']
变量,请添加
fastcgi_param DOCUMENT_URI /index.php;
fastcgi_param SCRIPT_NAME /index.php;
行到嵌套位置。
在“hacky fix”中,您缺少 proxy_pass
指令的结尾 /
。
这是一个可以轻松测试您的实施的回购协议:https://github.com/MelwinKfr/nginx-workaround97
如果您想了解有关尾部斜线的更多信息,请参阅 this thread。
如果您觉得 'hacky fix' 没问题,您可以让它更干净一些。只需将重复的配置放入其他文件即可。然后你可以使用 include
指令导入另一个文件。
/etc/nginx/some_other_file:
location / {
try_files $uri /index.php$is_args$args;
}
location ~ ^/index\.php(/|$) {
include /etc/nginx/fastcgi.conf;
fastcgi_pass unix:/run/php-fpm-php7.2.socket;
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
internal;
}
upstream v1 {
server 127.0.0.1;
}
upstream v2 {
server 127.0.0.1;
}
server {
listen 443 http2;
listen [::]:443 http2;
server_name api.com;
location /api/v1 {
proxy_pass http://v1;
}
location /api/v2 {
proxy_pass http://v2;
}
}
server {
server_name v1;
root /srv/api-v1/public/;
include some_other_file;
}
server {
server_name v2;
root /srv/api-v2/public/;
include some_other_file;
}