我的 MultiPartFormData 编码器有什么问题? Swift 4

What is wrong with my MultiPartFormData Encoder? Swift 4

你好)这是我的MultiPartFormDataEncoder:

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

            var body = ""

            if let parameters = parameters {
                for (key, value) in parameters {
                    body += "--\(boundary)\r\n"
                    body += "Content-Disposition:form-data; name=\"\(String(describing: key))\"\r\n\r\n"
                    body += "\(String(describing: value))\r\n"
                }
            }

            if let files = files {
                for file in files {
                    body += "--\(boundary)\r\n"
                    body += "Content-Disposition: form-data; name=\"\(file.key)\"; filename=\"\(file.name)\"\r\n"
                    body += "Content-Type: \(file.type)\r\n\r\n"
                    body += "\(file.data)\r\n"
                }
            }

            body += "--".appending(boundary.appending("--"))

            urlRequest.httpBody = body.data(using: .utf8)

            if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil {
                urlRequest.setValue("multipart/form-data; charset=utf8; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
            }
            urlRequest.timeoutInterval = 60
            urlRequest.httpShouldHandleCookies = false

向服务器发送请求后,我得到 "Bad Request"。当我尝试在邮递员请求中这样做时,成功了。 所有参数和文件都有正确的名称、类型和键。我检查了一个肯定。 所以问题不在 BackEnd 中,因为它收到我的请求并看到正确的 body 但它在获取后中断。

我认为 headers 有问题。

那么,你怎么看?

编辑: 正确响应解析后,我看到下一条消息:

{"image":["Upload a valid image. The file you uploaded was either not an image or a corrupted image."]}

所以,我找到了问题和解决方案。

这一行有错误:

body += "\(file.data)\r\n"

正如@Larme所说,我们不能将数据和字符串放在一行中。

更好的解决方案将数据和字符串分开添加,例如:

// old body += "\(file.data)\r\n"

// new:
var body: Data = Data()

body.append(file.data)
body.append("\r\n".data(using: .utf8)!)

在我的例子中,MultiPartFormDataParameterEncoder 看起来像:

func encode(urlRequest: inout URLRequest, with parameters: Parameters?, files: Files?) throws {
        do {
            let boundary =  "Boundary-\(UUID().uuidString)"

            var body: Data = Data()

            if let parameters = parameters {
                for (key, value) in parameters {
                    try body.appendString("--\(boundary)\r\n")
                    try body.appendString("Content-Disposition:form-data; name=\"\(String(describing: key))\"\r\n\r\n")
                    try body.appendString("\(String(describing: value))\r\n")
                }
            }

            if let files = files {
                for file in files {
                    try body.appendString("--\(boundary)\r\n")
                    try body.appendString("Content-Disposition: form-data; name=\"\(file.key)\"; filename=\"\(file.name)\"\r\n")
                    try body.appendString("Content-Type: \"\(file.type)\"\r\n\r\n")
                    body.append(file.data)
                    try body.appendString("\r\n")
                }
            }

            try body.appendString("--".appending(boundary.appending("--")))

            urlRequest.httpBody = body

            if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil {
                urlRequest.addValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
            }
        } catch {
            throw NetworkError.encodingMultiPartFormDataFailed
        }
    }

public enum NetworkError: String, Error {
    case encodingMultiPartFormDataFailed = "Parameter encoding in MultiPartFormData failed ..."
}

还必须添加文件和参数模型:

public typealias Parameters = [String:Any]
public typealias Files = [FileModel]

public struct FileModel {
    let name: String
    let key: String
    let data: Data
    let type: String

    init(name: String, type: FileType, data: Data) {
        self.name = name
        self.key = type.key // will be image or audio
        self.data = data
        self.type = type.rawValue // will be "audio/mp3" or "image/jpeg"
    }
}

extension FileModel {
    enum FileType: String {
        case image = "image/jpeg"
        case audio = "audio/mp3"

        var key: String {
            return String(describing: self)
        }
    }
}

// USAGE EXAMPLE
FileModel(name: name, type: .image, data: imageJpegData)

也不要忘记数据扩展以提高安全性:

extension Data {
    private enum DataError: String, Error {
        case encodingStringToDataFailed = "Encoding String to Data failed ..."
    }

    mutating func appendString(_ string: String) throws {
        guard let data: Data = string.data(using: String.Encoding.utf8, allowLossyConversion: false) else {
            throw DataError.encodingStringToDataFailed
        }
        append(data)
    }
}

谢谢^_^