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

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")
        }
        
    }

核心错误原因

  1. multipart/form-data类型的请求对请求体格式有严格要求,必须通过随机生成的边界(boundary)字符串分隔表单字段和文件内容,直接将裸PNG二进制数据作为请求体发送,服务端无法按格式解析内容,必然返回错误。
  2. 直接发送裸图片二进制时,Content-Type应匹配为image/png,但这种上传方式需要服务端专门支持原始二进制流接收逻辑,reqres.in等公共测试接口、默认配置的AWS API Gateway都不会自动解析这类请求。
  3. 代码中强制解包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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 18:15:58