Skip to content

Instantly share code, notes, and snippets.

@coryalder
Last active October 23, 2019 08:47
Show Gist options
  • Star 14 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save coryalder/d0ed60f14cb7f473a21a to your computer and use it in GitHub Desktop.
Save coryalder/d0ed60f14cb7f473a21a to your computer and use it in GitHub Desktop.
Amazon Product Advertising API + Alamofire
//
// AmazonProduct.swift
// Requires SHXMLParser for parsing Amazon's XML responses (https://github.com/simhanature/SHXMLParser)
//
// Created by Cory Alder on 2015-01-11.
// Copyright (c) 2015 Davander Mobile Corporation. All rights reserved.
//
// partly inspired by RWMAmazonProductAdvertisingManager
import Alamofire
import Foundation
import SHXMLParser
enum AmazonProductAdvertising: String {
case StandardRegion = "webservices.amazon.com"
case AWSAccessKey = "AWSAccessKeyId"
case TimestampKey = "Timestamp"
case SignatureKey = "Signature"
case VersionKey = "Version"
case AssociateTagKey = "AssociateTag"
case CurrentVersion = "2011-08-01"
}
let AWSDateISO8601DateFormat3 = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
func amazonRequest(parameters: [String: AnyObject]? = nil, serializer ser: AmazonSerializer?) -> Alamofire.Request {
let serializer: AmazonSerializer = {
if let s = ser {
return s
} else {
let s = AmazonSerializer(key: aws_key_default, secret: aws_secret_default)
s.useSSL = false
return s
}
}()
let URL = serializer.endpointURL()
let e = Alamofire.ParameterEncoding.Custom(serializer.serializerBlock())
let mutableURLRequest = NSMutableURLRequest(URL: URL)
mutableURLRequest.HTTPMethod = Alamofire.Method.GET.rawValue
let encoded: URLRequestConvertible = e.encode(mutableURLRequest, parameters: parameters).0
return Alamofire.request(encoded)
}
class AmazonSerializer {
let accessKey: String
let secret: String
var region: String = AmazonProductAdvertising.StandardRegion.rawValue
var formatPath: String = "/onca/xml"
var useSSL = true
init(key: String, secret sec: String) {
self.secret = sec
self.accessKey = key
}
func endpointURL() -> NSURL {
let scheme = useSSL ? "https" : "http"
let URL = NSURL(string: "\(scheme)://\(region)\(formatPath)")
return URL!
}
func serializerBlock() -> (URLRequestConvertible, [String: AnyObject]?) -> (NSMutableURLRequest, NSError?) {
return { (req, params) -> (NSMutableURLRequest, NSError?) in
// TODO: if params == nil error out ASAP
var mutableParameters = params!
let timestamp = AmazonSerializer.ISO8601FormatStringFromDate(NSDate())
if mutableParameters[AmazonProductAdvertising.AWSAccessKey.rawValue] == nil {
mutableParameters[AmazonProductAdvertising.AWSAccessKey.rawValue] = self.accessKey
}
mutableParameters[AmazonProductAdvertising.VersionKey.rawValue] = AmazonProductAdvertising.CurrentVersion.rawValue;
mutableParameters[AmazonProductAdvertising.TimestampKey.rawValue] = timestamp;
var canonicalStringArray = [String]()
// alphabetize
let sortedKeys = Array(mutableParameters.keys).sort {$0 < $1}
for key in sortedKeys {
canonicalStringArray.append("\(key)=\(mutableParameters[key]!)")
}
let canonicalString = canonicalStringArray.joinWithSeparator("&")
let encodedCanonicalString = CFURLCreateStringByAddingPercentEscapes(
nil,
canonicalString,
nil,
":,",//"!*'();:@&=+$,/?%#[]",
CFStringBuiltInEncodings.UTF8.rawValue
)
let method = req.URLRequest.HTTPMethod
let signature = "\(method)\n\(self.region)\n\(self.formatPath)\n\(encodedCanonicalString)"
let encodedSignatureData = signature.hmacSHA256(self.secret)
var encodedSignatureString = encodedSignatureData.base64EncodedString()
encodedSignatureString = CFURLCreateStringByAddingPercentEscapes(
nil,
encodedSignatureString,
nil,
"+=",//"!*'();:@&=+$,/?%#[]",
CFStringBuiltInEncodings.UTF8.rawValue
) as String
let newCanonicalString = "\(encodedCanonicalString)&\(AmazonProductAdvertising.SignatureKey.rawValue)=\(encodedSignatureString)"
let absString = req.URLRequest.URL!.absoluteString
let urlString = req.URLRequest.URL?.query != nil ? "\(absString)&\(newCanonicalString)" : "\(absString)?\(newCanonicalString)"
let request = req.URLRequest.mutableCopy() as! NSMutableURLRequest
request.URL = NSURL(string: urlString)
return (request, nil)
}
}
private class func ISO8601FormatStringFromDate(date: NSDate) -> NSString {
let dateFormatter = NSDateFormatter()
dateFormatter.timeZone = NSTimeZone(name: "GMT")
dateFormatter.dateFormat = AWSDateISO8601DateFormat3//"YYYY-MM-dd'T'HH:mm:ss'Z'"
dateFormatter.locale = NSLocale(localeIdentifier: "en_US_POSIX")
return dateFormatter.stringFromDate(date)
}
}
// XML ResponseSerializer
extension Request {
class func XMLResponseSerializer() -> GenericResponseSerializer<Dictionary<String,AnyObject>> {
return GenericResponseSerializer {
request, response, data in
guard data != nil else {
let failureReason = "Data could not be serialized. Input data was nil."
let error = Error.errorWithCode(.DataSerializationFailed, failureReason: failureReason)
return .Failure(data, error)
}
let parser = SHXMLParser()
if let document = parser.parseData(data) as? [String: AnyObject] {
return .Success(document)
} else {
let failureReason = "Data could not be serialized. for some unknwon reason"
let error = Error.errorWithCode(.DataSerializationFailed, failureReason: failureReason)
return .Failure(data, error as NSError)
}
}
}
public func responseXML(completionHandler: (NSURLRequest?, NSHTTPURLResponse?, Dictionary<String, AnyObject>?, ErrorType?) -> Void) -> Self {
let ser = Request.XMLResponseSerializer()
return response(queue: dispatch_get_main_queue(), responseSerializer: ser) {
req, res, xmlResult in
switch xmlResult {
case .Failure( _, let err): completionHandler(req, res, nil, err)
case .Success(let xml): completionHandler(req, res, xml, nil)
}
}
}
}
let associate_tag_default = "weio-20"
let aws_key_default = "your amazon key"
let aws_secret_default = "your amazon secret"
let asin = "B00DVDMRX6" // an amazon id to search for
let serializer = AmazonSerializer(key: aws_key_default, secret: aws_secret_default)
let amazonParams = [
"Service" : "AWSECommerceService",
"Operation" : "ItemLookup",
"ResponseGroup" : "Images,ItemAttributes",
"IdType" : "ASIN",
"ItemId" : asin,
"AssociateTag" : associate_tag_default,
"Condition" : "All"
]
amazonRequest(parameters: amazonParams, serializer: serializer).responseXML { (req, res, data, error) -> Void in
println("Got results! \(data)")
}
@odedharth
Copy link

Thanks!

Why did you remove the hmacSHA256 in the last revision?

@KevinHorvath
Copy link

I am having a lot of trouble getting this working.. Could you please update it to the latest version of swift or maybe a working example that we can open and run in Xcode?

@gtaylor5
Copy link

I am reading this code and I am wondering if it still works with out the hmacsha256. I will be trying it later.

@dudil
Copy link

dudil commented Aug 2, 2017

Thanks!
Though I ended up not using Alamofire it did helped a lot to understand the concept.

On line 112 you are missing the extension to string in order to hash the code.
I've added it from another source and it worked great! (just for other reference).

extension String {
    func hmacSHA256(key: String) -> String {
        let cKey = key.cString(using: String.Encoding.utf8)
        let cData = self.cString(using: String.Encoding.utf8)
        var result = [CUnsignedChar](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
        CCHmac(CCHmacAlgorithm(kCCHmacAlgSHA256), cKey!, Int(strlen(cKey!)), cData!, Int(strlen(cData!)), &result)
        let hmacData:NSData = NSData(bytes: result, length: (Int(CC_SHA256_DIGEST_LENGTH)))
        let hmacBase64 = hmacData.base64EncodedString(options: NSData.Base64EncodingOptions.lineLength76Characters)
        return String(hmacBase64)
    }
}

@pavan4
Copy link

pavan4 commented Nov 15, 2017

Did anyone manage to get this running?

@haansplosion
Copy link

It looks so useful! Unable to get it running on xcode 9.1 swift 3, so many errors :-(

@Parameshvadivel
Copy link

Hey can you update it to swift 4

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment