HTTP для начинающих. Часть 2. Ответы

HTTP для начинающих. Часть 2. Ответы

27 марта 2022 г.

В этой части серии я продемонстрирую создание HTTP-ответов с простого сервера Node.js Express. Вы можете просмотреть весь код в [репозитории Github] (https://github.com/abbeyperini/HTTP101). После этого в [Руководстве по HTTP для начинающих — Часть 3: Запросы] (https://app.hackernoon.com/mobile/DTZlQHZ4LTO42wxh9wN9) мы создадим сообщения-запросы, чтобы получить ответы, которые мы создадим здесь.


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


Введение и оглавление


В этой статье предполагается, что вы знакомы с основами JavaScript, командной строкой и терминами, определенными в части 1.


Я начну с ответов сервера/HTTP, потому что обычно вы строите код HTTP-запроса вокруг формата ответа HTTP, который вы получаете. (Вы неоднократно будете видеть этот шаблон в части 4, когда мы будем использовать API, созданные другими людьми.)


  1. [Простой экспресс-сервер Node.js] (#h-a-simple-nodejs-express-server)

  1. URL-адреса, маршруты и конечные точки

  1. Параметры URL

  1. Коды состояния и обработка ошибок

  1. [Моя база данных поддельной пряжи] (#h-my-fake-yarn-database)

  1. УДАЛИТЬ и почтальон

  1. [Синтаксический анализ тела и промежуточное ПО] (#h-body-parsing-and-middleware)

  1. POST и JSON

  1. КОРС

Простой экспресс-сервер Node.js


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


Чтобы следовать этому руководству, вам необходимо установить Node.js. (Если поначалу у вас ничего не получилось, сделайте перерыв и попробуйте еще раз. Есть причина, по которой профессиональные разработчики жалуются на настройку своей среды разработки.)


Начните с создания основной папки проекта. Если вы еще не придумали название приложения, вы можете использовать имя-заполнитель или [генератор имен приложений] (https://namelix.com/). Внутри создайте папку server.


npm — это менеджер пакетов, устанавливаемый вместе с Node.js для управления и установки пакетов/библиотек кода. Это также имя реестра, из которого менеджер пакетов получает указанные пакеты. Реестр можно использовать бесплатно, и вы можете загружать свои собственные пакеты. Команды, которые вы запускаете с помощью npm, менеджера пакетов, начинаются с npm.


Перейдите в папку вашего сервера в терминале и запустите npm install express. Мы могли бы настроить сервер только с Node.js, но [Express] (https://www.npmjs.com/package/express) — это библиотека веб-фреймворков, удобная для начинающих, которую мы можем запустить в Node.js. Эта команда сгенерирует несколько папок и файлов.


В папке вашего сервера добавьте файл с именем app.js. Откройте app.js в [текстовом редакторе] (https://tndl.medium.com/choosing-a-text-editor-an-important-decision-demystified-c414baf8dba8) и добавьте этот код:


```javascript


константный экспресс = требуется ('экспресс')


постоянное приложение = экспресс()


постоянный порт = 8080


app.listen(порт, () => {


console.log("Сервер работает на порту 8080...")


Этот код создает или создает экземпляр сервера Express, который я назвал «приложение». Теперь любые HTTP-сообщения, отправленные на http:localhost:8080, будут обрабатываться app.


Затем запустите node app.js в своем терминале, чтобы запустить сервер. Если это работает, вы должны увидеть «Сервер работает на порту 8080 ...» в своем терминале. Используйте crtl + C, чтобы убить сервер. Каждый раз, когда вы меняете код сервера, вам придется либо убивать сервер и запускать его снова, либо использовать такой инструмент, как nodemon, который отслеживает вновь сохраненные изменения. в ваших файлах и перезапускает сервер для вас.


Теперь, когда наш сервер запущен, давайте поговорим о настройке маршрутов.


URL-адреса, маршруты и конечные точки


URL означает унифицированный указатель ресурса, особый тип универсального идентификатора ресурса (URI). По сути, почтовый адрес, но для клиента или сервера, размещенного в Интернете. В части 1 мы говорили о том, что URL имеет протокол (http:// или https://). Я упомянул, что порты были необязательными. Если вы обращаетесь к URL-адресу, использующему HTTP или HTTPS, порт не указывается, если используется стандартный порт (80 для HTTP и 443 для HTTPS). Мой сервер работает на порту 8080 на моем локальном компьютере, поэтому его URL-адрес http://localhost:8080. После протокола, имени домена/хоста («localhost» для моего сервера) и, возможно, номера порта вы можете упаковать много информации в URL-адрес.


Возможно, вы знакомы с терминами «маршрут», «маршрутизация» и «маршрутизатор». Подобно тому, как ваш Wi-Fi-маршрутизатор помогает вашим устройствам получать доступ к различным маршрутам в Интернете, сервер имеет маршрутизатор, который определяет, что происходит, когда кто-то вводит этот URL-адрес в браузер. Если вы уже создавали веб-страницы, вы создали маршруты. В http://localhost:3000/index.html index.html можно назвать маршрутом. По мере того, как вы создаете более крупные и сложные внешние интерфейсы, вы можете также создавать и устанавливать маршрутизаторы в своем клиенте.


Давайте настроим наш самый простой маршрут:


```javascript


app.get('/', (требование, разрешение) => {


res.send('Добро пожаловать на сервер пряжи!')


Это сообщает серверу, что если сообщение запроса HTTP GET отправляется на наш основной маршрут (req означает запрос), он должен выполнить функцию стрелки. Функция стрелки отправляет ответное сообщение HTTP («res» означает ответ) со строкой «Добро пожаловать на сервер пряжи!» в организме. Функция стрелки называется обработчиком маршрута.


Снова запустите сервер и на этот раз перейдите по адресу http://localhost:8080 в браузере. Вы должны увидеть «Добро пожаловать на сервер пряжи!» отображается на странице. Введя этот URL-адрес в браузер, вы отправили HTTP-запрос GET на свой сервер по маршруту /. http://localhost:8080 совпадает с http://localhost:8080/. Затем сервер расшифровал ваш запрос GET и отправил ответ. Браузер расшифровал ответ и отобразил его для вас.


Далее мы создадим маршрут, который будет отправлять обратно все данные пряжи, /yarn. Это будет выглядеть так:


```javascript


app.get('/пряжа', (req, res) => {


res.send('Это все данные пряжи!')


При этом, если вы перейдете к http://localhost:8080/yarn при работающем сервере, вы увидите 'Это все данные пряжи!'.


Поскольку мой сервер крошечный, есть много [методов маршрутизации] (https://expressjs.com/en/guide/routing.html), предоставляемых Express, которые я не буду использовать. Метод в этом контексте — это функция, связанная с объектом. В предыдущем коде app.get() — это метод. array.find() — это встроенный метод JavaScript. Вам также не нужно использовать стрелочные функции — вы можете передать именованную функцию в app.get().


Если бы я создавал сложное приложение, скажем, связанное с пряжей и узорами для вязания, я мог бы настроить файлы маршрутизатора вне файла моего основного сервера. Тогда у меня мог бы быть маршрутизатор /yarn, который будет обрабатывать любые маршруты, начинающиеся с /yarn (например, /yarn/purple и /yarn/green), и маршрутизатор /pattern, который будет обрабатывать любые маршруты шаблонов. (например, /pattern/hats и /pattern/шарфы).


С точки зрения клиента, желающего запросить ресурс с сервера, /yarn в http://localhost:8080/yarn будет называться конечной точкой. Например, в документации API DEV вы можете увидеть, как /articles называется "конечной точкой статей", а весь URL-адрес имеет вид https ://dev.to/api/articles. Если вы сделаете HTTP-запрос GET к https://dev.to/api/articles, сервер DEV вернет все сообщения, созданные пользователями в DEV. Поэтому разработчики скажут в разговоре: «Выполнение запроса GET к конечной точке статей вернет все сообщения, созданные пользователями в DEV». Между тем, разработчик, создающий DEV API, вероятно, сказал бы что-то вроде «Маршрут статей будет отправлять обратно все сообщения, созданные пользователями в DEV».


Параметры URL


Если я хочу упростить запрос данных об одной пряже вместо всех данных обо всех пряжах, я могу потребовать, чтобы HTTP-запрос передал идентификатор в параметре URL. Код маршрута сервера будет выглядеть так:


```javascript


app.get('/yarn/:id', (req, res) => {


res.send(Это данные пряжи для ${req.params.id}.)


Точно так же, как мы использовали объект res, переданный нашей функции маршрута, для отправки ответа, мы используем объект req для ссылки на сообщение запроса, отправленное на этот маршрут. С помощью этого кода мы получаем id из URL-адреса сообщения HTTP-запроса и отправляем его обратно в виде строки.


Когда ваш сервер запущен, перейдите по адресу http://localhost:8080/yarn/23 в браузере, и вы должны увидеть «Это данные пряжи для 23».


Коды состояния и обработка ошибок


Если вы не укажете код состояния при отправке ответа, Express использует код Node.js для отправки 200. Если вы хотите явно отправить код состояния (и связанное с ним сообщение), вы можете связать его и функцию .send() следующим образом:


```javascript


app.get('/yarn/:id', (req, res) => {


res.status(200).send(Это данные пряжи для ${req.params.id}.)


Express имеет встроенную обработку ошибок. Если ошибка не обрабатывается написанным вами кодом, Express отправит ответ с кодом состояния 500 и соответствующим сообщением о состоянии. Если бы вы хотели указать, какой код состояния ошибки отправлять в обработчике маршрута, это выглядело бы очень похоже:


```javascript


app.get('/yarn/:id', (req, res) => {


если (isNaN(req.params.id)) {


res.status(404).send("Нет идентификатора, нет пряжи!")


} еще {


res.status(200).send(Это данные пряжи для ${req.params.id}.)


Таким образом, если я перейду к http:localhost:8080/yarn/purple в своем браузере с работающим сервером, я увижу "Нет идентификатора, нет пряжи!" Если я перейду к http:localhost:8080/yarn/43, я увижу «Это данные пряжи для 43». Браузер не отображает для нас код состояния или сообщение о состоянии, но вскоре я представлю инструмент, который это сделает.


Моя база данных поддельной пряжи


Я собираюсь очень быстро смоделировать базу данных, используя массив объектов на моем сервере для хранения данных. Это означает, что любые данные, которые не жестко закодированы, будут исчезать каждый раз, когда я убиваю свой сервер, но настройка базы данных выходит за рамки цели этого руководства.


Наряду с названием пряжи я хочу записать вес пряжи, цвет и количество метров, которые у меня есть, поэтому я добавляю этот массив в начало своего файла:


```javascript


пусть пряжаДБ = [


идентификатор: 1,


название: "Я хочу покрасить носок 75/25",


вес: "Аппликатура",


метров: 299,7


идентификатор: 2,


название: "Шикарный носок Magpie Fibers",


вес: "Аппликатура",


метров: 1097,3


идентификатор: 3,


название: "Rowan Alpaca Color",


вес: "ДК",


метров: 18


идентификатор: 4,


название: "Пряжа Малабриго Риос",


вес: "камвольный",


метров: 192


Во-первых, я изменю свой маршрут, который возвращает информацию обо всей пряже в моей «базе данных».


```javascript


app.get('/пряжа', (req, res) => {


let yarns = yarnDB.map(yarn => Пряжа ${yarn.id} называется ${yarn.name}. Она весит ${yarn.weight} и у вас есть ${yarn.meters} метров.)


res.send(пряжа)


Затем я также изменю свой обработчик маршрута /yarn/:id, чтобы он возвращал информацию о конкретных пряжах в массиве:


```javascript


app.get('/yarn/:id', (req, res) => {


пусть пряжа


for (пусть i=0; i < yarnDB.length; i++) {


если (yarnDB[i].id === parseInt(req.params.id)) {


пряжа = пряжаDB[i]


если (пряжа) {


res.send(Пряжа ${req.params.id} называется ${yarn.name}. Ее вес составляет ${yarn.weight}, а у вас есть ${yarn.meters} метров.)


} еще {


res.status(404).send("Нет пряжи с таким идентификатором")


Переход к http://localhost:8080/yarn/3 в моем браузере с работающим сервером возвращает "Пряжа 3 называется Rowan Alpaca Color. Это вес DK, и у вас 18 метров". Переход к http://localhost:8080/yarn/5 возвращает «Нет пряжи с таким идентификатором». Переход к http://localhost:8080/yarn превратит массив строк информации о пряже для каждой пряжи в «базе данных».


DELETE и почтальон


Возможно, вы заметили, что пока я строил маршруты только с помощью метода GET! У нас нет возможности добавить или удалить пряжу! Это потому, что мы можем генерировать запросы GET только с использованием URL-адреса в браузере. Чтобы использовать маршрут POST или DELETE, вам понадобится клиент или инструмент, например Postman. Далее мы создадим наш клиент, но сначала давайте рассмотрим, как использовать Postman для проверки моего маршрута DELETE:


```javascript


app.delete('/yarn/delete/:id', (req, res) => {


пусть индекс


for (пусть i=0; i < yarnDB.length; i++) {


если (yarnDB[i].id === parseInt(req.params.id)) {


индекс = я


если (индекс === 0 || индекс) {


yarnDB.splice(индекс, 1)


console.log(yarnDB)


res.send(Идентификатор пряжи ${req.params.id} удален!)


} еще {


res.status(404).send("Нет пряжи с таким идентификатором")


После того, как вы установили и открыли Postman, вам нужно будет открыть новую вкладку запроса и ввести информацию, необходимую для создания запроса. Для маршрута DELETE все, что вам нужно сделать, это выбрать метод DELETE из раскрывающегося списка и ввести URL-адрес. Если я введу http://localhost:8080/yarn/delete/3 и нажму кнопку отправки, я увижу "Идентификатор пряжи 3 удален!" в теле ответа в Postman. Когда массив регистрируется в терминале сервера, теперь я вижу только нити 1, 2 и 4 в моем массиве yarnDB.


скриншот Postman со всей описанной ранее информацией


Раздел ответа в Postman также показывает нам некоторую информацию об ответном сообщении HTTP, которую мы не могли видеть в браузере. Код состояния и сообщение отображаются прямо в теле ответа. В разделах запроса и ответа есть вкладки, такие как заголовки, где вы можете увидеть все заголовки сообщения, а также другую информацию и инструменты. Мы обязательно углубимся во вкладку заголовков в части 4, но ознакомьтесь с [документами Postman] (https://learning.postman.com/docs/getting-started/introduction/), чтобы увидеть все инструменты, которые она может предоставить.


Анализ тела и промежуточное ПО


Мне также нужно добавить синтаксический анализатор тела для декодирования данных тела моего запроса во что-то, с чем я могу работать в JavaScript на своем сервере. Вот почему и запросы, и ответы используют заголовки Content-Type. Преобразование тела HTTP-сообщения во что-то полезное значительно проще, если мы знаем, что такое медиа/MIME-тип тела.


Я добавляю промежуточное программное обеспечение в свой файл сервера, чтобы мой сервер Express автоматически анализировал JSON в теле запросов, которые он получает:


```javascript


app.use(express.json())


В этом контексте промежуточное ПО относится к функциям вне обработчика маршрута, которые выполняются, когда HTTP-сообщение запускает маршрут на сервере. Используя app.use(), я говорю приложению запустить встроенный анализатор тела JSON, который Express предоставляет до того, как будет выполнен каждый обработчик маршрута, который получает тело запроса.


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


Если вы хотите узнать больше о промежуточном программном обеспечении, включая обработчики ошибок, и узнать больше о том, что вы можете делать с Express, ознакомьтесь с руководством по LogRocket.


«Но подождите, — можете подумать вы, — мы все это время отправляли данные, не указывая заголовок Content Type и не форматируя тело!» Метод res.send() Express автоматически устанавливает заголовки и форматирует тело на основе типа переданного ему параметра. Использование res.json() вместо res.send() установит заголовок Content Type в «application/json» и отформатирует все, что передается как JSON. Вы также можете использовать res.type(), чтобы установить заголовок самостоятельно. Это основная причина, по которой я выбрал Express для этого руководства — форматирование и анализ HTTP-сообщений будет становиться все более ручным по мере продвижения.


POST и JSON


Далее, мой маршрут POST:


```javascript


app.post('/пряжа/создать', (req, res) => {


пусть пряжа = req.body.yarn


if (пряжа.id && пряжа.название && пряжа.вес && пряжа.метры) {


yarnDB.push(пряжа)


console.log(yarnDB)


res.send("Пряжа добавлена ​​в базу данных!")


} еще {


res.status(400).statusMessage("Объект Yarn неправильно отформатирован")


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


Чтобы протестировать этот маршрут, мне нужно создать допустимую строку JSON для передачи в теле моего HTTP-запроса. На практике написание JSON сводится к созданию объекта или массива JavaScript, но ничто не может быть переменной. Например, это допустимый объект JavaScript:


```javascript


пусть человек = {


имя: "Джордж"


В JavaScript я мог получить доступ к person.name и получить «Джордж». Чтобы быть действительным JSON, имена объектов и полей должны быть строками, и все должно содержаться в объекте или массиве:


```json


{ "человек":


"имя": "Джордж"


Как только мой сервер использует промежуточное программное обеспечение express.json(), эта строка JSON будет преобразована обратно в объект JavaScript, и мы сможем получить доступ к person.name, чтобы снова получить "George".


Чтобы получить доступ к req.body.yarn в моем обработчике маршрута, мой JSON будет выглядеть так:


```json


"пряжа": {


"идентификатор": 5,


"name": "Wonderland Yarns & Frabjous Fibers Мэри Энн",


«вес»: «Легкая аппликатура»,


"метров": 539,5


"Держать на секунду!" вы можете сказать: «5 и 539,5 — это не строки!» Это потому, что JSON позволяет использовать несколько типов данных. Чтобы привыкнуть к переводу данных в действительный JSON, попробуйте использовать анализатор JSON, например [форматтер JSON] (https://jsonformatter.org/json-parser). У них даже есть пример со всеми возможными типами данных можно играть с. Вскоре мы рассмотрим методы, доступные в JavaScript для преобразования объектов между JSON и JavaScript, но способность распознавать действительный JSON поможет, когда вы попытаетесь устранить неполадки в будущем.


Чтобы использовать маршрут POST с Postman, нам нужно создать тело. После выбора POST из раскрывающегося списка и ввода «http://localhost:8080/yarn/create», я перехожу к вкладкам запроса и выбираю вкладку body. Затем я выбираю переключатель с надписью raw. Затем в раскрывающемся списке, который появляется справа от переключателей, я выбираю JSON и ввожу свой объект JSON в поле ниже. Когда я нажимаю кнопку отправки, я вижу "Пряжа добавлена ​​в базу данных!" в Postman, и массив, зарегистрированный на моем сервере, подтверждает, что пряжа № 5 была добавлена ​​​​к моему массиву.


скриншот Postman со всей описанной ранее информацией


КОРС


Почтальон игнорирует CORS. Несмотря на то, что наш базовый сервер настроен на отправку HTTP-ответов после получения HTTP-запросов, нам все равно нужно включить CORS, прежде чем мы перейдем к созданию HTTP-запросов в клиенте. Для этого я устанавливаю пакет cors, запустив npm install cors в своем терминале. . В верхней части моего файла app.js я импортирую пакет:


```javascript


const корс = требуется ('корс')


Затем я добавляю промежуточное ПО CORS на каждый маршрут, как и парсер тела:


```javascript


app.use(cors())


Это эквивалентно добавлению этого заголовка к каждому предварительному сообщению и ответному сообщению, отправляемому этим сервером:


```javascript


Доступ-Контроль-Разрешить-Происхождение: *


* — это подстановочный знак. Это говорит браузерам разрешить любой запрос из любого источника. Это минимально возможная безопасность. Поскольку моя цель — попрактиковаться в HTTP-запросах на моей локальной машине, я выберу самый простой вариант. Если бы это был сервер, который я собирался развернуть, я бы использовал параметры конфигурации, чтобы ограничить источники и методы, которые могут получить доступ к моему серверу.


Заключение


Если вы остались в замешательстве или у вас есть какие-либо вопросы по любой из тем, которые я затронул в этой части серии, пожалуйста, не стесняйтесь оставлять комментарии! Я постарался дать ссылки на ресурсы для тем, когда они появились, но если есть темы, которые вы хотели бы видеть в разделе «дополнительные ресурсы», как в [часть 1] (https://app.hackernoon.com/ mobile/zMMaTOpLCUCCQs4ntqPT), дайте мне знать.


Я хотел начать с сервера не только потому, что вы обычно создаете сообщения-запросы и пишете клиентский код на основе формата ответных сообщений, которые хотите использовать, но и потому, что гораздо проще понять, что происходит, когда вы знаете, что происходит. ожидать ответа от сервера. Теперь мы готовы создать клиент для запроса этих ответов в [Руководство по HTTP для начинающих — Часть 3: Запросы] (https://app.hackernoon.com/mobile/DTZlQHZ4LTO42wxh9wN9)!


Вскоре:


  • Руководство для начинающих по HTTP — Часть 4: API

  • Впервые опубликовано [здесь] (https://dev.to/abbeyperini/a-beginners-guide-to-http-part-2-an-app-and-a-server-211p)*


Оригинал
PREVIOUS ARTICLE
NEXT ARTICLE