koa
koa copied to clipboard
[fix] Download file
Describe the bug
Node.js version: 16
OS version: docker (alpine)
Description: WhenI go to http://locahost:9000/test, a download of /app/file.xml should be initiated in my browser.
Actual behavior
I don't get the file content but the string "OK" (same behavior with another file or path)
$ curl http://localhost:9000/test
OK
$ curl -I http://localhost:9000/test
Content-Disposition: attachment; filename="numeezy.com.mobileconfig"
Content-Type: application/xml
Date: Sat, 03 Sep 2022 19:55:26 GMT
Connection: keep-alive
Keep-Alive: timeout=5
Code to reproduce
const Koa = require('koa');
const Router = require('koa-router');
const app = new Koa();
const router = new Router();
router.get('/test', (ctx) => {
ctx.response.attachment('/app/file.xml');
ctx.response.set('Content-Type', 'application/xml');
ctx.response.status = 200;
});
app.use(router.routes());
app.use(router.allowedMethods());
app.listen(process.env.PORT || 9000);
Thanks for your help
This isn't a bug. ctx.attachment()
(delegated to ctx.response.attachment
) only correctly sets Content-Disposition headerfield. You actually needs to provide the file you want the client to download yourself.
Try assigning a stream to body, e.g.:
ctx.body = fs.createReadStream(path)
@aellert For now, you can solve your problem by replacing the 'attachment' method with 2 lines;
router.get('/test', (ctx) => {
const filename = '/app/file.xml';
ctx.response.set('Content-disposition', 'attachment; filename=' + filename);
ctx.response.set('Content-Type', 'application/xml');
ctx.response.status = 200;
ctx.body = fs.createReadStream(filename); // don't forget to import the file system module
});
And you can add your own utility like this if you want a re-usable logic;
function download(ctx) {
return (sourceFileNamePath, config = {}) => {
const { status = 200, headers = {} } = config;
const responseHeaders = {
'content-Type': 'application/force-download',
...headers,
'content-disposition': `attachment; filename=${sourceFileNamePath}`
}
ctx.response.set(responseHeaders);
ctx.response.status = status;
ctx.response.body = fs.createReadStream(sourceFileNamePath);
}
}
I'm not sure but maybe we will need to create middleware or add a
download
method to make life easier here.