├── .npmrc ├── README.md ├── package.json ├── .gitignore ├── hiddie.js └── test.js /.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.com/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hiddie 2 | 3 | friendly fork of [fastify/middie](https://github.com/fastify/middie) module 4 | 5 | > forked version = 4.1.0 -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hiddie", 3 | "version": "1.0.0", 4 | "description": "friendly fork of fastify/middie module", 5 | "main": "hiddie.js", 6 | "scripts": { 7 | "test": "standard && tap test.js", 8 | "coverage": "tap --cov --coverage-report=html test.js" 9 | }, 10 | "license": "MIT", 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/hepsiburada/hiddie" 14 | }, 15 | "devDependencies": { 16 | "pre-commit": "^1.2.2", 17 | "serve-static": "^1.12.4", 18 | "standard": "^14.0.2", 19 | "tap": "^12.6.5" 20 | }, 21 | "dependencies": { 22 | "path-to-regexp": "^4.0.0", 23 | "reusify": "^1.0.2" 24 | }, 25 | "greenkeeper": { 26 | "ignore": [ 27 | "tap" 28 | ] 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | # mac files 40 | .DS_Store 41 | 42 | # vim swap files 43 | *.swp 44 | 45 | # package lock 46 | package-lock.json 47 | 48 | .idea -------------------------------------------------------------------------------- /hiddie.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const reusify = require('reusify') 4 | const { pathToRegexp } = require('path-to-regexp') 5 | 6 | function hiddie (complete) { 7 | var middlewares = [] 8 | var pool = reusify(Holder) 9 | 10 | return { 11 | use, 12 | run 13 | } 14 | 15 | function use (url, f) { 16 | if (f === undefined) { 17 | f = url 18 | url = null 19 | } 20 | 21 | var regexp 22 | var keys = [] 23 | if (url) { 24 | regexp = pathToRegexp(sanitizePrefixUrl(url), keys, { 25 | end: false, 26 | strict: false 27 | }) 28 | } 29 | 30 | if (Array.isArray(f)) { 31 | for (var val of f) { 32 | middlewares.push({ 33 | regexp, 34 | keys, 35 | fn: val 36 | }) 37 | } 38 | } else { 39 | middlewares.push({ 40 | regexp, 41 | keys, 42 | fn: f 43 | }) 44 | } 45 | 46 | return this 47 | } 48 | 49 | function run (req, res, ctx) { 50 | if (!middlewares.length) { 51 | complete(null, req, res, ctx) 52 | return 53 | } 54 | 55 | req.originalUrl = req.url 56 | 57 | var holder = pool.get() 58 | holder.req = req 59 | holder.res = res 60 | holder.url = sanitizeUrl(req.url) 61 | holder.context = ctx 62 | holder.done() 63 | } 64 | 65 | function Holder () { 66 | this.next = null 67 | this.req = null 68 | this.res = null 69 | this.url = null 70 | this.context = null 71 | this.i = 0 72 | 73 | var that = this 74 | this.done = function (err) { 75 | var req = that.req 76 | var res = that.res 77 | var url = that.url 78 | var context = that.context 79 | var i = that.i++ 80 | 81 | req.url = req.originalUrl 82 | 83 | if (res.finished === true) { 84 | that.req = null 85 | that.res = null 86 | that.context = null 87 | that.i = 0 88 | pool.release(that) 89 | return 90 | } 91 | 92 | if (err || middlewares.length === i) { 93 | complete(err, req, res, context) 94 | that.req = null 95 | that.res = null 96 | that.context = null 97 | that.i = 0 98 | pool.release(that) 99 | } else { 100 | var middleware = middlewares[i] 101 | var fn = middleware.fn 102 | var regexp = middleware.regexp 103 | var keys = middleware.keys 104 | if (regexp) { 105 | var result = regexp.exec(url) 106 | if (result) { 107 | var params = {} 108 | 109 | for (var j = 1; j < result.length; j++) { 110 | var prop = keys[j - 1].name 111 | var val = decodeParam(result[j]) 112 | 113 | if (!!val || !Object.prototype.hasOwnProperty.call(params, prop)) { 114 | params[prop] = val 115 | } 116 | } 117 | 118 | req.url = req.url.replace(result[0], '') 119 | req.params = params 120 | if (req.url.startsWith('/') === false) { 121 | req.url = '/' + req.url 122 | } 123 | fn(req, res, that.done) 124 | } else { 125 | that.done() 126 | } 127 | } else { 128 | fn(req, res, that.done) 129 | } 130 | } 131 | } 132 | } 133 | } 134 | 135 | function decodeParam (val) { 136 | if (typeof val !== 'string' || val.length === 0) { 137 | return val 138 | } 139 | 140 | try { 141 | return decodeURIComponent(val) 142 | } catch (err) { 143 | if (err instanceof URIError) { 144 | err.message = 'Failed to decode param \'' + val + '\'' 145 | err.status = err.statusCode = 400 146 | } 147 | 148 | throw err 149 | } 150 | } 151 | 152 | function sanitizeUrl (url) { 153 | for (var i = 0, len = url.length; i < len; i++) { 154 | var charCode = url.charCodeAt(i) 155 | if (charCode === 63 || charCode === 35) { 156 | return url.slice(0, i) 157 | } 158 | } 159 | return url 160 | } 161 | 162 | function sanitizePrefixUrl (url) { 163 | if (url === '') return url 164 | if (url === '/') return '' 165 | if (url[url.length - 1] === '/') return url.slice(0, -1) 166 | return url 167 | } 168 | 169 | module.exports = hiddie 170 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const hiddie = require('./hiddie') 4 | const t = require('tap') 5 | const http = require('http') 6 | const serveStatic = require('serve-static') 7 | const test = t.test 8 | 9 | test('use no function', t => { 10 | t.plan(3) 11 | 12 | const instance = hiddie(function (err, a, b) { 13 | t.error(err) 14 | t.equal(a, req) 15 | t.equal(b, res) 16 | }) 17 | 18 | const req = { 19 | url: '/test' 20 | } 21 | const res = {} 22 | 23 | instance.run(req, res) 24 | }) 25 | 26 | test('use a function', t => { 27 | t.plan(5) 28 | 29 | const instance = hiddie(function (err, a, b) { 30 | t.error(err) 31 | t.equal(a, req) 32 | t.equal(b, res) 33 | }) 34 | const req = { 35 | url: '/test' 36 | } 37 | const res = {} 38 | 39 | t.equal(instance.use(function (req, res, next) { 40 | t.pass('function called') 41 | next() 42 | }), instance) 43 | 44 | instance.run(req, res) 45 | }) 46 | 47 | test('use two functions', t => { 48 | t.plan(5) 49 | 50 | const instance = hiddie(function (err, a, b) { 51 | t.error(err) 52 | t.equal(a, req) 53 | t.equal(b, res) 54 | }) 55 | const req = { 56 | url: '/test' 57 | } 58 | const res = {} 59 | var counter = 0 60 | 61 | instance.use(function (req, res, next) { 62 | t.is(counter++, 0, 'first function called') 63 | next() 64 | }).use(function (req, res, next) { 65 | t.is(counter++, 1, 'second function called') 66 | next() 67 | }) 68 | 69 | instance.run(req, res) 70 | }) 71 | 72 | test('stop the middleware chain if one errors', t => { 73 | t.plan(1) 74 | 75 | const instance = hiddie(function (err, a, b) { 76 | t.ok(err, 'error is forwarded') 77 | }) 78 | const req = { 79 | url: '/test' 80 | } 81 | const res = {} 82 | 83 | instance.use(function (req, res, next) { 84 | next(new Error('kaboom')) 85 | }).use(function (req, res, next) { 86 | t.fail('this should never be called') 87 | next() 88 | }) 89 | 90 | instance.run(req, res) 91 | }) 92 | 93 | test('run restricted by path', t => { 94 | t.plan(11) 95 | 96 | const instance = hiddie(function (err, a, b) { 97 | t.error(err) 98 | t.equal(a, req) 99 | t.equal('/test', req.url) 100 | t.equal(b, res) 101 | }) 102 | const req = { 103 | url: '/test' 104 | } 105 | const res = {} 106 | 107 | t.equal(instance.use(function (req, res, next) { 108 | t.ok('function called') 109 | next() 110 | }), instance) 111 | 112 | t.equal(instance.use('/test', function (req, res, next) { 113 | t.ok('function called') 114 | next() 115 | }), instance) 116 | 117 | t.equal(instance.use('/test', function (req, res, next) { 118 | t.equal('/', req.url) 119 | next() 120 | }), instance) 121 | 122 | t.equal(instance.use('/no-call', function (req, res, next) { 123 | t.fail('should not call this function') 124 | next() 125 | }), instance) 126 | 127 | instance.run(req, res) 128 | }) 129 | 130 | test('run restricted by path - prefix override', t => { 131 | t.plan(10) 132 | 133 | const instance = hiddie(function (err, a, b) { 134 | t.error(err) 135 | t.equal(a, req) 136 | t.equal('/test/other/one', req.url) 137 | t.equal(b, res) 138 | }) 139 | const req = { 140 | url: '/test/other/one' 141 | } 142 | const res = {} 143 | 144 | t.equal(instance.use(function (req, res, next) { 145 | t.ok('function called') 146 | next() 147 | }), instance) 148 | 149 | t.equal(instance.use('/test', function (req, res, next) { 150 | t.ok('function called') 151 | next() 152 | }), instance) 153 | 154 | t.equal(instance.use('/test', function (req, res, next) { 155 | t.equal('/other/one', req.url) 156 | next() 157 | }), instance) 158 | 159 | instance.run(req, res) 160 | }) 161 | 162 | test('run restricted by path - prefix override 2', t => { 163 | t.plan(10) 164 | 165 | const instance = hiddie(function (err, a, b) { 166 | t.error(err) 167 | t.equal(a, req) 168 | t.equal('/tasks-api/task', req.url) 169 | t.equal(b, res) 170 | }) 171 | const req = { 172 | url: '/tasks-api/task' 173 | } 174 | const res = {} 175 | 176 | t.equal(instance.use(function (req, res, next) { 177 | t.ok('function called') 178 | next() 179 | }), instance) 180 | 181 | t.equal(instance.use('/tasks-api', function (req, res, next) { 182 | t.ok('function called') 183 | next() 184 | }), instance) 185 | 186 | t.equal(instance.use('/tasks-api', function (req, res, next) { 187 | t.equal('/task', req.url) 188 | next() 189 | }), instance) 190 | 191 | instance.run(req, res) 192 | }) 193 | 194 | test('run restricted by array path', t => { 195 | t.plan(9) 196 | 197 | const instance = hiddie(function (err, a, b) { 198 | t.error(err) 199 | t.equal(a, req) 200 | t.equal('/test', req.url) 201 | t.equal(b, res) 202 | }) 203 | const req = { 204 | url: '/test' 205 | } 206 | const res = {} 207 | 208 | t.equal(instance.use(function (req, res, next) { 209 | t.ok('function called') 210 | next() 211 | }), instance) 212 | 213 | t.equal(instance.use(['/test', '/other-path'], function (req, res, next) { 214 | t.ok('function called') 215 | next() 216 | }), instance) 217 | 218 | t.equal(instance.use(['/no-call', 'other-path'], function (req, res, next) { 219 | t.fail('should not call this function') 220 | next() 221 | }), instance) 222 | 223 | instance.run(req, res) 224 | }) 225 | 226 | test('run array of middleware restricted by path', t => { 227 | t.plan(10) 228 | 229 | const instance = hiddie(function (err, a, b) { 230 | t.error(err) 231 | t.equal(a, req) 232 | t.equal('/test', req.url) 233 | t.equal(b, res) 234 | }) 235 | const req = { 236 | url: '/test' 237 | } 238 | const res = {} 239 | 240 | t.equal(instance.use([function (req, res, next) { 241 | t.ok('function called') 242 | next() 243 | }, function (req, res, next) { 244 | t.ok('function called') 245 | next() 246 | }]), instance) 247 | 248 | t.equal(instance.use('/test', [function (req, res, next) { 249 | t.ok('function called') 250 | next() 251 | }, function (req, res, next) { 252 | t.ok('function called') 253 | next() 254 | }]), instance) 255 | 256 | instance.run(req, res) 257 | }) 258 | 259 | test('run array of middleware restricted by array path', t => { 260 | t.plan(10) 261 | 262 | const instance = hiddie(function (err, a, b) { 263 | t.error(err) 264 | t.equal(a, req) 265 | t.equal('/test', req.url) 266 | t.equal(b, res) 267 | }) 268 | const req = { 269 | url: '/test' 270 | } 271 | const res = {} 272 | 273 | t.equal(instance.use([function (req, res, next) { 274 | t.ok('function called') 275 | next() 276 | }, function (req, res, next) { 277 | t.ok('function called') 278 | next() 279 | }]), instance) 280 | 281 | t.equal(instance.use(['/test', '/other-path'], [function (req, res, next) { 282 | t.ok('function called') 283 | next() 284 | }, function (req, res, next) { 285 | t.ok('function called') 286 | next() 287 | }]), instance) 288 | 289 | instance.run(req, res) 290 | }) 291 | 292 | test('Should strip the url to only match the pathname', t => { 293 | t.plan(6) 294 | 295 | const instance = hiddie(function (err, a, b) { 296 | t.error(err) 297 | t.equal(a, req) 298 | t.equal(req.url, '/test#foo?bin=baz') 299 | t.equal(b, res) 300 | }) 301 | const req = { 302 | url: '/test#foo?bin=baz' 303 | } 304 | const res = {} 305 | 306 | t.equal(instance.use('/test', function (req, res, next) { 307 | t.pass('function called') 308 | next() 309 | }), instance) 310 | 311 | instance.run(req, res) 312 | }) 313 | 314 | test('should keep the context', t => { 315 | t.plan(6) 316 | 317 | const instance = hiddie(function (err, a, b, ctx) { 318 | t.error(err) 319 | t.equal(a, req) 320 | t.equal(b, res) 321 | t.ok(ctx.key) 322 | }) 323 | const req = { 324 | url: '/test' 325 | } 326 | const res = {} 327 | 328 | t.equal(instance.use(function (req, res, next) { 329 | t.pass('function called') 330 | next() 331 | }), instance) 332 | 333 | instance.run(req, res, { key: true }) 334 | }) 335 | 336 | test('should add `originalUrl` property to req', t => { 337 | t.plan(2) 338 | 339 | const instance = hiddie(function (err) { 340 | t.error(err) 341 | }) 342 | const req = { 343 | url: '/test' 344 | } 345 | const res = {} 346 | 347 | instance.use(function (req, res, next) { 348 | t.equal(req.originalUrl, '/test') 349 | next() 350 | }) 351 | 352 | instance.run(req, res) 353 | }) 354 | 355 | test('basic serve static', t => { 356 | const instance = hiddie(function () { 357 | t.fail('the default route should never be called') 358 | }) 359 | instance.use(serveStatic(__dirname)) 360 | const server = http.createServer(instance.run.bind(instance)) 361 | 362 | server.listen(0, function () { 363 | http.get(`http://localhost:${server.address().port}/README.md`, function (res) { 364 | t.is(res.statusCode, 200) 365 | res.resume() 366 | server.close() 367 | server.unref() 368 | t.end() 369 | }) 370 | }) 371 | }) 372 | 373 | test('limit serve static to a specific folder', t => { 374 | const instance = hiddie(function () { 375 | t.fail('the default route should never be called') 376 | req.destroy() 377 | server.close() 378 | server.unref() 379 | }) 380 | instance.use('/assets', serveStatic(__dirname)) 381 | const server = http.createServer(instance.run.bind(instance)) 382 | var req 383 | 384 | server.listen(0, function () { 385 | req = http.get(`http://localhost:${server.address().port}/assets/README.md`, function (res) { 386 | t.is(res.statusCode, 200) 387 | res.resume() 388 | server.close() 389 | server.unref() 390 | t.end() 391 | }) 392 | }) 393 | }) 394 | 395 | test('should match all chain', t => { 396 | t.plan(2) 397 | const instance = hiddie(function (err, req, res) { 398 | t.error(err) 399 | t.deepEqual(req, { 400 | url: '/inner/in/depth', 401 | originalUrl: '/inner/in/depth', 402 | params: {}, 403 | undefined: true, 404 | null: true, 405 | empty: true, 406 | root: true, 407 | inner: true, 408 | innerSlashed: true, 409 | innerIn: true, 410 | innerInSlashed: true, 411 | innerInDepth: true, 412 | innerInDepthSlashed: true 413 | }) 414 | }) 415 | const req = { 416 | url: '/inner/in/depth' 417 | } 418 | const res = {} 419 | 420 | const prefixes = [ 421 | { prefix: undefined, name: 'undefined' }, 422 | { prefix: null, name: 'null' }, 423 | { prefix: '', name: 'empty' }, 424 | { prefix: '/', name: 'root' }, 425 | { prefix: '/inner', name: 'inner' }, 426 | { prefix: '/inner/', name: 'innerSlashed' }, 427 | { prefix: '/inner/in', name: 'innerIn' }, 428 | { prefix: '/inner/in/', name: 'innerInSlashed' }, 429 | { prefix: '/inner/in/depth', name: 'innerInDepth' }, 430 | { prefix: '/inner/in/depth/', name: 'innerInDepthSlashed' } 431 | ] 432 | prefixes.forEach(function (o) { 433 | instance.use(o.prefix, function (req, res, next) { 434 | if (req[o.name]) throw new Error('Called twice!') 435 | req[o.name] = true 436 | next() 437 | }) 438 | }) 439 | 440 | instance.run(req, res) 441 | }) 442 | 443 | test('should match the same slashed path', t => { 444 | t.plan(3) 445 | const instance = hiddie(function (err, req, res) { 446 | t.error(err) 447 | t.deepEqual(req, { 448 | url: '/path', 449 | originalUrl: '/path', 450 | params: {} 451 | }) 452 | }) 453 | const req = { 454 | url: '/path' 455 | } 456 | const res = {} 457 | 458 | instance.use('/path/', function (req, res, next) { 459 | t.pass('function called') 460 | next() 461 | }) 462 | 463 | instance.use('/path/inner', function (req, res, next) { 464 | t.fail() 465 | next() 466 | }) 467 | 468 | instance.run(req, res) 469 | }) 470 | 471 | test('should match the same slashed path with paths', t => { 472 | t.plan(3) 473 | const instance = hiddie(function (err, req, res) { 474 | t.error(err) 475 | t.deepEqual(req, { 476 | url: '/path/test', 477 | originalUrl: '/path/test', 478 | params: { 479 | p: 'test' 480 | } 481 | }) 482 | }) 483 | const req = { 484 | url: '/path/test' 485 | } 486 | const res = {} 487 | 488 | instance.use('/path/:p', function (req, res, next) { 489 | t.pass('function called') 490 | next() 491 | }) 492 | 493 | instance.use('/path/inner', function (req, res, next) { 494 | t.fail() 495 | next() 496 | }) 497 | 498 | instance.run(req, res) 499 | }) 500 | 501 | test('if the function calls res.end the iterator should stop', t => { 502 | t.plan(1) 503 | 504 | const instance = hiddie(function () { 505 | t.fail('we should not be here') 506 | }) 507 | const req = { 508 | url: '/test' 509 | } 510 | const res = { 511 | finished: false, 512 | end: function () { 513 | t.pass('res.end') 514 | this.finished = true 515 | } 516 | } 517 | 518 | instance 519 | .use(function (req, res, next) { 520 | res.end('hello') 521 | next() 522 | }) 523 | .use(function (req, res, next) { 524 | t.fail('we should not be here') 525 | }) 526 | 527 | instance.run(req, res) 528 | }) 529 | --------------------------------------------------------------------------------