iOS swift post request with binary body

血红的双手。 提交于 2019-12-23 18:33:57

问题


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:

  1. Your error message tells us that the web service is expecting a x-www-form-urlencoded request (e.g. key=value) and in for the value, 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.

  2. 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 broader multipart/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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!