Alamofire请求Docker部署FastAPI流接口时JSON分片解码失败
问题分析与解决方案
核心问题定位
你的问题本质是Docker部署环境下,FastAPI的流式输出被代理/服务器缓冲,导致iOS客户端收到的TCP分片并非按单条JSON行分割,而本地运行时无缓冲,客户端能正确拿到单条JSON的分片。Postman、Swagger、安卓正常是因为这些客户端会自动处理NDJSON(换行分隔JSON)的分片合并与分割,而你的Alamofire代码直接解码每个原始TCP分片,自然会出现部分JSON的解码错误。
解决方案分两步:服务器端+客户端调整
一、服务器端:关闭输出缓冲
Docker部署时,Uvicorn或反向代理会默认开启输出缓冲,把多个yield的JSON行合并成一个大分片发送,必须禁用缓冲:
1. Uvicorn启动参数添加无缓冲
修改Docker启动命令,在uvicorn后加--no-buffer:
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--no-buffer"]
2. 若使用反向代理(如Nginx),禁用代理缓冲
如果你的Docker服务前有Nginx等反向代理,添加以下配置:
location /json/stream { proxy_pass http://fastapi:8000; proxy_buffering off; # 核心:关闭代理缓冲 chunked_transfer_encoding on; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; }
二、客户端:手动按行分割NDJSON
Alamofire的streamRequest返回的是TCP层面的原始分片,不一定对应服务器yield的单条JSON。必须维护一个缓冲区,按换行符分割出完整的JSON行再解码:
1. 修改ViewModel代码,添加缓冲区与行分割逻辑
import SwiftUI import Combine import Alamofire class ViewModel: ObservableObject { @Published var jsonObjects: [AllJsonsStream] = [] private var buffer = Data() // 新增:数据缓冲区 private let newlineData = "\n".data(using: .utf8)! fileprivate lazy var alamoSession: Session = { let configuration = URLSessionConfiguration.default configuration.timeoutIntervalForRequest = 20 configuration.urlCache?.removeAllCachedResponses() return Alamofire.Session(configuration: configuration) }() fileprivate func alamofireChunked () { guard let url = URL(string: "https://mydockerfastapiurl/json/stream") else { return } let parameters: [String: Any] = [ "arg1": val1 ] var request = URLRequest(url: url) request.httpMethod = "POST" request.addValue("application/json", forHTTPHeaderField: "Content-Type") // 修正:Accept改为服务器返回的NDJSON类型 request.addValue("application/x-ndjson", forHTTPHeaderField: "Accept") // 移除:不要手动设置transfer-encoding,URLSession会自动处理 print(request.headers) do { request.httpBody = try JSONSerialization.data(withJSONObject: parameters, options: .prettyPrinted) } catch { print("ERROR: \(error)") } alamoSession.streamRequest(request).validate().responseStream { [weak self] response in guard let self = self else { return } switch response.event { case .stream(let data): self.buffer.append(data) self.processBuffer() // 处理缓冲区中的完整JSON行 case .complete(let result): self.processBuffer(finish: true) // 处理剩余的最后一行 print("stream is finished") print("response:", result) case .failure(let error): print("stream error:", error) } } } // 新增:按换行分割缓冲区,解码完整JSON行 private func processBuffer(finish: Bool = false) { while let newlineIndex = buffer.firstIndex(of: newlineData.first!) { let lineData = buffer[..<newlineIndex] buffer = buffer[newlineIndex+1...] if !lineData.isEmpty { do { let decoded = try JSONDecoder().decode(AllJsonsStream.self, from: lineData) DispatchQueue.main.async { self.jsonObjects.append(decoded) } print("Decoded:", decoded) } catch { print("Decode error:", error) } } } // 流结束时处理缓冲区剩余的最后一行(可能无换行) if finish && !buffer.isEmpty { do { let decoded = try JSONDecoder().decode(AllJsonsStream.self, from: buffer) DispatchQueue.main.async { self.jsonObjects.append(decoded) } print("Decoded final line:", decoded) } catch { print("Final line decode error:", error) } buffer.removeAll() } } } struct ContentView: View { private var urlString = "" @StateObject var viewModel = ViewModel() // 修正:用@StateObject管理ObservableObject var body: some View { VStack { Button { viewModel.alamofireChunked() } label: { Text("Fetch stream") } // 示例:展示收到的JSON对象 List(viewModel.jsonObjects) { item in Text("\(item)") } } } }
关键修正点说明
- 服务器端禁用缓冲:确保每个
yield的JSON行被立即发送,不被合并 - 客户端按行分割:NDJSON的标准是每行一个JSON,必须手动处理分片合并与分割,不能依赖TCP分片刚好对应单条JSON
- 修正请求头:
Accept改为application/x-ndjson,与服务器返回的media_type一致,避免代理或服务器的内容协商异常 - 移除手动设置的
transfer-encoding:URLSession会自动处理分块传输,手动设置可能导致冲突
内容的提问来源于stack exchange,提问作者rockettradr
相关产品推荐
相关产品推荐

