Routing-controllers: response.sendFile

Created on 11 Sep 2017  路  20Comments  路  Source: typestack/routing-controllers

If I use response.sendFile("/file") I get the following error:

[NODE] Error
[NODE] at NotFoundError.HttpError [as constructor] (/app/src/http-error/HttpError.ts:19:22)
[NODE] at new NotFoundError (/app/src/http-error/NotFoundError.ts:10:9)
[NODE] at ExpressDriver.handleSuccess (/app/src/driver/express/ExpressDriver.ts:286:35)
[NODE] at /app/src/RoutingControllers.ts:161:45
[NODE] at propagateAslWrapper (/app/node_modules/async-listener/index.js:468:23)
[NODE] at /app/node_modules/async-listener/glue.js:188:31
[NODE] at /app/node_modules/async-listener/index.js:505:70
[NODE] at /app/node_modules/async-listener/glue.js:188:31
[NODE] at
[NODE] Error: Can't set headers after they are sent.
[NODE] at SendStream.headersAlreadySent (/app/node_modules/send/index.js:402:13)
[NODE] at SendStream.send (/app/node_modules/send/index.js:625:10)
[NODE] at onstat (/app/node_modules/send/index.js:737:10)
[NODE] at /app/node_modules/async-listener/glue.js:188:31
[NODE] at FSReqWrap.oncomplete (fs.js:153:5)

The file exists and is readable

fix

Most helpful comment

@aasailan sendFile basically opens a stream. You should await on it and return response only then.

@Controller()
export class ServeSPAController {

  @Get('*')
  public async serveSpa (@Res() res: Response): Promise<any> {
    const fileName = path.resolve(__dirname, '../../../..', staticPath, 'index.html')
    await promisify<string, void>(res.sendFile.bind(res))(fileName)
    return res
  }
}

It would be better to promisify the prototype of Response but I'm not sure how to do that in this case

All 20 comments

Try to use return response.sendFile("/file")

You have to wait for #286.

If I use this code

 public async getImage(@Res() response: express.Response, @Params() params) {
        try {
            let filePath = path.join(path.normalize(ImagesFolder), params.path);
            response.type("jpg").send();
        } catch (err) {

        }
    }

I get the error

[NODE] Error: Can't set headers after they are sent.
[NODE] at validateHeader (_http_outgoing.js:489:11)
[NODE] at ServerResponse.setHeader (_http_outgoing.js:496:3)
[NODE] at ServerResponse.header (/app/node_modules/express/lib/response.js:730:10)
[NODE] at ServerResponse.send (/app/node_modules/express/lib/response.js:170:12)
[NODE] at ServerResponse.json (/app/node_modules/express/lib/response.js:256:15)
[NODE] at ExpressDriver.handleError (/src/driver/express/ExpressDriver.ts:348:26)
[NODE] at /src/RoutingControllers.ts:129:32
[NODE] at propagateAslWrapper (/app/node_modules/async-listener/index.js:468:23)
[NODE] at /app/node_modules/async-listener/glue.js:188:31
[NODE] at proxyWrapper (/app/node_modules/async-listener/index.js:477:29)
[NODE] at /app/node_modules/async-listener/index.js:505:70
[NODE] at /app/node_modules/async-listener/glue.js:188:31

Is this also caused by the issue that gets fixed in #286?

Yes, routing-controllers is trying to send the response even if you already send it using res object.

Is there a ETA for this PR?

@NoNameProvided and @pleerock have to review this PR, then it will be released with other merged PRs.

For now you can compile the npm package by yourself.

@19majkel94 can it be closed now?

I've updated to: [email protected] and this is my code:

 @Get('/File/:name')
  async GetFile(@Req() request: Request, @Res() response: Response) {
    let filePath = path.join(__dirname, '../public', request.params.name);
    console.log('File: ', filePath);
    return response.sendFile(filePath);
  }

I get error at NotFoundError.HttpError [as constructor] ...
I checked that file exists and has right permissions.

You need to return response, not void, which is the result of calling sendFile.

    sendFile(path: string): void;
    sendFile(path: string, options: any): void;
    sendFile(path: string, fn: Errback): void;
    sendFile(path: string, options: any, fn: Errback): void;

so how can solve it

@Get('/File/:name')
async GetFile(@Req() request: Request, @Res() response: Response) {
    let filePath = path.join(__dirname, '../public', request.params.name);
    console.log('File: ', filePath);
    response.sendFile(filePath);
    return response;
}

@19majkel94 I use your code,but it do not work, the framework still try to callback my CustomErrorHandler, which is implements ExpressErrorMiddlewareInterface銆俿o I use Response.headersSent in my CustomErrorHandler to judge whether the Response has been sent. the code look like this;

export class CustomErrorHandler implements ExpressErrorMiddlewareInterface {
    error(error: any, req: Request, res: Response) {
        // if (res.statusCode === 200) return;
        if (res.headersSent) return;
        res.status(500);
        res.json({
            status: 'fail',
            error: error.message
        });
    }
}

@Middleware({ type: 'after' })
export class CustomNotFoundHandler implements ExpressMiddlewareInterface {
    use(req: Request, res: Response) {
        // if (res.statusCode === 200) return;
        if (res.headersSent) return;
        res.status(404);
        res.json({
            status: 'fail',
            error: 'Not Found'
        });
    }
}

so how can solve it

I don't know what "it" is, please open new issue and describe your problem with code samples.

@aasailan sendFile basically opens a stream. You should await on it and return response only then.

@Controller()
export class ServeSPAController {

  @Get('*')
  public async serveSpa (@Res() res: Response): Promise<any> {
    const fileName = path.resolve(__dirname, '../../../..', staticPath, 'index.html')
    await promisify<string, void>(res.sendFile.bind(res))(fileName)
    return res
  }
}

It would be better to promisify the prototype of Response but I'm not sure how to do that in this case

@keenondrums Right, I was just showing and example of returning response object, not a working solution 馃槈

For cases like serving spa's index.html it's better to use serve-static rather than custom routes with sendFile.

@19majkel94 you can't use only serve static. You have to match every route except your api routes to index html. I combine them to serve other static files like js with static middleware and add serveSpa route to serve index.html

sendFile is not for serving SPA index.html. If you really can't use webpack-dev-server/nginx, please don't reinvent the wheel:
https://github.com/bripkens/connect-history-api-fallback/tree/master/examples/static-files-and-index-rewrite
Just register the history with static after create/use ExpressServer call, so all not matched routes can be handled.

@19majkel94 works lie a charmm. Thnx for the info!

#376 solved the problem and we have to return response (not return ;)to avoid the double return problem: ' Can't set headers after they are sent '.

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

Was this page helpful?
0 / 5 - 0 ratings

Related issues

iangregsondev picture iangregsondev  路  3Comments

mlarsson picture mlarsson  路  5Comments

azaslonov picture azaslonov  路  5Comments

humbertowoody picture humbertowoody  路  4Comments

davidquintard picture davidquintard  路  4Comments