SwiftUI上传图片到reqres服务器返回520响应问题咨询
iOS端图片上传请求无法返回2xx响应问题修复
问题背景
- 已完成JSON格式POST上传功能测试,向reqres.in、AWS API Gateway接口发送JSON数据时运行正常
- 复用相同请求逻辑上传本地PNG图片时,尝试将
Content-Type修改为multipart/form-data、image/png、blob/png均无法获得2xx区间正常响应
正常运行的JSON上传代码
import SwiftUI import Foundation struct CommentData: Encodable, Decodable { var comment: String var userId: String } struct TestUpload: View { @State private var confirmationMessage = "" @State private var showingConfirmation = false func uploadData() async { let replyToSend = CommentData(comment: "Hello from app", userId: "bkim") guard let encoded = try? JSONEncoder().encode(replyToSend) else { print("Failed to encode data") return } let url = URL(string: "https://reqres.in/api/bkim")! var request = URLRequest(url: url) request.setValue("application/json", forHTTPHeaderField: "Content-Type") request.httpMethod = "POST" do { let (data, response) = try await URLSession.shared.upload(for: request, from: encoded) print(data) print(response) // It will automatically send back data let decodedData = try JSONDecoder().decode(CommentData.self, from: data) print(decodedData) confirmationMessage = "You received reply data with userID: \(decodedData.userId) desc: \(decodedData.comment)" showingConfirmation = true } catch { print("URL upload failed") } } var body: some View { ZStack { Image(imagePath) VStack { Spacer() Button(action: {Task{await uploadData()}}) { Text("Upload") } } } .alert("Thank you!", isPresented: $showingConfirmation) { Button("OK") {} } message: { Text(confirmationMessage) } } } struct TestUpload_Previews: PreviewProvider { static var previews: some View { TestUpload() } }
存在问题的图片上传代码
private var imagePath = "PurpleAnnotated" func uploadImage() async { guard let problemPhoto = UIImage(named: self.imagePath) else { print("Failed to get photo") return } let imageData = problemPhoto.pngData()! let url = URL(string: "https://reqres.in/api/bkim")! var request = URLRequest(url: url) request.setValue("multipart/form-data", forHTTPHeaderField: "Content-Type") request.httpMethod = "POST" do { let (data, response) = try await URLSession.shared.upload(for: request, from: imageData) print(response) print(data) confirmationMessage = "You sent \(imageData) and received data: \(data)" showingConfirmation = true } catch { print("URL upload failed") } }
核心错误原因
multipart/form-data类型的请求对请求体格式有严格要求,必须通过随机生成的边界(boundary)字符串分隔表单字段和文件内容,直接将裸PNG二进制数据作为请求体发送,服务端无法按格式解析内容,必然返回错误。- 直接发送裸图片二进制时,
Content-Type应匹配为image/png,但这种上传方式需要服务端专门支持原始二进制流接收逻辑,reqres.in等公共测试接口、默认配置的AWS API Gateway都不会自动解析这类请求。 - 代码中强制解包
problemPhoto.pngData()存在崩溃风险,且没有对响应的HTTP状态码做校验,无法快速定位错误原因。
修复方案
第一步:实现multipart格式请求体生成方法
// 生成符合规范的multipart/form-data请求体 func buildMultipartRequestBody( fileData: Data, boundary: String, fieldName: String = "image", fileName: String = "upload.png", mimeType: String = "image/png" ) -> Data { var body = Data() let lineBreak = "\r\n" // 写入文件段头部 body.append("--\(boundary + lineBreak)".data(using: .utf8)!) body.append("Content-Disposition: form-data; name=\"\(fieldName)\"; filename=\"\(fileName)\"\(lineBreak)".data(using: .utf8)!) body.append("Content-Type: \(mimeType + lineBreak + lineBreak)".data(using: .utf8)!) // 写入图片二进制内容 body.append(fileData) body.append(lineBreak.data(using: .utf8)!) // 写入结束边界 body.append("--\(boundary)--\(lineBreak)".data(using: .utf8)!) return body }
第二步:修改图片上传方法
func uploadImage() async { guard let problemPhoto = UIImage(named: self.imagePath) else { print("加载本地图片失败") return } guard let imageData = problemPhoto.pngData() else { print("图片转PNG数据失败") return } let url = URL(string: "https://reqres.in/api/bkim")! // 生成随机边界,避免和文件内容冲突 let boundary = UUID().uuidString var request = URLRequest(url: url) request.httpMethod = "POST" // Content-Type必须携带boundary参数,供服务端拆分请求内容 request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type") // 手动拼接符合格式的请求体 request.httpBody = buildMultipartRequestBody(fileData: imageData, boundary: boundary) do { let (data, response) = try await URLSession.shared.data(for: request) // 校验响应状态码 guard let httpResp = response as? HTTPURLResponse else { print("返回响应不是HTTP格式") return } print("响应状态码:\(httpResp.statusCode)") if let respText = String(data: data, encoding: .utf8) { print("响应内容:\(respText)") } confirmationMessage = "上传完成,状态码:\(httpResp.statusCode)" showingConfirmation = true } catch { print("上传失败:\(error.localizedDescription)") } }
调试注意事项
- 如果目标服务端要求直接上传原始二进制流,不需要拼接multipart格式,直接将
imageData赋值给request.httpBody,Content-Type设置为image/png即可。 - 调试时必须打印HTTP状态码和响应文本,不要仅通过请求是否抛错判断上传结果,状态码和服务端返回的错误提示是定位问题的核心依据。
- 对接AWS API Gateway时,需要提前在控制台配置对应接口的二进制媒体类型支持,否则接口会默认将上传的二进制内容做文本转码,导致文件损坏。
内容的提问来源于stack exchange,提问作者Wooshik Brian Kim
相关产品推荐
相关产品推荐

