Translate

2012年4月10日火曜日

CloudStack API を呼び出すURLを生成するJavaサンプルコード

CloudStack API を呼び出すURL文字列の作り方を調べる
で変換の仕方と
変換してくれるシェルスクリプトを見つけたところまで紹介しました。

が、やはりJavaプログラムから呼び出したい。

処理も面倒なだけで実装が難しいわけではないので
ちょこっと自分で書いてみました。

コマンド2つ(listAccountsとlistVirtualMachines)だけしか
動作確認していないのでバグがあるかもしれません。

誤りを見つけた方は、
できればコメント欄にて指摘をしてもらえるとありがたいです。

コピーして利用されるのはOKですが、
参考にされる方はAt Your Own Riskでお願いします。

Base64変換は、Java SEの標準ライブラリになさそうだったので
Apache Commonsのcommons-codec-1.6.jarを使用しています。

使い方はとりあえず先頭のコメントに書いておいたので
なんとなく分かると思います。

/*
 * CloudStack API をHTTP GETメソッドで呼び出すためのURLを生成するクラス実装サンプル。
 * 流用はOKですが、くれぐれもAt Your Own Riskでお願いします。
 * 
 * 使用例)
 * CloudStackApiUrlGenerator gen =
 *  new CloudStackApiGenerator(
 *   "qu1uxi73n6hkyltwna7yxho_uljuhef... ", // APIキー
 *   "zixctf7tkz3s6f-z0tr-pvcb... ");       // 秘密キー
 * gen.setParam("command", "listAccounts"); // command名登録
 * // パラメータを設定する場合はsetParam()/setParams()で格納する
 * String url = gen.getApiRequest(); // 呼び出すURL文字列
 * 
 * なお本クラスではBase64エンコード処理に Apache Commons ライブラリを使用している。
 * Apache Commons http://bit.ly/IywKHK よりdownloadし、
 * commons-codec-1.6.jar(1.6の場合) をクラスパスに追加して下さい。
 * 
 * (C)Copyright by Hara-Hara-Kaihatsu, Japan, 2012
 */
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.TreeMap;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.binary.Base64;

/**
 * CloudStackApiUrlGeneratorクラス
 * 
 * @author ton / Hara-Hara-Kaihatsu
 */
public class CloudStackApiUrlGenerator {
  /**
   * APIキー
   */
  private String apiKey = null;
  /**
   * 秘密キー
   */
  private String secretKey = null;
  /**
   * GETパラメータを格納するMap
   */
  private Map<String, String> params = new HashMap<String, String>();
  
  /**
   * 唯一のコンストラクタ。必須であるAPIキーと秘密キーはManagement Serverの
   * 管理コンソールのアカウント(ユーザ)から生成し文字列を取得する
   * @param apiKey APIキー文字列
   * @param secretKey 秘密キー文字列
   */
  public CloudStackApiUrlGenerator(String apiKey, String secretKey){
    this.apiKey = apiKey;
    this.secretKey = secretKey;
  }

  /**
   * Command String(?以降のGETパラメータ)を登録する。
   * field1=value1&field2=value2&..&fieldN=valueN 形式の文字列で
   * 複数のパラメータを一括セットすることができる。
   * @param paramPairs field1=value1&field2=value2&..&fieldN=valueN 形式の文字列
   * @throws UnsupportedEncodingException URLエンコード処理時発生する例外
   */
  public void setParams(String paramPairs)
      throws UnsupportedEncodingException{
    StringTokenizer st = new StringTokenizer(paramPairs, "&");
    while(st.hasMoreTokens()){
      setParam(st.nextToken());
    }
  }
  
  /**
   * Command String(?以降のGETパラメータ)を1件登録する。
   * field=value 形式の文字列で1セットのみ格納できる。
   * @param paramPair field=value 形式の文字列
   * @throws UnsupportedEncodingException URLエンコード処理時発生する例外
   */
  public void setParam(String paramPair)
      throws UnsupportedEncodingException{
    params.put(getField(paramPair), encodeUrl(getValue(paramPair)));
  }
  
  /**
   * Command String(?以降のGETパラメータ)を1件登録する。
   * @param field フィールド名
   * @param value 設定値
   * @throws UnsupportedEncodingException URLエンコード処理時発生する例外
   */
  public void setParam(String field, String value)
      throws UnsupportedEncodingException{
    params.put(field, encodeUrl(value));
  }
  
  /**
   * Command Stringを1つの文字列として取得する。
   * @return Command String文字列(signatureなし)
   * @throws UnsupportedEncodingException URLエンコード処理時発生する例外
   */
  public String getCommandString() throws UnsupportedEncodingException{
    Map<String, String> comParams = new HashMap<String, String>(params);
    String commandValue = comParams.remove("command");
    String apiKeyValue = comParams.remove("apikey");
    if(apiKeyValue==null || "".equals(apiKeyValue)){
      apiKeyValue = encodeUrl(apiKey);
    }
    comParams.remove("signature");

    StringBuffer commandString = new StringBuffer();
    commandString.append("command=");
    commandString.append(commandValue);
    commandString.append("&");
    Set<String> keyMap = comParams.keySet();
    for(String key: keyMap){
      commandString.append(key);
      commandString.append("=");
      commandString.append(comParams.get(key));
      commandString.append("&");
    }
    commandString.append("apikey=");
    commandString.append(apiKeyValue);
    return commandString.toString();
  }

  /**
   * signatureとして指定する値を生成する。
   * @return signatureとして指定する文字列
   * @throws InvalidKeyException HMACSHA1処理時に発生する例外
   * @throws NoSuchAlgorithmException HMACSHA1処理時に発生する例外
   * @throws UnsupportedEncodingException HMACSHA1処理時に発生する例外
   */
  public String getSigniture()
      throws InvalidKeyException, NoSuchAlgorithmException, 
      UnsupportedEncodingException{
    Map<String, String> sortedMap = new TreeMap<String, String>(params);
    if(apiKey!=null && !params.containsKey("apikey")){
      sortedMap.put("apikey", apiKey);
    }
    sortedMap.remove("signiture");
    int pos = 0;
    StringBuffer sortedParamPairs = new StringBuffer();
    Set<String> keySet = sortedMap.keySet();
    for(String key: keySet){
      sortedParamPairs.append(key.toLowerCase());
      sortedParamPairs.append("=");
      sortedParamPairs.append(sortedMap.get(key).toLowerCase());
      if((++pos)<keySet.size()) sortedParamPairs.append("&");
    }
    String signature = createHash(sortedParamPairs.toString());
    return encodeUrl(signature);
  }

  /**
   * HTTP GEPメソッドを呼び出すためのURLを取得する。
   * @param hostname ホスト名FQDNもしくはIPアドレス
   * @param port ポート番号文字列
   * @return URL文字列
   * @throws InvalidKeyException HMACSHA1処理中に発生する例外
   * @throws NoSuchAlgorithmException HMACSHA1処理中に発生する例外
   * @throws UnsupportedEncodingException HMACSHA1処理中に発生する例外
   */
  public String getApiRequestUrl(String hostname, String port)
      throws InvalidKeyException, NoSuchAlgorithmException, 
      UnsupportedEncodingException{
    StringBuffer request = new StringBuffer();
    request.append("http://");
    if(hostname==null || "".equals(hostname)) request.append("localhost");
    else request.append(hostname);
    request.append(":");
    if(port==null || "".equals(port)) request.append("8080");
    else request.append(port);
    request.append("/client/api?");
    request.append(getCommandString());
    request.append("&signature=");
    request.append(getSigniture());
    return request.toString();
  }
  
  /**
   * field=value 形式文字列からfield部分を切り出す。
   * @param paramPair field=value形式文字列
   * @return field部分の文字列
   */
  private String getField(String paramPair){
    if(paramPair==null || "".equals(paramPair)) return paramPair;
    int pos = paramPair.indexOf("=");
    if(pos < 0) return paramPair;
    if(pos == 0) return "";
    return paramPair.substring(0, pos);
  }
  
  /**
   * field=value 形式文字列からvalue部分を切り出す。
   * @param paramPair field=value形式文字列
   * @return value部分の文字列
   */  
  private String getValue(String paramPair){
    if(paramPair==null || "".equals(paramPair)) return paramPair;
    int pos = paramPair.indexOf("=");
    if(pos < 0) return paramPair;
    if(pos == (paramPair.length() - 1)) return "";
    return paramPair.substring(pos+1);
  }
  
  /**
   * URLエンコード(UTF-8)を実行し、スペース文字を%20に置換する。
   * @param org 処理対象文字列
   * @return 変換後文字列
   * @throws UnsupportedEncodingException URLエンコード処理中に発生する例外
   */
  private String encodeUrl(String org) throws UnsupportedEncodingException{
    if(org==null) return org;
    return URLEncoder.encode(org, "UTF-8").replaceAll("\\+", "%20");
  }
  
  /**
   * HMAC SHA1形式でエンコードする。
   * エンコード時にコンストラクタにて指定した秘密キーを使用する。
   * @param sortedParamPairs エンコード対象文字列
   * @return エンコード後文字列
   * @throws NoSuchAlgorithmException エンコード処理中発生する例外
   * @throws InvalidKeyException エンコード処理中発生する例外
   */
  private String createHash(String sortedParamPairs)
      throws NoSuchAlgorithmException, InvalidKeyException{
    SecretKeySpec secretKeySpec = 
        new SecretKeySpec(secretKey.getBytes(), "HmacSHA1");
    Mac mac = Mac.getInstance("HmacSHA1");
    mac.init(secretKeySpec);
    byte[] hash = mac.doFinal(sortedParamPairs.getBytes());
    // AppEngineの場合
    //return com.google.appengine.repackaged.com.google.common.util.Base64.encode(hash);
    // Apache Commonsを使う場合
    hash = Base64.encodeBase64(hash);
    return new String(hash);
  }
}

あとは..
Reference APIの日本語訳がほしいなあ..

誰か翻訳してくれないかなあ..
と、つぶやいてみる。

0 件のコメント:

既存アプリケーションをK8s上でコンテナ化して動かす場合の設計注意事項メモ

既存アプリをK8sなどのコンテナにして動かすには、どこを注意すればいいか..ちょっと調べたときの注意事項をメモにした。   1. The Twelve Factors (日本語訳からの転記) コードベース   バージョン管理されている1つのコードベースと複数のデプロイ 依存関係 ...