Давайте будем честны друг с другом, действительно ли нам нужно еще раз изучить простой, но потрясающий Http-сервер Node? К настоящему времени я уверен, что есть тысячи примеров Hello Node! Или Hello Simple Http Server. В конце концов, сейчас 2107, Node v8 станет LTS раньше, чем мы узнаем об этом, и у нас уже есть хорошо зарекомендовавшие себя фреймворки, такие как Express или Hapi?

Конечно, есть.

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

Это начало серии из трех частей, в которых мы будем делать больше, чем просто Hello World. В одном из следующих постов мы продолжим изучать, почему такие фреймворки, как Express и Hapi, играют такую ​​большую роль в нашей экосистеме. А пока давайте рассмотрим каждую часть этой серии.

Часть 1. Чтобы начать эту серию, мы собираемся создать простой HTTP-сервер, а затем добавить так называемый диспетчер, который поможет перенаправить запрос на соответствующие конечные точки.

Часть 2. Затем мы проведем автоматическое тестирование, рефакторинг созданной базы и увидим зеленые шары.

Часть 3: Наконец, в части 3 мы задействуем некоторые скрытые возможности npm, автоматизируя задачи и, наконец, развернем наши биты в Xervo.

Весь код этой серии можно найти в Github.

Наш первый HTTP-сервер

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

Давайте начнем с простого и создадим наш файл server.js. Это будет нашей основной точкой входа для нашего HTTP-сервера.

touch ./server.js

Далее мы собираемся реализовать встроенный http-модуль Node и использовать фреймворк ведения журналов под названием Winston. Чтобы добавить Уинстона, мы вызовем npm install:

npm install winston --save

Почему Уинстон, почему не просто console.log()? Что ж, Уинстон не блокирует, поэтому давайте с самого начала выработаем себе хорошую привычку. Кроме того, он предлагает большую гибкость в будущем, если и когда мы решим что-то расширить.

Давайте перемотаем вперед и оставим следующее в server.js.

const Http = require('http')
const Winston = require('winston')
const port = process.env.port || 8080
const handleRequest = (request, response) => {
  response.end(`Hello from server ${request.url}!`)
}
const server = Http.createServer(handleRequest)
server.listen(port, () => {
  WINSTON.info(`Http server listening on http://localhost:${port}`)
})

Чтобы запустить, мы можем просто набрать npm start.

[~/s/c/deleteMe]─(130)-> npm start
> [email protected] start /Users/csell5/source/csell/deleteMe
> node server.js
info: Http server listening on http://localhost:8080

Теперь, когда мы перейдем к http: // localhost: 8080, вы увидите, что и клиент, и сервер отображают сообщение. Просмотрите несколько разных путей, и вы увидите, что эти пути возвращаются к вам в обоих местах.

info: Http server listening on http://localhost:8080
info: Route requested: /
info: Route requested: /foo

Что сейчас произошло?

Давайте распакуем это, начав с создания веб-сервера. По сути, мы просто вызываем createServer () и передаем ему функцию, которая, в свою очередь, будет обрабатывать все без исключения HTTP-запросы.

const server = Http.createServer(handleRequest)

Достаточно просто, правда? Наша функция handleRequest сейчас тоже довольно проста. Это функция, которая принимает запрос и ответ, и мы можем действовать. В нашем случае мы собираемся использовать Winston и просто возвращаем URL-адрес вызывающему абоненту и закрываем этот HTTP-запрос.

const handleRequest = (request, response) => {
  Winston.info(`Route requested: ${request.url}`)
  response.end(`Hello from server ${request.url}!`)
}

Когда все это готово, нам просто нужно запустить наш сервер.

server.listen(port, () => {
  Winston.info(`HTTP server listening on http://localhost:${port}`)
})

Диспетчер входит

На данный момент у нас запущен базовый http-сервер. Это не очень полезно, поскольку для нас это эхо. Вдобавок наша единственная функция (handleRequest) - не лучшее решение, когда дело касается управления каждым URI, который мы собираемся обслуживать.

Познакомьтесь с диспетчером. Диспетчер - это своего рода маршрутизатор, который помогает в вызове желаемого обработчика запросов для каждого конкретного URI. Для наших целей мы собираемся использовать простой диспетчер под названием httpdispatcher. Давайте добавим его в наше приложение.

npm install httpdispatcher --save

После того, как это добавлено, нам потребуется создать модуль и его экземпляр в server.js.

const Http_Dispatcher = require('httpdispatcher')
const dispatcher = new Http_Dispatcher()

Затем нам нужно заменить основные функции нашей функции handleRequest вызовом диспетчера. В конце концов, ему нужно сделать некоторую диспетчеризацию.

const handleRequest = (request, response) => {
  Winston.info(`Route requested: ${request.url}`)
  dispatcher.dispatch(request, response)
}

Теперь наш сервер вызывает диспетчера, но диспетчер не знает, куда отправить каждый URI. Чтобы создать эту «карту», ​​мы собираемся создать новый файл routes.js, который будет содержать все наши маршруты. Давайте создадим routes.js:

touch routes.js

Давайте обновим наш server.js, чтобы включить эти маршруты и передать диспетчеру вниз по потоку.

require('./routes')(dispatcher)

Маршрутизация

Наш сервер слушает. Отправляет наш диспетчер. Далее нам нужно несколько функций, находящихся на другом конце этих запросов. Эти функции могут возвращать статический контент, они могут быть apis, они могут следовать за всеми ожидаемыми вами глаголами, GET, POST, PUT, DELETE и так далее.

Каждая функция должна определить глагол и путь, который она будет обслуживать, принимая как объекты запроса, так и объекты ответа.

dispatcher.onGet('/', (req, res) => {
  //DO SOME WORK
  res.end('<h1>I DID SOME WORK</h1>')
})

Давайте перемотаем вперед, создав несколько разных маршрутов.

const Winston = require('winston')
module.exports = (dispatcher) => {
  dispatcher.setStatic('/resources')
  dispatcher.setStaticDirname('static')
  dispatcher.onGet('/', (req, res) => {
    res.writeHead(200, {'Content-Type': 'text/html'})
    res.end('<h1>Index Page</h1>')
  })
  dispatcher.onGet('/api/customer', (req, res) => {
    const customer = {
      firstName: 'Tony',
      lastName: 'Stark'
    }
    res.writeHead(200, {'Content-Type': 'application/json'})
      res.end(JSON.stringify(customer))
    })
  dispatcher.onPost('/api/customer', (req, res) => {
    Winston.info(req.body)
    const response = {
      status: 'success'
    }
    res.writeHead(201, {'Content-Type': 'application/json'})
      res.end(JSON.stringify(response))
    })
  dispatcher.onError = (req, res) => {
    res.writeHead(404)
    res.end('<h1>Resource not found</h1>')
  }
}

Здесь нет ничего слишком сложного, и наша «работа» действительно выполняется на каждом из этих маршрутов. На самом деле мы бы фактически вызывали какой-то контроллер, который выполнял нашу работу, и оставляли бы маршрутизатор только для маршрутизации.

Как и раньше, тестирование может быть таким же простым, как открыть браузер и перейти к любому из маршрутов GET. Такие вещи, как пост, должны использовать такой инструмент, как Почтальон.

Вот и все. Достаточно просто. Теперь у нас есть простой HTTP-сервер, который прослушивает разные маршруты. Конечно, есть отличные фреймворки, такие как Express.js и Hapi.js, построенные на базовых модулях, что делает их еще более крутыми. Мы рассмотрим это в следующей публикации, но пока мы хотели начать с нуля.

Обязательно вернитесь и ознакомьтесь с частью 2, где я немного реорганизую наш сервер, чтобы лучше включить автоматическое тестирование с использованием node-tap.