vaid vaid - 21 days ago 7
HTTP Question

Send parameters AND data to web server with Swift 3

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

Answer

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 change that to whatever file size your system requires.