我在用Swift 4编写的iOS应用中编写了一个简单的HTTP POST请求。
Swift代码如下:
let url = URL(string: "http://example.com/api.php")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
let boundary = "Boundary-\(NSUUID().uuidString)"
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
let body = NSMutableData()
// Text parameter: Action
body.append(NSString(format: "\r\n--%@\r\n", boundary).data(using: String.Encoding.utf8.rawValue)!)
body.append(NSString(format: "Content-Disposition: form-data; name=\"action\"\r\n\r\n" as NSString).data(using: String.Encoding.utf8.rawValue)!)
body.append(NSString(format: ("my_action" as NSString)).data(using: String.Encoding.utf8.rawValue)!)
// Text parameter: Peer ID
body.append(NSString(format: "\r\n--%@\r\n", boundary).data(using: String.Encoding.utf8.rawValue)!)
body.append(NSString(format: "Content-Disposition: form-data; name=\"peerid\"\r\n\r\n" as NSString).data(using: String.Encoding.utf8.rawValue)!)
body.append(NSString(format: ("123456" as NSString)).data(using: String.Encoding.utf8.rawValue)!)
// Image
body.append(NSString(format: "\r\n--%@\r\n", boundary).data(using: String.Encoding.utf8.rawValue)!)
body.append(NSString(format:"Content-Disposition: form-data; name=\"secret_img\"; filename=\"secret.jpg\"\r\n").data(using: String.Encoding.utf8.rawValue)!)
body.append(NSString(format: "Content-Type: application/octet-stream\r\n\r\n").data(using: String.Encoding.utf8.rawValue)!)
body.append(imageData!)
body.append(NSString(format: "\r\n--%@\r\n", boundary).data(using: String.Encoding.utf8.rawValue)!)
request.httpBody = body as Data
let task = URLSession.shared.dataTask(with: request as URLRequest, completionHandler: { data, response, error in
guard error == nil else {
return
}
guard let data = data else {
return
}
do {
// create json object from response Json data
/*if let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any] {
print(json)
// handle json...
}*/
// Debug: check the response string
let responseString = String(data: data, encoding: .utf8)
print("responseString = \(responseString)")
} catch let error {
print(error.localizedDescription)
}
})
task.resume()
PHP响应为空字符串(未报告错误)。然后,我使用一个简单的HTML表单来测试PHP脚本是否还可以。这是form.html
:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Test Upload Form</title>
</head>
<body>
<form action="index.php" method="post" enctype="multipart/form-data">
<input type="hidden" name="action" value="my_action" />
<input type="hidden" name="peerid" value="123456" />
<p>Image: <input type="file" name="secret_img" required /></p>
<p><input type="submit" name="submit" id="submit" /></p>
</form>
</body>
</html>
PHP响应:
{"status": "OK"}
证明脚本没有问题。 PHP脚本如下,非常简单:
<?php
header('Content-Type: application/json');
if(!isset($_FILES['secret_img'])) {
echo json_encode(array('error' => 'missing image'));
exit;
}
$file = $_FILES['secret_img'];
$upload_folder = 'upload/';
if(move_uploaded_file($_FILES['secret_img']['tmp_name'], $upload_folder . $_POST['peerid'] . '_' . time() . '_' . rand(1000, 999999) . '.jpg')) {
// success
echo json_encode(array('status' => 'OK'));
} else {
echo json_encode(array('error' => 'unable to copy image'));
}
?>
我将错误缩小为Swift代码。我删除了body
中的内容(注释了body.append()
行),然后PHP响应如下:
{"error": "missing image"}
这是正常行为。
你们能帮我发现问题吗?谢谢。
更新,如果我在request.httpBody
行之前添加以下两行,则dump()
返回nil
。发生了什么事?
let debug = NSString(data: body as Data, encoding: String.Encoding.utf8.rawValue)
dump(debug)
body
变量已正确初始化,并且没有错误地附加了数据。
答案 0 :(得分:1)
问题很细微,不易发现。问题的原因是:
\r\n
丢失/重复要解决此问题,httpBody
必须更改为以下内容:
let body = NSMutableData()
// Text parameter: Action
body.append(NSString(format: "--%@\r\n", boundary).data(using: String.Encoding.utf8.rawValue)!)
body.append(NSString(format: "Content-Disposition: form-data; name=\"action\"\r\n\r\n" as NSString).data(using: String.Encoding.utf8.rawValue)!)
body.append(NSString(format: "my_action\r\n").data(using: String.Encoding.utf8.rawValue)!)
在第一个块中,不需要第一个\r\n
(这引起httpBody
中的空行),并且(与格式对齐),第一个块中的\r\n
第二个块将移到my_action
字符串的结尾。
// Text parameter: Peer ID
body.append(NSString(format: "--%@\r\n", boundary).data(using: String.Encoding.utf8.rawValue)!)
body.append(NSString(format: "Content-Disposition: form-data; name=\"peerid\"\r\n\r\n").data(using: String.Encoding.utf8.rawValue)!)
body.append(NSString(format: "123456\r\n").data(using: String.Encoding.utf8.rawValue)!)
与第一个块相同,\r\n
被重新排列(与403 Forbidden问题无关)。还简化了一些代码。
// Image
body.append(NSString(format: "--%@\r\n", boundary).data(using: String.Encoding.utf8.rawValue)!)
body.append(NSString(format:"Content-Disposition: form-data; name=\"secret_img\"; filename=\"secret.jpg\"\r\n").data(using: String.Encoding.utf8.rawValue)!)
body.append(NSString(format: "Content-Type: application/octet-stream\r\n\r\n").data(using: String.Encoding.utf8.rawValue)!)
body.append(imageData!)
body.append(NSString(format: "\r\n--%@--\r\n", boundary).data(using: String.Encoding.utf8.rawValue)!)
request.httpBody = body as Data
最后一个块有问题,导致403 Forbidden错误。最后一行错过了结束的边界符号。
结果如下:
--boundary
Content-Disposition: form-data; name="action"
my_action
--boundary
Content-Disposition: form-data; name="peerid"
123456
--boundary
Content-Disposition: form-data; name="secret_img"; filename="secret.jpg"
{image data here}
--boundary--
结论:缺少的结束边界导致403禁止(格式错误的HTTP正文)。
此修复程序的灵感来自MDN的Content-Disposition
文档:
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition