Published on

文件流式上传

Authors
  • avatar
    Name
    yushenw
    Linkedin

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.RequestBody 字段是一个 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 作为一个数据流,并且逐步地处理这个流,而不是一次性地处理整个数据块。