slower 2.1.8 → 2.2.0

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/README.md ADDED
@@ -0,0 +1,386 @@
1
+ # Slower
2
+
3
+ A minimal, dependency-free HTTP framework for Node.js.
4
+ Slower provides a small subset of Express.js–style routing and middleware with a simpler internal model.
5
+
6
+ Don't want to download 200 megabytes of modules from Express? Then try Slower 😉.
7
+
8
+ It's just a couple files and a few KB.
9
+
10
+ This might not be the most robust library, but it is small and lightweight, and it does the job. (Just don't use this in production, please.)
11
+
12
+ ---
13
+
14
+ #### A word about this:
15
+
16
+ Sorry for the (really) poorly written documentation, I need time to re-write it.
17
+ If you would like to help, just email me (email on package.json), and I will be more than happy to talk to you.
18
+
19
+ ---
20
+
21
+ ## Features
22
+
23
+ - No external dependencies
24
+ - Express-like API and functionality (`get`, `post`, `use`, `all`, etc.)
25
+ - Static file serving
26
+ - Sub-routers and route grouping
27
+ - SMALL!
28
+
29
+ ---
30
+
31
+ ## Installation
32
+
33
+ ```sh
34
+ npm install slower
35
+ ```
36
+
37
+ (Or just download this source code and put on a 'lib/slower' folder, it's what I usually do).
38
+
39
+ ---
40
+
41
+ ## Basic Usage
42
+
43
+ Check `examples/` for some samples on how to use this library.
44
+
45
+ The basic is: if works on Express, probably works here too (apart from some minor modifications, there is very little difference in the two public APIs)
46
+
47
+ ```js
48
+ const slower = require('slower');
49
+
50
+ const app = slower();
51
+
52
+ app.get('/', (req, res) => {
53
+ res.send('Hello world');
54
+ });
55
+
56
+ app.listen(3000);
57
+ ```
58
+
59
+ ### Template URLs:
60
+
61
+ The same basic rules that express.js applied are available here, since JS internalized the URLPattern API, and this library uses the pattern matching from URLPattern.
62
+
63
+ If you have questions, check this guide on URLPattern:
64
+ [https://developer.mozilla.org/en-US/docs/Web/API/URL_Pattern_API#pattern_syntax](https://developer.mozilla.org/en-US/docs/Web/API/URL_Pattern_API#pattern_syntax)
65
+
66
+ ### Routing
67
+
68
+ #### HTTP Methods
69
+
70
+ All standard HTTP verbs are supported:
71
+
72
+ ```js
73
+ app.get('/users', handler);
74
+ app.post('/users', handler);
75
+ app.put('/users/:id', handler);
76
+ app.delete('/users/:id', handler);
77
+ ```
78
+
79
+ #### Route Parameters
80
+
81
+ Routes use URLPattern internally and support named parameters:
82
+
83
+ ```js
84
+ app.get('/users/:id', (req, res) => {
85
+ res.json({ id: req.params.id });
86
+ });
87
+ ```
88
+
89
+ #### Multiple Handlers / Middleware
90
+
91
+ Handlers are executed sequentially and receive next():
92
+
93
+ ```js
94
+ app.get('/secure', authMiddleware, (req, res) => {
95
+ res.send('Authorized');
96
+ });
97
+ ```
98
+
99
+ #### Middleware
100
+
101
+ `use()`
102
+ Attach middleware for all HTTP methods:
103
+
104
+ ```js
105
+ app.use((req, res, next) => {
106
+ console.log(req.method, req.url);
107
+ next();
108
+ });
109
+ ```
110
+
111
+ `all()`
112
+
113
+ Apply handlers to specific paths or methods:
114
+
115
+ ```js
116
+ app.all('/health', (req, res) => {
117
+ res.send('OK');
118
+ });
119
+ ```
120
+
121
+ ### Request Object (req)
122
+
123
+ - req.params – route parameters
124
+ - req.query – parsed query string
125
+ - req.body
126
+ - buffer
127
+ - text()
128
+ - json()
129
+ - req.ip – remote IP address
130
+ - req.session
131
+ - host
132
+ - port
133
+ - rhost - remote connected host
134
+ - rport - remote connected port
135
+
136
+ Example:
137
+
138
+ ```js
139
+ app.post('/data', async (req, res) => {
140
+ const body = req.body.json();
141
+ res.json(body);
142
+ });
143
+ ```
144
+
145
+ ### Response Object (res)
146
+
147
+ - res.status(code)
148
+ - res.set(name, value)
149
+ - res.get(name)
150
+ - res.type(mimeOrExtension)
151
+ - res.send(body)
152
+ - res.json(object)
153
+ - res.file(path)
154
+ - res.render(templatePath, data)
155
+ - res.redirect(path)
156
+
157
+ Example:
158
+
159
+ ```js
160
+ res.status(201).json({ success: true });
161
+ ```
162
+
163
+ ### Static Files
164
+
165
+ Serve a directory of static files:
166
+
167
+ ```js
168
+ app.static('public');
169
+ ```
170
+
171
+ Or mount it at a specific path:
172
+
173
+ ```js
174
+ app.static('public', '/assets');
175
+ ```
176
+
177
+ HTML files are also served without the .html extension automatically.
178
+
179
+ ### Routers
180
+
181
+ #### Sub-Router
182
+
183
+ ```js
184
+ const router = slower.Router();
185
+
186
+ router.get('/users', handler);
187
+ router.post('/users', handler);
188
+
189
+ app.useRouter(router, '/api');
190
+ ```
191
+
192
+ #### Route Groups
193
+
194
+ ```js
195
+ app.route('/users').get(listUsers).post(createUser);
196
+ ```
197
+
198
+ ### HTTPS Support
199
+
200
+ Pass options directly to the underlying server:
201
+
202
+ ```js
203
+ const fs = require('fs');
204
+
205
+ const app = slower({
206
+ https: true,
207
+ key: fs.readFileSync('key.pem'),
208
+ cert: fs.readFileSync('cert.pem'),
209
+ });
210
+ ```
211
+
212
+ ### Fallback page
213
+
214
+ If no route matches, it returns a default 404 HTML response:
215
+
216
+ `Cannot {METHOD} /{path}`
217
+
218
+ Custom fallback behavior can be implemented using middleware.
219
+
220
+ ## Method documentation
221
+
222
+ ### Main Export
223
+
224
+ ```js
225
+ slower(options?: object) : <SlowerRouter>
226
+ ```
227
+
228
+ Creates a new Slower application instance.
229
+
230
+ - `options` are forwarded to `http.createServer` or `https.createServer`
231
+ - If `options.https === true`, HTTPS is used
232
+
233
+ ---
234
+
235
+ ### Class: \<SlowerRouter\>
236
+
237
+ Main application router and HTTP server wrapper.
238
+
239
+ #### Server Control
240
+
241
+ Start the HTTP/HTTPS server:
242
+
243
+ ```
244
+ <SlowerRouter>.listen(...args) : http.Server
245
+ ```
246
+
247
+ Stops the server:
248
+
249
+ ```
250
+ <SlowerRouter>.close(callback?: function) : void
251
+ ```
252
+
253
+ #### Routing Methods
254
+
255
+ The following methods share the same signature:
256
+
257
+ ```
258
+ <SlowerRouter>.get(path, ...handlers) : <SlowerRouter>
259
+
260
+ <SlowerRouter>.post(path, ...handlers) : <SlowerRouter>
261
+
262
+ <SlowerRouter>.put(path, ...handlers) : <SlowerRouter>
263
+
264
+ <SlowerRouter>.delete(path, ...handlers) : <SlowerRouter>
265
+
266
+ <SlowerRouter>.patch(path, ...handlers) : <SlowerRouter>
267
+
268
+ <SlowerRouter>.options(path, ...handlers) : <SlowerRouter>
269
+
270
+ <SlowerRouter>.head(path, ...handlers) : <SlowerRouter>
271
+
272
+ <SlowerRouter>.trace(path, ...handlers) : <SlowerRouter>
273
+
274
+ <SlowerRouter>.connect(path, ...handlers) : <SlowerRouter>
275
+ ```
276
+
277
+ - `path` may be a string, RegExp, or middleware function
278
+ - Handlers are called sequentially with `(req, res, next)`
279
+
280
+ #### Middleware
281
+
282
+ ```
283
+ <SlowerRouter>.use(...handlers: function\[\]) : <SlowerRouter>
284
+ ```
285
+
286
+ Registers global middleware for all HTTP methods.
287
+
288
+ ```
289
+ <SlowerRouter>.all(pathOrMethod, ...handlers) : <SlowerRouter>
290
+ ```
291
+
292
+ Registers handlers across multiple HTTP methods depending on the first argument.
293
+
294
+ #### Static Files
295
+
296
+ ```
297
+ <SlowerRouter>.static(directoryPath: string, mountPath?: string) : <SlowerRouter>
298
+ ```
299
+
300
+ Serves static files from a directory.
301
+
302
+ #### Routers
303
+
304
+ ```
305
+ <SlowerRouter>.useRouter(router: <SlowerSubRouter>, mountPath?: string) : <SlowerRouter>
306
+ ```
307
+
308
+ Mounts a sub-router at the specified path.
309
+
310
+ ```
311
+ <SlowerRouter>.route(path: string | RegExp) : <SlowerSubRouter>
312
+ ```
313
+
314
+ Creates a route group bound to a single path.
315
+
316
+ ### Class: <\SlowerSubRouter\>
317
+
318
+ Router container used for grouping routes before mounting.
319
+
320
+ ```
321
+ <SlowerSubRouter>.get(path, ...handlers) : <SlowerSubRouter>
322
+
323
+ <SlowerSubRouter>.post(path, ...handlers) : <SlowerSubRouter>
324
+
325
+ <SlowerSubRouter>.put(path, ...handlers) : <SlowerSubRouter>
326
+
327
+ <SlowerSubRouter>.delete(path, ...handlers) : <SlowerSubRouter>
328
+
329
+ <SlowerSubRouter>.patch(path, ...handlers) : <SlowerSubRouter>
330
+
331
+ <SlowerSubRouter>.use(...handlers) : <SlowerSubRouter>
332
+
333
+ <SlowerSubRouter>.all(pathOrMethod, ...handlers) : <SlowerSubRouter>
334
+
335
+ <SlowerSubRouter>.static(directoryPath, mountPath?) : <SlowerSubRouter>
336
+
337
+ <SlowerSubRouter>.route(path) : <SlowerMicroRouter>
338
+ ```
339
+
340
+ ### Request Extensions
341
+
342
+ ```
343
+ req.get(headerName: string) : string | undefined
344
+
345
+ req.body.buffer : Buffer
346
+
347
+ req.body.text() : string
348
+
349
+ req.body.json() : any
350
+
351
+ req.query : object
352
+
353
+ req.params : object | undefined
354
+
355
+ req.ip : string
356
+
357
+ req.session : object
358
+ ```
359
+
360
+ ### Response Extensions
361
+
362
+ ```
363
+ res.status(code: number) : res
364
+
365
+ res.set(name: string | object, value?: string) : res
366
+
367
+ res.get(name: string) : string | undefined
368
+
369
+ res.type(type: string) : res
370
+
371
+ res.send(body: string | Buffer) : void
372
+
373
+ res.json(data: any) : void
374
+
375
+ res.file(path: string) : void
376
+
377
+ res.render(templatePath: string, data?: object) : void
378
+
379
+ res.redirect(path: string) : void
380
+ ```
381
+
382
+ **License:** MIT
383
+
384
+ ## License
385
+
386
+ This is MIT-licensed. Just use it.
@@ -0,0 +1,45 @@
1
+ const middlewares = require('./third-party-middlewares');
2
+ const Slower = require('../index');
3
+
4
+ const app = Slower();
5
+
6
+ app.use(middlewares.requestTime); // Set timestamp: req.moment
7
+ app.use(middlewares.hashRequest); // Set request ID: req.id
8
+ app.use(middlewares.triageRequest); // Set request validity: req.invalid
9
+ app.use(middlewares.consoleLogger); // Logs to console (according to req.invalid)
10
+ app.use(middlewares.blockMethods); // Blocks request (according to req.invalid)
11
+ app.use(middlewares.nocache); // Prevents cache creation
12
+
13
+ app.get(
14
+ '/rea',
15
+ (req, res, next) => console.log('redirected') || next(),
16
+ (req, res) => res.status(301).redirect('/admin')
17
+ );
18
+
19
+ app.get('/admin', (req, res) =>
20
+ res
21
+ .status(200)
22
+ .send(
23
+ `<h2>${JSON.stringify(req.params)}<br/>${JSON.stringify(req.query)}</h2>`
24
+ )
25
+ );
26
+
27
+ app.get('/admins/:id', (req, res) => {
28
+ res.status(200).send(
29
+ `<h2>${JSON.stringify(req.params)}<br/>${JSON.stringify(req.query)}<br/></h2>`
30
+ );
31
+ });
32
+
33
+ app.get('/headers', (req, res) => res.status(200).json(req.headers));
34
+
35
+ app.get('/docs/:docname', (req, res) =>
36
+ res.file(`./${req.params.docname}.html`)
37
+ );
38
+
39
+ app.all((req, res) => {
40
+ res.status(404).send('<h2>Error 404 - Page Not Found<h2/>');
41
+ });
42
+
43
+ app.listen(80, '127.0.0.1', () =>
44
+ console.log('Listening on http://127.0.0.1:80/')
45
+ );
@@ -0,0 +1,29 @@
1
+ const Slower = require('../index');
2
+
3
+ (async () => {
4
+ const appr = Slower.Router();
5
+ appr.get('/a', (req, res) => res.status(200).end('a'));
6
+
7
+ const appf = Slower.Router();
8
+ appf.get('/c', (req, res) => res.status(200).end('c'));
9
+ appf.get('/x/:id', (req, res) => res.status(200).end('x=' + req.params.id));
10
+ appf.all('/d', (req, res) => res.status(200).end('d'));
11
+
12
+ const app = Slower();
13
+ app.useRouter(appr);
14
+ app.useRouter(appf, '/api');
15
+ app.get('/b', (req, res) => res.status(200).end('b'));
16
+ app.all((req, res) => res.status(404).end());
17
+ app.listen('8000', () => console.log('app started at 8000'));
18
+ })();
19
+
20
+ /**
21
+ * TESTING ENDPOINTS:
22
+ *
23
+ * /a Should print 'a'
24
+ * /b Should print 'b'
25
+ * /api/c Should print 'c'
26
+ * /api/d Should print 'd'
27
+ * /api/x/:id Should print 'x={ID}'
28
+ *
29
+ */
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Server middleware to force the client to NOT save any data as cache.
3
+ * @info This is very useful for development purposes, as browser cache for
4
+ * redirections and scripts is very annoying
5
+ * @param {http.IncomingMessage} req
6
+ * @param {http.ServerResponse} res
7
+ * @param {Function} next
8
+ * @return {undefined}
9
+ */
10
+ function nocache (req, res, next) {
11
+ res.set('Cache-Control', 'no-cache');
12
+ next();
13
+ }
14
+
15
+ // Add a timestamp to the request
16
+ function requestTime (req, res, next) {
17
+ req.moment = Date.now();
18
+ next();
19
+ }
20
+
21
+ // Add a hash to the request
22
+ function murmurHash (key, seed = 0) {
23
+ const C = { M:0x5bd1e995, R:0x18 };
24
+ const im32M = (a) => {
25
+ const c = { o: v => v & 0xffff, i: v => (v >>> 16) & 0xffff };
26
+ return c.o(a)*59797 + (((c.i(a)*59797 + c.o(a)*23505) << 16) >>> 0);
27
+ };
28
+ let len = key.length, h = seed ^ len, i = 0, k = 0;
29
+ while (len >= 4) {
30
+ k = key[i++] | (key[i++] << 8) | (key[i++] << 16) | (key[i++] << 24);
31
+ k = im32M(k);
32
+ k ^= k >>> C.R;
33
+ k = im32M(k);
34
+ h = im32M(h) ^ k;
35
+ len -= 4;
36
+ };
37
+ switch (len) {
38
+ case 3: h ^= key[i + 2] << 16;
39
+ case 2: h ^= key[i + 1] << 8;
40
+ case 1: h ^= key[i];
41
+ h = im32M(h);
42
+ };
43
+ h ^= h >>> 13;
44
+ h = im32M(h);
45
+ h ^= h >>> 15;
46
+ return h >>> 0;
47
+ }
48
+ function hashRequest (req, res, next) {
49
+ req.hash = (murmurHash(Date.now().toString() + JSON.stringify(req.session) + req.url)).toString().padStart(10, '0');
50
+ next();
51
+ }
52
+
53
+ // Set an 'invalid' property if the request is of a proper method
54
+ // This is used in the 'blockMethods' and 'consoleLogger' middlewares
55
+ function triageRequest (req, res, next) {
56
+ const allowedMethods = ['GET','POST','PUT','DELETE'];
57
+ if (!allowedMethods.includes(req.method)) req.invalid = true;
58
+ next();
59
+ }
60
+
61
+ // Logs request to console
62
+ const padStringEnd = (str, maxLen) => str.padEnd(maxLen, ' ');
63
+ const colorizeMethod = (req) => (req.invalid ? '\x1b[31m' : '\x1b[32m') + req.method + '\x1b[0m';
64
+ function consoleLogger (req, res, next) {
65
+ console.log(
66
+ ' ' +
67
+ `Hash:${req.hash} ` +
68
+ `Time:${new Date(req.moment).toISOString()} ` +
69
+ `${padStringEnd(req.session.rhost + ':' + req.session.rport + '->' + req.session.host + ':' + req.session.port, 32)} `+
70
+ `${padStringEnd(colorizeMethod(req), 20)} ${req.url}`
71
+ );
72
+ next();
73
+ }
74
+
75
+ // Blocks all requests that are not GET, POST, PUT, or DELETE ones
76
+ function blockMethods (req, res, next) {
77
+ if (req.invalid) res.status(405).end();
78
+ else next();
79
+ }
80
+
81
+ module.exports = {
82
+ nocache,
83
+ requestTime,
84
+ hashRequest,
85
+ consoleLogger,
86
+ blockMethods,
87
+ triageRequest
88
+ }
package/package.json CHANGED
@@ -1,16 +1,12 @@
1
1
  {
2
- "dependencies": {
3
- "path-to-regexp": "^6.2.2"
4
- },
5
- "name": "slower",
6
- "version": "2.1.8",
7
- "main": "index.js",
8
- "devDependencies": {},
9
- "scripts": {
10
- "test": "echo \"Error: no test specified\" && exit 1"
11
- },
12
- "keywords": [],
13
- "author": "Tomás Luchesi <no.mad.devtech@gmail.com>",
14
- "license": "MIT",
15
- "description": "A package for simple HTTP server routing."
16
- }
2
+ "name": "slower",
3
+ "version": "2.2.0",
4
+ "main": "index.js",
5
+ "scripts": {
6
+ "test": "echo \"Error: no test specified\" && exit 1"
7
+ },
8
+ "keywords": [],
9
+ "author": "Tomás Luchesi <no.mad.devtech@gmail.com>",
10
+ "license": "MIT",
11
+ "description": "A package for simple HTTP server routing."
12
+ }
package/src/decorators.js CHANGED
@@ -11,7 +11,7 @@ const utils = require('./utils');
11
11
  * @exposes .json()
12
12
  * @exposes .file()
13
13
  */
14
- function setupResponse(response) {
14
+ function setupResponse(response, request) {
15
15
  /**
16
16
  * Sets the response status code
17
17
  * @chainable
@@ -67,11 +67,7 @@ function setupResponse(response) {
67
67
  * @info And if no type is specified, binary type is used (application/octet-stream)
68
68
  */
69
69
  response.type = function (mime) {
70
- let mimetype =
71
- MIME_TABLE[mime] ||
72
- mime ||
73
- MIME_TABLE[extension] ||
74
- MIME_TABLE['default'];
70
+ let mimetype = MIME_TABLE[mime] || mime || MIME_TABLE['default'];
75
71
  response.setHeader('Content-type', mimetype);
76
72
  return response;
77
73
  };
@@ -193,7 +189,7 @@ function setupRequest(request) {
193
189
  * @property
194
190
  * Holds the request body data as a buffer
195
191
  */
196
- return new Promise(resolve => {
192
+ return new Promise((resolve, reject) => {
197
193
  // Set classical socket locals
198
194
  request.session = {
199
195
  port: request.socket.localPort,