Coder Social home page Coder Social logo

Comments (15)

alokrajiv avatar alokrajiv commented on April 29, 2024 18

So, the soln is exactly that. The stream is being piped continuously but is not waited on. So, before my end hits from the stream's end event, tsoa's generated controller issues its own response.status(statusCode || 204).end(); inside promiseHandler of its auto-generated routes.jsAdjust for that and everything works. Viola!

const mystream = fs.createReadStream("/path/to/test.jpg");
mystream.pipe(req.res);
await new Promise((resolve, reject) => {
  mystream.on("end", () => {
    req.res.end();
    resolve();
  });
});

Fyi, to anyone coming here for a similar issue: the above code is quite simple and doesn't handle any edge case handling of the stream itself. Its only to demonstrate and fix the usage in this context. If you are really implementing, might want to consider- pausing the stream first, pipe to res only on success, then resume, and resolve the promise only on end event ( or of course reject the promise on error events). I'm not doing that in the example in the interest of simplicity :).

from tsoa.

AmazingTurtle avatar AmazingTurtle commented on April 29, 2024 7

I also found a workaround for manually doing the routes. Basically you can get the response from the request object in express. Cast like this: const response = (<any>request).res as express.Response.

I'm using a custom routes template, to do nothing if the promise returns null explicitly.
Then I just overwrite the swagger "generates" part for the specific route.

This is how I return binary data like that.

    @Security('jwt')
    @Get('getAttachment/{attachmentId}')
    public async getAttachment(@Request() request: express.Request,
                               @Path() attachmentId: string) {
        const response = (<any>request).res as express.Response;

        console.log((<any>request).res, (<any>request).res === <any>response);

        const attachment = await this.attachmentService.getAttachment(attachmentId);
        if (attachment.found) {
            response.end(new Buffer(attachment._source.data, 'base64'));
        }
        return null;
        
    }

from tsoa.

mb21 avatar mb21 commented on April 29, 2024 3

Since we have this line in the express template now, you can simple return a Readable stream in your controller. However, a Buffer will not work.

Still need to do e.g. this.setHeader('Content-Type', 'image/png') though.

from tsoa.

stories2 avatar stories2 commented on April 29, 2024 2

So, the soln is exactly that. The stream is being piped continuously but is not waited on. So, before my end hits from the stream's end event, tsoa's generated controller issues its own response.status(statusCode || 204).end(); inside promiseHandler of its auto-generated routes.jsAdjust for that and everything works. Viola!

const mystream = fs.createReadStream("/path/to/test.jpg");
mystream.pipe(req.res);
await new Promise((resolve, reject) => {
  mystream.on("end", () => {
    req.res.end();
    resolve();
  });
});

Fyi, to anyone coming here for a similar issue: the above code is quite simple and doesn't handle any edge case handling of the stream itself. Its only to demonstrate and fix the usage in this context. If you are really implementing, might want to consider- pausing the stream first, pipe to res only on success, then resume, and resolve the promise only on end event ( or of course reject the promise on error events). I'm not doing that in the example in the interest of simplicity :).

@alokrajiv

tsoa == 2.5.13

First, ty for save my life.

In my case, must set response status 206. If not response status will set 200 automatically and response close.

@Get('/download')
    public download(@Request() request: any, @Query() rfid: number): Promise<any> {
        .
        .
        .
            response.status(HttpStatusCode.PARTIAL_CONTENT);
            response.setHeader('Content-Range', `bytes ${start}-${end}/${fileStat.size}`)
            response.setHeader('Content-Length', start === end ? 0 : (end - start) + 1);
            response.setHeader('Content-Type', resource.Type);
            response.setHeader('Accept-Ranges', 'bytes');
            response.setHeader('Cache-Control', 'no-cache');
            stream = fs.createReadStream(filePath, {
                start,
                end
            });
            stream.pipe(response);
            await new Promise((resolve, reject) => {
                stream.on('end', () => {
                    response.end();
                    resolve();
                })
            })
        .
        .
        .
    }

This link is my partial content response reference.

from tsoa.

alokrajiv avatar alokrajiv commented on April 29, 2024 1

@dgreene1 Interestingly, you see my second example I'm already using req.res.end.

But, you are right that, it is async because of its a stream after all, so I can't use promises. Maybe, I need to manually return a promise when the end event hits on the stream. I'll update here if I am able to crack it. Thanks anyways.

from tsoa.

lukeautry avatar lukeautry commented on April 29, 2024

@AntoineABI I think the solution here is going to be to improve the merge logic between custom/"provided" spec and the generated spec; ideally you should be able to override the generated spec when you need to.

If you're inclined to take a stab at a solution, the merge is happening here: https://github.com/lukeautry/tsoa/blob/master/src/swagger/specGenerator.ts#L47

We'd just need to do something custom there instead of using Object.assign, or it may be as easy as swapping the source/destination.

from tsoa.

AntoineABI avatar AntoineABI commented on April 29, 2024

@lukeautry That's whats I though ! We could use something like https://www.npmjs.com/package/merge which do exactly what I was expected.

from tsoa.

AntoineABI avatar AntoineABI commented on April 29, 2024

made PR #45

from tsoa.

lukeautry avatar lukeautry commented on April 29, 2024

@AntoineABI This is live in 1.0.14.

from tsoa.

alokrajiv avatar alokrajiv commented on April 29, 2024

ok, am I missing something here? The exact same code that works on a typical express response object, fails with no warning on tsoa.

Plain Express (this works):

router.get("/test", (req: express.Request) => {
  fs.createReadStream("/path/to/test.jpg").pipe(req.res);
});

Tsoa (doesn't work - 204 No Content):

@Get()
public view(@Request() req: express.Request ) {
  fs.createReadStream("/path/to/test.jpg").pipe(req.res);
}

@AmazingTurtle Do you think there anything happening under the hood that's making the difference?

from tsoa.

dgreene1 avatar dgreene1 commented on April 29, 2024

@alokrajiv, while visually scanning your two examples, I noticed that you’re missing a return statement in the second example since it’s not a “fat arrow” function like you have in your express example.

Side note: Since tsoa was created for typed data responses, maybe returning a jpeg is not the correct use of tsoa. Forgive me if that was just an example. But if it isn’t, might I recommend uploading your images to Amazon S3 (or any static file service) and serving them from there?

from tsoa.

alokrajiv avatar alokrajiv commented on April 29, 2024

@dgreene1 I've tried these too.

@Get()
public view(@Request() req: express.Request ) {
  fs.createReadStream("/path/to/test.jpg").pipe(req.res);
  return;
}
@Get()
public view(@Request() req: express.Request ) {
  const mystream = fs.createReadStream("/path/to/test.jpg");
  mystream.on("end", () => req.res.end());
  mystream.pipe(req.res);
  return;
}

None seems to be able to get it across. How are usually files served in tsoa at the moment? Even if its not a stream? (though i prefer a stream, because I'm pulling this data from an upstream server that is picking the file from S3/GridFS. So, streams would have been ideal to decrease footprint and make it fast)

from tsoa.

dgreene1 avatar dgreene1 commented on April 29, 2024

Forgive me for not seeing this earlier, but it actually has nothing to do with the return. I believe the code you’re writing (and this is a guess, just like my previous guess) is asynchronous because you’re using .on. So I would recommend using res.end( like in @AmazingTurtle ‘s example.

from tsoa.

cjam avatar cjam commented on April 29, 2024

I struggled with this one for quite a while. Found the root of the problem for me to be here:

if (data || data === false) {

The automatic setting of the status resets many of the properties on the response including header and length, also resets body to null in the case of the 204. I wanted a way that I could leverage existing libraries like koa-send so this is the solution that I ended on which feels like it could potentially be a simple enhancement for tsoa.

In my Controller Base which inherits from TSOA's I included a simple get and set for bypassing default handling logic:

    private _bypassDefaultHandling:boolean = false
    get bypassDefaultHandling():boolean{
        return this._bypassDefaultHandling;
    }

    set bypassDefaultHandling(val:boolean){
        this._bypassDefaultHandling = val;
    }

In the promiseHandler in the route template I just check to see if the default handling should be bypassed for this request:

function shouldBypassHandling(controller:any){
    return 'bypassDefaultHandling' in controller && controller.bypassDefaultHandling;
}

export function promiseHandler(controllerObj: any, promise: Promise<any>, context: any, next: () => Promise<any>) {
    return Promise.resolve(promise)
      .then((data: any) => {
          if(shouldBypassHandling(controllerObj)){
              return next();
          }

          if (data || data === false) {
              context.body = data;
              context.status = 200;
          } else {
              context.status = 204;
          }

          if (isController(controllerObj)) {
              const headers = controllerObj.getHeaders();
              Object.keys(headers).forEach((name: string) => {
                  context.set(name, headers[name]);
              });

              const statusCode = controllerObj.getStatus();
              if (statusCode) {
                  context.status = statusCode;
              }
          }
          return next();
      })
      .catch((error: any) => {
          context.status = error.status || 500;
          context.body = error;
          return next();
      });
  }

Which allows me to handle files like you normally would with vanilla koa which is kind of nice: (note: send is via https://github.com/koajs/send)

    @Get('/test/{testId}/results')
    public async downloadPdf(testId: string, @Request() request: KoaRequest) {
        this.bypassDefaultHandling = true;
        await send(request.ctx,"some-file.pdf",{
            root:"/somedirectory/",
        });
    }

I've ran into a couple places where I think bypassing the default behavior could be useful, but even just for a way to support files in a clean way is nice.

from tsoa.

sundowndev avatar sundowndev commented on April 29, 2024

Before using tsoa all I had to do was

res.download('/path/to/file');

But

this.setStatus(200);

req.res.download(filePath, filename);

does not seem to work. #44 (comment) doesn't seem to work either.

EDIT1: this is not working for me because req.res is undefined.

from tsoa.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.