В этом посте мы создадим HTTP-сервер с помощью модуля Nodejs net, который поможет вам понять основы HTTP. Сервер будет очень простым и будет обслуживать html-файлы в каталоге.
Сначала создадим tcp-сервер, который будет приветствовать клиентов, используя модуль net, чтобы мы могли понять, как работает HTTP.
const net = require('net');
const server = net.createServer();
server.on('connection', (socket) => {
console.log('client connected');
socket.on('data', (data) => {
console.log(data.toString());
socket.write('hello');
socket.end();
});
});
server.listen(9090, () => {
console.log('Listening 9090');
});
Здесь мы создали сервер, который слушает PORT 9090. После того как клиент устанавливает соединение, он печатает client connected
. Когда клиент отправляет данные, он печатает их и отправляет hello
клиенту. После этого socket.end();
закрывает соединение.
Давайте попробуем с netcat. Мы будем использовать netcat в качестве TCP-клиента. Если у вас нет netcat, вы можете использовать что угодно.
Запустите сервер. Откройте новую вкладку терминала для netcat. nc localhost 9090
создаст соединение, и вы сразу увидите сообщение client connected
на сервере. Перейдите на вкладку терминала netcat и напишите TEST
и нажмите Enter. Вы увидите сообщение hello
, пришедшее с сервера. Каждый раз, когда вы отправляете сообщение с клиента на сервер, вы будете видеть сообщение hello
.
Теперь перейдите на вкладку терминала сервера и вы должны увидеть сообщение, которое вы отправили с клиента.
Давайте проверим это на браузере. Вы можете использовать ваш браузер по умолчанию, но я буду использовать safari. Откройте localhost:9090 в браузере.
Мы видим, что клиент установил соединение и отправил некоторые данные, но в браузере мы видим ошибку can not connect to server
.
Почему 🤔. Потому что сообщение, которое возвращает сервер, еще не является действительным HTTP-ответом. Мы должны применить протокол передачи гипертекста (HTTP), чтобы браузер мог понять сообщение и отобразить его.
По сути, HTTP говорит следующее;
сообщение состоит из начальной строки, нуля или более полей заголовков (также известных как «заголовки»), пустой строки (т.е. строки, в которой ничего не предшествует CRLF), указывающей на конец полей заголовков, и, возможно, тела сообщения.
Дополнительная информация
Итак, наше ответное сообщение должно выглядеть следующим образом:
- Строка состояния. Она состоит из трех элементов:
- Номер версии HTTP.
HTTP/1.1
. - Код состояния.
200
- Фраза причины.
OK
- Номер версии HTTP.
- Заголовки
- Тело
const net = require('net');
const server = net.createServer();
server.on('connection', (socket) => {
console.log('client connected');
socket.on('data', (data) => {
console.log(data.toString());
const statusLine = 'HTTP/1.1 200 OKn';
socket.write(statusLine);
const header = 'Server: SimpleHTTPServer NodejsnContent-type: text/htmlnn';
socket.write(header);
const body = 'Hello World';
socket.write(body);
socket.end();
});
});
server.listen(9090, () => {
console.log('Listening 9090');
});
Обратите внимание, что есть один n
в конце statusLine
и между заголовками (Server:, Content-type:), но есть два n
после заголовков. Это указывает на то, что начинается тело.
Давайте еще раз откроем localhost:9090
в браузере.
Тадаа 🎉.
Подача HTML-файлов
Теперь мы можем создать программу, которая будет обслуживать HTML файлы. Она будет понимать, какой HTML файл хочет получить клиент из заголовка запроса. Например, если клиент обращается к localhost:9090/contact, мы должны увидеть GET /contact HTTP/1.1
в заголовке запроса. Поэтому мы разберем заголовок запроса, попытаемся найти этот файл (например, contact.html) и ответим клиенту. Если клиент перейдет на домашнюю страницу, мы ответим index.html.
Как и HTTP-ответ, HTTP-запрос следует тем же правилам. За каждым HTTP-заголовком следует строка возврата каретки (CRLF). После последнего из HTTP-заголовков используется дополнительный CRLF (для получения пустой строки), а затем начинается тело сообщения.
socket.on('data', (data) => {
const [requestHeader] = data.toString().split('nn');
const [requestLine] = requestHeader.split('n');
const [method, path, httpVersion] = requestLine.split(' ');
const header = 'HTTP/1.1 200 OKnServer: SimpleHTTPServer Nodejsnn';
socket.write(header);
const body = `${method} ${path} ${httpVersion}`;
socket.write(body);
socket.end();
});
data.toString().split('nn')
разделяет заголовок и тело запроса. Мы получаем первый элемент массива после разделения (который является заголовком) и присваиваем его requestHeader
.
Мы знаем, что заголовки делятся на новые строки. .split('n')
разделяет все заголовки и помещает каждый заголовок в массив, но мы получаем только первый заголовок, который сообщает нам метод, путь и версию (GET /contact HTTP/1.1
) и присваиваем его requestLine
.
Мы разделяем requestLine
на пустые пробелы и получаем method, path, httpVersion
.
Наконец, мы возвращаем эти значения клиенту. Если вы перейдете в браузер, то увидите на экране GET /contact HTTP/1.1
.
Теперь давайте создадим два HTML-файла, index.html и contact.html. Вы можете поместить в них все, что захотите!
Перед слушателем соединения создайте новую функцию handleRequest
. Она будет получать путь в качестве параметра.
const handleRequest = async (path) => {
let requestedFile = `${path}.html`;
if (path === '/') {
requestedFile = '/index.html';
}
const fileData = await fs.promises.readFile(`.${requestedFile}`);
return fileData;
};
Если клиент запросил домашнюю страницу, то запрашиваемый файл будет index.html, если нет, то запрашиваемый файл будет путь (например: localhost:9090/contact). Он прочитает запрошенный файл и передаст данные в fileData
с помощью обещаний (Мы также можем использовать .pipe()
вместо обещаний).
// ...
const fileData = await handleRequest(path);
const header = 'HTTP/1.1 200 OKnServer: SimpleHTTPServer Nodejsnn';
socket.write(header);
socket.write(fileData);
socket.end();
//...
Мы отправляем данные HTML файла, которые вернулись из handleRequest
с помощью socket.write
клиенту.
Чтобы лучше понять, мы можем снова использовать netcat. Откройте терминал. Пока сервер работает, напишите nc localhost 9090
. Это приведет к подключению к серверу. Теперь мы должны отправить сообщение на сервер. Нам нужен файл contact.html. Поэтому если мы отправим в заголовке путь /contact, сервер должен ответить файлом contact.html. Для этого напишите GET /contact HTTP/1.1rn
.
Теперь вы создали простой HTTP-сервер с помощью модуля nodejs net.
Спасибо за прочтение. Вы можете найти исходный код на Github