Цей контент є частиною серії:
Слідкуйте за виходом нових статей цієї серії.
Звичайний сценарій, до якого залучено, з одного боку - користувач з браузером, з іншого - віддалений веб-сервер, складається з наступних подій: користувач натискає в своєму браузері на посилання, надсилає запит на веб-сервер, який по ньому витягує певний документ і повертає вміст цього документа віддаленому клієнту.
1. Архітектура Apache 1.x
Клієнтський запит проходить в Apache кілька фаз:
Кожна фаза управляється власним модулем Apache.
На кожен запит ядро Apache створює об'єкт структури request_rec, яку потім передає по черзі відповідного модуля і отримує її в модифікованому варіанті.
На наступному малюнку архітектура Apache представлена більш детально. Є невеликі функціональні модулі - ap, regex, які використовуються як ядром, так і іншими базовими модулями. Окремий модуль OS реалізує кроссплатформенность Apache.
При найближчому розгляді дерева початкових кодів з'ясовується:
- Всі заголовки згруповані в каталозі include.
- Ядро лежить в каталозі main (libmain.a).
- Базові модулі лежать в каталозі modules (libstandard.a).
- Реалізація платформ знаходиться в каталозі os, який включає в себе відповідні підкаталоги, в кожному з яких лежить свій відповідний заголовок os.h. (Libos.a).
- Окремий бібліотечний компонент для роботи з регулярними виразами лежить в каталозі regex (libregex.a).
- Окремий компонент лежить в каталозі ap і включає набір різних функцій (libap.a).
- Окремий компонент лежить в каталозі support і включає в себе скрипти та код для допоміжних утиліт Apache, таких як ротація лог файлів, маніпуляція з файлами паролів і т.д.
- Каталог helpers включає скрипти, які використовуються при компіляції самого Apache.
Практично всі функції мають префікс ap_, це було введено в версії 1.3 для уникнення конфлікту імен при переході з версії 1.2.
2. Архітектура ядра Apache 1
Код ядра лежить в каталозі main. Ядро було розроблено з урахуванням того, що будь-який, хто хоче розширювати функціонал Apache, по можливості не чіпав код ядра, який вже включає всі можливості для такого розширення. Єдиний внутрішній компонент ядра, який може піддаватися зміни - це реалізація HTTP протоколу.
3. Модулі Apache
Модулі реалізують функціонал Apache. Модулі можуть безпосередньо спілкуватися з ядром, але не можуть безпосередньо спілкуватися один з одним. Кожен модуль фактично є набором оброблювачів (хендлерів) для різних фаз HTTP запиту. Наступний малюнок дає детальний огляд архітектури модуля. Модуль - це колекція функцій-обробників, що викликаються ядром. Кожен модуль має власну структуру під назвою module, в якій зберігаються покажчики на обробники, і об'єкт такої структури кожен модуль передає ядру.
Кожен модуль повинен бути ініціалізованим першим ядром. За ідеєю, будь-який модуль може використовувати команди, які користувач прописує в файлі конфігурації. Читанням файлу займається ядро, яке потім зіставляє їх з таблицею команд, отриманих від модуля в структурі module. Кожен рядок в цій таблиці складається з пари: тип контенту - покажчик на обробник, де тип контенту - це MIME type.
Обробники, які займаються безпосередньою відсиланням даних клієнта, називаються content handlers або response handlers. Для кожного типу об'єкта є свій власний обробник.
В Apache всі модулі згруповані в каталозі modules. Стандартні модулі згруповані в підкаталозі standard. реалізація проксі лежить в каталозі proxy. Демонстраційний модуль, який має всього один обробник, лежить в каталозі examples.
Всі стандартні модулі завантажуються статично, хоча можлива динамічне завантаження: їх можна слінковать з ядром і так, і так. При інсталяції Apache, коли ви запускаєте конфігурацію, створюється файл modules.c, в якому визначаються 2 масиву покажчиків на модульні структури:
При цьому всі перераховані модулі можна розбити на наступні групи:
4. Паралелізм
При старті Apache створює (fork) за замовчуванням 5 головних процесів, це мінімальне можливе число процесів. Кожен процес в свою чергу може підтримувати як мінімум 50 потоків (thread - якщо многопоточность підтримується операційною системою). На кожне клієнтське запит відводиться один процес. Існує спеціальна структура - scoreboard - яка зберігає стан всіх процесів. Число одночасних можливих процесів за замовчуванням - від 5 до 10. Максимальне число таких процесів - 256. Є також спеціальна чергу для запитів на очікування, яким поки немає місця в scoreboard. Максимальна довжина такої черги - 511.
Максимально можливе число одночасних клієнтських коннектов - 100.
5. Структури даних
На наступному малюнку показана тимчасова діаграма проходження різних фаз клієнтського запиту, при цьому основна робота виконується в ядрі, в файлі http_request.c.
При виклику обробника кожного модуля передається в якості параметра одна і та ж публічна структура - request_rec. При цьому може відбуватися зміна стану різних полів цієї структури. Responce handlers повертають контент клієнту. Оброблювач також може виконувати інший під-запит. Структура request_rec може включати покажчик на іншу структуру request_rec в тому випадку, коли відбувається редирект, а в разі під-запиту покажчик може вказувати на саму себе. Редирект означає виклик обробника картинки всередині документа або виклик CGI-скрипта. При цьому викликається обробник ap_internal_redirect, який створює новий об'єкт request_rec. Такий об'єкт може бути поміщений в зв'язний список request_recs.
Структура request_rec містить в собі такі поля:
- покажчики на інші request_rec;
- покажчик на пул ресурсів;
- об'єкти запиту:
- URI
- filename
- path
- інформація про контент:
- тип контенту
- кодування
- MIME headers;
- інформація про запит:
- протокол
- метод
Нижче дано вибіркове представлення структури request_rec з найбільш часто використовуваними полями:
Apache використовує пул ресурсів - pool - структуру даних, в якій зберігається список всіх виділених ресурсів для запиту. Коли обробка запиту закінчується, всі ресурси, витрачені на цей запит, звільняються. У модулів можуть бути свої власні пули на свої потреби.
Як вже було сказано, ядро зберігає таблицю конфігураційних команд. Також і кожен модуль може мати свою власну командну таблицю. Кожна команда представляє об'єкт структури command_struct і включає:
- ім'я команди;
- покажчик на функцію-обробник;
- переданий аргумент;
- умови ініціалізації;
- тип і число аргументів;
- опис аргументів;
Структура scoreboard зберігає список оброблюваних запитів. Зберігається статус запиту та його id. Для кожного створюваного процесу в scoreboard додається запис, яка після обробки запиту видаляється. Статус процесу в цій структурі змінюється самим процесом. Статус запиту може набувати таких значень:
6. Обробник response
Результат роботи більшості оброблювачів зводиться до банального зміни значення полів структури request_rec, або до повернення спеціальних кодів. Даний же обробник відсилає реальні дані клієнта. Спочатку клієнтові відсилається HTTP-заголовок за допомогою функції send_http_header. Якщо у запиту є мітка header_only, то клієнту більш нічого не висилається. В іншому випадку, клієнту надсилається саме повідомлення, для цього використовуються спеціальні примітиви rputc, rprintf, send_fd. Наступний код показує обробку GET-запиту і відсилання даних клієнта:
Ця функція може повернути або код помилки, або OK, у другому випадку викликається редирект на інший обробник за допомогою internal_redirect.
Основна проблема, яка розв'язується за допомогою пулу - pool - це запобігання витокам пам'яті.
Пул в Apache розроблений таким чином, що звільнення всіх ресурсів - пам'яті, дескрипторів і т.д. - відбувається автоматично, після завершення обробки клієнтського запиту. Кожному такому запиту виділяється свій власний пул, представлений структурою pool. Після обробки цей пул видаляється.
При старті Apache виділяться спеціальний пул - конфігураційний. При перезапуску сервера цей пул також очищається.
Пам'ять виділяється за допомогою palloc, що швидше, ніж malloc. Ця функція має 2 аргументу - покажчик на пул і кількість виділеної пам'яті:
Функція pfree відсутня, оскільки звільнення пам'яті йде автоматично тоді, коли звільняється пул. Є спеціальні прикладні функції, які виділяють пам'ять. наприклад, в наступному прикладі функція pstrcat виділяє пам'ять під 8 байтовий строковий модуль:
Аналогічно відбувається відкриття файлів:
На відміну від роботи з пам'яттю, тут є відповідна закриває функція pfclose.
Загальним недоліком цієї моделі виділення пам'яті є вкладені пули. Можна змоделювати таку ситуацію, при якій батьківський пул звільняється раніше дочірнього, при якому можливий витік пам'яті для останнього. Наприклад, можна навести як приклад лістинг каталогу сервера з великим рівнем вкладеності. В цьому випадку для створення вкладених пулів потрібно використовувати make_sub_pool.
Пул також можна звільнити в будь-який момент за допомогою clear_pool або destroy_pool.
У разі підзапитів для надійного очищення пам'яті використовується destroy_sub_request.
8. Конфігурація
Перший Apache розроблявся з прицілом на максимальну сумісність зі своїм попередником - вебсервер NCSA 1.3 - в плані читання конфігураційних файлів. Було поставлено завдання винести максимально весь функціонал з ядра в модулі. Для цього в ядрі була створена спеціальна таблиця команд. Визначення типу файлу по суффиксу робиться на основі конфігураційних директив AddType і DefaultType.
Робота з файловою системою виконується за допомогою директив Aliases і Redirect.
Робота з вкладеними файлами виконується на базі файлів .htaccess.
Коли сервер читає в файлі директиву
Команди AddType і AddEncoding зазвичай присутні в .htaccess.
Для створення такої структури потрібні 2 параметра - пул і ім'я каталогу:
Якщо в нашому серверному каталозі, який ми тільки що прочитали, є вкладений каталог, і там є свій власний .htaccess, нам доведеться об'єднувати дві структури:
висновок
Архітектура першого Apache має свої особливості - всі функції мають префікс ap_, реалізація платформ зроблена на основі окремих модулів, модулі не можуть спілкуватися один з одним, а тільки з ядром, всі модулі можна розбити на 7 основних груп: трансляція, аутентифікація, MIME, fix -ups, генератори контента, логирование, проксі. Управління пам'яттю реалізовано на основі пулів. Система конфігурація має широкі можливості для попереднього налаштування сервера.