@pirxpilot/router 1.0.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/HISTORY.md +206 -0
- package/LICENSE +23 -0
- package/README.md +17 -0
- package/SECURITY.md +24 -0
- package/index.js +717 -0
- package/lib/layer.js +225 -0
- package/lib/route.js +235 -0
- package/package.json +39 -0
package/lib/layer.js
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* router
|
|
3
|
+
* Copyright(c) 2013 Roman Shtylman
|
|
4
|
+
* Copyright(c) 2014-2022 Douglas Christopher Wilson
|
|
5
|
+
* MIT Licensed
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
'use strict'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Module dependencies.
|
|
12
|
+
* @private
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const pathRegexp = require('path-to-regexp')
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Module variables.
|
|
19
|
+
* @private
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
const TRAILING_SLASH_REGEXP = /\/+$/
|
|
23
|
+
const MATCHING_GROUP_REGEXP = /\((?:\?<(.*?)>)?(?!\?)/g
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Expose `Layer`.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
module.exports = Layer
|
|
30
|
+
|
|
31
|
+
function Layer (path, options, fn) {
|
|
32
|
+
if (!(this instanceof Layer)) {
|
|
33
|
+
return new Layer(path, options, fn)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const opts = options || {}
|
|
37
|
+
|
|
38
|
+
this.handle = fn
|
|
39
|
+
this.keys = []
|
|
40
|
+
this.name = fn.name || '<anonymous>'
|
|
41
|
+
this.params = undefined
|
|
42
|
+
this.path = undefined
|
|
43
|
+
this.slash = path === '/' && opts.end === false
|
|
44
|
+
this.matchers = Array.isArray(path) ? path.map(p => matcher(p, opts)) : [matcher(path, opts)]
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function matcher (path, { sensitive, end, strict }) {
|
|
48
|
+
return path instanceof RegExp
|
|
49
|
+
? createRegexMatcher(path)
|
|
50
|
+
: pathRegexp.match((strict ? path : loosen(path)), {
|
|
51
|
+
sensitive,
|
|
52
|
+
end,
|
|
53
|
+
trailing: !strict,
|
|
54
|
+
decode: decodeParam
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Handle the error for the layer.
|
|
60
|
+
*
|
|
61
|
+
* @param {Error} error
|
|
62
|
+
* @param {Request} req
|
|
63
|
+
* @param {Response} res
|
|
64
|
+
* @param {function} next
|
|
65
|
+
* @api private
|
|
66
|
+
*/
|
|
67
|
+
|
|
68
|
+
Layer.prototype.handleError = function handleError (error, req, res, next) {
|
|
69
|
+
const fn = this.handle
|
|
70
|
+
|
|
71
|
+
if (fn.length !== 4) {
|
|
72
|
+
// not a standard error handler
|
|
73
|
+
return next(error)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
// invoke function
|
|
78
|
+
const ret = fn(error, req, res, next)
|
|
79
|
+
|
|
80
|
+
// wait for returned promise
|
|
81
|
+
if (ret instanceof Promise) {
|
|
82
|
+
ret.catch((error = new Error('Rejected promise')) => next(error))
|
|
83
|
+
}
|
|
84
|
+
} catch (err) {
|
|
85
|
+
next(err)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Handle the request for the layer.
|
|
91
|
+
*
|
|
92
|
+
* @param {Request} req
|
|
93
|
+
* @param {Response} res
|
|
94
|
+
* @param {function} next
|
|
95
|
+
* @api private
|
|
96
|
+
*/
|
|
97
|
+
|
|
98
|
+
Layer.prototype.handleRequest = function handleRequest (req, res, next) {
|
|
99
|
+
const fn = this.handle
|
|
100
|
+
|
|
101
|
+
if (fn.length > 3) {
|
|
102
|
+
// not a standard request handler
|
|
103
|
+
return next()
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
// invoke function
|
|
108
|
+
const ret = fn(req, res, next)
|
|
109
|
+
|
|
110
|
+
// wait for returned promise
|
|
111
|
+
if (ret instanceof Promise) {
|
|
112
|
+
ret.catch((error = new Error('Rejected promise')) => next(error))
|
|
113
|
+
}
|
|
114
|
+
} catch (err) {
|
|
115
|
+
next(err)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Check if this route matches `path`, if so
|
|
121
|
+
* populate `.params`.
|
|
122
|
+
*
|
|
123
|
+
* @param {String} path
|
|
124
|
+
* @return {Boolean}
|
|
125
|
+
* @api private
|
|
126
|
+
*/
|
|
127
|
+
|
|
128
|
+
Layer.prototype.match = function match (path) {
|
|
129
|
+
if (path != null) {
|
|
130
|
+
// fast path non-ending match for / (any path matches)
|
|
131
|
+
if (this.slash) {
|
|
132
|
+
this.params = {}
|
|
133
|
+
this.path = ''
|
|
134
|
+
return true
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
for (const matcher of this.matchers) {
|
|
138
|
+
const matched = matcher(path)
|
|
139
|
+
if (matched) {
|
|
140
|
+
// store values
|
|
141
|
+
this.params = matched.params
|
|
142
|
+
this.path = matched.path
|
|
143
|
+
this.keys = Object.keys(matched.params)
|
|
144
|
+
return true
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
this.params = undefined
|
|
150
|
+
this.path = undefined
|
|
151
|
+
return false
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function createRegexMatcher (path) {
|
|
155
|
+
const keys = []
|
|
156
|
+
let name = 0
|
|
157
|
+
|
|
158
|
+
for (const m of path.source.matchAll(MATCHING_GROUP_REGEXP)) {
|
|
159
|
+
keys.push({
|
|
160
|
+
name: m[1] || name++,
|
|
161
|
+
offset: m.index
|
|
162
|
+
})
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return function regexpMatcher (p) {
|
|
166
|
+
const match = path.exec(p)
|
|
167
|
+
if (!match) {
|
|
168
|
+
return false
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const params = {}
|
|
172
|
+
for (let i = 1; i < match.length; i++) {
|
|
173
|
+
const key = keys[i - 1]
|
|
174
|
+
const prop = key.name
|
|
175
|
+
const val = decodeParam(match[i])
|
|
176
|
+
|
|
177
|
+
if (val !== undefined) {
|
|
178
|
+
params[prop] = val
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
params,
|
|
184
|
+
path: match[0]
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Decode param value.
|
|
191
|
+
*
|
|
192
|
+
* @param {string} val
|
|
193
|
+
* @return {string}
|
|
194
|
+
* @private
|
|
195
|
+
*/
|
|
196
|
+
|
|
197
|
+
function decodeParam (val) {
|
|
198
|
+
if (typeof val !== 'string' || val.length === 0) {
|
|
199
|
+
return val
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
try {
|
|
203
|
+
return decodeURIComponent(val)
|
|
204
|
+
} catch (err) {
|
|
205
|
+
if (err instanceof URIError) {
|
|
206
|
+
err.message = 'Failed to decode param \'' + val + '\''
|
|
207
|
+
err.status = 400
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
throw err
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Loosens the given path for path-to-regexp matching.
|
|
216
|
+
*/
|
|
217
|
+
function loosen (path) {
|
|
218
|
+
if (path instanceof RegExp || path === '/') {
|
|
219
|
+
return path
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return Array.isArray(path)
|
|
223
|
+
? path.map(function (p) { return loosen(p) })
|
|
224
|
+
: String(path).replace(TRAILING_SLASH_REGEXP, '')
|
|
225
|
+
}
|
package/lib/route.js
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* router
|
|
3
|
+
* Copyright(c) 2013 Roman Shtylman
|
|
4
|
+
* Copyright(c) 2014-2022 Douglas Christopher Wilson
|
|
5
|
+
* MIT Licensed
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
'use strict'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Module dependencies.
|
|
12
|
+
* @private
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const Layer = require('./layer')
|
|
16
|
+
const methods = require('methods')
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Module variables.
|
|
20
|
+
* @private
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Expose `Route`.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
module.exports = Route
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Initialize `Route` with the given `path`,
|
|
31
|
+
*
|
|
32
|
+
* @param {String} path
|
|
33
|
+
* @api private
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
function Route (path) {
|
|
37
|
+
this.path = path
|
|
38
|
+
this.stack = []
|
|
39
|
+
|
|
40
|
+
// route handlers for various http methods
|
|
41
|
+
this.methods = Object.create(null)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @private
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
Route.prototype._handlesMethod = function _handlesMethod (method) {
|
|
49
|
+
if (this.methods._all) {
|
|
50
|
+
return true
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// normalize name
|
|
54
|
+
let name = typeof method === 'string'
|
|
55
|
+
? method.toLowerCase()
|
|
56
|
+
: method
|
|
57
|
+
|
|
58
|
+
if (name === 'head' && !this.methods.head) {
|
|
59
|
+
name = 'get'
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return Boolean(this.methods[name])
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* @return {array} supported HTTP methods
|
|
67
|
+
* @private
|
|
68
|
+
*/
|
|
69
|
+
|
|
70
|
+
Route.prototype._methods = function _methods () {
|
|
71
|
+
const methods = Object.keys(this.methods)
|
|
72
|
+
|
|
73
|
+
// append automatic head
|
|
74
|
+
if (this.methods.get && !this.methods.head) {
|
|
75
|
+
methods.push('head')
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
for (let i = 0; i < methods.length; i++) {
|
|
79
|
+
// make upper case
|
|
80
|
+
methods[i] = methods[i].toUpperCase()
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return methods
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* dispatch req, res into this route
|
|
88
|
+
*
|
|
89
|
+
* @private
|
|
90
|
+
*/
|
|
91
|
+
|
|
92
|
+
Route.prototype.dispatch = function dispatch (req, res, done) {
|
|
93
|
+
let idx = 0
|
|
94
|
+
const stack = this.stack
|
|
95
|
+
let sync = 0
|
|
96
|
+
|
|
97
|
+
if (stack.length === 0) {
|
|
98
|
+
return done()
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
let method = typeof req.method === 'string'
|
|
102
|
+
? req.method.toLowerCase()
|
|
103
|
+
: req.method
|
|
104
|
+
|
|
105
|
+
if (method === 'head' && !this.methods.head) {
|
|
106
|
+
method = 'get'
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
req.route = this
|
|
110
|
+
|
|
111
|
+
next()
|
|
112
|
+
|
|
113
|
+
function next (err) {
|
|
114
|
+
// signal to exit route
|
|
115
|
+
if (err && err === 'route') {
|
|
116
|
+
return done()
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// signal to exit router
|
|
120
|
+
if (err && err === 'router') {
|
|
121
|
+
return done(err)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// no more matching layers
|
|
125
|
+
if (idx >= stack.length) {
|
|
126
|
+
return done(err)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// max sync stack
|
|
130
|
+
if (++sync > 100) {
|
|
131
|
+
return setImmediate(next, err)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
let layer
|
|
135
|
+
let match
|
|
136
|
+
|
|
137
|
+
// find next matching layer
|
|
138
|
+
while (idx < stack.length) {
|
|
139
|
+
layer = stack[idx++]
|
|
140
|
+
match = !layer.method || layer.method === method
|
|
141
|
+
if (match) {
|
|
142
|
+
break
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// no match
|
|
147
|
+
if (match !== true) {
|
|
148
|
+
return done(err)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (err) {
|
|
152
|
+
layer.handleError(err, req, res, next)
|
|
153
|
+
} else {
|
|
154
|
+
layer.handleRequest(req, res, next)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
sync = 0
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Add a handler for all HTTP verbs to this route.
|
|
163
|
+
*
|
|
164
|
+
* Behaves just like middleware and can respond or call `next`
|
|
165
|
+
* to continue processing.
|
|
166
|
+
*
|
|
167
|
+
* You can use multiple `.all` call to add multiple handlers.
|
|
168
|
+
*
|
|
169
|
+
* function check_something(req, res, next){
|
|
170
|
+
* next()
|
|
171
|
+
* }
|
|
172
|
+
*
|
|
173
|
+
* function validate_user(req, res, next){
|
|
174
|
+
* next()
|
|
175
|
+
* }
|
|
176
|
+
*
|
|
177
|
+
* route
|
|
178
|
+
* .all(validate_user)
|
|
179
|
+
* .all(check_something)
|
|
180
|
+
* .get(function(req, res, next){
|
|
181
|
+
* res.send('hello world')
|
|
182
|
+
* })
|
|
183
|
+
*
|
|
184
|
+
* @param {array|function} handler
|
|
185
|
+
* @return {Route} for chaining
|
|
186
|
+
* @api public
|
|
187
|
+
*/
|
|
188
|
+
|
|
189
|
+
Route.prototype.all = function all (...args) {
|
|
190
|
+
const callbacks = args.flat(Infinity)
|
|
191
|
+
|
|
192
|
+
if (callbacks.length === 0) {
|
|
193
|
+
throw new TypeError('argument handler is required')
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
for (const fn of callbacks) {
|
|
197
|
+
if (typeof fn !== 'function') {
|
|
198
|
+
throw new TypeError('argument handler must be a function')
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const layer = Layer('/', {}, fn)
|
|
202
|
+
layer.method = undefined
|
|
203
|
+
|
|
204
|
+
this.methods._all = true
|
|
205
|
+
this.stack.push(layer)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return this
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
methods.forEach(function (method) {
|
|
212
|
+
Route.prototype[method] = function (...args) {
|
|
213
|
+
const callbacks = args.flat(Infinity)
|
|
214
|
+
|
|
215
|
+
if (callbacks.length === 0) {
|
|
216
|
+
throw new TypeError('argument handler is required')
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
for (let i = 0; i < callbacks.length; i++) {
|
|
220
|
+
const fn = callbacks[i]
|
|
221
|
+
|
|
222
|
+
if (typeof fn !== 'function') {
|
|
223
|
+
throw new TypeError('argument handler must be a function')
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const layer = Layer('/', {}, fn)
|
|
227
|
+
layer.method = method
|
|
228
|
+
|
|
229
|
+
this.methods[method] = true
|
|
230
|
+
this.stack.push(layer)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return this
|
|
234
|
+
}
|
|
235
|
+
})
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pirxpilot/router",
|
|
3
|
+
"description": "Simple middleware-style router",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"author": "Douglas Christopher Wilson <doug@somethingdoug.com>",
|
|
6
|
+
"contributors": [
|
|
7
|
+
"Blake Embrey <hello@blakeembrey.com>"
|
|
8
|
+
],
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/pirxpilot/router.git"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"methods": "~1",
|
|
16
|
+
"parseurl": "~1",
|
|
17
|
+
"path-to-regexp": "~8"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"finalhandler": "~1",
|
|
21
|
+
"run-series": "~1",
|
|
22
|
+
"standard": "~17",
|
|
23
|
+
"supertest": "~7"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"lib/",
|
|
27
|
+
"LICENSE",
|
|
28
|
+
"HISTORY.md",
|
|
29
|
+
"README.md",
|
|
30
|
+
"SECURITY.md",
|
|
31
|
+
"index.js"
|
|
32
|
+
],
|
|
33
|
+
"engines": {
|
|
34
|
+
"node": ">= 0.10"
|
|
35
|
+
},
|
|
36
|
+
"scripts": {
|
|
37
|
+
"test": "make check"
|
|
38
|
+
}
|
|
39
|
+
}
|