Comments (15)
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.js
Adjust 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.
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.
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.
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();
insidepromiseHandler
of its auto-generatedroutes.js
Adjust 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 :).
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.
@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.
@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.
@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.
made PR #45
from tsoa.
@AntoineABI This is live in 1.0.14.
from tsoa.
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.
@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.
@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.
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.
I struggled with this one for quite a while. Found the root of the problem for me to be here:
tsoa/src/routeGeneration/templates/koa.hbs
Line 154 in f4f5434
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.
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)
- Snyk security vulnerability for inflight (one of tsoa indirect dependencies) HOT 3
- Version 6.1.0 leads to missing packages, breaks existing builds HOT 20
- v6.1.0 breaking change on Authentication middleware HOT 1
- Using `Omit` causes extra model to be output in OpenAPI file HOT 2
- Cannot read properties of undefined (reading 'length') with @UploadedFile HOT 1
- Build error: Property 'name' is missing HOT 7
- Can't Handle File and Non-File Field on multipart/form-data simultaneously HOT 4
- Can't Handle Optional File Field on multipart/form-data HOT 1
- Proposal/file upload configurations HOT 2
- UnknownType: IndexedAccessType error when using enum to index type object HOT 1
- The latest version 6.1.4 causes new build issues. HOT 10
- Middleware after response HOT 6
- After update 6.1.5 Types of property 'successStatus' are incompatible HOT 16
- Handling parameters that include mapped types with `Date` as a generic argument HOT 1
- ErrorHandlerMiddleware is ignore with 6.1.* HOT 11
- Populate causes error log: Debug Failure. False expression: Node must have a real position for this operation HOT 3
- ' HOT 1
- Treat null values as no value or undefined HOT 2
- jsdoc @example is used for union type but ignored for enum HOT 3
- programmatic way to generate the spec and routes HOT 12
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
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.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from tsoa.