Статті - мережа - туторіал по написанню власного веб-сервера

Інтернет відіграє в нашому житті велику роль. Ми беремо пошту, дізнаємося свіжу інформацію, відловлюємо своїх знайомих через ICQ. Але що асоціюється зі словом Інтернет у більшості людей в першу чергу? Сайти. Багато при слові "Інтернет" згадують свої улюблені сайти, а деякі (не дуже просунуті) ставлять знак рівності між WWW і Інтернетом, хоча в останньому є багато іншого цікавого: email, IRC, p2p-мережі, MUD'и і так далі. Але World Wide Web грає домінуючу роль.

В основі WWW лежить протокол HyperText Transfer Protocol. Треба сказати, що HTTP може використовуватися не тільки для передачі сайтів, але і для передачі всього щоб там не було. За допомогою протоколу HTTP ми можемо завантажити, наприклад, недавно вийшов фільм або свіжі mp3 :). В p2p-мережах HTTP-протокол застосовується саме в цих цілях.

В даному посібнику ми розглянемо, як написати простий веб-сервер. Я припускаю, що ви знайомі з основами програмування winsock і вмієте створювати сокети і коннектіться до чого-небудь :).

Основи HyperText Transfer Protocol

Ідея HTTP досить проста. Клієнт шле запит серверу, той розглядає його і шле відповідну відповідь. Відповіддю може бути запитаний файл, повідомлення про те, що такого файлу на сервері немає або щось ще. Орієнтовна структура запиту наступна:

<метод> - вид запиту. Основних два: GET і POST. Один від одного вони відрізняються, головним чином, способом передачі додаткової інформації, відсилає разом із запитом. У цьому туторіали ми розглянемо тільки метод GET - функціонально він схожий на POST, але трохи простіше. Про метод POST я розповім у другій частині даного туторіал, якщо, звичайно, така взагалі з'явиться на світ :).

<\n> - це два байта 0Dh, 0Ah, безсумнівно, добре знайомі всім ассемблерщікам :).

Таким чином, з методом ми визначилися. На даний момент запит, який ми (в якості клієнта) повинні будемо послати сервера виглядає так:

Тепер залишилося розібратися з заголовками полями. З їх допомогою клієнт передає додаткову інформацію про себе або характері запиту. Наприклад, досить часто використовуються заголовним полем є 'User-Agent'. Опера, скажімо, шле наступне:

User-Agent: Mozilla / 4.0 (compatible; MSIE 5.0; Windows 98) Opera 6.02 [en]

Ось як тепер виглядає наш запит:

Треба зауважити, що хоча ці та інші заголовні поля є дуже бажаними, але, строго кажучи, вони не є обов'язковими, тобто їх може і не бути. Також я хочу звернути вашу увагу на те, що за останнім заголовним файлом слідують _два_ <\n>, а не один. Це важливо.

Тепер поглянемо на все вищевикладене з точки зору веб-сервера (адже ми ж збиралися писати веб-сервер, пам'ятайте.)). Він чекає, поки до нього не надійде запит, обробляє його і посилає відповідь, який виглядає приблизно так:

Отримавши запит, сервер повинен видати клієнту код відповіді (це тризначне число), а також супутнє йому повідомлення. Наприклад, якщо запитаний документ був знайдений, перший рядок відповіді буде приблизно такою:

Якщо коди відповідей жорстко задані стандартом (200 означає, що запит був успішно оброблений і буде повернений відповідний документ / інформація), то <сообщение> залежить тільки від вашої фантазії (звичайно, за змістом воно має збігатися з <кодом_ответа>. Наприклад, замість "HTTP / 1.1 200 Ok" ми можемо послати "HTTP / 1.1 200 You want it, you'll get it" :).

У відповіді сервера також можуть бути заголовки поля. Вони містять додаткові відомості про відповідь і даних, що йдуть разом з ним. Далі я приведу найважливіші (для нас).

Content-Type - цей заголовок задає тип віддаються даних. Найголовніші типи задані в стандарті, і від того, що ви напишіть в даному полі, залежить поведінка клієнта при прийомі даних. Наприклад, якщо ви посилаєте браузеру користувача html-файл, то необхідно вказати тип даних 'text / html', інакше браузер може відобразити файл неправильно, або взагалі не буде його відображати, а запропонує користувачеві його скачати.

Content-Length - тут вказується довжина даних (не включаючи сам відповідь з заголовками даними) в байтах.

Виходячи з вищесказаного, відповідь сервера буде виглядати приблизно так:

Якщо ми хочемо відіслати простий html-файл, то можемо скоротити відповідь до наступного:

В результаті отримуємо наступне. Клієнт шле запит на отримання документа, що лежить, наприклад, в корені сервера:

Сервер отримує цей запит, обробляє і видає кореневу сторінку:

"Веб-сервер", чий вихідний код був наведений вище, дуже примітивний. Він вміє тільки приймати запит і, не перевіряючи його на правильність, видавати тільки вітальну html-сторінку.

У http.asm все, я сподіваюся, досить зрозуміло. Ми инициализируем сокети, створюємо вікно (для чого, я поясню пізніше), входимо в цикл обробки повідомлень, а перед тим, як завершити роботу додатка, викликаємо WSACleanup.

Найцікавіше знаходиться в http_window.asm. При обробці повідомлення WM_CREATE ми створюємо сокет:

А потім викликаємо наступну функцію:

Ось що про цю функцію каже Platform SDK:

[Початок опису функції WSAAsynctSelect]

Функція WSAAsyncSelect вказує Windows посилати повідомлення про події, що стосуються певного сокета.

s - Дескриптор сокета, про події, пов'язані з яким, буде повідомлятися.

hWnd - Хендл вікна, якому будуть надсилатися ці повідомлення.

wMsg - Повідомлення, яке буде надсилатися.

lEvent - Бітова маска, в якій задаються питання, що цікавлять події.

Якщо виклик функції WSAAsyncSelect пройшов успішно, повертається значення дорівнюватиме нулю. В іншому випадку буде повернуто SOCKET_ERROR, а код помилки можна буде отримати, викликавши WSAGetLastError.

Врахуйте, що після того, як WSAAsyncSelect відішле вам повідомлення про конкретну подію, пов'язаному з сокетом, то поки ви не зробите певних дій, нового повідомлення про таке ж подію ви не отримаєте. Наприклад, якщо ви отримали повідомлення FD_ACCEPT (хтось намагається Законекть до вас), то повідомлення про інший спроби конекту ви не отримаєте до тих пір, поки не викличте функцію accept.

Ми задаємо WM_SOCKET, певне в http.asm, як повідомлення, яке надсилатиметься Windows, коли відбудеться цікавить нас повідомлення. Необхідна інформація буде знаходитися в wParam (дескриптор сокета, з яким пов'язана подія) і в lParam (в нижньому слові - код події).

Власне, в даних рядках і міститься відповідь на те, як зробити з програми сервер (не обов'язково web). Це робить функція listen.

[Початок опису функції listen]

Функція listen встановлює сокет в стан, в якому він слухає порт на предмет вхідних з'єднань.

s - Дескриптор сокета

backlog - Максимальна кількість вхідних з'єднань.

Якщо під час виклику не відбулося ніякої помилки, listen поверне нуль. В іншому випадку буде повернуто значення SOCKET_ERROR, а код помилки можна буде отримати за допомогою функції WSAGetLastError.

Було отримано повідомлення WM_SOCKET. Це означає, що сталося якесь цікавить нас подія, пов'язана зі слухачем сокетом.

Хтось намагається під'єднатися до нашого веб-сервера. Викликаємо функцію accept, щоб дозволити вхідні повідомлення.

[Початок опису функції accept]

Функція accept дозволяє вхідні повідомлення.

s - Дескриптор сокета, який раніше був поміщений в стан прослуховування за допомогою функції listen. Фактичне з'єднання здійснюється за допомогою сокета, який повертається accept'ом.

addrlen - Необов'язковий покажчик на подвійне слово, яке містить довжину addr.

Якщо не відбулося ніякої помилки, accept поверне дескриптор нового сокета, через який і буде відбуватися з'єднання.

В іншому випадку буде повернуто INVALID_SOCKET, а код помилки можна буде отримати за допомогою функції WSAGetLastError.

Якщо сокет був закритий клієнтом, то ми його теж закриваємо зі свого боку.

Для отримання докладної інформації про протокол HTTP я рекомендую вам звернутися до RFC 2068.

[C] Aquila / WASM.RU