3

I am trying to upload an image with json data. I just followed the post and implemented the mechanism. But I get the error

{ status code: 400, headers {
    "Cache-Control" = "no-cache, no-store, max-age=0, must-revalidate";
    Connection = close;
    "Content-Language" = en;
    "Content-Length" = 1033;
    "Content-Type" = "text/html;charset=utf-8";
    Date = "Wed, 27 Jan 2016 10:44:34 GMT";
    Expires = 0;
    Pragma = "no-cache";
    Server = "Apache-Coyote/1.1";
    "X-Content-Type-Options" = nosniff;
    "X-XSS-Protection" = "1; mode=block";
} }`

Below is the complete HTTP Request

Content-Type: multipart/form-data;boundary=Boundary_123456789
Authorization: Basic bihdwbcIUkbcdwjnoNOn
User-Agent: Jersey/2.21.1 (HttpUrlConnection 1.8.0_45)
MIME-Version: 1.0
Host: localhost:8080
Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
Connection: keep-alive
Content-Length: 3526

--Boundary_123456789
Content-Type: application/json
Content-Disposition: form-data; name="userDTO"

{"id":"id","name":"name","age":23}
--Boundary_123456789
Content-Type: image/png
Content-Disposition: form-data; filename="sample-image2.png"; modification-date="Fri, 22 Jan 2016 04:56:48 GMT"; size=3308; name="file"

‰PNG

<Binary data>

--Boundary_123456789—

Below is my implementation

func addUser(completion: (message: String?, error: String?) -> Void) -> NSURLSessionDataTask {
        // create the request            
        let request = createRequest()

        let task = NSURLSession.sharedSession().dataTaskWithRequest(request) { data, response, error in
            print(response) // 400 Error is printed here
        }
        task.resume()

        return task
    }
func createRequest () -> NSURLRequest {
    let param = [
        "id": "id",
        "name": "name",
        "age": 23]  // build your dictionary however appropriate

    let boundary = generateBoundaryString()

    let url = NSURL(string: SERVERURL)!
    let request = NSMutableURLRequest(URL: url)
    request.HTTPMethod = "POST"
    request.addValue("Basic \(base64LoginString())", forHTTPHeaderField: "Authorization")
    request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")

    let path1 = NSBundle.mainBundle().pathForResource("userImage", ofType: "png") as String!
    request.HTTPBody = createBodyWithParameters(param, paths: [path1], boundary: boundary)

    return request
}

func createBodyWithParameters(json: [String:AnyObject], paths: [String]?, boundary: String) -> NSData {
    let body = NSMutableData()
    let key = "userDTO"

    body.appendString("--\(boundary)\r\n")
    body.appendString("Content-Type: application/json")
    body.appendString("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n")
    var requestBody = NSData()

    do {
        requestBody = try NSJSONSerialization.dataWithJSONObject(json, options: NSJSONWritingOptions(rawValue:0))
    } catch (let e) {
        print(e)
    }
    body.appendData(requestBody)
    //body.appendString("\(json)\r\n")

    if paths != nil {
        for path in paths! {
            let url = NSURL(fileURLWithPath: path)
            let data = NSData(contentsOfURL: url)!
            let mimetype = mimeTypeForPath(path)
            let fileName = "sample-image23.png"
            let date = "Fri, 22 Jan 2016 04:56:48 GMT"
            let name = "file"

            body.appendString("--\(boundary)\r\n")
            body.appendString("Content-Type: \(mimetype)\r\n\r\n")
            body.appendString("Content-Disposition: form-data; filename=\"\(fileName)\"; modification-date=\"\(date)\"; size=3308; name=\"\(name)\"\r\n")
            body.appendData(data)
            body.appendString("\r\n")
        }
    }

    body.appendString("--\(boundary)--\r\n")
    return body
}

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

func mimeTypeForPath(path: String) -> String {
    let url = NSURL(fileURLWithPath: path)
    let pathExtension = url.pathExtension

    if let uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, pathExtension! as NSString, nil)?.takeRetainedValue() {
        if let mimetype = UTTypeCopyPreferredTagWithClass(uti, kUTTagClassMIMEType)?.takeRetainedValue() {
            return mimetype as String
        }
    }
    return "application/octet-stream";
}

Also the extension

extension NSMutableData {

    func appendString(string: String) {
        let data = string.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true)
        appendData(data!)
    }
}

I guess I am making some mistake in the json format. Someone can help? Thanks!

Community
  • 1
  • 1
iOS
  • 3,526
  • 3
  • 37
  • 82

2 Answers2

1

It seems, uploading the image is not your issue - it succeeded as far as I can see. You should realize, that uploading the image has nothing to do with JSON. Rather, getting a response in your expected format (JSON) is probably what you are seeking for. So, if you require to get a response whose body is JSON, you should explicitly state this with setting the appropriate Accept header. For example:

Accept: application/json

and in code:

request.setValue("application/json", forHTTPHeaderField: "Accept")

When you get a response, you should also first check the response' status code and then the Content-Type header (respectively, the response' MIMEType property) which should match what you expect: application/json.

If the content type is not what you expect, you could alternatively try additional "response serializers" - each suitable to parse other content-types, e.g. text/plain etc., as you like.

Edit:

The server responded with a hint, that the second part of the multipart request is malformed. Taking a look at how it is composed:

        body.appendString("--\(boundary)\r\n")
        body.appendString("Content-Type: \(mimetype)\r\n\r\n")
        body.appendString("Content-Disposition: form-data; filename='sample-image23.png'; modification-date='Fri, 22 Jan 2016 04:56:48 GMT'; size=3308; name='file'\r\n")
        body.appendData(data)
        body.appendString("\r\n")

Now, looking closely, we can see that the second header Content-Type will be delimited with two CRLF - but there's another header following. Headers should be separated with just one CRLF.

Then, the last header must be delimited with two CRLF.

Suggested fix:

        body.appendString("--\(boundary)\r\n")
        body.appendString("Content-Disposition: form-data; filename='sample-image23.png'; modification-date='Fri, 22 Jan 2016 04:56:48 GMT'; size=3308; name='file'\r\n")
        body.appendString("Content-Type: \(mimetype)\r\n\r\n")
        body.appendData(data)
        body.appendString("\r\n")

(Edit: removed incorrect description)

Edit 2:

There's also a missing CRLF in these lines, above this one:

let key = "userDTO"

body.appendString("--\(boundary)\r\n")
body.appendString("Content-Type: application/json")
body.appendString("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n")

You see the issue? Content-Type has no trailing CRLF!

See also: NSURLRequest Upload Multiple Files

Community
  • 1
  • 1
CouchDeveloper
  • 18,174
  • 3
  • 45
  • 67
  • I have identified that it is a JSON parsing error. But the image fails to upload. – iOS Jan 27 '16 at 10:35
  • @iOS There's or lot of ambiguity in what you are stating: you state it's a parsing error, but you didn't mention _where_ this happens in the code (you have two occurrences where this might happen). Then you state, your upload fails, yet you have posted the log of the request, which indicates the request did actually _succeed_. So, you should clarify these things first, possibly logging the errors you get, and the response you received. – CouchDeveloper Jan 27 '16 at 10:42
  • @iOS This means _"The request could not be understood by the server due to malformed syntax. The client SHOULD NOT repeat the request without modifications."_. So, possibly your body is malformed. This can happen easily when you try to setup a multipart request manually. If you look closely to your request log at the very bottom, you see `--Boundary_123456789—`. The final delimiter however should be `----` In your code it seems correct, though. But this may an indication to the culprit. – CouchDeveloper Jan 27 '16 at 10:50
  • @iOS When getting a status code which indicates an error, e.g. 400, the server may not send a response in the MIME type you requested (e.g. `application/json`. It frequently sends this as `text/html`. So, your JSON parser will fail to parse the response body as JSON. You see? – CouchDeveloper Jan 27 '16 at 10:57
  • Thanks for the point. But still investigating what I'm missing – iOS Jan 27 '16 at 11:08
  • @iOS You may set the accept header as suggested (this will not fix a potentially malformed multipart body, though). Then, log the response data, it is in `text/html; charset=utf-8` - so you need to create a string with this encoding and then log to the console (alternatively, sniff on the wire with a tool, e.g. Wireshark, and check the response). – CouchDeveloper Jan 27 '16 at 11:18
  • I get `The request sent by the client was syntactically incorrect` for the second form-data where I have imageData – iOS Jan 27 '16 at 11:42
  • Removing "\r\n" didn't help – iOS Jan 27 '16 at 12:07
  • @iOS As usual, print the error response and the status code. There's a lot what can go wrong. – CouchDeveloper Jan 27 '16 at 12:11
  • @iOS Please note: the last comment for the boundary was incorrect. Removed it. So, a trailing `CRLF` is correct, in the context how you compose the part. – CouchDeveloper Jan 27 '16 at 12:16
  • 1
    Thank you so much. Adding one more CRFL fixed the problem. `body.appendString("Content-Type: image/png\r\n\r\n")` – iOS Jan 27 '16 at 12:25
  • @iOS The _last_ header must be delimited with _two_ `CRLF`. Note also, above this code, here in `body.appendString("Content-Type: application/json")` there's also possibly a missing `CRLF`. ` – CouchDeveloper Jan 27 '16 at 12:30
  • It leaves the image corrupted. Any idea? – iOS Jan 27 '16 at 12:52
  • @iOS You might check in test code whether you can create an image from the NSData and if that looks OK. _Again_ check and confirm, that the part will be properly composed (review code, and with Wireshark or similar tool). Furthermore, the Disposition params `modification-date` and `size` are non-standard, and possibly ignored by the server, unless it states otherwise. You should know already that composing a multipart request is cumbersome and error prone. Consider a network utility, like Alamofire. That's my hints today. – CouchDeveloper Jan 27 '16 at 16:38
0

this code snippet will help you to upload the UIImage file to the web service using POST method.

func uploadImageOne(){
    var imageData = UIImagePNGRepresentation(exampleImageView.image)

    if imageData != nil{
        var request = NSMutableURLRequest(URL: NSURL(string:"Your URL")!)
        var session = NSURLSession.sharedSession()

        request.HTTPMethod = "POST"

        var boundary = NSString(format: "---------------------------14737809831466499882746641449")
        var contentType = NSString(format: "multipart/form-data; boundary=%@",boundary)
      //  println("Content Type \(contentType)")
        request.addValue(contentType, forHTTPHeaderField: "Content-Type")

        var body = NSMutableData.alloc()

        // Title
         body.appendData(NSString(format: "\r\n--%@\r\n",boundary).dataUsingEncoding(NSUTF8StringEncoding)!)
        body.appendData(NSString(format:"Content-Disposition: form-data; name=\"title\"\r\n\r\n").dataUsingEncoding(NSUTF8StringEncoding)!)
        body.appendData("Hello World".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true)!)

        // Image
        body.appendData(NSString(format: "\r\n--%@\r\n", boundary).dataUsingEncoding(NSUTF8StringEncoding)!)
        body.appendData(NSString(format:"Content-Disposition: form-data; name=\"profile_img\"; filename=\"img.jpg\"\\r\n").dataUsingEncoding(NSUTF8StringEncoding)!)
        body.appendData(NSString(format: "Content-Type: application/octet-stream\r\n\r\n").dataUsingEncoding(NSUTF8StringEncoding)!)
        body.appendData(imageData)
        body.appendData(NSString(format: "\r\n--%@\r\n", boundary).dataUsingEncoding(NSUTF8StringEncoding)!)



        request.HTTPBody = body


        var returnData = NSURLConnection.sendSynchronousRequest(request, returningResponse: nil, error: nil)

        var returnString = NSString(data: returnData!, encoding: NSUTF8StringEncoding)

        println("returnString \(returnString)")

    }


}
SARATH SASI
  • 1,395
  • 1
  • 15
  • 39
  • Your final boundary is not correct. This can cause the server to reject your request entirely. Furthermore, you are using `NSURLConnection` which is now deprecated. And you are suggesting a synchronous form to issue the request which is strongly discouraged to do so. – CouchDeveloper Jan 27 '16 at 10:46