9

[WARNING]
As I am seeing that this question is getting noticed more than it should, I want to tell you not to use any of the following code.
At the time I asked the question, Swift had less than a year, was moving fast, most of the libraries were not Swift-friendly and unstable. I strongly recommend you to try using Alamofire or another library for that kind of task. But don't do it yourself.
[/WARNING]

I want to upload an image to a Drupal endpoint.

The problem I have is that I receive an HTTP 200 OK response with text/html content type. In the HTML response, there is a clear message that the node has been correctly created. But on the server side the image is not associated with the node.

Also I am not expecting text/html but application/json as I specify it in the Accept header.

It already works in the Android app using Android Rest Template. Here is the code for reference:

String url = getUrl("node/{info_id}/attach_file");

HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);

if (user.isLoggedIn()) {
    headers.add(user.getSessionName(), user.getSessionId());
    headers.add("X-CSRF-Token", user.getToken());
    headers.add("Cookie", user.getSessionName() + "=" + user.getSessionId());
}

MultiValueMap<String, Object> parts = new LinkedMultiValueMap<>();

parts.add("files[field_mobileinfo_image]",
        new FileSystemResource(info.getImageUri()));
parts.add("field_name", "field_mobileinfo_image");

HttpEntity<MultiValueMap<String, Object>> request = new HttpEntity<>(parts, headers);
return getRestTemplate().exchange(url, HttpMethod.POST, request, Void.class, info.getId()).getBody();

I know I don't check the response in Android (Void.class) but everything works fine and the image is attached to the node on the server side.

Now on iOS in Swift I tried multiple things.

With AFNetworking:

func upload(mobileInfo: MobileInfo) {
    let user = userService.load()
    let url = Config.buildUrl("")

    let manager = AFHTTPRequestOperationManager(baseURL: NSURL(string:url)!)
    let serializer = AFHTTPRequestSerializer()
    serializer.setValue(user.sessionId, forHTTPHeaderField: user.sessionName)
    serializer.setValue(user.token, forHTTPHeaderField: "X-CSRF-Token")
    serializer.setValue("\(user.sessionName)=\(user.sessionId)", forHTTPHeaderField: "Cookie")
    manager.requestSerializer = serializer

    manager.responseSerializer.acceptableContentTypes.removeAll(keepCapacity: false)
    manager.responseSerializer.acceptableContentTypes.insert("application/json")

    let imageData = UIImageJPEGRepresentation(mobileInfo.image, 0.3)    
    manager.POST("/node/\(mobileInfo.id)/attach_file", parameters: nil, constructingBodyWithBlock: { (formData) -> Void in
        formData.appendPartWithFileData(
            imageData,
            name: "files[field_mobileinfo_image]",
            fileName: "field_mobileinfo_image",
            mimeType: "image/jpeg")
        formData.appendPartWithFormData("field_mobileinfo_image".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true), name: "field_name")
    },
    success: { (operation, data) -> Void in
        println(data)
    }) { (operation, error) -> Void in
        println(error)
    }
}

Manually with information grabbed from other stackoverflow questions:

func upload2(mobileInfo: MobileInfo) {
    let user = userService.load()
    let imageData = UIImageJPEGRepresentation(mobileInfo.image, 0.3)
    let url = NSURL(string:Config.buildUrl("/node/\(mobileInfo.id)/attach_file"))!
    println(url)
    var request = NSMutableURLRequest(URL: url)
    var session = NSURLSession.sharedSession()
    request.HTTPMethod = "POST"
    var boundary = "---------------------------14737809831466499882746641449"
    var contentType = "multipart/form-data; boundary=\(boundary)"
    println(contentType)
    request.addValue(contentType, forHTTPHeaderField: "Content-Type")
    request.addValue("application/json", forHTTPHeaderField: "Accept")
    request.addValue("\(user.sessionName)=\(user.sessionId)", forHTTPHeaderField: "Cookie")
    request.addValue(user.sessionId, forHTTPHeaderField: user.sessionName)
    request.addValue(user.token, forHTTPHeaderField: "X-CSRF-Token")

    println(request.allHTTPHeaderFields)

    var body = NSMutableData()

    body.appendData("\r\n--\(boundary)\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
    body.appendData("Content-Disposition: form-data; name=\"field_name\"\r\n\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
    body.appendData("field_mobileinfo_image".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true)!)

    body.appendData("\r\n--\(boundary)\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
    body.appendData("Content-Disposition: form-data; name=\"files[field_mobileinfo_image]\"; filename=\"img.jpg\"\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
    body.appendData("Content-Type: application/octet-stream\r\n\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
    body.appendData(imageData)
    body.appendData("\r\n--\(boundary)\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)


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

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

    println("returnString \(returnString)")
}

With SRWebClient:

func upload3(mobileInfo: MobileInfo) {
    let user = userService.load()
    let imageData:NSData = NSData(data: UIImageJPEGRepresentation(mobileInfo.image, 0.3))
    SRWebClient.POST("http://master.test.lesfrontaliers.lu/node/\(mobileInfo.id)/attach_file")
        .headers(["Accept": "application/json",
            user.sessionName: user.sessionId,
            "X-CSRF-Token": user.token,
            "Cookie": "\(user.sessionName)=\(user.sessionId)"])
        .data(imageData, fieldName:"files[field_mobileinfo_image]", data:["field_name":"field_mobileinfo_image"])
        .send({ (response: AnyObject!, status: Int) -> Void in
            println(status)
            println(response)
        },failure:{(error:NSError!) -> Void in
            println(error)
        })
}

Please save me! ;-) I tried so many things to make it work that I can't see anymore if I am doing something wrong. It seems ok for me. The only difference I can see is that I am not storing the image on the filesystem but directly sending the binary data which is the same thing in the end.

Here is an image of the request created in Postman (working and receiving json)

Postman

[EDIT] If it can help someone here is the correct code of the wrong part of the above manual request:

var body = NSMutableData()

body.appendData("--\(boundary)\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
body.appendData("Content-Disposition: form-data; name=\"field_name\"\r\n\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
body.appendData("field_mobileinfo_image\r\n".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true)!)

body.appendData("--\(boundary)\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
body.appendData("Content-Disposition: form-data; name=\"files[field_mobileinfo_image]\"; filename=\"img.jpg\"\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
body.appendData("Content-Type: image/jpeg\r\n\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
body.appendData(imageData)
body.appendData("\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)

body.appendData("--\(boundary)--\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)

request.HTTPBody = body
grandouassou
  • 2,500
  • 3
  • 25
  • 60
  • how is this done when I need to send up post parameters as well? http://stackoverflow.com/questions/30006290/uploading-image-in-swift-with-multiple-parameters – Tyler May 02 '15 at 19:55
  • Why do you specifiy `image.jpeg` if your file has the extension `png`? – Nicolas Miari Dec 22 '15 at 07:11
  • Also, I always wonder what are the two additional, leading hyphens before the boundary string (`--`). – Nicolas Miari Dec 22 '15 at 07:26
  • It was work in progress code because I knew I was sending jpeg file and my concern was to send a multipart form request, not to know what I was really sending. The two hyphens are just part of the spec I think. Each --boundary is a part and the last part is closed with --boundary-- [RFC](http://www.w3.org/Protocols/rfc1341/7_2_Multipart.html) – grandouassou Dec 22 '15 at 17:18

2 Answers2

1

Don't know if this will work for what you are trying to do but we use this to upload images, the key difference is to use filename and Content-type:

[self appendBody:body data:[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\".jpg\"\r\n", key]];

[self appendBody:body data:@"Content-Type: image/jpeg\r\n\r\n"];
[body appendData:imageData];
rmp
  • 3,503
  • 1
  • 17
  • 26
  • 1
    OMG. Thank you so much. You put me on the right track. I don't know why I did so much crap with the "manual" request but if you look to my question it is completely wrong (no final boundary etc) and on top of that... the body is not set on the request. Adding the Content-Type image/jpeg finally gave me an error from the server instead of the wrong HTTP 200. What bothers me though is that AFNetworking and SRWebClient didn't work either though they are heavily used. – grandouassou Apr 30 '15 at 22:35
  • Awesome, glad you got it working! Yeah, networking always seems to be more painful then it should be. – rmp Apr 30 '15 at 22:38
0

For any swift 2.0 JSON Request AND PHP code :- ( Manual )

    let imageData = UIImageJPEGRepresentation(userImage, 0.3)
    let url:NSURL = NSURL(string: serverURL!)! // Give ur request URL
    let request = NSMutableURLRequest(URL: url)
    request.HTTPMethod = "POST"
    let boundary = "---------------------------14737809831466499882746641449"
    let contentType = "multipart/form-data; boundary=\(boundary)"
    request.addValue(contentType, forHTTPHeaderField: "Content-Type")
    let body = NSMutableData()
    body.appendData("--\(boundary)\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
    body.appendData("Content-Disposition: form-data; name=\"userfile\"; filename=\"img.jpg\"\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
    body.appendData("Content-Type: image/jpeg\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
    body.appendData("Content-Transfer-Encoding: binary\r\n\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
    body.appendData(imageData!)
    body.appendData("\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
    body.appendData("--\(boundary)--\r\n".dataUsingEncoding(NSUTF8StringEncoding)!)
    request.HTTPBody = body

PHP Code :-

<?php

//http://192.168.1.154/Contact/uploadImgwebservice.php

//print the username  and password using php

echo $_POST[‘username’];

echo $_POST[‘password’];

//upload your file

$uploaddir = ‘./uploads/’;

$file = basename($_FILES[‘userfile’][‘name’]);

$uploadfile = $uploaddir . $file;

if (move_uploaded_file($_FILES[‘userfile’][‘tmp_name’], $uploadfile)) {

    echo “http://192.168.1.154/Contact/uploads/{$file}”;

}

?>
grandouassou
  • 2,500
  • 3
  • 25
  • 60
Mudith Chathuranga Silva
  • 7,253
  • 2
  • 50
  • 58