I need your help with an application that use as technology stack :
I would like to split the page in different parts and cache some of them because are quite slow to be generated.
So I try to use SSI ( Server Side Inclue ) as is explaned in the documentation : https://symfony.com/doc/current/http_cache/ssi.html
This is the configuration of my dockers :
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
and the configuration files :
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;
}
As you can see I enable the SSI on the webserver.
Moreover I add this in the configuration of the framework (like doc) :
framework:
ssi: { enabled: true }
fragments: { path: /_fragment }
In the template / controller I follow the doc :
template
{{ render_ssi(controller('App\\Controller\\Pages\\HomeController::xxxx')) }}
controller
public function xxxx() {
sleep(2);
$response = $this->render('pages/home/xxxx.html.twig', [
]);
$response->setSharedMaxAge(Constants::SSI_CACHE_TTL);
return $response;
}
The sleep command is to test if the cache and iss is working propriety...
MORE INFOs :
I see in the vendor after reading this in the doc : render_ssi ensures that SSI directive are generated only if the request has the header requirement like Surrogate-Capability: device="SSI/1.0" (normally given by the web server). Otherwise it will embed directly the sub-response.
So I try to find in the code where is the block that decide if use SSI or not :
vendor/symfony/http-kernel/HttpCache/AbstractSurrogate.php
in this line :
/**
* {@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())));
}
So I think my webserver doesn't send the ISS-Header (Surrogate-Capability
) to my php-fpm.
I don't have any Idea on what can I change to make some test...
Thank you all if you can help me...
Regards
EDIT :
I crate a repository with the same problem exposed before, you can test it directly.
I'm sharing the solution I previously gave you in private, so everybody can have access to it.
fastcgi_cache_*
directives,for example:
fastcgi_cache_path /tmp/nginx levels=1:2 keys_zone=foobar:10m;
instead of
proxy_cache_path /tmp/nginx levels=1:2 keys_zone=foobar:10m;
otherwise you would always cache the entire page. You can use:
fastcgi_cache_key $scheme://$host$saved_uri$is_args$args;
I see you call:
http://localhost:8101/home
It would match the "/" location, then nginx would make an internal request to ^/index.php(/|$)
The problem is, in this way the current $uri
variable will be changed to "index.php", so you are losing "/home" and can't pass it anymore to Symfony (where it is handled by the Symfony routing). To solve this, save it to a custom nginx variable:
set $saved_uri $uri;
then, pass it to fastcgi:
fastcgi_param REQUEST_URI $saved_uri;
NOTE By default the REQUEST_URI
fastcgi param is set to $request_uri
. If you don't change it, Symfony will always receive the path provided by curl ("/home") for the ssi fragments requests too! Therefore you'll get an infinite loop when solving the ssi includes. (see: Wrong cache key for SSI-subrequests with FastCGI)
I suggest you to include the default fastcgi_params asap, so you can override them later. If you put include fastcgi_params at the bottom of you configuration, your custom values would be overridden by the default ones.
$uri
variable. To solve this, explicitly update it. (NOTE: Here i'm using $saved_uri
instead of $uri
because of the problem described before). Assuming you are using _fragment to identify the path for your ssi fregments generated by Symfony,you need to add:
set $saved_uri /_fragment;
ssi on
directive is not enough, because nginx doesn't automatically send the Surrogate-Capability: device="SSI/1.0" header to Symfony.To do so, use:
fastcgi_param HTTP_SURROGATE_CAPABILITY "device=\"SSI/1.0\"";
you also need to tell nginx to use it:
fastcgi_cache foobar;
In conclusion, the complete configuration will be:
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;
}