Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Fastify-allow plugin reference #3242

Merged

Conversation

mattbishop
Copy link
Contributor

Checklist

@github-actions github-actions bot added documentation Improvements or additions to documentation plugin Identify a pr to the doc that adds a plugin. labels Aug 8, 2021
Copy link
Member

@RafaelGSS RafaelGSS left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Very usefull plugin!

Copy link
Member

@zekth zekth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@mcollina
Copy link
Member

mcollina commented Aug 9, 2021

Great plugin! May I ask for a change? I think by default it should not add a new not found handler but rather provide an utility that could be used to implement such behavior. All the applications I have worked with have a custom not found handler.. which will make this plugin significantly less useful.

@mattbishop
Copy link
Contributor Author

Interesting, I didn't know that it was common to replace the notFoundHandler. Do you have a plugin that does this well? My first thought is to expose a method routeNotFoundHandler with the same interface as notFoundHander. Then people who don't want to automatically send 405s would use the existing options and then get this method to handle themselves.

@mcollina
Copy link
Member

mcollina commented Aug 9, 2021

Interesting, I didn't know that it was common to replace the notFoundHandler. Do you have a plugin that does this well? My first thought is to expose a method routeNotFoundHandler with the same interface as notFoundHander. Then people who don't want to automatically send 405s would use the existing options and then get this method to handle themselves.

I would not really use such behavior. The best thing I think would be to add a property on the thrown error with the allowed methods

@mattbishop
Copy link
Contributor Author

I don't think I understand. When would I throw an error? Is there a hook I can use that is for requests that do not match to a route where I could throw an error?

@mcollina
Copy link
Member

mcollina commented Aug 9, 2021

I don't think I understand. When would I throw an error? Is there a hook I can use that is for requests that do not match to a route where I could throw an error?

I'm sorry I've responded too quickly. I think the best approach would be to add a decorator in the app to fetch all the allowed method of a route.

@mattbishop
Copy link
Contributor Author

mattbishop commented Aug 9, 2021

The decorator approach is fine for known routes with handlers like GET, but I don't think it will work for the routes that have one handler but not more, where the spec expects the server to return 405 with an Allow header. For instance:

app.get("/route", ...)

Then the client calls:

HTTP GET /route
... response:
200 OK,  Allow: GET

HTTP POST /route
... response:
405 Method Not Allowed, Allow: GET

The only way I found to respond with the 405 automatically is to replace the notFoundHandler because no route exists for POST:/route. This is important to the usefulness of the plugin. I thought of other ways, like registering a bunch of handlers for all the methods that are not supported but I didn't have access to the routes table at the plugin level.

This may actually be better as a core behaviour of fastify? It is part of the http 1.1 spec.

@mcollina
Copy link
Member

mcollina commented Aug 10, 2021

Actually you are better off implementing this as a hook and throwing an Error with .statusCode 405, something like the MethodNotAllowed error in https://www.npmjs.com/package/http-errors.

Something like (this works):

import Fastify from 'fastify'
import errors from 'http-errors'

const app = Fastify({ logger: true })

app.addHook('onRequest', async function (req) {
  if (req.method === 'POST') {
    throw new errors.MethodNotAllowed('POST')
  }
})

await app.listen(3000)

Which in pseudocode would become:

import Fastify from 'fastify'
import errors from 'http-errors'

const app = Fastify({ logger: true })

await app.register(import('fastify-allow'))
app.addHook('onRequest', async function (req) {
  if (app.isAllowed(req)) {
    throw new errors.MethodNotAllowed('POST')
  }
})

await app.listen(3000)

@mattbishop
Copy link
Contributor Author

The 'onRequest' approach was actually my first idea, but I found that it was not called if a request had no registered handler. Find-my-way includes the method in the search for a route, so if fastify has a handler for app.get('/route'...), fastify will trigger onRequest for GET /route, but it will not trigger for POST /route.

The only hook I could find for non-registered routes was the notFoundHandler.

This plugin does allow the developer to turn off the handleNotFound override with a plugin option. Let me take your original suggestion of a utility export and try it out. For those cases where they don't need to override handleNotFound, the plugin works as-is. For those who want a different notFound behaviour, they can turn it off with the plugin option "send405": false. It's the last use case I'm missing–they want their own notFound handler, but they also want to send 405s too.

@mcollina
Copy link
Member

The 'onRequest' approach was actually my first idea, but I found that it was not called if a request had no registered handler. Find-my-way includes the method in the search for a route, so if fastify has a handler for app.get('/route'...), fastify will trigger onRequest for GET /route, but it will not trigger for POST /route.

I think we fixed this, this will get triggered all the time.

@mattbishop
Copy link
Contributor Author

mattbishop commented Aug 10, 2021 via email

@mcollina
Copy link
Member

I think it was fixed since the v3 cycle but I might be mistaken. Anyway, I could not reproduce the behavior you are mentioning now.

@mattbishop
Copy link
Contributor Author

Great news, I was wrong and onRequest is indeed called when a path matches but not the method. I tested both fastify 3.3.0 and 3.18.1:

test("onRequest for unregistered routes", (t) => {
  t.plan(2)
  const app = Fastify()
  app.addHook("onRequest", (request: FastifyRequest, reply: FastifyReply, done: () => void) => {
    t.pass(`onRequest called for ${request.method} ${request.url}`)
    done()
  })
  const url = "/a-route"
  app.get(url, (request: FastifyRequest, reply: FastifyReply) => {
    reply.send()
  })

  app.inject({method: "GET", url})
  app.inject({method: "DELETE", url})
})
TAP version 13
# onRequest for unregistered routes
ok 1 onRequest called for GET /a-route
ok 2 onRequest called for DELETE /a-route

So I can take out the notFoundHandler approach altogether. I'll post in this issue when I do that.

@mattbishop
Copy link
Contributor Author

I have refactored to not use the noFoundHandler. Thanks again for the direction.

Copy link
Member

@mcollina mcollina left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@mcollina mcollina merged commit 846284d into fastify:main Aug 11, 2021
@mattbishop mattbishop deleted the mattbishop-add-fastify-allow-to-ecosystem branch August 11, 2021 23:31
@github-actions
Copy link

This pull request has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Aug 12, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
documentation Improvements or additions to documentation plugin Identify a pr to the doc that adds a plugin.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants