NGINX: gzip и ETag weak validation

Автор: | 09/20/2018
 

ETag

Собственно сам ETag является идентификатором ресурса, запрошенного клиентом (браузером): если изменений нет, то etag не меняется, если изменения были — изменится и его etag.

См. тут>>>тут>>> и тут>>>.

Пример его работы — проверяем URL:

curl -I etag.rtfm.co.ua
HTTP/1.1 200 OK
Server: nginx/1.10.3
...
ETag: "5ba22fcb-6"
Accept-Ranges: bytes

Теперь добавляем --header If-None-Match, в котором передаём значение из полученного ETag — и NGINX вернёт нам код 304 Not Modified:

curl -IH 'If-None-Match: "5ba22fcb-6"' etag.rtfm.co.ua
HTTP/1.1 304 Not Modified
Server: nginx/1.10.3
...
ETag: "5ba22fcb-6"

В таком случае браузер использует закешированное содержимое вместо того, что бы получить всё содержимое с сервера.

Если же ETag будет другим — получим код 200, и всё содержимое index.html будет передано самим NGINX:

curl -IH 'If-None-Match: "blabla"' etag.rtfm.co.ua
HTTP/1.1 200 OK
Server: nginx/1.10.3
...
ETag: "5ba22fcb-6"
Accept-Ranges: bytes

Если же файл будет изменён:

root@ip-172-31-32-251:/etc/nginx# echo index2 > /var/www/etag/index.html

А в браузере отправить прежнее значение ETag — то NGINX передаст код 200, и всё содержимое файла с новым значением ETag:

curl -IH 'If-None-Match: "5ba22fcb-6"' etag.rtfm.co.ua
HTTP/1.1 200 OK
Server: nginx/1.10.3
...
ETag: "5ba232f9-7"
Accept-Ranges: bytes

gzip и weak validation

Для ETag имеется два типа валидации контента — strong validation, которая работает по умолчанию, и weak validation.

По ETag и strong/weak валидацию см. тут>>> и тут>>>.

Собственно проблема возникла из-за модификатора W/, который добавляется NGINX при использовании weak validation — мобильное приложение игнорировало etag и каждый раз выкачивало видео-контент заново.

Выглядит он так:

curl -I -H "Accept-Encoding: gzip,deflate" etag.rtfm.co.ua/index.html
HTTP/1.1 200 OK
Server: nginx/1.10.3
...
ETag: W/"5ba367ff-264"
Content-Encoding: gzip

Вот — ETag: W/"5ba367ff-264".

А возникает проблема из-за включенного gzip — заголовок Content-Encoding: gzip.

На нашем старом production-сервере всё работает, а на новом — кешировение не работает, и как показал «анализ» (grep -r /etc/nginx, ага) конфигов NGINX — из-за того, что в конфигах виртуалхостов старого сервера gzip был отключен явно, а на новом — нет.

Собственно, решение — отключить gzip для виртуалхоста:

server {

    listen 80;
    server_name etag.rtfm.co.ua;

    gzip off;

    root /var/www/etag;
    index index.html;
}

Проверяем:

curl -I -H "Accept-Encoding: gzip,deflate" etag.rtfm.co.ua/index.html
HTTP/1.1 200 OK
Server: nginx/1.10.3
...
ETag: "5ba367ff-264"
Accept-Ranges: bytes

Теперь в ETag нет W/, и всё работает.

Либо можно его выключить прямо в nginx.conf (просто убрать gzip on;), тем более EC2 с NGINX работает за AWS Application Load Balancer, на котором к тому же есть SSL (см. почему не стоит использовать gzip и SSL — см. тут>>>).

Обсуждение и описание фикса, который добавляет weak validation для gzipped-данных есть тут>>>.

«Bonus»: gzip_min_length и документация

Если обратить внимание на примеры с curl в начале поста — то там нет gzip и W/ в etag. И это было ОК, пока я не дошёл до момента, когда надо было показать пример того, как выглядит weak validation в ответе, и надо было включить gzip — хотя он включен по умолчанию и, казалось бы, должен был отдаваться сразу.

Но т.к. примеры приводились с тестового хоста, с тестовыми файлами, то возникла интересная проблема: как я ни старался включить gzip, что бы увидеть его в response headers — он не срабатывал.

Причина оказалась банальна — надо внимательнее читать документацию к модулю, в которой сказано:

Syntax: gzip_min_length length;
Default:
gzip_min_length 20;

Тогда как файл index.html на тестовом хосте содержал одно слово — «index2«, и был размером:

root@ip-172-31-41-252:/etc/nginx# stat -c %s /var/www/etag/index.html
7

7 байт — и gzip его игнорировал.

Решение — явно указать gzip_min_length 0; в конфиге.