VB.NETでのProduct Advertising API 署名認証
VB.NETでのProduct Advertising API 署名認証してみました。
いろいろなサンプルとかあるけど、VB.NETの完全なサンプル見つからないし、
わかりにくかったので、情報集めて、動くようにしました。
Amazonが署名認証導入してから、ハマっている人たくさんいそう。
VS.NET2008で作ってます。
まず、System.WEBを参照に追加。
次に以下のモジュールを追加
**********************************************************************
Module ModuleAmazonAPI
    Dim sAssociateTag As String = "[アソシエイトタグ]"
    Dim sAccessKeyId As String = "[アクセスキーID]"
    Dim sSecretAccessKey As String = "[シークレットアクセスキー]"
    Public Function MakeAmazonRequest( _
    ByVal sAssociateTag As String, _
    ByVal sAccessKeyId As String, _
    ByVal sSecretAccessKey As String, _
    ByVal sSearchKeyWord As String) As String
        Dim dictRequest As New Dictionary(Of String, String)
        'リクエストパラメータをハッシュテーブルに保存
        dictRequest("Service") = "AWSECommerceService"
        dictRequest("Version") = "2008-04-07"
        dictRequest("AssociateTag") = sAssociateTag
        dictRequest("ContentType") = "Text/XML"
        dictRequest("Operation") = "ItemSearch"
        dictRequest("ResponseGroup") = "Small,ItemAttributes,Reviews,EditorialReview,Images,BrowseNodes,SalesRank,Offers"
        dictRequest("AWSAccessKeyId") = sAccessKeyId
        dictRequest("SearchIndex") = "All"
        dictRequest("Keywords") = sSearchKeyWord
        Dim dteNow As DateTime = DateTime.UtcNow
        Dim sTimeStamp As String = dteNow.ToString("yyyy-MM-ddTHH:mm:ssZ")
        'タイムスタンプはGMT
        dictRequest("Timestamp") = sTimeStamp
        Dim pc As New ParamComparer()
        Dim sortedHash As New SortedDictionary(Of String, String)(dictRequest, pc)
        ' Get the canonical query string
        Dim canonicalQS As String = ConstructCanonicalQueryString(sortedHash)
        '以下の部分を追加
        '============================
        'GET
        'ecs.amazonaws.jp
        '/onca/xml
        '============================
        Dim builder As New System.Text.StringBuilder()
        builder.Append("GET").Append(vbLf).Append(CStr("ecs.amazonaws.jp").ToLower()).Append(vbLf).Append("/onca/xml").Append(vbLf).Append(canonicalQS)
        Dim stringToSign As String = builder.ToString()
        Dim toSign As Byte() = System.Text.Encoding.UTF8.GetBytes(stringToSign)
        Dim secret As Byte()
        secret = System.Text.Encoding.UTF8.GetBytes(sSecretAccessKey)
        Dim signer As System.Security.Cryptography.HMAC
        signer = New Security.Cryptography.HMACSHA256(secret)
        Dim sigBytes As Byte() = signer.ComputeHash(toSign)
        Dim signature As String = Convert.ToBase64String(sigBytes)
        Dim qsBuilder As New System.Text.StringBuilder()
        qsBuilder.Append(" http://").Append("ecs.amazonaws.jp").Append("/onca/xml").Append("?").Append(canonicalQS).Append("&Signature=").Append(PercentEncodeRfc3986(signature))
        Return qsBuilder.ToString()
    End Function
    Private Function PercentEncodeRfc3986(ByVal str As String) As String
        str = System.Web.HttpUtility.UrlEncode(str, System.Text.Encoding.UTF8)
        'str.Replace("'", "%27").Replace("(", "%28").Replace(")", "%29").Replace("*", "%2A").Replace("!", "%21").Replace("%7e", "~")
        str = str.Replace("'", "%27").Replace("(", "%28").Replace(")", "%29").Replace("*", "%2A").Replace("!", "%21").Replace("%7e", "~").Replace("+", "%20")
        Dim sbuilder As New System.Text.StringBuilder(str)
        For i As Integer = 0 To sbuilder.Length - 1
            If sbuilder(i) = "%"c Then
                'コメント化
                'If [Char].IsDigit(sbuilder(i + 1)) AndAlso [Char].IsLetter(sbuilder(i + 2)) Then
                sbuilder(i + 1) = [Char].ToUpper(sbuilder(i + 1)) '日本語対策で追加
                sbuilder(i + 2) = [Char].ToUpper(sbuilder(i + 2))
                'End If
            End If
        Next
        Return sbuilder.ToString()
    End Function
    Private Function ConstructCanonicalQueryString(ByVal sortedParamMap As SortedDictionary(Of String, String)) As String
        Dim builder As New System.Text.StringBuilder()
        If sortedParamMap.Count = 0 Then
            builder.Append("")
            Return builder.ToString()
        End If
        For Each kvp As KeyValuePair(Of String, String) In sortedParamMap
            builder.Append(PercentEncodeRfc3986(kvp.Key))
            builder.Append("=")
            builder.Append(PercentEncodeRfc3986(kvp.Value))
            builder.Append("&")
        Next
        Dim canonicalString As String = builder.ToString()
        canonicalString = canonicalString.Substring(0, canonicalString.Length - 1)
        Return canonicalString
    End Function
    Class ParamComparer
        Implements IComparer(Of String)
        Public Function Compare(ByVal p1 As String, ByVal p2 As String) As Integer Implements IComparer(Of String).Compare
            Return String.CompareOrdinal(p1, p2)
        End Function
    End Class
End Module
**********************************************************************
使いかたは、
**********************************************************************
        Dim sAssociateTag As String = "[アソシエイトタグ]"
        Dim sAccessKeyId As String = "[アクセスキーID]"
        Dim sSecretAccessKey As String = "[シークレットアクセスキー]"
        Dim sURL as String
        sURL = MakeAmazonRequest(sAssociateTag, sAccessKeyId, sSecretAccessKey, "ラーメンマン")
**********************************************************************
sURLでアクセスすれば、検索結果を得られます。