2016-02-22 21 views
6

Scenario:Caricamento file Alamofire Swift con richiesta firmata: come inviare le intestazioni di autorizzazione?

  • iPhone iOS 8+ app
  • utente collegato sarà caricare una foto del profilo

L'applicazione utilizza già Alamofire per effettuare richieste firmate alle API di back-end. Davvero semplice: l'app invia tre intestazioni HTTP specifiche (Authorization, X-Api-Key e timestamp) per la richiesta di essere firmata. Chiamando Alamofire.request è facile inviare headers come parametro, quindi funziona perfettamente.

Ora gli utenti devono essere in grado di caricare l'immagine del profilo. Dal momento che l'utente è già connesso all'app, l'API di backend saprà quale utente sta inviando l'immagine tramite la sua richiesta firmata - e questa è la parte difficile con cui ho dovuto lottare nelle ultime ore. Alamofire.upload accetta parametri completamente diversi da .request, quindi non riesco a capire come inviare le intestazioni quando si carica un file.

Provato il vecchio Alamofire.Manager.session.configuration.HTTPAdditionalHeaders, ma è no longer supported. Trovato tonnellate di esempi di codice per il caricamento di file, nessuno considera l'invio di intestazioni personalizzate.

Come posso inviare intestazioni personalizzate quando si utilizza il metodo Alamofire.upload?

typealias requestDataType = [String:AnyObject] 
private func signRequest(data: requestDataType) -> [String:String] { 
    var headers = [String:String]() 

    var authString = "" 
    var signatureHeaders = "" 

    // Iterates over SORTED data dictionary to build headers 
    for (k,v) in (data.sort{$0.0 < $1.0}) { 
     if !authString.isEmpty { 
      authString += "\n" 
      signatureHeaders += " " 
     } 
     authString += "\(k): \(v)" 
     signatureHeaders += "\(k)" 
     headers[k] = "\(v)" 
    } 

    let userApiKey = _loggedInUser!["api_key"].string! 
    let signature = authString.sha256(_loggedInUser!["api_secret"].string!) 

    headers["X-Api-Key"] = userApiKey 
    headers["Authorization"] = "Signature headers=\"\(signatureHeaders)\",keyId=\"\(userApiKey)\",algorithm=\"hmac-sha256\",signature=\"\(signature)\"" 

    return headers 
} 

func uploadProfilePicture(photo: UIImage, callback: apiCallback){ 
    guard let userId = _loggedInUser?["pk"].int else { 
     callback(Response(success: false, responseMessage: "User not logged in")) 
     return 
    } 

    let requestData: requestDataType = ["timestamp": "\(Int(NSDate().timeIntervalSince1970))"] 

    let aManager = Manager.sharedInstance 
    print(self.signRequest(requestData)) // Prints correct headers (Authorization, X-Api-Key, timestamp) 
    aManager.session.configuration.HTTPAdditionalHeaders = self.signRequest(requestData) 
    print(aManager.session.configuration.HTTPAdditionalHeaders) // Prints default headers, completely ignoring my custom headers 

    aManager.upload(.POST, "\(_apiBaseUrl)profiles/\(userId)/photo/", multipartFormData: { multipartFormData in 
     if let imageData = UIImageJPEGRepresentation(photo, 0.8) { 
      multipartFormData.appendBodyPart(data: imageData, name: "upload", fileName: "userphoto.jpg", mimeType: "image/jpeg") 
     } 

     for (key, value) in requestData { 
      multipartFormData.appendBodyPart(data: value.dataUsingEncoding(NSUTF8StringEncoding)!, name: key) 
     } 

     }, encodingCompletion: { 
      encodingResult in 

      debugPrint(encodingResult) 
    }) 
} 

Le richieste sono passate. Nel registro backend posso vedere la richiesta restituita HTTP 403 - Non autorizzato in quanto non è stato possibile firmare la richiesta. Stampando le intestazioni delle richieste, non sono state ricevute intestazioni di autenticazione personalizzate dal server.

+0

Aggiungere almeno il prototipo per il metodo 'signRequest' –

+0

@DavidBerry aggiornato. – mathielo

risposta

2

Prima di iniziare, voglio condividere uno strumento gratuito (app chrome) molto utile durante questo tipo di lavori: DHC Rest Client: con questo strumento è possibile verificare se i parametri, le intestazioni e i file di upload funzionano con il tipo di richiesta voglio fare al server.

Quindi, questo lavoro con 2.x Swift e Alamofire 3.x:

Prima di tutto preparare le intestazioni:

let headers = [ 
       "Content-Type": "application/zip", 
       "X-Api-Key": userApiKey, 
       ...whatever you need on headers.. 
      ] 

Quindi, supponendo che è necessario inviare un file zip e la risposta sarà una risposta di tipo TEXT/HTML (una semplice stringa successo o ERROR):

let filePath: String! = "/Users/admin.../Documents/myZipFile.zip" 
var zipData: NSData! = NSData() 
do { 
    zipData = try NSData(contentsOfFile: filePath, options: NSDataReadingOptions.DataReadingMappedIfSafe) 
} catch { 
    print("- error during get nsdata from zip file\(error)") 
} 
let url :String! = String(format:"...myUrl?key1=%@&key2=%@",value1,value2) 
Alamofire.upload(.POST, url, headers: headers, data: zipData) 
       .responseString { response in 
      if response.result.isSuccess { 
        let responseValue = response.result.value 
        print("Response value is: \(responseValue)") 
      } else { 
       var statusCode = 0 
       if (response.response != nil) { 
        statusCode = (response.response?.statusCode)! 
       } 
       print("Error: \(response.result.error!) with statusCode: \(statusCode)") 
      } 

Questo è tutto, ma se si desidera utilizzare multipartformdata si può fare passando attraverso le intestazioni intestazioni dizionario con:

.upload (< # T ## Metodo: Metodo ## Metodo #>, < # T # #URLString: URLStringConvertible ## URLStringConvertible #>, intestazioni: < #T ## [Stringa: Stringa]?#>, MultipartFormData: < # T ## MultipartFormData -> Void #>

+0

Grazie Alessandro! Non mi ero reso conto che '.upload' aveva delle firme che includevano i parametri di' headers', la tua soluzione funziona molto! Finito usando 'multipartFormData', pubblicheremo il mio codice finale anche per riferimento futuro. – mathielo

0

Utilizzando @alessandro-ornano 's risposta sono stato in grado di fare una richiesta firmata caricamento utilizzando multipartFormData:

func uploadProfilePicture(photo: UIImage, callback: apiCallback){ 
    guard let userId = _loggedInUser?["pk"].int else { 
     callback(Response(success: false, responseMessage: "User not logged in")) 
     return 
    } 

    let requestData: requestDataType = ["timestamp": "\(Int(NSDate().timeIntervalSince1970))"] 
    let headers = self.signRequest(requestData) 

    _alamofireManager 
     .upload(.POST, "\(_apiBaseUrl)profiles/\(userId)/photo/", headers: headers, multipartFormData: { formData in 
      if let imageData = UIImageJPEGRepresentation(photo, 1){ 
       formData.appendBodyPart(data: imageData, name: "upload", fileName: "userphoto.jpg", mimeType: "image/jpg") 
      } 
      for (k, v) in requestData { 
       formData.appendBodyPart(data: v.dataUsingEncoding(NSUTF8StringEncoding)!, name: k) 
      } 
     }, encodingCompletion: { encodingResult in 
      switch encodingResult { 
      case .Success(let upload, _, _): 
       upload.responseJSON { response in 
        self.responseHandler(response, callback: callback) // Class' private method 
       } 
      case .Failure(let encodingError): 
       print(encodingError) 
       self.dispatch_callback(callback, response: Response(success: false, responseMessage: "Unable to encode files for upload")) // Class' private method 
      } 
     }) 
}