Зарегистрируйтесь, чтобы продолжить обучение

Реализация сервера JS: HTTP Server

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

Операционная система (ОС)

Основой основ для программиста можно считать ОС. Да, у нас есть еще железо и архитектуру ЭВМ никто не отменял, но прикладному разработчику достаточно иметь общее понимание того, как работает железо, а вот от ОС не уйти совсем.

Процесс

Базовой единицей исполнения в ОС является процесс. Каждый раз, когда вы запускаете какую-либо программу, то запускается минимум один процесс. Кстати, может быть и больше, отсюда следует что программа != процесс. Список запущенных процессов можно посмотреть так:

ps -xf

PID TTY      STAT   TIME COMMAND
29 pts/1    S      0:00 bash
30 pts/1    S+     0:00  \_ top
22 pts/0    S      0:00 bash
32 pts/0    R+     0:00  \_ ps xf

Каждая строчка на рисунке выше – это информация о каком-либо процессе. Как видно, ОС содержит в себе различную информацию о каждом из процессов. На текущий момент нас интересует только один параметр, который называется PID. PID, как ни странно, расшифровывается как process identifier и фактически представляет из себя целое число, однозначно определяющее то, о каком процессе идет речь.

Сети

Основным (высокоуровневым) способом коммуникации между машинами является семейство протоколов TCP/IP. Большинство людей, которые пользуются интернетом или сетями в целом так или иначе слышали выражение ip адрес. Этот адрес (для версии IPv4) может выглядеть так: 10.0.152.23. Он указывает на какое-то устройство в сети, которое не обязательно представлено компьютером в привычном понимании этого слова.

Интерфейсы

По правде говоря, этот адрес связан даже не с самим устройством, а с конкретным интерфейсом устройства. Например, каждая сетевая карта будет представлена в системе как отдельный интерфейс. Кроме того, интерфейсы бывают виртуальными, то есть у них отсутствует физический элемент. Зачем это нужно? Самый тривиальный пример это так называемая обратная петля (loopback). Интерфейс, который присутствует по умолчанию в большинстве ОС. Любой трафик, посланный в этот интерфейс, тут же принимается им же.

Этот интерфейс позволяет обращаться к серверному приложению, расположенному на той же машине, без активного подключения к сети. Такая возможность особенно полезна для тестирования служб и их разработки. Адрес этого интерфейса всегда 127.0.0.1. Так же к нему можно обращаться по имени localhost.

Domain Name System

DNS - система доменных имён, благодаря которой нам необходимо запоминать только буквенные имена сайтов без необходимости знать конкретный ip адрес машины, на которую надо пойти. Общий принцип работы довольно прост. Каждый раз, когда в браузер вводится имя сайта, он обращается к специальным серверам и спрашивает их: 'Какой ip адрес у hexlet.io?'. Дальше происходит немного магии, и, в конце концов, эта система возвращает (если найдёт) этот адрес. Затем браузер устанавливает tcp соединение и начинает свою работу.

Порты

Когда происходит общение удаленных машин друг с другом по tcp, то в реальности между собой общаются процессы ОС, а не компьютеры в целом. Отсюда возникает вопрос: каким образом, зная только ip адрес, постучаться на чужую машину в интересующую нас программу. Конкретно в этом курсе нас интересует веб-сервер. Короткий ответ: никак. И действительно, одного ip адреса недостаточно.

В tcp существует такое понятие, как порт. Это целое число, означающее точку входа в процесс запущенной программы. То есть при получении данных по tcp ОС смотрит то, для какого порта они предназначены. Затем она находит процесс, соответствующий этому порту, и передает данные в него. Важным следствием этого подхода становится тот факт, что невозможно занять уже занятый порт. Иначе это ввело бы неоднозначность. И действительно, при старте сервера, который пытается слушать занятый другой программой порт, будет получена такая ошибка:

Error: listen EADDRINUSE :::4000

Мы можем даже посмотреть на того, кто занял этот порт:

lsof -i :4000

COMMAND   PID     USER TYPE NODE NAME
node    40726 mokevnin IPv6 TCP  *:terabase (LISTEN)

Суммируя вышесказанное, делаем вывод, что любое серверное приложение при старте должно начать слушать определенный порт для возможности получать данные по сети.

Если вернуться к браузеру, то может возникнуть вопрос: почему мы не указываем порт, когда загружаем сайты, откуда браузер знает, куда стучаться на сервер? Ответ на этот вопрос крайне прост: браузер действительно знает порт, на который нужно идти. И по умолчанию это порт с номером 80.

Веб-сервер

Начнём с иллюстрации:

// server.js
import http from 'http';

const server = http.createServer((request, response) => {
  // content-length формируется автоматически!
  response.write('hello, world!');
  response.end();
});

const port = 4000;
server.listen(port, () => {
  console.log('Server has been started');
});
  1. Импортируется модуль http. Он встроен в node.js и позволяет создать веб-сервер (и не только).
  2. Создаётся веб-сервер. В функцию createServer передается обработчик запросов. Он будет вызываться на каждый входящий запрос.
  3. Сервер вешается на порт.

Обработки запроса в данном примере как таковой нет. Сервер будет отвечать по http фразой hello, world! на любой входящий запрос. Делается это с помощью объекта response, который представляет собой http-ответ. В примере выше мы используем две функции интерфейса response. Функцию write, которая позволяет передать текст в теле http ответа, и функцию end, которая означает, что мы закончили формирование ответа. Обратите внимание: нам не пришлось руками выставлять заголовок content-length, модуль http самостоятельно вычисляет размер тела и подставляет необходимый заголовок.

Для запуска нашего сервера необходимо набрать команду:

node server.js # blocking

Запуск приводит к блокировке. Сервер запущен и работает. Чтобы его остановить, надо нажать комбинацию ctrl+c.

Теперь можно выполнить запрос к серверу:

Проделав данную процедуру, можно будет сказать, что вы запустили свой первый сайт на node.js ;)

Но дальше нас поджидает сюрприз. Если попробовать поменять код сервера, и писать в ответ my first web server, то без перезапуска сервера ничего не изменится. После того, как сервер запущен, он больше не перечитывает файлы с диска и не изменяет своего состояния. Такое поведение не является особенностью модуля http, так устроен сам язык. Поэтому не забывайте перезапускать сервер, после внесения изменений.


Дополнительные материалы

  1. Что такое DNS

Аватары экспертов Хекслета

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят команда поддержки Хекслета или другие студенты

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff

Используйте Хекслет по-максимуму!

  • Задавайте вопросы по уроку
  • Проверяйте знания в квизах
  • Проходите практику прямо в браузере
  • Отслеживайте свой прогресс

Зарегистрируйтесь или войдите в свой аккаунт

Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»