【问题标题】:send multi part form data in swift快速发送多部分表单数据
【发布时间】:2019-05-05 11:01:09
【问题描述】:

我使用下面的代码发送多部分参数

 let headers = [
        "Content-Type": "application/x-www-form-urlencoded",
        "Authorization": "Bearer \(myToken)",
        "cache-control": "no-cache"
    ]

    let parameters = [
        [
            "name": "firstname",
            "value": "alex"
        ],
        [
            "name": "lastname",
            "value": "black"
        ],
        [
            "name": "birthdate_day",
            "value": "1"
        ],
        [
            "name": "birthdate_month",
            "value": "5"
        ],
        [
            "name": "birthdate_year",
            "value": "1989"
        ],
        [
            "name": "gender",
            "value": "m"
        ],
        [
            "name": "avatar",
            "fileName": "\(imageURL)"
        ]
    ]

    let boundary = "Boundary-\(NSUUID().uuidString)"

    var body = ""
    let error: NSError? = nil
    for param in parameters {
        let paramName = param["name"]!
        body += "--\(boundary)\r\n"
        body += "Content-Disposition:form-data; name=\"\(paramName)\""
        if let filename = param["fileName"] {
            if let contentType = param["content-type"] {
            do {
                let fileContent = try String(contentsOfFile: filename, encoding: String.Encoding.utf8)
                if (error != nil) {
                    print(error as Any)
                }

                body += "; filename=\"\(filename)\"\r\n"
                body += "Content-Type: \(contentType)\r\n\r\n"
                body += fileContent
            } catch {
                print(error)
            }
            }
        } else if let paramValue = param["value"] {
            body += "\r\n\r\n\(paramValue)"
        }
    }

    let postData = NSMutableData(data: body.data(using: String.Encoding.utf8)!)

    let request = NSMutableURLRequest(url: NSURL(string: "myUrl")! as URL,
                                      cachePolicy: .useProtocolCachePolicy,
                                      timeoutInterval: 10.0)
    request.httpMethod = "POST"
    request.allHTTPHeaderFields = headers
    request.httpBody = postData as Data

    let session = URLSession.shared
    let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
        if (error != nil) {
            print(error as Any)
        } else {
            let httpResponse = response as? HTTPURLResponse
            print(httpResponse?.statusCode as Any)
        }
    })

    dataTask.resume()
    return dataTask

图像 url 和其余数据但是我会收到 Satus 代码 500 我知道这个错误是服务器端的但是 android 版本使用相同的 api url 并且运行良好我知道这个代码可以修复并且也许小的改变可以修复这个代码的工作

【问题讨论】:

  • 使用 CodyFire 库真的很简单,你可以试试看
  • 您要在imageURL 上传图片吗?还是您真的只是向服务器发送某个图像的 URL?你也说“多部分”(在这种情况下你会做类似stackoverflow.com/a/26163136/1271826的事情),但你的标题说x-www-form-urlencoded(这表明你没有发送文件而是做类似stackoverflow.com/a/26365148/1271826的事情)。
  • @imike +1,来自 iOS 的多部分请求是一个巨大的痛苦 - 只需使用 Alamofire 或 CodyFire。

标签: ios swift multipartform-data


【解决方案1】:
  • 使用URL而不是NSURL
  • var request = URLRequest 是可变的,使用它代替 NSMutableURLRequest
  • var data = Data() 是可变的,使用它代替 NSMutableData
  • 使用Data(contentsOf:options:)方法安全地追加文件blob数据
  • 参数中缺少content-type,因此if let contentType = param["content-type"] { ... } 将无法继续,使用application/octet-stream 默认mime 类型
  • 根据服务器的不同,可能需要为上传文件提供文件名

我解决了上述所有问题并将URLRequest.httpBody 生成代码移至以下扩展。

extension URLRequest {
    private func formHeader(_ name: String, crlf: String, fileName: String? = nil, mimeType: String? = nil) -> String {
        var str = "\(crlf)Content-Disposition: form-data; name=\"\(name)\""
        guard fileName != nil || mimeType != nil else { return str + crlf + crlf }

        if let name = fileName {
            str += "; filename=\"\(name)\""
        }
        str += crlf
        if let type = mimeType {
            str += "Content-Type: \(type)\(crlf)"
        }
        return str + crlf
    }

    private func getFileUrl(_ file: Any) -> URL? {
        if let url = file as? String {
            return URL(string: url)
        }
        return file as? URL
    }

    private func getFileData(_ url: URL) -> Data? {
        do {
            return try Data(contentsOf: url, options: .mappedIfSafe)
        } catch {
            print(error)
            return nil
        }
    }

    mutating func setPost(body parameters: [[String: Any]]) {
        let boundary = "Boundary+\(arc4random())\(arc4random())"
        self.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")

        var data = Data()
        data.append("--\(boundary)".data(using: .utf8)!)

        let crlf = "\r\n"
        for parameter in parameters {
            guard let paramName = parameter["name"] as? String else { continue }

            if let value = parameter["value"] {
                let header = formHeader(paramName, crlf: crlf)
                data.append("\(header)\(value)".data(using: .utf8)!)
            } else if let file = parameter["file"], let fileUrl = getFileUrl(file), let fileData = getFileData(fileUrl) {
                let fileName = parameter["fileName"] as? String
                let contentType = parameter["content-type"] as? String
                let header = formHeader(paramName, crlf: crlf, fileName: fileName ?? fileUrl.lastPathComponent, mimeType: contentType ?? "application/octet-stream")
                data.append(header.data(using: .utf8)!)
                data.append(fileData)
            } else {
                print("\(paramName): empty or invalid value")
                continue
            }
            data.append("\(crlf)--\(boundary)".data(using: .utf8)!)
        }
        data.append("--\(crlf)".data(using: .utf8)!)
        self.httpBody = data
        self.httpMethod = "POST"
    }
}

用法

let parameters = [
    ["name": "firstname", "value": "alex"],
    ["name": "avatar", "file": URL],
    ["name": "avatar", "file": "file:///", "fileName": "image.png", "content-type": "image/png"]
]
request.setPost(body: parameters)

注意上面的参数

  • file 键代表URL 对象或文件路径字符串。
  • fileName: image.png 用于后端,代表文件名。

最后添加标题并创建URLSession.shared.dataTask 作为您的原始代码。

Update-2 函数而不是扩展

func getParameterData(_ name: String, parameter: [String : Any]) -> Data? {
    var str = "\r\nContent-Disposition: form-data; name=\"\(name)\""

    if let value = parameter["value"] {
        return "\(str)\r\n\r\n\(value)".data(using: .utf8)!
    }

    guard
        let file = parameter["file"],
        let url = (file is String ? URL(string: file as! String) : file as? URL)
    else {
        return nil
    }

    let data: Data
    do {
        data = try Data(contentsOf: url, options: .mappedIfSafe)
    } catch {
        print(error)
        return nil
    }

    let fileName = (parameter["fileName"] as? String) ?? url.lastPathComponent
    str += "; filename=\"\(fileName)\"\r\n"

    let contentType = (parameter["content-type"] as? String) ?? "application/octet-stream"
    str += "Content-Type: \(contentType)\r\n"

    return (str + "\r\n").data(using: .utf8)! + data
}

func setPostRequestBody(_ request: inout URLRequest, parameters: [[String: Any]]) {
    let boundary = "Boundary+\(arc4random())\(arc4random())"
    request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")

    var data = Data()
    data.append("--\(boundary)".data(using: .utf8)!)

    for parameter in parameters {
        guard
            let name = parameter["name"] as? String,
            let value = getParameterData(name, parameter: parameter)
        else {
            continue
        }

        data.append(value)
        data.append("\r\n--\(boundary)".data(using: .utf8)!)
    }
    data.append("--\r\n".data(using: .utf8)!)
    request.httpBody = data
}

用法2

var request = URLRequest(url: URL(string: "myUrl")!, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 10.0)
setPostRequestBody(&request, parameters: [
    ["name": "firstname", "value": "alex"],
    ["name": "avatar", "file": URL object or path String]
])

let dataTask = URLSession.shared.dataTask(with: request) { data, response, error in
    guard error != nil else {
        print(error!.localizedDescription)
        return
    }

    let statusCocde = (response as? HTTPURLResponse)?.statusCode
    print(statusCode ?? 0)
    if let data = data {
        print(String(data: data, encoding: .utf8) ?? "")
    }
}
dataTask.resume()

【讨论】:

  • 你能帮我把它变成像上面我发送到这里的代码那样的函数吗?因为我再次收到状态码 500
  • 谢谢 formHeader 是 Header 对吗? Web 服务网址在哪里?我在这个函数中找不到,所以它只是让身体对吗?要完成网络服务,我是否需要将其添加到我的方法中?您可以编辑我的代码以使用它吗?
  • 我把名字改成getParameterData,它解析参数的值,对setPostRequestBody函数也有一些改进
  • 谢谢,我会测试一下,所以在成功后我可以得到错误响应和状态码吗?
  • 是的,从回复中添加了statusCocde
猜你喜欢
  • 2015-02-14
  • 1970-01-01
  • 2020-04-11
  • 2014-01-10
  • 1970-01-01
  • 2017-10-28
  • 1970-01-01
  • 2016-02-29
  • 1970-01-01
相关资源
最近更新 更多