Failing to Upload Picture with multipart/form-data

2019-06-12 16:00发布

问题:

I've read quite a lot of topics here on multipart/form-data. It still doesn't work. I am able to upload a file to my server with URLSession.shared.uploadTask.

class MainViewController: UIViewController {
    @IBOutlet weak var pictureView: UIImageView!

    @IBAction func postTapped(_ sender: UIButton) {
        postData()
    }

    func postData() {
        var request = URLRequest(url: URL(string: "http://www.mywebsite.com/upload.php")!)
        request.httpMethod = "POST"
        request.timeoutInterval = 30.0

        guard let imageData = UIImagePNGRepresentation(pictureView.image!) else {
            print("oops")
            return
        }

        let uuid = UUID().uuidString
        let CRLF = "\r\n"
        let filename = uuid + ".png"   // file name
        let formName = uuid + ".png"   // file name in the form
        let type = "image/png"     // file type
        let titleData = "hoge"      // title
        let titleName = uuid + ".png"     // title name in the form
        let boundary = String(format: "----iOSURLSessionBoundary.%08x%08x", arc4random(), arc4random())
        var body = Data()
        // form data //
        body.append(("--\(boundary)" + CRLF).data(using: .utf8)!)
        body.append(("Content-Disposition: form-data; name=\"\(titleName)\"" + CRLF + CRLF).data(using: .utf8)!)
        body.append(titleData.data(using: .utf8)!)
        body.append(CRLF.data(using: .utf8)!)
        // file data //
        body.append(("--\(boundary)" + CRLF).data(using: .utf8)!)
        body.append(("Content-Disposition: form-data; name=\"\(formName)\"; filename=\"\(filename)\"" + CRLF).data(using: .utf8)!)
        body.append(("Content-Type: \(type)" + CRLF + CRLF).data(using: .utf8)!)
        body.append(imageData)
        body.append(CRLF.data(using: .utf8)!)
        // footer //
        body.append(("--\(boundary)--" + CRLF).data(using: .utf8)!)
        request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
        request.setValue("\(body.count)", forHTTPHeaderField: "Content-Length")
        request.httpBody = body
        let session = URLSession(configuration: .default)
        session.dataTask(with: request) { (data, response, error) in
            if let error = error {
                print(error)
            }
            if let respose = response {
                print(respose)
            }
        }
        .resume()
    }
}

PHP (upload.php at index)

$dir = __DIR__ . '/upload/';
$path = $dir . basename($_FILES['filename']['name']);

$data['result'] = 'failure';
if (move_uploaded_file($_FILES['filename']['tmp_name'], $path)) {
    chmod($path, 0777);
    $data['result'] = date("H:i:s") . ' ' . $_POST['title'] . ' success';
}

header('Content-Type: application/json');
echo json_encode($data);

The code above is one of many versions I have tried. There's no error. The response says the status code is 200. But there's no file in the server. I wonder why it doesn't work? It's not about App Transport Security Settings. I'm not ready to use Alamofire, yet. Muchos thankos.

回答1:

I debugged your code, simplified and left the debug statements in for clarity. Here is what I came up with:

func postData() {
    var request = URLRequest(url: URL(string: "http://localhost/uploadimages/uploadtry.php")!)
    request.httpMethod = "POST"
    request.timeoutInterval = 30.0

    guard let imageData = UIImagePNGRepresentation(pictureView.image!) else {
        print("oops")
        return
    }

    let CRLF = "\r\n"
    let filename = "user.png"
    let formName = "file"
    let type = "image/png"     // file type

    let boundary = String(format: "----iOSURLSessionBoundary.%08x%08x", arc4random(), arc4random())

    var body = Data()

    // file data //
    body.append(("--\(boundary)" + CRLF).data(using: .utf8)!)
    body.append(("Content-Disposition: form-data; name=\"\(formName)\"; filename=\"\(filename)\"" + CRLF).data(using: .utf8)!)
    body.append(("Content-Type: \(type)" + CRLF + CRLF).data(using: .utf8)!)
    body.append(imageData as Data)
    body.append(CRLF.data(using: .utf8)!)

    // footer //
    body.append(("--\(boundary)--" + CRLF).data(using: .utf8)!)
    request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")

    //TW debug the body data.
    let theString:NSString = NSString(data: body as Data, encoding: String.Encoding.ascii.rawValue)!
    print(theString)

    request.setValue("\(body.count)", forHTTPHeaderField: "Content-Length")

    request.httpBody = body
    let session = URLSession(configuration: .default)
    session.dataTask(with: request) { (data, response, error) in
        if let error = error {
            print(error)
        }
        if let respose = response {
            print(respose)
        }
        // TW
        if let data = data {
            // This gives us same as server log

            // You can print out response object
            print("******* response = \(String(describing: response))")

            print(data.count)
            // you can use data here

            // Print out reponse body
            let responseString = NSString(data: data, encoding: String.Encoding.utf8.rawValue)
            print("****** response data = \(responseString!)")

        }

        }
        .resume()
}

The mentioned uploadtry.php

<?php
error_reporting(E_ALL);
ini_set("display_errors", 1);

$dir = 'media';
$path = $dir . "/" . basename($_FILES['file']['name']);


$data['result'] = 'failure';
if (move_uploaded_file($_FILES['file']['tmp_name'], $path)) {
    chmod($path, 0777);
    $data['result'] = ' success';
}

header('Content-Type: application/json');
echo json_encode($data);


?>

Basically I saw errors like this on the server:

Undefined index: filename in /pathtoserver/uploadimages/uploadtry.php on line 6

So the server could not understand filename in your message. The debug statements help you to see the error also on the client side if you have no server access.

Hope that gets you started.