For my sites I typically have two server{}
blocks: one serves the site, and the other serves static content for that site from a different domain. Because the favicon.ico
file is always asked for from the primary domain instead of the static domain, I have to handle that specific file in the site’s server{}
block rather than the static file block.
When a favicon.ico
file is not found, Nginx returns my define 404 error page, as well as the 404 HTTP status code. However, I don’t want to send those bits for my pretty 404 page. I want to send a totally empty response with a 204 status code. Below is what I tried, however it does not work. Is there a correct way to do this? The idea being that a 204 would indicate a found file, however a totally blank image.
Also, is my attempt to save bits actually a bad idea? If, in fact, returning 204 instead of 404 is a bad idea, is there a more elegant way to return an empty 404 page without creating a new-and-actually-empty file and setting that as the error_page
directive inside that location block?
server {
...
error_page 404 /../static/404.html;
location @return_204 {
return 204;
}
# Case-insensitive matching of .txt and .xml files.
# (Example: robots.txt, crossdomain.xml, and sitemap.xml)
location ~* .(txt|xml)$ {
try_files $uri /../static/$uri;
}
location = /favicon.ico {
log_not_found off;
error_page 404 = @return_204;
try_files $uri /../static/$uri;
}
...
}
BNAME.RU » Код HTTP запроса 204 No Content
Что означает код 204 No Content?
Запрос обработан успешно, но возвращать данные не требуется. Также новая или обновленная информация может быть возвращена в ответе, но в итоге она не будет отличаться относительно того, что было первоначально отправлено на сервер и, таким образом, считается, что клиент уже имеет актуальную информацию. Если клиент является браузером, то он не должен изменять отображение документа, а его состояние до и после отправки запроса не должно изменяться. Этот статус ответа в основном используется для указания успешности запроса, поскольку данные и ракурс данных не должны изменяться, даже если (или с учетом) новая (обновленная информация) уже отображается в ракурсе документа. 204 — не должно содержать тело ответа. Если он есть, он обычно игнорируется, и одна пустая строка считается присутствующей после заголовков.
I too ran into this and after hours of hitting the walls, here is what now hopefully can save someone’s time:
Background We were using python app and one day, we added a reverse proxy in our nginx conf proxy_pass and then started noticing the same calls started responding slower. Initially we were blindly checking the conf and postman, curl stuff and finally figured the pattern that it’s only happening for 204 response codes. Since the same call if was hit using cURL, we were immediately getting the response hence we thought, it’s a bug at postman level or something to do with nginx and postman together.
Towards finding the fix:
1. On requesting, without nginx: ran cURL with -v
this time,
found that the server sends «assume close after body» also notice the content length.
2. On requesting using nginx’s reverse proxy:
the reponse disn’t have any instruction to close and observe th last line «connection left intact», also notice the content length.
This difference was enough to confirm it’s nginx config which needs a fix and found that keepalive_timeout 0;
came as the life savior hack, i.e. after reloading nginx, now postman knows that connection is closed, there is no need to wait more for 204 case.
And more reading revealed that the default keepalive_timeout is 60 seconds hence postman was receiving the signal to close connection and return after a minute (yes I had calculated the time it was taking to return from postman to report the issue to team postman, because the cURL copied from the same postman was returning immidately 🤦♀️)
For reference, Nginx conf looks like this:
location /my_route/ {
proxy_pass <my_domain>:<my_port>/;
proxy_http_version 1.1;
keepalive_timeout 0;
}
I’m still new to nginx and proxies, learned new lessons of API redirection rules against refusing to change from 204 to 200 without understanding the actual reason 😄
Описание кода статуса кода Nginx
Недавно я узнал о коде состояния кода Nginx, вот краткое изложение.Поток обработки http-запроса:
Обычный поток обработки HTTP-запроса, как показано на рисунке выше: A -> клиент инициирует запрос к nginxB -> nginx, затем пересылает запрос в uwsgi и ждет результата C -> uwsgi обрабатывает запрос и возвращает данные в nginxD. -> nginx возвращает результат обработки клиенту. Каждый этап будет иметь предустановленный период тайм-аута. По разным причинам, таким как сеть, загрузка машины, нарушение кода и т. д., если этап не возвращается нормально в течение ожидаемого времени, тогда Это вызовет аномальный запрос, а затем сгенерирует другой код состояния.
1)504504 в основном предназначен для стадий B и C. Общая конфигурация nginx:
location / {
...
uwsgi_connect_timeout 6s;
uwsgi_send_timeout 6s;
uwsgi_read_timeout 10s;
uwsgi_buffering on;
uwsgi_buffers 80 16k;
...
}
Это представляет собой период тайм-аута для связи между nginx и вышестоящим сервером (uwsgi), то есть, если uwsgi не отвечает в течение этого времени, запрос считается истекшим и возвращенным504код состояния.
Конкретный журнал выглядит следующим образом:
access_log
[16/May/2016:22:11:38 +0800] 10.4.31.56 201605162211280100040310561523 15231401463407888908 10.*.*.* 127.0.0.1:8500 "GET /api/media_article_list/?count=10&source_type=0&status=all&from_time=0&item_id=0&flag=2&_=1463407896337 HTTP/1.1" 504 **.***.com **.**.**.39, **.**.**.60 10.000 10.000 "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.71 Safari/537.36" ...
error_log
2016/05/16 22:11:38 [error] 90674#0: *947302032 upstream timed out (110: Connection timed out) while reading response header from upstream, client: 10.6.19.81, server: **.***.com, request: "GET /api/media_article_list/?count=10&source_type=0&status=all&from_time=0&item_id=0&flag=2&_=1463407896337 HTTP/1.1", upstream: "http://127.0.0.1:8500/**/**/api/media_article_list/?count=10&source_type=0&status=all&from_time=0&item_id=0&flag=2&_=1463407896337", host: "mp.toutiao.com", referrer: "https://**.***.com/articles/?source_type=0"
время восходящего потока в error_log out (110: Connection timed out) while reading response header from upstream,
Это означает, что в течение указанного времени данные из заголовка не поступают, то есть uwsgi не возвращает никаких данных.
2)502502 в основном предназначен для стадий B и C. Когда генерируется 502, в соответствующем error_log есть несколько типов содержимого:
access_log
[16/May/2016:16:39:49 +0800] 10.4.31.56 201605161639490100040310562612 2612221463387989972 10.6.19.81 127.0.0.1:88 "GET /articles/?source_type=0 HTTP/1.1" 503 **.***.com **.**.**.4, **.**.**.160 0.000 0.000 "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36" "uuid=x22w:546d345b86ca443eb44bd9bb1120e821x22; tt_webid=15660522398; lasttag=news_culture; sessionid=f172028cc8310ba7f503adb5957eb3ea; sid_tt=f172028cc8310ba7f503adb5957eb3ea; _ga=GA1.2.354066248.1463056713; _gat=1"
error_log
2016/05/16 16:39:49 [error] 90693#0: *944980723 recv() failed (104: Connection reset by peer) while reading response header from upstream, client: 10.6.19.80, server: **.***.com, request: "GET /articles/ HTTP/1.1", upstream: "http://127.0.0.1:8500/**/**/articles/", host: "**.***.com", referrer: "http://**.***.com/new_article/"
Перечислите некоторые общие error_logs, соответствующие 502:
- recv() failed (104: Connection reset by peer) while reading response header from upstream
- upstream prematurely closed connection while reading response header from upstream
- connect() failed (111: Connection refused) while connecting to upstream
- ….
Все это означает, что в течение периода тайм-аута, установленного nginx, восходящий uwsgi не дал правильный ответ (но есть ответ, в противном случае, если нет ответа, он станет тайм-аутом 504), поэтому код состояния на nginx равен 502 .
Как и выше, 503 появляется в access_log, почему?
Это из-за механизма отказоустойчивости восходящего потока nginx. Если nginx имеет следующую конфигурацию:
upstream app_backup {
server 127.0.0.1:8500 max_fails=3 fail_timeout=5s;
server 127.0.0.1:88 backup;
}
- max_fails = 3 означает, что после 3 попыток «server 127.0.0.1:8500» будет считаться недействительным, поэтому введите «server 127.0.0.1:88 backup», то есть доступ к порту 88 машины;
- Механизм отказоустойчивости восходящего потока nginx.По умолчанию Nginx оценивает состояние отказавшего узла по отказу в подключении и тайм-ауту по умолчанию, но эта конфигурация добавляется в расположение: proxy_next_upstream error http_502; proxy_connect_timeout 1s; proxy_send_timeout 6s; proxy_read_timeout 10s; proxy_set_header Host $ хост;
- Эта конфигурация означает, что если статус http равен 502, также будет использоваться восходящий механизм аварийного восстановления;
- Подводя итог, если есть 3 последовательных (max_fails = 3) запроса со статусом 502, задача внутреннего сервера 127.0.0.1:8500 зависнет, и в течение следующих 5 секунд (fail_timeout = 5s) она будет Посетите резервную копию, то есть сервер 127.0.0.1:88, и посмотрите, какой порт 88 соответствует: серверу {listen 88; access_log /var/log/nginx/failover.log; истекает через 1 мин; error_page 500 502 503 504 /500.html; location / {return 503;} location = /500.html {root / ** / ** / ** / nginx / 5xx /;}}
Это означает, что для запроса на доступ к порту 88 nginx вернет код состояния 503, а также вернет файл 500.html по пути / opt / tiger / ss_conf / nginx / 5xx /. Таким образом, 503 отображается в access_log.
3)499После того, как клиент отправит запрос, если он не получит ответа от nginx в течение указанного времени (при условии, что период ожидания составляет 500 мс), считается, что время ожидания запроса истекло и он будет активно завершен. В это время журнал access_log nginx напечатает код состояния 499. A + B + C + D> 500 мс. Фактически, в это время сервер все еще может обрабатывать запрос, но клиент отключился, поэтому результат обработки не может быть возвращен клиенту. Если будет больше 499, это может вызвать лавину обслуживания. Например, клиент инициировал запрос, а клиент по некоторым причинам обрабатывает медленно и не вернул данные в течение указанного времени. Клиент считает запрос неудачным, прерывает запрос, а затем повторно инициирует запрос. Такое постоянное повторение, сервер запрашивает все больше и больше, нагрузка на машину становится больше, обработка запросов становится все медленнее и медленнее, нет возможности отвечать на какие-либо запросы
Официальный сайт резюмирует ситуацию, когда nginx возвращает 499, потому что:
клиент закрыл соединение # Клиент активно закрыл соединение.
клиент закрыл соединение # Клиент активно закрыл соединение.
клиент закрыл соединение # Клиент активно закрыл соединение.
Если вы решите это, вы можете добавить
proxy_ignore_client_abort on;
Есть еще одна причина, действительно, клиент закрыл соединение или время ожидания соединения истекло. Основная причина в том, что количество процессов PHP слишком мало или процесс PHP занят, ресурсы не могут быть быстро освобождены, а запросы накапливаются. Чтобы решить эту ситуацию, вам необходимо оптимизировать программу.
4)500 Внутренняя ошибка сервера, то есть сервер обнаружил непредвиденную ситуацию и не смог выполнить запрос. Есть несколько общих ситуаций, когда возникает ошибка:
- Ошибки веб-скриптов, такие как синтаксические ошибки php, синтаксические ошибки lua и т. Д.
- Когда объем доступа велик, невозможно открыть слишком много дескрипторов файлов из-за ограничений системных ресурсов
Проанализировать причину ошибки
- Посмотреть журнал ошибок nginx и php
- Если открытых файлов слишком много, измените параметр worker_rlimit_nofile в nginx, используйте ulimit для просмотра ограничения открытых файлов в системе, измените /etc/security/limits.conf
- Если есть проблема со скриптом, нужно исправить ошибку скрипта и оптимизировать код
- Все оптимизации выполнены хорошо или появляется слишком много открытых файлов, тогда вам нужно подумать о балансировке нагрузки и распределить трафик по разным серверам.
5)503503 — это статус возврата, что услуга недоступна. В конфигурации nginx установлено ограничение трафика limit_req, в результате чего многие запросы возвращают код ошибки 503. В текущих условиях ограничения, чтобы улучшить взаимодействие с пользователем, я надеюсь вернуться к обычному коду 200 и вернуть информацию о частых операциях:
location /test {
...
limit_req zone=zone_ip_rm burst=1 nodelay;
error_page 503 =200 /dealwith_503?callback=$arg_callback;
}
location /dealwith_503{
set $ret_body '{"code": "V00006", "msg": "Операция выполняется слишком часто, пожалуйста, сядьте и выпейте чашку чая."}';
if ( $arg_callback != "" )
{
return 200 'try{$arg_callback($ret_body)}catch(e){}';
}
return 200 $ret_body;
}
…………………………………………Nginx Code Status………………………….
200: Сервер успешно вернулся на веб-страницу
403: Сервер отклонил запрос.
404: Запрошенная страница не существует
499: Клиент активно отключился.
500: Сервер обнаружил ошибку и не смог выполнить запрос.
502: Сервер действует как шлюз или прокси и получил недопустимый ответ от вышестоящего сервера.
503 - услуга недоступна
504: Сервер действует как шлюз или прокси, но не получил вовремя запрос от вышестоящего сервера.
Эти коды состояния делятся на пять категорий:
100-199 Используется для указания определенных действий, на которые должен отвечать клиент.
200-299 Используется, чтобы указать, что запрос успешен.
300-399 Используется для файлов, которые были перемещены и часто включаются в заголовок позиционирования для указания новой адресной информации.
400-499 Используется для обозначения ошибок клиента. (Проблемы на вашем компьютере) Проблемы на вашем компьютере)
500-599 Используется для поддержки ошибок сервера. (Вопрос другой стороны) Вопрос другой стороны)
---------------------------------------------------------------------------------------------
200 (Успешно) Сервер успешно обработал запрос. Обычно это означает, что запрашиваемую страницу предоставил сервер.
201 (Создано) Запрос был успешным, и сервер создал новый ресурс.
202 (Принято) Сервер принял запрос, но еще не обработал его.
203 (Неавторизованная информация) Сервер успешно обработал запрос, но возвращенная информация может быть получена из другого источника.
204 (Нет содержимого) Сервер успешно обработал запрос, но не вернул никакого содержимого.
205 (Сбросить содержимое) Сервер успешно обработал запрос, но не вернул никакого содержимого.
206 (Частичное содержимое) Сервер успешно обработал несколько запросов GET.
---------------------------------------------------------------------------------------------
300 (Несколько вариантов) В ответ на запросы сервер может выполнять несколько операций. Сервер может быть основан на запросчике (user agent) Выберите операцию или предоставьте список операций для выбора запрашивающей стороны.
301 (Перемещено навсегда) Запрошенная страница была навсегда перемещена в новое место. Когда сервер возвращает этот ответ (ответ на запрос GET или HEAD), он автоматически перенаправляет запрашивающего в новое место.
302 (Временное перемещение) Сервер в настоящее время отвечает на запросы с веб-страниц в разных местах, но запрашивающая сторона должна продолжать использовать исходное расположение для будущих запросов.
303 (См. Другие местоположения.) Когда запрашивающая сторона должна использовать отдельные запросы GET для разных местоположений для получения ответа, сервер возвращает этот код.
304 (Без изменений) Запрошенная веб-страница не изменялась с момента последнего запроса. Когда сервер вернет этот ответ, содержимое веб-страницы не будет возвращено.
305 (Использовать прокси) Запрашивающая сторона может использовать прокси только для доступа к запрошенной веб-странице. Если сервер возвращает этот ответ, это также указывает, что запрашивающая сторона должна использовать прокси.
307 (Временное перенаправление) Сервер в настоящее время отвечает на запросы с веб-страниц в разных местах, но запрашивающая сторона должна продолжать использовать исходное расположение для будущих запросов.
---------------------------------------------------------------------------------------------
400 (Неверный запрос) Сервер не понимает синтаксис запроса.
401 (Неавторизовано) Запрос требует аутентификации. Для веб-страниц, требующих входа в систему, сервер может вернуть этот ответ.
403 (Запрещено) Сервер отклонил запрос.
404 (Не найдено) Сервер не может найти запрошенную страницу.
405 (Отключить метод) Отключить метод, указанный в запросе.
406 (Не принято) Невозможно ответить на запрошенную веб-страницу с запрошенными характеристиками содержания.
407 (Требуется авторизация агента) Этот код состояния такой же, как и 401(Неавторизовано) Аналогично, но назначенный запрашивающий должен иметь разрешение на использование прокси.
408 (Тайм-аут запроса) Время ожидания сервера истекло.
409 (Конфликт) На сервере возник конфликт при выполнении запроса. Сервер должен включить в ответ информацию о конфликте.
410 (Удалено) Если запрошенный ресурс был окончательно удален, сервер вернет этот ответ.
411 (Требуется допустимая длина) Сервер не принимает запросы, не содержащие допустимого поля заголовка длины содержимого.
412 (Предварительные условия не выполнены) Сервер не выполнил одно из предварительных условий, установленных запрашивающей стороной в запросе.
413 (Объект запроса слишком велик). Сервер не может обработать запрос, поскольку объект запроса слишком велик и превышает возможности сервера по обработке.
414 (Запрошенный URI слишком длинный) Запрошенный URI (обычно URL-адрес) слишком длинный для обработки сервером.
415 (Неподдерживаемый тип носителя) Запрошенный формат не поддерживается запрошенной страницей.
416 (Запрошенный диапазон не соответствует требованиям) Если страница не может предоставить запрошенный диапазон, сервер вернет этот код состояния.
417 (Ожидаемое значение не выполнено) Сервер не найден"ожидать"Требования к полям заголовка запроса.
---------------------------------------------------------------------------------------------
500 (Внутренняя ошибка сервера) Сервер обнаружил ошибку и не смог выполнить запрос.
501 (Еще не реализовано) Сервер не имеет функции для выполнения запроса. Например, сервер может вернуть этот код, если метод запроса не распознан.
502 (Неверный шлюз) Сервер действует как шлюз или прокси-сервер и получил недопустимый ответ от вышестоящего сервера.
503 (Служба недоступна) Сервер в настоящее время недоступен (из-за перегрузки или останова для обслуживания). Обычно это временное состояние.
504 (Тайм-аут шлюза) Сервер действует как шлюз или прокси, но не получил вовремя запрос от вышестоящего сервера.
505 (Версия HTTP не поддерживается) Сервер не поддерживает версию протокола HTTP, используемую в запросе.
proxy_intercept_errors Когда возвращается заголовок ответа вышестоящего сервера, он может перехватить обработку ошибок в соответствии со значением кода состояния ответа в сочетании с инструкцией error_page. Используется при ошибке доступа к вышестоящему серверу.
Пример конфигурации следующим образом:
[[email protected] ~]# cat ssl-zp.wangshibo.conf
upstream mianshi1 {
server 192.168.1.33:8080 max_fails=3 fail_timeout=10s;
#server 192.168.1.32:8080 max_fails=3 fail_timeout=10s;
}
server {
listen 443;
server_name zp.wangshibo.com;
ssl on;
### SSL log files ###
access_log logs/zrx_access.log;
error_log logs/zrx_error.log;
### SSL cert files ###
ssl_certificate ssl/wangshibo.cer;
ssl_certificate_key ssl/wangshibo.key;
ssl_session_timeout 5m;
error_page 404 301 https://zp.wangshibo.com/zrx-web/;
location /zrx-web/ {
proxy_pass http://mianshi1;
proxy_next_upstream error timeout invalid_header http_500 http_502 http_503;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # proxy_set_header X-Forwarded-Proto https;
#proxy_set_header X-Forwarded-Proto https;
proxy_redirect off;
proxy_intercept_errors on;
}
}
Исходное время публикации: 19.07.2017
Эта статья участвуетПлан обмена собственными медиафайлами Tencent Cloud, Добро пожаловать, кто читает, чтобы присоединиться и поделиться вместе.
https://cloud.tencent.com/developer/article/1027325
Этап обработки запросов Nginx (90%)
Процесс получения запроса (99%)
Введение в формат HTTP-запроса (99%)
Сначала представьте базовый формат HTTP-запроса, определенный в rfc2616:
Request = Request-Line * (( general-header | request-header | entity-header ) CRLF) CRLF [ message-body ]
Первая строка — это строка запроса, которая описывает метод запроса, ресурс, к которому нужно получить доступ, и используемую версию HTTP:
Request-Line = Method SP Request-URI SP HTTP-Version CRLF
Определение метода запроса (Method) следующее, среди которых наиболее часто используются методы GET и POST:
Method = "OPTIONS" | "GET" | "HEAD" | "POST" | "PUT" | "DELETE" | "TRACE" | "CONNECT" | extension-method extension-method = token
Доступ к ресурсу определяется унифицированным идентификатором ресурса (Uniform Resource Identifier), и его более общий формат композиции (rfc2396) выглядит следующим образом:
<scheme>://<authority><path>?<query>
Вообще говоря, в зависимости от метода запроса (Method) формат URI запроса будет отличаться, обычно нужно записывать только путь и части запроса.
Версия http (версия) определяется следующим образом, и сейчас обычно используются версии 1.0 и 1.1:
Следующая строка в строке запроса — это заголовок запроса. В rfc2616 определены три различных типа заголовков запроса, а именно общий заголовок, заголовок запроса и заголовок объекта. Каждый тип rfc определяет некоторые общие заголовки. Тип заголовка объекта может содержать настраиваемые заголовки.
Чтение заголовка запроса (99%)
В этом разделе представлен анализ заголовков запросов в nginx. В потоке обработки запросов в nginx задействованы две очень важные структуры данных, ngx_connection_t и ngx_http_request_t, которые используются для представления соединений и запросов, соответственно. Эти две структуры данных описаны в этой книге. Более подробное введение было дано в предыдущей части. Читатели, у которых нет впечатления, могут вернуться и просмотреть его. Весь поток обработки запроса от начала до конца соответствует выделению, инициализации, использованию, повторному использованию и уничтожению этих двух структур данных.
На этапе инициализации nginx, в частности в функции ngx_event_process_init фазы процесса инициализации, для каждого слушающего сокета выделяется структура соединения (ngx_connection_t), а функция обработки событий элемента события чтения (чтение) структуры соединения устанавливается на ngx_event_accept , И если мьютекс accept не используется, эта функция будет монтировать событие чтения в модель обработки событий nginx (опрос или epoll и т. Д.), В противном случае она будет ждать завершения фазы процесса инициализации, в случае рабочего процесса В цикле обработки процесс может монтировать событие чтения только после захвата блокировки принятия.
static ngx_int_t ngx_event_process_init(ngx_cycle_t *cycle) { ... / * Инициализируем красно-черное дерево, используемое для управления всеми таймерами * / if (ngx_event_timer_init(cycle->log) == NGX_ERROR) { return NGX_ERROR; } / * Инициализируем модель событий * / for (m = 0; ngx_modules[m]; m++) { if (ngx_modules[m]->type != NGX_EVENT_MODULE) { continue; } if (ngx_modules[m]->ctx_index != ecf->use) { continue; } module = ngx_modules[m]->ctx; if (module->actions.init(cycle, ngx_timer_resolution) != NGX_OK) { /* fatal */ exit(2); } break; } ... /* for each listening socket */ / * Назначаем структуру соединения для каждого слушающего сокета * / ls = cycle->listening.elts; for (i = 0; i < cycle->listening.nelts; i++) { c = ngx_get_connection(ls[i].fd, cycle->log); if (c == NULL) { return NGX_ERROR; } c->log = &ls[i].log; c->listening = &ls[i]; ls[i].connection = c; rev = c->read; rev->log = c->log; / * Отметить это событие чтения как новое событие соединения запроса * / rev->accept = 1; ... #if (NGX_WIN32) / * Нет анализа в среде Windows, но принцип аналогичен * / #else / * Устанавливаем функцию обработки структуры события чтения на ngx_event_accept * / rev->handler = ngx_event_accept; / * Если вы используете блокировку принятия, вы должны захватить блокировку позже, чтобы смонтировать дескриптор прослушивателя в модели обработки событий * / if (ngx_use_accept_mutex) { continue; } / * В противном случае, напрямую монтируем дескриптор слушателя в модель обработки событий * / if (ngx_event_flags & NGX_USE_RTSIG_EVENT) { if (ngx_add_conn(c) == NGX_ERROR) { return NGX_ERROR; } } else { if (ngx_add_event(rev, NGX_READ_EVENT, 0) == NGX_ERROR) { return NGX_ERROR; } } #endif } return NGX_OK; }
Когда рабочий процесс в определенный момент подключает событие мониторинга к модели обработки событий, nginx может официально получить и обработать запрос от клиента. В это время, если пользователь вводит доменное имя в адресной строке браузера, а сервер разрешения доменных имен разрешает доменное имя на сервер, контролируемый nginx, модель обработки событий nginx получает событие чтения и передаст его Обрабатывается ранее зарегистрированная функция обработки событий ngx_event_accept.
В функции ngx_event_accept nginx вызывает функцию accept, чтобы получить соединение и соответствующий сокет из подключенной очереди, а затем выделяет структуру подключения (ngx_connection_t) и сохраняет вновь полученный сокет в структуре подключения. Выполним базовую работу по инициализации подключения:
1. Сначала выделите пул памяти для подключения, начальный размер по умолчанию равен 256 байтам, который может быть установлен командой connection_pool_size;
2, Выделите структуру журнала и сохраните ее для последующего использования системой журнала;
3. Инициализировать и подключить соответствующую функцию приемопередатчика io, конкретная функция приемопередатчика io связана с используемой моделью событий и операционной системой;
4. Назначьте адрес сокета (sockaddr), скопируйте в него одноранговый адрес, полученный с помощью accept, и сохраните его в поле sockaddr;
5. Сохраните адрес локального сокета в поле local_sockaddr, потому что это значение доступно из структуры прослушивания ngx_listening_t, а структура прослушивания — это только адрес прослушивания, установленный в файле конфигурации, но настроенный адрес прослушивания может быть подстановочным знаком *, то есть Слушайте все адреса, поэтому значение, сохраненное в соединении, может со временем измениться и будет определено как реальный адрес приема;
6. Установите для события записи соединения состояние готовности, то есть установите готово на 1, по умолчанию nginx подключается в первый раз с возможностью записи;
7. Если для прослушивающего сокета установлен атрибут TCP_DEFER_ACCEPT, это означает, что по соединению идет пакет данных, поэтому установите событие чтения как готовое;
8. Отформатируйте одноранговый адрес, сохраненный в поле sockaddr, в читаемую строку и сохраните его в поле addr_text;
Наконец, вызовите функцию ngx_http_init_connection, чтобы инициализировать другие части структуры соединения.
Самая важная работа функции ngx_http_init_connection заключается в инициализации функции обработки событий чтения и записи: установите функцию обработки события записи структуры соединения на ngx_http_empty_handler, эта функция обработки событий не будет выполнять никаких операций, фактически, соединение по умолчанию nginx может быть записано впервые. События записи не будут подключены. Если есть данные для отправки, nginx будет писать прямо в это соединение. Только в том случае, если запись не может быть завершена один раз, событие записи будет подключено к модели события и будет установлена реальная обработка события записи Функции, которые будут подробно описаны в следующих главах; функция обработки события чтения установлена на ngx_http_init_request, если есть данные о соединении (установлено отложенное принятие), функция ngx_http_init_request будет вызываться непосредственно для обработки запроса, и наоборот. Установите таймер и установите событие чтения в модели обработки событий и дождитесь прибытия данных или истечения времени ожидания. Конечно, независимо от того, поступают ли уже данные, или вам нужно дождаться поступления данных, или ждать тайм-аута, он в конечном итоге войдет в функцию обработки событий чтения-ngx_http_init_request.
Основная задача функции ngx_http_init_request — инициализировать запрос. Поскольку это функция обработки событий, она имеет только один параметр типа ngx_event_t *. Структура ngx_event_t представляет событие в nginx. Контекст обработки событий аналогичен контексту обработки прерывания. Этот контекст получает релевантную информацию. В nginx ссылка на структуру соединения обычно хранится в поле данных структуры события, а ссылка на структуру запроса сохраняется в поле данных структуры соединения, так что соответствующее соединение можно легко получить в функции обработки событий. Структура и структура запроса. Загляните внутрь функции, сначала определите, является ли событие событием тайм-аута, если это так, закройте соединение и вернитесь; в противном случае это означает, что есть запрос на ранее принятое соединение, которое необходимо обработать.
Функция ngx_http_init_request сначала выделяет структуру ngx_http_request_t для запроса в подключенном пуле памяти. Эта структура будет использоваться для хранения всей информации запроса. После выделения ссылка на эту структуру будет упакована и сохранена в поле запроса подключенного члена hc, так что структура запроса может быть повторно использована в длинном соединении или конвейерном запросе. В этой функции nginx находит конфигурацию виртуального сервера по умолчанию на основе принимающего порта и адреса запроса (атрибут default_server команды listen используется для идентификации виртуального сервера по умолчанию, в противном случае он прослушивает несколько виртуальных серверов на одном и том же порту и адресе, первый из которых Определенный является значением по умолчанию).
Вы можете настроить несколько виртуальных серверов, прослушивающих разные порты и адреса в файле конфигурации nginx (каждый блок сервера соответствует виртуальному серверу), а также различать прослушивание одного порта в соответствии с именем домена (инструкция server_name может настроить имя домена, соответствующее виртуальному серверу) Каждый виртуальный сервер может иметь разное содержимое конфигурации, и это содержимое конфигурации определяет, как nginx обрабатывает запрос после его получения. После нахождения соответствующая конфигурация сохраняется в структуре ngx_http_request_t, соответствующей запросу. Обратите внимание, что найденная здесь конфигурация по умолчанию на основе порта и адреса предназначена только для временного использования. В конце концов, nginx найдет реальную конфигурацию виртуального сервера на основе имени домена. Последующая работа по инициализации также включает:
1. Установите функцию обработки связанного события чтения на функцию ngx_http_process_request_line. Эта функция используется для анализа строки запроса и установки запрошенного read_event_handler на функцию ngx_http_block_reading. Эта функция ничего не делает (конечно, когда модель событий настроена на горизонтальный триггер) , Единственное, что нужно сделать, это удалить событие из списка мониторинга модели событий, чтобы событие не срабатывало постоянно), я расскажу о том, почему здесь read_event_handler установлен на эту функцию;
2. Выделите буфер для этого запроса, чтобы сохранить его заголовок. Адрес хранится в поле header_in. Размер по умолчанию составляет 1024 байта. Для его изменения можно использовать команду client_header_buffer_size. Обратите внимание, что nginx используется для хранения буфера заголовка запроса. Область выделяется в пуле памяти соединения, в котором находится запрос, и адрес сохраняется в поле буфера соединения. Целью этого является повторное использование этого буфера для следующего запроса соединения. Кроме того, если клиент отправляет Если заголовок входящего запроса превышает 1024 байта, nginx повторно выделяет большую буферную область. Буфер по умолчанию, используемый для больших заголовков запросов, составляет не более 8 КБ и не более 4. Эти 2 значения могут быть установлены с помощью команды large_client_header_buffers и следующих Будет сказано, что ни строка запроса, ни заголовок запроса не могут превышать размер максимального буфера;
3. Выделите пул памяти для этого запроса. Все последующие выделения памяти, связанные с этим запросом, обычно будут использовать этот пул памяти. Размер по умолчанию составляет 4096 байт, который можно изменить с помощью команды request_pool_size;
4. Назначьте этому запросу связанный список заголовков ответов, начальный размер — 20;
5. Создайте массив указателей context ctx и переменные данные всех модулей;
6. Установите основное поле запроса на себя, указав, что это основной запрос.Также в nginx существует концепция подзапросов, которая будет подробно представлена в следующих главах;
7. Установите поле счетчика запроса на 1, а поле счетчика представляет счетчик ссылок запроса;
8. Сохраните текущее время в полях start_sec и start_msec. Это время является временем начала запроса и будет использоваться для расчета времени обработки запроса (времени запроса). Начальная точка, используемая nginx, немного отличается от точки начала apache. Начальная точка запроса посередине — это начало события, когда получен первый пакет клиента, и apache начинает отсчет после получения всей строки запроса клиента;
9, Инициализировать другие поля запроса, например установить uri_changes на 11, что означает, что uri запроса можно переписать не более 10 раз, а subrequests установлен на 201, что означает, что запрос может инициировать до 200 подзапросов;
После того, как все работы по инициализации будут выполнены, функция ngx_http_init_request вызовет функцию обработки события чтения, чтобы действительно проанализировать данные, отправленные клиентом, то есть она войдет в функцию ngx_http_process_request_line для обработки.
Разобрать строку запроса (99%)
Основная функция функции ngx_http_process_request_line — анализировать строку запроса. Также из-за операций сетевого ввода-вывода даже очень короткая строка запроса не может быть прочитана сразу. Поэтому в предыдущей функции ngx_http_init_request функция ngx_http_process_request_line установлена как событие чтения Функция обработки, у нее есть только уникальный параметр типа ngx_event_t *, и в начале функции также необходимо определить, является ли это событием тайм-аута, если это так, то закрыть запрос и соединение; в противном случае запустить обычный процесс анализа. Сначала вызовите функцию ngx_http_read_request_header, чтобы прочитать данные.
Поскольку функция ngx_http_process_request_line может быть введена несколько раз, функция ngx_http_read_request_header сначала проверяет, есть ли данные в буфере, на который указывает header_in запроса, и возвращает напрямую, если они есть; в противном случае данные считываются из соединения и сохраняются в буфере, на который указывает header_in запроса, и буферизируются только Если в области есть место, он будет читать как можно больше данных за раз и возвращать столько, сколько читает; если клиент не отправляет никаких данных в течение некоторого времени и возвращает NGX_AGAIN, он выполнит две вещи перед возвратом:
1. Установите таймер, длительность по умолчанию — 60 секунд, которую можно установить с помощью команды client_header_timeout.Если до наступления события синхронизации нет читаемого события, nginx закроет запрос;
2. Вызовите функцию ngx_handle_read_event для обработки события чтения — если соединение еще не смонтировало событие чтения в модели обработки событий, смонтируйте его;
Если клиент закрывает соединение раньше времени или возникают другие ошибки при чтении данных, он вернет клиенту ошибку 400 (конечно, нет гарантии, что клиент сможет получить данные ответа, потому что клиент мог закрыть соединение), и, наконец, Функция возвращает NGX_ERROR;
Если функция ngx_http_read_request_header считывает данные в обычном режиме, функция ngx_http_process_request_line вызовет функцию ngx_http_parse_request_line для синтаксического анализа.Эта функция реализует конечный автомат в соответствии с определением строки запроса в спецификации протокола http. После этого конечный автомат запишет ngx_http_process_request_line. Метод запроса (Method), начальная позиция uri запроса и версия протокола http в буфере, некоторая другая полезная информация будет записана во время процесса синтаксического анализа для использования в последующей обработке. Если нет проблем с синтаксическим анализом строки запроса, функция вернет NGX_OK; если строка запроса не соответствует спецификации протокола, функция немедленно завершит процесс синтаксического анализа и вернет соответствующий номер ошибки; если данных буфера недостаточно, функция вернет NGX_AGAIN .
Во всем конечном автомате для анализа HTTP-запросов всегда соблюдаются два важных принципа: сокращение объема копирования в памяти и отслеживание с возвратом.
Копирование памяти — относительно дорогая операция, и большое количество копий памяти снижает эффективность выполнения. nginx пытается скопировать только начальный и конечный адреса памяти, а не саму память, куда ему нужно копировать память. Таким образом, необходимы только две операции присваивания, что значительно снижает накладные расходы.Конечно, это влияет на последующую операцию Сама память не может быть изменена. Если она будет изменена, это повлияет на все ссылки на диапазон памяти, поэтому с ней нужно обращаться осторожно, и при необходимости следует копировать копию.
Здесь я должен упомянуть структуру данных в nginx, которая наилучшим образом отражает эту идею, ngx_buf_t, которая используется для представления кеша в nginx. Во многих случаях вам нужно сохранить только начальный и конечный адрес части памяти в ее В элементах pos и last установите его флаг памяти в значение 1. Это может указывать на диапазон памяти, который не может быть изменен. В других случаях, когда требуется кэш, который может быть изменен, необходимо выделить и сохранить память требуемого размера. Начальный адрес, затем установите временный флаг ngx_buf_t в 1, указывая, что это область памяти, которую можно изменить.
Возвращаясь к функции ngx_http_process_request_line, если функция ngx_http_parse_request_line возвращает ошибку, она напрямую вернет клиенту ошибку 400; если она вернет NGX_AGAIN, вам необходимо определить, вызвано ли это недостаточным объемом буфера или недостаточными данными для чтения. Если размера буфера недостаточно, nginx вызовет функцию ngx_http_alloc_large_header_buffer для выделения другого большого буфера. Если большого буфера недостаточно для размещения всей строки запроса, nginx вернет клиенту ошибку 414, в противном случае будет выделен больший буфер После копирования предыдущих данных продолжайте вызывать функцию ngx_http_read_request_header для чтения данных и входа в автоматическую обработку строки запроса, пока не закончится синтаксический анализ строки запроса;
Если возвращается NGX_OK, это означает, что строка запроса была проанализирована правильно. В это время сначала запишите начальный адрес и длину строки запроса и сохраните путь и часть параметров запроса uri в поле uri структуры запроса, начиная с метода запроса. Начальная позиция и длина хранятся в поле method_name, а начальная позиция и длина http-версии записываются в поле http_protocol. Параметры и расширение запрошенного ресурса также анализируются из uri и сохраняются в полях args и exten соответственно. Затем мы проанализируем заголовок запроса, который будет представлен в следующем разделе.
Заголовки запроса на синтаксический анализ (99%)
В функции ngx_http_process_request_line после синтаксического анализа строки запроса, если uri строки запроса содержит часть имени домена, сохраните его в поле server элемента headers_in структуры запроса. Headers_in используется для хранения всех заголовков запроса, и его тип — ngx_http_headers_in_t:
typedef struct { ngx_list_t headers; ngx_table_elt_t *host; ngx_table_elt_t *connection; ngx_table_elt_t *if_modified_since; ngx_table_elt_t *if_unmodified_since; ngx_table_elt_t *user_agent; ngx_table_elt_t *referer; ngx_table_elt_t *content_length; ngx_table_elt_t *content_type; ngx_table_elt_t *range; ngx_table_elt_t *if_range; ngx_table_elt_t *transfer_encoding; ngx_table_elt_t *expect; #if (NGX_HTTP_GZIP) ngx_table_elt_t *accept_encoding; ngx_table_elt_t *via; #endif ngx_table_elt_t *authorization; ngx_table_elt_t *keep_alive; #if (NGX_HTTP_PROXY || NGX_HTTP_REALIP || NGX_HTTP_GEO) ngx_table_elt_t *x_forwarded_for; #endif #if (NGX_HTTP_REALIP) ngx_table_elt_t *x_real_ip; #endif #if (NGX_HTTP_HEADERS) ngx_table_elt_t *accept; ngx_table_elt_t *accept_language; #endif #if (NGX_HTTP_DAV) ngx_table_elt_t *depth; ngx_table_elt_t *destination; ngx_table_elt_t *overwrite; ngx_table_elt_t *date; #endif ngx_str_t user; ngx_str_t passwd; ngx_array_t cookies; ngx_str_t server; off_t content_length_n; time_t keep_alive_n; unsigned connection_type:2; unsigned msie:1; unsigned msie6:1; unsigned opera:1; unsigned gecko:1; unsigned chrome:1; unsigned safari:1; unsigned konqueror:1; } ngx_http_headers_in_t;
Затем функция проверит, использует ли входящий запрос http0.9, и если да, то использует доменное имя, полученное из строки запроса, и вызовет функцию ngx_http_find_virtual_server (), чтобы найти конфигурацию виртуального сервера, используемую для обработки запроса.Порт ранее использовался Конфигурация по умолчанию, найденная с адресом, больше не используется. После того, как соответствующая конфигурация найдена, для обработки запроса напрямую вызывается функция ngx_http_process_request (). Поскольку http0.9 является наиболее примитивным протоколом http, в нем не определен заголовок запроса, поэтому, очевидно, он не нужен Операция чтения заголовка запроса.
if (r->host_start && r->host_end) { host = r->host_start; n = ngx_http_validate_host(r, &host, r->host_end - r->host_start, 0); if (n == 0) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "client sent invalid host in request line"); ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); return; } if (n < 0) { ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } r->headers_in.server.len = n; r->headers_in.server.data = host; } if (r->http_version < NGX_HTTP_VERSION_10) { if (ngx_http_find_virtual_server(r, r->headers_in.server.data, r->headers_in.server.len) == NGX_ERROR) { ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } ngx_http_process_request(r); return; }
Конечно, если это протокол http 1.0 или новее, следующее, что нужно сделать, — это прочитать заголовок запроса. Во-первых, nginx выделит пространство для заголовка запроса. Поле заголовков структуры ngx_http_headers_in_t представляет собой структуру связанного списка, которая используется для хранения всех заголовков запросов. , Изначально для него выделено 20 узлов, каждый тип узла — ngx_table_elt_t, сохраните пару имя / значение значение заголовка запроса, вы также можете увидеть, что структура ngx_http_headers_in_t имеет много членов-указателей типа ngx_table_elt_t *, и их можно назвать Видно, что это некоторые общие имена заголовков запросов. Nginx сохраняет ссылку на эти часто используемые заголовки запросов в структуре ngx_http_headers_in_t. Если вам понадобится использовать ее позже, вы можете получить ее напрямую через эти члены. Кроме того, он также заранее выделяет 2 элемента для заголовка cookie. После завершения этих приготовлений памяти функция обработки структуры события чтения, соответствующая запросу, устанавливается на ngx_http_process_request_headers, и функция вызывается сразу после этого.
if (ngx_list_init(&r->headers_in.headers, r->pool, 20, sizeof(ngx_table_elt_t)) != NGX_OK) { ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } if (ngx_array_init(&r->headers_in.cookies, r->pool, 2, sizeof(ngx_table_elt_t *)) != NGX_OK) { ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } c->log->action = "reading client request headers"; rev->handler = ngx_http_process_request_headers; ngx_http_process_request_headers(rev);
Функция ngx_http_process_request_headers циклически считывает все заголовки запросов, а также сохраняет и инициализирует структуру, связанную с заголовками запросов.Подробный анализ этой функции:
Поскольку nginx имеет ограничение по времени ожидания на чтение заголовков запросов, функция ngx_http_process_request_headers используется как функция обработки события чтения для совместной обработки события тайм-аута. Если время ожидания истекает, nginx напрямую возвращает в запрос ошибку 408:
if (rev->timedout) { ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out"); c->timedout = 1; ngx_http_close_request(r, NGX_HTTP_REQUEST_TIME_OUT); return; }
Логика чтения и анализа заголовка запроса аналогична логике обработки строки запроса. Общий процесс также состоит в том, чтобы вызвать функцию ngx_http_read_request_header () для чтения данных в цикле, а затем вызвать функцию синтаксического анализа для анализа заголовка запроса из прочитанных данных, пока весь анализ не будет завершен. Заголовок запроса или ошибка синтаксического анализа возникает в основном. Конечно, из-за задействованного сетевого io этот процесс может происходить в контексте нескольких событий io.
Затем давайте более подробно рассмотрим функцию. Сначала вызовите функцию ngx_http_read_request_header () для чтения данных. Если данные не поступают из текущего соединения, вернитесь напрямую и дождитесь следующего события чтения. Если некоторые данные будут прочитаны, вызовите функцию ngx_http_parse_header_line () для анализа , Та же функция синтаксического анализа реализована в виде конечного автомата. Логика очень проста. Он просто анализирует заголовок запроса в соответствии с протоколом http. При каждом вызове функции можно анализировать не более одного заголовка запроса. Функция возвращает 4 разных возвращаемых значения, указывающих на различный синтаксический анализ. результат:
1. Верните NGX_OK, что означает, что строка заголовка запроса была проанализирована. В настоящее время необходимо определить, есть ли недопустимые символы в имени анализируемого заголовка запроса. Допустимые символы в имени включают буквы, цифры и дефисы (-), и если он установлен Команда underscores_in_headers включена, подчеркивание также является допустимым символом, но подчеркивание по умолчанию nginx недопустимо. Когда заголовок запроса содержит недопустимые символы, nginx просто игнорирует эту строку заголовка запроса по умолчанию; если все в порядке, nginx добавит заголовок запроса к запросу Хэш-значение имени заголовка сохраняется в связанном списке заголовков элемента headers_in структуры запроса, а для некоторых общих заголовков запросов, таких как Host, Connection, nginx, используется метод, аналогичный инструкциям по настройке, и функция обработки назначается этим заголовкам запроса заранее. , Когда заголовок запроса анализируется, он проверяет, установлена ли в заголовке запроса функция обработки, если таковая имеется, она будет вызвана.Все заголовки запросов с функциями обработки в nginx записываются в глобальный массив ngx_http_headers_in:
typedef struct { ngx_str_t name; ngx_uint_t offset; ngx_http_header_handler_pt handler; } ngx_http_header_t; ngx_http_header_t ngx_http_headers_in[] = { { ngx_string("Host"), offsetof(ngx_http_headers_in_t, host), ngx_http_process_host }, { ngx_string("Connection"), offsetof(ngx_http_headers_in_t, connection), ngx_http_process_connection }, { ngx_string("If-Modified-Since"), offsetof(ngx_http_headers_in_t, if_modified_since), ngx_http_process_unique_header_line }, { ngx_string("If-Unmodified-Since"), offsetof(ngx_http_headers_in_t, if_unmodified_since), ngx_http_process_unique_header_line }, { ngx_string("User-Agent"), offsetof(ngx_http_headers_in_t, user_agent), ngx_http_process_user_agent }, { ngx_string("Referer"), offsetof(ngx_http_headers_in_t, referer), ngx_http_process_header_line }, { ngx_string("Content-Length"), offsetof(ngx_http_headers_in_t, content_length), ngx_http_process_unique_header_line }, { ngx_string("Content-Type"), offsetof(ngx_http_headers_in_t, content_type), ngx_http_process_header_line }, { ngx_string("Range"), offsetof(ngx_http_headers_in_t, range), ngx_http_process_header_line }, { ngx_string("If-Range"), offsetof(ngx_http_headers_in_t, if_range), ngx_http_process_unique_header_line }, { ngx_string("Transfer-Encoding"), offsetof(ngx_http_headers_in_t, transfer_encoding), ngx_http_process_header_line }, { ngx_string("Expect"), offsetof(ngx_http_headers_in_t, expect), ngx_http_process_unique_header_line }, #if (NGX_HTTP_GZIP) { ngx_string("Accept-Encoding"), offsetof(ngx_http_headers_in_t, accept_encoding), ngx_http_process_header_line }, { ngx_string("Via"), offsetof(ngx_http_headers_in_t, via), ngx_http_process_header_line }, #endif { ngx_string("Authorization"), offsetof(ngx_http_headers_in_t, authorization), ngx_http_process_unique_header_line }, { ngx_string("Keep-Alive"), offsetof(ngx_http_headers_in_t, keep_alive), ngx_http_process_header_line }, #if (NGX_HTTP_PROXY || NGX_HTTP_REALIP || NGX_HTTP_GEO) { ngx_string("X-Forwarded-For"), offsetof(ngx_http_headers_in_t, x_forwarded_for), ngx_http_process_header_line }, #endif #if (NGX_HTTP_REALIP) { ngx_string("X-Real-IP"), offsetof(ngx_http_headers_in_t, x_real_ip), ngx_http_process_header_line }, #endif #if (NGX_HTTP_HEADERS) { ngx_string("Accept"), offsetof(ngx_http_headers_in_t, accept), ngx_http_process_header_line }, { ngx_string("Accept-Language"), offsetof(ngx_http_headers_in_t, accept_language), ngx_http_process_header_line }, #endif #if (NGX_HTTP_DAV) { ngx_string("Depth"), offsetof(ngx_http_headers_in_t, depth), ngx_http_process_header_line }, { ngx_string("Destination"), offsetof(ngx_http_headers_in_t, destination), ngx_http_process_header_line }, { ngx_string("Overwrite"), offsetof(ngx_http_headers_in_t, overwrite), ngx_http_process_header_line }, { ngx_string("Date"), offsetof(ngx_http_headers_in_t, date), ngx_http_process_header_line }, #endif { ngx_string("Cookie"), 0, ngx_http_process_cookie }, { ngx_null_string, 0, NULL } };
Массив ngx_http_headers_in в настоящее время содержит 25 часто используемых заголовков запросов. Каждый заголовок запроса задается функцией обработки. Некоторые из заголовков запроса устанавливаются с помощью общих функций обработки. Имеются 2 общие функции обработки: ngx_http_process_header_line и ngx_http_process_unique_header_line. Сначала посмотрите на определение указателя функции для функции обработки:
typedef ngx_int_t (*ngx_http_header_handler_pt)(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset);
Он имеет 3 параметра, r — соответствующая структура запроса, h — указатель на соответствующий узел заголовка запроса в связанном списке headers_in.headers, а смещение — это смещение соответствующего поля заголовка запроса в структуре ngx_http_headers_in_t.
Давайте еще раз посмотрим на функцию ngx_http_process_header_line:
static ngx_int_t ngx_http_process_header_line(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset) { ngx_table_elt_t **ph; ph = (ngx_table_elt_t **) ((char *) &r->headers_in + offset); if (*ph == NULL) { *ph = h; } return NGX_OK; }
Эта функция просто сохраняет ссылку на заголовок запроса в структуре ngx_http_headers_in_t. Функция ngx_http_process_unique_header_line аналогична, разница в том, что функция проверяет, дублируется ли заголовок запроса, и если да, то она вернет в запрос ошибку 400.
Остальные заголовки запросов в массиве ngx_http_headers_in имеют свои собственные специальные функции обработки. Эти специальные функции имеют некоторую специальную обработку в соответствии с соответствующими заголовками запросов. Функция обработки ngx_http_process_host заголовка Host представлена ниже:
static ngx_int_t ngx_http_process_host(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset) { u_char *host; ssize_t len; if (r->headers_in.host == NULL) { r->headers_in.host = h; } host = h->value.data; len = ngx_http_validate_host(r, &host, h->value.len, 0); if (len == 0) { ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "client sent invalid host header"); ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); return NGX_ERROR; } if (len < 0) { ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); return NGX_ERROR; } if (r->headers_in.server.len) { return NGX_OK; } r->headers_in.server.len = len; r->headers_in.server.data = host; return NGX_OK; }
Цель этой функции также состоит в том, чтобы сохранить краткую ссылку на заголовок Host. Она будет выполнять некоторые проверки законности значения заголовка Host, анализировать имя домена из него и сохранять его в поле headers_in.server. Фактически, при синтаксическом анализе строки запроса headers_in.server Возможно, он был назначен доменному имени, полученному из строки запроса. Согласно спецификации протокола http, если uri в строке запроса имеет доменное имя, доменное имя имеет преимущественную силу, поэтому здесь вам нужно проверить, пусто ли headers_in.server. Если он не пуст, дальнейшее присвоение не требуется.
Специальные функции обработки других заголовков запроса вводиться не будут. Обычно они устанавливают некоторые атрибуты запроса в соответствии со смыслом и значением заголовка запроса, указанными в протоколе http, и должны использоваться позже.
Обработка заголовка юридического запроса примерно такая, как описано выше;
2. Верните NGX_AGAIN, указывая, что текущих полученных данных недостаточно, и заголовок запроса строки не закончился, и следующий цикл необходимо продолжить. В следующем цикле цикла nginx сначала проверяет, заполнен ли буфер заголовка запроса header_in. Если он заполнен, он вызывает функцию ngx_http_alloc_large_header_buffer () для выделения дополнительных буферов. Давайте проанализируем функцию ngx_http_alloc_large_header_buffer:
static ngx_int_t ngx_http_alloc_large_header_buffer(ngx_http_request_t *r, ngx_uint_t request_line) { u_char *old, *new; ngx_buf_t *b; ngx_http_connection_t *hc; ngx_http_core_srv_conf_t *cscf; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http alloc large header buffer"); /* * На этапе анализа строки запроса, если клиент отправляет большое количество возвратов каретки перед отправкой строки запроса, он будет * Буфер заполнен. В этом случае nginx просто сбрасывает буфер и отбрасывает мусор. * Данные, не нужно выделять больше памяти. */ if (request_line && r->state == 0) { /* the client fills up the buffer with "rn" */ r->request_length += r->header_in->end - r->header_in->start; r->header_in->pos = r->header_in->start; r->header_in->last = r->header_in->start; return NGX_OK; } / * Сохраняем начальный адрес строки запроса или заголовок запроса в старом буфере * / old = request_line ? r->request_start : r->header_name_start; cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); / * Если большой буфер не помещается в строку запроса или заголовок запроса, будет возвращена ошибка * / if (r->state != 0 && (size_t) (r->header_in->pos - old) >= cscf->large_client_header_buffers.size) { return NGX_DECLINED; } hc = r->http_connection; / * Сначала выясняем, есть ли свободный буфер в структуре ngx_http_connection_t, если да, возьмем его напрямую * / if (hc->nfree) { b = hc->free[--hc->nfree]; ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http large header free: %p %uz", b->pos, b->end - b->last); / * Проверяем, не превышает ли количество выделенных для запроса буферов заголовка запроса ограничение, максимальное количество по умолчанию - 4 * / } else if (hc->nbusy < cscf->large_client_header_buffers.num) { if (hc->busy == NULL) { hc->busy = ngx_palloc(r->connection->pool, cscf->large_client_header_buffers.num * sizeof(ngx_buf_t *)); if (hc->busy == NULL) { return NGX_ERROR; } } / * Если максимальный объем распределения не был достигнут, выделяется новый большой буфер * / b = ngx_create_temp_buf(r->connection->pool, cscf->large_client_header_buffers.size); if (b == NULL) { return NGX_ERROR; } ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http large header alloc: %p %uz", b->pos, b->end - b->last); } else { / * Если был достигнут максимальный предел выделения, возвращается ошибка * / return NGX_DECLINED; } / * Добавляем буфер, полученный из свободной очереди или вновь выделенный в используемую очередь * / hc->busy[hc->nbusy++] = b; /* * Потому что в nginx все заголовки запросов сохраняются в виде указателей (начальный и конечный адреса), * Таким образом, полный заголовок запроса должен быть помещен в непрерывный блок памяти. Если старый буфер не может * Запишите всю строку заголовков запроса, выделите новый буфер и скопируйте некоторые из заголовков запроса, которые были прочитаны из старого буфера. * После копирования все соответствующие указатели необходимо изменить, чтобы они указывали на новый буфер. * Статус 0 означает, что после разбора строки заголовков запроса буфер просто израсходован. В этом случае нет копии */ if (r->state == 0) { /* * r->state == 0 means that a header line was parsed successfully * and we do not need to copy incomplete header line and * to relocate the parser header pointers */ r->request_length += r->header_in->end - r->header_in->start; r->header_in = b; return NGX_OK; } ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http large header copy: %d", r->header_in->pos - old); r->request_length += old - r->header_in->start; new = b->start; / * Копируем неполный заголовок запроса в старый буфер * / ngx_memcpy(new, old, r->header_in->pos - old); b->pos = new + (r->header_in->pos - old); b->last = new + (r->header_in->pos - old); / * Измените соответствующий указатель, чтобы он указывал на новый буфер * / if (request_line) { r->request_start = new; if (r->request_end) { r->request_end = new + (r->request_end - old); } r->method_end = new + (r->method_end - old); r->uri_start = new + (r->uri_start - old); r->uri_end = new + (r->uri_end - old); if (r->schema_start) { r->schema_start = new + (r->schema_start - old); r->schema_end = new + (r->schema_end - old); } if (r->host_start) { r->host_start = new + (r->host_start - old); if (r->host_end) { r->host_end = new + (r->host_end - old); } } if (r->port_start) { r->port_start = new + (r->port_start - old); r->port_end = new + (r->port_end - old); } if (r->uri_ext) { r->uri_ext = new + (r->uri_ext - old); } if (r->args_start) { r->args_start = new + (r->args_start - old); } if (r->http_protocol.data) { r->http_protocol.data = new + (r->http_protocol.data - old); } } else { r->header_name_start = new; r->header_name_end = new + (r->header_name_end - old); r->header_start = new + (r->header_start - old); r->header_end = new + (r->header_end - old); } r->header_in = b; return NGX_OK; }
Когда функция ngx_http_alloc_large_header_buffer возвращает NGX_DECLINED, это означает, что клиент отправил строку слишком большого заголовка запроса или весь заголовок запроса превышает лимит, nginx вернет ошибку 494. Обратите внимание, что nginx устанавливает запрошенный флаг lingering_close перед возвратом 494 1. Это делается с целью отбросить другие данные, отправленные клиентом, перед возвратом ответа;
3. Верните NGX_HTTP_PARSE_INVALID_HEADER, указывая, что во время анализа заголовка запроса произошла ошибка. Обычно клиент отправляет заголовок, не соответствующий спецификации протокола. В это время nginx возвращает ошибку 400;
4. Верните NGX_HTTP_PARSE_HEADER_DONE, указывая, что все заголовки запроса были успешно проанализированы. В это время статус запроса установлен на NGX_HTTP_PROCESS_REQUEST_STATE, что означает, что фаза чтения запроса завершена и фаза обработки запроса официально введена, но на самом деле запрос может содержать тело запроса, nginx На этапе чтения запроса тело запроса не читается. Эта работа передается последующему модулю обработки запроса. Это делается для того, чтобы сам nginx не знал, полезно ли тело запроса, если последующему модулю оно не нужно. С одной стороны, тело запроса обычно велико. Если все тело запроса считывается в память, большой объем памяти тратится впустую. С другой стороны, даже если nginx записывает тело запроса на диск, это займет много времени, когда он задействует disk io. Следовательно, это наиболее разумный способ позволить последующему модулю решить, читать или отклонять тело запроса.
После прочтения заголовка запроса nginx вызывает функцию ngx_http_process_request_header (). Эта функция в основном выполняет две задачи: одна — вызвать функцию ngx_http_find_virtual_server () для поиска конфигурации виртуального сервера; другая — выполнить некоторые проверки протокола для некоторых заголовков запроса. Например, для запросов, которые используют протокол http1.1, но не отправляют заголовок Host, nginx возвращает этим запросам ошибку 400. Кроме того, текущая версия nginx не поддерживает фрагментированный ввод. Если некоторые запросы заявляют, что они используют фрагментированный ввод (запрос имеет заголовок transfer_encoding со значением chunked), nginx возвращает 411 ошибок для этих запросов. и многое другое.
Наконец, для обработки запроса вызывается функция ngx_http_process_request (), а пока что процесс получения заголовка запроса nginx представлен.
Чтение тела запроса (100%)
В последнем разделе говорилось, что ядро nginx само не читает тело запроса активно. Эта работа передается модулю обработки запросов для ее выполнения. Однако ядро nginx предоставляет интерфейс ngx_http_read_client_request_body () для чтения тела запроса, а также предоставляет запрос на сброс. Body interface-ngx_http_discard_request_body (), на каждом этапе выполнения запроса, если модуль на любом этапе заинтересован в теле запроса или хочет отбросить тело запроса, отправленное клиентом, эти два интерфейса могут быть вызваны для завершения. Эти два интерфейса являются стандартными интерфейсами, предоставляемыми ядром nginx для обработки тела запроса. Если вы хотите, чтобы некоторые инструкции, относящиеся к телу запроса, в файле конфигурации (например, client_body_in_file_only, client_body_buffer_size и т. Д.) Работали должным образом, и чтобы можно было использовать встроенные в nginx некоторые инструкции, связанные с телом запроса. Переменные (такие как $ request_body и $ request_body_file), вообще говоря, все модули должны вызывать эти интерфейсы для выполнения соответствующих операций. Если вам нужно настроить интерфейс для обработки тела запроса, вы также должны попытаться быть совместимыми с поведением nginx по умолчанию.
Читать тело запроса
Чтение тела запроса обычно происходит в обработчике содержимого nginx. Некоторые встроенные модули nginx, такие как модуль прокси, модуль fastcgi, модуль uwsgi и т. Д. Поведение этих модулей должно основываться на теле запроса (если есть) от клиента. Соответствующий протокол полностью перенаправляется в процесс внутренней службы, и все эти модули вызывают интерфейс ngx_http_read_client_request_body () для завершения чтения тела запроса. Стоит отметить, что эти модули будут полностью читать тело запроса клиента перед пересылкой данных в бэкэнд.
Из-за ограничений памяти тело запроса, считываемое интерфейсом ngx_http_read_client_request_body (), частично или полностью записывается во временный файл. В зависимости от размера тела запроса и соответствующей конфигурации команды, тело запроса может быть помещено в непрерывную память или размещено отдельно В двух разных частях памяти также возможно, что все они существуют во временном файле, и, в конце концов, часть его может находиться в памяти, а оставшаяся часть — во временном файле. Давайте сначала представим инструкции, относящиеся к этим различным режимам хранения:
client_body_buffer_size: | |
---|---|
Задайте размер буфера тела запроса кеша. По умолчанию в 2 раза больше размера системной страницы. Когда размер тела запроса превышает этот размер, nginx запишет тело запроса во временный файл. Вы можете установить подходящий размер в соответствии с потребностями бизнеса и постараться избежать операций с дисковым io; | |
client_body_in_single_buffer: | |
Указывает, следует ли сохранять тело запроса полностью в непрерывной памяти. По умолчанию выключено. Если для этой инструкции установлено значение on, nginx будет гарантировать, что тело запроса будет сохранено в непрерывной памяти, если оно не превышает значение, установленное client_body_buffer_size. , Но когда он превышает размер, он будет записан во временный файл; | |
client_body_in_file_only: | |
Укажите, следует ли всегда сохранять тело запроса во временном файле. По умолчанию выключено. Когда эта спецификация включена, даже если клиент явно указывает, что длина тела запроса равна 0, nginx все равно будет создавать временный файл для запроса. |
Затем представьте реализацию интерфейса ngx_http_read_client_request_body (), который определяется следующим образом:
ngx_int_t ngx_http_read_client_request_body(ngx_http_request_t *r, ngx_http_client_body_handler_pt post_handler)
Этот интерфейс имеет два параметра: первый — это указатель на структуру запроса, а второй — указатель на функцию. Он будет вызван при чтении тела запроса. Как упоминалось ранее, в соответствии с существующим поведением nginx логика модуля будет выполняться после чтения тела запроса.Эта функция обратного вызова обычно является функцией обработки логики модуля. Функция ngx_http_read_client_request_body () сначала добавляет 1 к ссылке основного запроса, соответствующего параметру r. Цель этого связана с контекстом, в котором вызывается интерфейс. Вообще говоря, модуль вызывает этот интерфейс в обработчике содержимого. Типичный вызов выглядит следующим образом:
static ngx_int_t ngx_http_proxy_handler(ngx_http_request_t *r) { ... rc = ngx_http_read_client_request_body(r, ngx_http_upstream_init); if (rc >= NGX_HTTP_SPECIAL_RESPONSE) { return rc; } return NGX_DONE; }
Приведенный выше код находится в обработчике содержимого модуля porny, функция ngx_http_read_client_request_body () вызывается в ngx_http_proxy_handler (), где ngx_http_upstream_init () передается в интерфейс как функция обратного вызова, а контекст вызова обработчика содержимого модуля ngx выглядит следующим образом:
ngx_int_t ngx_http_core_content_phase(ngx_http_request_t *r, ngx_http_phase_handler_t *ph) { ... if (r->content_handler) { r->write_event_handler = ngx_http_request_empty_handler; ngx_http_finalize_request(r, r->content_handler(r)); return NGX_OK; } ... }
В приведенном выше коде после вызова обработчика содержимого его возвращаемое значение используется в качестве параметра для вызова функции ngx_http_finalize_request (). Если тело запроса получено не полностью, функция ngx_http_read_client_request_body () возвращает NGX_AGttAIN. В это время обработчик содержимого, например ngp_proxy_handp Будет возвращен NGX_DONE, и передача NGX_DONE в качестве параметра функции ngx_http_finalize_request () приведет к уменьшению счетчика ссылок основного запроса на 1, поэтому он просто компенсирует увеличение счетчика основного запроса в начале функции ngx_http_read_client_request_body ().
Затем вернитесь к функции ngx_http_read_client_request_body (). Она проверит, было ли тело запроса прочитано или отклонено. Если это так, он напрямую вызовет функцию обратного вызова и вернет NGX_OK. Это фактически для проверки подзапроса. Запрос — это концепция в nginx. В nginx один или несколько новых подзапросов могут быть инициированы в текущем запросе для доступа к другим местоположениям. Конкретное введение подзапросов будет подробно проанализировано в следующих главах. В целом, подзапросы Запрос не должен сам по себе читать тело запроса.
Затем функция вызывает ngx_http_test_expect (), чтобы проверить, отправил ли клиент заголовок Expect: 100-continue. Если да, он ответит клиенту «HTTP / 1.1 100 Continue». Согласно протоколу HTTP 1.1, клиент может отправить заголовок Expect, чтобы указать на сервер Ожидается, что тело запроса будет отправлено. Если сервер разрешает клиенту отправить тело запроса, он ответит «HTTP / 1.1 100 Continue», и клиент начнет отправлять тело запроса, когда оно будет получено.
Затем продолжайте подготовку к получению тела запроса, выделите структуру ngx_http_request_body_t и сохраните ее в r-> request_body. Эта структура используется для хранения ссылки на кеш, ссылки на временный файл, оставшегося размера тела запроса и другой информации, используемой в процессе чтения тела запроса. Это определяется следующим образом:
typedef struct { ngx_temp_file_t *temp_file; ngx_chain_t *bufs; ngx_buf_t *buf; off_t rest; ngx_chain_t *to_write; ngx_http_client_body_handler_pt post_handler; } ngx_http_request_body_t;
temp_file: | Указатель на временный файл, в котором хранится тело запроса; |
---|---|
bufs: | Наведите указатель мыши на заголовок связанного списка, чтобы сохранить тело запроса; |
buf: | Укажите кэш памяти, который в настоящее время используется для сохранения тела запроса; |
rest: | Текущий оставшийся размер тела запроса; |
post_handler: | Сохраните функцию обратного вызова, переданную в функцию ngx_http_read_client_request_body (). |
После подготовки работы функция начинает проверять, есть ли у запроса заголовок content_length. Если такого заголовка нет или клиент отправляет заголовок content_length со значением 0, это указывает на отсутствие тела запроса. В этом случае вызовите функцию обратного вызова напрямую и верните NGX_OK. Конечно, если для инструкции client_body_in_file_only задано значение on и content_length равно 0, эта функция создаст пустой временный файл перед вызовом функции обратного вызова.
Вводя нижнюю часть функции, он указывает, что клиентский запрос действительно указывает, что тело запроса должно быть отправлено. Функция сначала проверит, было ли предварительно прочитано тело запроса при чтении заголовка запроса. Здесь проверка выполняется путем оценки кеша (r -> есть ли необработанные данные в header_in). Если есть предварительно считанные данные, выделите структуру ngx_buf_t и сохраните в ней предварительно считанные данные в r-> header_in, и если еще есть место в r-> header_in, и может вместить оставшееся непрочитанное тело запроса , Эти пространства будут по-прежнему использоваться без выделения новых кешей. Конечно, даже если тело запроса было полностью упреждающим чтением, нет необходимости продолжать обработку. В это время функция обратного вызова вызывается, а затем возвращается.
Если предварительное чтение данных отсутствует или предварительное чтение не завершено, эта функция выделит новую память (если r-> header_in не имеет достаточно свободного места), и если для инструкции request_body_in_single_buf установлено значение no, предварительно прочитанные данные будут скопированы. Во вновь открытом блоке памяти фактическая операция чтения тела запроса выполняется в функции ngx_http_do_read_client_request_body (), которая считывает тело запроса в цикле и сохраняет его в кеше. Если кеш заполнен, данные в нем будут очищены и Запишите обратно во временный файл. Конечно, возможно, что данные не могут быть прочитаны одновременно. Эта функция подключит событие чтения и установит обработчик события чтения на ngx_http_read_client_request_body_handler. Кроме того, ядро nginx также устанавливает тайм-аут между двумя событиями чтения тела запроса. Команда client_body_timeout может установить это По умолчанию время ожидания составляет 60 секунд. Если следующее событие чтения истечет, nginx вернет клиенту 408.
После окончательного чтения тела запроса, ngx_http_do_read_client_request_body () настроит тело запроса на ожидаемую позицию (память или файл) в соответствии с конфигурацией. Во всех случаях тело запроса можно получить из связанного списка bufs r-> request_body. Связанный список может содержать не более Два узла, каждый узел является буфером, но содержимое этого буфера может храниться в памяти или в файле на диске. Кроме того, переменная $ request_body может получить соответствующие данные только тогда, когда тело запроса было прочитано и все сохранено в памяти.
Отменить тело запроса
Если модуль хочет активно отбросить тело запроса, отправленное клиентом, он может вызвать интерфейс ngx_http_discard_request_body (), предоставляемый ядром nginx. Для активного отказа может быть много причин. Например, бизнес-логике модуля тело запроса не требуется, и клиент отправляет его. Тело запроса слишком велико.Кроме того, чтобы быть совместимым с конвейерным запросом протокола http1.1, модуль обязан активно отбрасывать ненужное тело запроса. Короче говоря, для обеспечения хорошей совместимости с клиентом nginx должен активно отбрасывать бесполезные тела запросов. Разберем функцию ngx_http_discard_request_body ():
ngx_int_t ngx_http_discard_request_body(ngx_http_request_t *r) { ssize_t size; ngx_event_t *rev; if (r != r->main || r->discard_body) { return NGX_OK; } if (ngx_http_test_expect(r) != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } rev = r->connection->read; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0, "http set discard body"); if (rev->timer_set) { ngx_del_timer(rev); } if (r->headers_in.content_length_n <= 0 || r->request_body) { return NGX_OK; } size = r->header_in->last - r->header_in->pos; if (size) { if (r->headers_in.content_length_n > size) { r->header_in->pos += size; r->headers_in.content_length_n -= size; } else { r->header_in->pos += (size_t) r->headers_in.content_length_n; r->headers_in.content_length_n = 0; return NGX_OK; } } r->read_event_handler = ngx_http_discarded_request_body_handler; if (ngx_handle_read_event(rev, 0) != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } if (ngx_http_read_discarded_request_body(r) == NGX_OK) { r->lingering_close = 0; } else { r->count++; r->discard_body = 1; } return NGX_OK; }
Поскольку функция небольшая, она перечислена здесь полностью. В начале функции она также оценивает ситуацию, которая не требует повторной обработки: подзапросы не нужно обрабатывать, а те, которые уже вызвали эту функцию, не нужно обрабатывать. Затем вызовите ngx_http_test_expect () для обработки ситуации ожидания http1.1. Согласно механизму ожидания http1.1, если клиент отправляет ожидаемый заголовок, а сервер не хочет получать тело запроса, он должен вернуть ошибку 417 (ожидание не выполнено). Nginx этого не делает, он просто просит клиента отправить тело запроса и отклонить его. Затем функция удаляет таймер на событии чтения, потому что тело запроса в это время не требуется, поэтому не имеет значения, отправляет ли его клиент быстро или медленно. Конечно, я расскажу об этом позже, когда nginx обработает запрос. Но когда клиент не отправил бесполезное тело запроса, nginx повесит таймер на событие чтения.
Если клиент намеревается отправить тело запроса, он должен отправить заголовок длины содержимого, поэтому функция проверяет заголовок длины содержимого в заголовке запроса, а также проверяет, читалось ли тело запроса где-либо еще. Если действительно существует тело запроса, подлежащее обработке, функция затем проверяет предварительно прочитанные данные в буфере заголовка запроса. Предварительно считанные данные будут напрямую отброшены. Конечно, если все тело запроса было предварительно прочитано, функция возвращается напрямую.
Затем, если есть оставшиеся тела запроса, которые не были обработаны, функция вызывает ngx_handle_read_event () для монтирования события чтения в механизме обработки событий и устанавливает для функции обработки событий чтения значение ngx_http_discarded_request_body_handler. После выполнения этих приготовлений функция наконец вызывает интерфейс ngx_http_read_discarded_request_body (), чтобы прочитать тело запроса от клиента и отклонить его. Если клиент не отправил тело запроса один раз, функция вернется, а оставшиеся данные будут переданы ngx_http_discarded_request_body_handler (), когда наступит следующее событие чтения. В это время discard_body запроса будет установлен в 1, чтобы идентифицировать это Эта ситуация. Кроме того, количество ссылок (счетчик) запроса также увеличивается на 1. Цель этого заключается в том, что клиент не может полностью отправить тело запроса, которое будет отправлено после того, как nginx обработал запрос. Увеличение ссылки предназначено для предотвращения того, чтобы ядро nginx выполнило его сразу после обработки запроса. Запрошенные связанные ресурсы.
Функция ngx_http_read_discarded_request_body () очень проста. Она считывает данные из ссылки и циклически отбрасывает их до тех пор, пока все данные в приемном буфере не будут прочитаны. Если тело запроса было прочитано, эта функция установит функцию обработки события чтения на ngx_http_block_reading. Функция удаляет только событие чтения, инициированное уровнем, чтобы предотвратить непрерывное срабатывание одного и того же события.
Наконец, взгляните на функцию обработки события чтения ngx_http_discarded_request_body_handler. Эта функция будет вызываться каждый раз, когда приходит событие чтения. Давайте посмотрим на ее исходный код:
void ngx_http_discarded_request_body_handler(ngx_http_request_t *r) { ... c = r->connection; rev = c->read; if (rev->timedout) { c->timedout = 1; c->error = 1; ngx_http_finalize_request(r, NGX_ERROR); return; } if (r->lingering_time) { timer = (ngx_msec_t) (r->lingering_time - ngx_time()); if (timer <= 0) { r->discard_body = 0; r->lingering_close = 0; ngx_http_finalize_request(r, NGX_ERROR); return; } } else { timer = 0; } rc = ngx_http_read_discarded_request_body(r); if (rc == NGX_OK) { r->discard_body = 0; r->lingering_close = 0; ngx_http_finalize_request(r, NGX_DONE); return; } /* rc == NGX_AGAIN */ if (ngx_handle_read_event(rev, 0) != NGX_OK) { c->error = 1; ngx_http_finalize_request(r, NGX_ERROR); return; } if (timer) { clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); timer *= 1000; if (timer > clcf->lingering_timeout) { timer = clcf->lingering_timeout; } ngx_add_timer(rev, timer); } }
Функция обрабатывает тайм-аут события чтения с самого начала. Было сказано, что таймер для события чтения был удален в функции ngx_http_discard_request_body (). Когда будет установлен таймер? Ответ заключается в том, что когда nginx обработал запрос, но не полностью отбросил тело запроса (клиент, возможно, не отправил его), в функции ngx_http_finalize_connection (), если проверено, что есть все еще не удаленное тело запроса Когда время, nginx добавит таймер события чтения, его продолжительность определяется командой lingering_timeout, по умолчанию — 5 секунд, но на этот раз — только тайм-аут между двумя событиями чтения, общее время ожидания тела запроса указывается командой lingering_time , По умолчанию — 30 секунд. В этом случае функция возвращается напрямую и отключается, если обнаруживает событие тайм-аута. Точно так же необходимо контролировать, чтобы продолжительность всего тела запроса отбрасывания не могла превышать время, заданное параметром lingering_time.Если максимальная длительность превышена, он будет напрямую возвращаться и отключаться.
Если событие чтения происходит до обработки запроса, нет необходимости обрабатывать событие тайм-аута или устанавливать таймер.Функция просто вызывает ngx_http_read_discarded_request_body () для чтения и отбрасывания данных.
Многоступенчатая обработка запроса
После прочтения заголовка запроса nginx переходит в этап обработки запроса. В простых случаях унифицированный указатель ресурса (url), отправленный клиентом, соответствует ресурсу на определенном пути на сервере. Все, что нужно сделать веб-серверу, — это сопоставить URL-адрес с путем к локальной файловой системе, а затем прочитать соответствующий файл и Вернитесь к клиенту. Но это только начальное требование Интернета.В настоящее время в Интернете возникли различные сложные требования, требующие от веб-серверов решения таких проблем, как безопасность и контроль доступа, мультимедийный контент и динамические веб-страницы. Эти сложные требования привели к тому, что веб-сервер больше не является короткой программой, а системой, которую необходимо тщательно спроектировать и разбить на модули. Хорошая модульность nginx отражается в его многоступенчатом разделении потока обработки запросов. Многоступенчатый поток обработки подобен конвейеру. Процесс nginx может одновременно обрабатывать несколько запросов на разных этапах. Nginx позволяет разработчикам регистрировать модули на любом этапе процесса обработки. На этапе запуска nginx организует все функции обработки модулей, зарегистрированные на каждом этапе, в цепочку выполнения по порядку.
На самом деле Nginx делит процесс обработки запроса на 11 этапов. Причина этого разделения состоит в том, чтобы разделить логику выполнения запроса. Каждый этап определяет четкую семантику выполнения в соответствии с временем обработки. Разработчики могут легко определить, какие модули им необходимо разработать. На каком этапе ниже описывается каждый этап:
NGX_HTTP_POST_READ_PHASE: | |
---|---|
Первый этап после получения заголовка запроса — перед перезаписью uri. Фактически, на этом этапе будет зарегистрировано очень мало модулей. По умолчанию этот этап пропускается; | |
NGX_HTTP_SERVER_REWRITE_PHASE: | |
Фаза перезаписи uri на уровне сервера, то есть выполнение инструкций перезаписи в серверном блоке и вне блока местоположения. В предыдущих главах объяснялось, что nginx найдет соответствующий виртуальный хост в соответствии с хостом и портом во время процесса чтения заголовка запроса. Конфигурация | |
NGX_HTTP_FIND_CONFIG_PHASE: | |
Найдите этап конфигурации местоположения. Этот этап использует перезаписанный uri для поиска соответствующего местоположения. Стоит отметить, что этот этап может выполняться несколько раз, потому что также могут быть инструкции перезаписи на уровне местоположения; | |
NGX_HTTP_REWRITE_PHASE: | |
Этап перезаписи uri уровня местоположения, на котором выполняются базовые инструкции перезаписи местоположения, также может выполняться несколько раз; | |
NGX_HTTP_POST_REWRITE_PHASE: | |
Последний этап перезаписи на уровне местоположения используется для проверки наличия перезаписи uri на предыдущем этапе и перехода к соответствующему этапу в соответствии с результатом; | |
NGX_HTTP_PREACCESS_PHASE: | |
Предыдущий этап контроля разрешений доступа, этот этап обычно используется для контроля доступа перед этапом контроля разрешений, например, ограничение частоты доступа, количества ссылок и т.д .; | |
NGX_HTTP_ACCESS_PHASE: | |
Этап контроля полномочий доступа, например контроль полномочий на основе черного и белого списка IP-адресов, контроль полномочий на основе имени пользователя и пароля и т.д .; | |
NGX_HTTP_POST_ACCESS_PHASE: | |
На последнем этапе управления доступом соответствующая обработка выполняется на этом этапе в соответствии с результатом выполнения этапа управления; | |
NGX_HTTP_TRY_FILES_PHASE: | |
Этап обработки инструкции try_files, если инструкция try_files не настроена, этот этап пропускается; | |
NGX_HTTP_CONTENT_PHASE: | |
На этапе генерации контента на этом этапе генерируется ответ и отправляется клиенту; | |
NGX_HTTP_LOG_PHASE: | |
Этап ведения журнала, на этом этапе записываются журналы доступа. |
Многоступенчатая цепочка исполнения
Nginx делит поток обработки на несколько этапов в соответствии с порядком выполнения обработки запроса. Как правило, каждый этап может регистрировать несколько функций обработки модуля. Nginx организует эти функции обработки в цепочку выполнения по этапам. Эта цепочка выполнения хранится в главном файле http. В поле phase_engine конфигурации (ngx_http_core_main_conf_t) тип поля phase_engine — ngx_http_phase_engine_t:
typedef struct { ngx_http_phase_handler_t *handlers; ngx_uint_t server_rewrite_index; ngx_uint_t location_rewrite_index; } ngx_http_phase_engine_t;
Поле обработчиков — это цепочка выполнения. Фактически, это массив, и каждый элемент объединен в связанный список, что позволяет процессу выполнения перемещаться вперед или назад. Структура данных узла цепочки выполнения определяется следующим образом :
struct ngx_http_phase_handler_s { ngx_http_phase_handler_pt checker; ngx_http_handler_pt handler; ngx_uint_t next; };
И средство проверки, и обработчик являются указателями на функции. Узлы на одном и том же этапе имеют одну и ту же функцию проверки. В поле обработчика хранится функция обработки модуля. Как правило, функция обработчика текущего узла выполняется в функции проверки, но исключениями являются NGX_HTTP_FIND_CONFIG_PHASE, NGX_HTTP_POST_REWRITEX_HASE_PGX_HTTP_POST_REWRITEX_PHASE_P Функции модуля не могут быть зарегистрированы на 4 этапах NGX_HTTP_TRY_FILES_PHASE. Следующее поле — это индекс быстрого перехода. В большинстве случаев поток выполнения выполняется вперед в порядке цепочки выполнения, но на некоторых этапах выполнения функции проверки может потребоваться вернуться к предыдущему этапу выполнения из-за выполнения определенной логики. Может потребоваться пропустить некоторые последующие этапы выполнения, и следующее поле содержит целевой индекс перехода.
Структура данных, относящаяся к созданию цепочки выполнения, хранится в основной конфигурации http, одна из которых — это поле фаз, а другая — поле phase_engine. Поле фаз представляет собой массив, количество элементов которого равно количеству фаз, то есть каждый элемент соответствует фазе. Каждый элемент массива фаз представляет собой динамический массив (ngx_array_t). Каждый раз, когда модуль регистрирует функцию обработки, только один элемент необходимо добавить в динамический массив соответствующей фазы, чтобы сохранить указатель функции обработки. Поскольку на некоторых этапах выполнения может возникнуть необходимость перейти назад или вперед, неудобно просто использовать два массива, поэтому nginx организует цепочку выполнения и сохраняет ее в поле phase_engine.Каждый узел содержит следующее поле. Он используется для сохранения индекса переходящего узла назначения, и создание цепочки выполнения завершается вызовом функции ngx_http_init_phase_handlers после фазы постконфигурации инициализации nginx. Функция анализируется ниже:
static ngx_int_t ngx_http_init_phase_handlers(ngx_conf_t *cf, ngx_http_core_main_conf_t *cmcf) { ngx_int_t j; ngx_uint_t i, n; ngx_uint_t find_config_index, use_rewrite, use_access; ngx_http_handler_pt *h; ngx_http_phase_handler_t *ph; ngx_http_phase_handler_pt checker; cmcf->phase_engine.server_rewrite_index = (ngx_uint_t) -1; cmcf->phase_engine.location_rewrite_index = (ngx_uint_t) -1; find_config_index = 0; use_rewrite = cmcf->phases[NGX_HTTP_REWRITE_PHASE].handlers.nelts ? 1 : 0; use_access = cmcf->phases[NGX_HTTP_ACCESS_PHASE].handlers.nelts ? 1 : 0; n = use_rewrite + use_access + cmcf->try_files + 1 /* find config phase */; for (i = 0; i < NGX_HTTP_LOG_PHASE; i++) { n += cmcf->phases[i].handlers.nelts; } ph = ngx_pcalloc(cf->pool, n * sizeof(ngx_http_phase_handler_t) + sizeof(void *)); if (ph == NULL) { return NGX_ERROR; } cmcf->phase_engine.handlers = ph; n = 0; for (i = 0; i < NGX_HTTP_LOG_PHASE; i++) { h = cmcf->phases[i].handlers.elts; switch (i) { case NGX_HTTP_SERVER_REWRITE_PHASE: if (cmcf->phase_engine.server_rewrite_index == (ngx_uint_t) -1) { cmcf->phase_engine.server_rewrite_index = n; } checker = ngx_http_core_rewrite_phase; break; case NGX_HTTP_FIND_CONFIG_PHASE: find_config_index = n; ph->checker = ngx_http_core_find_config_phase; n++; ph++; continue; case NGX_HTTP_REWRITE_PHASE: if (cmcf->phase_engine.location_rewrite_index == (ngx_uint_t) -1) { cmcf->phase_engine.location_rewrite_index = n; } checker = ngx_http_core_rewrite_phase; break; case NGX_HTTP_POST_REWRITE_PHASE: if (use_rewrite) { ph->checker = ngx_http_core_post_rewrite_phase; ph->next = find_config_index; n++; ph++; } continue; case NGX_HTTP_ACCESS_PHASE: checker = ngx_http_core_access_phase; n++; break; case NGX_HTTP_POST_ACCESS_PHASE: if (use_access) { ph->checker = ngx_http_core_post_access_phase; ph->next = n; ph++; } continue; case NGX_HTTP_TRY_FILES_PHASE: if (cmcf->try_files) { ph->checker = ngx_http_core_try_files_phase; n++; ph++; } continue; case NGX_HTTP_CONTENT_PHASE: checker = ngx_http_core_content_phase; break; default: checker = ngx_http_core_generic_phase; } n += cmcf->phases[i].handlers.nelts; for (j = cmcf->phases[i].handlers.nelts - 1; j >=0; j--) { ph->checker = checker; ph->handler = h[j]; ph->next = n; ph++; } } return NGX_OK; }
Первое, что следует отметить, это то, что все функции модуля, зарегистрированные до постконфигурации, хранятся в массиве cmcf-> phase. Вышеупомянутая функция сначала вычисляет количество узлов в цепочке выполнения и выделяет соответствующее пространство. Как упоминалось ранее, есть 4 фазы, которые не могут быть зарегистрированы. Module и два этапа POST_REWRITE и POST_ACCESS существуют только тогда, когда модуль зарегистрирован на этапах REWRITE и ACCESS. Кроме того, этап TRY_FILES существует только тогда, когда настроена инструкция try_files. Наконец, хотя модуль не может быть зарегистрирован на этапе FIND_CONFIG, это необходимо Существуют, поэтому эти факторы необходимо учитывать при расчете количества узлов цепочки выполнения.
После выделения памяти создается связанный список.Процесс очень прост: он просматривает функции модуля, зарегистрированные на каждом этапе, и назначает функции проверки, функции обработчика и следующие индексы узлам каждого этапа. Финальная цепочка выполнения выглядит следующим образом:
(Недоступен)
Следующее поле узла в фазе SERVER_REWRITE указывает на первый узел в фазе FIND_CONFIG, следующее поле в фазе REWRITE указывает на первый узел в фазе POST_REWRITE, а следующее поле в фазе POST_REWRITE указывает на FIND_CONFIG, потому что, когда происходит перезапись uri на уровне местоположения , Вам может потребоваться повторно сопоставить новое местоположение. Следующее поле в фазе PRECCESS указывает на поле ACCESS, а следующее поле в фазах ACCESS и POST_ACCESS указывает на фазу CONTENT. Конечно, если фаза TRY_FILES существует, она указывает на фазу TRY_FILES и, наконец, фазу CONTENT. Следующий домен указывает на этап LOG. Конечно, следующий домен используется функцией проверки каждой фазы в соответствии с потребностями фазы. Когда в этом нет необходимости, функция проверки может не использовать его.
Фаза POST_READ
Этап POST_READ — это первый этап в процессе запроса обработки nginx, на котором могут быть добавлены функции модуля.Любая логика, которая должна быть обработана сразу после получения заголовка запроса, может зарегистрировать функции обработки на этом этапе. В исходном коде nginx только модуль realip имеет зарегистрированные функции на этом этапе.Когда внешний интерфейс nginx имеет 7-уровневый уровень балансировки нагрузки и реальный IP-адрес клиента сохраняется в заголовке запроса внешним интерфейсом, этот модуль используется для замены IP-адреса клиента запросом. Значение сохранено в шапке. Причина, по которой модуль realip выполняется на этапе POST_READ, заключается в том, что ему необходимо незаметно заменить клиентский ip реальным значением до того, как будут выполнены другие модули, а необходимая информация — это только заголовок запроса. Как правило, на этапе POST_READ необходимо зарегистрировать очень мало модулей, а модуль realip по умолчанию не компилируется в nginx.
Функция проверки фазы POST_READ — ngx_http_core_generic_phase. Эта функция является функцией проверки по умолчанию для фазы nginx. Следующая фаза PRECCESS также использует проверку. Давайте представим ее:
ngx_int_t ngx_http_core_generic_phase(ngx_http_request_t *r, ngx_http_phase_handler_t *ph) { ngx_int_t rc; /* * generic phase checker, * used by the post read and pre-access phases */ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "generic phase: %ui", r->phase_handler); rc = ph->handler(r); if (rc == NGX_OK) { r->phase_handler = ph->next; return NGX_AGAIN; } if (rc == NGX_DECLINED) { r->phase_handler++; return NGX_AGAIN; } if (rc == NGX_AGAIN || rc == NGX_DONE) { return NGX_OK; } /* rc == NGX_ERROR || rc == NGX_HTTP_... */ ngx_http_finalize_request(r, rc); return NGX_OK; }
Логика этой функции очень проста. Вызов функции обработчика, зарегистрированной фазой. Вам нужно обратить внимание на то, как функция обрабатывает возвращаемое значение обработчика. Вообще говоря, обработчик возвращает:
NGX_OK: | Указывает, что этот этап обработан и его нужно перенести на следующий этап; |
---|---|
NG_DECLINED: | Указывает, что следующий обработчик должен быть переведен на этот этап для продолжения обработки; |
NGX_AGAIN, NGX_DONE: | |
Указывает, что вам нужно дождаться возникновения события, прежде чем вы сможете продолжить обработку (например, ожидание сетевого ввода-вывода). В это время, чтобы не блокировать обработку других запросов, Nginx должен прервать цепочку выполнения текущего запроса и дождаться, пока событие продолжит выполнение обработчика; | |
NGX_ERROR: | Указывает, что произошла ошибка и запрос нужно завершить. |
Функция проверки возвращает NGX_AGAIN или NGX_OK функции ngx_http_core_run_phases верхнего уровня в соответствии с различными возвращаемыми значениями функции-обработчика. Если вы ожидаете, что верхний уровень продолжит выполнение последующих этапов, вам необходимо убедиться, что функция проверки не возвращает NGX_OK. Различные функции проверки возвращают значения в функцию-обработчик. Обработка отличается. Вам необходимо убедиться, что функция проверки на соответствующем этапе обрабатывает возвращаемое значение в пределах ваших ожиданий при разработке модуля.
Фаза SERVER_REWRITE
Фаза SERVER_REWRITE — это первая важная фаза, которую необходимо пройти в nginx.Когда запрос входит в эту фазу, соответствующая конфигурация виртуального хоста (сервера) была найдена. Модуль перезаписи nginx регистрирует обработчик на этом этапе. Модуль перезаписи предоставляет инструкции перезаписи URL-адресов, перезапись, набор инструкций по установке переменных и инструкции логического управления, если, прервать и вернуться. Пользователи могут комбинировать эти инструкции в конфигурации сервера для удовлетворения своих потребностей. Нет необходимости писать другой модуль, например перенаправлять некоторые URI с префиксом, который соответствует определенному шаблону, на фиксированный URL. Вы также можете решить, нужно ли переписывать или отправлять определенный код возврата пользователю в соответствии с атрибутами запроса. Инструкции логического управления, обеспечиваемые перезаписью, могут удовлетворять некоторым простым требованиям.Для более сложной логики может потребоваться регистрация обработчика для удовлетворения требований посредством реализации независимого модуля.
Необходимо обратить внимание на разницу между этой фазой и последующей фазой REWRITE. На этапе SERVER_REWRITE запрос не был сопоставлен с конкретным местоположением. Результат выполнения этого этапа (например, перезаписанный uri) повлияет на выполнение следующего этапа FIND_CONFIG. Кроме того, этот этап также является первым этапом выполнения внутреннего подзапроса. Функция проверки фазы SERVER_REWRITE — ngx_http_core_rewrite_phase:
ngx_int_t ngx_http_core_rewrite_phase(ngx_http_request_t *r, ngx_http_phase_handler_t *ph) { ngx_int_t rc; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "rewrite phase: %ui", r->phase_handler); rc = ph->handler(r); if (rc == NGX_DECLINED) { r->phase_handler++; return NGX_AGAIN; } if (rc == NGX_DONE) { return NGX_OK; } /* NGX_OK, NGX_AGAIN, NGX_ERROR, NGX_HTTP_... */ ngx_http_finalize_request(r, rc); return NGX_OK; }
Эта функция в основном аналогична вышеупомянутой функции ngx_http_core_generic_phase. Единственное отличие состоит в том, что она немного обрабатывает возвращаемое значение обработчика. Например, здесь обработка NGX_OK заключается в вызове ngx_http_finalize_request для завершения запроса, поэтому я подчеркиваю, что возвращаемое значение функции-обработчика должно быть Установите в соответствии с функцией проверки различных фаз. Модуль перезаписи Nginx повесит обработчик с именем ngx_http_rewrite_handler.
Фаза FIND_CONFIG
Фаза FIND_CONFIG, как следует из названия, является фазой конфигурации поиска. Конкретный момент — найти конфигурацию местоположения в соответствии с uri. Фактически, это установить r-> loc_conf. До этого использовался уровень сервера, используемый r-> loc_conf. Процесс поиска местоположения завершался функцией ngx_http_core_find_location. Здесь процесс повторяться не будет. Вы можете обратиться к содержанию управления местоположением в предыдущей главе. Стоит отметить, что когда функция ngx_http_core_find_location возвращает NGX_DONE, Nginx вернет 301 для перенаправления запроса пользователя. Эта ситуация возникает только в этом местоположении. Proxy_pass / fastcgi / scgi / uwsgi / memcached, а имя местоположения заканчивается символом /, а запрошенный uri является префиксом местоположения, отличного от /, например, местоположение / xx /, если определенный запрос / xx обращается Местоположение будет перенаправлено в / xx /. Кроме того, расположение в Nginx можно определить как внутреннее, то есть внутреннее расположение, доступ к которому можно получить только с помощью подзапросов или внутренних переходов.
Найдя конфигурацию местоположения, Nginx вызывает функцию ngx_http_update_location_config для обновления соответствующей конфигурации запроса. Самое важное — обновить обработчик содержимого запроса. В разных местах могут быть свои собственные обработчики содержимого.
Наконец, из-за наличия REWRITE_PHASE фаза FIND_CONFIG может выполняться несколько раз.
Фаза ПЕРЕПИСАНИЯ
Этап REWRITE — это перезапись на уровне местоположения. Этап проверки и SERVER_REWRITE этого этапа — это одна и та же функция, а модуль перезаписи Nginx регистрирует один и тот же обработчик для этих двух этапов. Единственная разница между ними заключается в том, что время выполнения отличается, REWRITE Этап — это перезапись на уровне местоположения.После выполнения SERVER_REWRITE следует этап FIND_CONFIG, а выполнение этапа REWRITE — этап POST_REWRITE.
Фаза POST_REWRITE
Обработчики не могут быть зарегистрированы на этом этапе, просто проверьте, было ли выполнено переписывание uri на предыдущем этапе, если не было переписано, переходите непосредственно к следующему этапу; если переписано, используйте поле следующего перехода, чтобы перейти к этапу FIND_CONFIG Повторно запустить. Nginx ограничивает количество перезаписей uri, по умолчанию — 10 раз.
Этап PREACCESS
Переход на этот этап указывает на то, что Nginx определил запрос в определенное место (когда сервер не имеет никакого местоположения, он также может быть сервером), например, как была определена запрошенная конфигурация loc_conf, этот этап обычно используется для управления ресурсами по умолчанию Ниже такие модули, как ngx_http_limit_conn_module, ngx_http_limit_req_module, будут регистрировать обработчики на этом этапе для управления количеством подключений, частотой запросов и т. Д. Проверка, используемая на этапе PREACCESS, — это функция по умолчанию ngx_http_core_generic_phase.
Фаза ДОСТУПА
Основная цель этого этапа — управлять разрешениями.По умолчанию модули Nginx ngx_http_access_module и ngx_http_auth_basic_module регистрируют обработчик на этом этапе.
Средство проверки на этапе ACCESS — это функция ngx_http_core_access_phase. Эта функция обрабатывает возвращаемое значение обработчика примерно так же, как ngx_http_core_generic_phase. Особое место в том, что когда clcf-> execute — это NGX_HTTP_SATISFY_ALL, то есть требуется, когда проверка всех обработчиков на этом этапе требуется, когда проверка всех обработчиков При возврате NGX_OK на этом этапе необходимо продолжить обработку других обработчиков. Значение clcf-> удовлетворить можно указать с помощью команды удовлетворения.
Фаза POST_ACCESS
POST_ACCESS совпадает с этапом POST_REWRITE. Он просто обрабатывает результаты предыдущего этапа и не может смонтировать ваш собственный обработчик. В частности, если этап ACCESS возвращает NGX_HTTP_FORBIDDEN или NGX_HTTP_UNAUTHORIZED (записанный в поле r-> access_code), этот этап завершит запрос.
Этап TRY_FILES
Этап TRY_FILES вступает в силу только тогда, когда сконфигурирована инструкция try_files. Фактически, эта инструкция обычно не используется. Ее функция состоит в том, чтобы указать один или несколько файлов или каталогов. Последний параметр может быть указан как местоположение или код возврата. Когда установлена инструкция , Этап TRY_FILES вызывает функцию проверки ngx_http_core_try_files_phase, чтобы проверить, существует ли указанный файл или каталог соответственно. Если файл или каталог существует в локальной файловой системе, выйдите из этого этапа и продолжите следующий этап, в противном случае внутреннее перенаправление в расположение, указанное последним параметром Или верните указанный код возврата.
Обработчик не может быть зарегистрирован на этом этапе.
Этап СОДЕРЖАНИЕ
Можно сказать, что этап CONTENT является наиболее важным этапом во всей цепочке выполнения. Отсюда начинается запрос для выполнения бизнес-логики и генерации ответа. Давайте проанализируем его функцию проверки ниже:
ngx_int_t ngx_http_core_content_phase(ngx_http_request_t *r, ngx_http_phase_handler_t *ph) { size_t root; ngx_int_t rc; ngx_str_t path; if (r->content_handler) { r->write_event_handler = ngx_http_request_empty_handler; ngx_http_finalize_request(r, r->content_handler(r)); return NGX_OK; } ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "content phase: %ui", r->phase_handler); rc = ph->handler(r); if (rc != NGX_DECLINED) { ngx_http_finalize_request(r, rc); return NGX_OK; } /* rc == NGX_DECLINED */ ph++; if (ph->checker) { r->phase_handler++; return NGX_AGAIN; } /* no content handler was found */ if (r->uri.data[r->uri.len - 1] == '/') { if (ngx_http_map_uri_to_path(r, &path, &root, 0) != NULL) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "directory index of "%s" is forbidden", path.data); } ngx_http_finalize_request(r, NGX_HTTP_FORBIDDEN); return NGX_OK; } ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "no handler found"); ngx_http_finalize_request(r, NGX_HTTP_NOT_FOUND); return NGX_OK; }
Фаза CONTENT немного особенная, в отличие от других фаз, которые могут выполнять только фиксированную цепочку обработчиков, есть также специальный content_handler, каждое местоположение может иметь свой собственный независимый обработчик контента, а когда есть обработчик контента, фаза CONTENT будет выполнять только обработчик контента , На этом этапе цепочка обработчиков больше не выполняется.
По умолчанию Nginx вешает обработчики, такие как модуль индекса и модуль обработки статических файлов, в цепочку обработчиков этапа CONTENT. Кроме того, модуль также может установить независимый обработчик содержимого, например, инструкция proxy_pass модуля ngx_http_proxy_module установит обработчик содержимого с именем ngx_http_proxy_handler.
Затем взгляните на поток выполнения функции проверки выше. Сначала проверьте, установлен ли r-> content_handler. Если он установлен, выполните его. Обратите внимание, что перед его выполнением Nginx устанавливает для r-> write_event_handler значение ngx_http_request_empty_handler. Давайте посмотрим на значение перед установкой r-> write_event_handler. В функции ngx_http_handler оно установлено на ngx_http_core_run_phases, а ngx_http_core_run_phases будет запускать функцию проверки каждой фазы. В обычном процессе, если на определенном этапе необходимо дождаться события записи, обработчик этого этапа вернет NGX_OK, чтобы прервать работу ngx_http_core_run_phases. Когда наступит следующее событие записи, он продолжит выполнение обработчика предыдущего этапа; когда r- выполняется В процессе> content_handler модуль по умолчанию Nginx будет обрабатывать значение r-> write_event_handler, то есть, предполагая, что r-> content_handler может быть выполнен только один раз, если обработчик контента, установленный модулем, включает операции ввода-вывода, вам необходимо установить разумные настройки для обработки чтения Обработчик событий записи (r-> read_event_handler и r-> write_event_handler).
Также следует отметить, что после выполнения r-> content_handler Nginx напрямую вызывает функцию ngx_http_finalize_request с ее возвращаемым значением. Nginx концентрирует много логики связывания в этой функции, включая длинные соединения, lingering_close и подзапросы. Эта функция участвует в обработке, и будет отдельный раздел, посвященный этой функции. Здесь следует напомнить, что если r-> content_handler не завершил обработку всего запроса, а просто нужно дождаться возникновения события и выхода из потока обработки, он должен вернуть соответствующее значение в ngx_http_finalize_request, вообще говоря, он возвращает NGX_DONE, и Необходимо добавить 1 к счетчику ссылок запроса (r-> count), чтобы гарантировать, что функция ngx_http_finalize_request не освободит запрос.
Другая часть функции обрабатывает случай цепочки обработчиков.Особое место в том, что фаза CONTENT является последней фазой функции ngx_http_core_run_phases. Если последний обработчик возвращает NGX_DECLINED, то Nginx вернет NGX_HTTP_FORBIDDEN (403) или NGX_HTTP_NOT_OK (403) или NGX_HTTP_NOT_ клиенту. .
Этап LOG
Основная цель фазы LOG — записать журнал доступа.Вход на эту фазу указывает, что ответ на запрос был отправлен в системный буфер отправки. Кроме того, цепочка обработчиков на этом этапе фактически выполняется не в функции ngx_http_core_run_phases, а в функции ngx_http_free_request, которая освобождает запрошенный ресурс. Причина этого на самом деле состоит в том, чтобы упростить процесс, поскольку ngx_http_core_run_phases может выполняться только несколько раз, в то время как этап LOG Необходимо запросить, чтобы вся логика была завершена, так что это очень хороший выбор для запуска цепочки обработчиков этапа LOG в функции ngx_http_free_request. Конкретная функция выполнения — ngx_http_log_request:
static void ngx_http_log_request(ngx_http_request_t *r) { ngx_uint_t i, n; ngx_http_handler_pt *log_handler; ngx_http_core_main_conf_t *cmcf; cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); log_handler = cmcf->phases[NGX_HTTP_LOG_PHASE].handlers.elts; n = cmcf->phases[NGX_HTTP_LOG_PHASE].handlers.nelts; for (i = 0; i < n; i++) { log_handler[i](r); } }
Функция очень проста, она просто просматривает цепочку обработчиков на этапе LOG, выполняется один за другим и не проверяет возвращаемое значение. Есть два отличия между фазой LOG и другими фазами: во-первых, точка выполнения находится в ngx_http_free_request, а во-вторых, все обработчики на этой фазе будут выполнены.
До сих пор были представлены различные этапы многоступенчатой цепочки выполнения обработки запросов Nginx.Очень важно понимать время выполнения каждого этапа и различные характеристики каждого этапа для написания модулей.
Nginx filter
Данные, сгенерированные на этапе CONTENT, будут отфильтрованы перед отправкой клиенту (системный буфер отправки). Принцип работы фильтра Nginx аналогичен приготовлению рыбы. Например, рыбу можно нарезать рыбным филе (также можно нарезать кусочками, нарезать пюре), а затем использовать различные методы приготовления, чтобы получить вареную рыбу, японские сашими или отходы и т. Д. Это одна и та же рыба, но результаты, получаемые при обработке, совершенно разные, потому что различные процессы в середине наделяют рыбу различными свойствами. Фильтр Nginx также такой же. Предыдущий обработчик похож на эту рыбу. Фильтр отвечает за обработку. Окончательный HTTP-ответ будет различным. Формат может быть JSON или YAML. Содержимое может быть более или менее. Атрибуты HTTP могут быть разными. Однако вы можете выбрать сжатие, и даже содержимое можно отбросить.
В соответствии с заголовком ответа и телом ответа HTTP-запроса, Nginx устанавливает фильтр заголовка и фильтр тела соответственно. Оба механизма используют связанный список.Различные модули фильтрации соответствуют узлу связанного списка.Обще говоря, модуль регистрирует фильтр заголовка и фильтр тела одновременно. Типичный модуль фильтра, такой как модуль gzip, для регистрации использует код, подобный следующему:
static ngx_http_output_header_filter_pt ngx_http_next_header_filter; static ngx_http_output_body_filter_pt ngx_http_next_body_filter; ... static ngx_int_t ngx_http_gzip_filter_init(ngx_conf_t *cf) { ngx_http_next_header_filter = ngx_http_top_header_filter; ngx_http_top_header_filter = ngx_http_gzip_header_filter; ngx_http_next_body_filter = ngx_http_top_body_filter; ngx_http_top_body_filter = ngx_http_gzip_body_filter; return NGX_OK; }
В приведенном выше коде модуль gzip сначала объявляет две статические глобальные переменные ngx_http_next_header_filter и ngx_http_next_body_filter в начале модуля. В функции ngx_http_gzip_filter_init этим двум переменным назначаются значения ngx_header_http_body и ngx_header_http_body. Последние два определены в ngx_http.c и экспортируются в заголовочный файл ngx_http.h. ngx_http_top_header_filter и ngx_http_top_body_filter на самом деле являются головными узлами списка фильтров.Каждый раз, когда регистрируется новый модуль фильтра, их значения сначала сохраняются во внутренних глобальных переменных ngx_http_next_header_filter и ngx_http_next_body_filter, а затем регистрируются в новом модуле filter и назначаются новым модулем filter. , И фильтр Nginx сначала выполняется с головного узла, поэтому модуль, зарегистрированный позже, выполняется раньше.
С параметрами компиляции по умолчанию Nginx по умолчанию скомпилирует следующие модули:
ngx_module_t *ngx_modules[] = { &ngx_core_module, &ngx_errlog_module, &ngx_conf_module, &ngx_events_module, &ngx_event_core_module, &ngx_epoll_module, &ngx_regex_module, &ngx_http_module, &ngx_http_core_module, &ngx_http_log_module, &ngx_http_upstream_module, &ngx_http_static_module, &ngx_http_autoindex_module, &ngx_http_index_module, &ngx_http_auth_basic_module, &ngx_http_access_module, &ngx_http_limit_conn_module, &ngx_http_limit_req_module, &ngx_http_geo_module, &ngx_http_map_module, &ngx_http_split_clients_module, &ngx_http_referer_module, &ngx_http_rewrite_module, &ngx_http_proxy_module, &ngx_http_fastcgi_module, &ngx_http_uwsgi_module, &ngx_http_scgi_module, &ngx_http_memcached_module, &ngx_http_empty_gif_module, &ngx_http_browser_module, &ngx_http_upstream_ip_hash_module, &ngx_http_upstream_keepalive_module, & ngx_http_write_filter_module, / * Последний фильтр тела, отвечающий за отправку данных * / & ngx_http_header_filter_module, / * Последний фильтр заголовка отвечает за разделение полного заголовка HTTP-ответа в памяти, И вызовите ngx_http_write_filter для отправки * / & ngx_http_chunked_filter_module, / * Для запросов без заголовка content_length в заголовке ответа принудительно установить короткое соединение (ниже, чем http 1.1) Или используйте фрагментированную кодировку (http 1.1) * / & ngx_http_range_header_filter_module, / * фильтр заголовка, отвечающий за обработку заголовка диапазона * / & ngx_http_gzip_filter_module, / * Поддержка сжатия потоковых данных * / & ngx_http_postpone_filter_module, / * фильтр тела, отвечающий за обработку порядка вывода данных подзапроса и основного запроса * / & ngx_http_ssi_filter_module, / * Поддержка фильтрации запросов SSI с использованием метода инициирования подзапросов для получения включенных файлов * / & ngx_http_charset_filter_module, / * поддерживает добавление кодировки, а также поддерживает преобразование содержимого из одного набора символов в другой * / & ngx_http_userid_filter_module, / * Поддержка добавления файлов cookie для идентификации пользователей для статистики * / & ngx_http_headers_filter_module, / * Поддержка настройки истечения срока действия и заголовков Cache-control, поддержка добавления заголовков с произвольными именами * / & ngx_http_copy_filter_module, / * Повторное копирование некоторых узлов в выходной связанный список в соответствии с требованиями (Например, прочтите узел in_file из файла и скопируйте его в новый узел) и передайте его следующему фильтру Для обработки */ & ngx_http_range_body_filter_module, / * фильтр тела, поддерживает функцию диапазона, если запрос содержит запрос диапазона, Затем отправляйте только часть контента, запрошенную диапазоном * / & ngx_http_not_modified_filter_module, / * Если запрошенное значение if-modified-Since равно последнему измененному значению в ответе, Объясните, что ответ не изменился, очистите все содержимое ответа и верните 304 * / NULL };
По названию модулей легко увидеть, какие модули являются фильтрующими модулями.В общем, имя фильтрующего модуля Nginx заканчивается на filter_module, а имя обычных модулей заканчивается на module. Если посмотреть на приведенный выше список снизу вверх, ngx_http_not_modified_filter_module фактически является первым узлом в цепочке фильтров, а ngx_http_write_filter_module — последним узлом. Порядок выполнения модуля фильтра особенно важен. Например, после того, как данные проходят через модуль gzip, они становятся сжатыми данными. Если модулю фильтра, работающему за модулем gzip, необходимо просмотреть исходное содержимое данных, это невозможно (если он не распакован). Сторонние модули будут зарегистрированы Nginx после ngx_http_copy_filter_module и до ngx_http_headers_filter_module. Причина этого параметра заключается в том, чтобы гарантировать, что некоторые модули, такие как фильтр gzip, фильтр по частям и фильтр копирования, работают в начале или в конце цепочки фильтров.
анализ фильтра заголовков
Обычно Nginx вызывает функцию ngx_http_send_header для отправки заголовков ответов, посмотрите на ее реализацию:
ngx_int_t ngx_http_send_header(ngx_http_request_t *r) { if (r->err_status) { r->headers_out.status = r->err_status; r->headers_out.status_line.len = 0; } return ngx_http_top_header_filter(r); }
Приведенный выше код вызывает ngx_http_top_header_filter, который является головным узлом фильтра заголовков. В соответствии с порядком, представленным в предыдущем разделе, ngx_http_not_modified_filter_module является последним зарегистрированным модулем фильтра, и последний определенный модуль будет выполнен первым. После инициализации он фактически будет ngx_http_not_filter функция:
static ngx_int_t ngx_http_not_modified_header_filter(ngx_http_request_t *r) { if (r->headers_out.status != NGX_HTTP_OK || r != r->main || r->headers_out.last_modified_time == -1) { return ngx_http_next_header_filter(r); } if (r->headers_in.if_unmodified_since) { return ngx_http_test_precondition(r); } if (r->headers_in.if_modified_since) { return ngx_http_test_not_modified(r); } return ngx_http_next_header_filter(r); }
В функции ngx_http_not_modified_header_filter она вызовет переменную указателя функции ngx_http_next_header_filter, определенную в модуле, и эта переменная сохраняет функцию фильтра заголовка, зарегистрированную предыдущим модулем, и та же самая следующая функция фильтра заголовка также вызовет ngx_http_next_header_filter внутри модуля. Пока не будет вызван последний фильтр заголовка-ngx_http_header_filter.
ngx_http_header_filter, этот фильтр отвечает за вычисление общего размера заголовков ответов, выделение памяти, сборку заголовков ответов и вызов ngx_http_write_filter для отправки. В Nginx фильтр заголовка будет вызываться только один раз. Функция ngx_http_header_filter сначала проверяет, установлен ли флаг r-> header_sent, и если он установлен, он вернется напрямую; в противном случае будет установлен флаг и будет отправлен заголовок ответа. Кроме того, если это подзапрос, функция также выйдет напрямую.
анализ фильтра тела
Nginx обычно вызывает функцию ngx_http_output_filter для отправки тела ответа, и ее реализация выглядит следующим образом:
ngx_int_t ngx_http_output_filter(ngx_http_request_t *r, ngx_chain_t *in) { ngx_int_t rc; ngx_connection_t *c; c = r->connection; ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, "http output filter "%V?%V"", &r->uri, &r->args); rc = ngx_http_top_body_filter(r, in); if (rc == NGX_ERROR) { /* NGX_ERROR may be returned by any filter */ c->error = 1; } return rc; }
Принцип вызова цепочки основного фильтра такой же, как и у фильтра заголовка. Отличие от функции ngx_http_send_header состоит в том, что указанная выше функция имеет дополнительный тип ngx_chain_t.*Поскольку Nginx реализует потоковый вывод, ему не нужно ждать, пока будет сгенерировано все тело ответа, прежде чем отправлять данные клиенту. Вместо этого после генерации части содержимого организуйте его в связанный список, вызовите ngx_http_output_filter для отправки и содержимое, которое будет отправлено Это может быть файл или память. Nginx будет отвечать за потоковую передачу данных и их эффективную передачу. А когда буфер отправки заполнен, Nginx также будет отвечать за сохранение неотправленных данных. Вызывающей стороне нужно только один раз вызвать ngx_http_output_filter для новых данных.
ngx_http_copy_filter_module анализ
ngx_http_copy_filter_module — очень важный модуль в цепочке фильтров тела ответа. Этот модуль фильтра в основном используется для копирования некоторых буферов (возможно, в файлах или в памяти), которые необходимо скопировать в последующий фильтр. Модульная обработка. Сначала посмотрите на его функцию инициализации:
static ngx_int_t ngx_http_copy_filter_init(ngx_conf_t *cf) { ngx_http_next_body_filter = ngx_http_top_body_filter; ngx_http_top_body_filter = ngx_http_copy_filter; return NGX_OK; }
Как видите, он зарегистрировал только основной фильтр, а не фильтр заголовка, что означает, что этот модуль находится только в цепочке основного фильтра.
В этом модуле есть команда, имя которой — output_buffers, которая используется для настройки количества доступных буферов и размера буфера. Ее значение хранится в поле bufs конфигурации loc conf фильтра копирования. Значение по умолчанию — 1, а размер — 32768 байт. Конкретная функция этого параметра будет представлена позже.
В Nginx модуль общего фильтра может установить контекст (контекст) модуля в функции фильтрации заголовка в соответствии с заголовком ответа на запрос, чтобы сохранить соответствующую информацию, и использовать этот контекст в функции фильтрации тела. Фильтр копирования не имеет фильтра заголовка, поэтому его контекст инициализируется в фильтре тела, а его ctx — ngx_output_chain_ctx_t. Почему такое имя output_chain? Это связано с тем, что основная логическая обработка фильтра копирования помещается в модуль ngx_output_chain Кроме того, этот модуль находится в основном каталоге, а не в каталоге http.
Затем взгляните на структуру контекста, упомянутую выше:
struct ngx_output_chain_ctx_s { ngx_buf_t * buf; / * сохраняем временный buf * / ngx_chain_t * in; / * Цепочка для отправки сохраняется * / ngx_chain_t * free; / * Отправленная цепочка сохраняется для повторного использования * / ngx_chain_t * busy; / * Цепочка, которая не была отправлена, сохраняется * / unsigned sendfile: 1; / * тег sendfile * / unsigned directio: 1; / * маркер направления * / #if (NGX_HAVE_ALIGNED_DIRECTIO) unsigned unaligned:1; #endif unsigned need_in_memory: 1; / * Нужно ли сохранять копию в памяти (если вы используете sendfile, В памяти нет копий файлов, и нам иногда приходится обрабатывать файлы, Этот флаг необходимо установить сейчас) * / unsigned need_in_temp: 1; / * Нужно ли делать копию в памяти, независимо от того, находится ли buf в памяти или в файле, В этом случае последующие модули могут напрямую изменять эту память * / #if (NGX_HAVE_FILE_AIO) unsigned aio:1; ngx_output_chain_aio_pt aio_handler; #endif off_t alignment; ngx_pool_t *pool; ngx_int_t selected; / * Количество разделенных буферов * / ngx_bufs_t bufs; / * соответствует bufs, установленным в loc conf * / ngx_buf_tag_t tag; / * Тег модуля, в основном используется для восстановления buf * / ngx_output_chain_filter_pt output_filter; / * Обычно это ngx_http_next_filter, то есть продолжает вызывать цепочку фильтров * / void * filter_ctx; / * Контекст текущего фильтра, Это связано с тем, что восходящий поток также будет вызывать output_chain * / };
Чтобы лучше понять конкретное значение каждого поля структуры контекста, проанализируйте конкретную реализацию фильтра:
static ngx_int_t ngx_http_copy_filter(ngx_http_request_t *r, ngx_chain_t *in) { ngx_int_t rc; ngx_connection_t *c; ngx_output_chain_ctx_t *ctx; ngx_http_core_loc_conf_t *clcf; ngx_http_copy_filter_conf_t *conf; c = r->connection; ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, "http copy filter: "%V?%V"", &r->uri, &r->args); / * Получить ctx * / ctx = ngx_http_get_module_ctx(r, ngx_http_copy_filter_module); / * Если он пуст, это означает, что ctx необходимо инициализировать * / if (ctx == NULL) { ctx = ngx_pcalloc(r->pool, sizeof(ngx_output_chain_ctx_t)); if (ctx == NULL) { return NGX_ERROR; } ngx_http_set_ctx(r, ctx, ngx_http_copy_filter_module); conf = ngx_http_get_module_loc_conf(r, ngx_http_copy_filter_module); clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); / * Установить файл отправки * / ctx->sendfile = c->sendfile; / * Если в запросе установлен filter_need_in_memory, это поле ctx будет установлено * / ctx->need_in_memory = r->main_filter_need_in_memory || r->filter_need_in_memory; / * Аналогично описанному выше * / ctx->need_in_temp = r->filter_need_temporary; ctx->alignment = clcf->directio_alignment; ctx->pool = r->pool; ctx->bufs = conf->bufs; ctx->tag = (ngx_buf_tag_t) &ngx_http_copy_filter_module; / * Вы можете видеть, что output_filter - это следующий узел фильтра тела * / ctx->output_filter = (ngx_output_chain_filter_pt) ngx_http_next_body_filter; / * На данный момент ctx фильтра является текущим запросом * / ctx->filter_ctx = r; ... if (in && in->buf && ngx_buf_size(in->buf)) { r->request_output = 1; } } ... for ( ;; ) { / * Наиболее важная функция будет подробно проанализирована ниже * / rc = ngx_output_chain(ctx, in); if (ctx->in == NULL) { r->buffered &= ~NGX_HTTP_COPY_BUFFERED; } else { r->buffered |= NGX_HTTP_COPY_BUFFERED; } ... return rc; } }
Приведенный выше код удаляет части, связанные с AIO. Функция сначала устанавливает и инициализирует контекст, а затем вызывает функцию ngx_output_chain. Эта функция фактически содержит основную логику модуля фильтра копирования. Ее прототип:
ngx_int_t ngx_output_chain(ngx_output_chain_ctx_t *ctx, ngx_chain_t *in)
Посмотрите на его код в разделах. Следующий код представляет собой короткий путь, что означает, что, когда вы можете напрямую определить, что все в цепочке не нужно копировать, вы можете напрямую вызвать output_filter, чтобы задать оставшиеся фильтры Иметь дело с:
if (ctx->in == NULL && ctx->busy == NULL) { /* * the short path for the case when the ctx->in and ctx->busy chains * are empty, the incoming chain is empty too or has the single buf * that does not require the copy */ if (in == NULL) { return ctx->output_filter(ctx->filter_ctx, in); } if (in->next == NULL #if (NGX_SENDFILE_LIMIT) && !(in->buf->in_file && in->buf->file_last > NGX_SENDFILE_LIMIT) #endif && ngx_output_chain_as_is(ctx, in->buf)) { return ctx->output_filter(ctx->filter_ctx, in); } }
Вы можете увидеть функцию ngx_output_chain_as_is выше. Эта функция очень важна и будет снова вызвана ниже. Эта функция в основном используется для определения необходимости копирования buf. Верните 1, чтобы указать, что копия не требуется, в противном случае требуется копия:
static ngx_inline ngx_int_t ngx_output_chain_as_is(ngx_output_chain_ctx_t *ctx, ngx_buf_t *buf) { ngx_uint_t sendfile; / * Является ли это специальным буфером (special buf), если да, вернуть 1, то есть копия не требуется * / if (ngx_buf_special(buf)) { return 1; } / * Если в файле есть buf и используется directio, вам нужно скопировать buf * / if (buf->in_file && buf->file->directio) { return 0; } / * тег sendfile * / sendfile = ctx->sendfile; #if (NGX_SENDFILE_LIMIT) / * Если pos больше, чем предел sendfile, установить отметку 0 * / if (buf->in_file && buf->file_pos >= NGX_SENDFILE_LIMIT) { sendfile = 0; } #endif if (!sendfile) { / * Если вы не переходите в sendfile и buf отсутствует в памяти, нам нужно скопировать его в память * / if (!ngx_buf_in_memory(buf)) { return 0; } buf->in_file = 0; } / * Если вам нужна копия в памяти, но не в памяти, вернуть в это время 0, указывая, что вам нужно скопировать * / if (ctx->need_in_memory && !ngx_buf_in_memory(buf)) { return 0; } / * Если вам нужна модифицируемая копия в памяти, а buf существует в постоянной памяти или в mmap, верните 0 * / if (ctx->need_in_temp && (buf->memory || buf->mmap)) { return 0; } return 1; }
Есть два флага, на которые следует обратить внимание, один — need_in_memory, он в основном используется при использовании sendfile, Nginx не копирует запрошенный файл в память, и иногда необходимо манипулировать содержимым файла, этот флаг необходимо установить в это время . Затем следующий основной фильтр может управлять содержимым.
Второй — need_in_temp. Он в основном используется для создания модифицируемой копии buf, изначально существующей в памяти. Используемые здесь модули представляют собой кодировку, которая является фильтром кодека.
Затем следующий абзац — скопировать по цепочке в конец ctx-> in. Это добавить копию, вызвав ngx_output_chain_add_copy. Эта функция относительно проста и здесь не будет анализироваться, но есть только одно место, на которое следует обратить внимание, а именно, если buf существует в файле, а file_pos превышает ограничение для файла отправки. В это время buf разделен на два буфера, затем сохраняется в двух цепочках и, наконец, подключается:
/* add the incoming buf to the chain ctx->in */ if (in) { if (ngx_output_chain_add_copy(ctx->pool, &ctx->in, in) == NGX_ERROR) { return NGX_ERROR; } }
Затем идет этап основной логической обработки. Здесь nginx очень умен и очень сложен, сначала повторное использование цепочки, а затем повторное использование buf.
Давайте сначала посмотрим на повторное использование цепочки. Несколько ключевых структур и доменов: свободный, занятый ctx и домен цепочки ctx-> pool.
Цепочка, которая не была отправлена каждый раз, переводится в состояние занятости, а та, которая была отправлена, становится свободной, и, наконец, вызывается ngx_free_chain, чтобы поместить свободную цепочку в pool-> chain, а в ngx_alloc_chain_link, Если в цепочке pool-> есть цепочка, malloc не нужен, но напрямую возвращается в цепочку pool->. Соответствующий код выглядит следующим образом:
/ * Ссылка cl на пул-> цепочку * / #define ngx_free_chain(pool, cl) cl->next = pool->chain; pool->chain = cl / * выделить цепочку из пула * / ngx_chain_t * ngx_alloc_chain_link(ngx_pool_t *pool) { ngx_chain_t *cl; cl = pool->chain; / * Если cl существует, вернуть cl напрямую * / if (cl) { pool->chain = cl->next; return cl; } / * В противном случае это вызовет цепочку malloc * / cl = ngx_palloc(pool, sizeof(ngx_chain_t)); if (cl == NULL) { return NULL; } return cl; }
Затем следует повторное использование buf. Строго говоря, повторное использование buf получается из цепочки в free. Когда buf в free повторно используется, цепочка, соответствующая этому buf, будет связана с ctx-> pool, таким образом, это Цепочка будет использоваться повторно. Другими словами, первое соображение — это повторное использование buf.Только тогда, когда не нужно повторно использовать buf этой цепочки (или она была повторно использована), цепочка будет связана с ctx-> pool для повторного использования.
Также имеется выделенное поле ctx. Это поле указывает, сколько буферов было выделено в текущем контексте. Команда output_buffer используется для установки размера выходного буфера и количества буферов. Если выделенный больше, чем output_buffer, вам нужно сначала отправить существующий буфер, а затем снова перераспределить буфер.
Глядя на код, в нем ясно видны упомянутые выше элементы управления повторным использованием и buf. Следующий абзац в основном посвящен некоторой работе, проделанной перед копированием buf, такой как определение необходимости копирования, предоставление буфера памяти в децибелах и т. Д .:
/ * out - это цепочка, которую необходимо передать в конечном итоге, то есть цепочка, которая передается оставшимся фильтрам для обработки * / out = NULL; / * last_out - последняя цепочка out * / last_out = &out; last = NGX_NONE; for ( ;; ) { / * Начать обход цепочки * / while (ctx->in) { / * Получить размер буфера текущей цепочки * / bsize = ngx_buf_size(ctx->in->buf); / * Пропустить buf с размером 0 * / if (bsize == 0 && !ngx_buf_special(ctx->in->buf)) { ngx_debug_point(); ctx->in = ctx->in->next; continue; } / * Определяем, нужно ли копировать buf * / if (ngx_output_chain_as_is(ctx, ctx->in->buf)) { /* move the chain link to the output chain */ / * Если вам не нужно копировать, напрямую соедините цепочку с выходом, а затем продолжите цикл * / cl = ctx->in; ctx->in = cl->next; *last_out = cl; last_out = &cl->next; cl->next = NULL; continue; } / * Прибыл сюда, указывая, что нам нужно скопировать buf, где buf в конечном итоге будет скопирован в ctx-> buf, Поэтому сначала оцените, пуст ли ctx-> buf * / if (ctx->buf == NULL) { / * Если он пуст, получить buf. Обратите внимание. Вообще говоря, если директива не включена, Эта функция вернет NGX_DECLINED * / rc = ngx_output_chain_align_file_buf(ctx, bsize); if (rc == NGX_ERROR) { return NGX_ERROR; } / * В большинстве случаев он попадет в эту ветку * / if (rc != NGX_OK) { / * Готовимся к выделению buf, сначала ищем buf, который можно повторно использовать бесплатно * / if (ctx->free) { /* get the free buf */ / * получаем бесплатный буфер * / cl = ctx->free; ctx->buf = cl->buf; ctx->free = cl->next; / * Свяжите цепочку, которую нужно повторно использовать, с ctx-> poll, чтобы облегчить повторное использование цепочки * / ngx_free_chain(ctx->pool, cl); } else if (out || ctx->allocated == ctx->bufs.num) { / * Если оно равно предельному количеству буферов, выскакиваем из цикла и отправляем существующий буфер. Здесь вы можете видеть, что если out существует, nginx выскочит из цикла и затем отправит его, Он будет обработан снова при отправке. Это хороший пример потоковой передачи nginx * / break; } else if (ngx_output_chain_get_buf(ctx, bsize) != NGX_OK) { / * Вышеупомянутая функция также важна, она используется для получения buf. Далее я подробно рассмотрю эту функцию * / return NGX_ERROR; } } } / * Копируем содержимое из исходного буфера или считываем содержимое из файла * / rc = ngx_output_chain_copy_buf(ctx); if (rc == NGX_ERROR) { return rc; } if (rc == NGX_AGAIN) { if (out) { break; } return rc; } /* delete the completed buf from the ctx->in chain */ if (ngx_buf_size(ctx->in->buf) == 0) { ctx->in = ctx->in->next; } / * Назначаем новый узел цепочки * / cl = ngx_alloc_chain_link(ctx->pool); if (cl == NULL) { return NGX_ERROR; } cl->buf = ctx->buf; cl->next = NULL; *last_out = cl; last_out = &cl->next; ctx->buf = NULL; } ... }
В приведенном выше анализе кода есть очень важная функция, то есть ngx_output_chain_get_buf, эта функция используется для выделения buf, когда нет многоразового buf.
Если текущий буфер буфера расположен в последней цепочке, требуется специальная обработка, одно — это повторно используемое поле буфера, а другое — размер буфера, который должен быть выделен.
Давайте сначала поговорим о поле «recycled». Это поле указывает на то, что текущий buf необходимо переработать. Как правило, Nginx (например, не последний buf) кэширует часть buf (по умолчанию 1460 байт), а затем отправляет ее, и если установлено значение «recycled», ему не разрешено кэшировать buf, то есть пытаться отправить его, а затем Для переработки. Следовательно, если это последний buf, вам не нужно устанавливать повторно используемое поле, в противном случае вам нужно установить повторно используемое поле.
Тогда есть размер buf. Будет два размера: один — это размер буфера, который нужно скопировать, а другой — размер, установленный в файле конфигурации. Если это не последний буфер, вам нужно только выделить размер буфера, установленного в конфигурации. Если это последний буфер, обработка будет другой. Следующий код увидит:
static ngx_int_t ngx_output_chain_get_buf(ngx_output_chain_ctx_t *ctx, off_t bsize) { size_t size; ngx_buf_t *b, *in; ngx_uint_t recycled; in = ctx->in->buf; / * Здесь вы можете увидеть выделенный буфер, размер каждого буфера - это размер, установленный в файле конфигурации * / size = ctx->bufs.size; / * По умолчанию установлен переработанный домен * / recycled = 1; / * Если текущий буфер принадлежит последней цепочке, требуется специальная обработка * / if (in->last_in_chain) { / * Если размер буфера меньше размера, указанного в конфигурации, он будет выделен непосредственно в соответствии с фактическим размером без установки метки повторного использования * / if (bsize < (off_t) size) { /* * allocate a small temp buf for a small last buf * or its small last part */ size = (size_t) bsize; recycled = 0; } else if (!ctx->directio && ctx->bufs.num == 1 && (bsize < (off_t) (size + size / 4))) { /* * allocate a temp buf that equals to a last buf, * if there is no directio, the last buf size is lesser * than 1.25 of bufs.size and the temp buf is single */ size = (size_t) bsize; recycled = 0; } } / * Начать выделение буферной памяти * / b = ngx_calloc_buf(ctx->pool); if (b == NULL) { return NGX_ERROR; } if (ctx->directio) { / * необходимо выровнять директиву * / b->start = ngx_pmemalign(ctx->pool, size, (size_t) ctx->alignment); if (b->start == NULL) { return NGX_ERROR; } } else { / * Сюда входит большинство ситуаций * / b->start = ngx_palloc(ctx->pool, size); if (b->start == NULL) { return NGX_ERROR; } } b->pos = b->start; b->last = b->start; b->end = b->last + size; / * Установить временный * / b->temporary = 1; b->tag = ctx->tag; b->recycled = recycled; ctx->buf = b; / * Обновление выделено, вы можете видеть, что каждое выделенное добавляет 1 * / ctx->allocated++; return NGX_OK; }
После выделения нового буфера и цепочки и вызова ngx_output_chain_copy_buf для копирования данных Nginx передаст новый список цепочек следующему фильтру тела для продолжения обработки:
if (out == NULL && last != NGX_NONE) { if (ctx->in) { return NGX_AGAIN; } return last; } last = ctx->output_filter(ctx->filter_ctx, out); if (last == NGX_ERROR || last == NGX_DONE) { return last; } ngx_chain_update_chains(ctx->pool, &ctx->free, &ctx->busy, &out, ctx->tag); last_out = &out;
После обработки других основных фильтров функции ngx_output_chain также необходимо обновить список цепочек для повторного использования. Функция ngx_chain_update_chains в основном помещает обработанные узлы цепочки в список свободных, а необработанные — в список занятости. Кроме того, эта функция использует Когда тег достигнут, он повторно использует только узлы цепочки, созданные фильтром копирования.
ngx_http_write_filter_module анализ
ngx_http_write_filter_module — последний фильтр тела, вы можете увидеть особенность его функции регистрации:
static ngx_int_t ngx_http_write_filter_init(ngx_conf_t *cf) { ngx_http_top_body_filter = ngx_http_write_filter; return NGX_OK; }
ngx_http_write_filter_module — это первый модуль для регистрации основного фильтра, поэтому он также является последним выполненным модулем основного фильтра.
Если посмотреть непосредственно на ngx_http_write_filter, из следующего кода был удален некоторый отладочный код:
ngx_int_t ngx_http_write_filter(ngx_http_request_t *r, ngx_chain_t *in) { off_t size, sent, nsent, limit; ngx_uint_t last, flush; ngx_msec_t delay; ngx_chain_t *cl, *ln, **ll, *chain; ngx_connection_t *c; ngx_http_core_loc_conf_t *clcf; c = r->connection; if (c->error) { return NGX_ERROR; } size = 0; flush = 0; last = 0; ll = &r->out; /* find the size, the flush point and the last link of the saved chain */ for (cl = r->out; cl; cl = cl->next) { ll = &cl->next; #if 1 if (ngx_buf_size(cl->buf) == 0 && !ngx_buf_special(cl->buf)) { return NGX_ERROR; } #endif size += ngx_buf_size(cl->buf); if (cl->buf->flush || cl->buf->recycled) { flush = 1; } if (cl->buf->last_buf) { last = 1; } } /* add the new chain to the existent one */ for (ln = in; ln; ln = ln->next) { cl = ngx_alloc_chain_link(r->pool); if (cl == NULL) { return NGX_ERROR; } cl->buf = ln->buf; *ll = cl; ll = &cl->next; #if 1 if (ngx_buf_size(cl->buf) == 0 && !ngx_buf_special(cl->buf)) { return NGX_ERROR; } #endif size += ngx_buf_size(cl->buf); if (cl->buf->flush || cl->buf->recycled) { flush = 1; } if (cl->buf->last_buf) { last = 1; } } *ll = NULL; clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); /* * avoid the output if there are no last buf, no flush point, * there are the incoming bufs and the size of all bufs * is smaller than "postpone_output" directive */ if (!last && !flush && in && size < (off_t) clcf->postpone_output) { return NGX_OK; } / * Если запрос должен быть отложен из-за ограничения скорости, устанавливаем флаг и выходим * / if (c->write->delayed) { c->buffered |= NGX_HTTP_WRITE_BUFFERED; return NGX_AGAIN; } / * Если общий размер буфера равен 0, и нет задержки из-за базового интерфейса отправки перед текущим соединением, Затем проверьте, есть ли специальная отметка * / if (size == 0 && !(c->buffered & NGX_LOWLEVEL_BUFFERED)) { / * Метка last_buf указывает, что тело запроса отправлено на завершение * / if (last) { r->out = NULL; c->buffered &= ~NGX_HTTP_WRITE_BUFFERED; return NGX_OK; } / * Если происходит сброс и нет фактических данных, текущая неотправленная очередь очищается * / if (flush) { do { r->out = r->out->next; } while (r->out); c->buffered &= ~NGX_HTTP_WRITE_BUFFERED; return NGX_OK; } return NGX_ERROR; } / * Запрос имеет ограничение скорости, затем вычисляем текущий размер, который может быть отправлен * / if (r->limit_rate) { limit = r->limit_rate * (ngx_time() - r->start_sec + 1) - (c->sent - clcf->limit_rate_after); if (limit <= 0) { c->write->delayed = 1; ngx_add_timer(c->write, (ngx_msec_t) (- limit * 1000 / r->limit_rate + 1)); c->buffered |= NGX_HTTP_WRITE_BUFFERED; return NGX_AGAIN; } if (clcf->sendfile_max_chunk && (off_t) clcf->sendfile_max_chunk < limit) { limit = clcf->sendfile_max_chunk; } } else { limit = clcf->sendfile_max_chunk; } sent = c->sent; / * отправляем данные * / chain = c->send_chain(c, r->out, limit); if (chain == NGX_CHAIN_ERROR) { c->error = 1; return NGX_ERROR; } / * Обновить информацию об ограничении скорости * / if (r->limit_rate) { nsent = c->sent; if (clcf->limit_rate_after) { sent -= clcf->limit_rate_after; if (sent < 0) { sent = 0; } nsent -= clcf->limit_rate_after; if (nsent < 0) { nsent = 0; } } delay = (ngx_msec_t) ((nsent - sent) * 1000 / r->limit_rate); if (delay > 0) { limit = 0; c->write->delayed = 1; ngx_add_timer(c->write, delay); } } if (limit && c->write->ready && c->sent - sent >= limit - (off_t) (2 * ngx_pagesize)) { c->write->delayed = 1; ngx_add_timer(c->write, 1); } / * Обновляем выходную цепочку и освобождаем отправленные узлы * / for (cl = r->out; cl && cl != chain; /* void */) { ln = cl; cl = cl->next; ngx_free_chain(r->pool, ln); } r->out = chain; / * Если данные не были отправлены, устанавливаем флаг * / if (chain) { c->buffered |= NGX_HTTP_WRITE_BUFFERED; return NGX_AGAIN; } c->buffered &= ~NGX_HTTP_WRITE_BUFFERED; / * Если данные не полностью отправлены из-за базового интерфейса отправки, и в текущем запросе нет других данных для отправки, В это время должен быть возвращен NGX_AGAIN, указывающий, что есть еще данные, которые не были отправлены * / if ((c->buffered & NGX_LOWLEVEL_BUFFERED) && r->postponed == NULL) { return NGX_AGAIN; } return NGX_OK; }
Nginx сохраняет список цепочек для отправки в r-> out. Вышеупомянутая функция сначала проверяет, есть ли флаги flush, recycled и last_buf в ранее неотправленном списке, и вычисляет размер всех буферов, а затем выполняет операции с новым списком входной цепочки. То же самое и добавить новый связанный список в конец команды r-> out.
Если в выходном связанном списке нет узла, который не идентифицирован как последний буфер, и нет буфера, который необходимо очистить или срочно восстановить, а общий размер буфера в текущей очереди недостаточен для размера, установленного инструкцией postpone_output (по умолчанию 1460 байт), функция выполнит Вернитесь прямо.
ngx_http_write_filter вызовет c-> send_chain для отправки данных клиенту. Значение c-> send_chain будет выполнять разные функции в разных операционных системах, параметрах компиляции и протоколах (ngx_ssl_send_chain используется в https), типичных операционных системах Linux. , Его значение — ngx_linux_sendfile_chain, что означает, что эта функция в конечном итоге будет вызвана для отправки данных. Его функциональный прототип:
ngx_chain_t * ngx_linux_sendfile_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit)
Первый параметр — это текущее соединение, второй параметр — это цепочка, которую нужно отправить, а третий параметр — максимальное значение, которое может быть отправлено.
Сначала посмотрите на некоторые важные локальные переменные, определяемые этой функцией:
send указывает размер буфера для отправки, который был отправлен;
Отправлено указывает размер отправленного буфера;
prev_send представляет размер последнего отправленного, то есть размер буфера, который был отправлен;
fprev похожа на prev-send, за исключением того, что имеет файловый тип;
complete указывает, полностью ли передан buf, то есть равен ли отправленный send-prev_send;
Заголовок указывает, что это должно быть отправлено buf командой writev, которая находится только в buf памяти;
struct iovec *iov, headers [NGX_HEADERS] Это в основном используется для параметров sendfile и writev. Обратите внимание, что массив заголовков выше — iovec.
Давайте посмотрим на код инициализации в начале функции:
wev = c->write; if (!wev->ready) { return in; } /* the maximum limit size is 2G-1 - the page size */ if (limit == 0 || limit > (off_t) (NGX_SENDFILE_LIMIT - ngx_pagesize)) { limit = NGX_SENDFILE_LIMIT - ngx_pagesize; } send = 0; / * Устанавливаем заголовок, который представляет собой массив в памяти * / header.elts = headers; header.size = sizeof(struct iovec); header.nalloc = NGX_HEADERS; header.pool = c->pool;
Следующий фрагмент кода — это часть, которая обрабатывается в памяти, а затем помещает buf в соответствующий массив iovec. Основная идея обработки состоит в объединении непрерывных и смежных буферов в памяти (будь то в памяти или в файле):
for (cl = in; cl && send < limit; cl = cl->next) { if (ngx_buf_special(cl->buf)) { continue; } / * Если его нет ни в памяти, ни в файле, возвращается ошибка * / if (!ngx_buf_in_memory(cl->buf) && !cl->buf->in_file) { return NGX_CHAIN_ERROR; } / * Если это не только в buf, это потому, что иногда может потребоваться скопировать buf из файла в память. Если buf находится и в мемоее, и в файле, Nginx будет рассматривать его как находящийся в файле * / if (!ngx_buf_in_memory_only(cl->buf)) { break; } / * Получаем размер buf * / size = cl->buf->last - cl->buf->pos; / * Измените размер, если он больше лимита * / if (send + size > limit) { size = limit - send; } / * Если prev равно pos, это означает, что текущие данные buf и предыдущие данные buf являются непрерывными * / if (prev == cl->buf->pos) { iov->iov_len += (size_t) size; } else { if (header.nelts >= IOV_MAX) { break; } / * В противном случае это означает другой buf, поэтому добавьте iovc * / iov = ngx_array_push(&header); if (iov == NULL) { return NGX_CHAIN_ERROR; } iov->iov_base = (void *) cl->buf->pos; iov->iov_len = (size_t) size; } / * Здесь вы можете видеть, что prev сохраняет конец текущего буфера * / prev = cl->buf->pos + (size_t) size; / * Обновляем отправленный размер * / send += size; }
Затем идет обработка в файле. Основное суждение здесь — fprev == cl-> buf-> file_pos, что аналогично описанному выше в памяти, fprev сохраняет хвост буфера, обработанного в последний раз. Если эти два равны, это означает, что текущие два буфера являются непрерывными (файл непрерывный):
/ * Если размер заголовка не равен 0, это означает, что есть буфер, который нужно отправить раньше. И размер данных превысил лимит, пропустите обработку в файле * / if (header.nelts == 0 && cl && cl->buf->in_file && send < limit) { /* получить файл file = cl->buf; / * Начать слияние * / do { / * Получаем размер * / size = cl->buf->file_last - cl->buf->file_pos; / * Если он слишком большой, выровняйте его * / if (send + size > limit) { size = limit - send; aligned = (cl->buf->file_pos + size + ngx_pagesize - 1) & ~((off_t) ngx_pagesize - 1); if (aligned <= cl->buf->file_last) { size = aligned - cl->buf->file_pos; } } / * Установить размер_файла * / file_size += (size_t) size; / * Устанавливаем размер для отправки * / send += size; / * То же, что и обработка в памяти выше, для сохранения этого последнего * / fprev = cl->buf->file_pos + size; cl = cl->next; } while (cl && cl->buf->in_file && send < limit && file->file->fd == cl->buf->file->fd && fprev == cl->buf->file_pos); }
Затем идет часть отправки, где в файле используется sendfile, а в памяти используется writev. Логика обработки относительно проста, она заключается в определении размера успешной передачи после отправки
if (file) { #if 1 if (file_size == 0) { ngx_debug_point(); return NGX_CHAIN_ERROR; } #endif #if (NGX_HAVE_SENDFILE64) offset = file->file_pos; #else offset = (int32_t) file->file_pos; #endif / * Если данные находятся в файле, вызываем sendfile для отправки данных * / rc = sendfile(c->fd, file->file->fd, &offset, file_size); ... / * Получаем количество успешно отправленных байтов * / sent = rc > 0 ? rc : 0; } else { / * Если данные находятся в памяти, вызываем writev для отправки данных * / rc = writev(c->fd, header.elts, header.nelts); ... / * Получаем количество успешно отправленных байтов * / sent = rc > 0 ? rc : 0; }
Следующим шагом является обновление цепочки в соответствии с количеством успешно отправленных байтов:
/ * Если send-prev_send == sent, это означает, что все, что должно быть отправлено, отправлено * / if (send - prev_send == sent) { complete = 1; } / * Обновляем отправленный домен подключения * / c->sent += sent; / * Снова начать движение по цепочке, чтобы предотвратить ситуацию, когда передача не завершена, На этом этапе нам нужно вырезать buf * / for (cl = in; cl; cl = cl->next) { if (ngx_buf_special(cl->buf)) { continue; } if (sent == 0) { break; } / * Получить размер буфера * / size = ngx_buf_size(cl->buf); / * Если он больше текущего размера, это означает, что данные этого буфера были полностью отправлены. Так что обновите его домен * / if (sent >= size){ / * Обновить отправленный домен * / sent -= size; / * Обновить позицию, если она есть в памяти * / if (ngx_buf_in_memory(cl->buf)) { cl->buf->pos = cl->buf->last; } / * Если он находится в файле, file_pos более виден * / if (cl->buf->in_file) { cl->buf->file_pos = cl->buf->file_last; } continue; } / * Это показывает, что была отправлена только часть текущего буфера, поэтому нужно изменить только указатель. Чтобы его можно было отправить в следующий раз * / if (ngx_buf_in_memory(cl->buf)) { cl->buf->pos += (size_t) sent; } / * То же, что и выше * / if (cl->buf->in_file) { cl->buf->file_pos += sent; } break; }
Последняя часть — это некоторые суждения о выходе из цикла. Здесь следует отметить, что если передача в Nginx не завершена, она вернется напрямую, и будет возвращена цепочка, которая не была отправлена, и ее buf также был обновлен. Затем Nginx возвращается, чтобы заняться другими делами, а затем снова отправляет неотправленные данные после ожидания, чтобы они были доступны для записи:
if (eintr) { continue; } / * Если не завершено, установить wev-> ready на 0 и вернуть * / if (!complete) { wev->ready = 0; return cl; } / * Отправленные данные превышают лимит, или данных больше нет * / if (send >= limit || cl == NULL) { return cl; } / * Обновление в, то есть начало обработки следующей цепочки * / in = cl;
Анализ принципа подзапроса (99%)
Подзапрос не является концепцией стандарта http. Это новый запрос, инициированный текущим запросом. Он имеет собственную структуру ngx_http_request_t, uri и args. Вообще говоря, эффективность использования подзапроса может иметь некоторое влияние, потому что он должен снова пройти ФАЗУ обработки запроса от сервера перезаписать, но это может быть удобно в некоторых случаях. Чаще используется подзапрос для доступа к восходящему потоку. И дайте ему обработчик обратного вызова ngx_http_post_subrequest_t, который чем-то похож на вызов асинхронной функции. Для данных, возвращаемых из восходящего потока, подзапрос позволяет пользователю обработать его (в обработчике обратного вызова) или отправить его непосредственно в выходной фильтр восходящим модулем в соответствии с флагом, указанным при его создании. Чтобы кратко рассказать о поведении подзапроса, nginx использует подзапрос для доступа к определенному месту, генерирует соответствующие данные и вставляет их в соответствующую позицию выходной цепочки nginx (позиция, когда создается подзапрос). Ниже используется модуль добавления в коде nginx (по умолчанию не компилируется Чтобы войти в ядро nginx, используйте параметр —with-http_addition_module, чтобы включить этот модуль), чтобы проиллюстрировать:
location /main.htm { # content of main.htm: main add_before_body /hello.htm; add_after_body /world.htm; } location /hello.htm { #content of hello.htm: hello } location /world.htm { #content of world.htm: world }
Посетите /main.htm, вы получите следующий ответ:
Приведенная выше инструкция add_before_body инициирует подзапрос для доступа к /hello.htm и вставляет сгенерированный контент (привет) в начало основного тела ответа на запрос, инструкция add_after_body инициирует подзапрос для доступа к /world.htm и добавляет сгенерированный контент (мир) В конце основного тела ответа на запрос. Модуль сложения — это модуль фильтра, но подзапрос может использоваться либо в фазовом модуле, либо в модуле фильтра.
Прежде чем приступить к анализу исходного кода, давайте сначала подумаем о том, что нам делать, если мы сами хотим реализовать описанное выше поведение подзапроса? Подзапрос также может иметь свой собственный подзапрос, и каждый подзапрос не обязательно выводит данные в том порядке, в котором он был создан, поэтому непросто просто использовать связанный список, поэтому дополнительно считается, что это можно сделать в древовидной структуре, а основным запросом является корневой Узлы, каждый узел может иметь свои дочерние узлы. Обход узла означает обработку запроса. Естественно, можно использовать метод обхода посткорневого (порядкового). Да, на самом деле Игорь использует комбинацию дерева и связанного списка. Функция подзапроса реализована, но поскольку порядок, в котором узлы (запросы) генерируют данные, не фиксирован в порядке создания узла (слева-> справа), и данные могут генерироваться несколько раз, их нельзя просто пройти пост-корнем (порядком). Для реализации Игорь использует структуру двух связанных списков. Первый — это отложенный связанный список для каждого запроса. Как правило, каждый узел связанного списка хранит подзапрос запроса. Узел связанного списка определяется следующим образом:
struct ngx_http_postponed_request_s { ngx_http_request_t *request; ngx_chain_t *out; ngx_http_postponed_request_t *next; };
Вы можете видеть, что у него есть поле запроса, которое можно использовать для хранения подзапроса, а также есть поле out типа ngx_chain_t. Фактически, помимо хранения узла подзапроса в отложенном списке запроса, он также хранит данные, сгенерированные самим запросом. Данные хранятся в поле out; второй — связанный список posted_requests, который монтирует текущий запрос (узел), который необходимо пройти. Связанный список хранится в поле posted_requests основного запроса (корневой узел). Узел связанного списка определяется следующим образом:
struct ngx_http_posted_request_s { ngx_http_request_t *request; ngx_http_posted_request_t *next; };
В функции ngx_http_run_posted_requests будет последовательно проходить связанный список posted_requests основного запроса:
void ngx_http_run_posted_requests(ngx_connection_t *c) { ... for ( ;; ) { / * Соединение было прервано, возврат напрямую * / if (c->destroyed) { return; } r = c->data; / * Начать обход с заголовка связанного списка posted_requests * / pr = r->main->posted_requests; if (pr == NULL) { return; } / * Удаляем узел, по которому нужно пройти, из связанного списка * / r->main->posted_requests = pr->next; / * Получить запрос, сохраненный в узле * / r = pr->request; ctx = c->log->data; ctx->current_request = r; ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, "http posted request: "%V?%V"", &r->uri, &r->args); / * Обходим узел (запрос) * / r->write_event_handler(r); } }
Точка вызова функции ngx_http_run_posted_requests будет объяснена позже.
Разобравшись с некоторыми принципами реализации, взглянуть на код намного проще.Теперь официально проводится анализ исходного кода подзапроса.Сначала посмотрим на определение функции для создания подзапроса:
ngx_int_t ngx_http_subrequest(ngx_http_request_t *r, ngx_str_t *uri, ngx_str_t *args, ngx_http_request_t **psr, ngx_http_post_subrequest_t *ps, ngx_uint_t flags)
Параметр r — это текущий запрос, uri и args — это новые uri и args, которые должны быть инициированы. Конечно, args может иметь значение NULL, а psr — указатель на указатель ngx_http_request_t. Его функция — получить созданный подзапрос. Тип ps — ngx_http_post_subrequest_t , Его определение выглядит следующим образом:
typedef struct { ngx_http_post_subrequest_pt handler; void *data; } ngx_http_post_subrequest_t; typedef ngx_int_t (*ngx_http_post_subrequest_pt)(ngx_http_request_t *r, void *data, ngx_int_t rc);
Это обработчик обратного вызова, упомянутый ранее. Тип обработчика в структуре — ngx_http_post_subrequest_pt, который является указателем на функцию, а data — дополнительным параметром, передаваемым обработчику. Давайте посмотрим на последнюю из функции ngx_http_subrequest — флаги. На самом деле в текущем исходном коде есть только два типа флагов, а именно NGX_HTTP_SUBREQUEST_IN_MEMORY и NGX_HTTP_SUBREQUEST_WAITED. Первый — указать метод обработки восходящего потока для упомянутой статьи подзапроса. Два параметра указывают, завершен ли подзапрос заранее (в порядке последующего обхода), следует ли установить его статус на «Выполнено». Если этот параметр установлен, будет задано «сделано заранее». Если не задано, подзапрос будет установлен как «выполнено». Запрос ожидает обработки своих предыдущих подзапросов, прежде чем установить статус «Выполнено».
Загляните внутрь функции ngx_http_subrequest:
{ ... / * Разбираем флаги, subrequest_in_memory анализирует заголовок в восходящем модуле, Используется при отправке тела в нисходящий поток * / sr->subrequest_in_memory = (flags & NGX_HTTP_SUBREQUEST_IN_MEMORY) != 0; sr->waited = (flags & NGX_HTTP_SUBREQUEST_WAITED) != 0; sr->unparsed_uri = r->unparsed_uri; sr->method_name = ngx_http_core_get_method; sr->http_protocol = r->http_protocol; ngx_http_set_exten(sr); / * Основной запрос сохраняется в основном поле * / sr->main = r->main; / * Родительский запрос - это текущий запрос * / sr->parent = r; / * Сохраняем обработчик обратного вызова и данные. После выполнения подзапроса он будет вызван * / sr->post_subrequest = ps; / * Обработчик события чтения назначается функции, которая ничего не делает, потому что подзапросу больше не нужно читать данные или проверять статус соединения; Напишите обработчик события как ngx_http_handler, он повторит фазу * / sr->read_event_handler = ngx_http_request_empty_handler; sr->write_event_handler = ngx_http_handler; / * Поле данных ngx_connection_s более критично, оно сохраняет текущий запрос, который может выводить данные в цепочку out, Конкретное значение будет подробно описано позже * / if (c->data == r && r->postponed == NULL) { c->data = sr; } / * Переменные родительского запроса являются общими по умолчанию. Конечно, вы можете создать отдельный набор переменных для дочернего запроса после создания дочернего запроса в соответствии с вашими потребностями * / sr->variables = r->variables; sr->log_handler = r->log_handler; pr = ngx_palloc(r->pool, sizeof(ngx_http_postponed_request_t)); if (pr == NULL) { return NGX_ERROR; } pr->request = sr; pr->out = NULL; pr->next = NULL; / * Монтируем дочерний запрос в конец отложенного списка родительского запроса * / if (r->postponed) { for (p = r->postponed; p->next; p = p->next) { /* void */ } p->next = pr; } else { r->postponed = pr; } / * Подзапрос - это внутренний запрос, он может обращаться к внутреннему типу местоположения * / sr->internal = 1; / * Наследование некоторого состояния родительского запроса * / sr->discard_body = r->discard_body; sr->expect_tested = 1; sr->main_filter_need_in_memory = r->main_filter_need_in_memory; sr->uri_changes = NGX_HTTP_MAX_URI_CHANGES + 1; tp = ngx_timeofday(); r->start_sec = tp->sec; r->start_msec = tp->msec; r->main->subrequests++; / * Увеличиваем количество ссылок основного запроса. Это поле в основном используется в некоторых конечных запросах и вызовах, сделанных ngx_http_finalize_request Используется в подключенных функциях * / r->main->count++; *psr = sr; / * Монтируем подзапрос в конце списка posted_requests основного запроса * / return ngx_http_post_request(sr, NULL); }
На этом этапе создается подзапрос. Вообще говоря, создание подзапроса происходит в обработчике контента определенного запроса или определенного фильтра. Из приведенной выше функции вы можете видеть, что подзапрос не выполняется сразу, а просто монтируется. Когда в связанном списке posted_requests основного запроса он может быть выполнен? Как упоминалось ранее, связанный список posted_requests просматривается в функции ngx_http_run_posted_requests, тогда когда вызывается функция ngx_http_run_posted_requests? Фактически он вызывается в обработчике события чтения (записи) определенного запроса после выполнения соответствующей обработки запроса. Например, основной запрос вызовет ngx_http_run_posted_requests, когда ФАЗА завершена, а затем может быть выполнен подзапрос.
На данный момент на самом деле есть одна проблема, которую необходимо решить. Поскольку nginx является многопроцессорным, его нельзя заблокировать по желанию (если запрос блокирует текущий процесс, это эквивалентно блокированию всех других запросов, принимаемых этим процессом, и процесс также Невозможно принять новые запросы), запрос может потребоваться заблокировать по некоторым причинам (например, доступ к io), подход nginx состоит в том, чтобы установить некоторый статус запроса и добавить соответствующее событие в epoll, а затем передать его для обработки других запросов и дождаться события Продолжить обработку запроса, когда он поступит. Такое поведение означает, что для выполнения запроса может потребоваться несколько возможностей выполнения. Для нескольких подзапросов запроса это означает, что порядок, в котором они выполняются, может отличаться от порядка, в котором они были созданы. То же самое, поэтому должен быть механизм для предварительно завершенных подзапросов для сохранения данных, которые он генерирует, вместо прямого вывода в цепочку out, но также для включения текущего запроса, который может выводить данные в цепочку out для вывода сгенерированных данных во времени. Автор Игорь использует поле данных в ngx_connection_t и основной фильтр, а именно ngx_http_postpone_filter, и некоторую логику в функции ngx_http_finalize_request для решения этой проблемы.
Ниже приведена диаграмма для иллюстрации, следующая диаграмма представляет собой древовидную структуру основного запроса и всех его потомков в определенное время:
Корневой узел на рисунке является основным запросом. В его отложенном списке 3 узла смонтированы слева направо: SUB1 — это его первый подзапрос, DATA1 — это порция данных, которые он генерирует, а SUB2 — его второй подзапрос. Request, и эти два подзапроса имеют свои собственные подзапросы и данные соответственно. Поле данных в ngx_connection_t сохраняет текущий запрос, который может отправлять данные в цепочку out. В начале статьи было сказано, что данные, отправляемые клиенту, должны отправляться в том порядке, в котором создаются подзапросы. Вот метод обхода в следующем порядке (SUB11-> DATA11 -> SUB12-> DATA12 -> (SUB1) -> DATA1-> SUB21-> SUB22 -> (SUB2) -> (ROOT)), на картинке выше текущий запрос на отправку данных клиенту (выходная цепочка) явно SUB11, если выполнение SUB12 завершено заранее, и данные DATA121 сгенерированы, пока у него есть узлы, которые не были отправлены ранее, DATA121 может быть установлен только в отложенном списке SUB12. Также обратите внимание на настройку c-> data. После выполнения SUB11 и отправки данных следующим отправляемым узлом должен быть DATA11, но этот узел фактически сохраняет данные, а не подзапрос, поэтому c-> data должны указывать на запрос SUB1, который содержит узел для изменения данных.
Посмотрим, как реализован исходный код.Первый — это функция ngx_http_postpone_filter:
static ngx_int_t ngx_http_postpone_filter(ngx_http_request_t *r, ngx_chain_t *in) { ... / * Текущий запрос не может отправлять данные в цепочку out. Если данные сгенерированы, создайте новый узел, Сохраните его в конце отложенной команды текущего запроса. Это гарантирует, что данные будут отправлены клиенту в порядке * / if (r != c->data) { if (in) { ngx_http_postpone_filter_add(r, in); return NGX_OK; } ... return NGX_OK; } / * Здесь это означает, что текущий запрос может отправлять данные в цепочку out.Если нет подзапроса и нет данных в его отложенном списке, Затем напрямую отправьте сгенерированные в данный момент данные или продолжите отправку данных в исходящей цепочке, которые не были отправлены ранее * / if (r->postponed == NULL) { if (in || c->buffered) { return ngx_http_next_filter(r->main, in); } / * Нет данных для отправки в текущем запросе * / return NGX_OK; } / * В списке отложенных текущего запроса есть узел, который нужно обработать раньше, создать новый узел и сохранить сгенерированные в данный момент данные в, И вставляем в конец отложенной команды * / if (in) { ngx_http_postpone_filter_add(r, in); } / * Обрабатываем узлы в отложенном списке * / do { pr = r->postponed; / * Если узел сохраняет подзапрос, добавить его в связанный список posted_requests основного запроса, Чтобы в следующий раз вызвать функцию ngx_http_run_posted_requests для обработки дочернего узла * / if (pr->request) { ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, "http postpone filter wake "%V?%V"", &pr->request->uri, &pr->request->args); r->postponed = pr->next; / * Обходим сгенерированную последовательность в следующем порядке, потому что текущий запрос (узел) имеет необработанные подзапросы (узлы), Запрос на изменение должен быть обработан до того, как последующие дочерние узлы смогут быть обработаны. Здесь подзапрос установлен как запрос, который может отправлять данные в цепочку out. * / c->data = pr->request; / * Добавляем подзапрос в связанный список posted_requests основного запроса * / return ngx_http_post_request(pr->request, NULL); } / * Если узел сохраняет данные, вы можете напрямую обработать узел и отправить его в цепочку out * / if (pr->out == NULL) { ngx_log_error(NGX_LOG_ALERT, c->log, 0, "http postpone filter NULL output", &r->uri, &r->args); } else { ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, "http postpone filter output "%V?%V"", &r->uri, &r->args); if (ngx_http_next_filter(r->main, pr->out) == NGX_ERROR) { return NGX_ERROR; } } r->postponed = pr->next; } while (r->postponed); return NGX_OK; }
Посмотрим на функцию ngx_http_finalzie_request:
void ngx_http_finalize_request(ngx_http_request_t *r, ngx_int_t rc) { ... / * Если текущий запрос является подзапросом, проверяем, есть ли у него обработчик обратного вызова, и выполняем его, если он есть * / if (r != r->main && r->post_subrequest) { rc = r->post_subrequest->handler(r, r->post_subrequest->data, rc); } ... / * Подзапрос * / if (r != r->main) { / * Подзапрос все еще содержит необработанные данные или подзапросы * / if (r->buffered || r->postponed) { / * Добавляем событие записи для этого подзапроса и устанавливаем соответствующий обработчик событий записи, Чтобы продолжить обработку, когда наступит следующее событие записи, здесь на самом деле функция ngx_http_output_filter будет вызываться при следующем выполнении, В конце концов он войдет в ngx_http_postpone_filter для обработки * / if (ngx_http_set_write_handler(r) != NGX_OK) { ngx_http_terminate_request(r, 0); } return; } ... pr = r->parent; / * Дочерний запрос обработан, если он имеет право отправлять данные, он передаст право на родительский запрос, * / if (r == c->data) { r->main->count--; if (!r->logged) { clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); if (clcf->log_subrequest) { ngx_http_log_request(r); } r->logged = 1; } else { ngx_log_error(NGX_LOG_ALERT, c->log, 0, "subrequest: "%V?%V" logged again", &r->uri, &r->args); } r->done = 1; / * Если дочерний запрос не был выполнен раньше времени, удалим его из отложенного списка родительского запроса * / if (pr->postponed && pr->postponed->request == r) { pr->postponed = pr->postponed->next; } / * Передаем право на отправку родительскому запросу, и родительский запрос будет отправлен в своем отложенном списке, когда родительский запрос будет выполнен в следующий раз. Узел отправляющих данных или следующий подзапрос, передающий ему право отправки * / c->data = pr; } else { / * На самом деле это означает, что подзапрос был выполнен раньше времени, и он не произвел никаких данных, он получит их в следующий раз Когда возможность выполняется, будет выполнена функция ngx_http_request_finalzier, которая фактически выполняет ngx_http_finalzie_request (r, 0), то есть ничего не делать, пока не наступит его очередь отправлять данные, Функция ngx_http_finalzie_request удалит его из отложенного списка родительского запроса * / r->write_event_handler = ngx_http_request_finalizer; if (r->waited) { r->done = 1; } } / * Добавляем родительский запрос в конец команды posted_request и получаем возможность запустить * / if (ngx_http_post_request(pr, NULL) != NGX_OK) { r->main->count++; ngx_http_terminate_request(r, 0); return; } return; } / * Вот логика обработки конца основного запроса. Если в основном запросе есть неотправленные данные или необработанные подзапросы, Затем добавьте событие записи в основной запрос и установите соответствующий обработчик событий записи, Чтобы продолжить обработку, когда придет следующее событие записи * / if (r->buffered || c->buffered || r->postponed || r->blocked) { if (ngx_http_set_write_handler(r) != NGX_OK) { ngx_http_terminate_request(r, 0); } return; } ... }
анализ обработки запросов https
Введение в поддержку nginx ssl
По умолчанию nginx-1.2.0 не поддерживает протокол ssl при компиляции. Вам необходимо включить его поддержку с помощью инструкций по компиляции:
./configure --with-http_ssl_module
В исходном коде nginx связанный с ssl код использует макрос для определения переменной NGX_HTTP_SSL, чтобы контролировать, включена ли она. Это дает нам удобство поиска и чтения кода, связанного с ssl, а именно:
Протокол ssl работает между протоколом tcp и протоколом http. Когда nginx поддерживает протокол ssl, вам нужно обратить внимание на три момента. В других случаях вам нужно только нормально обработать протокол http:
- Когда соединение TCP установлено, соединение SSL устанавливается на соединение TCP
- После получения данных tcp расшифруйте полученные данные и отправьте расшифрованные данные в обычный поток обработки протокола http.
- Перед отправкой данных TCP зашифруйте (http) данные перед отправкой
В следующих главах мы познакомимся с этими тремя пунктами соответственно.
Установление соединения ssl (подтверждение ssl)
Подготовка к установлению ssl-соединения
Согласно протоколу ssl, соединение ssl должно быть установлено до того, как передача и прием данных будут официально инициированы.Процесс установления соединения — это подтверждение ssl. Nginx готовится к установлению TCP-соединения при создании и инициализации фазы HTTP-запроса. Основной процесс реализован в функции ngx_http_init_request:
static void ngx_http_init_request(ngx_event_t *rev) { ... #if (NGX_HTTP_SSL) { ngx_http_ssl_srv_conf_t *sscf; sscf = ngx_http_get_module_srv_conf(r, ngx_http_ssl_module); if (sscf->enable || addr_conf->ssl) { / * Если c-> ssl не пуст, это означает, что длинное соединение запрошено для повторного использования (соединение ssl установлено) * / if (c->ssl == NULL) { c->log->action = "SSL handshaking"; /* * Откройте протокол ssl в nginx.conf (слушайте 443 ssl;), * Бесполезно устанавливать сертификат сервера (ssl_certificate <certificate_path>;) */ if (addr_conf->ssl && sscf->ssl.ctx == NULL) { ngx_log_error(NGX_LOG_ERR, c->log, 0, "no "ssl_certificate" is defined " "in server listening on SSL port"); ngx_http_close_connection(c); return; } /* * Создать и инициализировать ngx_ssl_connection_t * Инициализация ssl-соединения в библиотеке openssl */ if (ngx_ssl_create_connection(&sscf->ssl, c, NGX_SSL_BUFFER) != NGX_OK) { ngx_http_close_connection(c); return; } rev->handler = ngx_http_ssl_handshake; } / * данные, зашифрованные ssl, должны быть прочитаны в память * / r->main_filter_need_in_memory = 1; } } #endif ... }
Большая часть процесса ngx_http_init_request была проанализирована в предыдущих главах. Эта функция в основном отвечает за инициализацию HTTP-запроса. В настоящее время она фактически не анализирует HTTP-запрос. Если входящий запрос зашифрован через протокол ssl, при прямом разборе http-запроса возникнет ошибка. Поток обработки, связанный с протоколом ssl, в ngx_http_init_request:
1. Сначала определите, пуст ли c-> ssl. Если он не пустой: это означает, что это случай соединения http long, и соединение ssl было установлено, когда приходит первый запрос. Просто повторно используйте здесь ssl-соединение, пропустите фазу подтверждения ssl.
2. (1), если c-> ssl пусто: для установления соединения требуется подтверждение ssl. В это время вызовите ngx_ssl_create_connection, чтобы подготовиться к установлению ssl-соединения.
Упрощенный код ngx_ssl_create_connection выглядит следующим образом:
ngx_int_t ngx_ssl_create_connection(ngx_ssl_t *ssl, ngx_connection_t *c, ngx_uint_t flags) { ngx_ssl_connection_t *sc; / * ngx_ssl_connection_t - это структура описания nginx для ssl-соединения, которая записывает информацию и статус ssl-соединения * / sc = ngx_pcalloc(c->pool, sizeof(ngx_ssl_connection_t)); sc->buffer = ((flags & NGX_SSL_BUFFER) != 0); / * Создаем структуру описания SSL-соединения в библиотеке openssl * / sc->connection = SSL_new(ssl->ctx); / * Ассоциация (библиотека openssl) ssl-соединение с сокетом, соответствующим tcp-соединению * / SSL_set_fd(sc->connection, c->fd); if (flags & NGX_SSL_CLIENT) { / * Инициируем ssl-соединение с серверной частью в восходящем потоке, указывая, что ssl-соединение nginx является клиентом * / SSL_set_connect_state(sc->connection); } else { / * Указываем, что ssl-соединение nginx является сервером * / SSL_set_accept_state(sc->connection); } / * Ассоциация (библиотека openssl) ssl-соединение с пользовательскими данными (текущее соединение c) * / SSL_set_ex_data(sc->connection, ngx_ssl_connection_index, c); c->ssl = sc; return NGX_OK; }
2. (2) Установите для функции обработки событий чтения соединения значение ngx_http_ssl_handshake, что изменит нормальный процесс последующей обработки HTTP-запросов следующим образом: сначала выполните рукопожатие ssl, а затем обработайте HTTP-запрос в обычном режиме.
3. Укажите, что текущие данные для отправки должны находиться в памяти, чтобы ssl мог зашифровать данные. Поскольку протокол ssl включен, отправляемые данные должны быть зашифрованы, что требует, чтобы отправляемые данные находились в памяти. Флаг r-> main_filter_need_in_memory равен 1, что позволяет считывать данные в память перед отправкой последующих данных (чтобы предотвратить отправку данных в файле напрямую через sendfile без шифрования).
Фактическая фаза подтверждения ssl
Поскольку для функции обработки событий чтения соединения установлено значение ngx_http_ssl_handshake в ngx_http_init_request, когда в соединении есть читаемые данные, он вводит ngx_http_ssl_handshake для обработки (если ssl не включен, он вводит ngx_http_process_line напрямую в запрос http_process_request)
В ngx_http_ssl_handshake, чтобы выполнить рукопожатие ssl:
1. Сначала определите, не истек ли время ожидания соединения, и закройте соединение, если оно истекло.
static void ngx_http_process_request(ngx_http_request_t *r) { if (rev->timedout) { ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out"); c->timedout = 1; ngx_http_close_request(r, NGX_HTTP_REQUEST_TIME_OUT); return; }
2. Упреждающее чтение первого байта: просмотрите байт из TCP-соединения (проверьте данные в TCP-соединении через MSG_PEEK, но на самом деле не будет читать данные), если нет подготовленных данных в TCP-соединении, добавьте событие чтения еще раз Выйдите и дождитесь поступления новых данных.
n = recv(c->fd, (char *) buf, 1, MSG_PEEK); if (n == -1 && ngx_socket_errno == NGX_EAGAIN) { if (!rev->timer_set) { ngx_add_timer(rev, c->listening->post_accept_timeout); } if (ngx_handle_read_event(rev, 0) != NGX_OK) { ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); } return; }
3. Обнаружение первого байта: если вы успешно просматриваете 1 байт данных, используйте первый байт, чтобы определить, являются ли полученные данные пакетом подтверждения ssl или данными http. Согласно протоколу ssl, первый байт пакета подтверждения ssl содержит информацию о версии протокола ssl. На основании этого nginx определяет, следует ли выполнить квитирование ssl или вернуться к нормальной обработке http-запроса (фактический ответ — 400 BAD REQUEST).
if (n == 1) { if (buf[0] & 0x80 /* SSLv2 */ || buf[0] == 0x16 /* SSLv3/TLSv1 */) { ngx_log_debug1(NGX_LOG_DEBUG_HTTP, rev->log, 0, "https ssl handshake: 0x%02Xd", buf[0]); /* * Вызовите функцию ngx_ssl_handshake, чтобы выполнить рукопожатие ssl, и подключенные стороны обменяются фазами во время рукопожатия ssl * Соответствующие данные (версия ssl, алгоритм шифрования ssl, открытый ключ сервера и т. Д.) И формальное установление ssl-соединения. * Функция ngx_ssl_handshake инкапсулирует библиотеку openssl. * Вызов SSL_do_handshake () для рукопожатия и проверка того, завершено ли подтверждение ssl в соответствии с его возвращаемым значением * Или что-то пошло не так. */ rc = ngx_ssl_handshake(c); /* * Подтверждение ssl может потребовать нескольких взаимодействий с данными для завершения. * Если рукопожатие ssl не завершено, ngx_ssl_handshake будет основываться на конкретной ситуации (если вам нужно узнать больше * Несколько пакетов подтверждения или необходимость отправки пакетов подтверждения), чтобы снова добавить события чтения и записи */ if (rc == NGX_AGAIN) { if (!rev->timer_set) { ngx_add_timer(rev, c->listening->post_accept_timeout); } c->ssl->handler = ngx_http_ssl_handshake_handler; return; } /* * Если рукопожатие ssl завершено или возникла ошибка, ngx_ssl_handshake вернет NGX_OK или NGX_ERROR, а затем будет вызван ngx_http_ssl_handshake * ngx_http_ssl_handshake_handler для продолжения обработки */ ngx_http_ssl_handshake_handler(c); return; } else { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0, "plain http"); r->plain_http = 1; } }
Особого внимания требует, если подтверждение ssl завершено, ngx_ssl_handshake заменит подключенный интерфейс чтения-записи. Таким образом, когда вам нужно будет прочитать и записать данные позже, замененный интерфейс зашифрует и расшифрует данные. Подробный код выглядит следующим образом:
ngx_int_t ngx_ssl_handshake(ngx_connection_t *c) { n = SSL_do_handshake(c->ssl->connection); / * Возвращаем 1, чтобы указать успешное подтверждение ssl * / if (n == 1) { ... c->ssl->handshaked = 1; c->recv = ngx_ssl_recv; c->send = ngx_ssl_write; c->recv_chain = ngx_ssl_recv_chain; c->send_chain = ngx_ssl_send_chain; return NGX_OK; } ... }
4. Обнаружение — протокол http: обычная обработка пакетов протокола http напрямую вызывает ngx_http_process_request_line для обработки HTTP-запросов и устанавливает функцию обработки событий чтения на ngx_http_process_request_line. (Фактический результат обработки состоит в том, чтобы вернуть клиенту 400 BAD REQUET, а флаг r-> plain_http обрабатывается отдельно в ngx_http_process_request.)
c->log->action = "reading client request line"; rev->handler = ngx_http_process_request_line; ngx_http_process_request_line(rev); } /* end of ngx_http_process_request() */
5. Если подтверждение ssl прошло успешно или произошла ошибка, вызовите функцию ngx_http_ssl_handshake_handler.
5. (1), если подтверждение ssl завершено (c-> ssl-> handshaked установлено в 1 после того, как рукопожатие определено ngx_ssl_handshake ()), установите функцию обработки событий чтения на ngx_http_process_request_line и вызовите эту функцию для нормальной обработки HTTP-запросов.
5. (2), если подтверждение ssl не завершено (подтверждение ssl неверно), клиенту возвращается 400 BAD REQUST.
На этом этапе ssl-соединение установлено, после чего данные будут считаны и расшифрованы в ngx_http_process_request, а затем HTTP-запрос будет обработан в обычном режиме.
static void ngx_http_ssl_handshake_handler(ngx_connection_t *c) { ngx_http_request_t *r; if (c->ssl->handshaked) { /* * The majority of browsers do not send the "close notify" alert. * Among them are MSIE, old Mozilla, Netscape 4, Konqueror, * and Links. And what is more, MSIE ignores the server's alert. * * Opera and recent Mozilla send the alert. */ c->ssl->no_wait_shutdown = 1; c->log->action = "reading client request line"; c->read->handler = ngx_http_process_request_line; /* STUB: epoll edge */ c->write->handler = ngx_http_empty_handler; ngx_http_process_request_line(c->read); return; } r = c->data; ngx_http_close_request(r, NGX_HTTP_BAD_REQUEST); return; }
протокол ssl для приема данных
Чтобы обрабатывать HTTP-запросы в ngx_http_process_request, необходимо прочитать и проанализировать http-протокол. Фактические данные считываются с помощью функции c-> recv (), которая была заменена на ngx_ssl_recv в ngx_ssl_handshake.
Функция ngx_ssl_recv вызывает функцию библиотеки openssl SSL_read () для чтения и дешифрования данных, что упрощается следующим образом:
ssize_t ngx_ssl_recv(ngx_connection_t *c, u_char *buf, size_t size) { ... n = SSL_read(c->ssl->connection, buf, size); ... return n; }
протокол ssl для отправки данных
Когда nginx отправляет данные, например, используя функцию ngx_output_chain для отправки цепочки кешированных данных http, данные отправляются путем вызова c-> send_chain (). Эта функция была установлена на ngx_ssl_send_chain в ngx_ssl_handshake. ngx_ssl_send_chain далее вызовет ngx_ssl_write. И ngx_ssl_write вызывает функцию SSL_write библиотеки openssl для шифрования и отправки данных.
/* ngx_output_chain * -> .. * -> ngx_chain_writer * -> c->send_chain (ngx_ssl_send_chain) * -> ngx_ssl_write */ ssize_t ngx_ssl_write(ngx_connection_t *c, u_char *data, size_t size) { ... n = SSL_write(c->ssl->connection, data, size); ... return n; }
http://tengine.taobao.org/book/chapter_12.html
I have simple erlang web application with cowboy web server. I’m trying to launch it behind nginx reverse proxy.
My /etc/nginx/sites-available/default
:
server {
listen 9090;
default_type text/html;
location /my_app {
proxy_pass http://localhost:8001/;
}
}
If i’m openning http://localhost:9090/my_app
it’s ok, i’m seeing my html page from my web application. But if i’m adding Content-Type: text/html
header to the cowboy response i’m getting 204 instead 200.
Thank you.