Score:0

Nginx returns 415 when using image_filter with webp

za flag

I have some jpg/png files that get resized in a location (with image_filter module) and it's working fine. But, I have also a webp version of some images and I want to serve the webp one if it exists. If not, the original jpg/png image should be served.

I'm using the following configuration:

map $http_accept $webp_suffix {
    default        "";
    "~image/webp"  "webp";
}

location ~ "/@s/(.*)(png|jpe?g)" {
    alias                       $BASE_PATH/$1;
    try_files                   $webp_suffix $2 $uri;

    image_filter                resize 1200 -;
    image_filter_jpeg_quality   80;
    image_filter_buffer         10M;
}

But nginx returns a 415 Unsupported Media Type error when the webp version is found. If the webp file is missing, it serves the jpg/png file without any error. The Nginx version is 1.16.1.

Ivan Shatsky avatar
gr flag
Does this answer your question? [NGINX: map and try\_files not working](https://serverfault.com/questions/1036804/nginx-map-and-try-files-not-working)
Ivan Shatsky avatar
gr flag
Besides you are using the `try_files` directive incorrectly, there is one more caveat using `map` derived variables inside the regular expression location with the numbered capture groups. An explanation and a working example are given here: [NGINX: map and try_files not working](https://serverfault.com/questions/1036804/nginx-map-and-try-files-not-working).
Erfun avatar
za flag
@IvanShatsky I changed the numeric variables to named variables but I see no difference. Still getting `415 Unsupported Media Type`.
us flag
It might be that `image_filter` reads the filename from `$uri` variable, and it thinks the image is JPEG.
Erfun avatar
za flag
@TeroKilkanen As described in the docs, the WEBP should be supported in image_filter but for some reason, it doesn't work.
us flag
I guess it supports the WEBP format properly when the actual request URL contains `.webp` extension. In your configuration, the request URL is `.png` or `.jpg`, whichever is the original extension. Therefore `image_filter` tries to access the image using wrong decoder. It might not be possible to combine the mapping and `image_filter` modules.
drboczek avatar
dk flag
do you resolved your problem with nginx and webp? I have the same problem :) centos 7 too, epel nginx 1.20 i trying nginx repo with nginx 1.21 but no effect.
Erfun avatar
za flag
@drboczek Unfortunately no, I couldn't. Instead, I used our CDN provider WEBP conversion feature which is available on Cloudflare too.
Score:0
us flag

A more streamlined configuration for this is:

map $http_accept $webpuri {
    ~image/webp    $uri.webp;
    default        "";
}

location ~ \.png|jpe?g$ {
    try_files    $webpuri $uri;
    ...
}

Your root directive is used here to get the full path to files on your filesystem.

Erfun avatar
za flag
I don't get where is the difference. I used `alias` instead of `root` for addressing the file path on the filesystem.
us flag
The code above does not use regular expressions to split the URI into filename base part and extension, which is quite hacky. Instead we take the full file path and add `.webp` after the whole path.
Erfun avatar
za flag
I think the `image_filter` caused the problem, not the regular expression. If I remove the image_filter, my own code works. So obviously there is nothing wrong with addressing to the file.
Score:0
gr flag

Not an answer, but too long to be a comment.

@TeroKilkanen made an assumption:

It might be that image_filter reads the filename from $uri variable, and it thinks the image is JPEG.

Lets check out if it is true. Fortunately, nginx is an open source software and the source code of ngx_http_image_filter_module is available here.

The main ngx_http_image_body_filter function started at line 291. Let's look at the beginning:

    ...
    switch (ctx->phase) {

    case NGX_HTTP_IMAGE_START:

        ctx->type = ngx_http_image_test(r, in);

        conf = ngx_http_get_module_loc_conf(r, ngx_http_image_filter_module);

        if (ctx->type == NGX_HTTP_IMAGE_NONE) {

            if (conf->filter == NGX_HTTP_IMAGE_SIZE) {
                out.buf = ngx_http_image_json(r, NULL);

                if (out.buf) {
                    out.next = NULL;
                    ctx->phase = NGX_HTTP_IMAGE_DONE;

                    return ngx_http_image_send(r, ctx, &out);
                }
            }

            return ngx_http_filter_finalize_request(r,
                                              &ngx_http_image_filter_module,
                                              NGX_HTTP_UNSUPPORTED_MEDIA_TYPE);
        }
        ...

We see it is the ngx_http_image_test function that is responsible for the decision about image validity. Let's look at that function (started at line 423):

static ngx_uint_t
ngx_http_image_test(ngx_http_request_t *r, ngx_chain_t *in)
{
    u_char  *p;

    p = in->buf->pos;

    if (in->buf->last - p < 16) {
        return NGX_HTTP_IMAGE_NONE;
    }

    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "image filter: \"%c%c\"", p[0], p[1]);

    if (p[0] == 0xff && p[1] == 0xd8) {

        /* JPEG */

        return NGX_HTTP_IMAGE_JPEG;

    } else if (p[0] == 'G' && p[1] == 'I' && p[2] == 'F' && p[3] == '8'
               && p[5] == 'a')
    {
        if (p[4] == '9' || p[4] == '7') {
            /* GIF */
            return NGX_HTTP_IMAGE_GIF;
        }

    } else if (p[0] == 0x89 && p[1] == 'P' && p[2] == 'N' && p[3] == 'G'
               && p[4] == 0x0d && p[5] == 0x0a && p[6] == 0x1a && p[7] == 0x0a)
    {
        /* PNG */

        return NGX_HTTP_IMAGE_PNG;

    } else if (p[0] == 'R' && p[1] == 'I' && p[2] == 'F' && p[3] == 'F'
               && p[8] == 'W' && p[9] == 'E' && p[10] == 'B' && p[11] == 'P')
    {
        /* WebP */

        return NGX_HTTP_IMAGE_WEBP;
    }

    return NGX_HTTP_IMAGE_NONE;
}

I think it is pretty clear that the above function analyze first 16 bytes of the buffer trying to find one of the four known signatures. So the problem isn't related to the $uri variable value.

What can be the cause? Well, ngx_http_image_filter_module documentation says the following:

This module utilizes the libgd library. It is recommended to use the latest available version of the library.

The WebP format support appeared in version 1.11.6. To transform images in this format, the libgd library must be compiled with the WebP support.

Maybe the problem is in your nginx build. Check the WebP and image_filter compatibility without any additional URI transformations, something like

location ~ \.webp$ {
    image_filter                resize 1200 -;
    image_filter_jpeg_quality   80;
    image_filter_buffer         10M;
}

and then requesting the existing WebP file explicitly. If you'll still get a 415 Unsupported Media Type error, the problem is most likely in your nginx build.

Erfun avatar
za flag
I had tested the `image_filter` with `webp` and the same `415 Unsupported Media Type` problem existed. So there is likely a problem with `libgd`. I had installed my `nginx` from the official yum repository. Should I build it manually with any custom configuration?
Ivan Shatsky avatar
gr flag
What linux distro are you using?
Erfun avatar
za flag
It's a Centos 7 server.
Ivan Shatsky avatar
gr flag
And one more question, what repo are you using? Is it nginx official repository or EPEL or something else?
Erfun avatar
za flag
It's the EPEL repo.
mangohost

Post an answer

Most people don’t grasp that asking a lot of questions unlocks learning and improves interpersonal bonding. In Alison’s studies, for example, though people could accurately recall how many questions had been asked in their conversations, they didn’t intuit the link between questions and liking. Across four studies, in which participants were engaged in conversations themselves or read transcripts of others’ conversations, people tended not to realize that question asking would influence—or had influenced—the level of amity between the conversationalists.