Skip to content

Commit

Permalink
refactor!: Replace app.setNotFoundHandler() with notFoundHandler
Browse files Browse the repository at this point in the history
…option

BREAKING CHANGE: The `app.setNotFoundHandler()` method has been removed.
  • Loading branch information
nwoltman committed Jun 28, 2019
1 parent 60ce94d commit 4a36b13
Show file tree
Hide file tree
Showing 8 changed files with 168 additions and 505 deletions.
33 changes: 0 additions & 33 deletions docs/App.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ const app = medley();
+ [`.onLoad(callback)`](#on-load)
+ [`.register(plugin [, options])`](#register)
+ [`.route(options)`](#route)
+ [`.setNotFoundHandler([options,] handler)`](#set-not-found-handler)
+ [`[@@iterator]()`](#iterator)


Expand Down Expand Up @@ -403,38 +402,6 @@ app.register(require('./routes'));
Registers a new route handler. There are also shorthand methods (like `app.get()`)
that aren't included here. See the [Routes](Routes.md) documentation.

<a id="set-not-found-handler"></a>
### `app.setNotFoundHandler([options,] handler)`

+ `options` *object* - Accepts the `responseSchema`, `preHandler`, and `config` options defined in [Routes#options](Routes.md#options).
+ `handler(req, res)` *(function)* - A request handler function that receives the [`request`](Request.md) and [`response`](Response.md) objects.
+ Chainable

Sets the handler that will be called when no registered route matches the
incoming request. The handler is treated like a regular route handler so
requests will go through the full [request lifecycle](Lifecycle.md).

```js
app.setNotFoundHandler((req, res) => {
// Send "404 Not Found" response
});
```

Sub-apps that are registered with a [`prefix`](#createsubapp) can have
their own not-found handler.

```js
app.setNotFoundHandler((req, res) => {
// Default not-found handler
});

const subApp = app.createSubApp('/v1');

subApp.setNotFoundHandler((req, res) => {
// Handle unmatched requests to URLs that begin with '/v1'
});
```

<a id="iterator"></a>
### `app[@@iterator]()`

Expand Down
8 changes: 3 additions & 5 deletions docs/Lifecycle.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,9 @@ Incoming Request

## Routing

The first step Medley takes after receiving a request is to look up a route that matches the URL of the request.
The first step Medley takes after receiving a request is to find the route that matches the URL of the request.

If no route matches the request, a not-found handler that matches the URL is selected (or the default not-found handler is used if no handlers set with [`app.setNotFoundHandler()`](App.md#set-not-found-handler) were a match).

If the request method is not one of the [supported HTTP methods](https://nodejs.org/api/http.html#http_http_methods), a `501 Not Implemented` error response is sent immediately and the entire lifecycle is skipped.
Medley uses the [`find-my-way`](https://www.npmjs.com/package/find-my-way) router to make this step fast and efficient.

## `onRequest` Hooks

Expand Down Expand Up @@ -80,7 +78,7 @@ See the [`Routes` documentation](Routes.md) for more information on route handle

#### Not-Found Handler

If the request URL does not match any routes, a *not-found handler* (set with [`app.setNotFoundHandler()`](App.md#set-not-found-handler)) is invoked. Global hooks **are** run before the not-found handler.
If the request URL does not match any routes, the [`notFoundHandler`](Medley.md#notfoundhandler) is invoked. Global hooks **are** run before/after this handler.

## Serialize Payload

Expand Down
25 changes: 22 additions & 3 deletions docs/Medley.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ object which is used to customize the resulting instance. The options are:
+ [`http2`](#http2)
+ [`https`](#https)
+ [`maxParamLength`](#maxparamlength)
+ [`notFoundHandler`](#notfoundhandler)
+ [`onErrorSending`](#onerrorsending)
+ [`queryParser`](#queryparser)
+ [`server`](#server)
Expand All @@ -17,7 +18,7 @@ object which is used to customize the resulting instance. The options are:

### `http2`

*object | boolean*<br>
Type: `object` | `boolean`<br>
Default: `false`

An object used to configure the HTTP server to use HTTP/2. The options are the
Expand All @@ -36,7 +37,7 @@ The `https` option is ignored if this option is present.

### `https`

Default: `undefined`
Type: `object`

An object used to configure the server's listening socket for TLS. The options
are the same as the Node.js core
Expand All @@ -53,6 +54,7 @@ const app = medley({

### `maxParamLength`

Type: `number`<br>
Default: `100`

This option sets a limit on the number of characters in the parameters of
Expand All @@ -63,6 +65,23 @@ for routes with regex parameters.

*If the maximum length limit is reached, the request will not match the route.*

### `notFoundHandler`

Type: `function(req, res)` (`req` - [Request](Request.md), `res` - [Response](Response.md))

A handler function that is called when no routes match the request URL.

```js
const medley = require('@medley/medley');
const app = medley({
notFoundHandler: (req, res) => {
res.status(404).send('Route Not Found');
}
});
```

[Hooks](Hooks.md) that are added to the root `app` will run before/after the `notFoundHandler`.

### `onErrorSending`

Type: `function(err)`
Expand Down Expand Up @@ -90,7 +109,7 @@ Specifically, this function will be called when:

### `queryParser`

+ Default: [`querystring.parse`](https://nodejs.org/dist/latest/docs/api/querystring.html#querystring_querystring_parse_str_sep_eq_options)
Default: [`querystring.parse`](https://nodejs.org/dist/latest/docs/api/querystring.html#querystring_querystring_parse_str_sep_eq_options)

A custom function to parse the URL's query string into the value for
[`req.query`](Request.md#reqquery). It will receive the complete query
Expand Down
25 changes: 4 additions & 21 deletions lib/RequestHandlers.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,12 @@ const compileJSONStringify = require('compile-json-stringify')
const {runOnRequestHooks, runOnErrorHooks} = require('./HookRunners')
const {STATUS_CODES} = require('http')

function createRequestHandler(router, notFoundRouter, Request, Response) {
function createRequestHandler(router, notFoundRoute, Request, Response) {
return function requestHandler(req, res) {
var queryIndex = req.url.indexOf('?') // find-my-way needs the query string removed
var url = queryIndex >= 0 ? req.url.slice(0, queryIndex) : req.url

var route = router.find(req.method, url, undefined) // Avoid arguments adaptor trampoline

if (route === null) {
route = notFoundRouter.find(req.method, url, undefined)

if (route === null) {
notFoundFallbackHandler(req, res)
return
}
}
var route = router.find(req.method, url, undefined) || notFoundRoute

var routeContext = route.store
var request = new Request(req, req.headers, route.params)
Expand Down Expand Up @@ -89,16 +80,8 @@ function create405Handler(allowedMethods) {
}

function defaultNotFoundHandler(req, res) {
res.status(404).send(`Not Found: ${req.method} ${req.url}`)
}

function notFoundFallbackHandler(req, res) { // Node's req/res
const payload = `Unsupported request method: ${req.method}`
res.writeHead(501, { // Not Implemented
'Content-Type': 'text/plain; charset=utf-8',
'Content-Length': '' + Buffer.byteLength(payload),
})
res.end(payload)
res.statusCode = 404
res.send(`Not Found: ${req.method} ${req.url}`)
}

function defaultErrorHandler(err, req, res) {
Expand Down
86 changes: 24 additions & 62 deletions medley.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ const {
defaultNotFoundHandler,
} = require('./lib/RequestHandlers')

const kIsNotFoundHandlerSet = Symbol('isNotFoundHandlerSet')

const supportedMethods = http.METHODS.filter(method => method !== 'CONNECT')

/* istanbul ignore next - This is never used. It's just needed to appease find-my-way. */
Expand Down Expand Up @@ -83,15 +81,32 @@ function medley(options) {
throw new TypeError(`'onErrorSending' option must be a function. Got value of type '${typeof options.onErrorSending}'`)
}

const onErrorSending = options.onErrorSending || defaultOnErrorSending
if (options.notFoundHandler !== undefined && typeof options.notFoundHandler !== 'function') {
throw new TypeError(`'notFoundHandler' option must be a function. Got value of type '${typeof options.notFoundHandler}'`)
}

const router = findMyWay({
ignoreTrailingSlash: !options.strictRouting,
maxParamLength: options.maxParamLength,
})
const notFoundRouter = findMyWay()
const rootAppHooks = new Hooks()
const onErrorSending = options.onErrorSending || defaultOnErrorSending
const notFoundRouteContext = RouteContext.create(
null, // Serializers
options.notFoundHandler || defaultNotFoundHandler,
{}, // config
null, // preHandler
rootAppHooks,
onErrorSending
)
const notFoundRoute = {
handler: noop, // To match shape of find-my-way routes
params: {},
store: notFoundRouteContext,
}
const Request = buildRequest(!!options.trustProxy, options.queryParser)
const Response = buildResponse()
const requestHandler = createRequestHandler(router, notFoundRouter, Request, Response)
const requestHandler = createRequestHandler(router, notFoundRoute, Request, Response)

var loadCallbackQueue = null
var loaded = false
Expand All @@ -113,7 +128,7 @@ function medley(options) {

// Hooks
addHook,
_hooks: new Hooks(),
_hooks: rootAppHooks,

// Routing
route,
Expand All @@ -133,9 +148,6 @@ function medley(options) {
},
_routePrefix: '/',

setNotFoundHandler,
[kIsNotFoundHandlerSet]: false,

// App setup
onLoad,
load,
Expand All @@ -154,7 +166,9 @@ function medley(options) {
}

const routes = new Map()
const routeContexts = new Map()
const routeContexts = new Map([
[app, [notFoundRouteContext]],
])

const onLoadHandlers = []
const onCloseHandlers = []
Expand Down Expand Up @@ -193,7 +207,6 @@ function medley(options) {
}

subApp._routePrefix += subApp._routePrefix.endsWith('/') ? prefix.slice(1) : prefix
subApp[kIsNotFoundHandlerSet] = false
}

return subApp
Expand Down Expand Up @@ -293,53 +306,6 @@ function medley(options) {
return this // Chainable
}

function setNotFoundHandler(opts, handler) {
throwIfAppIsLoaded('Cannot call "setNotFoundHandler()" when app is already loaded')

if (!this.hasOwnProperty('_routePrefix')) {
throw new Error('Cannot call "setNotFoundHandler()" on a sub-app created without a prefix')
}

const prefix = this._routePrefix

if (this[kIsNotFoundHandlerSet]) {
throw new Error(`Not found handler already set for app instance with prefix: '${prefix}'`)
}

this[kIsNotFoundHandlerSet] = true

if (handler === undefined) {
handler = opts
opts = {}
}

const serializers = buildSerializers(opts.responseSchema)
const routeContext = RouteContext.create(
serializers,
handler,
opts.config || {},
opts.preHandler,
this._hooks,
onErrorSending
)

if (prefix.endsWith('/')) {
notFoundRouter.on(supportedMethods, prefix + '*', noop, routeContext)
} else {
notFoundRouter.on(supportedMethods, prefix, noop, routeContext)
notFoundRouter.on(supportedMethods, prefix + '/*', noop, routeContext)
}

const appRouteContexts = routeContexts.get(this)
if (appRouteContexts === undefined) {
routeContexts.set(this, [routeContext])
} else {
appRouteContexts.push(routeContext)
}

return this
}

function recordRoute(routePath, methods, routeContext, appInstance) {
const methodRoutes = {}
for (var i = 0; i < methods.length; i++) {
Expand Down Expand Up @@ -432,10 +398,6 @@ function medley(options) {
return
}

if (!app[kIsNotFoundHandlerSet]) {
app.setNotFoundHandler(defaultNotFoundHandler)
}

registeringAutoHandlers = true
registerAutoHandlers()

Expand Down
Loading

0 comments on commit 4a36b13

Please sign in to comment.