question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Send Buffer/binary response to client

See original GitHub issue

https://github.com/lukeautry/tsoa/issues/435 and #44 were both closed but the issue is still here. Is there any way to send files to client using tsoa? Why doesn’t @Response decorator exists ?

Sorting

  • I’m submitting a …

    • bug report
    • feature request
    • support request
  • I confirm that I

    • used the search to make sure that a similar issue hasn’t already been submit

Expected Behavior

Router to send file buffer to be downloaded by the client.

Current Behavior

When using Attempt 1: the file is sent but the file is invalid and cannot be opened.

When using Attempt 2:

TypeError: Cannot read property 'on' of undefined
    at ReadStream.Readable.pipe (_stream_readable.js:669:8)
    at OrderETicketsController.read (/app/dist/controllers/orderEtickets/controllers.js:22:24)
    at processTicksAndRejections (internal/process/task_queues.js:97:5)
error Command failed with signal "SIGINT".

Possible Solution

Steps to Reproduce

Attempt 1

this.setStatus(200);
this.setHeader('Content-Type', 'application/pdf');
this.setHeader('Content-Disposition', `attachment; filename=${fileName}`);

return fs.promises.readFile(filePath);

Attempt 2

req.res.download(filePath) // req.res is undefined!

Context (Environment)

Version of the library: 3.3.0 (latest) Version of NodeJS: v12.18.1

  • Confirm you were using yarn not npm: [x]

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Reactions:2
  • Comments:6

github_iconTop GitHub Comments

2reactions
sundowndevcommented, Oct 7, 2020

Ok I finally managed to do it!

The generated routes file already handles streams, you just have to return a valid readable stream.

function returnHandler(response: any, statusCode?: number, data?: any, headers: any = {}) {
    Object.keys(headers).forEach((name: string) => {
      response.set(name, headers[name]);
    });

    if (data && typeof data.pipe === 'function' && data.readable && typeof data._read === 'function') {
      // Here, data.pipe must be a function
      data.pipe(response);

    } else if (data || data === false) { // === false allows boolean result
      response.status(statusCode || 200).json(data);
    } else {
      response.status(statusCode || 204).end();
    }
  }

Controller:

public async download(): Promise<fs.ReadStream> {
      const filePath: string = '/path/to/file';
      const fileName: string = 'doc.pdf';

      const stat: fs.Stats = await fs.promises.stat(filePath);

      this.setStatus(200);
      this.setHeader('Content-Type', mime.lookup(filePath));
      this.setHeader('Content-Length', stat.size.toString());
      // Removing this line will cause to not launch the download, just serve the file as it
      this.setHeader('Content-Disposition', `attachment; filename=${fileName}`);

      return fs.createReadStream(filePath);
  }

The solution is more complicated than using res.download but it doesn’t even work because type fs.ReadStream cannot be imported by tsoa. So there’s an option in routes.ts to use readable stream but it’s impossible to return a stream type from the controller. I’d suggest to implement a feature to make file download easier (like it is already with Express).

Generate routes error.
 Error: No matching module declarations found for fs.
    at new GenerateMetadataError (/.../node_modules/tsoa/dist/metadataGeneration/exceptions.js:22:28)

So the only way I found to fix this is to use Readable interface from stream :

import { Readable } from 'stream';

// ...
public async download(): Promise<Readable> {
  // ...

The documentation is wrong but it works as needed.

image

0reactions
mb21commented, Jul 25, 2022

Adding something like this to the default express template should fix it:

import { Readable } from 'stream';

...

} else if (data instanceof Buffer) {
  Readable.from(data).pipe(response);
Read more comments on GitHub >

github_iconTop Results From Across the Web

Send a binary Buffer to client through http.ServerResponse in ...
I managed to find out the answer. Just add "binary" encoding to both write() and end(). res.write(buffer,'binary'); res.end(null, 'binary');.
Read more >
A guide to sending things in Express.js | by John Au-Yeung
The Express response object lets us send a response to the client. Various kinds of responses ... For example, we can send a...
Read more >
Sending and Receiving Binary Data - Web APIs | MDN
The response property will contain the entity body according to responseType , as an ArrayBuffer , Blob , Document , JSON , or...
Read more >
How to send Buffer with express to client? : r/node - Reddit
A Buffer is just a stream of bytes. It's up to the client to decide how to parse them. Octet-stream should be the...
Read more >
How to handle binary data in Node.js? - Mario Kandut
The Buffer constructor is a global , hence, no import is needed to use it. Type node -p "Buffer" in your terminal and...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found