Главная страница
Навигация по странице:

  • Установление TCP-соединения

  • О создании простого сервера средствами Node.js

  • Node.js

  • Выполнение GET-запросов

  • Выполнение POST-запроса

  • Выполнение PUT-запросов и DELETE-запросов

  • Выполнение HTTP-запросов в Node.js с использованием библиотеки Axios

  • Использование параметров в GET-запросах

  • Использование протокола WebSocket в Node.js

  • Защищённая версия протокола WebSocket

  • Создание WebSocket-соединения

  • Отправка данных на сервер

  • Получение данных с сервера

  • Реализация WebSocket-сервера в среде Node.js

  • Протоколы http и WebSocket Часть 9 работа с файловой системой Часть 10 стандартные модули, потоки, базы данных, node env


    Скачать 1.79 Mb.
    НазваниеПротоколы http и WebSocket Часть 9 работа с файловой системой Часть 10 стандартные модули, потоки, базы данных, node env
    Дата03.03.2019
    Размер1.79 Mb.
    Формат файлаpdf
    Имя файлаee86eb4f-db9f-48d3-8094-c76e14414678.pdf
    ТипПротокол
    #69437
    страница6 из 9
    1   2   3   4   5   6   7   8   9
    Часть 8: протоколы HTTP и WebSocket
    Что происходит при выполнении HTTP-запросов?
    Поговорим о том, как браузеры выполняют запросы к серверам с использованием протокола HTTP/1.1.
    Если вы когда-нибудь проходили собеседование в IT-сфере, то вас могли спросить о том, что происходит, когда вы вводите нечто в адресную строку браузера и нажимаете Enter. Пожалуй, это один из самых популярных вопросов, который встречается на подобных собеседованиях. Тот, кто задаёт подобные вопросы, хочет узнать, можете ли вы объяснить некоторые довольно-таки простые концепции и выяснить, понимаете ли вы принципы работы интернета.
    Этот вопрос затрагивает множество технологий, понимать общие принципы которых — значит понимать, как устроена одна из самых сложных систем из когда-либо построенных человечеством, которая охватывает весь мир.
    Протокол HTTP
    Современные браузеры способны отличать настоящие URL-адреса, вводимые в их адресную строку, от поисковых запросов, для обработки которых обычно используется заданная по умолчанию поисковая система. Мы будем говорить именно об URL-адресах. Если вы введёте в строку браузера адрес сайта, вроде ​flaviocopes.com​, браузер преобразует этот адрес к виду ​http://flaviocopes.com​, исходя из предположения о том, что для обмена данными с указанным ресурсом будет использоваться протокол HTTP. Обратите внимание на то, что в Windows то, о чём мы будем тут говорить, может выглядеть немного иначе, чем в macOS и Linux.
    Фаза DNS-поиска
    Итак, браузер, начиная работу по загрузке данных с запрошенного пользователям адреса, выполняет операцию DNS-поиска (DNS Lookup) для того, чтобы выяснить IP-адрес соответствующего сервера.
    Символьные имена ресурсов, вводимые в адресную строку, удобны для людей, но устройство интернета подразумевает возможность обмена данными между компьютерами с использованием
    IP-адресов, которые представляют собой наборы чисел наподобие 222.324.3.1 (для протокола IPv4).
    Сначала, выясняя IP-адрес сервера, браузер заглядывает в локальный DNS-кэш для того, чтобы узнать, не выполнялась ли недавно подобная процедура. В браузере Chrome, например, есть удобный способ посмотреть DNS-кэш, введя в адресной строке следующий адрес: chrome://net-internals/#dns​.
    Если в кэше ничего найти не удаётся, браузер использует системный вызов POSIX ​gethostbyname для того, чтобы узнать IP-адрес сервера.

    Функция gethostbyname
    Функция ​gethostbyname​ сначала проверяет файл ​hosts​, который, в macOS или Linux, можно найти по адресу ​/etc/hosts​, для того, чтобы узнать, можно ли, выясняя адрес сервера, обойтись локальными сведениями.
    Если локальными средствами разрешить запрос на выяснение IP-адреса сервера не удаётся, система выполняет запрос к DNS-серверу. Адреса таких серверов хранятся в настройках системы.
    Вот пара популярных DNS-серверов:
    ● 8.8.8.8: DNS-сервер Google.
    ● 1.1.1.1: DNS-сервер CloudFlare.
    Большинство людей используют DNS-сервера, предоставляемые их провайдерами. Браузер выполняет
    DNS-запросы с использованием протокола UDP.
    TCP и UDP — это два базовых протокола, применяемых в компьютерных сетях. Они расположены на одном концептуальном уровне, но TCP — это протокол, ориентированный на соединениях, а для обмена UDP-сообщениями, обработка которых создаёт небольшую дополнительную нагрузку на системы, процедура установления соединения не требуется. О том, как именно происходит обмен данными по UDP, мы говорить не будем.
    IP-адрес, соответствующий интересующему нас доменному имени, может иметься в кэше
    DNS-сервера. Если это не так — он обратиться к корневому DNS-серверу. Система корневых
    DNS-серверов состоит из 13 серверов, от которых зависит работа всего интернета.
    Надо отметить, что корневому DNS-серверу неизвестны соответствия между всеми существующими в мире доменными именами и IP-адресами. Но подобным серверам известны адреса DNS-серверов верхнего уровня для таких доменов, как .com, .it, .pizza, и так далее.
    Получив запрос, корневой DNS-сервер перенаправляет его к DNS-серверу домена верхнего уровня, к так называемому TLD-серверу (от Top-Level Domain).
    Предположим, браузер ищет IP-адрес для сервера ​flaviocopes.com​. Обратившись к корневому
    DNS-серверу, браузер получит у него адрес TLD-сервера для зоны .com. Теперь этот адрес будет сохранён в кэше, в результате, если будет нужно узнать IP-адрес ещё какого-нибудь URL из зоны .com, к корневому DNS-серверу не придётся обращаться снова.
    У TLD-серверов есть IP-адреса серверов имён (Name Server, NS), средствами которых и можно узнать
    IP-адрес по имеющемуся у нас URL. Откуда NS-сервера берут эти сведения? Дело в том, что если вы покупаете домен, доменный регистратор отправляет данные о нём серверам имён. Похожая процедура выполняется и, например, при смене хостинга.
    Сервера, о которых идёт речь, обычно принадлежат хостинг-провайдерам. Как правило, для защиты от сбоев, создаются по несколько таких серверов. Например, у них могут быть такие адреса:
    ● ns1.dreamhost.com
    ● ns2.dreamhost.com
    ● ns3.dreamhost.com
    Для выяснения IP-адреса по URL, в итоге, обращаются к таким серверам. Именно они хранят актуальные данные об IP-адресах.
    Теперь, после того, как нам удалось выяснить IP-адрес, стоящий за введённым в адресную строку браузера URL, мы переходим к следующему шагу нашей работы.

    Установление TCP-соединения
    Узнав IP-адрес сервера, клиент может инициировать процедуру TCP-подключения к нему. В процессе установления TCP-соединения клиент и сервер передают друг другу некоторые служебные данные, после чего они смогут обмениваться информацией. Это означает, что, после установления соединения, клиент сможет отправить серверу запрос.
    Отправка запроса
    Запрос представляет собой структурированный в соответствии с правилами используемого протокола фрагмент текста. Он состоит из трёх частей:
    ● Строка запроса.
    ● Заголовок запроса.
    ● Тело запроса.
    Строка запроса
    Строка запроса представляет собой одну текстовую строку, в которой содержатся следующие сведения:
    ● Метод HTTP.
    ● Адрес ресурса.
    ● Версия протокола.
    Выглядеть она, например, может так:
    GET / HTTP/1.1
    Заголовок запроса
    Заголовок запроса представлен набором пар вида ​поле: значение​. Существуют 2 обязательных поля заголовка, одно из которых — ​Host​, а второе — ​Connection​. Остальные поля необязательны.
    Заголовок может выглядеть так:
    Host: flaviocopes.com
    Connection: close
    Поле ​Host​ указывает на доменное имя, которое интересует браузер. Поле ​Connection​, установленное в значение ​close​, означает, что соединение между клиентом и сервером не нужно держать открытым.
    Среди других часто используемых заголовков запросов можно отметить следующие:
    ● Origin
    ● Accept
    ● Accept-Encoding
    ● Cookie
    ● Cache-Control
    ● Dnt
    На самом деле, их существует гораздо больше.
    Заголовок запроса завершается пустой строкой.
    Тело запроса
    Тело запроса необязательно, в GET-запросах оно не используется. Тело запроса используется в
    POST-запросах, а также в других запросах. Оно может содержать, например, данные в формате JSON.

    Так как сейчас речь идёт о GET-запросе, тело запроса будет пустым, с ним мы работать не будем.
    Ответ
    После того, как сервер получает отправленный клиентом запрос, он его обрабатывает и отправляем клиенту ответ.
    Ответ начинается с кода состояния и с соответствующего сообщения. Если запрос выполнен успешно, то начало ответа будет выглядеть так:
    200 OK
    Если что-то пошло не так, тут могут быть и другие коды. Например, следующие:
    ● 404 Not Found
    ● 403 Forbidden
    ● 301 Moved Permanently
    ● 500 Internal Server Error
    ● 304 Not Modified
    ● 401 Unauthorized
    Далее в ответе содержится список HTTP-заголовков и тело ответа (которое, так как запрос выполняет браузер, будет представлять собой HTML-код).
    Разбор HTML-кода
    После того, как браузер получает ответ сервера, в теле которого содержится HTML-код, он начинает его разбирать, повторяя вышеописанный процесс для каждого ресурса, который нужен для формирования страницы. К таким ресурсам относятся, например, следующие:
    ● CSS-файлы.
    ● Изображения.
    ● Значок веб-страницы (favicon).
    ● JavaScript-файлы.
    То, как именно браузер выводит страницу, к нашему разговору не относится. Главное, что нас тут интересует, заключается в том, что вышеописанный процесс запроса и получения данных используется не только для HTML-кода, но и для любых других объектов, передаваемых с сервера в браузер с использованием протокола HTTP.
    О создании простого сервера средствами Node.js
    Теперь, после того, как мы разобрали процесс взаимодействия браузера и сервера, вы можете по-новому взглянуть на раздел ​Первое
    Первое Node.js-приложение​
    приложение​
    из ​
    первой части​
    этой серии материалов, в котором мы описывали код простого сервера.
    Выполнение HTTP-запросов средствами Node.js
    Для выполнения HTTP-запросов средствами Node.js используется соответствующий ​
    модуль​
    . В приведённых ниже примерах применяется модуль ​
    https​
    . Дело в том, что в современных условиях всегда, когда это возможно, нужно применять именно протокол HTTPS.
    Выполнение GET-запросов
    Вот пример выполнения GET-запроса средствами Node.js: const https = require('https') const options = {
    hostname: 'flaviocopes.com', port: 443, path: '/todos', method: 'GET'
    } const req = https.request(options, (res) => { console.log(`statusCode: ${res.statusCode}`) res.on('data', (d) => { process.stdout.write(d)
    })
    }) req.on('error', (error) => { console.error(error)
    }) req.end()
    Выполнение POST-запроса
    Вот как выполнить POST-запрос из Node.js: const https = require('https') const data = JSON.stringify({ todo: 'Buy the milk'
    }) const options = { hostname: 'flaviocopes.com', port: 443, path: '/todos', method: 'POST', headers: {
    'Content-Type': 'application/json',
    'Content-Length': data.length
    }
    }
    const req = https.request(options, (res) => { console.log(`statusCode: ${res.statusCode}`) res.on('data', (d) => { process.stdout.write(d)
    })
    }) req.on('error', (error) => { console.error(error)
    }) req.write(data) req.end()
    Выполнение PUT-запросов и DELETE-запросов
    Выполнение таких запросов выглядит так же, как и выполнение POST-запросов. Главное отличие, помимо смыслового наполнения таких операций, заключается в значении свойства ​method​ объекта options​.
    Выполнение HTTP-запросов в Node.js с использованием библиотеки Axios
    Axios — это весьма популярная JavaScript-библиотека, работающая и в браузере (сюда входят все современные браузеры и IE, начиная с IE8), и в среде Node.js, которую можно использовать для выполнения HTTP-запросов.
    Эта библиотека основана на промисах, она обладает некоторыми преимуществами перед стандартными механизмами, в частности, перед API Fetch. Среди её преимуществ можно отметить следующие:
    ● Поддержка старых браузеров (для использования Fetch нужен полифилл).
    ● Возможность прерывания запросов.
    ● Поддержка установки тайм-аутов для запросов.
    ● Встроенная защита от CSRF-атак.
    ● Поддержка выгрузки данных с предоставлением сведений о ходе этого процесса.
    ● Поддержка преобразования JSON-данных.
    ● Работа в Node.js
    Установка
    Для установки Axios можно воспользоваться npm: npm install axios
    Того же эффекта можно достичь и при работе с yarn: yarn add axios
    Подключить библиотеку к странице можно с помощью ​unpkg.com​:


    API Axios
    Выполнить HTTP-запрос можно, воспользовавшись объектом ​axios​: axios({ url: 'https://dog.ceo/api/breeds/list/all', method: 'get', data: { foo: 'bar'
    }
    })
    Но обычно удобнее пользоваться специальными методами:
    ● axios.get()
    ● axios.post()
    Это похоже на то, как в jQuery, вместо ​$.ajax()​ пользуются ​$.get()​ и ​$.post()​.
    Axios предлагает отдельные методы и для выполнения других видов HTTP-запросов, которые не так популярны, как GET и POST, но всё-таки используются:
    ● axios.delete()
    ● axios.put()
    ● axios.patch()
    ● axios.options()
    В библиотеке имеется метод для выполнения запроса, предназначенного для получения лишь
    HTTP-заголовков, без тела ответа:
    ● axios.head()
    Запросы GET
    Axios удобно использовать с применением современного синтаксиса async/await. В следующем примере кода, рассчитанном на Node.js, библиотека используется для загрузки списка пород собак из
    API Dog​
    . Здесь применяется метод ​axios.get()​ и осуществляется подсчёт пород: const axios = require('axios') const getBreeds = async () => { try { return await axios.get('https://dog.ceo/api/breeds/list/all')
    } catch (error) { console.error(error)
    }
    }
    const countBreeds = async () => { const breeds = await getBreeds() if (breeds.data.message) { console.log(`Got ${Object.entries(breeds.data.message).length} breeds`)
    }
    } countBreeds()
    То же самое можно переписать и без использования async/await, применив промисы: const axios = require('axios') const getBreeds = () => { try { return axios.get('https://dog.ceo/api/breeds/list/all')
    } catch (error) { console.error(error)
    }
    } const countBreeds = async () => { const breeds = getBreeds()
    .then(response => { if (response.data.message) { console.log(
    `Got ${Object.entries(response.data.message).length} breeds`
    )
    }
    })
    .catch(error => { console.log(error)
    })
    } countBreeds()

    Использование параметров в GET-запросах
    GET-запрос может содержать параметры, которые в URL выглядят так: https://site.com/?foo=bar
    При использовании Axios запрос подобного рода можно выполнить так: axios.get('https://site.com/?foo=bar')
    Того же эффекта можно достичь, настроив свойство ​params​ в объекте с параметрами: axios.get('https://site.com/', { params: { foo: 'bar'
    }
    })
    Запросы POST
    Выполнение POST-запросов очень похоже на выполнение GET-запросов, но тут, вместо метода axios.get()​, используется метод ​axios.post()​: axios.post('https://site.com/')
    В качестве второго аргумента метод ​post​ принимает объект с параметрами запроса: axios.post('https://site.com/', { foo: 'bar'
    })
    Использование протокола WebSocket в Node.js
    WebSocket представляет собой альтернативу HTTP, его можно применять для организации обмена данными в веб-приложениях. Этот протокол позволяет создавать долгоживущие двунаправленные каналы связи между клиентом и сервером. После установления соединения канал связи остаётся открытым, что даёт в распоряжение приложения очень быстрое соединение, характеризующееся низкими задержками и небольшой дополнительной нагрузкой на систему.
    Протокол WebSocket поддерживают все современные браузеры.
    Отличия от HTTP
    HTTP и WebSocket — это очень разные протоколы, в которых используются различные подходы к обмену данными. HTTP основан на модели «запрос — ответ»: сервер отправляет клиенту некие данные после того, как они будут запрошены. В случае с WebSocket всё устроено иначе. А именно:
    ● Сервер может отправлять сообщения клиенту по своей инициативе, не дожидаясь поступления запроса от клиента.
    ● Клиент и сервер могут обмениваться данными одновременно.
    ● При передаче сообщения используется крайне малый объём служебных данных. Это, в частности, ведёт к низким задержкам при передаче данных.
    Протокол WebSocket очень хорошо подходит для организации связи в режиме реального времени по каналам, которые долго остаются открытыми. HTTP, в свою очередь, отлично подходит для организации эпизодических сеансов связи, инициируемых клиентом. В то же время надо отметить, что,
    с точки зрения программирования, реализовать обмен данными по протоколу HTTP гораздо проще, чем по протоколу WebSocket.
    Защищённая версия протокола WebSocket
    Существует небезопасная версия протокола WebSocket (URI-схема ​ws://​), которая напоминает, в плане защищённости, протокол ​http://​. Использования ​ws://​ следует избегать, отдавая предпочтение защищённой версии протокола — ​wss://​.
    Создание WebSocket-соединения
    Для создания WebSocket-соединения нужно воспользоваться соответствующим ​
    конструктором​
    : const url = 'wss://myserver.com/something' const connection = new WebSocket(url)
    После успешного установления соединения вызывается событие ​open​. Организовать прослушивание этого события можно, назначив функцию обратного вызова свойству ​onopen​ объекта ​connection​: connection.onopen = () => {
    //...
    }
    Для обработки ошибок используется обработчик события ​onerror​: connection.onerror = error => { console.log(`WebSocket error: ${error}`)
    }
    Отправка данных на сервер
    После открытия WebSocket-соединения с сервером ему можно отправлять данные. Сделать это можно, например в коллбэке ​onopen​: connection.onopen = () => { connection.send('hey')
    }
    Получение данных с сервера
    Для получения с сервера данных, отправленных с использованием протокола WebSocket, можно назначить коллбэк ​onmessage​, который будет вызван при получении события ​message​: connection.onmessage = e => { console.log(e.data)
    }
    Реализация WebSocket-сервера в среде Node.js
    Для того чтобы реализовать WebSocket-сервер в среде Node.js, можно воспользоваться популярной библиотекой ​
    ws​
    . Мы применим её для разработки сервера, но она подходит и для создания клиентов, и для организации взаимодействия между двумя серверами.
    Установим эту библиотеку, предварительно инициализировав проект:
    yarn init yarn add ws
    Код WebSocket-сервера, который нам надо написать, довольно-таки компактен: constWebSocket = require('ws') const wss = newWebSocket.Server({ port: 8080 }) wss.on('connection', ws => { ws.on('message', message => { console.log(`Received message => ${message}`)
    }) ws.send('ho!')
    })
    Здесь мы создаём новый сервер, который прослушивает стандартный для протокола WebSocket порт
    8080 и описываем коллбэк, который, когда будет установлено соединение, отправляет клиенту сообщение ​ho!​ и выводит в консоль сообщение, полученное от клиента.
    Вот​
    рабочий пример WebSocket-сервера, а ​
    вот​
    — клиент, который может с ним взаимодействовать.
    1   2   3   4   5   6   7   8   9


    написать администратору сайта