Simple Rails. Часть 1 — начало: свой веб сервер

Всем привет, думаю читатель, нажавший на данный заголовок, уже, возможно, догадывается, о чем примерно пойдет речь. Данная статья рассчитана на тех, у кого уже есть какое-либо понимания языков программирования (лучше если именно Ruby), а также хотя бы общие сведения о работе ОС и сети.

Данный цикл статей будет посвящен написанию с нуля без использования сторонних библиотек аналога известного фреймворка Ruby on Rails который назовем SimpleRails. Конечно, мы не будем его повторять полностью, а лишь попытаемся повторить основные функциональные возможности.

Для тех, кто не в курсе в мире веб разработки существует фреймворк Ruby on Rails. Фреймворк горячо любим автором и сообществом и используется для создания полнофункционального сайта или по-другому веб приложения. Но все ли знают и понимают, как оно там вообще работает?

Как говорил классик — позвольте мне 30 секунд исторической справки. Все примерно слышали, что сайты работают через протокол HTTP/ HTTPS. Для упрощения возьмем протокол HTTP. Данный протокол текстовый (и работает поверх протокола TCP описание работы которого мы опустим), но что нам это говорит? По сути, это просто строка, в которой определенным способом хранится информация. То есть, по сути, вся работа сайта — это отправка и получение строк в определенном формате. Вот и все. Возможно, для кого-то это покажется слишком простым, но так оно и есть. Когда вы открыли сайт, на самом деле вам пришла текстовая строка, которую браузер обработал и вам отобразил.

Простейшая схема будет выглядеть так. Здесь HTTP request и HTTP response это просто строки. Верхняя отправляется на сервер, где находится сайт (пока опустим сетевые моменты). Когда вы у себя в браузере вводите название сайта или нажимаете на его ссылку — вы отправляете строку на сервер о желании получить страничку сайта, сервер отправляет вам строку с этой страничкой.

Рис 1 – Простейшая работа сайта

Рис 1 — Простейшая работа сайта

Что же за строки такие отправляются и что из себя по итогу представляет этот HTTP протокол?

Простейший запрос GET
GET /customers/42 HTTP/1.1

Простейший запрос POST

POST /customers HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 38

email=test@acme.com&password=pass123

P.S. пример запросов любезно взят с сайта

Запрос может содержать разные части — Разберем сначала GET запрос (интерпретация будет вольная, за точной можно отправиться за соответствующим RFC). В первой строке сначала указывается метод запроса (GET), затем путь до ресурса (/customers/42), который мы хотим получить, и затем версия самого протокола (HTTP/1.1). Запрос может быть и сложнее, как например предоставленный далее POST запрос. Аналогичным образом у него формируется первая строка, но затем идут еще несколько строк. Сначала идут строки заголовков (7 и 8 строки), а затем тело запроса (10 строка). Строки разделяются между собой символами \r\n.

С самими запросами разобрались, но кто их шлет и какими средствам получает?

Поскольку мы работаем на компьютере, на нем установлена та или иная ОС. У основных широко используемых ОС, на которых есть возможность сетевого взаимодействия есть понятие сокетов. Согласно определению википедии сокет — это программный интерфейс для обеспечения обмена данными между процессами. Возможно все равно не до конца понятно. Для большего упрощения будем считать, что сокет — это буфер, в который можно записать или считать строку.

Возьмем аналогию с кассой в банке. Как там происходит взаимодействие? Вы идете в банк, просите вас обслужить в кассе, подходите к окну с деньгами и документами, кладете их в специальный ящик и ждете пока оператор за окошком их обработает и вернет вам обратно тоже деньги или документы. С сокетами также, вы «идете» к операционной системе, просите дать вам сокет, ОС предоставляет вам сокет, вы в него пишете свою строку, ждете и получаете другую строку.

Рис 2 – Простейшая работа кассы в банке

Рис 2 — Простейшая работа кассы в банке

Вернемся к реализации. Взглянем на вырезку из документации Ruby.

# A simple server might look like this:
require 'socket'

server = TCPServer.new 2000 # Server bound to port 2000

loop do
  client = server.accept    # Wait for a client to connect
  client.puts "Hello !"
  client.puts "Time is #{Time.now}"
  client.close
end

# A simple client may look like this:
require 'socket'

s = TCPSocket.new 'localhost', 2000

while line = s.gets # Read lines from socket
  puts line         # and print them
end

s.close             # close socket when done

Немного разберем данный код. В коде сервера мы сначала подключаем гем с сокетами, затем просим ОС открыть новый сокет на определенном порту, затем в цикле начинаем ожидать получения данных. После получения данных мы пишем обратно пару строк кода с текущем временем и отправляем их обратно. В коде клиента все работает схожим образом. Мы также подключаем гем с сокетами, затем просим ОС подключиться к существующему сокету на определенном порту, затем запрашиваем данные у сервера, печатаем их в консоль и закрываем подключение к сокету. Можно провести соответствующие аналогии с примером с банком выше.

Сверху уже вполне работающий код сервера. К сожалению, он не удовлетворяет требованию формата HTTP, но мы можем его проверить запустив клиентский код. Немного дополним его для удовлетворения требованиям формата HTTP.

require 'socket'
server = TCPServer.new 2000

puts "server started"

while session = server.accept
  puts 'get request'
  session.print "HTTP/1.1 200\r\n"
  session.print "Content-Type: text/html\r\n"
  session.print "\r\n"
  session.print "

Hello from SimpleRails!

" session.close end

Этот код уже можно запустить и получить ответ в браузере. В нем мы отправляем ответ клиенту/браузеру тоже в формате HTTP. Сначала указываем версию протокола и код возврата (200), затем пишем заголовок о том, что формат данных будет текст или html и затем сами данные в формате html.

Рис 3 – Ответ от сервера

Рис 3 — Ответ от сервера

Но почему мы не пишем наши приложения таким образом? Почему никогда не залезаем в такие дебри? Можно, конечно, в этом одном файлике вручную обрабатывать каждый приходящий пакет, но это выйдет слишком сложно, получится непонятное месиво не поддерживаемого кода. В результате были придуманы некоторые абстракции, которым будут посвящены следующие статьи.

© Habrahabr.ru