Hello Node.js

Создание сервера¶

Для работы с сервером и протоколом http в Node.js используется модуль http.

Чтобы создать сервер, следует вызвать метод http.createServer() :

const http = require('http'); http.createServer().listen(3000); 

Метод createServer() возвращает объект http.Server . Но чтобы сервер мог прослушивать и обрабатывать входящие подключения, у объекта сервера необходимо вызвать метод listen() , в который в качестве параметра передается номер порта, по которому запускается сервер.

Для обработки подключений в метод createServer можно передать специальную функцию:

const http = require('http'); http.createServer(function (request, response)  response.end('Hello world!'); >).listen(3000); 

Эта функция принимает два параметра:

Request¶

Параметр request позволяет получить информацию о запросе и представляет объект http.IncomingMessage . Отметим некоторые основные свойства этого объекта:

  • headers : возвращает заголовки запроса
  • method : тип запроса ( GET , POST , DELETE , PUT )
  • url : представляет запрошенный адрес

Например, определим следующий файл app.js :

 1 2 3 4 5 6 7 8 9 10 11 12 13
var http = require('http'); http.createServer(function (request, response)  console.log('Url: ' + request.url); console.log('Тип запроса: ' + request.method); console.log( 'User-Agent: ' + request.headers['user-agent'] ); console.log('Все заголовки'); console.log(request.headers); response.end(); >).listen(3000); 

Запустим его и обратимся в браузере по адресу http://localhost:3000/index.html:

3.3.png

Response¶

Параметр response управляет отправкой ответа и представляет объект http.ServerResponse . Среди его функциональности можно выделить следующие методы:

  • statusCode : устанавливает статусный код ответа
  • statusMessage : устанавливает сообщение, отправляемое вместе со статусным кодом
  • setHeader(name, value) : добавляет в ответ один заголовок
  • write : пишет в поток ответа некоторое содержимое
  • writeHead : добавляет в ответ статусный код и набор заголовков
  • end : сигнализирует серверу, что заголовки и тело ответа установлены, в итоге ответ отсылается клиента. Данный метод должен вызываться в каждом запросе.

Например, изменим файл app.js следующим образом:

const http = require('http'); http.createServer(function (request, response)  response.setHeader('UserId', 12); response.setHeader( 'Content-Type', 'text/html; charset=utf-8;' ); response.write('

hello world

'
); response.end(); >).listen(3000);

Запустим файл и обратимся в браузере к приложению:

3.4.png

Если предстоит отправить довольно большой ответ, то мы можем несколько раз вызвать метод write() , последовательно оправляя в исходящий поток каждый кусочек информации. Например, отправим код более менее полноценной веб-страницы:

 1 2 3 4 5 6 7 8 9 10 11 12 13 14
const http = require('http'); http.createServer(function (request, response)  response.setHeader('Content-Type', 'text/html'); response.write(''); response.write(''); response.write(''); response.write(''); response.write(''); response.write(''); response.write('

Привет миг

'
); response.write(''); response.end(); >).listen(3000);

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

По умолчанию Node.js не имеет встроенной системы маршрутизации. Обычно она реализуется с помощью специальных фреймворках типа Express, о котором речь пойдет в следующей главе. Однако если необходимо разграничить простейшую обработку пары-тройки маршрутов, то вполне можно использовать для этого свойство url объекта Request . Например:

 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
const http = require('http'); http.createServer(function (request, response)  response.setHeader( 'Content-Type', 'text/html; charset=utf-8;' ); if (request.url === '/home' || request.url === '/')  response.write('

Home

'
); > else if (request.url == '/about') response.write('

About

'
); > else if (request.url == '/contact') response.write('

Contacts

'
); > else response.write('

Not found

'
); > response.end(); >).listen(3000);

В данном случае обрабатываются три маршрута. Если идет обрашение к корню сайта или по адресу localhost:3000/home , то пользователю выводится строка Home . Ели обращение идет по адресу localhost:3000/about , то пользователю в браузере отображается строка About и так далее. Если запрошенный адрес не соответствует ни одному маршруту, то выводится заговлок Not Found .

3.7.png

Однако опять же отмечу, что рамках специальных фреймворков, которые работают поверх Node.js, например, Express, есть более удобные способы для обработки маршрутов, которые нередко и используются.

Переадресация¶

Переадресация предполагает отправку статусного кода 301 (постоянная переадресация) или 302 (временная переадресация) и заголовка Location , который указывает на новый адрес. Например, выполним переадресацию с адреса localhost:3000/ на адрес localhost:3000/newpage

 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
const http = require('http'); http.createServer(function (request, response)  response.setHeader( 'Content-Type', 'text/html; charset=utf-8;' ); if (request.url === '/')  response.statusCode = 302; // временная переадресация // на адрес localhost:3000/newpage response.setHeader('Location', '/newpage'); > else if (request.url == '/newpage')  response.write('New address'); > else  response.write('Not Found'); response.statusCode = 404; // адрес не найден > response.end(); >).listen(3000); 

Источник

Свой веб-сервер на NodeJS, и ни единого фреймворка. Часть 1

Для многих людей JavaScript ассоциативно связан с обилием разнообразных фреймворков и библиотек. Разумеется, инструменты, которые помогают нам каждый день — это хорошо, но, мне кажется, нужно искать некий баланс между использованием инструментов и прокрастинацией, а также знать, как работают вещи, которыми ты пользуешься. Поэтому, когда я только сел разбираться с NodeJS, мне было особенно интересно написать полноценный веб-сервер, которым я мог бы пользоваться сам.

Новичку в NodeJS действительно может быть нелегко. JS — один из языков, в котором часто не существует единственного правильного решения конкретной задачи, а добавленные в ноду модули для работы с файловой системой, http сервером и прочими вещами, характерными для работы на сервере, затрудняют переход даже тем, кто пишет хороший код для браузеров. Тем не менее, я надеюсь, что вы знаете основы этого языка и его работы в серверном окружении, если нет, советую посмотреть замечательный скринкаст, который поможет разобраться в основах. И последнее — я не претендую на какой-то исключительно правильный код и буду рад услышать критику — мы все учимся, и это отличный способ получать знания.

Начнём с файловой структуры

Исходная папка nodejs хранится на сервере по пути /var/www/html/. В ней и будет наш веб-сервер. Дальше всё просто: создаём в ней директорию routing, в которой будет лежать наш скрипт index.js, а также 4 папки — dynamic, static, nopage и main — для динамически генерируемых страниц, статики, страницы 404 и главной страницы. Выглядит всё это так:

Создаём наш сервер

Отлично, с файловой структурой более-менее определились. Теперь создаём в исходной папке файл server.js со следующим содержимым:

// server.js // Для начала установим зависимости. const http = require('http'); const routing = require('./routing'); let server = new http.Server(function(req, res) < // API сервера будет принимать только POST-запросы и только JSON, так что записываем // всю нашу полученную информацию в переменную jsonString var jsonString = ''; res.setHeader('Content-Type', 'application/json'); req.on('data', (data) =>< // Пришла информация - записали. jsonString += data; >); req.on('end', () => ); >); server.listen(8000, 'localhost'); 

Здорово! Теперь наш сервер будет принимать запросы, записывать JSON-данные, если они есть, но пока что будет вылетать с ошибкой, потому что у нас нет функции define в /routing/index.js. Время это исправить.

// /routing/index.js const define = function(req, res, postData) < res.end('Hello, Habrahabr!'); >exports.define = define; 

Заходим туда, где он слушает запросы. Если вы не меняли код, это будет localhost:8000. Ура. Ответ есть.

image

Замечательно. Только это не совсем то, что нам нужно от сервера, правда?

Ловим запросы к нашим API

Да, мы получили ответ, но пока что не слишком близки к конечной цели. Самое время писать логику для нашего роутера.

// /routing/index.js // Для начала установим зависимости. const url = require('url'); const fs = require('fs'); const define = function(req, res, postData) < // Теперь получаем наш адрес. Если мы переходим на localhost:3000/test, то path будет '/test' const urlParsed = url.parse(req.url, true); let path = urlParsed.pathname; // Теперь записываем полный путь к server.js. Мне это особенно нужно, так как сервер будет // висеть в systemd, и путь, о котором он будет думать, будет /etc/systemd/system/. prePath = __dirname; try < // Здесь мы пытаемся подключить модуль по ссылке. Если мы переходим на // localhost:8000/api, то скрипт идёт по пути /routing/dynamic/api, и, если находит там // index.js, берет его. Я знаю, что использовать тут try/catch не слишком правильно, и потом // переделаю через fs.readFile, но пока у вас не загруженный проект, разницу в скорости // вы не заметите. let dynPath = './dynamic/' + path; let routeDestination = require(dynPath); res.end('We have API!'); >catch (err) < // Не нашлось api? Грустно. res.end("We don't have API!"); >>; exports.define = define; 

Готово. Теперь мы можем создать /routing/dynamic/api , и протестировать то, что у нас есть. Я воспользуюсь для этих целей своим готовым скриптом по адресу /dm/shortenUrl.

image

Определяем, есть ли страница

Мы научились находить скрипты, теперь нужно научиться находить статику. Первым делом пойдём в /routing/nopage и создадим там index.html . Просто создайте костяк html-страницы, и сделайте один-единственный заголовок h1 с текстом: «404». После этого возвращаемся в /routing/index.js , но теперь мы сосредоточимся на уже написанном блоке catch:

// /routing/index.js: блок catch catch (err) < // Находим наш путь к статическому файлу и пытаемся его прочитать. // Если вы не знаете, что это за '=>', тогда прочитайте про стрелочные функции в es6, // очень крутая штука. let filePath = prePath+'/static'+path+'/index.html'; fs.readFile(filePath, 'utf-8', (err, html) => < // Если не находим файл, пытаемся загрузить нашу страницу 404 и отдать её. // Если находим — отдаём, народ ликует и устраивает пир во имя царя-батюшки. if(err) < let nopath = '/var/www/html/nodejs/routing/nopage/index.html'; fs.readFile(nopath, (err , html) =>< if(!err) < res.writeHead(404, ); res.end(html); > // На всякий случай напишем что-то в этом духе, мало ли, иногда можно случайно // удалить что-нибудь и не заметить, но пользователи обязательно заметят. else< let text = "Something went wrong. Please contact webmaster@forgetable.ru"; res.writeHead(404, ); res.end(text); > >); > else< // Нашли файл, отдали, страница загружается. res.writeHead(200, ); res.end(html); > >); > 

Воодушевляет. Теперь мы можем отдавать страницу 404, а так же html-страницы, которые мы добавляем сами в /routing/static . В моём случае страница 404 выглядит так:

image

Пара слов об API

Способ организации скриптов — личное дело каждого. На данный момент код в блоке try у меня такой:

let dynPath = './dynamic/' + path; let routeDestination = require(dynPath); routeDestination.promise(res,postData,req).then( result => < res.writeHead(200); res.end(result); return; >, error => < let endMessage = <>; endMessage.error = 1; endMessage.errorName = error; res.end(JSON.stringify(endMessage)); return; > ); 

По сути, все мои скрипты представляют собой функцию, которая передаёт замыканиями параметры и возвращает промис, и дальнейшая логика на промисах и завязана. В данном контексте такой подход кажется мне очень удобным, так отлов ошибок становится очень лёгким делом. Уже можно, в принципе, переписать эту логику на async / await, но особого смысла для себя я в этом не вижу.

Обрабатываем запросы браузера

Теперь мы уже можем пользоваться нашим сервером, и он будет возвращать страницы. Однако, если вы поместите в /routing/static/somepage ту же страницу, которая прекрасно работает, например, на апаче, вы столкнётесь с некоторыми проблемами.

Во-первых, для этого веб-сервера, как и для, наверное, всех в таком роде, нужно иначе задавать ссылки на css/js/img/… файлы. Если вам хочется подключить к странице 404 css-файл и сделать её красивой, то в случае с апачем мы создали бы в той же папке nopage файл style.css и подключили бы его, указав в тэге link следующее: ‘href=«style.css»’. Однако, теперь нам нужно писать путь иначе, а именно: «/routing/nopage/style.css».

Во-вторых, даже если мы подключим всё правильно, то ничего не произойдёт, и у нас всё ещё будет голая страница html. И вот тут мы подходим к самой последней части сегодняшней статьи — дополним скрипт, чтобы он ловил и обрабатывал запросы, которые браузер отправляет сам, читая разметку html. Ну и про favicon не забудем — возьмите фавиконку и положите её в /routing директорию нашего сервера.

Итак, переходим опять в /routing/index.js . Теперь мы будем писать код прямо перед try/catch:

// До этого мы уже получили path и prePath. Теперь осталось понять, какие запросы // мы получаем. Отсеиваем все запросы по точке, так чтобы туда попали только запросы к // файлам, например: style.css, test.js, song.mp3 if(/\./.test(path)) < if(path == 'favicon.ico') < // Если нужна фавиконка - возвращаем её, путь для неё всегда будет 'favicon.ico' // Получается, если добавить в начале prePath, будет: '/var/www/html/nodejs/routing/favicon.ico'. // Не забываем про return, чтобы сервер даже не пытался искать файлы дальше. let readStream = fs.createReadStream(prePath+path); readStream.pipe(res); return; >else< // А вот если у нас не иконка, то нам нужно понять, что это за файл, и сделать нужную // запись в res.head, чтобы браузер понял, что он получил именно то, что и ожидал. // На данный момент мне нужны css, js и mp3 от сервера, так что я заполнил только // эти случаи, но, на самом деле, стоит написать отдельный модуль для этого. if(/\.mp3$/gi.test(path)) < res.writeHead(200, < 'Content-Type': 'audio/mpeg' >); > else if(/\.css$/gi.test(path)) < res.writeHead(200, < 'Content-Type': 'text/css' >); > else if(/\.js$/gi.test(path)) < res.writeHead(200, < 'Content-Type': 'application/javascript' >); > // Опять же-таки, отдаём потом серверу и пишем return, чтобы он не шёл дальше. let readStream = fs.createReadStream(prePath+path); readStream.pipe(res); return; > > 

Фух. Всё готово. Теперь можно подключить наш css-файл и увидеть нашу страницу 404 со всеми стилями:

image

Выводы

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

В общем и целом, работой сервера я очень даже доволен, и сейчас отказался от апача в сторону этого решения, и в целом это был очень интересный опыт. Большое спасибо, что ты, читатель, разделил его со мной, дойдя до этого момента.

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

Источник

Читайте также:  Объединение ячеек
Оцените статью