• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Kikobeats / router-http / 30890521675

04 Aug 2026 08:06AM UTC coverage: 98.783%. First build
30890521675

Pull #35

github

web-flow
Merge b22f99776 into efe010aec
Pull Request #35: fix: match .use() mounts by decoded longest prefix

103 of 107 branches covered (96.26%)

Branch coverage included in aggregate %.

67 of 68 new or added lines in 1 file covered. (98.53%)

303 of 304 relevant lines covered (99.67%)

86.45 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.78
/src/index.js
1
'use strict'
1✔
2

1✔
3
const NullProtoObj = require('null-prototype-object')
1✔
4
const FindMyWay = require('find-my-way')
1✔
5

1✔
6
const HTTP_METHODS = [
1✔
7
  'get',
1✔
8
  'post',
1✔
9
  'put',
1✔
10
  'patch',
1✔
11
  'delete',
1✔
12
  'head',
1✔
13
  'options',
1✔
14
  'trace',
1✔
15
  'connect'
1✔
16
]
1✔
17

1✔
18
const SLASH_CHAR_CODE = 47
1✔
19
const SYNC_ITERATION_LIMIT = 100
1✔
20

1✔
21
const requiredFinalHandler = () => {
1✔
22
  throw new TypeError('You should to provide a final handler')
1✔
23
}
1✔
24

1✔
25
const ensureLeadingSlash = route =>
1✔
26
  route.charCodeAt(0) === SLASH_CHAR_CODE ? route : `/${route}`
17✔
27

1✔
28
// Strip trailing slashes so `.use('/admin/', …)` registers under `/admin`.
1✔
29
const normalizeMountPath = path => {
1✔
30
  const withSlash = ensureLeadingSlash(path)
17✔
31
  let end = withSlash.length
17✔
32
  while (end > 1 && withSlash.charCodeAt(end - 1) === SLASH_CHAR_CODE) {
17✔
33
    end--
1✔
34
  }
1✔
35
  return end === withSlash.length ? withSlash : withSlash.substring(0, end)
17✔
36
}
17✔
37

1✔
38
const decodePathname = pathname => {
1✔
39
  try {
84✔
40
    return decodeURIComponent(pathname)
84✔
41
  } catch {
84✔
42
    return pathname
1✔
43
  }
1✔
44
}
84✔
45

1✔
46
const countSegments = path => {
1✔
47
  if (path.length <= 1) return 0
14!
48
  let count = 1
14✔
49
  for (let i = 1; i < path.length; i++) {
14✔
50
    if (path.charCodeAt(i) === SLASH_CHAR_CODE) count++
86✔
51
  }
86✔
52
  return count
14✔
53
}
14✔
54

1✔
55
// Return the raw URL prefix covering `segmentCount` segments so encoded mounts
1✔
56
// (e.g. `/%61dmin/panel`) strip the same span as the decoded mount `/admin/panel`.
1✔
57
const getRawMountPrefix = (pathname, segmentCount) => {
1✔
58
  if (segmentCount <= 0) return ''
26!
59
  let count = 0
26✔
60
  let i = 1
26✔
61
  while (i < pathname.length) {
26✔
62
    const next = pathname.indexOf('/', i)
33✔
63
    count++
33✔
64
    if (count === segmentCount) {
33✔
65
      return next === -1 ? pathname : pathname.substring(0, next)
26✔
66
    }
26✔
67
    if (next === -1) return pathname
33!
68
    i = next + 1
7✔
69
  }
7✔
NEW
70
  return pathname
×
71
}
26✔
72

1✔
73
// Match the longest registered mount against the decoded pathname so multi-segment
1✔
74
// and percent-encoded `.use()` mounts are not skipped while routes still match.
1✔
75
const matchPathMiddleware = (pathname, middlewaresByPath) => {
1✔
76
  const decoded = decodePathname(pathname)
84✔
77
  let matchedPath
84✔
78
  let matchedMw
84✔
79

84✔
80
  for (const mountPath in middlewaresByPath) {
84✔
81
    if (decoded === mountPath || decoded.startsWith(mountPath + '/')) {
29✔
82
      if (matchedPath === undefined || mountPath.length > matchedPath.length) {
27✔
83
        matchedPath = mountPath
27✔
84
        matchedMw = middlewaresByPath[mountPath]
27✔
85
      }
27✔
86
    }
27✔
87
  }
29✔
88

84✔
89
  return matchedMw
84✔
90
}
84✔
91

1✔
92
const parseUrl = ({ url }) => {
1✔
93
  const queryIndex = url.indexOf('?', 1)
84✔
94
  if (queryIndex === -1) {
84✔
95
    return { pathname: url, query: null, search: null }
77✔
96
  }
77✔
97
  const search = url.substring(queryIndex)
7✔
98
  return {
7✔
99
    pathname: url.substring(0, queryIndex),
7✔
100
    query: search.substring(1),
7✔
101
    search
7✔
102
  }
7✔
103
}
84✔
104

1✔
105
const QUESTION_MARK_CHAR_CODE = 63
1✔
106

1✔
107
const mutateRequestUrl = (prefix, req) => {
1✔
108
  const remainingUrl = req.url.substring(prefix.length)
26✔
109
  req.url = !remainingUrl || remainingUrl.charCodeAt(0) === QUESTION_MARK_CHAR_CODE
26✔
110
    ? `/${remainingUrl}`
26✔
111
    : remainingUrl
26✔
112
  const remainingPath = req.path.substring(prefix.length)
26✔
113
  req.path = remainingPath || '/'
26✔
114
}
26✔
115

1✔
116
module.exports = (finalhandler = requiredFinalHandler(), options = {}) => {
1✔
117
  const router = FindMyWay({
62✔
118
    ...options,
62✔
119
    defaultRoute: (req, res) => finalhandler(undefined, req, res)
62✔
120
  })
62✔
121

62✔
122
  const globalMiddlewares = []
62✔
123
  const middlewaresByPath = new NullProtoObj()
62✔
124

62✔
125
  const findRoute = (method, path, constraints) => {
62✔
126
    const result = router.find(method, path, constraints)
85✔
127
    if (result === null) {
85✔
128
      return { params: {}, handlers: [] }
16✔
129
    }
16✔
130
    return { params: result.params, handlers: result.handler.handlers }
69✔
131
  }
85✔
132

62✔
133
  const registerRoute = (method, path, handlers) => {
62✔
134
    const routeHandler = () => {}
71✔
135
    routeHandler.handlers = handlers
71✔
136
    router.on(method, path, routeHandler)
71✔
137
  }
71✔
138

62✔
139
  const addRoute = (method, path, ...handlers) => {
62✔
140
    const fns = handlers.flat().filter(Boolean)
64✔
141
    if (fns.length === 0) return handler
64✔
142

63✔
143
    const methods = method === '' ? HTTP_METHODS : [method]
64✔
144

64✔
145
    for (let i = 0; i < methods.length; i++) {
64✔
146
      registerRoute(methods[i].toUpperCase(), path, fns)
71✔
147
    }
71✔
148

62✔
149
    return handler
62✔
150
  }
64✔
151

62✔
152
  const selectMiddleware = (
62✔
153
    index,
341✔
154
    globalLen,
341✔
155
    pathLen,
341✔
156
    globalMw,
341✔
157
    pathMw,
341✔
158
    routeHandlers
341✔
159
  ) => {
341✔
160
    if (index < globalLen) return globalMw[index]
341✔
161
    if (index < globalLen + pathLen) return pathMw[index - globalLen]
341✔
162
    return routeHandlers[index - globalLen - pathLen]
62✔
163
  }
341✔
164

62✔
165
  const handler = (req, res, next) => {
62✔
166
    const urlInfo = parseUrl(req)
84✔
167
    const pathname = urlInfo.pathname
84✔
168
    req.path = pathname
84✔
169

84✔
170
    let route = findRoute(req.method, pathname)
84✔
171

84✔
172
    if (route.handlers.length === 0 && req.method === 'HEAD') {
84✔
173
      route = findRoute('GET', pathname)
1✔
174
    }
1✔
175

84✔
176
    const globalMw = globalMiddlewares
84✔
177
    const pathMw = matchPathMiddleware(pathname, middlewaresByPath)
84✔
178
    const routeHandlers = route.handlers.length > 0 ? route.handlers : null
84✔
179

84✔
180
    if (routeHandlers !== null) {
84✔
181
      req.params =
69✔
182
        req.params !== undefined
69✔
183
          ? { ...req.params, ...route.params }
69✔
184
          : route.params
69✔
185
    } else {
84✔
186
      req.params = req.params || {}
15✔
187
    }
15✔
188

84✔
189
    req.search = req.query || urlInfo.search
84✔
190
    req.query = req.query || urlInfo.query
84✔
191

84✔
192
    let index = 0
84✔
193
    let syncCount = 0
84✔
194

84✔
195
    const globalLen = globalMw.length
84✔
196
    const pathLen = pathMw !== undefined ? pathMw.length : 0
84✔
197
    const routeLen = routeHandlers !== null ? routeHandlers.length : 0
84✔
198
    const totalMiddlewares = globalLen + pathLen + routeLen
84✔
199

84✔
200
    const handleNext = err => {
84✔
201
      if (err === 'router') {
273✔
202
        if (next !== undefined) return next()
2✔
203
        index = totalMiddlewares
1✔
204
        err = undefined
1✔
205
      }
1✔
206
      if (err !== undefined) return finalhandler(err, req, res, next)
273✔
207
      if (++syncCount > SYNC_ITERATION_LIMIT) {
273✔
208
        syncCount = 0
1✔
209
        return setImmediate(executeLoop)
1✔
210
      }
1✔
211
      executeLoop()
265✔
212
    }
273✔
213

84✔
214
    const executeLoop = () => {
84✔
215
      if (index < totalMiddlewares) {
350✔
216
        if (res.writableEnded) return
342✔
217

341✔
218
        const currentIndex = index++
341✔
219
        const middleware = selectMiddleware(
341✔
220
          currentIndex,
341✔
221
          globalLen,
341✔
222
          pathLen,
341✔
223
          globalMw,
341✔
224
          pathMw,
341✔
225
          routeHandlers
341✔
226
        )
341✔
227

341✔
228
        try {
341✔
229
          const result = middleware(req, res, handleNext)
341✔
230
          if (
341✔
231
            result !== null &&
342✔
232
            result !== undefined &&
342✔
233
            typeof result.then === 'function'
46✔
234
          ) {
342✔
235
            result.then(undefined, handleNext)
4✔
236
          }
4✔
237
        } catch (err) {
342✔
238
          handleNext(err)
2✔
239
        }
2✔
240
        return
341✔
241
      }
341✔
242

8✔
243
      if (res.writableEnded) return
350✔
244
      if (next !== undefined) return next()
350✔
245
      finalhandler(undefined, req, res, handleNext)
6✔
246
    }
350✔
247

84✔
248
    executeLoop()
84✔
249
  }
84✔
250

62✔
251
  handler.use = (path = '/', ...fns) => {
62✔
252
    if (typeof path === 'function' || typeof path === 'boolean') {
236✔
253
      const middlewares = [path, ...fns].filter(Boolean)
216✔
254
      for (let i = 0; i < middlewares.length; i++) {
216✔
255
        globalMiddlewares.push(middlewares[i])
216✔
256
      }
216✔
257
    } else if (path === '/') {
236✔
258
      const middlewares = fns.filter(Boolean)
3✔
259
      for (let i = 0; i < middlewares.length; i++) {
3✔
260
        globalMiddlewares.push(middlewares[i])
3✔
261
      }
3✔
262
    } else {
20✔
263
      const normalizedPath = normalizeMountPath(path)
17✔
264
      const middlewares = fns.filter(Boolean)
17✔
265

17✔
266
      if (middlewares.length > 0) {
17✔
267
        let pathMiddlewares = middlewaresByPath[normalizedPath]
16✔
268

16✔
269
        if (pathMiddlewares === undefined) {
16✔
270
          pathMiddlewares = []
14✔
271
          const mountSegments = countSegments(normalizedPath)
14✔
272
          // Strip the request's raw mount prefix (may still be percent-encoded),
14✔
273
          // not `normalizedPath.length`, which cuts the wrong span for encoded URLs.
14✔
274
          pathMiddlewares.push((req, _, next) => {
14✔
275
            mutateRequestUrl(getRawMountPrefix(req.path, mountSegments), req)
26✔
276
            next()
26✔
277
          })
14✔
278
          middlewaresByPath[normalizedPath] = pathMiddlewares
14✔
279
        }
14✔
280

16✔
281
        for (let i = 0; i < middlewares.length; i++) {
16✔
282
          pathMiddlewares.push(middlewares[i])
16✔
283
        }
16✔
284
      }
16✔
285
    }
17✔
286
    return handler
236✔
287
  }
236✔
288

62✔
289
  handler.all = addRoute.bind(null, '')
62✔
290

62✔
291
  for (let i = 0; i < HTTP_METHODS.length; i++) {
62✔
292
    const method = HTTP_METHODS[i]
549✔
293
    handler[method] = addRoute.bind(null, method)
549✔
294
  }
549✔
295

61✔
296
  handler.prettyPrint = (...args) => router.prettyPrint(...args)
61✔
297

61✔
298
  Object.defineProperty(handler, 'routes', {
61✔
299
    get: () => router.routes,
61✔
300
    enumerable: true
61✔
301
  })
61✔
302

61✔
303
  return handler
61✔
304
}
62✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc