Re: proxy cache key и fastcgi cache key

Gena Makhomed gmm at csdoc.com
Sat Jan 11 10:34:35 UTC 2014


On 11.01.2014 1:51, Валентин Бартенев wrote:

>>>> с RFC то как раз все в порядке: "network location of the URI
>>>> (authority) MUST be transmitted in a Host header field",
>>>> только вот nginx не соответствует этим требованиям...
>>>>
>>>> http://tools.ietf.org/search/rfc2616#section-5.1.2
>> >
>>> Если почитать внимательнее, то приведенные требования относятся к клиенту.
>>> Понятно, что сервер в принципе не может влиять на то, что transmitted в
>>> запросе.
>>
>> nginx выступает в роли сервера только в том случае,
>> когда он самостоятельно обслуживает клиентский запос.
>>
>> В тот момент, когда nginx делает http запрос к удаленному
>> серверу он выступает в роли клиента. поэтому я и цитировал 5.1.2
>
> С точки зрения удаленного сервера да.  И при этом поступает совершенно
> корректно, а именно передает "network location of the URI (authority)"
> в заголовки Host.

В том-то и дело, что в настройке по-умолчанию он этого не делает:

server { server_name example.com; proxy_pass http://127.0.0.1/; }

Хотя nginx и определил, что имя хоста example.com,
на backend в заголовке Host: запроса он отправляет
совсем другое значение, в данном случае: 127.0.0.1

Очень наивно отправлять в заголовке Host: 127.0.0.1
клиентского запроса к своему upstream`у и ожидиать,
что в ответ придет контент для хоста example.com

То же самое касается и работы по протоколу FastCGI.
nginx верно определил, что имя виртуального хоста good-site.com
но на backend отправил в запросе совсем другу инфу bad-site.com

>> например, если исходный запрос от клиента к nginx был
>>
>> GET http://good-site.com/pub/WWW/TheProject.html HTTP/1.1
>> Host: bad-site.com
>>
>> содержимое заголовка Host: согласно 5.2.1 должно игнорироваться,
>> адрес хоста в этом случае: good-site.com
>>
>> а согласно требований 5.1.2 - network location of the URI (authority)
>> MUST be transmitted in a Host header field, то есть исходящий запрос
>> должен быть
>>
>> GET /pub/WWW/TheProject.html HTTP/1.1
>> Host: good-site.com
>>
>> nginx же в настройке по-умолчанию не соответсвует RFC,
>> и вместо требуемого значения пишет в заголовок Host:
>> значение переменной $proxy_host
>
> Поздравляю. Это самое необычное толкование RFC 2616, которое я когда либо
> встречал.
>
> К счастью оно разбивается об определение терминов client и server из него же:
>
>     client
>        A program that establishes connections for the purpose of sending
>        requests.
>
>     server
>        An application program that accepts connections in order to
>        service requests by sending back responses. Any given program may
>        be capable of being both a client and a server; our use of these
>        terms refers only to the role being performed by the program for a
>        particular connection, rather than to the program's capabilities
>        in general. Likewise, any server may act as an origin server,
>        proxy, gateway, or tunnel, switching behavior based on the nature
>        of each request.
>
> ключевой момент тут: "refers only to the role being performed by the program
> for a particular connection".

 > Соединение между клиент->nginx и соединение nginx->upstream - это два
 > разных соединения, и в каждом из них nginx играет исключительно одну
 > единственную роль, применимую только к конкретному соединению.

Все верно. В particular connection между браузером и nginx
- nginx выступает в роли сервера, и поэтому там требования из 5.2
а в particular connection между nginx и backend`ом - nginx выступает
в роли клиента и поэтому здесь он обязан выполнять требования 5.1.2

> В соединении "клиент->nginx" не играет роль клиента и клиентские требования
> RFC 2616 в данном соединении к нему не применимы.

Клиентские требования из п.5.1.2 RFC 2616
применимы к nginx в своединении nginx->upstream

Внутри какого-то server { ... } - nginx определил,
имя виртуального хоста (authority) соответственно
именно это имя виртуального хоста он MUST передать
в заголовке Host: при запросе к upstream серверу.

Только таким способом, с помощью заголовка Host:
upstream сможет отличить один виртуальный сервер
от другого и дать своему клиенту верный ответ.

Это же очевидно, если кто-то хочет получить в ответ
страницу с сайта гугла, то в заголовке Host: своего
запроса он обязан отправить именно "google.com".
А если отправить там "microsoft.com" - то ничего
работать не будет и так делать нельзя, см. 5.1.2

Тут у нас точно такая же ситуация,
только в роли клиента выступает nginx.

>> Теперь рассмотрим вариант связи с backend`ом по протоколу FastCGI,
>> но поскольку у нас большой и сложный сайт сделаем два фронтенда:
>>
>> 1) основной nginx frontend
>> 2) nginx frontend на хосте backend`а
>> 3) backend, работающий по протоколу FastCGI.
>>
>> запрос от клиента проходит цепочку (1)->(2)->(3).
>> поскольку между (1) и (2) используется протокол http,
>> то согласно требований 5.1.2 и 5.2.1 на (2) запрос приходит
>> в виде
>>
>> GET /pub/WWW/TheProject.html HTTP/1.1
>> Host: good-site.com
>>
>> не смотря на то, что в исходном запросе
>> от клиента был игнорируемый заголовок Host: bad-site.com
>>
>> а дальше - все просто. В соответствии с требованиями
>> спецификации протокола FastCGI - nginx записывает в переменную
>> HTTP_HOST значение good-site.com.
>>
>> точнее, он ДОЛЖЕН так делать, согласно требований HTTP протокола
>> прямо из коробки, без какой-либо дополнительной настройки.

> В спецификации нет требования, согласно котором сервер из коробки
> должен быть клиентом и отправлять запросы куда-то дальше.

Такого требования действительно нет. И запросы клиентов к статике
никуда дальше nginx не уходят, он их обрабатывает самостоятельно.
Кстати, именно по этой причине nginx и называется веб-сервером.

Но в том случае, когда nginx, как http клиент делает запросы к своему
upstream серверу по протоколу HTTP/1.1 - он обязан выполнять требования 
RFC 2616 в частности http://tools.ietf.org/search/rfc2616#section-5.1.2

> Так что дополнительная настройка все равно потребуется, и я так
> понимаю, исходя из описания, она была какой-то такой:
>
>      location {
>          proxy_pass http://good-site.com;
>      }

Что именно и как именно писать в конфигах nginx - это его личное дело,
требования к оформлению конфигов изложены только в документации к nginx.

Мы сейчас о другом аспекте говорим. О том, как работает протокол HTTP.
Если nginx хочет получит от backend`а ответ для виртуального хоста
good-site.com - тогда он обязан в заголовке Host отправить именно
good-site.com и он не имеет права отправлять там 127.0.0.1 и т.п.

Это то, что касается работы name-based virtual host`ов.
Если же upstream у nginx не name-based virtual host,
а IP-based virtual host - тогда можно и не отправлять
заголовок Host: или писать там все что угодно, backend
всеравно проигнорирует этот заголовок и даст ответ для good-site.com
даже если в заголовке Host: запроса было 127.0.0.1

>> если в случае отсутствия промежуточных серверов nginx ведет себя
>> не так, - то это BUG, ибо в случае, когда на nginx frontend
>> приходит запрос в виде absoluteURI, - тогда "Any Host header
>> field value in the request MUST be ignored".
>> nginx этого по каким-то причинам не делает.
>
> Почему не делает?  Процитированная фраза относится к rules из
> фразы которые находятся по общим заголовком из:
>
>     An origin server that does differentiate resources based on the host
>     requested (sometimes referred to as virtual hosts or vanity host
>     names) MUST use the following rules for determining the requested
>     resource on an HTTP/1.1 request:
>
> именно таким правилом и пользуется nginx _при выборе_ виртуального хоста.

Если nginx _выбрал_ виртуальный хост good-site.com
зачем после выбора он резко меняет свое мнение и начинает
делать запросы к backend`у на виртуальный хост bad-site.com ?

Каким образом это согласуется со здравым смыслом?

Если пользователь nginx написал в конфиге nginx:

server {
     server_name good-site.com;
     // ...
}

И nginx выбрал этот name-based виртуальный хост, с какой радости
тогда на backend уходит запрос для виртуального хоста bad-site.com ?

В конфиге рядом есть отдельный

server {
     server_name bad-site.com;
     // ...
}

И пользователь nginx хочет, чтобы все запросы к bad-site.com
nginx обрабатывал именно в этом блоке server.

Сейчас же - вполне возможна такая ситуация, что запрос клиента
nginx со своей стороны будет обрабатывать как виртуальный хост
good-site.com, но на backend он отправит запрос к bad-site.com
и наоборот. Это - BUG. Workaround: proxy_set_header Host $host;

Действительно, в RFC 2616 про это ничего не написано,
но разве здравый смысл не подсказывает, что все запросы
к name-based виртуальному хосту good-site.com backend`а
должны происходить исключительно из блока server
который соответствует server_name good-site.com ?

И если nginx ведет себя не так,
то разве не очевидно, что это BUG ?

Например, запрос клиента попал в блок

server {
     server_name good-site.com;
     // ...
}

при этом в переменную $host записано значение good-site.com.

Если вдруг внутри этого server`а nginx начнет отдавать
статику от совсем другого виртуального хоста, например,
от bad-site.com - это ведь будет BUG, верно?

Аналогично это BUG и в случае с динамикой.

>> хотя согласно требований RFC - обе эти формы записи:
>>
>> GET http://good-site.com/pub/WWW/TheProject.html HTTP/1.1
>>
>> и
>>
>> GET /pub/WWW/TheProject.html HTTP/1.1
>> Host: good-site.com
>>
>> полностью эквивалентны между собой. и nginx имеет
>> полное право и даже обязанность трансформировать
>>
>> запрос с absoluteURI в запрос с relativeURI
>> и network location of the URI (authority)
>> MUST be transmitted in a Host header field.

> Как сервер он такой обязанности не имеет, а в данном конкретном соединении,
> как я уже пояснил вначале, он выступает в роли сервера и никакой другой.

nginx выступает и в роли сервера и в роли клиента,
в зависимости от particular connection. Такую обязанность
он имеет как http клиент при совершении запросов к upstream.

> Сервер вообще не обязан делать запросы и что-то куда-то transmitted.

http клиент обязан выполнять требования RFC 2616 -
http://tools.ietf.org/search/rfc2616#section-5.1.2

>> Та часть nginx, которая работает в режиме сервера определяет Host
>> правильно. И правильно сохранет его в свою переменную $host.
>>
>> Но дальше в нарушение RFC вместо $host зачем-то используется $http_host
>> не смотря на прямой запрет: Any Host header field value in the request
>> MUST be ignored.
>>
>> А несоответствие требованиям RFC 2616 - это ведь BUG, верно?
>
> Поскольку мысль повторена по меньшей мере 3 раза, то и в третий раз,
> на всякий случай: на ту часть nginx, которая работает в режиме сервера,
> не распространяется клиентских требований RFC 2616, что в самом же RFC 2616
> прописано, а именно роли клиента и сервера взаимоисключающие, и закреплены
> для каждого индивидуального соединения.

Роли клиента и сервера взаимоисключающие
только for a particular connection.

При запросе клиент->nginx - он сервер,
а при запросе nginx->upsteam - он клиент.

Если nginx знает что это name-based virtual host с именем good-site.com
он MUST NOT обманывать upstream server и говорить, что это bad-site.com

-- 
Best regards,
  Gena



Подробная информация о списке рассылки nginx-ru