Send parameters AND data to web server with Swift

2019-04-17 22:55发布

问题:

I'm trying to figure out how to send a photo from an iPhone to my web server.

I also need to send parameters containing the size of the photo, it's filename and other additional information about the photo in the same request as the parameter data.

The code below is on the right track I think, but where do I put the parameter data called params:

        let params: Array<String> = [aI.filename, String(aI.size), String(aI.dateTime.year), String(aI.dateTime.month), String(aI.dateTime.day), String(aI.dateTime.hour), String(aI.dateTime.minute), String(aI.dateTime.second), String(aI.dateTime.millisecond)]


        var serverURL = URL(string: "http://192.168.0.23/upload.php");
        var req = NSMutableURLRequest(url: serverURL!, cachePolicy: NSURLRequest.CachePolicy.useProtocolCachePolicy, timeoutInterval: 60.0);
        //Set request to post
        req.httpMethod = "POST";

        //Set content type
        req.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type");


        let task = URLSession.sharedSession().dataTaskWithRequest(req){ data, response, error in
            if error != nil{
                print("Error -> \(error)")
                return
            }

            do {
                let result = try NSJSONSerialization.JSONObjectWithData(data!, options: []) as? [String:AnyObject]

                print("Result -> \(result)")

            } catch {
                print("Error -> \(error)")
            }
        }

        task.resume()
        return task

回答1:

Allthough some of the answers pushed me in the right direction, they still didn't fit my project and so I continued googling an I managed to find exactly what I needed in the following article: http://swiftdeveloperblog.com/image-upload-example/

I needed to make the HTTP request asynchronously and using sessions, which I didn't specify in the question because the question was merely about how to send both several parameters along with data in one single request.

It is called Multipart Form Data when doing so.

I had to modify the code from the article a little bit to make it work for my application, so I'm sharing my Swift 3 code below:

Trigger code

let params = [
            "filename"      : chunkOwner.filename                       ,
            "size"          : String(describing: chunkOwner.size)                   ,
            "year"          : String(chunkOwner.dateTime.year)          ,
            "month"         : String(chunkOwner.dateTime.month)         ,
            "day"           : String(chunkOwner.dateTime.day)           ,
            "hour"          : String(chunkOwner.dateTime.hour)          ,
            "minute"        : String(chunkOwner.dateTime.minute)        ,
            "second"        : String(chunkOwner.dateTime.second)        ,
            "millisecond"   : String(chunkOwner.dateTime.millisecond)   ,
        ]

uploadChunk(url: URL(string: "http://192.168.0.23/upload.php")!, data: photoData, params: params)

Upload code:

func uploadData(url: URL, data: Data!, params: [String: String])
    {
        let cachePolicy = NSURLRequest.CachePolicy.reloadIgnoringLocalCacheData;
        let request = NSMutableURLRequest(url: url, cachePolicy: cachePolicy, timeoutInterval: 6.0);
        request.httpMethod = "POST";

        let boundary = generateBoundaryString()

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


        if(data == nil)  { return; }

        request.httpBody = createBodyWithParameters(parameters: params, filePathKey: "file", data: data, boundary: boundary)



        //myActivityIndicator.startAnimating();

        let task = URLSession.shared.dataTask(with: request as URLRequest) {
            data, response, error in

            if error != nil {
                print("error=\(error)")
                return
            }

            // You can print out response object
            print("******* response = \(response)")

            // Print out reponse body
            let responseString = NSString(data: data!, encoding: String.Encoding.utf8.rawValue)
            print("****** response data = \(responseString!)")

            do {
                let json = try JSONSerialization.jsonObject(with: data!, options: []) as? NSDictionary

                print(json)



            }catch
            {
                //if you recieve an error saying that the data could not be uploaded,
                //make sure that the upload size is set to something higher than the size
                print(error)
            }


        }

        task.resume()

    }


    func createBodyWithParameters(parameters: [String: String]?, filePathKey: String?, data: Data!, boundary: String) -> Data {
        var body = Data();

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

        let mimetype = "text/csv"

        body.appendString(string: "--\(boundary)\r\n")
        body.appendString(string: "Content-Disposition: form-data; name=\"\(filePathKey!)\"; filename=\"\(parameters!["filename"]!)\"\r\n")
        body.appendString(string: "Content-Type: \(mimetype)\r\n\r\n")


        body.append(data)
        body.appendString(string: "\r\n")

        body.appendString(string: "--\(boundary)--\r\n")

        return body
    }




    func generateBoundaryString() -> String {
        return "Boundary-\(NSUUID().uuidString)"
    }

Also include the following code at the bottom of your .swift file outside of your class:

extension Data {
    mutating func appendString(string: String) {
        append(string.data(using: .utf8)!)
    }
}

And for the PHP upload script I did some changes and now looks like this:

<?php
    $target_dir = "/var/www/html/uploads";if(!file_exists($target_dir)){
        mkdir($target_dir, 0777, true);
    }

    $target_dir = $target_dir . "/" . basename($_FILES["file"]["name"]);
    echo count("size: ".$_FILES["file"]["tmp_name"]);


    if (move_uploaded_file($_FILES["file"]["tmp_name"], $target_dir)){

        echo json_encode([
            "Message" => "The file ". basename( $_FILES["file"]["name"]). " has been uploaded.",
            "Status" => "OK",
        ]);

    } else {

        echo json_encode([
            "Message" => "Sorry, there was an error uploading your file.",
            "Status" => "Error",
        ]);

    }
?>

Important Note:

Your app will fail to upload data if your server php file called php.ini is configured to accept files smaller than the data you're trying to upload.

For example: If php.ini is configured to accept 2 MB, then any uploads larger than 2 MB will be ignored and your app will receive a response saying that something went wrong.

To change the file size acceptance in php.ini you need to look for the variable called upload_max_filesize and post_max_sizeand change those to whatever file size your system requires.



回答2:

You can put them to httpBody or to httpBodyStream (by using NSInputStream)

But don't forget to transform params for server protocol (for example xml, json, or binary data with custom format).
For your content type (application/x-www-form-urlencoded), you can find format in wikipedia:

 keyName=value&keyName2=value2

The keys and values should contain of URLPathAllowedCharacterSet, to achieve it you can use stringByAddingPercentEncodingWithAllowedCharacters.
To convert the KeyValue string to NSData, you can use method dataUsingEncoding.



回答3:

I am sharing you one way of posting data using NSURLConnection in Swift3

Your URL

 var serverURL = URL(string: "http://192.168.0.23/upload.php")

Your parameters to be like this , just discuss with server people to which parameters you to need pass data Then assign your value to that parameter like below

serverparameter1 = \(value to post)& serverparameter2 = \(value to post2).......

With your params I did like this have a look

let params = "filename= \(aI.filename)&size = \(String(aI.size))& dateTimeYear =\(String(aI.dateTime.year))&dateTimeMonth =\(String(aI.dateTime.month))& dateTimeDay =\(String(aI.dateTime.day))&dateTimeHour =\(String(aI.dateTime.hour))&dateTimeMinute =\(String(aI.dateTime.minute))&dateTimeSecond =\(String(aI.dateTime.second))&dateTimeMilliSecond=\(String(aI.dateTime.millisecond))"

Convert your Photo Data to Base64String like below

var base64String: NSString!
let myImage = UIImage(named:"image.png")
let imageData = UIImageJPEGRepresentation(myImage, 0.9)
base64String = imageData!.base64EncodedString(options: NSData.Base64EncodingOptions.endLineWithLineFeed) as NSString!
print(base64String)

then pass as stringParameter

&ImageDataStr = \(base64String)

then final Url seems to be look like

 \(serverURL)/\(params)

OR

 \(serverURL)/Upload?\(params)

Step by step request

    var serverURL = URL(string: "http://192.168.0.23/upload.php")

    let params = "filename= \(aI.filename)&size = \(String(aI.size))& dateTimeYear =\(String(aI.dateTime.year))&dateTimeMonth =\(String(aI.dateTime.month))& dateTimeDay =\(String(aI.dateTime.day))&dateTimeHour =\(String(aI.dateTime.hour))&dateTimeMinute =\(String(aI.dateTime.minute))&dateTimeSecond =\(String(aI.dateTime.second))&dateTimeMilliSecond=\(String(aI.dateTime.millisecond))&photoDataStr = \(base64String)"

    var status:NSString = "\(serverURL)/Upload?\(params)" as NSString

    status = status.addingPercentEscapes(using: String.Encoding.utf8.rawValue)! as NSString


    let url = URL(string: status as String)!


    let request = URLRequest(url: url, cachePolicy:NSURLRequest.CachePolicy.reloadIgnoringLocalAndRemoteCacheData, timeoutInterval: 600)
    // need synchronous

Here you will get responseData

      var response:URLResponse?
      var responseD:Data = try! NSURLConnection.sendSynchronousRequest(request, returning:&response)

Finally make that BinaryData to readable

    // save to string - the result came from the Server call
    var serverResults:NSString = NSString(data: responseD, encoding: String.Encoding.utf8.rawValue)!

    print(serverResults)

For Example your Result

if serverResults.range(of: "RESULT&gt;APPROVED").location != NSNotFound
    {
        return "Data posted"
    }
    else
    {
        return "Failed to post"
    }


标签: ios http swift3