Pull to refresh

Comments 97

Спасибо! Да, я очень хотел бы сделать продолжение, есть несколько достаточно занятных идей, сейчас как раз занимаюсь их реализацией.

Еще бы ссылку на гитхаб с дефолтным проектом.

Ссылка будет в следующей статье

Э… и с каких пор понятие "архитектура" стало означать "структура директорий"?

Да, я действительно допустил неточность, спасибо.
UFO just landed and posted this here
Я же не говорю, что написал инструмент, которым кто-либо должен пользоваться. В первую очередь это была интересная задача, и я думаю, есть люди, которым она тоже интересна. По поводу require я объяснил в комментариях в коде, я знаю, что это не идеальная практика. Спасибо за комментарий!
UFO just landed and posted this here
Я был бы рад, если бы вы по возможности указали мне на ошибки. Спасибо, про статью я на самом деле подумаю, это интересный момент.
если бы вы по возможности указали мне на ошибки

Сразу бросается в глаза использование regexp в парсе пути...

Так же радует, как вы определяете mime… Попробуйте нормальные инструменты для этого дела, только сначала нужно проверить, чтобы этот файл вообще существовал.

Да, я знаю, что это делается иначе, здесь только самый базовый пример, спасибо. В следующей статье будет много, и это в том числе.
UFO just landed and posted this here

Ну и конечно же, стоит заметить, что для настоящих сайтов лучше использовать проверенные библиотеки, типа express. Потому что нужно поддерживать разные кодировки, выставлять заголовок content-length и много чего еще.


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

Полностью поддерживаю. Я ни в коем случае не призывал и не призываю использовать что-то подобное. На каких-то своих вещах, которые я буду делать, я буду использовать этот сервер, по работе буду использовать Koa, и это абсолютно нормально.
выставлять заголовок content-length и много чего еще.

Не обязательно, так как с HTTP/1.1 поддерживается 'Transfer-Encoding': 'chunked', который node.js выставляет автоматически.
А если будет еще nginx с включенным gzip, то Content-Length заранее и вовсе нет смысла высчитывать, так как nginx всё равно удалит его и поставит 'Transfer-Encoding': 'chunked'.

но до полноценного сервера нужно сделать много чего еще

По сути полноценный сервер это вот:
const http = require('http')

http.createServer((req, res) => {
  let html = 'hello<br>world'
  res.writeHead(200, {
    'Content-Type': 'text/html; charset=utf-8',
  })
  res.end(html)
}).listen(8080)


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

Поэтому в общем-то у автора уже полноценный сервер с нужными ему удобствами, и даже тот участок, где require в try-catch по сути не проблема, так как уже успешные require nodejs закэширует, и производительность не упадет.

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

Помнится написал свою реализацию мидлверов, чтобы не грузить в проект express, ибо там было тупое api. Так что да, по сути модуль http — вот, что нам нужно.
А на проду ставить чистый nodejs без какого-нибудь nginx — просто глупо.

А неуспешные require нода тоже закеширует? Если нет, будет хохма — сервер, который отдает динамический контент быстрее чем статику...

А что делать с HEAD запросом? Там вроде как длина нужна. По крайней мере iOS отказывается качать ipa-бинарники с кривым ответом в длине.
Ничего не делать, всё будет работать. Если не работает, то это проблема реализации приложения, что скачивает бинайрники, а не ios
Это штатный функционал системы: нужен манифест и сам бинарник. Так вот сама система сначала отправляет HEAD запрос и ждет валидный ответ с явным указанием длины, только потом пытается выкачивать через GET. Писал когда-то сервер для подобных вещей, пришлось явно ставить длину в хедере.
Да, отличный вопрос. Дело в том, что это всё, несомненно, важные вещи, но цель была немного другая — собрать базовый функционал, который бы вменяемо работал. Другие важные вещи, вроде поддержки кодировок, длины запросов, да даже нормального определения mime-типов, это всё материал для следующих статей.
Писал когда-то сервер для подобных вещей, пришлось явно ставить длину в хедере

Если речь про старую версию ios, где было HTTP/1.0, то только так.

Но в любом случае раздавать бинарники через ноду смысла нет, а если вы раздаете через nginx, то он сам для статики посчитает и выставит нужный размер content-length.
Проблема в том, что это дополнительная обработка / проверка на 1.0 / 1.1, в моем случае вся мета-информация о бинарнике была посчитана заранее — проще было просто отдавать валидный HEAD без body и с длиной + GET без длины. Ну и это был внутренний корпоративный сервис, смысла в nginx не было.
Это не проблема, это просто ваш уникальный опыт со старым HTTP/1.0, который можно не тянуть в текущие реалии.
Если уж HTTP/2.0 почти везде уже давно поддерживается, то HTTP/1.1 тем более

Ну, допустим, с content-length разобрались. Но остается еще немало других вопросов.


1) Что будет, если клиент пришлет POST-запрос с многогигабайтным body? Сервер загнется в попытках сохранить его в памяти.
2) Запрос может оборваться на середине. Надо обрабатывать эту ситуацию, чтобы избежать утечки открытых соединений.


Этот список можно продолжать долго. Свой "сервер без фреймворков" стоит писать лишь для того, чтобы наступить на эти грабли самому. Ну и ставить такой сервер наружу в интернет тоже опасно. Ботов-дудосеров, которые ходят по сети и автоматически ищут уязвимости, в интернете хватает.

1. Да ничего особенного не будет. Будет какой-нибудь эксепшен вида «RangeError: Invalid string length» и nodejs упадет, после этого владелец донастроит его и проблема решена.
2. node сам после истечения keep-alive прибьет этот запрос.

Полноценный сервер на ноде это те 5 строчек кода.
А перед сервером, на который предполагается что будет кто-то заходить извне, как ни крути лучше поставить nginx, который и body запрос ограничит в размере, и от примитивного ддоса защитит и т.д.

По сути ведь что? Не настроенный экспресс точно так же будет доставлять проблемы, да и пытаться все уровни защиты дублировать на ноде — это оверхед, всё равно nginx лучше с этим справится.

Ставить экспресс или что-то такое, это не то же самое что залить 2 файла на сервер и запустить их.
Да ничего особенного не будет. Будет какой-нибудь эксепшен вида «RangeError: Invalid string length»

А вот и нет. Приведенный в статье код


req.on('data', (data) => {
  jsonString += data;
});

req.on('end', () => {
 //...
});

Будет записывать все в jsonString, пока не кончится память.

Вот именно этот кусок кода на виртуалке с 2гб памяти, при попытке залить 3гб файл:
/nodejs/server.js:11
        jsonString += data;
                      ^

RangeError: Invalid string length
В js строки иммутабельны, и когда что-то плюсуется к строке, то создается новая строка. В итоге память занимает и старая строка, и новая и так далее. Таким образом память кончается намного быстрее, чем кажется, может до 2гб дело даже не дошло. Чтобы этого избежать можно использовать arr.push(newData) и arr.join('')

Но то что ни в одном из этих случаев сам сервер не «загнется», с этим я согласна

Если бы закончилась память — было бы написано что закончилась память. А тут совсем другая ошибка.

То, что выделяется новый участок памяти под результат конкатенации, это логично, но почему старый участок не должен освобождаться?


И на сколько я помню конкатенация работает быстрее чем Array#join.


UPD: пруф

Всё верно, а старые строки будут по мере надобности подхватываться сборщиком мусора, так как на них больше нет ссылок.
Если бы закончилась память — было бы написано что закончилась память. А тут совсем другая ошибка.

Я и не говорила, что память закончилась, я сказала, что может до 2гб дело и не дошло.
А то, что память таким образом кончается намного быстрее — это особенность строк в js.

Всё верно, а старые строки будут по мере надобности подхватываться сборщиком мусора, так как на них больше нет ссылок.

Нет, не верно. Ссылка на старые строки остается, в этом и проблема

То, что выделяется новый участок памяти под результат конкатенации, это логично, но почему старый участок не должен освобождаться?

Дело в том, что строки не только иммутабельны, но еще и pooled. Поэтому при создании новой строки, старая остается в пуле, и остается там до тех пор пока вручную не нормализовать строку или не удалить весь объект строки

То есть сборщиком мусора сама она не очиститься

И на сколько я помню конкатенация работает быстрее чем Array#join.
UPD: пруф

В этом примере размер массива всего лишь 13 символов. Естественно конкатенация будет быстрее. Речь про огромные массивы строк. Там arr.join будет быстрее, меньше израсходует памяти и вообще будет работать, в отличии от строк, которые могут свалится с такой ошибкой как выше.
При чем я бы вообще рекомендовала вместо arr.push использовать Map.set — будет в 2 раза быстрее чем []

Интересная информация, спасибо за ответ. Никогда во внутренности реализаций js не погружался, но видимо стоит.

Я вот прямо сейчас написал ради интереса такую штуку:
const teststring = "" //здесь было 512 utf-8 символов.
    let string = "";
    let multiplier = "2";
    setInterval(() => {
      if(string.length < 1024*1024*2) {
        string += teststring;
        console.log('1'); // для отслеживания
      }
    }, 10)


Следил встроенным в хром диспетчером задач за потреблением памяти и потреблением памяти javascript. Скрипт сделал всё верно, досчитал до 4096. За это время общая потребляемая память выросла, но даже не на 2 мегабайта. Было видно, как сборщик памяти раз в несколько секунд очищал память на 100-200кб.

Нет, любые строки по умолчанию pooled не являются, в пул попадают только строковые литералы. Результат конкатенации строковым литералом не является.


И на старую строку ссылку никто не удерживает, с этой стороны проблемы нет.


Вот производительность конкатенации в большом цикле и правда хромает, это общая особенность java, c#, javascript и еще кучи других языков.

Я вот прямо сейчас написал ради интереса такую штуку:
Скрипт сделал всё верно
И на старую строку ссылку никто не удерживает, с этой стороны проблемы нет.

Строки в js при конкатенации делают 2 вещи:
1. Создается новая строка
2. Новая строка складывается в пул

Если в коде встречается, что новая строка, которую прибавляют к старой уже есть в пуле, то новая строка не создается, просто используется та что уже в пуле. Поэтому если складывать одни и те же строки, то размер, можно сказать, увеличиваться не будет

Но если новая строка всегда новая и в пуле ее нет, начинается ад
Вот правильный пример:
'use strict';

function getRandomStr() {
    let testString = ''
    for (let i = 0; i < 512; i++) {
        testString += '' + Math.random() * 100 | 0
    }
    return testString
}

let string = ""
setInterval(() => {
    if (string.length < 1024 * 1024 * 2) {
        string += getRandomStr()
        console.log(string.length, process.memoryUsage())
    }
}, 10)


В начале:
974 { rss: 20697088,
  heapTotal: 5685248,
  heapUsed: 3913832,
  external: 9284 }

Спустя 25 секунд:
2097369 { rss: 104075264,
  heapTotal: 87474176,
  heapUsed: 65276264,
  external: 9284 }

Память и не думает очищаться. Но помимо памяти есть еще и накладные расходы на всё это, которые в случае с 2гб файлом переходят разумный предел и выбрасывается исключение до расхода всей доступной памяти
Сделал вот сейчас же функцию, которая огромное количество раз конкатенирует Math.random() в переменную, чтобы исключить любую возможность кэширования, а потом конкатенирует эту переменную к исходной, одновременно с этим показывал размер в килобайтах строки. На строке в ~134 мбайта нода ела 138 мбайт.
Вот выше код, его изучите
Спасибо, понял. Не знаю, почему в моём случае не было такого, нужно будет изучить этот вопрос. Спасибо, о конкатенации строк и памяти вообще нигде ничего нет.
Вот еще пример, тут символы, а не цифры, это ближе к тому, что передается в качестве боди:
'use strict';
const iter = 100000

function getRandomStr() {
    let testString = []
    for (let i = 0; i < 512; i++) {
        testString.push(String.fromCharCode(Math.random() * 255 | 0))
    }
    return testString.join('')
}

let string = []
for (let i = 0; i < iter; i++) {
    string.push(getRandomStr())
}
console.log((process.memoryUsage().rss / 1024 / 1024 | 0) + 'MB')

Результат:
>node sdfs.js
104MB

И результат довольно быстрый. А вариант с конкатенацией строк
'use strict';
const iter = 100000

function getRandomStr() {
    let testString = ''
    for (let i = 0; i < 512; i++) {
        testString += String.fromCharCode(Math.random() * 255 | 0)
    }
    return testString
}

let string = ''
for (let i = 0; i < iter; i++) {
    string += getRandomStr()
}
console.log((process.memoryUsage().rss / 1024 / 1024 | 0) + 'MB')

Работает вечность и всё равно в конце выдает:
FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory


Спасибо, о конкатенации строк и памяти вообще нигде ничего нет.

Еще как есть. Вот например от одного из разработчиков ноды:
https://habrahabr.ru/post/283090/#comment_9641904
В мире много чего есть, только вот найти сложно. Спасибо, это действительно бесценные комментарии.

Обратите внимание: суммарный объем сгенерированных вами же промежуточных строк — гигабайт для внешнего цикла и еще сто метров во внутреннем. Ой, то есть два гигабайта и еще 200 метров, там же 16ти битная кодировка если я правильно помню.


Тот факт, что итоговое потребление памяти — всего 65 мегабайт, как раз и говорит о том, что строки ни в каком пуле не удерживаются.

Второй пример — https://habrahabr.ru/post/327440/#comment_10194348

Результат для arr.push:
>node sdfs.js
104MB

Работает очень быстро

Результат для конкатенации строк работает вечность и всё равно в конце выдает:
FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory

Какое это имеет отношение к пулу строк?

Какое это имеет отношение к пулу строк?
Тот факт, что итоговое потребление памяти — всего 65 мегабайт, как раз и говорит о том, что строки ни в каком пуле не удерживаются.

Вернемся к тому первому примеру:
Длина получившейся строки — 2097369. Каждый символ допустим занимает 16 бит или 2 байта, то есть размер такой строки должен быть 4мб.
А реальный размер занимаемой памяти — 79мб, которая не очищается а только продолжает расти

Это всего лишь 4мб текста, в примере выше передается что-то около 3гб данных. Тут не память раньше кончится, а проблема с накладными расходами на поддержание таких строк начнется, что и произошло

Не знаю как вы умудрились прийти к выводу, что раз памяти в 10 раз (а чем дальше, тем больше) больше расходуется чем надо, то значит всё в порядке со строками

Вот еще пример:
'use strict';

function getRandomStr() {
    let testString = ''
    for (let i = 0; i < 512; i++) {
        testString += '' + Math.random() * 100 | 0
    }
    return testString
}

const iter = 1000

let string = ''
let i = 0
const intr = setInterval(() => {
    i++
    string += getRandomStr()
    if (i > iter) {
        stopIntr()
    }
}, 10)

function stopIntr() {
    clearInterval(intr)
    console.log('Занимает памяти: ' + (process.memoryUsage().rss / 1024 | 0) + 'KB')
    console.log('Хотя должно занимать всего лишь: ' + (Buffer.from(string).length / 1024 | 0) + 'KB')
}

>node sdfs.js
Занимает памяти: 77268KB
Хотя должно занимать всего лишь: 951KB

Тоесть в 77 раз больше памяти расходует, чем требуется всего лишь на 1000 итераций. В примере с arr.push было 100.000 итераций, а памяти расходовалось всего «104MB»

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

И чтобы этого избежать можно использовать arr.push(newData) и arr.join('')
Это и был мой изначальный совет (слово в слово)
Если вы до сих пор хотите гнуть линию, что никаких удержаний нет, в пул не попадают — то я пожалуй пас.
Да, это действительно так работает, хоть мне и не совсем понятна эта логика. Мне кажется, логичнее было бы очищать наименее используемые строки из пула при достижении определённого размера оперативной памяти. В общем, эта вещь отлично помогает при частой конкатенации одних и тех же строк, что используется в разработке на js часто, но собирать большие объемы данных по кусочкам с помощью неё не стоит.

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


В процессе работы исходного примера совершается около 2 * 1024 итераций, каждая из которых добавляет в среднем 1024 символа к строке.


Суммарная длина промежуточных строк — это 1024 (2048 2047) / 2, то есть 2 миллиарда символов или 4 гигабайта.


Потребление памяти — 79 Мб, то есть в 50 раз меньше.


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


Растет же потребляемая память в данном случае, скорее всего, не из-за утечек, а потому что нода таким образом пытается уменьшить число вызовов сборщика мусора.

Я повторял этот опыт, и при размере сгенерированного файла в 150мб нода ела под 800 мб памяти и сборка мусора не произошла ни разу. При этом процесс выполнения стал крайне медленным, по одной инерации в секунду примерно, так что особого смысла продолжать не было. Даже если часть памяти бы всё-таки освобождалась сразу, всё равно это не отменяет несостоятельность использования такого метода для получения данных по кускам и склейки их. Тем более, сейчас в ноде Array.join работает не медленнее конкатенации.

Заметьте: нода ела всего лишь 800 мб! Если бы все строки хранились в пуле — зависимость потребляемой памяти от размера файла была бы квадратичной, а тут разница всего в 5-6 раз.


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

Мне кажется, эти 5-6 раз достаточно критичны для серверных приложений, всё-таки. И тема всё равно интересная, как ни крути.

Так я же не спорю что они критичны! Я спорю с утверждением что любые строки интернируются в пул строк и становятся недоступны сборщику мусора, потому что это бред.

Растет же потребляемая память в данном случае, скорее всего, не из-за утечек,

Абы, да кабы. У вас догадки, а я опираюсь на слова одного из разработчиков ноды и собственные эксперименты.
К тому же это не утечки, а стандартная работа со строками

Я спорю с утверждением что любые строки интернируются в пул строк и становятся недоступны сборщику мусора, потому что это бред.
Не было такого утверждения

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

Ну если бы мы были в мире, где оптимизатор был совсем туп, то да. Что-то он умеет оптимизировать, и это заметно

И кто сказал про навечное застревание в пуле? Те строки, что сгенерированы в getRandomStr освобождаются, так как они не используются больше нигде, только их финальная копия сохранена в пуле
Когда строки свободны, а когда нет — это чуть поработав с собственным шаблонизатором вы быстро научитесь чувствовать, но это оверхед

Вот чтобы не гадать, и предлагают использовать arr.push (а лучше Map/Set), так как он даже с одинаковым расходом памяти работает быстрее на больших строках
И кто сказал про навечное застревание в пуле?

Вы:


Дело в том, что строки не только иммутабельны, но еще и pooled. Поэтому при создании новой строки, старая остается в пуле, и остается там до тех пор пока вручную не нормализовать строку или не удалить весь объект строки

Поскольку в приведенном вами коде нет никаких операций нормализации строки или явного удаления объекта — строки в пуле должны оставаться все время работы программы. Чего не наблюдается.


Ну если бы мы были в мире, где оптимизатор был совсем туп, то да. Что-то он умеет оптимизировать, и это заметно

Причем тут оптимизатор? Строки успешно собирает самый обычный сборщик мусора, как и любые другие объекты.

Вы:

И где же?

Поскольку в приведенном вами коде нет никаких операций нормализации строки или явного удаления объекта — строки в пуле должны оставаться все время работы программы. Чего не наблюдается.

Вот я так и думала, что мы будем не по существу, а придираться к словам.

Ну хорошо, вот вам 3 условие, когда строки освобождаются: Они больше нигде не используются, их пул больше не нужен, они стали частью другого пула. Оптимизатор сам умеет понимать когда это произошло, и делает это успешно

Причем тут оптимизатор? Строки успешно собирает самый обычный сборщик мусора, как и любые другие объекты.

Оптимизатор тут при том, что он понимает, когда можно срезать углы, и превращает функцию getRandomStr(), которая прогоняется не один раз, в нечто более быстро и оптимальное

Ситуацию "они больше нигде не используются" отслеживает не оптимизатор, а сборщик мусора.

Ситуацию «они больше нигде не используются» отслеживает не оптимизатор, а сборщик мусора.

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

Вашему терпению можно позавидовать

Еще ошибки, видимые сходу:


  1. Страница 404 отдается с кодом 200. Это так и задумано?
  2. Вместо хардкода в prePath стоило бы использовать __dirname или require.resolve
Да, действительно, с кодом 404 вышла ошибка, сейчас исправлю.
__dirname я почему-то пропустил мимо своего взгляда, большое спасибо за совет.

Я бы еще несколько сомнительных моментов добавил.


Если __dirname по некоторым причинам не подходит, то различные базовые константы по хорошему надо хотя бы выносить в конфигурационный файл, или в опции к запуску, или еще куда. Так же увидел что пути через конкатенацию формируются, для этого есть path.join, а иначе можно напороться на проблемы при запуске сервера под Windows, например.


Дублирующийся код при формирование Content-Type и не правильные регулярки, не говоря уже про сам подход определения типа файлов.


/.mp3/.test('test.mp3') // true
/.mp3/.test('test.mp3.gz') // true
/.mp3/.test('testmp3') // true
/.mp3/.test('testmp3test') // true

/.mp3/.test('test.MP3') // false

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

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

Про регулярные выражения — да, надо исправить, и чтобы она смотрела в конце страницы. Благодарю.

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

Большое спасибо за развёрнутый комментарий

А вообще есть библиотека mime.


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

Спасибо, кстати, интересная штука. Потом было бы интересно сравнить и какие-то вещи скорректировать.
А еще вот задачка, заставить свой сервер работать в многопроцессорной среде.
К сожалению, у меня на VDS одно ядро. Слышал про кластеры, но увы, этим получится заняться только позже.
UFO just landed and posted this here

Для стандартного http сервера, это не задача даже, скорее способ запуска:


$ pm2 start -i 2 server.js

Интересно очень.
Погуглите PillarJS и понятие BYO-фреймворк

Ищу что такое BYO-фреймворк получаю или ссылку на pillarjs или на этот комментарий. Вы можете дать ссылку на ресурс, который бы объяснял что это такое, пожалуйста.

BYO — Build you own, собери себе сам
https://pillarjs.github.io/
Берешь нужные компоненты и строишь.
Express, например, использует path-to-regexp и router из PillarJS (ветка 5.0 Express)

> Свой веб-сервер
> ни единого фреймворка
> const http = require('http');
> let server = new http.Server(


Что, правда?
Ваша статья — дерьмо. Hello world работающий по протоколу HTTP.

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

По поводу статьи — я не ставил себе целью написать апач. Это небольшая работа, и действительно своего рода «hello-world», который я бы с радостью прочёл, только взявшись за nodejs.

Класс. Старые добрые локальные инклюды — теперь и в node.js.


А идея отдавать статику нодой и отказаться от апача просто прекрасна.

Почему у вас в коде почти везде с единичным присваиванием let вместо const?
Я пока что не слишком свыкся с let/const, к сожалению, а некоторые вещи пишу на автомате и даже не вижу. Спасибо, я разберусь с этим.
Если на автомате, тогда надо наоборот — везде писать const, а там где IDE будет ругаться на повторное присваивание менять на let.

Из комментариев, данных автором, и кода, который он написал в статье, могу резюмировать следующее: человек только только нашёл что такое nodejs, по некоторым соображениям даже только только притронулся к js, поэтому не стоит рассматривать статью как какой-то призыв к действию. Вообще не стоит рассматривать статью как какой-то ценный кусок ума. Просто hello world на публику.

Да, частично это правда. Я не призывал людей к тому, чтобы все сделали себе что-то подобное, вовсе нет. Но, когда я только начинал разбираться в nodejs, я бы многое дал за подобную статью. В этом и суть.
На Go подобный этому «hello world» выглядит лаконичнее

Конечно лаканичнее, но мы же тут про ноду статью читаем. Как поднять вэб-сервер на стандартной библиотеке, уникальная в своём роде статья. А Вы тут со своим Go. Автор чётко выразился, что в своё время он много чего бы отдал за такую статью. Речь идет о годах 4 назад, за это время ничего подобного так и не появилось.

server.js
'use strict';

const http = require('http')
const routing = require('./routing')

http.Server(function (req, res) {
    let jsonString = ''

    res.setHeader('Content-Type', 'application/json; charset=utf-8')
    req.on('data', (data) => {
        jsonString += data
    })
    req.on('end', () => {
        routing(req, res, jsonString)
    })
}).listen(8000)


routing/index.js
'use strict';

const url = require('url')
const fs = require('fs')
const path = require('path')

function show404(req, res) {
    const nopath = path.join(__dirname, 'nopage', 'index.html')

    fs.readFile(nopath, (err, html) => {
        if (!err) {
            res.writeHead(404, { 'Content-Type': 'text/html; charset=utf-8' })
            res.end('' + html)
        }
        else {
            const text = "Something went wrong. Please contact webmaster";
            res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' })
            res.end(text)
        }
    })
}

function loadStaticFile(pathname, req, res) {
    const staticPath = path.join(__dirname, pathname)
    if (pathname === '/favicon.ico') {
    }
    else if (/[.]mp3$/gi.test(pathname)) {
        res.writeHead(200, { 'Content-Type': 'audio/mpeg' })
    }
    else if (/[.]css$/gi.test(pathname)) {
        res.writeHead(200, { 'Content-Type': 'text/css' })
    }
    else if (/[.]js$/gi.test(pathname)) {
        res.writeHead(200, { 'Content-Type': 'application/javascript; charset=utf-8' })
    }
    fs.createReadStream(staticPath).pipe(res)
}

function router(req, res, postData) {
    const urlParsed = url.parse(req.url, true)
    const pathname = urlParsed.pathname

    if (/[.]/.test(pathname)) {
        loadStaticFile(pathname, req, res)
        return
    }
    
    let filepath = path.join(__dirname, 'dynamic', pathname, 'index.js')
    fs.access(filepath, err => {
        if (!err) {
            const routeDestination = require(filepath)
            routeDestination.promise(res, req, postData)
                .then(result => {
                    res.writeHead(200)
                    res.end('' + result)
                })
                .catch(err => {
                    res.writeHead(404, { 'Content-Type': 'text/html; charset=utf-8' })
                    res.end(`${err.name}: ${err.message}`)
                })
        }
        else {
            filepath = path.join(__dirname, 'static', pathname, 'index.html')
            fs.readFile(filepath, 'utf-8', (err, html) => {
                if (err) {
                    show404(req, res)
                }
                else {
                    res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' })
                    res.end('' + html)
                }
            })
        }
    })
}

module.exports = router


Думаю, от require избавится не получится, всё таки у запрашиваемых модулей могут быть зависимости. Только если все модули переписать соответствующим образом.

А вместо postData можно сделать:
    req.on('end', () => {
        req.body = jsonString
        routing(req, res)
    })

Чтобы не пробрасывать постоянно лишнюю переменную
UFO just landed and posted this here
У меня свой небольшой сервер, на котором я пишу всякое, и мне более чем удобно использовать свой костыль. Если буду писать что-либо по работе, разумеется, буду использовать «нормальный сервер».
UFO just landed and posted this here
Я много раз говорил и повторю ещё раз, что эта статья не призывала и не призывает кого-либо использовать подобное решение на постоянной основе. Это сервер, который я начал писать исключительно для себя, и пока что в нём нет многих важных функций, например. Пока что этот сервер – скорее игрушка для тех, кто хочет сделать что-либо подобное на досуге. Тем не менее, спасибо за комментарии!
UFO just landed and posted this here
Статья-выдох))) из разряда простенько и со вкусом.
А вот тут вы немного не правы:
Пара слов об API
Способ организации скриптов — личное дело каждого.

Представьте, что ваш проект разросся, у него появилась команда и огромная кодовая база, но — «способ организации скриптов — личное дело каждого»… В итоге ориентироваться в таком коде с личными делами — совершенно невозможно, невозможно и написать гайд по коду для вольных программистов гитхаба.
То есть на самом деле способ организации (скриптов или чего-то другого) — одна из первых вещей, о которых надо думать.
Хорошего дня!
Ну да, я просто имел в виду, скорее, каждую команду. А так — обязательно должна быть чёткая структура в рамках каждого проекта. Спасибо!
В Express, кстати, есть структура проекта по-умолчанию. Создается командой (предварительно нужно установить Express глобально)
express -someFlag -anotherFlag projectFolder

Автору можно было бы создать аналогичную структуру. Тогда она была бы унифицирована со стандартным решением, привычнее бы воспринималась и в случае необходимости упростила бы переход на фреймворк. Заодно можно было бы использовать package.json и запускать сервер стандартным
npm start
Да, мне нравится эта идея, спасибо!
Sign up to leave a comment.

Articles