И, спрашивается, зачем?
А затем, чтобы рассказать на примере, как обрабатываются GET-запросы. Пока что мой сервер ничего не умеет, кроме обработки GET-запросов и вывода информации о клиенте, он даже не читает никакие файлы с диска. Его задача другая, показать, как происходит обработка запросов. Я это сделал потому, что как оказалось, далеко не все веб-разработчики понимают, как это происходит. Мне вот повезло, давным давно приходилось разбирать аж multipart-запросы и обрабатывать заголовки, приходящие на сервер - в бытность, когда писал свою библиотеку на Perl.
Но сейчас я сделал пример на Ruby и не поймет его только баран (или обезьяна). Я постарался максимально документировать его, чтобы было все понятно.
require 'socket' # Из стандартной библиотеки Ruby
# Открываем TCP сокет (Это можно было сделать и по-другому, но я решил
# не создавать сокет вручную, а воспользоваться более высоким уровнем).
# Как видно, сокет будет ожидать соединения (т.к. он является сервером) на
# localhost на 8000-м порту.
server = TCPServer.open('localhost', 8000)
# Бесконечный цикл.
# После того, как соединение от клиента будет принято - клиенту
# будет возвращен ответ и соединение закроется - программа перейдет
# в начало цикла, чтобы ожидать новый запрос
loop do
# Принимаем входящее соединение клиента. Пока соединение не будет принято,
# сокет будет ожидать его, и код ниже этой строки
# не будет выполняться.
client = server.accept
client_headers = []
# Получаем данные клиента построчно
while line = client.gets
client_headers << line
# Протокол HTTP предписывает \r\n как
# перевод строки — всегда, независимо от операционной системы.
# Поэтому, тело запроса нам не нужно (т.к. мы обрабатываем
# только GET-запросы. Получить тело нам понадобилось бы
# только в POST-запросе, а в GET-запросе тела просто-напросто нет).
break if line == "\r\n"
end
# Поспим 10 секунд. Я сделал это для того, чтобы проиллюстрировать кое что.
# Попробуйте одновременно обратиться в 2-х закладках браузера к веб-серверу и
# вы увидите, что второй запрос будет происходить в два раза дольше
# первого, т.к. он будет дожидаться, пока обработается первый запрос. Чтобы запросы
# обрабатывались параллельно (т.е. не стояли в очереди) - можно сделать
# несколько тредов (воркеров).
sleep 10
# Возвращаем ответ клиенту и закрываем соединение. В виде тела ответа
# показываем клиенту его же заголовки.
client.print "HTTP/1.1 200/OK\n"
client.print "Content-type: text/html\n\n"
client.print client_headers.join "<br/>"
client.close
end
Я создал TCP-сервер при помощи объекта TCPServer, но можно было сделать и на чистом объекте Socket. Просто TCPServer, который наследуется от TCPSocket, а тот, в свою очередь, от Socket - уже настроен для того, чтобы быть сервером, то есть он умеет висеть на определенном хосте и порту и ожидать входящие соединения.
Из кода видно, что:
- Веб-сервер обрабатывает только GET-запрос
- Он не читает никаких файлов с диска, а просто отображает информацию о клиенте, который к нему присоединился
- Всегда отвечает со статусом 200
Можно сохранить код в файл server.rb, а затем запустить его:
ruby server.rb