Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Nginx return statement not accepting "text"

Tags:

nginx

Following config is working for me:

server {
  listen 80;

  root /app/web;
  index index.json;

  location / {
    return 409;
  }
}

If I hit the website the 409 page will be presented. However following is not working:

server {
  listen 80;

  root /app/web;
  index index.json;

  location / {
    return 409 "foobar";
  }
}

The page is unreachable. But according to the docs http://nginx.org/en/docs/http/ngx_http_rewrite_module.html#return

return 409 "foobar";

should work. Any ideas whats wrong? There are no logs in nginx/error.log.

like image 994
UpCat Avatar asked Dec 11 '22 23:12

UpCat


2 Answers

The thing is, Nginx does exactly what you ask it to do. You can verify this by calling curl -v http://localhost (or whatever hostname you use). The result will look somewhat like this:

* Rebuilt URL to: http://localhost/
* Hostname was NOT found in DNS cache
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 80 (#0)
> GET / HTTP/1.1
> User-Agent: curl/7.35.0
> Host: localhost
> Accept: */*
> 
< HTTP/1.1 409 Conflict
* Server nginx/1.4.6 (Ubuntu) is not blacklisted
< Server: nginx/1.4.6 (Ubuntu)
< Date: Fri, 08 May 2015 19:43:12 GMT
< Content-Type: application/octet-stream
< Content-Length: 6
< Connection: keep-alive
< 
* Connection #0 to host localhost left intact
foobar

As you can see, Nginx returns both 409 and foobar, as you ordered.

So the real question here is why your browser shows the pretty formatted error page when there is no custom text after the return code, and the gray "unreachable" one, when such text is present.

And the answer is: because of the Content-Type header value.

The HTTP standard states that some response codes should or must come with the response body. To comply with the standard, Nginx does this: whenever you return a special response code without the required body, the web server sends its own hardcoded HTML response to the client. And a part of this response is the header Content-Type: text/html. This is why you see that pretty white error page, when you do return 409 without the text part — because of this header your browser knows that the returned data is HTML and it renders it as HTML.

On the other hand, when you do specify the text part, there is no need for Nginx to send its own version of the body. So it just sends back to the client your text, the response code and the value of Content-Type that matches the requested file (see /etc/nginx/mime.types).

When there is no file, like when you request a folder or a site root, the default MIME type is used instead. And this MIME type is application/octet-stream, which defines some binary data. Since most browsers have no idea how to render random binary data, they do the best they can, that is, they show their own hardcoded error pages.

And this is why you get what you get.

Now if you want to make your browser to show your foobar, you need to send a suitable Content-Type. Something like text/plain or text/html. Usually, this can be done with add_header, but not in your case, for this directive works only with a limited list of response codes (200, 201, 204, 206, 301, 302, 303, 304, or 307).

The only other option I see is to rewrite your original request to something familiar to Nginx, so that it could use a value from /etc/nginx/mime.types for Content-Type:

server {
    listen 80;

    root /app/web;
    index index.json;

    location / {
        rewrite ^.*$ /index.html;
        return 409 "foobar";
    }
}

This might seem somewhat counter-intuitive but this will work.

EDIT:

It appears that the Content-Type can be set with the default_type directive. So you can (and should) use default_type text/plain; instead of the rewrite line.

like image 134
Ivan Tsirulev Avatar answered Jan 01 '23 14:01

Ivan Tsirulev


Updating @ivan-tsirulev 's answer:

By now you can set headers even for page with status codes for errors using always.

location @custom_error_page {
    return 409 "foobar";
    add_header Content-Type text/plain always;
}

But if you set default_type, the response headers will have two Content-Type headers: default, then added. Nevertheless, it works fine.

like image 43
Nick Veld Avatar answered Jan 01 '23 14:01

Nick Veld