1

I need to upload an image to the server endpoint where the structure has to be as following:

 { "image": { "file": imageData }, "access_token": access_token }

How can I send such a request using NSURLSession (or maybe even Alamofire or AFNetworking)?

Bhavin Bhadani
  • 22,224
  • 10
  • 78
  • 108
mohonish
  • 1,396
  • 1
  • 9
  • 21

3 Answers3

5

You cannot just include binary image data in a JSON request. JSON requires text representation, so if you do this, you must convert it to string (e.g. base64 encoding), use that in the JSON, and then the server code would presumably convert the base64 string back to binary data before attempting to use it.

But if you were base64 encoding of the image, it might look something like:

// get image data

let imageData = UIImagePNGRepresentation(image)

// convert to base64

let base64String = imageData.base64EncodedStringWithOptions(nil)

// build parameters

let parameters = ["image": ["file" : base64String], "access_token" : accessToken]

// get JSON

var error: NSError?
let data = NSJSONSerialization.dataWithJSONObject(parameters, options: nil, error: &error)
assert(data != nil, "Unable to serialize \(error)")

// build request

let url = NSURL(string: "http://example.com/upload")!
let request = NSMutableURLRequest(URL: url)
request.addValue("text/json", forHTTPHeaderField: "Content-Type")
request.HTTPMethod = "POST"

let task = NSURLSession.sharedSession().uploadTaskWithRequest(request, fromData: data) { data, response, error in
    // check for basic connectivity errors

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

    // check for server errors

    if let httpResponse = response as? NSHTTPURLResponse, let statusCode = httpResponse.statusCode as Int? {
        if statusCode != 200 {
            println("status code is \(statusCode)")
        }
    }

    // check for details of app-level server response, e.g. if JSON that was dictionary:

    var parseError: NSError?
    if let responseObject = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: &parseError) as? [String : AnyObject] {
        println(responseObject)
    } else {
        println("JSON parse failed: \(parseError)")
        println("response was: \(response)")
        let responseString = NSString(data: data, encoding: NSUTF8StringEncoding)
        println("responseString was: \(responseString)")
    }
}
task.resume()

If you use Alamofire, this is simplified:

// build parameters

let parameters = ["image": ["file" : base64String], "access_token" : accessToken] as [String : AnyObject]

// build request

let urlString = "http://example.com/upload"

Alamofire.request(.POST, urlString, parameters: parameters, encoding: .JSON)
    .responseJSON(options: nil) { request, response, responseObject, error in
        if error != nil {
            println(error)
        } else {
            println(responseObject)
        }
    }

But both of the above are making assumptions about the nature of the response, that the server is base64 decoding the image data from the JSON, etc., but hopefully this illustrates the basic patterns.

Alternatively, use an application/x-www-form-urlencoded request, in which you can send binary data as illustrated here.

Community
  • 1
  • 1
Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • Thanks! @Rob It seems like your solution should work, but I already found a simpler implementation using AFNetworking (posted answer) – mohonish Aug 19 '15 at 14:34
  • @mohonish - Great. If using Swift, I'd personally use Alamofire (same author as AFNetworking; doesn't use the now deprecated `NSURLConnection` which `AFHTTPRequestOperationManager` does; etc.), but both AFNetworking and Alamofire make your life much easier than writing your own `NSURLSession` code. – Rob Aug 19 '15 at 15:07
1

Found a solution using AFNetworking with help from https://stackoverflow.com/a/11092052/3871476

For others looking for the solution.

let manager = AFHTTPRequestOperationManager(baseURL: NSURL(string: url))
let request = manager.POST(url, parameters: param, constructingBodyWithBlock: {(formData: AFMultipartFormData!) -> Void in
    formData.appendPartWithFileData(imgdata, name: "image[file]", fileName: "photo.jpeg", mimeType: "image/jpeg")
    }, success: {(operation: AFHTTPRequestOperation!, responseObject: AnyObject!) -> Void in
        //Success
    }, failure: {(operation: AFHTTPRequestOperation!, error: NSError!) -> Void in
        //Failure
        println(error.localizedDescription)
})

The trick was to use the "image[file]" parameter.

Community
  • 1
  • 1
mohonish
  • 1,396
  • 1
  • 9
  • 21
  • 1
    If you're going to use AFNetworking, I might suggest `AFHTTPSessionManager` instead of `AFHTTPRequestOperationManager`. The former uses `NSURLSession` whereas the latter uses the `NSURLConnection`, deprecated in iOS 9. – Rob Aug 19 '15 at 15:10
  • 1
    BTW, your answer above does not create a JSON request like you asked for in your question. This solution creates `x-www-form-urlencoded` request (which is achieved here by `AFHTTPRequestOperationManager`'s default `requestSerializer` of `AFHTTPRequestSerializer`), which is better, because it eliminates the need for the base64 encoding silliness. – Rob Aug 19 '15 at 15:35
  • @Rob: I wasn't aware of that. Thanks for your insights. – mohonish Aug 20 '15 at 04:56
0

Try this:

    var request = NSMutableURLRequest(URL: NSURL(string: "https://\(IP):\(port)/")!)
    var response: NSURLResponse?
    var error: NSError?


    //Adding the JSON String in HTTP Body
    request.HTTPBody = NSJSONSerialization.dataWithJSONObject(jsonString, options: nil, error: &error)
    request.timeoutInterval = (number as! NSTimeInterval)
    request.HTTPMethod = "POST"
    request.setValue("application/json", forHTTPHeaderField: "Content-Type")
    request.setValue("gzip", forHTTPHeaderField: "Accept-encoding")

     let urlData = NSURLConnection.sendSynchronousRequest(request, returningResponse: &response, error: &error)
Karlos
  • 1,653
  • 18
  • 36
  • Rob : I am still using 8.4 and I was not aware that this was deprecated in iOS 9. Thanks for informing. – Karlos Aug 19 '15 at 07:23