带有 Symfony 和 PHP-FPM 的 NGINX SSI

NGINX SSI with Symfony and PHP-FPM

我需要您帮助我开发一个用作技术堆栈的应用程序:

我想将页面分成不同的部分并缓存其中一些,因为生成速度很慢。

所以我尝试按照文档中的说明使用 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。

我不知道我可以更改什么来进行一些测试...

谢谢大家,如果你能帮助我...

此致

编辑:

我建了一个仓库,之前暴露了同样的问题,你可以直接测试。

https://github.com/alessandro-candon/ssi-symfony

我正在私下分享我之前给你的解决方案,这样每个人都可以访问它。

  1. 首先,由于你使用的是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;
  1. 由于 Symfony 使用唯一的查询字符串来识别 ssi 片段,因此您必须在缓存键参数中包含查询字符串,

否则您将始终缓存整个页面。您可以使用:

fastcgi_cache_key   $scheme://$host$saved_uri$is_args$args;
  1. 来自您在 https://github.com/alessandro-candon/ssi-symfony
  2. 上的代码

我看到你打电话:

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 放在配置的底部,您的自定义值将被默认值覆盖。

  1. 您还必须考虑到在对 ssi 包含进行内部请求时,nginx 不会更新 $uri 变量。要解决此问题,请明确更新它。 (注意:由于前面描述的问题,我在这里使用 $saved_uri 而不是 $uri )。假设您正在使用 _fragment 来识别 Symfony 生成的 ssi 片段的路径,

您需要添加:

set $saved_uri /_fragment;
  1. 关于 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\"";
  1. 最后但同样重要的是,记住定义缓存路径是不够的,

你还需要告诉 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;
}