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>
サーバー側では受け取った itemID, itemName, 画像ファイルを保存し、成功した場合は以下のようなレスポンスを返します。
<SaveItemResponse>
<Message/>
<Success>true</Success>
</SaveItemResponse>
フォームで、Submit した時のデータを、Wireshark などのツールを使ってキャプチャしてみると、次のような感じになっています。
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 で Content-Type: multipart/form-data で画像ファイルを POST する方法をご説明しました。