GAE + GWT プログラミングメモ

Google App Engine とGoogle Web Toolkit のメモ

JavaでTwitter REST API v1.1のリクエスト

概要

GAEでTwitterREST API v1.1を使って色々情報を取得する方法を書いていきます。
有名どころでtwitter4jというものもありますが、GAEだからかうまくいかなかったので、
素でリクエストを投げます。

Twitterアプリケーション登録

以前のTwitter APIバージョンであれば、特定のプロファイル情報を取得するのは苦なく
できていたみたいだけれど、v1.1からは、OAuth認証必須になっています。
ので、まずは、Twitterにアプリケーションを登録します。
参考したのはこの辺
Twitterアプリケーション登録の仕方 - Ignis
今回はプロファイル情報を取得したいので、
1.Consumer key
2.Consumer secret
3.Access token
4.Access token secret
があればOK!

目標

ローゼンメイデンの公式ツイッターのプロファイル情報(プロファイル画像URL等)を取得する。

ライブラリ

apache commonsのBase64を使いたいので、
Codec - Download Commons Codec
からダウンロードし、ビルドパスに追加。
Eclipse上のwar/WEB-INF/libにドラッグアンドドラッグしてコピーする。

Javaコード

Simplest Java example retrieving user_timeline with twitter API version 1.1 - Stack Overflowを参考にしました。
というより、自分にわかりやすいように、書き換えただけです。

String getUsersShow(String screen_name)

このメソッドは指定したscreen_nameのプロファイル情報をJSON形式のStringで返す。
今回、実装したいメソッドになる。
ローゼンメイデン公式ツイッターのscreen_nameはrozen_anime。

    public String getUsersShow(String screen_name)
            throws InvalidKeyException, NoSuchAlgorithmException, MalformedURLException, IOException {
        String method = "GET";
        String url = "https://api.twitter.com/1.1/users/show.json";
        Map<String, String> paramMap = getUsersShowParamMap(screen_name);
        Map<String, String> oAuthParamMap = getOAuthParamMap();
        
        String urlWithParams = getUrlWithParams(url, paramMap);
        String signatureBaseString = getSignatureBaseString(method, url, paramMap, oAuthParamMap);
        String authorizationHeaderValue = getAuthorizationHeaderValue(signatureBaseString, oAuthParamMap);
        
        return request(urlWithParams, authorizationHeaderValue);
    }

urlはGET users/show | Twitter Developersで与えられているものを指定する。

Map getUsersShowParamMap(String screen_name)

screen_nameとrozen_animeをMapに入れる。
このMapに入っている値が、URLのケツに追加される。
https://api.twitter.com/1.1/users/show.json?screen_name=rozen_animeみたいな感じ。
別のAPIであれば、このパラメータMapを変更すればよい。

    private Map<String, String> getUsersShowParamMap(String screen_name) {
        Map<String, String> urlParamMap = new HashMap<String, String>();
        urlParamMap.put("screen_name",screen_name);
        
        return urlParamMap;
    }
Map getOAuthParamMap()

OAuth認証するために、必要なパラメータをMapで返す。

    private Map<String, String> getOAuthParamMap() {
        String oAuthConsumerKey = "1.Consumer key";
        String oAuthAccessToken = "3.Access token";
        String oAuthNonce = String.valueOf(System.currentTimeMillis());
        String oAuthSignatureMethod = "HMAC-SHA1";
        String oAuthTimestamp = getTimestamp();
        String oAuthVersion = "1.0";
        
        Map<String, String> paramMap = new HashMap<String, String>();
        
        paramMap.put("oauth_consumer_key", oAuthConsumerKey);
        paramMap.put("oauth_nonce", oAuthNonce);
        paramMap.put("oauth_signature_method", oAuthSignatureMethod);
        paramMap.put("oauth_timestamp", oAuthTimestamp);
        paramMap.put("oauth_token", oAuthAccessToken);
        paramMap.put("oauth_version", oAuthVersion);

        return paramMap;
    }

oAuthConsumerKeyとoAuthAccessTokenにはTwitterアプリケーション登録で取得した1.Consumer keyと3.Access tokenを入れる。

String getSignatureBaseString(String method, String url, Map urlParamMap, Map oAuthParamMap)

OAuth認証のために、getUsersShowParamMapとgetOAuthParamMapで取得したパラメータを足し合わせてを一つの文字列にする。

    private String getAuthorizationHeaderValue(String signatureBaseString, Map<String, String> oAuthParamMap)
            throws UnsupportedEncodingException, InvalidKeyException, NoSuchAlgorithmException {
        String oAuthConsumerSecret = "2.Consumer secret";
        String oAuthAccessTokenSecret = "4.Access token secret";
        String compositeKey = URLEncoder.encode(oAuthConsumerSecret, "UTF-8") + "&" 
                + URLEncoder.encode(oAuthAccessTokenSecret, "UTF-8");

        String oAuthSignature =  computeSignature(signatureBaseString, compositeKey);

        String oAuthSignatureEncoded = URLEncoder.encode(oAuthSignature, "UTF-8");

        String authorizationHeaderValueTempl = 
                "OAuth oauth_consumer_key=\"%s\", oauth_nonce=\"%s\", oauth_signature=\"%s\", " + 
                "oauth_signature_method=\"%s\", oauth_timestamp=\"%s\", oauth_token=\"%s\", oauth_version=\"%s\"";
        String authorizationHeaderValue = String.format(
                authorizationHeaderValueTempl,
                oAuthParamMap.get("oauth_consumer_key"),
                oAuthParamMap.get("oauth_nonce"),
                oAuthSignatureEncoded,
                oAuthParamMap.get("oauth_signature_method"),
                oAuthParamMap.get("oauth_timestamp"),
                oAuthParamMap.get("oauth_token"),
                oAuthParamMap.get("oauth_version"));
        
        return authorizationHeaderValue;
    }

oAuthConsumerSecretとoAuthAccessTokenSecret にはTwitterアプリケーション登録で取得した2.Consumer secretと4.Access token secretを入れる。

private String getUrlWithParams(String url, Map paramMap)

https://api.twitter.com/1.1/users/show.json?screen_name=rozen_animeを作る。

    private String getUrlWithParams(String url, Map<String, String> paramMap) {
        StringBuffer urlWithParams = new StringBuffer(url);
        TreeMap<String, String> treeMap = new TreeMap<String, String>();
        treeMap.putAll(paramMap);
        for (Entry<String, String> paramEntry : treeMap.entrySet()) {
            if (paramEntry.equals(treeMap.firstEntry())) {
                urlWithParams.append("?");
            } else {
                urlWithParams.append("&");
            }
            urlWithParams.append(paramEntry.getKey() + "=" + paramEntry.getValue());
        }
        
        return urlWithParams.toString();
    }
String computeSignature(String baseString, String keyString)

signatureBaseString、2.Consumer secret、4.Access token secretでシグネチャを計算する。
得られたシグネチャBase64エンコードする。

    private static String computeSignature(String baseString, String keyString)
            throws NoSuchAlgorithmException, InvalidKeyException
    {
        SecretKey secretKey = null;

        byte[] keyBytes = keyString.getBytes();
        secretKey = new SecretKeySpec(keyBytes, "HmacSHA1");

        Mac mac = Mac.getInstance("HmacSHA1");

        mac.init(secretKey);

        byte[] text = baseString.getBytes();

        return new String(Base64.encodeBase64(mac.doFinal(text))).trim();
    }
String getTimestamp()

タイムスタンプをStringで取得する。

    private String getTimestamp() {
        long millis = System.currentTimeMillis();
        long secs = millis / 1000;
        return String.valueOf( secs );
    }
String request(String urlWithParams, String authorizationHeaderValue)

getUrlWithParamsとgetAuthorizationHeaderValueで得られる値を使って、Twitterにリクエストする。
TwitterからはJSONの文字列が返ってくる。

    private String request(String urlWithParams, String authorizationHeaderValue)
            throws MalformedURLException, IOException {
        URLConnection urlConnection = new URL(urlWithParams).openConnection();
        urlConnection.setRequestProperty("Authorization", authorizationHeaderValue);
        
        BufferedReader br = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));
        StringBuilder sb = new StringBuilder();
        String line;
        while ((line = br.readLine()) != null) {
            sb.append(line);
        }

        br.close();
        
        return sb.toString();
    }
全コード
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;

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

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

public class Twitter {
    public String getUsersShow(String screen_name)
            throws InvalidKeyException, NoSuchAlgorithmException, MalformedURLException, IOException {
        String method = "GET";
        String url = "https://api.twitter.com/1.1/users/show.json";
        Map<String, String> paramMap = getUsersShowParamMap(screen_name);
        Map<String, String> oAuthParamMap = getOAuthParamMap();
        
        String urlWithParams = getUrlWithParams(url, paramMap);
        String signatureBaseString = getSignatureBaseString(method, url, paramMap, oAuthParamMap);
        String authorizationHeaderValue = getAuthorizationHeaderValue(signatureBaseString, oAuthParamMap);
        
        return request(urlWithParams, authorizationHeaderValue);
    }
    
    private String request(String urlWithParams, String authorizationHeaderValue)
            throws MalformedURLException, IOException {
        URLConnection urlConnection = new URL(urlWithParams).openConnection();
        urlConnection.setRequestProperty("Authorization", authorizationHeaderValue);
        
        BufferedReader br = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));
        StringBuilder sb = new StringBuilder();
        String line;
        while ((line = br.readLine()) != null) {
            sb.append(line);
        }

        br.close();
        
        return sb.toString();
    }
    
    private Map<String, String> getUsersShowParamMap(String screen_name) {
        Map<String, String> urlParamMap = new HashMap<String, String>();
        urlParamMap.put("screen_name",screen_name);
        
        return urlParamMap;
    }
    
    private Map<String, String> getOAuthParamMap() {
        String oAuthConsumerKey = "1.Consumer key";
        String oAuthAccessToken = "3.Access token";
        String oAuthNonce = String.valueOf(System.currentTimeMillis());
        String oAuthSignatureMethod = "HMAC-SHA1";
        String oAuthTimestamp = getTimestamp();
        String oAuthVersion = "1.0";
        
        Map<String, String> paramMap = new HashMap<String, String>();
        
        paramMap.put("oauth_consumer_key", oAuthConsumerKey);
        paramMap.put("oauth_nonce", oAuthNonce);
        paramMap.put("oauth_signature_method", oAuthSignatureMethod);
        paramMap.put("oauth_timestamp", oAuthTimestamp);
        paramMap.put("oauth_token", oAuthAccessToken);
        paramMap.put("oauth_version", oAuthVersion);

        return paramMap;
    }
    
    private String getSignatureBaseString(String method, String url,
            Map<String, String> urlParamMap, Map<String, String> oAuthParamMap) throws UnsupportedEncodingException {
        TreeMap<String, String> sortedParamMap = new TreeMap<String, String>();
        sortedParamMap.putAll(urlParamMap);
        sortedParamMap.putAll(oAuthParamMap);
        
        StringBuffer paramStringBuffer = new StringBuffer();
        for (Entry<String, String> paramEntry : sortedParamMap.entrySet()) {
            if (!paramEntry.equals(sortedParamMap.firstEntry())) {
                paramStringBuffer.append("&");
            }
            paramStringBuffer.append(paramEntry.getKey() + "=" + paramEntry.getValue());
        }
        
        String signatureBaseStringTemplate = "%s&%s&%s";
        String signatureBaseString =  String.format(
                signatureBaseStringTemplate, 
                URLEncoder.encode(method, "UTF-8"), 
                URLEncoder.encode(url, "UTF-8"),
                URLEncoder.encode(paramStringBuffer.toString(), "UTF-8"));
        
        return signatureBaseString;
    }
    
    private String getAuthorizationHeaderValue(String signatureBaseString, Map<String, String> oAuthParamMap)
            throws UnsupportedEncodingException, InvalidKeyException, NoSuchAlgorithmException {
        String oAuthConsumerSecret = "2.Consumer secret";
        String oAuthAccessTokenSecret = "4.Access token secret";
        String compositeKey = URLEncoder.encode(oAuthConsumerSecret, "UTF-8") + "&" 
                + URLEncoder.encode(oAuthAccessTokenSecret, "UTF-8");

        String oAuthSignature =  computeSignature(signatureBaseString, compositeKey);
        System.out.println("oAuthSignature       : "+oAuthSignature);

        String oAuthSignatureEncoded = URLEncoder.encode(oAuthSignature, "UTF-8");
        System.out.println("oAuthSignatureEncoded: "+oAuthSignatureEncoded);

        String authorizationHeaderValueTempl = 
                "OAuth oauth_consumer_key=\"%s\", oauth_nonce=\"%s\", oauth_signature=\"%s\", " + 
                "oauth_signature_method=\"%s\", oauth_timestamp=\"%s\", oauth_token=\"%s\", oauth_version=\"%s\"";
        String authorizationHeaderValue = String.format(
                authorizationHeaderValueTempl,
                oAuthParamMap.get("oauth_consumer_key"),
                oAuthParamMap.get("oauth_nonce"),
                oAuthSignatureEncoded,
                oAuthParamMap.get("oauth_signature_method"),
                oAuthParamMap.get("oauth_timestamp"),
                oAuthParamMap.get("oauth_token"),
                oAuthParamMap.get("oauth_version"));
        
        return authorizationHeaderValue;
    }
    
    private String getUrlWithParams(String url, Map<String, String> paramMap) {
        StringBuffer urlWithParams = new StringBuffer(url);
        TreeMap<String, String> treeMap = new TreeMap<String, String>();
        treeMap.putAll(paramMap);
        for (Entry<String, String> paramEntry : treeMap.entrySet()) {
            if (paramEntry.equals(treeMap.firstEntry())) {
                urlWithParams.append("?");
            } else {
                urlWithParams.append("&");
            }
            urlWithParams.append(paramEntry.getKey() + "=" + paramEntry.getValue());
        }
        
        return urlWithParams.toString();
    }
    
    private static String computeSignature(String baseString, String keyString)
            throws NoSuchAlgorithmException, InvalidKeyException
    {
        SecretKey secretKey = null;

        byte[] keyBytes = keyString.getBytes();
        secretKey = new SecretKeySpec(keyBytes, "HmacSHA1");

        Mac mac = Mac.getInstance("HmacSHA1");

        mac.init(secretKey);

        byte[] text = baseString.getBytes();

        return new String(Base64.encodeBase64(mac.doFinal(text))).trim();
    }

    private String getTimestamp() {
        long millis = System.currentTimeMillis();
        long secs = millis / 1000;
        return String.valueOf( secs );
    }
}

JSONのパース

JSONをパースして、情報を得る必要がありますが、適当にライブラリ導入してやればいいじゃないですかね。
GWTには、JSONをパースするクラスがあるので、クライアントサイドにそのまま投げちゃってもいい。

終わり

雲の中の2次にアニメ情報を追加したいなと思い、Twitter APIでプロファイル情報を取得する方法を調べました。
今時、公式ツイッターアカウントを持っていないほうが珍しいし、うまく組み込めると面白いことができるんじゃないですかね。