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
Send file / return raw binary "application/octect-stream" or "image/jpeg" ... #44
Comments
@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. |
@lukeautry That's whats I though ! We could use something like https://www.npmjs.com/package/merge which do exactly what I was expected. |
made PR #45 |
@AntoineABI This is live in 1.0.14. |
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: I'm using a custom routes template, to do nothing if the promise returns null explicitly. 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;
} |
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):
Tsoa (doesn't work - 204 No Content):
@AmazingTurtle Do you think there anything happening under the hood that's making the difference? |
@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? |
@dgreene1 I've tried these too.
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) |
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 |
@dgreene1 Interestingly, you see my second example I'm already using 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. |
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
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 == 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.
This link is my partial content response reference. |
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 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: @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. |
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. |
Since we have this line in the express template now, you can simple return a Still need to do e.g. |
Hello,
I am struggling on something I wan't to do and it's seems not possible to do it with the tsoa framework.
Returning raw data or file is not allowed by using a standard controller. Obviously, I can define my own express route and custom controller but I can't add the spec to swagger.json automatically because when I fill the spec field in tsoa.json it is overriding every paths !
Proposal:
My solution:
(not yet done wanna see if you guys have another way around or if I missed something?)
Regards.
PS: It is whats looks like my tsoa.json if it can help
The text was updated successfully, but these errors were encountered: