- Веб сервер на JavaScript
- Веб сервер в две строки кода
- Saved searches
- Use saved searches to filter your results more quickly
- License
- mafintosh/browser-server
- Name already in use
- Sign In Required
- Launching GitHub Desktop
- Launching GitHub Desktop
- Launching Xcode
- Launching Visual Studio Code
- Latest commit
- Git stats
- Files
- README.md
- About
- Свой веб-сервер на NodeJS, и ни единого фреймворка. Часть 1
- Начнём с файловой структуры
- Создаём наш сервер
- Ловим запросы к нашим API
- Определяем, есть ли страница
- Пара слов об API
- Обрабатываем запросы браузера
- Выводы
Веб сервер на JavaScript
Уже давно есть возможность написать свой сервер, с нуля на JavaScript, с использованием Node.js движка от браузера Chrome.
В первом моем посте-заметке на хабре, хочу поделиться своим опитом создания самописного сервера.
Так ка я люблю хардкор в програмировании, то когда необходимо било сделать сайт, я решил делать всю серверную часть с нуля! Почему я вибрал JavaScript? — дело в том что мне также необходимо било запрограмировать и сам сайт (странички хтмл). И как Вы догадались, и там и там один и тот же язык — JavaScript, что несомненно является плюсом для разработчика, так как не надо изучать и понимать десять разных синтаксесов и парадигм языков.
Веб сервер в две строки кода
Напишем пару строчек кода и создадим собственный веб сервер, назовем файл index.js.
var http = require(‘http’);
http.createServer(function (req, res) res.end(‘Hello World\n’);
>).listen(80, ‘localhost’);
console.log(‘Server running at localhost’);
Поясню, сначала подключаем модуль http для собственно веб сервера, далее создаем асинхронную функцию калбек для входящих запросов и на конец то стартуем сервер на 80 порту. Очень просто и все понятно, нет ничего лишнего.
Важно помнить, что Skype может висеть на 80 порту, по этому необходимо убить Skype перед запуском сервера!
Запускаем сервер, с консоли, следующим образом:
Вот и все, переходим в браузер и вводим localhost, в ответ получим Hello World!
Ето только вершина айсберга, в следующих заметках я расскажу более детально как сделать полноценный веб сервер для большого сайта.
Saved searches
Use saved searches to filter your results more quickly
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session. You switched accounts on another tab or window. Reload to refresh your session.
A HTTP «server» in the browser that uses a service worker to allow you to easily send back your own stream of data.
License
mafintosh/browser-server
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Sign In Required
Please sign in to use Codespaces.
Launching GitHub Desktop
If nothing happens, download GitHub Desktop and try again.
Launching GitHub Desktop
If nothing happens, download GitHub Desktop and try again.
Launching Xcode
If nothing happens, download Xcode and try again.
Launching Visual Studio Code
Your codespace will open once ready.
There was a problem preparing your codespace, please try again.
Latest commit
Git stats
Files
Failed to load latest commit information.
README.md
A HTTP «server» in the browser that uses a service worker to allow you to easily send back your own stream of data.
npm install browser-server
First generate the service worker, using the browser-server command line tool
npm install -g browser-server # /demo is the prefix you want to intercept browser-server /demo > worker.js
Then create a simple app and browserify it
var createServer = require('browser-server') var server = createServer() server.on('request', function (req, res) console.log('intercepting request', req) res.end('hello world') >) server.on('ready', function () fetch('/demo/test.txt').then(function (r) return r.text() >).then(function (txt) console.log('fetch returned', txt) >) >)
browserify app.js > bundle.js
And make a index.html page like this
> html> body> script src pl-s">bundle.js">script> body> html>
Make sure the worker.js file is also stored in the same folder.
Now serve the folder using a http server, fx
npm install -g http-server http-server .
Now if you open index.html you should see the server intercepting the request and returning hello world.
Works for all http apis, including video/audio tags!
About
A HTTP «server» in the browser that uses a service worker to allow you to easily send back your own stream of data.
Свой веб-сервер на 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', () =>/ Информации больше нет - передаём её дальше. routing.define(req, res, jsonString); // Функцию define мы ещё не создали. > ); >); 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. Ура. Ответ есть.
Замечательно. Только это не совсем то, что нам нужно от сервера, правда?
Ловим запросы к нашим 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.
Определяем, есть ли страница
Мы научились находить скрипты, теперь нужно научиться находить статику. Первым делом пойдём в /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 выглядит так:
Пара слов об 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 со всеми стилями:
Выводы
Ура! Мы сделали свой веб-сервер, который работает, и работает хорошо. Разумеется, это только начало работы над приложением, но самое главное уже готово — на таком веб-сервере можно поднимать любые страницы, он справляется и со статикой, и с динамическим контентом, и роутинг, на мой взгляд, выглядит удобно — достаточно просто положить соответствующий файл в static или dynamic, и он тут же подхватится, и не надо писать роутинг для каждого конкретного случая.
В общем и целом, работой сервера я очень даже доволен, и сейчас отказался от апача в сторону этого решения, и в целом это был очень интересный опыт. Большое спасибо, что ты, читатель, разделил его со мной, дойдя до этого момента.
UPD: Я не призываю кого-либо использовать этот сервер на постоянной основе. Несмотря на то, что он полностью меня устраивает, в нём нет большого количества нужных для обычного веб-сервера функций и не оптимизирован некоторый готовый функционал, вроде внятного определения mime-типов. Это всё будет в следующих статьях.