2010年2月27日土曜日

Slim3でクラウド開発を始める。その3


Slim3の開発はこんなブログより正式ドキュメントが有効です。
Slim3サイト
非公式と言われていますが充実した日本語サイトもあります。
Slim3日本語サイト




第一弾第二弾

そういえば少し前にGAEがDataNucleusを使ってるので
それを使って、HSQLDBにつなげた事もありました(>_<)


で本題です。
Slim3は同時にテストも出力してくれます。
なのでその設定をします。

まずプロジェクトを選択して、設定を行います。
その後、Javaのコードスタイル、インポートの編成を選択して
プロジェクト特有の設定を行います。



(java.lang.Math.*)に必要なインポート数を「1」に設定します。



終わったら画面を閉じて「ウィンドウ」から「設定」を選択します。


Javaのエディタにコンテンツ・アシストがあります。
そこにお気に入りがあるので選択して


以下を追加します。

org.hamcrest.CoreMatchers
org.junit.Assert
org.junit.matchers.JUnitMatchers


で「一般」を選択すると「ワークスペース」があります。


そこに「自動的にリフレッシュ」がありますんで


選択してください。

2010年2月22日月曜日

Slim3でクラウド開発を始める。その2


Slim3の開発はこんなブログより正式ドキュメントが有効です。
Slim3サイト
非公式と言われていますが充実した日本語サイトもあります。
Slim3日本語サイト





第一弾

さぁ第二弾です。
いよいよSlim3 RC1が近づいて参りました。
今回はsllim3-blankの名称を変更したい時の話をします。


まずプロジェクトで右クリックします。
その後リファクタを選んで、名前変更を選択します。
もしくはプロジェクトを選択して、F2でもOK。

出てきたボックスに対して、
自分の好きな名前(任意)を書きます。

するとプロジェクト名が変わります。
その後、build.xmlを選択して、
nameを変更します。

こんな感じですかね?

第三弾に続く。

FlexでGoogleAnalytics

Flexでサービスを作成していましたが、
HTMLの呼び出ししかアクセス解析ができてなくて
ViewStack化しているような位置のアクセス解析ができてなかったので
アクセスログを取りたいなぁ。。。って思ってたら
「gaforflash」たるものを見つけました。


ダウンロードしたファイルのなかに[lib/analytics.swc]がありますので
ライブラリに追加してあげます。

Flashで使う場合はもう一個の「lib/analytics_flash.swc」を
使うみたいですね。



private static var tracker:AnalyticsTracker = null;
public static function initTracker(object:DisplayObject):void {
if (tracker == null) {
tracker = new GATracker(object, "UA-?????-??", "AS3");
}
}
public static function viewPage(pageUrl:String):void {
if ( tracker == null ) return;
tracker.trackPageview(pageUrl);
}



巷に落ちてるサンプルだと、常にnewしているようでしたが、
なんか勿体なかったので、Singletonパターンで作成しておいて
viewPage()でURLを設定する形を行いました。

コンストラクタの第四引数に「true」を設定してあげたら
デバッグモードで動作して、アクセスを確認できるみたいです。

2010年2月21日日曜日

Slim3でクラウド開発を始める


Slim3の開発はこんなブログより正式ドキュメントが有効です。
Slim3サイト
非公式と言われていますが充実した日本語サイトもあります。
Slim3日本語サイト



Slim3に飛びついたのは、GoogleAppEngineで
ファイルのアップロードができるし、Strutsだったから
飛びついたのを覚えています。
現在はStrutsには対応していませんが
MVCが整った軽量なフレームワークです。

使用するにはeclipseとGoogle Plugin for eclipseが必要です。

「http://slim3.googlecode.com/svn」
[slim3-blank]をチェックアウトします。
※Downloadからblankをダウンロードしてインポートしても
  開発は開始できます。
現在SDKは1.3.1のようですね。

作成されたプロジェクトで右クリックを押して、
コンパイラのアノテーションのファクトリーパスを選択します。
右の画像の画面を出してjarを選択します。


まだアーリーアクセス(EA)なんですね(>_<)
完成度結構高いんですけどね。


んでプロジェクトで「war/WEB-INF/web.xml」を
開きます。
右の部分が自分の名前空間になりますから
何か適当な名前をつけましょう!

その後はプロジェクト直下にある
build.xmlを選択して右クリックで
実行を押して、下のantを選択します。





すると右の画面が出ます。

gen-controllerはコントロールを自動作成
gen-modelはモデルを自動作成してくれます。

gen-controllerで出てくるボックスに
URLを指定してあげるとその指定したURLを
操作するControllerとJSP、プラステストコードを出力してくれます。
modelはそのモデルクラスとテストコードを出力してくれます。


2010年2月13日土曜日

mixiにOAuthなRESTfulアクセス(Java)

最近サービスづいてきています。OAuthばっかりです。
今度はJavaでmixiにアクセスです!

まず、、、RESTful APIでアクセスするには
まずmixiアプリの登録が必要です。

http://developer.mixi.co.jp/appli/pc/pc_prepare/add_app_flow

アプリケーションを登録できたら、
アプリケーションに対してConsumer KeyとConsumer Secretが発行されます。

これを元にOAuthアクセスを行います。

まずHMAC-SHA1と呼ばれる暗号化を行う為のインスタンスを生成します。


byte[] secretyKeyBytes = secret.getBytes("UTF-8");
secretKeySpec = new SecretKeySpec(secretyKeyBytes,hmac);
mac = Mac.getInstance(hmac);
mac.init(secretKeySpec);


ここでのsecretはConsumer Secretに「&」を付与したものになります。
secretKeySpecはjavax.crypto.spec.SecretKeySpecになります。
macはjavax.crypto.Macですね。

URLに設定する引数を作成します。


params.put("oauth_consumer_key", KEY);
params.put("oauth_signature_method", "HMAC-SHA1");
params.put("oauth_timestamp", String.valueOf(cal.getTimeInMillis()).substring(0,10));
params.put("oauth_version", "1.0");
params.put("oauth_nonce", nonce);
params.put("xoauth_requestor_id", viewerId);
params.put("format", "atom");



このparamsはURLを作成する為のオリジナルクラスのマップです。

oauth_consumer_keyに設定しているKEYはConsumer Keyですね。
oauth_signature_methodは暗号化を示す"HMAC-SHA1"を固定で指定します。
oauth_timestampは時刻を設定します。
oauth_versionはmixiに指定された文字列
oauth_nonceはリクエスト毎に違うランダムな文字列です。UUID.randomUUID().toString()などで生成します。
xoauth_requestor_idは対象のユーザIDです。
formatはatomを設定していますが、最終的に取得するデータの形式でjsonでも取得できます。


次にシグネチャを取得する為の文字列を取得します。


String method = "GET";
String http ="http://" + endpoint + requestURI ;
String args = canonicalQS;
String toSign;
try {
toSign = URLEncoder.encode(method,"UTF-8")+"&"+
URLEncoder.encode(http,"UTF-8")+"&"+
URLEncoder.encode(args,"UTF-8");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}


endpointはmixiのエンドポイント、、、って公開して良いのかな?
requestURIは/people/{id}/@allです。{id}は取得したい人のIDを設定します。
canonicalQSは前に設定したマップから取得したURLの引数です。

これらの文字列をそれぞれURLエンコードして”&”で連結します。

その文字列を最初に作ったmacを元にしてシグネチャを作成します。


String signature = null;
byte[] data;
byte[] rawHmac;
try {
data = stringToSign.getBytes(CHARSET);
rawHmac = mac.doFinal(data);
Base64 encoder = new Base64();
signature = new String(encoder.encode(rawHmac));
signature = signature.substring(0, hmac.length()-2);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(CHARSET + " is unsupported!", e);
}

try {
signature = URLEncoder.encode(signature, CHARSET).replace("+", "%20")
.replace("*", "%2A").replace("%7E", "~");
} catch (UnsupportedEncodingException e) {
}


この文字列には改行が入ってますので2文字削除しています。
そのあと、文字列の変換をかけてます。

で、、、作成されたシグネチャを元に、アクセスするURLを作成します。


String url = "http://" + endpoint + requestURI + "?" + canonicalQS + "&oauth_signature=" + sig;


でここにアクセスするとそのユーザの情報がXML化されて取得できるってわけです。
但し、ここで問題が発生します。
アクセスすると401エラーが返ってきます。

だので、、、mixiアプリの事を調べていくと。。。


対象ユーザが対象のmixiアプリを起動していない、もしくは起動から一定時間が経過した後にRESTful APIにアクセスを行った際には、
HTTPレスポンスコードとして「401 Unauthorized」が返却されます。


ををを、、、そういう事なのね。
ようはしばらくmixiアプリを起動した人の情報を
xoauth_requestor_idに設定してあげないと
アクセスできないってわけです。

APIを使うにはいろいろ画面遷移を考えないときついっぽいですね。
しかしこれでアクセスできました。
さぁ問題は何を作るかですね。。。。。



OAuthの仕様は
http://oauth.googlecode.com/svn/spec/ext/consumer_request/1.0/drafts/1/spec.html
にあります。

シグネチャがうまく取れるまで結構かかりました。
なのでここにある仕様(文字列)を元にテストを書きました。

実際のコードはその他サービスなどにアクセスしている為
複雑なクラスになっているのを平たく記述しているので
ペローンって感じのコードになっていますが、テストは通ります。



package jp.co.ziro.surpre.helper;

import static org.junit.Assert.*;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;

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

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


public class OAuthServiceHelperTest {

private static final String KEY = "dpf43f3p2l4k3l03";
private static final String SECRET = "kd94hf93k423kf44";
private static final String endpoint = "provider.example.net";
private static final String HMAC = "HmacSHA1";
private static final String REQUEST_URI = "/profile";
private static final String NONCE = "kllo9940pd9333jh";
private static final String REQUEST_METHOD = "GET";

@Test
public void testOAuthService() {

Map<String, String> paramMap = new TreeMap<String, String>();

//基本的な引数をすべて設定
paramMap.put("oauth_consumer_key", KEY);
paramMap.put("oauth_signature_method", "HMAC-SHA1");
paramMap.put("oauth_timestamp", "1191242096");
paramMap.put("oauth_version", "1.0");
paramMap.put("oauth_nonce", NONCE);

String canonicalQS = canonicalize(paramMap);

String method = REQUEST_METHOD;
String http ="http://" + endpoint + REQUEST_URI ;
String args = canonicalQS;

String toSign;
try {
toSign = URLEncoder.encode(method,"UTF-8")+"&"+
URLEncoder.encode(http,"UTF-8")+"&"+
URLEncoder.encode(args,"UTF-8");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}

String hmac = hmac(toSign);
hmac = hmac.substring(0, hmac.length()-2);

assertEquals(hmac,"SGtGiOrgTGF5Dd4RUMguopweOSU=");
}

private String hmac(String stringToSign) {
String signature = null;
byte[] data;
byte[] rawHmac;
try {
String secret = SECRET + "&";
byte[] secretyKeyBytes = secret.getBytes("UTF-8");
SecretKeySpec secretKeySpec = new SecretKeySpec(secretyKeyBytes,HMAC);
Mac mac = Mac.getInstance(HMAC);
mac.init(secretKeySpec);
data = stringToSign.getBytes("UTF-8");
rawHmac = mac.doFinal(data);
Base64 encoder = new Base64();
signature = new String(encoder.encode(rawHmac));
} catch (Exception e) {
throw new RuntimeException(e);
}
return signature;
}


private String percentEncodeRfc3986(String s) {
String out;
try {
out = URLEncoder.encode(s, "UTF-8").replace("+", "%20")
.replace("*", "%2A").replace("%7E", "~");
} catch (UnsupportedEncodingException e) {
out = s;
}
return out;
}

private String canonicalize(Map<String, String> sortedParamMap) {
if (sortedParamMap.isEmpty()) {
return "";
}
StringBuffer buffer = new StringBuffer();
Iterator<Map.Entry<String, String>> iter = sortedParamMap.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<String, String> kvpair = iter.next();
buffer.append(percentEncodeRfc3986(kvpair.getKey()));
buffer.append("=");
buffer.append(percentEncodeRfc3986(kvpair.getValue()));
if (iter.hasNext()) {
buffer.append("&");
}
}
String cannoical = buffer.toString();
return cannoical;
}


}


元のコードは
http://bit.ly/bVSEgt
なども参考にしています。

2010年2月10日水曜日

URL短縮サービスbit.ly

Twitterでつぶやけるようにしたものの、
URLを貼り付けたら文章が長くなってしまうので
URL短縮サービスとつなげることにしました。

まずbit.lyにユーザ登録します。
するとアカウント情報のところにAPIKeyが発行されます。



String endPoint = "http://api.bit.ly/shorten";
paramMap.put("apiKey", APIKEY);
paramMap.put("login", LOGIN);
paramMap.put("version", VARSION);
paramMap.put("format", FORMAT);
paramMap.put("longUrl", longUrl);

※paramMapはURLの引数です。
 ちょっとクラス化しててうまく説明できないです。

URLを指定して、その他引数を渡します。
LOGINにはログイン用に登録したID、
versionには現在"2.0.1"、formatには"xml"を指定します。
※jsonでの取得もあるようです。

longUrlには変換対象のURLを指定します。


それでURLアクセスして取得してきたRESTに
"sortUrl"タグがありますので
それを利用すればOKです。


いやぁしかし、URL短縮ツールってURL短くするサービスだと思ってましたが
そのURLをアクセス解析なんかに使えるんですね。

メーリングリストとかでリンク送るときに使えば
アクセス数が簡易的にわかってよいかもですね。

あっちをたてればこっちがたたず

新しいサービスをつくろうとして悩んでます。
OAuthでつなげようと思いながら、それはあれで、あれはそれで。

結論はY化しようと思います。

2010年2月9日火曜日

OAuthとTwitter4J(Slim3とFlexも)

先日Flex(AS3)でOAuth認証を行いました。
しかし、セキュリティサンドボックスの問題により、棚上げしました。
セキュリティサンドボックスも何かまとめないとですね。

って事でWeb側でTwitterにアクセスしてサービスを実現する事にしました。
結局、プロキシになっちゃいました。
まぁ挙動さえ分かってしまえばこっちのもんです。

ちなみにOAuth部分はTwitter4Jに任せてますので
その挙動を見たければsourceを添付してあげて調べてください。

※ソースは一部関数化してるものを平らにしているので
変数名とかはおかしいかも。。。


Twitter twitter = new TwitterFactory().getInstance();
twitter.setOAuthConsumer(KEY, KEY_SECRET);
try {
RequestToken requestToken = twitter.getOAuthRequestToken();
String url = requestToken.getAuthorizationURL();
requestScope("url",url);
sessionScope("requestToken",requestToken);
} catch (TwitterException e) {
new RuntimeException(e);
}


まず、Twitterオブジェクトを取得してアプリケーションのキー値を設定します。
そこからURLを取得してきます。それを返します。

その後、Pinを取得して処理する場合に新たにRequestTokenを作成すると
おかしくなるのでセッションに設定しました。
※requestScope(),sessionScope()はSlim3のメソッドです。
リクエストやセッションに溜め込んでください。

そのリクエストを元にURLにアクセスします。
私の場合はFlexなので


//リンクを飛ばす
navigateToURL(new URLRequest(oauthUrl));


って感じになりました。

ここにアクセスすると横の画像のWebが別ブラウザで表示されます。
Twitterアカウントを入力すると認証され、



Pinが表示されます。
。。。。画像で表示されます。選択してコピーして
そのPinを自分のWebページなどで入力します。

。。。ここでコールバックなどをうまく使えば
多分何手かUIを改善できそうな気がするんですけどね。



私の場合はそれを元にPinを入力して再度サーバサイドで処理を行いました。


Twitter twitter = new TwitterFactory().getInstance();
twitter.setOAuthConsumer(KEY, KEY_SECRET);
String pin = requestScope("pin");
RequestToken requestToken = sessionScope("requestToken");

AccessToken accessToken = null;
try {
accessToken = twitter.getOAuthAccessToken(requestToken, pin);
} catch (TwitterException e) {
throw new RuntimeException(e);
}
String token = accessToken.getToken();
String tokenSecret = accessToken.getTokenSecret();


先ほど保存しておいたRequestTokenと取得したPinをリクエストから受け取り、
AccessTokenを取得します。
それによりトークンを取得する事が必要です。

私はここでSlim3 Datastoreを使用して、トークンを永続化しています。
ちなみにですが、私が作ってるのは認証アプリなので
認証をしたUserオブジェクト(GAE)も保存しています。


その後はログインしたユーザを元に
このトークンを取得してきて処理をします。


Twitter twitter = new TwitterFactory().getInstance();
twitter.setOAuthConsumer(KEY, KEY_SECRET);

AccessToken accessToken = new AccessToken(data.getToken(),data.getTokenSecret());
twitter.setOAuthAccessToken(accessToken);
twitter.updateStatus(tweet);


dataは永続化したオブジェクトです。
updateStatus()はtwitter4jのつぶやく機能です。

こんな感じでサービスを作成しました。
ユーザ情報を保存したくなくて、OAuthに対応しましたが、
色々とツールがあって簡単にできるもんですね。

2010年2月7日日曜日

TwitterアプリでOAuth認証

とあるサービスを作る過程でTwitterクライアントを
作ろうと思い、作り始めました。
始めはユーザIDとパスワードを入力してもらおうと考えていましたが
やはり自分のアプリにそれを保存しておくの億劫になり、
OAuth認証で作る事にしました。

まずここに行って
アプリケーションを登録する必要があります。

登録を終えるとアプリケーションのページができて、
キーが2つ発行されます。
「https://twitter.com/apps」で確認ができます。
ちなみに認証して使用しているアプリケーションを確認するには
「https://twitter.com/account/connections」
に行くと確認できます。

さてここで開発開始ですが。。。。
Javaの部分でサービスのアクセスを作成していたのですが、
認証部分はFlexで行った方が画面遷移とか簡単そうだったので
Flex側で書くことにしました。

Twitterさんのサイトに各言語におけるサンプルサイトを紹介しています。
https://twitterapi.pbworks.com/OAuth-Examples

そこでFlexのサイトがあったので
http://www.coderanger.com/blog/?p=59
これを元に作成しました。

なのでここのソースを元に説明していきます。

private var _twitauth:OAuthManager = new OAuthManager();
_twitauth.addEventListener( OAuthEvent.ON_REQUEST_TOKEN_RECEIVED, onRequestTokenReceived );
_twitauth.addEventListener( OAuthEvent.ON_REQUEST_TOKEN_FAILED, onRequestTokenFailed );
_twitauth.addEventListener( OAuthEvent.ON_ACCESS_TOKEN_RECEIVED, onAccessTokenReceived );
_twitauth.addEventListener( OAuthEvent.ON_ACCESS_TOKEN_FAILED, onAccessTokenFailed );
_twitauth.usePinWorkflow = true;
_twitauth.consumerKey = key.text;
_twitauth.consumerSecret = secret.text;
_twitauth.oauthDomain = "twitter.com";
_twitauth.requestToken();


この箇所はアクセス後の呼び出される関数を設定して
アプリケーションで取得したキーの設定を行っています。
サンプルではコントロールから取得している感じになっていますが
ここに設定してあげます。

_twitauth.requestToken();

で最初のリクエストを行っています。
この関数と同時にアプリケーション認証のサイトを別のブラウザで表示してくれます。

ここでユーザが許可し認証を行うと
アプリケーションが認証され、PINコードが表示されます。

そのPINをユーザにテキストボックスなどに入力してもらって


private function onSendPin():void
{
_twitauth.requestAccessToken( Number( pin.text ) );
}


この関数で送ってあげます。
このリクエストで成功した場合「onAccessTokenReceived()」が発生するので
そこでユーザの認証情報を取得できます。

あとはアプリケーションでこの情報を永続化するなどをして
アクセスを行います。
下の方にあるコードはその情報を使ってつぶやいているコードです。


認証を行うとこのようにアプリケーションが追加されます。



おそらくアプリケーションの設定でCallbackURLなどを使って
一部自動化できるような気もしていますが、一応これでつぶやく事ができました。
ただし、このブログ公開時点ではサーバサイドにあげた場合に
アクセスできないという不具合があるので何か間違っているのかもしれません。


このコードを参考にしてオープンソース化されている方もいらっしゃるので
http://code.google.com/p/q-oauth/
ぜひお使いください。