2014-02-18

ApexからSWFを触ってみる【REST API説明編】

前回、SWFの概要を説明しましたが、きっと私の説明力不足により

よくわからなかったと思うので、今回からは実際にApexからSWFを触ってみて挙動を確認します。

 

ApexでSWFを動かすには例のごとくSDKは存在しないので、

直接REST APIをコールする必要があります。

 

ということで、今日はREST APIをApexからコールするところのベース(Signature生成)を説明します!

 

さて、AWS REST API恒例のSignature生成ですが、

SimpleDBのSignature Version2やDynamoDBのSignature Version4と異なり、

Signature Version3という署名認証方式で行われます。

 

Signature Version3は以下の形式で生成します。

Canonicalヘッダ =
  "ヘッダ1:値1\n" +
  "ヘッダ2:値2" + ...;

string_to_sign = 
  "HTTPメソッド名\n" +
  "リソースのパス\n" +
  "クエリパラメータ\n" +
  "Canonicalヘッダ\n" +
  "HTTPリクエストのBody";

signature = hmac(digest(string_to_sign));

 

Apexのサンプルは以下のとおり

//Signatureの生成
private static String createSignature(Map<String, String> header_map, String postBody) {
    String string_to_sign = '';
    string_to_sign += 'POST' + Constants.LF;
    string_to_sign += '/' + Constants.LF;
    string_to_sign += '' + Constants.LF;
    string_to_sign += createCanonicalHeaders(header_map) + Constants.LF;
    string_to_sign += postBody;
    System.debug(string_to_sign);
    Blob b_sig = Crypto.generateMac(
        'hmacSHA256', 
        Crypto.generateDigest(
            'SHA256',
            Blob.valueOf(string_to_sign)
        ), 
        Blob.valueOf(AWSAccessKeySecret)
    );
    return EncodingUtil.base64Encode(b_sig); 
}

//Canonicalヘッダの生成
private static String createCanonicalHeaders(Map<String, String> headers) {
    if (headers == null || headers.keySet().isEmpty()) {
        return '';
    }

    Map<String, String> lowerCaseHeaders = new Map<String, String>();
    for (String key : headers.keySet()) {
        lowerCaseHeaders.put(key.toLowerCase(), headers.get(key));
    }
    String param = '';
    List<String> sortedKey = getSortedList(lowerCaseHeaders.keySet());
    for(String key: sortedKey){
        param += key + ':' + lowerCaseHeaders.get(key).trim() + '\n';
    }
    return param;
}

 

SWFだとHTTPメソッドはPOST固定で

リソースパスも"/"固定、クエリパラメータはないので空白

ということでstring_to_signの生成は4行目からが動的な部分になります。

 

実際に生成したstring_to_signはこんな感じになります。

POST
/

host:swf.us-east-1.amazonaws.com
x-amz-date:Sat, 15 Feb 2014 17:04:24 +0900
x-amz-target:com.amazonaws.swf.service.model.SimpleWorkflowService.StartWorkflowExecution

{JSON Body...}

 

あとは生成したstring_to_signのdigest値からハッシュ値を計算してBase64エンコードするだけ。

アルゴリズムはSHA1 or SHA256で。

 

Signatureを生成したら、AWS認証用ヘッダに入れてコールアウト。

private static String callSWFAPI (String action, String postBody) {
    Map<String, String> header_map = new Map<String, String> {
        'Host' => SWF_ENDPOINT_HOST,
        'X-Amz-Target' => 'com.amazonaws.swf.service.model.SimpleWorkflowService.' + action,
        'X-Amz-Date' => DateTime.now().format('EEE, dd MMM yyyy HH:mm:ss ') + '+0900'
    };

    HttpRequest req = new HttpRequest();

    for (String key : header_map.keySet()) {
        req.setHeader(key, header_map.get(key));
    }

    req.setHeader(
        'X-Amzn-Authorization',
        'AWS3 AWSAccessKeyId=' + AWSAccessKeyId + ',' +
        'Algorithm=HmacSHA256,' +
        'Signature=' + createSignature(header_map, postBody)
    );
    req.setHeader('Content-Type', 'application/x-amz-json-1.0');
    req.setHeader('Content-Length', String.valueOf(postBody.length()));

    req.setEndpoint('https://' + SWF_ENDPOINT_HOST + '/');
    req.setMethod('POST');
    req.setBody(postBody);

    Http http = new Http();
    HTTPResponse res = http.send(req);
    return res.getBody();
}

ちなみに、Host、X-Amz-Date、X-Amz-Targetヘッダは必須で

X-Amz-TargetはコールするAPIのメソッドを指定することになります。

Content-Typeもapplication/jsonとかじゃ通らないので、"application/x-amz-json-1.0"を指定。

HostはRESTのエンドポイントなのでリージョン毎の値を利用します。

 

あとは各メソッドに応じてX-Amz-TargetとHTTP Request Bodyを作っていくだけ!

 

ということで次回は実際にワークフローのスタート(StartWorkflowExecution)から

DeciderによるActivity Taskのスケジュール(ScheduleActivityTaskDecision)までをやってみます。

このエントリーをはてなブックマークに追加