slimphp / Slim

Slim is a PHP micro framework that helps you quickly write simple yet powerful web applications and APIs.
http://slimframework.com
MIT License
11.98k stars 1.95k forks source link

ngnix sets Content-type even though \Slim\App finalize removes it #1612

Closed rbairwell closed 8 years ago

rbairwell commented 8 years ago

During testing, I found that I was getting a "Content-Type: text/html; charset=UTF-8" header being sent when I was creating a 204 No Content page (in Slim 3 RC 2 within \Slim\App::finalize and \Slim\App::isEmptyResponse it removes the Content-length and Content-type headers if the response code is 204, 205 or 304).

Obviously, this isn't a fault in Slim "per-se" but an unexpected "feature" of nginx/1.8.0 running under fastcgi. I have managed to remove the header myself using an empty Content-type header setting like:

header('Content-type:',true);

would it be an idea of Slim to "sniff" for nginx/fastcgi and do the same if detected (I haven't been able to test this "fix" on other web servers to see if they will just strip out the empty Content-type header or not).

geggleto commented 8 years ago

are you sure that you are not writing to the browser?

If you say do a print "" or echo "" it will over write the content type header.

rbairwell commented 8 years ago

Yep, I'm, sure. I've just done new test case which consists of a single file:

<?php
// file /var/www/test/index.php
require __DIR__.'/vendor/autoload.php';

$app=new \Slim\App();
$app->get('/204test',function ($request,$response,$arg) {
    $response = $response->withStatus(204, 'No Content');
    return $response;
});
$app->run();

with my nginx configuration being:

server {
    listen 88;
    server_name "";
    root   /var/www/test/;

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

    include php.conf;

}

and /etc/nginx/php.config being:

location ~ \.php {
        include                  fastcgi_params;
        fastcgi_keep_conn on;
        fastcgi_index            index.php;
        fastcgi_split_path_info  ^(.+\.php)(/.+)$;
        fastcgi_param PATH_INFO $fastcgi_path_info;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_intercept_errors on;
        fastcgi_pass             unix:/var/run/php-fpm.sock;
    }

and a test curl transaction is:

vagrant@host:/vagrant/project$ curl --raw http://127.0.0.1:88/204test -v
* Hostname was NOT found in DNS cache
*   Trying 127.0.0.1...
* Connected to 127.0.0.1 (127.0.0.1) port 88 (#0)
> GET /204test HTTP/1.1
> User-Agent: curl/7.38.0
> Host: 127.0.0.1:88
> Accept: */*
>
< HTTP/1.1 204 No Content
* Server nginx/1.8.0 is not blacklisted
< Server: nginx/1.8.0
< Date: Tue, 24 Nov 2015 16:47:22 GMT
< Content-Type: text/html; charset=UTF-8
< Connection: keep-alive
< X-Powered-By: PHP/7.0.0-dev
<
* Connection #0 to host 127.0.0.1 left intact
geggleto commented 8 years ago

Also Slim's response is initialized with a Content-Type header ... https://github.com/slimphp/Slim/blob/3.x/Slim/Container.php#L132-L137

OR

I believe nginx might be adding the content-type header because of this little nugget...

The 204 response MUST NOT include a message-body, and thus is always terminated by the first empty line after the header fields.

Your browser would lose the extra headers after content-type if it were broken.

You can test this by manually adding a Content-Type header to your response. NGinx should let your's go through.

rbairwell commented 8 years ago

I've amended the test script to add a

$response=$response->withHeader('Content-type','text/plain');

but if the status code is 204, Slim (in App->finalize) still strips it out: causing ngnix to add the text/html Content-type back in (I've even tried add body content to see if it makes a difference: it doesn't) [changing the status code to 200 does give my text/plain content type and the body].

Changing App->finalize to:

 if ($this->isEmptyResponse($response)) {
            return $response->withoutHeader('Content-Type')->withoutHeader('Content-Length')->withHeader('Content-Type','');
}

did work (i.e. no Content-Type header was actually sent by nginx).

(as an aside, /etc/nginx/nginx.conf has application/octet-stream as the default_type )

akrabat commented 8 years ago

I'm confused. The spec for 204 states:

The 204 response MUST NOT include a message-body, and thus is always terminated by the first empty line after the header fields.

Surely that means that there cannot be a Content-Type or a Content-Length header?

geggleto commented 8 years ago

That is my understanding as well.

rbairwell commented 8 years ago

@akrabat That's how I read it as well. I believe nginx is trying to be "helpful" and if there is absolutely no Content-type header set, it is adding one - however, if a Content-type header is set, but empty, "it trust you know what you are doing" but won't send the blank content-type (but also will not add its own content-type)

akrabat commented 8 years ago

Right, so really we should look at seeing if we can let you manipulate the response after finalize() has run.

rbairwell commented 8 years ago

Just doing some tests on an Apache server and the inbuilt PHP server with a very very basic script:

It's looking more like, to achieve what Slim is trying to do by unsetting the Content-Type header, it is better off setting a blank content header (similar to "header('Content-Type:');") which will stop the content-type line being sent on all three tested servers. Counter intuitive, but works on two of the major web servers and the inbuilt one. Is there a platform where this "hack" doesn't work?

akrabat commented 8 years ago

This is happening because of the php.ini default_mimetype setting:

By default, PHP will output a character encoding using the Content-Type header. To disable sending of the charset, simply set it to be empty.

akrabat commented 8 years ago

Fixed by #1629.