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

fix(status): Send response with 404 when ENOENT is found #207

Open
wants to merge 5 commits into
base: master
Choose a base branch
from

Conversation

tristan957
Copy link

@tristan957 tristan957 commented May 13, 2020

PR Checklist

Please check if your PR fulfills the following requirements:

PR Type

What kind of change does this PR introduce?

[x] Bugfix
[ ] Feature
[ ] Code style update (formatting, local variables)
[ ] Refactoring (no functional changes, no api changes)
[ ] Build related changes
[ ] CI related changes
[ ] Other... Please describe:

What is the current behavior?

Issue Number: #139

What is the new behavior?

Sends the response back with a 404 when ENOENT is included in the error message.

Does this PR introduce a breaking change?

[x] Yes
[ ] No

Maybe it is a breaking change. If you are someone who is catching 500 level errors in a catch all interceptor and checking for ENOENT existence in your error message, then chances are you would be affected by this change.

@tristan957
Copy link
Author

@kamilmysliwiec I am not familiar with Fastify, so I would not feel comfortable trying to make a similar change, nor do I know if the same behavior even exists in the FastifyAdapter.

I am looking into testing now.

@tristan957
Copy link
Author

I am actually not sure how to test this is my project. @kamilmysliwiec would appreciate a tip here. I thought npm link and adding it to my project would do the trick but no dice.

@micaelmbagira
Copy link

@tristan957 Yes you need to clone, build and link it npm linkin the clone repo and npm link @nestjs/serve-static in your project.

@tristan957
Copy link
Author

Hi @micaelmbagira I will give that a try again hopefully some time this week. Maybe I forgot to build it last time I tried, and had only linked it.

@kamilmysliwiec kamilmysliwiec added the enhancement New feature or request label Jun 19, 2020
@micaelmbagira
Copy link

@kamilmysliwiec what's the status on this one? Is there anything left to do or can it be merged?

@tristan957
Copy link
Author

@micaelmbagira really just needs to be tested I think. I haven't had a chance recently.

@micaelmbagira
Copy link

@tristan957 Will do now

lib/loaders/express.loader.ts Outdated Show resolved Hide resolved
@tristan957
Copy link
Author

Sweet let me know how it goes.

@tristan957
Copy link
Author

@micaelmbagira I committed your suggested change. Does everything work for you now?

@tristan957
Copy link
Author

Whoops looks like an import statement is needed according CircleCI. @micaelmbagira I can add you as a contrib on my fork or you can suggest the change and I can commit it again. Later today I can squash the commits

@micaelmbagira
Copy link

@tristan957 yes you can add me

@tristan957
Copy link
Author

you should be in now

@micaelmbagira
Copy link

@tristan957 @kamilmysliwiec Done, please let me know if any more changes needed. In any case, seems to work fine

@@ -48,6 +48,14 @@ export class ExpressLoader extends AbstractLoader {
app.use(express.static(clientPath, options.serveStaticOptions));
app.get(options.renderPath, renderFn);
}

app.use((err: Error, req, res, next: Function) => {
Copy link
Author

Choose a reason for hiding this comment

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

@micaelmbagira I just learned this recently, but express exports a type called NextFunction specifically for the next parameter. Might be better to use that instead.

Choose a reason for hiding this comment

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

Not sure if that would be useful in that case. We need to send the 404 to the client and I don't see any other way to do it

Copy link
Author

Choose a reason for hiding this comment

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

No your solution is good. I am just suggesting a narrower type on next. Right now we are using Function, but we could use https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/express/index.d.ts#L107. It would help narrow the typing down a bit :). Unless there is something that I am missing.

@tristan957
Copy link
Author

@micaelmbagira thanks for your work :)

@micaelmbagira
Copy link

@kamilmysliwiec could you review the changes? 🙂

@micaelmbagira
Copy link

@kamilmysliwiec Hello? Can we merge that one? Or is there anything to be adjusted?

Copy link
Member

@jmcdo29 jmcdo29 left a comment

Choose a reason for hiding this comment

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

Overall the change looks pretty small, though due to a change in functionality it would probably need to wait until the next major version. Is there any chance we can get an update to fastify.lodader.ts as well, to make sure we have parity between adapters?

@@ -48,6 +48,14 @@ export class ExpressLoader extends AbstractLoader {
app.use(express.static(clientPath, options.serveStaticOptions));
app.get(options.renderPath, renderFn);
}

app.use((err: Error, req, res, next: Function) => {
if (err?.message?.includes('ENOENT')) {
Copy link
Member

Choose a reason for hiding this comment

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

Quick question: What happens if the route doesn't exist anywhere in the server? Would an error with ENOENT still come back, or does express take care of that in some other way?

Copy link

Choose a reason for hiding this comment

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

If the route doesn't exist it returns 404

{
  "response":{
    "statusCode":404,
    "message":"ENOENT: no such file or directory, stat '/usr/local/foo/bar/index.html'",
    "error":"Not Found"
  },
  "status":404,
  "message":"ENOENT: no such file or directory, stat '/usr/local/foo/bar/index.html'"
}

I am testing my fix with this sample app that has no route but only ServeStaticModule pointing to a path that does not exist.

@micaelmbagira
Copy link

Yes, I will make the change for fastify.loader.ts. serve-static seems to be broken (not working at all) with fastify...

Regarding the release, it's actually a bug fix IMO: it should not return 500 if the files are not found. What do you think?

@jmcdo29
Copy link
Member

jmcdo29 commented Aug 30, 2020

serve-static seems to be broken (not working at all) with fastify...

How so? I have a working example on my machine.

@micaelmbagira
Copy link

@jmcdo29 Never mind it was my bad.
So, I found out the issue is not caused by express.static nor fastify-static (they both behave as expected when the requested file does not exist) but by app.get(options.renderPath, renderFn);.

With fastify, the fix is not easy because res.send(stream) causes the error to happen inside fastify. Hence I decided to use fs.exists.
For express, I scoped the error handling to res.sendFile (because that's what triggers the error) instead of having this general error handler.

Overall, I don't think the solution looks super nice but couldn't come up with something better...

@kamilmysliwiec if you have time to give it a look

@donnd-t
Copy link

donnd-t commented Nov 4, 2020

Can this be merged? And not as a major release as this is a bug IMO. Besides the current behaviour being wrong I doubt if my security department will allow me to deploy any app with a broken HTTP status like this. A merge would be much appreciated otherwise I will need to fork or move to an alternate solution.

However, there is one problem with the PR. The response includes the full internal path of the file that wasn't found. This is a security issue as it exposes information about the internal server. If the file name is included in the error in needs to be relative to the public endpoint. Also, it would be nice is the error was completely customizable.

@tristan957
Copy link
Author

I am in the same boat.

@gax97
Copy link

gax97 commented Apr 15, 2021

Any updates?

1 similar comment
@gabdusch
Copy link

Any updates?

@adalinesimonian
Copy link

adalinesimonian commented Sep 19, 2022

Is there any interest from the maintainers to review this PR? It's been open for a few years but at least to my eyes looks like it was in a working state.

Folks have been working around this by creating global exception filters, but then if an ENOENT springs up from somewhere else in your application when you don't expect it, it gets handled like a 404, which is not desired.

Edit: Turns out global filters don't quite work for this. Nest's logic checks if any filters apply by using the type that's passed to the @Catch(MyError) decorator, and if there's a match, your filter takes over with no way to fall through. So in order to catch ENOENT errors I'd have to use @Catch(Error) and then I lose the ability to allow any other filters to handle the error. i.e. there's no way just to handle errors with error.code === 'ENOENT'.

@NeoDobby
Copy link

Any updates?

@tristan957
Copy link
Author

I have given up on getting this merged, and I no longer work with Nest.

@Lokkve0126
Copy link

you can do it with

app.get(renderPath, async (req: any, res: any) => {
          await new Promise((resolve, reject) => {
            const stream = fs.createReadStream(indexFilePath);
            stream.on("data", (chunk) => {
              res.type('text/html').send(chunk);
              resolve(undefined);
            });
            stream.on("error", () => {
              reject(new NotFoundException("Not found"));
            });
          });
        });

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

10 participants