NGINX: gzip и ETag weak validation

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

ETag

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

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

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

[simterm]

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

[/simterm]

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

[simterm]

$ 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"

[/simterm]

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

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

[simterm]

$ 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

[/simterm]

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

[simterm]

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

[/simterm]

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

[simterm]

$ 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

[/simterm]

gzip и weak validation

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

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

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

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

[simterm]

# 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

[/simterm]

Вот – 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;
}

Проверяем:

[simterm]

# 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

[/simterm]

Теперь в 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“, и был размером:

[simterm]

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

[/simterm]

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

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