WiR3D WiR3D - 1 year ago 56
PHP Question

nginx add_header on specific URI to PHP app with front controller

I have a pretty standard setup with a symfony2-like app with a front controller, running on nginx 1.10 and Centos7. It all works as expected, blocks where expected etc.

server {
listen 80;

root /opt/my/code/web;
index app.php;
charset utf-8;

location / {
try_files $uri $uri/ /app.php$is_args$args;
}

# pass the PHP scripts to php5-fpm
location ~ ^/app\.php(/|$) {

# problem here
location ~ ^/recording {
add_header Content-Type audio/x-wav;
}

fastcgi_split_path_info ^(.+?\.php)(/?.*)$;
fastcgi_pass unix:/var/run/php-fpm.sock;
fastcgi_index app.php;
include /etc/nginx/fastcgi_params;
fastcgi_param DOCUMENT_ROOT $realpath_root;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;

# Prevents URIs that include the front controller. This will 404:
internal;
}

# return 404 for all other php files not matching the front controller
location ~ \.php$ {
return 404;
}
}


I have a few issues but the main one is that I want special handling for a URI matching
/recording
but it still has to go through the front controller. (This is not debatable, it HAS to go through the front controller and modify a response header if the URI matches
/recording
)

Since
try_files
redirects to
location ~ ^/app\.php(/|$)
nginx's
$uri
parameter used for location matching gets updated to
/app.php
, so any nested locations won't work.

I cant use
add_header
outside of the front controller block because any
add_header
directives get dropped on an internal redirect.

Obviously I can't use
location if
with
add_header
either.

This is easy in apache, but the only remote solution I have found uses a third party lua module and the installation docs are a bit thin on that and the thought of compiling that in from source on centos is giving me heart palpitations.

Answer Source

If internal redirect bother us, lets remove internal redirect :) You can solve it easy with fastcgi config duplication

server {
    listen 80;

    root /opt/my/code/web;
    index app.php;
    charset utf-8;

    location / {
        try_files $uri $uri/ /app.php$is_args$args;
    }

    location ~ ^/recording {
        add_header Content-Type audio/x-wav;
        fastcgi_pass unix:/var/run/php-fpm.sock;
        include /etc/nginx/fastcgi_params;
        fastcgi_param SCRIPT_NAME /app.php;
        fastcgi_param SCRIPT_FILENAME $document_root/app.php;
    }    

    # pass the PHP scripts to php5-fpm
    location ~ ^/app\.php(/|$) {
        fastcgi_split_path_info ^(.+?\.php)(/?.*)$;
        fastcgi_pass unix:/var/run/php-fpm.sock;
        fastcgi_index app.php;
        include /etc/nginx/fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;

        # Prevents URIs that include the front controller. This will 404:
        internal;
    }

    # return 404 for all other php files not matching the front controller
    location ~ \.php$ {
        return 404;
    }
}

Second solution works only if you know content type of all other requests. We can use variables. (btw, I don't suggest this solution because harder to support and not cute :))

server {
    listen 80;

    root /opt/my/code/web;
    index app.php;
    charset utf-8;

    location / {
        try_files $uri $uri/ /app.php$is_args$args;
        set $ct "text/html";
    }

    location ~ ^/recording {
        try_files $uri $uri/ /app.php$is_args$args;
        set $ct "audio/x-wav";
    }    

    # pass the PHP scripts to php5-fpm
    location ~ ^/app\.php(/|$) {
        fastcgi_split_path_info ^(.+?\.php)(/?.*)$;
        fastcgi_pass unix:/var/run/php-fpm.sock;
        fastcgi_index app.php;
        include /etc/nginx/fastcgi_params;
        fastcgi_param DOCUMENT_ROOT $realpath_root;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;

        add_header "Content-Type $ct;

        # Prevents URIs that include the front controller. This will 404:
        internal;
    }

    # return 404 for all other php files not matching the front controller
    location ~ \.php$ {
        return 404;
    }
}