- Published on
文件流式上传
NodeJS例子
客户端
<!DOCTYPE html> <html> <body> <h2>File Upload Example</h2> <input type="file" id="fileInput"> <button onclick="uploadFile()">Upload</button> <script src="script.js"></script> </body> </html>
function uploadFile() { const input = document.getElementById('fileInput'); const file = input.files[0]; // 获取选中的文件 const formData = new FormData(); formData.append('file', file); // 创建一个FormData对象,并添加文件 fetch('http://localhost:3000/upload', { // 发送POST请求到服务器 method: 'POST', body: formData }) .then(response => response.json()) .then(data => console.log(data)) .catch(error => console.error(error)); }
服务端
依赖安装 首先,你需要安装必要的Node.js模块。在这个例子中,我们将使用 express 作为web框架,multer 作为处理文件上传的中间件。你可以通过运行以下命令来安装它们:
npm install express multer
const express = require('express'); const multer = require('multer'); const fs = require('fs'); const path = require('path'); const app = express(); const upload = multer(); // 使用multer处理数据流 app.post('/upload', upload.single('file'), (req, res) => { if (!req.file) { return res.status(400).send('No file uploaded.'); } // 创建一个可写流将文件写入磁盘 const targetPath = path.join(__dirname, 'uploads', req.file.originalname); const writableStream = fs.createWriteStream(targetPath); // 使用流式处理写入文件 writableStream.write(req.file.buffer, (err) => { if (err) { return res.status(500).send('Error saving file.'); } res.send('File uploaded successfully.'); }); }); app.listen(3000, () => { console.log('Server started on port 3000'); });
这个服务器监听在3000端口,并处理指向 /upload 的POST请求。使用 multer 库来处理上传的文件。Multer会处理数据流,并将上传的文件暂存在内存中。然后我们创建一个可写流(Writable Stream),并将文件内容写入到磁盘上的特定位置。 这只是一个简单的例子,在实际部署这样的服务时,需要考虑安全性问题,如限制上传文件的大小、类型,以及处理潜在的错误情况。
GO流式上传例子
客户端
客户端的代码将创建一个HTTP请求,其中包含要上传的文件数据。这里我们使用标准库 net/http 来发送请求。
package main import ( "bytes" "io" "mime/multipart" "net/http" "os" ) func main() { // 打开要上传的文件 file, err := os.Open("path/to/your/file") if err != nil { panic(err) } defer file.Close() // 创建一个新的表单数据缓冲 body := &bytes.Buffer{} writer := multipart.NewWriter(body) part, err := writer.CreateFormFile("file", "filename") if err != nil { panic(err) } // 将文件复制到表单 io.Copy(part, file) writer.Close() // 发送POST请求 request, err := http.NewRequest("POST", "http://localhost:8080/upload", body) if err != nil { panic(err) } request.Header.Add("Content-Type", writer.FormDataContentType()) client := &http.Client{} response, err := client.Do(request) if err != nil { panic(err) } defer response.Body.Close() // 输出响应状态 io.Copy(os.Stdout, response.Body) }
服务端
package main import ( "io" "net/http" "os" ) func main() { http.HandleFunc("/upload", func(w http.ResponseWriter, r *http.Request) { // 只允许POST方法 if r.Method != "POST" { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } // 打开一个文件用于保存上传的内容 file, err := os.Create("uploaded_file") if err != nil { http.Error(w, "Internal server error", http.StatusInternalServerError) return } defer file.Close() // 直接将请求体写入文件 _, err = io.Copy(file, r.Body) if err != nil { http.Error(w, "Internal server error", http.StatusInternalServerError) return } w.WriteHeader(http.StatusOK) w.Write([]byte("File uploaded successfully")) }) http.ListenAndServe(":8080", nil) }
在Go服务端使用 io.Copy(file, r.Body) 时就实现了流式上传,这里的关键在于理解 r.Body 的性质和 io.Copy 的工作方式。
r.Body 的性质
在Go的HTTP服务器中,r *http.Request 的 Body 字段是一个 io.ReadCloser 接口。这意味着 Body 不是一个静态的数据块或整个文件的内存表示,而是一个可以从中连续读取数据的流。当HTTP请求被接收时,Body 流是逐步填充的,这特别适用于大文件或数据流,因为它们不需要完全加载到内存中。
io.Copy 的工作方式
io.Copy 函数在Go中用于从一个流(源)复制数据到另一个流(目的地)。在这种情况下,源是 r.Body,目的地是一个文件。io.Copy 会从 r.Body 读取数据块并将它们写入文件,直到整个流被读取完毕。
这个过程是逐步的,意味着数据是一边从网络接收,一边写入文件,而不是等待整个请求体被接收后再开始处理。这就是为什么 io.Copy(file, r.Body) 表现为流式上传:文件数据是在不断到达时即时写入的,而不是在全部到达后才写入。
流式处理的优点
- 内存效率:对于大文件,不需要将整个文件内容加载到内存中,从而节省了大量内存。
- 速度:处理可以开始得更早,而不是等到整个文件都被上传。这对于大文件或慢速网络连接特别有利。
- 可扩展性:服务器能够更好地处理多个并发上传,因为它不需要为每个上传的文件分配大量内存。
总结来说,io.Copy(file, r.Body) 在Go中实现流式上传的关键在于它如何利用 r.Body 作为一个数据流,并且逐步地处理这个流,而不是一次性地处理整个数据块。