带有 Symfony 和 PHP-FPM 的 NGINX SSI
NGINX SSI with Symfony and PHP-FPM
我需要您帮助我开发一个用作技术堆栈的应用程序:
- DOCKER NGINX
- DOCKER 与 PHP-FPM 和 Symfony
我想将页面分成不同的部分并缓存其中一些,因为生成速度很慢。
所以我尝试按照文档中的说明使用 SSI(服务器端包含):https://symfony.com/doc/current/http_cache/ssi.html
这是我的码头工人的配置:
NGINX :
FROM nginx:1.19.2
COPY docker-compose/nginx /
ADD docker-compose/nginx/nginx.conf /etc/nginx/nginx.conf
ADD docker-compose/nginx/symfony.dev.conf /etc/nginx/conf.d/default.conf
和配置文件:
nginx.conf
user www-data;
worker_processes 4;
pid /run/nginx.pid;
events {
worker_connections 2048;
multi_accept on;
use epoll;
}
http {
server_tokens on;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 15;
types_hash_max_size 2048;
include /etc/nginx/mime.types;
default_type application/octet-stream;
access_log on;
error_log on;
access_log /dev/stdout;
error_log /dev/stdout;
gzip on;
gzip_disable "msie6";
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
open_file_cache max=300;
client_body_temp_path /tmp 1 2;
client_body_buffer_size 256k;
client_body_in_file_only off;
}
symfony.dev.conf
proxy_cache_path /tmp/nginx levels=1:2 keys_zone=default:10m;
server {
listen 80;
root /var/www/html/symfony/public;
client_max_body_size 40M;
location = /health {
return 200 "healthy\n";
}
location = /ping {
return 200 "pong\n";
}
location / {
ssi on;
try_files $uri /index.php$is_args$args;
}
location ~ ^/index\.php(/|$) {
ssi on;
fastcgi_pass php-fpm:9000;
fastcgi_split_path_info ^(.+\.php)(/.*)$;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param DOCUMENT_ROOT $document_root;
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param CONTENT_TYPE $content_type;
fastcgi_param CONTENT_LENGTH $content_length;
fastcgi_read_timeout 300;
internal;
}
location ~ \.php$ {
return 404;
}
location /status {
access_log off;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_pass php-fpm:9000;
fastcgi_index status.html;
}
error_log /var/log/nginx/error.log;
access_log /var/log/nginx/access.log;
}
如您所见,我在网络服务器上启用了 SSI。
此外,我将其添加到框架的配置中(如 doc):
framework:
ssi: { enabled: true }
fragments: { path: /_fragment }
在模板/控制器中,我遵循文档:
模板
{{ render_ssi(controller('App\Controller\Pages\HomeController::xxxx')) }}
控制器
public function xxxx() {
sleep(2);
$response = $this->render('pages/home/xxxx.html.twig', [
]);
$response->setSharedMaxAge(Constants::SSI_CACHE_TTL);
return $response;
}
睡眠命令是为了测试缓存和iss是否正常工作...
更多信息:
我在阅读文档后在供应商中看到:render_ssi 确保仅当请求具有 header 要求时才生成 SSI 指令,例如 Surrogate-Capability: device="SSI/1.0" (通常由网络服务器提供)。否则它将直接嵌入 sub-response.
所以我尝试在代码中找到决定是否使用 SSI 的块在哪里:
vendor/symfony/http-kernel/HttpCache/AbstractSurrogate.php
在这一行中:
/**
* {@inheritdoc}
*/
public function hasSurrogateCapability(Request $request)
{
if (null === $value = $request->headers->get('Surrogate-Capability')) {
return false;
}
return false !== strpos($value, sprintf('%s/1.0', strtoupper($this->getName())));
}
所以我认为我的网络服务器不会将 ISS-Header (Surrogate-Capability
) 发送到我的 php-fpm。
我不知道我可以更改什么来进行一些测试...
谢谢大家,如果你能帮助我...
此致
编辑:
我建了一个仓库,之前暴露了同样的问题,你可以直接测试。
我正在私下分享我之前给你的解决方案,这样每个人都可以访问它。
- 首先,由于你使用的是fastcgi,你必须使用
fastcgi_cache_*
指令,
例如:
fastcgi_cache_path /tmp/nginx levels=1:2 keys_zone=foobar:10m;
而不是
proxy_cache_path /tmp/nginx levels=1:2 keys_zone=foobar:10m;
- 由于 Symfony 使用唯一的查询字符串来识别 ssi 片段,因此您必须在缓存键参数中包含查询字符串,
否则您将始终缓存整个页面。您可以使用:
fastcgi_cache_key $scheme://$host$saved_uri$is_args$args;
我看到你打电话:
http://localhost:8101/home
它会匹配“/”位置,然后nginx会发出内部请求
^/index.php(/|$)
问题是,通过这种方式,当前的 $uri
变量将被更改为“index.php”,因此您将丢失“/home”并且无法再将其传递给 Symfony (它由 Symfony 路由处理)。要解决这个问题,请将其保存到自定义 nginx 变量中:
set $saved_uri $uri;
然后传给fastcgi:
fastcgi_param REQUEST_URI $saved_uri;
注意默认情况下 REQUEST_URI
fastcgi 参数设置为 $request_uri
。如果你不改变它,Symfony 总是会收到 curl ("/home") 为 ssi 片段请求提供的路径!因此,在解决 ssi includes 时你会得到一个无限循环。(参见:Wrong cache key for SSI-subrequests with FastCGI)
我建议您尽快包含默认值 fastcgi_params,以便稍后可以覆盖它们。如果您将 include fastcgi_params 放在配置的底部,您的自定义值将被默认值覆盖。
- 您还必须考虑到在对 ssi 包含进行内部请求时,nginx 不会更新
$uri
变量。要解决此问题,请明确更新它。 (注意:由于前面描述的问题,我在这里使用 $saved_uri
而不是 $uri
)。假设您正在使用 _fragment 来识别 Symfony 生成的 ssi 片段的路径,
您需要添加:
set $saved_uri /_fragment;
- 关于 SSI header:告诉 Symfony nginx 已经启用了 ssi,
ssi on
指令是不够的,因为 nginx 不会自动发送 Surrogate-Capability: device="SSI/1.0" header 到 Symfony。
为此,请使用:
fastcgi_param HTTP_SURROGATE_CAPABILITY "device=\"SSI/1.0\"";
- 最后但同样重要的是,记住定义缓存路径是不够的,
你还需要告诉 nginx 使用它:
fastcgi_cache foobar;
总之,完整的配置为:
fastcgi_cache_path /tmp/nginx levels=1:2 keys_zone=foobar:10m;
fastcgi_cache_key $scheme://$host$saved_uri$is_args$args; # The query string must be used here too, because Symfony uses it to identify the ssi fragment
server {
listen 80;
root /var/www/html/symfony/public;
client_max_body_size 40M;
include fastcgi_params; # We must put this here ahead, to let locations override the params
location = /health {
return 200 "healthy\n";
}
location = /ping {
return 200 "pong\n";
}
location /_fragment {
set $saved_uri /_fragment; # We hardcode the value because internal ssi requests DO NOT update the $uri variable !
try_files $uri /index.php$is_args$args;
internal;
}
location / {
set $saved_uri $uri; # We need this because the $uri is renamed later when making the internal request towards "index.php", so we would lose the original request !
try_files $uri /index.php$is_args$args;
}
location ~ ^/index\.php(/|$) {
fastcgi_cache foobar; # Remember to not use the directive "proxy_cache" fastcgi
add_header X-Cache-Status $upstream_cache_status; # Used for debugging
# NOTE nginx<->Symfony cache is NOT considered in this
fastcgi_param HTTP_SURROGATE_CAPABILITY "device=\"SSI/1.0\""; # Nginx doesn't pass this http header to Symfony even if ssi is on, but Symfony needs it to know if the proxy is able to use ssi
ssi on;
fastcgi_param REQUEST_URI $saved_uri; # IMPORTANT The included default "fastcgi_params" uses $request_uri, so internal requests are skipped ! This causes an infinite loop because of ssi inclusion.
fastcgi_param QUERY_STRING $args; # For some reason, we need to pass it again even if the included default "fastcgi_params" looks correct
fastcgi_pass php-fpm:9000;
fastcgi_split_path_info ^(.+\.php)(/.*)$;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param DOCUMENT_ROOT $document_root;
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param CONTENT_TYPE $content_type;
fastcgi_param CONTENT_LENGTH $content_length;
fastcgi_read_timeout 300;
internal;
}
location ~ \.php$ {
return 404;
}
location /status {
access_log off;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_pass php-fpm:9000;
fastcgi_index status.html;
}
error_log /var/log/nginx/error.log;
access_log /var/log/nginx/access.log;
}
我需要您帮助我开发一个用作技术堆栈的应用程序:
- DOCKER NGINX
- DOCKER 与 PHP-FPM 和 Symfony
我想将页面分成不同的部分并缓存其中一些,因为生成速度很慢。
所以我尝试按照文档中的说明使用 SSI(服务器端包含):https://symfony.com/doc/current/http_cache/ssi.html
这是我的码头工人的配置:
NGINX :
FROM nginx:1.19.2
COPY docker-compose/nginx /
ADD docker-compose/nginx/nginx.conf /etc/nginx/nginx.conf
ADD docker-compose/nginx/symfony.dev.conf /etc/nginx/conf.d/default.conf
和配置文件:
nginx.conf
user www-data;
worker_processes 4;
pid /run/nginx.pid;
events {
worker_connections 2048;
multi_accept on;
use epoll;
}
http {
server_tokens on;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 15;
types_hash_max_size 2048;
include /etc/nginx/mime.types;
default_type application/octet-stream;
access_log on;
error_log on;
access_log /dev/stdout;
error_log /dev/stdout;
gzip on;
gzip_disable "msie6";
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
open_file_cache max=300;
client_body_temp_path /tmp 1 2;
client_body_buffer_size 256k;
client_body_in_file_only off;
}
symfony.dev.conf
proxy_cache_path /tmp/nginx levels=1:2 keys_zone=default:10m;
server {
listen 80;
root /var/www/html/symfony/public;
client_max_body_size 40M;
location = /health {
return 200 "healthy\n";
}
location = /ping {
return 200 "pong\n";
}
location / {
ssi on;
try_files $uri /index.php$is_args$args;
}
location ~ ^/index\.php(/|$) {
ssi on;
fastcgi_pass php-fpm:9000;
fastcgi_split_path_info ^(.+\.php)(/.*)$;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param DOCUMENT_ROOT $document_root;
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param CONTENT_TYPE $content_type;
fastcgi_param CONTENT_LENGTH $content_length;
fastcgi_read_timeout 300;
internal;
}
location ~ \.php$ {
return 404;
}
location /status {
access_log off;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_pass php-fpm:9000;
fastcgi_index status.html;
}
error_log /var/log/nginx/error.log;
access_log /var/log/nginx/access.log;
}
如您所见,我在网络服务器上启用了 SSI。
此外,我将其添加到框架的配置中(如 doc):
framework:
ssi: { enabled: true }
fragments: { path: /_fragment }
在模板/控制器中,我遵循文档:
模板
{{ render_ssi(controller('App\Controller\Pages\HomeController::xxxx')) }}
控制器
public function xxxx() {
sleep(2);
$response = $this->render('pages/home/xxxx.html.twig', [
]);
$response->setSharedMaxAge(Constants::SSI_CACHE_TTL);
return $response;
}
睡眠命令是为了测试缓存和iss是否正常工作...
更多信息:
我在阅读文档后在供应商中看到:render_ssi 确保仅当请求具有 header 要求时才生成 SSI 指令,例如 Surrogate-Capability: device="SSI/1.0" (通常由网络服务器提供)。否则它将直接嵌入 sub-response.
所以我尝试在代码中找到决定是否使用 SSI 的块在哪里:
vendor/symfony/http-kernel/HttpCache/AbstractSurrogate.php
在这一行中:
/**
* {@inheritdoc}
*/
public function hasSurrogateCapability(Request $request)
{
if (null === $value = $request->headers->get('Surrogate-Capability')) {
return false;
}
return false !== strpos($value, sprintf('%s/1.0', strtoupper($this->getName())));
}
所以我认为我的网络服务器不会将 ISS-Header (Surrogate-Capability
) 发送到我的 php-fpm。
我不知道我可以更改什么来进行一些测试...
谢谢大家,如果你能帮助我...
此致
编辑:
我建了一个仓库,之前暴露了同样的问题,你可以直接测试。
我正在私下分享我之前给你的解决方案,这样每个人都可以访问它。
- 首先,由于你使用的是fastcgi,你必须使用
fastcgi_cache_*
指令,
例如:
fastcgi_cache_path /tmp/nginx levels=1:2 keys_zone=foobar:10m;
而不是
proxy_cache_path /tmp/nginx levels=1:2 keys_zone=foobar:10m;
- 由于 Symfony 使用唯一的查询字符串来识别 ssi 片段,因此您必须在缓存键参数中包含查询字符串,
否则您将始终缓存整个页面。您可以使用:
fastcgi_cache_key $scheme://$host$saved_uri$is_args$args;
我看到你打电话:
http://localhost:8101/home
它会匹配“/”位置,然后nginx会发出内部请求 ^/index.php(/|$)
问题是,通过这种方式,当前的 $uri
变量将被更改为“index.php”,因此您将丢失“/home”并且无法再将其传递给 Symfony (它由 Symfony 路由处理)。要解决这个问题,请将其保存到自定义 nginx 变量中:
set $saved_uri $uri;
然后传给fastcgi:
fastcgi_param REQUEST_URI $saved_uri;
注意默认情况下 REQUEST_URI
fastcgi 参数设置为 $request_uri
。如果你不改变它,Symfony 总是会收到 curl ("/home") 为 ssi 片段请求提供的路径!因此,在解决 ssi includes 时你会得到一个无限循环。(参见:Wrong cache key for SSI-subrequests with FastCGI)
我建议您尽快包含默认值 fastcgi_params,以便稍后可以覆盖它们。如果您将 include fastcgi_params 放在配置的底部,您的自定义值将被默认值覆盖。
- 您还必须考虑到在对 ssi 包含进行内部请求时,nginx 不会更新
$uri
变量。要解决此问题,请明确更新它。 (注意:由于前面描述的问题,我在这里使用$saved_uri
而不是$uri
)。假设您正在使用 _fragment 来识别 Symfony 生成的 ssi 片段的路径,
您需要添加:
set $saved_uri /_fragment;
- 关于 SSI header:告诉 Symfony nginx 已经启用了 ssi,
ssi on
指令是不够的,因为 nginx 不会自动发送 Surrogate-Capability: device="SSI/1.0" header 到 Symfony。
为此,请使用:
fastcgi_param HTTP_SURROGATE_CAPABILITY "device=\"SSI/1.0\"";
- 最后但同样重要的是,记住定义缓存路径是不够的,
你还需要告诉 nginx 使用它:
fastcgi_cache foobar;
总之,完整的配置为:
fastcgi_cache_path /tmp/nginx levels=1:2 keys_zone=foobar:10m;
fastcgi_cache_key $scheme://$host$saved_uri$is_args$args; # The query string must be used here too, because Symfony uses it to identify the ssi fragment
server {
listen 80;
root /var/www/html/symfony/public;
client_max_body_size 40M;
include fastcgi_params; # We must put this here ahead, to let locations override the params
location = /health {
return 200 "healthy\n";
}
location = /ping {
return 200 "pong\n";
}
location /_fragment {
set $saved_uri /_fragment; # We hardcode the value because internal ssi requests DO NOT update the $uri variable !
try_files $uri /index.php$is_args$args;
internal;
}
location / {
set $saved_uri $uri; # We need this because the $uri is renamed later when making the internal request towards "index.php", so we would lose the original request !
try_files $uri /index.php$is_args$args;
}
location ~ ^/index\.php(/|$) {
fastcgi_cache foobar; # Remember to not use the directive "proxy_cache" fastcgi
add_header X-Cache-Status $upstream_cache_status; # Used for debugging
# NOTE nginx<->Symfony cache is NOT considered in this
fastcgi_param HTTP_SURROGATE_CAPABILITY "device=\"SSI/1.0\""; # Nginx doesn't pass this http header to Symfony even if ssi is on, but Symfony needs it to know if the proxy is able to use ssi
ssi on;
fastcgi_param REQUEST_URI $saved_uri; # IMPORTANT The included default "fastcgi_params" uses $request_uri, so internal requests are skipped ! This causes an infinite loop because of ssi inclusion.
fastcgi_param QUERY_STRING $args; # For some reason, we need to pass it again even if the included default "fastcgi_params" looks correct
fastcgi_pass php-fpm:9000;
fastcgi_split_path_info ^(.+\.php)(/.*)$;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param DOCUMENT_ROOT $document_root;
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param CONTENT_TYPE $content_type;
fastcgi_param CONTENT_LENGTH $content_length;
fastcgi_read_timeout 300;
internal;
}
location ~ \.php$ {
return 404;
}
location /status {
access_log off;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_pass php-fpm:9000;
fastcgi_index status.html;
}
error_log /var/log/nginx/error.log;
access_log /var/log/nginx/access.log;
}