- Оригинальный адрес:Node.js Fundamentals: Web Server Without Dependencies
- Оригинальный автор:Seva Zaikov
- Перевод с:Программа перевода самородков
- Постоянная ссылка на эту статью:GitHub.com/rare earth/gold-no…
- Переводчик:Mirosalva
- Корректор:kasheemlew,Endone
Node.js — очень популярный выбор технологии для создания серверной части веб-приложений, и существует множество зрелых веб-фреймворков, таких какexpress, koa, hapijs. Тем не менее, в этом уроке у нас нет никаких зависимостей, мы просто используем ядро Node.httpПакет собирает сервер и шаг за шагом исследует все важные детали. Это не та ситуация, с которой вы будете сталкиваться часто, и она может помочь вам лучше понять все упомянутые выше фреймворки — многие существующие библиотеки не только используют этот пакет под капотом, но и часто выставляют примитивные объекты, благодаря чему вы можете применять их в некоторых специальные задачи.
Оглавление
- Hello, World
- детали ответа
- HTTP-сообщение
- HTTP-заголовок
- Коды состояния HTTP
- маршрутизация
- HTTP-метод
- Кэш файлов cookie
- параметры запроса
- Содержимое тела запроса
- конец
Hello, world
Для начала начнем с самой простой программы — возвращающей классический ответ «hello, world». Чтобы построить сервер с Node.js, нам нужно использоватьhttpВстроенные модули моделирования, особенноcreateServerфункция.
const { createServer } = require("http");
// 这是一种好的实现
// 允许运行在不同的端口
const PORT = process.env.PORT || 8080;
const server = createServer();
server.on("request", (request, response) => {
response.end("Hello, world!");
});
server.listen(PORT, () => {
console.log(`starting server at port ${PORT}`);
});
Давайте перечислим все для этого короткого примера:
- Используйте функцию createServer для создания экземпляра объекта службы.
- для нашей сервисной программы
request
Добавить прослушиватель событий к событию - Запустите нашу сервисную программу на порту, указанном в переменной среды, и используйте порт 8080 по умолчанию.
Сервисная программа, которую мы создали,http.ServerЭкземпляр класса, унаследованный от Objectnet.Server, который, в свою очередь, наследуется от классаEventEmitter. Есть много событий, которые мы можем услышать, но самые важные из нихrequest, и предоставить его прослушиватель при создании службы.Обычные реализации следующие:
const { createServer } = require("http");
// 这样等同于 `server.on('request', fn);`
createServer((request, response) => {
response.end("Hello, world!");
}).listen(8080);
Последним шагом является запуск нашего сервиса. Я звонюserver.listenспособ запуска, и вы можете указать порт и что выполнять после запуска. Стоит отметить, что сервис запускается не сразу, он должен сначала привязаться к порту при доступе к входящим запросам, но на практике это не очень важно, потому что процесс практически мгновенный. Вы также можете пройтиlisteningметод события для прослушивания только этого конкретного события.
детали ответа
Теперь, когда мы узнали, как создать экземпляр нового приложения-службы, давайте посмотрим, как на самом деле ответить на запрос пользователя. В нашем единственном обработчике событий мы используемresponse.endметод отвечает обычным классическимHello, world!повторить. Вы можете увидеть эту подпись с помощью метода записываемого потокаwritable.endочень похоже, потому что объекты запроса и ответа являются объектами потокаstreams, в то время как запрос — это только поток для чтения, а ответ — только для записи. Почему они должны быть потоковыми объектами? Почему мы не можем отправить весь ответ?
Ответ заключается в том, что нам не нужно делать все, прежде чем мы сможем ответить. Представьте себе такую ситуацию, когда мы читаем файл из файловой системы, и этот файл относительно большой. Поэтому мы можем пройтиfs.createReadStreamметод открывает файловый поток, чтобы мы могли немедленно записать ответ. Кроме того, мы можем напрямую передавать вход на выход!
Теперь, поскольку это объект потока, мы можем сделать следующее:
const { createServer } = require("http");
createServer((request, response) => {
response.write("Hello");
response.write(", ");
response.write("World!");
response.end();
}).listen(8080);
Таким образом, мы можем напрямую писать в наш объект потока несколько раз. Будьте осторожны, делая это в любой форме цикла, так как вам придется иметь дело с обратным давлением самостоятельно, и лучше всего направить поток непосредственно на объект потока. Опять же, обратите внимание, что в конце используйтеresponse.end()
метод. Это обязательно, без этого вызова узел будет держать это соединение открытым, вызывая утечку памяти и ожидание клиентов.
Наконец, давайте продемонстрируем, как конвейерный метод потоков работает для объектов ответа и других потоков. Для этого мы используем__filenameпеременная для чтения исходного файла:
const { createReadStream } = require("fs");
const { createServer } = require("http");
createServer((request, response) => {
createReadStream(__filename).pipe(response);
}).listen(8080);
Нам не обязательно вручную вызыватьres.end
метод, потому что он также автоматически закрывает переданный по конвейеру поток, когда исходный поток заканчивается.
HTTP-сообщение
Наша сервисная программа реализуетHTTP-протокол, который представляет собой набор текстовых правил, которые позволяют клиентам запрашивать определенную информацию в предпочитаемом ими формате, а также позволяют серверам отвечать данными и дополнительной информацией, такой как формат, состояние подключения, информация о кэше и т. д.
Давайте посмотрим на типичный запрос к веб-странице:
GET / HTTP/1.1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_2) AppleWebKit/537.36 (KHTML, like Gecko)
Host: blog.bloomca.me
Accept-Language: en-us
Accept-Encoding: gzip, deflate
Connection: Keep-Alive
Это то, что отправляет наш браузер, когда вы запрашиваете страницу, в дополнение к вышеперечисленному он отправляет больше заголовков, передает файлы cookie (тоже тип заголовка) и другую информацию. Нам важно понимать: у всех запросов есть методы, пути (маршруты) и списки заголовков, которые представляют собой пары ключ-значение (если вы хотите знать о куках, это просто заголовок со специальным значением). HTTP — это текстовый протокол, и, как видите, вы сами можете его прочитать. Хотя это всего лишь набор протоколов, браузеры и сервисы, реализующие этот протокол, стараются его соблюдать, и именно так работает весь Интернет. Соблюдаются не все правила, но основные — HTTP-операции, маршрутизация, куки — достаточно надежны, чтобы всегда стремиться к предсказуемому поведению.
Заголовок HTTP-заголовков
я могу пройтиrequest.headersсвойство для доступа ко всем заголовкам, отправленным клиентом. Например, чтобы определить тип языка, выбранный клиентом, мы можем сделать что-то вроде этого:
const { createServer } = require("http");
createServer((request, response) => {
// 这个对象中所有的 header 都是小写
const languages = request.headers["accept-language"];
response.end(languages);
}).listen(8080);
Для моего личного выбора языка я использую "en-US,en;q=0.9,ru;q=0.8,de;q=0.7", что означает, что я предпочитаю английский, затем русский и, наконец, немецкий. Обычно браузер использует язык вашей ОС, но он будет заменен, что не является лучшей зависимостью, поскольку пользователь не может напрямую управлять им (и разные браузеры имеют разные варианты для этой строки кода).
Чтобы написать заголовок, вы должны понимать, что HTTP — это протокол, который предписывает, чтобы метаданные шли первыми, а затем разделитель (две новые строки) сопровождался собственно телом сообщения. Это означает, что как только вы начнете отправлять контент, вы не сможете изменить свои заголовки! Это вызовет ошибку в Node и фактически прервет вашу программу.
Есть два способа установить заголовок:response.setHeaderМетоды иresponse.writeHeadметод. Разница между ними заключается в том, что первый более особенный, и если используются оба, все заголовки будут объединены и начнутся сwriteHeadЗначение заголовка, установленное методом, имеет более высокий приоритет.writeHeadиwriteФункция метода та же, что означает, что вы не можете изменить заголовок позже.
const { createServer } = require("http");
createServer((req, res) => {
res.setHeader("content-type", "application/json");
// 我们需要发送 Buffer 或者 String 类型数据,我们不能直接传递一个对象
res.end(JSON.stringify({ a: 2 }));
}).listen(8080);
Коды состояния HTTP Коды состояния
HTTP определяет коды состояния, которые должен иметь каждый ответ,списокЗначение каждого кода состояния определяется в . Опять же, не все строго следуют этому списку.
Перечислим наиболее важные коды состояния:
2xx — код успеха:
- 200: наиболее распространенный код состояния, который по умолчанию означает «ОК» в Node.js.
- 201: Создан новый объект.
- 204: Код успеха, но ответ не получен. Например, код состояния после удаления сущности.
3xx – код перенаправления
- 301: постоянная миграция, в возвращаемой информации есть новый URL-адрес.
- 302: временная миграция, но с другим новым URL. После успешного POST-запроса на страницу перенаправления вновь созданная страница объекта становится доступной.
Обратите внимание на коды состояния 301/302. Браузеры, как правило, запоминают 301, и если вы случайно пометите некоторые URL-адреса кодом состояния 301, браузеры могут по-прежнему делать это после получения нового ответа (они даже не проверяют).
4xx - код ошибки клиента
- 400: неверный запрос, например, неправильная передача параметров или отсутствие некоторых параметров.
- 401: Не авторизован, пользователь не аутентифицирован и поэтому недоступен.
- 403: Доступ запрещен. Пользователь обычно аутентифицирован, но операция не авторизована. Опять же, на некоторых серверах его можно спутать с кодом состояния 401.
- 404: Не найдено, указанная страница или данные не могут быть найдены по указанному URL-адресу.
5xx — код ошибки сервера
- 500: Внутренняя ошибка сервера, например, ошибка подключения к базе данных.
Эти коды ошибок являются наиболее распространенными типами, и их достаточно, чтобы вы могли сопоставить правильный код состояния для запроса. В Node.js мы можем использовать либоresponse.statusCodeметод, вы также можете использоватьresponse.writeHeadметод. На этот раз воспользуемсяwriteHeadметод установки пользовательского HTTP-сообщения:
const { createServer } = require("http");
createServer((req, res) => {
// 表明没有内容
res.writeHead(204, "My Custom Message");
res.end();
}).listen(8080);
Если вы попытаетесь открыть эти коды в браузере и просмотреть HTML-запрос на вкладке «Сеть», вы увидите «Код состояния: 204 Мое пользовательское сообщение».
маршрутизация
На сервере Node.js все запросы обрабатываются одним обработчиком запросов. Мы можем проверить это, запустив любой из наших сервисов или запросив другой URL-адрес, например адресhttp://localhost:8080/homeиhttp://localhost:8080/about. Вы можете видеть, что тест вернет тот же ответ. Однако в объекте запроса у нас есть свойствоrequest.url, мы можем использовать его для создания простой функции маршрутизации:
const { createServer } = require("http");
createServer((req, res) => {
switch (req.url) {
case "/":
res.end("You are on the main page!");
break;
case "/about":
res.end("You are on about page!");
break;
default:
res.statusCode = 404;
res.end("Page not found!");
}
}).listen(8080);
Есть много предостережений (попробуйте в/about/
добавляет косую черту в конце), но у вас есть способ. Во всех фреймворках есть главный обработчик, который направляет все запросы зарегистрированным обработчикам.
HTTP-метод
вы можете быть знакомы сHTTP methods/verbs,НапримерGETиPOST. Они являются частью самого протокола HTTP, и их значение очевидно. Однако и в них есть много тонких деталей, в которые я не хочу вникать, и ради краткости скажуGETзаключается в получении данных, аPOSTзаключается в создании новых объектов сущности. Никто не запрещает вам использовать их для других целей, но стандарты и соглашения не советуют этого делать.
Как упоминалось выше, сервисная программа в Node.js имеетrequest.methodСвойства, которые можно использовать для нашей внутренней логической обработки. Кроме того, в самом Node.js нет ничего, что могло бы абстрагироваться от обработки различных методов. Нам нужно самим построить абстрактный метод обработки:
const { createServer } = require("http");
createServer((req, res) => {
if (req.method === "GET") {
return res.end("List of data");
} else if (req.method === "POST") {
// 创建新实体
return res.end("success");
} else {
res.statusCode(400);
return res.end("Unsupported method");
}
}).listen(8080);
Кэш файлов cookie
Файлы cookie заслуживают отдельной статьи, так что не стесняйтесь читать о них подробнее.MDN guide.
Два ключевых слова: файлы cookie используются для хранения некоторых данных во время процесса запроса, поскольку HTTP — это протокол без сохранения состояния, технически без файлов cookie (или локального хранилища) мы должны делать это до того, как каждая операция, требующая аутентификации, должна выполнять операцию входа в систему. Мы храним файлы cookie на стороне клиента (обычно в браузере), чтобы браузер мог отправить намCookieи содержит заголовки всех объектов cookie, мы можем передатьSet-Cookie
заголовок в ответ на запрос, сообщающий клиенту, какой файл cookie установить (например, токен доступа); после того, как клиент сохранит его, он отправляет его обратно на сервер при каждом последующем запросе.
Запустим следующий код:
const { createServer } = require("http");
createServer((req, res) => {
res.setHeader(
"Set-Cookie",
["myCookie=myValue"],
["mySecondCookie=mySecondValue"]
);
res.end(`Your cookies are: ${req.headers.cookie}`);
}).listen(8080);
При первом обновлении браузера вы можете увидеть некоторые старые кэшированные файлы cookie, но не можетеmyCookieилиmySecondCookie. Однако, если вы снова обновите свой браузер, вы увидите значения для обоих! Причина этого в том, что ответЗаднийКлиент устанавливает их значение в файлах cookie, и именно этот ответ отображает нашу страницу. Поэтому мы будем тольков следующий разЭти возвращенные файлы cookie кэша не принимаются от клиента до тех пор, пока не будет получен запрос.
А что, если мы хотим использовать значение cookie в нашем коде? Cookie — это просто заголовок в HTTP, поэтому это строка со своими правилами — использование cookie.key=value
режим записи, включая параметры, в;
Сегментация символов. Вы можете написать свой собственный парсер (что-то вроде этого постаthis SO answer), но я рекомендую вам использовать другие внешние библиотеки, совместимые с вашим фреймворком или библиотекой, на выбор.
Кроме того, обратите внимание, что вы не можете удалить файл cookie, так как он принадлежит клиенту, но вы можете сделать его нулевым или просроченным, установив для него значениенедействителен.
параметры запроса
Обычно для специальных обработчиков задаются параметры: например, если вы хотите отобразить все изображения, мы можем указать страницу, чего можно добиться с помощью параметров запроса. Они добавляются к URL через символ?
Отдельно от пути:http://localhost:8080/pictures?page=2
, как видите, мы запросили вторую страницу галереи изображений. Или мы могли бы просто встроить его в саму URL-ссылку, но вот проблема: если есть более одного параметра, URL-адрес может быстро запутаться. Параметры запроса не фиксированы, поэтому мы можем добавлять любое количество контента, а также удалять/добавлять новый контент в будущем.
Для того, чтобы получить его в нашей сервисной программе, мы используемrequest.urlсвойства, вмаршрутизацияМы уже использовали его в разделе. Теперь нам нужно отделить наши URL-адреса от параметров запроса, хотя мы могли бы сделать это вручную, но в этом нет необходимости, поскольку это уже реализовано в Node.js:
const { createServer } = require("http");
createServer((req, res) => {
const { query } = require("url").parse(req.url, true);
if (query.name) {
res.end(`You requested parameter name with value ${query.name}`);
} else {
res.end("Hello!");
}
}).listen(8080);
Теперь, если вы добавите параметры запроса для запроса любой страницы, вы увидите эффект в ответе, например этотhttp://localhost:8080/about?name=SevaЗапрос вернет строку с нашим идентификатором:
你的请求参数名带有值 Seva
Содержимое тела запроса
Последнее, на что мы хотим обратить внимание, — это содержимое тела запроса. Мы уже видели, что всю информацию (маршрутизацию и параметры запроса) можно получить из самого URL, но как нам получить реальные данные от клиента? Вам не нужно обращаться к нему напрямую, но мы можем получить переданные данные напрямую, прочитав поток, что является одной из причин, по которой объект запроса является объектом потока. Давайте напишем простую сервисную программу, которая ожидает получить объект JSON из запроса POST и вернет, если он недействителен JSON.400код состояния.
const { createServer } = require("http");
createServer((req, res) => {
if (req.method === "POST") {
let data = "";
req.on("data", chunk => {
data += chunk;
});
req.on("end", () => {
try {
const requestData = JSON.parse(data);
requestData.ourMessage = "success";
res.setHeader("Content-Type", "application/json");
res.end(JSON.stringify(requestData));
} catch (e) {
res.statusCode = 400;
res.end("Invalid JSON");
}
});
} else {
res.statusCode = 400;
res.end("Unsupported method, please POST a JSON object");
}
}).listen(8080);
Самый простой способ проверить это использоватьcurl. Во-первых, используйтеGETметод запроса:
> curl http://localhost:8080
Unsupported method, please POST a JSON object
Теперь используйте случайную строку в качестве наших данных, чтобы инициироватьPOSTпросить
> curl -X POST -d "some random string" http://localhost:8080
Invalid JSON
Наконец, сгенерируйте правильный ответ и посмотрите результат:
> curl -X POST -d '{"property": true}' http://localhost:8080
{"property":true,"ourMessage":"success"}
конец
Как видите, при использовании только встроенных модулей для обработки каждого запроса требуется много утомительной работы — например, не забывать закрывать поток ответов каждый раз или каждый раз, когда вы отправляете объект в виде строкового JSON, установите один.Content-Type: application/json
Введите заголовки, или проанализируйте параметры запроса, или напишите свою собственную систему маршрутизации... все это делается, просто помните, что под капотом фреймворка используются эти основные методы, вам не нужно беспокоиться о том, как это на самом деле работает внутри.
Если вы обнаружите ошибки в переводе или в других областях, требующих доработки, добро пожаловать наПрограмма перевода самородковВы также можете получить соответствующие бонусные баллы за доработку перевода и PR. начало статьиПостоянная ссылка на эту статьюЭто ссылка MarkDown этой статьи на GitHub.
Программа перевода самородковэто сообщество, которое переводит высококачественные технические статьи из ИнтернетаНаггетсДелитесь статьями на английском языке на . Охват контентаAndroid,iOS,внешний интерфейс,задняя часть,блокчейн,продукт,дизайн,искусственный интеллекти другие поля, если вы хотите видеть больше качественных переводов, пожалуйста, продолжайте обращать вниманиеПрограмма перевода самородков,официальный Вейбо,Знай колонку.