File upload and download processing
File Upload Handling
File upload is a common functional requirement in web development. Koa2 simplifies the file upload process through its middleware mechanism. The koa-body
middleware can be used to easily implement file upload functionality. First, install the dependency:
npm install koa-body
Basic configuration example:
const Koa = require('koa');
const koaBody = require('koa-body');
const app = new Koa();
app.use(koaBody({
multipart: true,
formidable: {
maxFileSize: 200*1024*1024, // Set upload file size limit, default 2M
keepExtensions: true // Preserve file extensions
}
}));
app.use(async ctx => {
if (ctx.method === 'POST' && ctx.url === '/upload') {
const file = ctx.request.files.file; // Get the uploaded file
// File processing logic...
ctx.body = 'File uploaded successfully';
}
});
app.listen(3000);
Frontend HTML form example:
<form action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="file">
<button type="submit">Upload</button>
</form>
For handling multiple file uploads, use the multiple
attribute in the frontend:
<input type="file" name="files" multiple>
Backend processing code:
const files = ctx.request.files.files; // Get the array of files
files.forEach(file => {
// Process each file
});
File Upload Security Considerations
File upload functionality requires special attention to security issues:
- File type validation:
const ALLOWED_TYPES = ['image/jpeg', 'image/png'];
if (!ALLOWED_TYPES.includes(file.type)) {
ctx.throw(400, 'Unsupported file type');
}
- Rename files to prevent directory traversal attacks:
const path = require('path');
const fs = require('fs');
const ext = path.extname(file.name);
const newFilename = `${Date.now()}${ext}`;
const filePath = path.join(__dirname, 'uploads', newFilename);
fs.renameSync(file.path, filePath);
- File content inspection:
const fileBuffer = fs.readFileSync(file.path);
if (fileBuffer.toString('utf8', 0, 2) === '#!') {
ctx.throw(400, 'Potential executable file');
}
File Download Handling
Koa2 provides multiple ways to handle file downloads. The simplest method is to set response headers directly:
app.use(async ctx => {
if (ctx.path === '/download') {
const filePath = path.join(__dirname, 'files', 'example.pdf');
ctx.set('Content-Disposition', 'attachment; filename="example.pdf"');
ctx.body = fs.createReadStream(filePath);
}
});
For large file downloads, use streaming:
const fileStream = fs.createReadStream(filePath);
fileStream.on('error', err => {
ctx.throw(404, 'File does not exist');
});
ctx.set('Content-Length', fs.statSync(filePath).size);
ctx.body = fileStream;
Resumable Download Implementation
For large file downloads, implementing resumable downloads can enhance user experience:
app.use(async ctx => {
if (ctx.path === '/download') {
const filePath = path.join(__dirname, 'files', 'large.zip');
const stat = fs.statSync(filePath);
const range = ctx.headers.range;
if (range) {
const parts = range.replace(/bytes=/, "").split("-");
const start = parseInt(parts[0], 10);
const end = parts[1] ? parseInt(parts[1], 10) : stat.size - 1;
const chunksize = (end - start) + 1;
ctx.set('Content-Range', `bytes ${start}-${end}/${stat.size}`);
ctx.set('Accept-Ranges', 'bytes');
ctx.set('Content-Length', chunksize);
ctx.status = 206;
const file = fs.createReadStream(filePath, {start, end});
ctx.body = file;
} else {
ctx.set('Content-Length', stat.size);
ctx.body = fs.createReadStream(filePath);
}
}
});
File Download Progress Display
The frontend can display download progress using XMLHttpRequest:
const xhr = new XMLHttpRequest();
xhr.open('GET', '/download/large.zip', true);
xhr.responseType = 'blob';
xhr.onprogress = function(e) {
if (e.lengthComputable) {
const percentComplete = (e.loaded / e.total) * 100;
console.log(`${percentComplete}% downloaded`);
}
};
xhr.onload = function() {
if (this.status === 200) {
const blob = this.response;
const a = document.createElement('a');
a.href = window.URL.createObjectURL(blob);
a.download = 'large.zip';
a.click();
}
};
xhr.send();
Compressed File Download
Use the archiver library to download multiple files as a compressed archive:
const archiver = require('archiver');
app.use(async ctx => {
if (ctx.path === '/download-zip') {
const archive = archiver('zip', {
zlib: { level: 9 } // Set compression level
});
ctx.set('Content-Type', 'application/zip');
ctx.set('Content-Disposition', 'attachment; filename="files.zip"');
archive.pipe(ctx.res);
archive.directory('public/files/', false);
archive.finalize();
}
});
File Upload Progress Monitoring
The frontend can monitor upload progress using FormData and XMLHttpRequest:
const formData = new FormData();
formData.append('file', fileInput.files[0]);
const xhr = new XMLHttpRequest();
xhr.open('POST', '/upload', true);
xhr.upload.onprogress = function(e) {
if (e.lengthComputable) {
const percentComplete = (e.loaded / e.total) * 100;
console.log(`${percentComplete}% uploaded`);
}
};
xhr.onload = function() {
if (xhr.status === 200) {
console.log('Upload successful');
}
};
xhr.send(formData);
Cloud Storage Integration
Example of integrating AWS S3 storage:
const AWS = require('aws-sdk');
const s3 = new AWS.S3({
accessKeyId: 'YOUR_ACCESS_KEY',
secretAccessKey: 'YOUR_SECRET_KEY'
});
app.use(async ctx => {
if (ctx.method === 'POST' && ctx.url === '/upload-s3') {
const file = ctx.request.files.file;
const params = {
Bucket: 'your-bucket-name',
Key: `uploads/${Date.now()}_${file.name}`,
Body: fs.createReadStream(file.path)
};
try {
const data = await s3.upload(params).promise();
ctx.body = { url: data.Location };
} catch (err) {
ctx.throw(500, 'Failed to upload to S3');
}
}
});
File Management API Design
A complete file management API example:
const router = require('koa-router')();
const File = require('../models/file'); // Assuming a File model exists
router.post('/files', async ctx => {
const file = ctx.request.files.file;
const newFile = await File.create({
name: file.name,
size: file.size,
type: file.type,
path: `/uploads/${file.name}`
});
ctx.body = newFile;
});
router.get('/files', async ctx => {
const files = await File.find();
ctx.body = files;
});
router.get('/files/:id', async ctx => {
const file = await File.findById(ctx.params.id);
if (!file) ctx.throw(404);
ctx.set('Content-Disposition', `attachment; filename="${file.name}"`);
ctx.body = fs.createReadStream(path.join(__dirname, file.path));
});
router.delete('/files/:id', async ctx => {
const file = await File.findByIdAndRemove(ctx.params.id);
if (!file) ctx.throw(404);
fs.unlinkSync(path.join(__dirname, file.path));
ctx.status = 204;
});
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:Session 管理的实现方案
下一篇:数据流式传输优化