@e22m4u/js-http-static-router 0.1.2 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.mocharc.json +2 -1
- package/README.md +265 -17
- package/build-cjs.js +1 -1
- package/dist/cjs/index.cjs +114 -76
- package/example/server.js +23 -13
- package/mocha.setup.js +4 -0
- package/package.json +10 -8
- package/src/http-static-router.d.ts +7 -2
- package/src/http-static-router.js +73 -61
- package/src/http-static-router.spec.js +899 -0
- package/src/static-route.js +18 -8
- package/src/types.d.ts +7 -0
- package/src/utils/create-cookie-string.d.ts +6 -0
- package/src/utils/create-cookie-string.js +28 -0
- package/src/utils/create-cookie-string.spec.js +32 -0
- package/src/utils/create-error.d.ts +14 -0
- package/src/utils/create-error.js +29 -0
- package/src/utils/create-error.spec.js +42 -0
- package/src/utils/create-request-mock.d.ts +35 -0
- package/src/utils/create-request-mock.js +483 -0
- package/src/utils/create-request-mock.spec.js +646 -0
- package/src/utils/create-response-mock.d.ts +18 -0
- package/src/utils/create-response-mock.js +131 -0
- package/src/utils/create-response-mock.spec.js +150 -0
- package/src/utils/fetch-request-body.d.ts +26 -0
- package/src/utils/fetch-request-body.js +148 -0
- package/src/utils/fetch-request-body.spec.js +209 -0
- package/src/utils/get-pathname-from-url.d.ts +1 -1
- package/src/utils/{get-request-pathname.spec.js → get-pathname-from-url.spec.js} +1 -1
- package/src/utils/index.d.ts +8 -0
- package/src/utils/index.js +8 -0
- package/src/utils/is-readable-stream.d.ts +9 -0
- package/src/utils/is-readable-stream.js +12 -0
- package/src/utils/is-readable-stream.spec.js +23 -0
- package/src/utils/parse-content-type.d.ts +15 -0
- package/src/utils/parse-content-type.js +34 -0
- package/src/utils/parse-content-type.spec.js +79 -0
- package/src/utils/parse-cookie-string.d.ts +19 -0
- package/src/utils/parse-cookie-string.js +36 -0
- package/src/utils/parse-cookie-string.spec.js +45 -0
- package/{example/static → static}/index.html +2 -2
- /package/{example/static/nested/file.txt → static/assets/nested/heart.txt} +0 -0
- /package/{example/static/file.txt → static/assets/rabbit.txt} +0 -0
package/.mocharc.json
CHANGED
package/README.md
CHANGED
|
@@ -2,6 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
HTTP-маршрутизатор статичных ресурсов для Node.js.
|
|
4
4
|
|
|
5
|
+
Модуль удобен для встраивания документации или административных панелей
|
|
6
|
+
непосредственно в серверное приложение, позволяя избежать развертывания
|
|
7
|
+
дополнительной инфраструктуры.
|
|
8
|
+
|
|
9
|
+
- Интеграция в существующий *http*-сервер.
|
|
10
|
+
- Управление доступом к файловой системе через маршруты.
|
|
11
|
+
- Использование потоков для экономии оперативной памяти.
|
|
12
|
+
|
|
13
|
+
## Содержание
|
|
14
|
+
|
|
15
|
+
- [Установка](#установка)
|
|
16
|
+
- [Базовый пример](#базовый-пример)
|
|
17
|
+
- [Маршрутизатор](#маршрутизатор)
|
|
18
|
+
- [Создание экземпляра](#создание-экземпляра)
|
|
19
|
+
- [Регистрация маршрута](#регистрация-маршрута)
|
|
20
|
+
- [Обработка запросов](#обработка-запросов)
|
|
21
|
+
- [Тесты](#тесты)
|
|
22
|
+
- [Лицензия](#лицензия)
|
|
23
|
+
|
|
5
24
|
## Установка
|
|
6
25
|
|
|
7
26
|
```bash
|
|
@@ -22,9 +41,24 @@ import {HttpStaticRouter} from '@e22m4u/js-http-static-router';
|
|
|
22
41
|
const {HttpStaticRouter} = require('@e22m4u/js-http-static-router');
|
|
23
42
|
```
|
|
24
43
|
|
|
25
|
-
##
|
|
44
|
+
## Базовый пример
|
|
45
|
+
|
|
46
|
+
Пример предполагает следующую структуру проекта.
|
|
47
|
+
|
|
48
|
+
```txt
|
|
49
|
+
/static
|
|
50
|
+
├── index.html
|
|
51
|
+
└── /assets
|
|
52
|
+
└── rabbit.txt
|
|
53
|
+
/src
|
|
54
|
+
└── server.js
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Создание маршрутизатора, определение маршрутов и запуск сервера.
|
|
26
58
|
|
|
27
59
|
```js
|
|
60
|
+
// src/server.js
|
|
61
|
+
import path from 'path';
|
|
28
62
|
import http from 'http';
|
|
29
63
|
import {HttpStaticRouter} from '@e22m4u/js-http-static-router';
|
|
30
64
|
|
|
@@ -33,32 +67,33 @@ const staticRouter = new HttpStaticRouter({
|
|
|
33
67
|
// при использовании опции "baseDir", относительные пути
|
|
34
68
|
// в регистрируемых маршрутах будут разрешаться относительно
|
|
35
69
|
// указанного адреса файловой системы
|
|
36
|
-
baseDir: import.meta.dirname,
|
|
37
|
-
// в данном случае "baseDir" указывает
|
|
38
|
-
// на путь к директории текущего модуля
|
|
70
|
+
baseDir: path.join(import.meta.dirname, '../static'),
|
|
39
71
|
});
|
|
40
|
-
// доступ к import.meta.dirname
|
|
41
|
-
// только для ESM начиная с Node.js 20.11.0
|
|
72
|
+
// доступ к import.meta.dirname (директория текущего модуля)
|
|
73
|
+
// возможен только для ESM начиная с Node.js 20.11.0
|
|
42
74
|
|
|
43
|
-
//
|
|
44
|
-
//
|
|
75
|
+
// объявление файла "index.html"
|
|
76
|
+
// в качестве индексной страницы
|
|
45
77
|
staticRouter.defineRoute({
|
|
46
|
-
remotePath: '/
|
|
47
|
-
resourcePath: '
|
|
78
|
+
remotePath: '/',
|
|
79
|
+
resourcePath: './index.html',
|
|
48
80
|
});
|
|
49
81
|
|
|
50
|
-
//
|
|
51
|
-
// для доступа
|
|
82
|
+
// экспозиция содержимого директории "assets"
|
|
83
|
+
// для доступа относительно корня
|
|
84
|
+
// пример: http://localhost:3000/rabbit.txt
|
|
52
85
|
staticRouter.defineRoute({
|
|
53
|
-
remotePath: '/
|
|
54
|
-
resourcePath: './
|
|
86
|
+
remotePath: '/',
|
|
87
|
+
resourcePath: './assets',
|
|
55
88
|
});
|
|
56
89
|
|
|
57
90
|
// создание HTTP сервера и определение
|
|
58
|
-
//
|
|
91
|
+
// слушателя для обработки запросов
|
|
59
92
|
const server = new http.Server();
|
|
60
93
|
server.on('request', async (req, res) => {
|
|
61
94
|
const fileSent = await staticRouter.handleRequest(req, res);
|
|
95
|
+
// если файл не был отправлен,
|
|
96
|
+
// то возвращается 404 Not Found
|
|
62
97
|
if (!fileSent) {
|
|
63
98
|
res.writeHead(404, {'Content-Type': 'text/plain'});
|
|
64
99
|
res.write('404 Not Found');
|
|
@@ -66,14 +101,227 @@ server.on('request', async (req, res) => {
|
|
|
66
101
|
}
|
|
67
102
|
});
|
|
68
103
|
|
|
104
|
+
// запуск сервера
|
|
69
105
|
server.listen(3000, () => {
|
|
70
106
|
console.log('Server is running on http://localhost:3000');
|
|
71
107
|
console.log('Try to open:');
|
|
72
|
-
console.log('http://localhost:3000
|
|
73
|
-
console.log('http://localhost:3000/
|
|
108
|
+
console.log('http://localhost:3000');
|
|
109
|
+
console.log('http://localhost:3000/rabbit.txt');
|
|
74
110
|
});
|
|
75
111
|
```
|
|
76
112
|
|
|
113
|
+
Запуск Node.js процесса.
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
node ./src/server.js
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Маршрутизатор
|
|
120
|
+
|
|
121
|
+
Класс `HttpStaticRouter` является основным компонентом модуля. Он отвечает
|
|
122
|
+
за хранение определений маршрутов, сопоставление входящих HTTP-запросов
|
|
123
|
+
с ресурсами файловой системы и потоковую передачу данных клиенту.
|
|
124
|
+
|
|
125
|
+
### Создание экземпляра
|
|
126
|
+
|
|
127
|
+
Конструктор маршрутизатора принимает объект настроек, который позволяет
|
|
128
|
+
задать базовые параметры работы.
|
|
129
|
+
|
|
130
|
+
Сигнатура:
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
type HttpStaticRouterOptions = {
|
|
134
|
+
baseDir?: string;
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
constructor(options?: HttpStaticRouterOptions);
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
**Параметры**
|
|
141
|
+
|
|
142
|
+
- `baseDir: string` - абсолютный путь к базовой директории.
|
|
143
|
+
Если параметр указан, все относительные пути при регистрации маршрутов
|
|
144
|
+
будут разрешаться относительно этой директории.
|
|
145
|
+
|
|
146
|
+
**Пример**
|
|
147
|
+
|
|
148
|
+
Создание экземпляра с указанием базовой директории.
|
|
149
|
+
|
|
150
|
+
```js
|
|
151
|
+
import path from 'path';
|
|
152
|
+
import {HttpStaticRouter} from '@e22m4u/js-http-static-router';
|
|
153
|
+
|
|
154
|
+
// создание маршрутизатора с указанием абсолютного пути
|
|
155
|
+
// к директории, в которой хранятся статические файлы
|
|
156
|
+
const staticRouter = new HttpStaticRouter({
|
|
157
|
+
baseDir: path.join(import.meta.dirname, '../public'),
|
|
158
|
+
});
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
*i. Доступ к переменной `import.meta.dirname` (директория текущего модуля)
|
|
162
|
+
возможен только при использовании *ESM* стандарта, начиная с версии
|
|
163
|
+
Node.js 20.11.0. Для более ранних версий или *CommonJS*
|
|
164
|
+
используется `__dirname`.*
|
|
165
|
+
|
|
166
|
+
### Регистрация маршрута
|
|
167
|
+
|
|
168
|
+
Метод `defineRoute` добавляет новое правило маршрутизации, связывая виртуальный
|
|
169
|
+
путь (*URL*) с реальным файлом или директорией на сервере. В момент вызова
|
|
170
|
+
данного метода маршрутизатор проверяет физическое существование указанного
|
|
171
|
+
ресурса в файловой системе.
|
|
172
|
+
|
|
173
|
+
Сигнатура:
|
|
174
|
+
|
|
175
|
+
```ts
|
|
176
|
+
type StaticRouteDefinition = {
|
|
177
|
+
remotePath: string;
|
|
178
|
+
resourcePath: string;
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
defineRoute(routeDef: StaticRouteDefinition): StaticRoute;
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
**Параметры**
|
|
185
|
+
|
|
186
|
+
- `remotePath: string`
|
|
187
|
+
Префикс URL-адреса, с которого должен начинаться входящий запрос
|
|
188
|
+
(обязательно должен начинаться со слеша `/`).
|
|
189
|
+
|
|
190
|
+
- `resourcePath: string`
|
|
191
|
+
Путь к существующему файлу или директории в файловой системе.
|
|
192
|
+
|
|
193
|
+
*i. Если при [создании экземпляра](#создание-экземпляра) маршрутизатора не была
|
|
194
|
+
указана опция `baseDir`, значение параметра `resourcePath` обязано быть
|
|
195
|
+
абсолютным путем.*
|
|
196
|
+
|
|
197
|
+
**Примеры**
|
|
198
|
+
|
|
199
|
+
Регистрация конкретного файла. Запрос по указанному пути в параметре
|
|
200
|
+
`remotePath` вернет содержимое связанного файла. Маршрутизатор учитывает
|
|
201
|
+
наличие/отсутствие завершающего слеша в конце пути.
|
|
202
|
+
|
|
203
|
+
```js
|
|
204
|
+
// предполагается, что маршрутизатор был создан
|
|
205
|
+
// с указанием базовой директории (опция "baseDir")
|
|
206
|
+
staticRouter.defineRoute({
|
|
207
|
+
remotePath: '/about',
|
|
208
|
+
resourcePath: './pages/about.html',
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// GET /about
|
|
212
|
+
// -> вернет содержимое ./pages/about.html
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Экспозиция содержимого директории. Если маршрут указывает на директорию,
|
|
216
|
+
дополнительная часть URL-адреса будет автоматически добавлена к пути
|
|
217
|
+
файловой системы.
|
|
218
|
+
|
|
219
|
+
```js
|
|
220
|
+
// открытие доступа ко всем файлам
|
|
221
|
+
// внутри директории "assets"
|
|
222
|
+
staticRouter.defineRoute({
|
|
223
|
+
remotePath: '/public',
|
|
224
|
+
resourcePath: './assets',
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
// GET /public/images/logo.png
|
|
228
|
+
// -> вернет содержимое ./assets/images/logo.png
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Регистрация маршрута с использованием абсолютного пути.
|
|
232
|
+
|
|
233
|
+
```js
|
|
234
|
+
import path from 'path';
|
|
235
|
+
import {HttpStaticRouter} from '@e22m4u/js-http-static-router';
|
|
236
|
+
|
|
237
|
+
// создание экземпляра без параметров
|
|
238
|
+
const router = new HttpStaticRouter();
|
|
239
|
+
|
|
240
|
+
// так как опция "baseDir" не задана, маршрутизатор требует
|
|
241
|
+
// передачи абсолютного пути для свойства "resourcePath",
|
|
242
|
+
// иначе будет выброшена ошибка InvalidArgumentError
|
|
243
|
+
router.defineRoute({
|
|
244
|
+
remotePath: '/robots.txt',
|
|
245
|
+
resourcePath: path.join(import.meta.dirname, '../public/robots.txt'),
|
|
246
|
+
});
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
*i. Маршрутизатор выполняет проверку безопасности. Если при запросе клиент
|
|
250
|
+
попытается выйти за пределы каталога с помощью относительных переходов
|
|
251
|
+
(например, `GET /public/../../etc/passwd`), обработчик прервет поиск
|
|
252
|
+
и файл не будет отправлен.*
|
|
253
|
+
|
|
254
|
+
### Обработка запросов
|
|
255
|
+
|
|
256
|
+
Метод `handleRequest` выполняет сопоставление входящего HTTP-запроса
|
|
257
|
+
с зарегистрированными маршрутами и выполняет отправку найденного файла
|
|
258
|
+
клиенту. При чтении файла маршрутизатор использует потоки, что позволяет
|
|
259
|
+
безопасно отдавать файлы большого размера без переполнения оперативной
|
|
260
|
+
памяти сервера.
|
|
261
|
+
|
|
262
|
+
Маршрутизатор автоматически определяет *MIME-тип* ресурса на основе его
|
|
263
|
+
расширения, устанавливает необходимые заголовки, а также корректно обрабатывает
|
|
264
|
+
обрыв соединения со стороны клиента, своевременно закрывая файловый поток
|
|
265
|
+
для предотвращения утечек памяти.
|
|
266
|
+
|
|
267
|
+
Сигнатура:
|
|
268
|
+
|
|
269
|
+
```ts
|
|
270
|
+
handleRequest(
|
|
271
|
+
request: IncomingMessage,
|
|
272
|
+
response: ServerResponse,
|
|
273
|
+
): Promise<boolean>;
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
**Параметры**
|
|
277
|
+
|
|
278
|
+
- `request: IncomingMessage` - нативный поток входящего запроса Node.js;
|
|
279
|
+
- `response: ServerResponse` - нативный поток исходящего ответа Node.js;
|
|
280
|
+
|
|
281
|
+
**Возвращаемое значение**
|
|
282
|
+
|
|
283
|
+
Метод возвращает `Promise`, который разрешается логическим значением `false`,
|
|
284
|
+
если маршрут не совпал, целевой файл физически отсутствует или метод запроса
|
|
285
|
+
не поддерживается. Во всех остальных случаях значением будет `true`, что
|
|
286
|
+
позволяет определить, взял ли на себя ответственность за обработку запроса
|
|
287
|
+
маршрутизатор.
|
|
288
|
+
|
|
289
|
+
**Пример**
|
|
290
|
+
|
|
291
|
+
Интеграция метода в обработчик событий нативного HTTP-сервера.
|
|
292
|
+
Если `handleRequest` возвращает `false`, сервер берет на себя
|
|
293
|
+
ответственность за отправку ответа с ошибкой клиенту.
|
|
294
|
+
|
|
295
|
+
```js
|
|
296
|
+
import http from 'http';
|
|
297
|
+
import {HttpStaticRouter} from '@e22m4u/js-http-static-router';
|
|
298
|
+
|
|
299
|
+
// создание экземпляра маршрутизатора
|
|
300
|
+
const staticRouter = new HttpStaticRouter();
|
|
301
|
+
// staticRouter.defineRoute(...)
|
|
302
|
+
|
|
303
|
+
const server = new http.Server();
|
|
304
|
+
|
|
305
|
+
server.on('request', async (req, res) => {
|
|
306
|
+
// передача объектов запроса и ответа маршрутизатору
|
|
307
|
+
const fileSent = await staticRouter.handleRequest(req, res);
|
|
308
|
+
|
|
309
|
+
// если маршрутизатор вернул false, файл не был отправлен
|
|
310
|
+
if (!fileSent) {
|
|
311
|
+
// ручная отправка статуса 404 Not Found
|
|
312
|
+
res.writeHead(404, {'Content-Type': 'text/plain; charset=utf-8'});
|
|
313
|
+
res.write('404 Not Found');
|
|
314
|
+
res.end();
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
Маршрутизатор обрабатывает исключительно запросы с методами `GET` и `HEAD`.
|
|
320
|
+
При получении запроса с любым другим методом, обработка прерывается и метод
|
|
321
|
+
возвращает `false`. В случае `HEAD` запроса маршрутизатор корректно вычисляет
|
|
322
|
+
размер файла и отправляет соответствующие заголовки, пропуская отправку
|
|
323
|
+
тела ответа.
|
|
324
|
+
|
|
77
325
|
## Тесты
|
|
78
326
|
|
|
79
327
|
```bash
|