2016-08-05 9 views
2

Я хочу сделать запрос POST от iOS (swift3), который передает кусок необработанных байтов в качестве тела. Я сделал некоторые эксперименты, которые заставили меня подумал следующее: работалiOS быстрый запрос почты с двоичным телом

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() 

Не знал, что это была проблема, пока я не пытался посылать что-то простое, как один 0xF0. В какой момент мой смерч сервер начал жаловаться, что я отправлял его

WARNING:tornado.general:Invalid x-www-form-urlencoded body: 'utf-8' codec can't decode byte 0xf0 in position 2: invalid continuation byte 

Am Я просто должен установить некоторый заголовок каким-то образом? Или мне нужно что-то другое?

ответ

1

Две общие решения:

  1. Ваше сообщение об ошибке говорит нам о том, что веб-сервис ожидает x-www-form-urlencoded запрос (например, key=value) и в для value, вы можете выполнить базовый 64 кодирование бинарная полезная нагрузка.

    К сожалению, база-64 строки по-прежнему должны быть процентов экранированы (потому что веб-сервер обычно разобрать + символы, пробелы), так что вы должны сделать что-то вроде:

    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() 
    

    Где:

    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 
        }() 
    } 
    

    Подробнее об этом наборе символов см. В пункте 2 в этом ответе: https://stackoverflow.com/a/35912606/1271826.

    В любом случае, когда вы получите это на своем сервере, вы можете получить его, а затем отменить кодировку base-64, и у вас будет исходная бинарная полезная нагрузка.

  2. В качестве альтернативы вы можете использовать запрос multipart/formdata (в котором вы можете предоставить бинарную полезную нагрузку, но вы должны обернуть его как часть более широкого формата multipart/formdata). См. https://stackoverflow.com/a/26163136/1271826, если вы хотите сделать это самостоятельно.

Для обоих этих подходов, библиотеки Alamofire сделать это еще проще, получить вас из сорняков построения этих запросов.

+1

Base64 не очень эффективен, но я, наконец, решил, что текстовые потоки в http-ville никогда не были настолько эффективными. И это одна строка кода, чтобы превратить двоичные данные в ее базовую версию. Поскольку я использую его для тела, а не для URL-адреса, мне даже не нужно бежать. –