Swift - multipart/form-data で画像ファイルを POST する方法

ここでは、Swift で Content-Type: multipart/form-data で画像ファイルを POST する方法をご説明します。

multipart/form-data で POST されるデータ形式について

Swift で multipart/form-data のデータをポストする前に、マルチパート形式で POST されるデータを確認します。

次のような簡単なフォームから Item の情報をポストします。

* form の acton の URL はダミーです。実際にポストはできません。

<form method="POST" enctype="multipart/form-data" action="http://xxx.com/Test/SaveItemMultipart">
    <input name="itemID">
    <input name="itemName">
    <input type="file" name="imageFile">
    <input type="submit" value="Submit">
</form>

Swift - multipart/form-data で画像ファイルを POST する方法 1


サーバー側では受け取った itemID, itemName, 画像ファイルを保存し、成功した場合は以下のようなレスポンスを返します。

<SaveItemResponse>
    <Message/>
    <Success>true</Success>
</SaveItemResponse>

フォームで、Submit した時のデータを、Wireshark などのツールを使ってキャプチャしてみると、次のような感じになっています。

Swift - multipart/form-data で画像ファイルを POST する方法 2

Content-Type は multipart/form-data になっています。

boundary として ----WebKitFormBoundaryZwEFFOVFw0vcvPuS が定義されていて、フォームデータを区切っています。

フォームデータがシンプルなテキストのキー・バリューペアの場合は、--boundary改行の後に、Content-Disposition: form-data; name="キー名"がきて、改行、改行に続けて値がきています。

フォームデータがファイルの時には、--boundary改行の後に、Content-Disposition: form-data; name="キー名"; に続けて filename="ファイル名" があり、改行がきて、Content-Type: 今回の場合は image/jpeg、改行、改行の後に、画像のバイナリデータが入ります。

この画像では見えていませんが、画像のバイナリデータの後に終わりを示す --boundary--改行 があります。

このようなデータが POST されています。


Swift で multipart/form-data で画像ファイルをPOST する方法

Swift で multipart/form-data で画像ファイルを POST するために、先ほど確認したようなデータを生成します。

ここでは multipart/form-data で画像ファイルを POST する箇所のコードだけをご説明します。

imageView という UIImageView に表示されている jpeg の画像を sendToServerTapped のアクションで POST する前提です。

@IBAction func sendToServerTapped(_ sender: Any) {
    guard let image = imageView.image else { return }
    guard let imageData = image.jpegData(compressionQuality: 1) else { return }

    let itemID = 555
    let itemName = "Item Name 555"
    let imageFileName = "itemp555.jpg"
    
    let boundary = "----------" + UUID().uuidString
    
    var httpBody1 = "--\(boundary)\r\n"
    httpBody1 += "Content-Disposition: form-data; name=\"ItemID\"\r\n"
    httpBody1 += "\r\n"
    httpBody1 += "\(itemID)\r\n"
    httpBody1 += "--\(boundary)\r\n"
    httpBody1 += "Content-Disposition: form-data; name=\"ItemName\"\r\n"
    httpBody1 += "\r\n"
    httpBody1 += "\(itemName)\r\n"
    httpBody1 += "--\(boundary)\r\n"
    httpBody1 += "Content-Disposition: form-data; name=\"imageFile\"; filename=\"\(imageFileName)\"\r\n"
    httpBody1 += "Content-Type: image/jpeg\r\n"
    httpBody1 += "\r\n"
    
    var httpBody = Data()
    httpBody.append(httpBody1.data(using: .utf8)!)
    httpBody.append(imageData)
    
    var httpBody2 = "\r\n"
    httpBody2 += "--\(boundary)--\r\n"

    httpBody.append(httpBody2.data(using: .utf8)!)
    
    let url = URL(string: "http://xxx.com/Test/SaveItemMultipart")!
    var request = URLRequest(url: url)
    request.httpMethod = "POST"
    request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
    request.setValue("\(httpBody.count)", forHTTPHeaderField: "Content-Length")
    request.httpBody = httpBody
    
    URLSession.shared.dataTask(with: request) {(data, response, error) in
        
        if let error = error {
            if let err = error as? URLError, err.code == URLError.Code.notConnectedToInternet {
                print("Internet connection not available. Please try again when internet connection is available.")
            } else {
                print("Unexpected error: \(error.localizedDescription).")
            }
            return;
        }
        
        if let response = response as? HTTPURLResponse {
            if !(200...299).contains(response.statusCode) {
                print("Request Failed - Status Code: \(response.statusCode).")
                return
            }
        }
        
        if let data = data {
            do {
                let jsonDict = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
                
                let success = jsonDict!["Success"] as! Bool
                let message = jsonDict!["Message"] as! String
                
                if(success) {
                    print("success")
                }
                else {
                    print(message)
                    return
                }
            
            } catch {
                print("Error parsing the response.")
            }
        } else {
            print("Unexpected error.")
        }
        
    }.resume()
}

Swift で multipart/form-data のデータをポストするコードを順番にご説明します。

guard let image = imageView.image else { return }
guard let imageData = image.jpegData(compressionQuality: 1) else { return }

let itemID = 555
let itemName = "Item Name 555"
let imageFileName = "itemp555.jpg"

2 行目では UIImageView の UIImage を image に代入しています。

3 行目では UIImage から jpegData で JPEG の Data に変換して imageData に代入しています。

5 ~ 7 行目はフォームデータの値とファイル名をハードコードで定義しています。


let boundary = "----------" + UUID().uuidString
    
var httpBody1 = "--\(boundary)\r\n"
httpBody1 += "Content-Disposition: form-data; name=\"ItemID\"\r\n"
httpBody1 += "\r\n"
httpBody1 += "\(itemID)\r\n"
httpBody1 += "--\(boundary)\r\n"
httpBody1 += "Content-Disposition: form-data; name=\"ItemName\"\r\n"
httpBody1 += "\r\n"
httpBody1 += "\(itemName)\r\n"
httpBody1 += "--\(boundary)\r\n"
httpBody1 += "Content-Disposition: form-data; name=\"imageFile\"; filename=\"\(imageFileName)\"\r\n"
httpBody1 += "Content-Type: image/jpeg\r\n"
httpBody1 += "\r\n"

9 行目では boundary に使う文字列を UUID を使って生成しています。

--boundary で区切って、httpBody1 という文字列にフォームデータを追加していきます。

12 行目 ~ 14 行目では ItemID のフォームデータを先ほど確認した形式で httpBody1 に追加しています。

16 行目 ~ 18 行目では ItemName のフォームデータを先ほど確認した形式で httpBody1 に追加しています。

20 行目 ~ 22 行目では、画像ファイルのフォームデータの、バイナリデータの直前までを先ほど確認した形式で httpBody1 に追加しています。


var httpBody = Data()
httpBody.append(httpBody1.data(using: .utf8)!)
httpBody.append(imageData)

var httpBody2 = "\r\n"
httpBody2 += "--\(boundary)--\r\n"

httpBody.append(httpBody2.data(using: .utf8)!)

24 行目 ~ 26 行目では、httpBody という Data 型の変数を定義し、そこに httpBody1 を Data に変換して append し、づづけて、JPEG の imageData を append しています。

28 行目 ~ 29 行目では、バイナリデータの後に続く改行と最後を表す --boundary--改行を httpBody2 に生成しています。

31 行目で httpBody に httpBody2 を Data に変換して append しています。

これで、httpBody に POST したいデータをマルチパート形式で生成することができました。


let url = URL(string: "http://xxx.com/Test/SaveItemMultipart")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
request.setValue("\(httpBody.count)", forHTTPHeaderField: "Content-Length")
request.httpBody = httpBody

33 行目では、文字列で URL を渡して、URL オブジェクトを生成しています。

34 行目 ~ 38 行目で URLRequest を生成しています。

35 行目では httpMethod に POST を設定しています。

36 行目では setValue() でヘッダーの Content-Type に multipart/form-data; と boundary=\(boundary) を設定しています。

37 行目では setValue() でヘッダーの Content-Length に httpBody.count (バイト数) を設定しています。

38 行目で、URLRequest の httpBody に先ほど生成した httpBody を代入しています。


URLSession.shared.dataTask(with: request) {(data, response, error) in
...

40 行目以降は、生成した request を URLSession.shared.dataTask(with: request) で POST し、レスポンスのデータから Success と Message のデータを取り出しているだけです。

Swift で URLSession を使って HTTP リクエスト (POST)する方法の詳細が知りたい方は「URLSession で HTTP リクエスト (POST) する方法」をご覧ください。

成功した場合は以下のように console に success と print されます。

Swift - multipart/form-data で画像ファイルを POST する方法 3


以上、Swift で Content-Type: multipart/form-data で画像ファイルを POST する方法をご説明しました。

© 2024 iOS 開発入門