You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift Vapor服务端:如何按需下载文件至磁盘或内存?

在Vapor中实现流式下载文件到磁盘(避免内存占用)

Vapor的ClientResponse响应体本身是ByteBuffer类型,支持流式读取,完全可以实现边下载边写入磁盘,无需将整个文件加载到内存中。以下是具体实现方案:

1. 流式写入磁盘的核心实现

通过逐段读取响应体并写入文件句柄,彻底解决大文件内存占用问题:

import Vapor
import NIO

func downloadFileToDisk(app: Application, url: String, headers: HTTPHeaders, filePath: String) async throws {
    // 构建带自定义头的请求
    let request = ClientRequest(
        method: .GET,
        url: URI(string: url),
        headers: headers
    )
    
    // 发送请求获取响应
    let response = try await app.client.send(request)
    
    // 校验响应状态
    guard response.status.isSuccessful else {
        throw Abort(.badGateway, reason: "下载失败,状态码:\(response.status.code)")
    }
    
    // 准备文件路径与句柄
    let fileURL = URL(fileURLWithPath: filePath)
    try FileManager.default.createDirectory(at: fileURL.deletingLastPathComponent(), withIntermediateDirectories: true)
    let fileHandle = try FileHandle(forWritingTo: fileURL)
    defer { try? fileHandle.close() }
    
    // 流式读取响应体并写入磁盘
    for try await bufferPart in response.body {
        try fileHandle.write(contentsOf: bufferPart.readableBytesView)
    }
}

2. 内存中读取小文件(兼容原有需求)

对于无需存盘的文件,可使用Vapor内置便捷方法直接读取到内存:

func downloadFileToMemory(app: Application, url: String, headers: HTTPHeaders) async throws -> String? {
    let request = ClientRequest(
        method: .GET,
        url: URI(string: url),
        headers: headers
    )
    
    let response = try await app.client.send(request)
    guard response.status.isSuccessful else { return nil }
    
    // 直接解码为字符串(也可读取为Data)
    return try await response.content.decode(String.self)
}

3. 批量处理多文件下载

利用TaskGroup并发处理不同类型的下载任务,区分存盘与内存存储:

func downloadMultipleFiles(app: Application) async throws {
    // 定义任务列表:(目标URL, 请求头, 是否存盘, 存盘路径/内存处理标记)
    let tasks: [(url: String, headers: HTTPHeaders, saveToDisk: Bool, path: String?)] = [
        ("https://example.com/large-file.zip", HTTPHeaders(), true, "./downloads/large.zip"),
        ("https://example.com/small-config.txt", HTTPHeaders(["Authorization": "Bearer your-token"]), false, nil)
    ]
    
    try await withThrowingTaskGroup(of: Void.self) { group in
        for task in tasks {
            if task.saveToDisk, let path = task.path {
                group.addTask {
                    try await downloadFileToDisk(app: app, url: task.url, headers: task.headers, filePath: path)
                }
            } else {
                group.addTask {
                    let content = try await downloadFileToMemory(app: app, url: task.url, headers: task.headers)
                    // 这里处理内存中的内容,比如解析、存入数据库等
                    print("内存读取内容:\(content ?? "空内容")")
                }
            }
        }
        try await group.waitForAll()
    }
}

关键说明

  • 流式写入的核心是利用response.body的AsyncSequence特性,逐段读取数据块写入磁盘,不会一次性占用大量内存。
  • 所有实现基于Vapor与NIO原生API,完全不需要依赖curl/wget等外部工具。
  • 自定义请求头的配置与你原有ClientRequest的用法完全兼容。

内容的提问来源于stack exchange,提问作者Anton

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 09:35:14