简体   繁体   中英

Uploading an image and parameters with multipart/form-data in Swift

[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)

邮差

[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

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];

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}”;

}

?>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM