Scenario:
The app already uses Alamofire to make signed requests to the backend API. Really simple: the app sends three specific HTTP headers (Authorization
, X-Api-Key
and timestamp
) for the request to be signed. Calling Alamofire.request
it's easy to send headers
as parameter so it's working beautifully.
Now the users need to be able to upload their profile picture. Since the user is already logged into the app, the backend API will know which user is sending the picture by it's signed request - and that's the tricky part I've been struggling with for the past few hours. Alamofire.upload
accepts completely different parameters from .request
, so I can't figure out how to send headers when uploading a file.
Tried the old Alamofire.Manager.session.configuration.HTTPAdditionalHeaders
, but it's no longer supported. Found tons code examples of file upload, none consider sending custom headers.
How can I send custom headers when using Alamofire.upload
method?
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)
})
}
The requests goes through. In the backend log I can see the request returned HTTP 403
- Not authorized as it wasn't possible to sign the request. Printing the request headers, no custom auth headers were received by the server.
Before init, I want to share a free tool (chrome app) very useful during this type of works: DHC Rest Client : with this tool you can verify if your parameters, headers and your upload files work with the type of request you want to make to the server.
So, this work with Swift 2.x and Alamofire 3.x:
First of all prepare your headers:
let headers = [
"Content-Type": "application/zip",
"X-Api-Key": userApiKey,
...whatever you need on headers..
]
So, supposing you must send a zip file and the response will be a TEXT/HTML response type (a simple string with SUCCESS or 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)")
}
Thats all but if you want to use multipartformdata you can do it passing your headers through headers dictionary with:
.upload(<#T##method: Method##Method#>, <#T##URLString: URLStringConvertible##URLStringConvertible#>, headers: <#T##[String : String]?#>, multipartFormData: <#T##MultipartFormData -> Void#>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With