问题
I want to make a POST request from iOS (swift3) which passes a chunk of raw bytes as the body. I had done some experimenting which made me thought the following worked:
let url = URL(string: "https://bla/foo/bar")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = Data(hex: "600DF00D")
let session = URLSession.shared
let task = session.dataTask(with: request) { (data, response, error) in
"DATA \(data ?? Data()) RESPONSE \(response) ERROR \(error)".print()
}
task.resume()
Didn't know it was a problem until I tried sending something simple like a single 0xF0. At which point my tornado server started complaining that I was sending it
WARNING:tornado.general:Invalid x-www-form-urlencoded body: 'utf-8' codec can't decode byte 0xf0 in position 2: invalid continuation byte
Am I just supposed to set some header somehow? Or is there something different I need to do?
回答1:
The two common solutions are:
Your error message tells us that the web service is expecting a
x-www-form-urlencoded
request (e.g.key=value
) and in for thevalue
, you can perform a base-64 encoding of the binary payload.Unfortunately, base-64 strings still need to be percent escaped (because web servers generally parse
+
characters as spaces), so you have to do something like:let base64Encoded = data .base64EncodedString(options: []) .addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed)! .data(using: String.Encoding.utf8)! var body = "key=".data(using: .utf8)! body.append(base64Encoded) var request = URLRequest(url: url) request.httpBody = body request.httpMethod = "POST" request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type") let task = URLSession.shared.dataTask(with: request) { data, response, error in guard error == nil else { print(error!) return } ... } task.resume()
Where:
extension CharacterSet { static let urlQueryValueAllowed: CharacterSet = { let generalDelimitersToEncode = ":#[]@" // does not include "?" or "/" due to RFC 3986 - Section 3.4 let subDelimitersToEncode = "!$&'()*+,;=" var allowed = CharacterSet.urlQueryAllowed allowed.remove(charactersIn: generalDelimitersToEncode + subDelimitersToEncode) return allowed }() }
For more discussion on that character set, see point 2 in this answer: https://stackoverflow.com/a/35912606/1271826.
Anyway, when you receive this on your server, you can retrieve it as and then reverse the base-64 encoding, and you'll have your original binary payload.
Alternatively, you can use
multipart/formdata
request (in which you can supply binary payload, but you have to wrap it in as part of the broadermultipart/formdata
format). See https://stackoverflow.com/a/26163136/1271826 if you want to do this yourself.
For both of these approaches, libraries like Alamofire make it even easier, getting you out of the weeds of constructing these requests.
来源:https://stackoverflow.com/questions/38798273/ios-swift-post-request-with-binary-body