仅在 Swift 4.2 中使用多部分 form-data 上传图像

Upload image with multipart form-data only in Swift 4.2

我尝试了很多解决方案。我得到了一些,但他们在某个地方使用了 objective c code。我只需要 swift 4.2 中的解决方案,没有任何第三方(如 Alamofire)。使用 objective c 类 可以正常工作。

我已经能够仅使用 headers 和其他参数和图像发出 POST 请求:

How to do multipart/form-data post request with Swift?

Upload image with multipart form-data iOS in Swift

How to upload images to a server in iOS with Swift?


func sendFile(
    urlPath:String,
    fileName:String,
    data:NSData,
    completionHandler: (NSURLResponse!, NSData!, NSError!) -> Void){

        var url: NSURL = NSURL(string: urlPath)!
        var request1: NSMutableURLRequest = NSMutableURLRequest(URL: url)

        request1.HTTPMethod = "POST"

        let boundary = generateBoundary()
        let fullData = photoDataToFormData(data,boundary:boundary,fileName:fileName)

        request1.setValue("multipart/form-data; boundary=" + boundary,
            forHTTPHeaderField: "Content-Type")

        // REQUIRED!
        request1.setValue(String(fullData.length), forHTTPHeaderField: "Content-Length")

        request1.HTTPBody = fullData
        request1.HTTPShouldHandleCookies = false

        let queue:NSOperationQueue = NSOperationQueue()

        NSURLConnection.sendAsynchronousRequest(
            request1,
            queue: queue,
            completionHandler:completionHandler)
}

// this is a very verbose version of that function
// you can shorten it, but i left it as-is for clarity
// and as an example
func photoDataToFormData(data:NSData,boundary:String,fileName:String) -> NSData {
    var fullData = NSMutableData()

    // 1 - Boundary should start with --
    let lineOne = "--" + boundary + "\r\n"
    fullData.appendData(lineOne.dataUsingEncoding(
        NSUTF8StringEncoding,
        allowLossyConversion: false)!)

    // 2
    let lineTwo = "Content-Disposition: form-data; name=\"image\"; filename=\"" + fileName + "\"\r\n"
    NSLog(lineTwo)
    fullData.appendData(lineTwo.dataUsingEncoding(
        NSUTF8StringEncoding,
        allowLossyConversion: false)!)

    // 3
    let lineThree = "Content-Type: image/jpg\r\n\r\n"
    fullData.appendData(lineThree.dataUsingEncoding(
        NSUTF8StringEncoding,
        allowLossyConversion: false)!)

    // 4
    fullData.appendData(data)

    // 5
    let lineFive = "\r\n"
    fullData.appendData(lineFive.dataUsingEncoding(
        NSUTF8StringEncoding,
        allowLossyConversion: false)!)

    // 6 - The end. Notice -- at the start and at the end
    let lineSix = "--" + boundary + "--\r\n"
    fullData.appendData(lineSix.dataUsingEncoding(
        NSUTF8StringEncoding,
        allowLossyConversion: false)!)

    return fullData
} 

请检查我的解决方案,它采用了参数并且 images.I 已经使用 PHP、.net、java 进行了测试。

class func request(withImages path:APIMethods, method:URLMethod, token : String?, headers:[String:String]?, parameters: [String:Any]?,imageNames : [String], images:[Data], completion: @escaping(Any?, Error?, Bool)->Void) {


    if !Reachability.isConnectedToNetwork() {
        DispatchQueue.main.async {
            showAlert(message: AppMessages.connectionFailed.rawValue)
        }
        dissmissHud()
        return
    }

    var stringUrl = "\(APIManager.url)\(APIMethods.preFix.rawValue)\(path.rawValue)"
    if method == .get, let lastPath = parameters?.values.first as? String {
        stringUrl += lastPath
    }else{
        stringUrl += token ?? ""
    }


    // generate boundary string using a unique per-app string
    let boundary = UUID().uuidString

    let config = URLSessionConfiguration.default
    let session = URLSession(configuration: config)

    print("\n\ncomplete Url :-------------- ",stringUrl," \n\n-------------: complete Url")
    guard let url = URL(string: stringUrl) else { return }
    var request = URLRequest(url: url)
    request.httpMethod = method.rawValue

    if headers != nil{
        print("\n\nHeaders :-------------- ",headers as Any,"\n\n --------------: Headers")
        for (key, value) in headers! {
            request.setValue(value, forHTTPHeaderField: key)

        }
    }

    // Set Content-Type Header to multipart/form-data, this is equivalent to submitting form data with file upload in a web browser
    // And the boundary is also set here
    request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")

    var data = Data()
    if parameters != nil{
        for(key, value) in parameters!{
            // Add the reqtype field and its value to the raw http request data
            data.append("\r\n--\(boundary)\r\n".data(using: .utf8)!)
            data.append("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n".data(using: .utf8)!)
            data.append("\(value)".data(using: .utf8)!)
        }
    }
    for (index,imageData) in images.enumerated() {
        // Add the image data to the raw http request data
        data.append("\r\n--\(boundary)\r\n".data(using: .utf8)!)
        data.append("Content-Disposition: form-data; name=\"\(imageNames[index])\"; filename=\"\(imageNames[index])\"\r\n".data(using: .utf8)!)
        data.append("Content-Type: image/jpeg\r\n\r\n".data(using: .utf8)!)
        data.append(imageData)
    }

    // End the raw http request data, note that there is 2 extra dash ("-") at the end, this is to indicate the end of the data
    data.append("\r\n--\(boundary)--\r\n".data(using: .utf8)!)

    // Send a POST request to the URL, with the data we created earlier
    session.uploadTask(with: request, from: data, completionHandler: { data, response, error in

        if let checkResponse = response as? HTTPURLResponse{
            if checkResponse.statusCode == 200{
                guard let data = data, let json = try? JSONSerialization.jsonObject(with: data, options: [JSONSerialization.ReadingOptions.allowFragments]) else {
                    completion(nil, error, false)
                    return
                }
                let jsonString = String(data: data, encoding: .utf8)!
                print("\n\n---------------------------\n\n"+jsonString+"\n\n---------------------------\n\n")
                print(json)
                completion(json, nil, true)
            }else{
                guard let data = data, let json = try? JSONSerialization.jsonObject(with: data, options: []) else {
                    completion(nil, error, false)
                    return
                }
                let jsonString = String(data: data, encoding: .utf8)!
                print("\n\n---------------------------\n\n"+jsonString+"\n\n---------------------------\n\n")
                print(json)
                completion(json, nil, false)
            }
        }else{
            guard let data = data, let json = try? JSONSerialization.jsonObject(with: data, options: []) else {
                completion(nil, error, false)
                return
            }
            completion(json, nil, false)
        }

    }).resume()

}

extension Data {

    /// Append string to Data
    ///
    /// Rather than littering my code with calls to `data(using: .utf8)` to convert `String` values to `Data`, this wraps it in a nice convenient little extension to Data. This defaults to converting using UTF-8.
    ///
    /// - parameter string:       The string to be added to the `Data`.

    mutating func append(_ string: String, using encoding: String.Encoding = .utf8) {
        if let data = string.data(using: encoding) {
            append(data)
        }
    }
}