2011/04/24
GAE/J の OAuth 認証を試してみた。その2
その1で一応 OAuth 認証は出来ているんだけど
公開鍵を使った認証ができるのでこれを試してみる。
最初、公開鍵を使うと consumer_key と consumer_secret で
2-legged で認証できると思っていたのだがどうやら出来ないならしい。
そもそも API がユーザを取得するようになっているので そういう物なんだろう。
しかも公開鍵登録しても HMAC-SHA1 でアクセス出来ちゃうし。
そもそも API がユーザを取得するようになっているので そういう物なんだろう。
鍵の準備
まず公開鍵を用意する。 現状、使えるのは RSA だけ。 openssl で鍵を作る。- http://code.google.com/intl/ja/apis/gdata/docs/auth/authsub.html#Registered
$ openssl req -x509 -nodes -days 365 -newkey rsa:1024 -sha1 \ -subj '/C=US/ST=CA/L=test/CN=wsjs-gae.appspot.com' \ -keyout wsjs-gae_pri.pem -out wsjs-gae_pub.pemsubject は適当に修正が必要。 java は pkcs8 形式しか受け付けないので秘密鍵を変換する。
$ openssl pkcs8 -in wsjs-gae_pri.pem -out wsjs-gae_pri.pk8 -topk8 -nocrypt
サーバに公開鍵を登録
管理画面から公開鍵と登録する。- https://www.google.com/accounts/UpdateDomain
クライアント側に秘密鍵を指定
クライアント側で秘密鍵を使うコードは既にその1のコードに入っている。protected OAuthAccessor getOAuthAccessor() throws Exception { OAuthServiceProvider provider = new OAuthServiceProvider(reqUrl, authUrl, accessUrl); OAuthConsumer consumer = new OAuthConsumer(null, domain, consumerSecret, provider); String key = props.getProperty(PRIVATE_KEY); if (key != null) { EncodedKeySpec privKeySpec = new PKCS8EncodedKeySpec( OAuthSignatureMethod.decodeBase64(key) ); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); PrivateKey privateKey = keyFactory.generatePrivate(privKeySpec); consumer.setProperty(RSA_SHA1.PRIVATE_KEY, privateKey); consumer.setProperty(OAuth.OAUTH_SIGNATURE_METHOD, OAuth.RSA_SHA1); } return new OAuthAccessor(consumer); }プロパティファイルに private_key を指定すると時動的に RSA_SHA1 モードに切り替わるようになっている。 鍵の準備で作成した pkcs8 の秘密鍵を設定する。
- oauth.props:
app_id=wsjs-gae consumer_secret=XXXXXXXXXXXXXXXXXXXXXXX private_key=\ MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALFqa/6eR/Eb8Jf8\ ftkuszfdjyybKeZXFBbnJejDjYc40wA9+kaEZbBwrFNCPFcRyF9+CCGbfkVKKvxH\ hpe+81xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxghxtTb\ VnKfhexxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxZfL1C4\ OKEMI/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxJ/Ngts\ EaP0YgxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxBi9jlS\ i6vZN0xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxQG7eqM\ UN4pjJxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxi8zjWZ\ 3vt8Epxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx0cEgB0\ 8iu3AExxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxR1OYlW\ SmWJbbxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxexkVe+\ GVe9XRTSzVU5zIk70oTocR9qFwUZhMk1jzcNuNvTVZbPr7mPXhNhAkEArFu3d3eK\ csyGYG5+4e6QkYWkjce/xTdmt0YBdOLzyxMPcVZpUPxN75TBBDiLmpNAr2kRkWw5\ 0VlVlA/KMbN+qQ==これで その1 と同じ用に request、承認、access、目的URL と進めば RSA_SHA1 で 3-legged oauth で認証を行う事ができる。 2-legged 出来ないんでセキュリティが強化されただけで あんまり面白く無い。
しかも公開鍵登録しても HMAC-SHA1 でアクセス出来ちゃうし。
2011/04/17
GAE/J の OAuth 認証を試してみた。その1
CUI のクライアントから GAE にアクセスするバッチ的な
プログラムの為に OAuth 認証を調べてみた。
GAE 側は極めて単純で
.appspot.com を指定して [add domain] をクリックする。
※普通は https にして置いた方が良いと思う。
するとサイトの所有権を確認される。
今回は meta ダグをトップページに張り付ける方法を選択した。
meta ダグを張り付けた後で [確認] をクリックすると ドメインが追加される。
ドメイン名をクリックするとドメインの管理画面に移動するので
URL を入力して [Save] する。
これて OAuth の Key と Secret が登録され OAuth が使えるようになる。
テスト用のサーバ側コードを用意する。
var OAuthServiceFactory = Packages.com.google.appengine.api.oauth.OAuthServiceFactory;
function doGet(req, res) {
var oauthService = OAuthServiceFactory.getOAuthService();
var text =
"user:"+oauthService.getCurrentUser()+"\n"
+"admin:"+oauthService.isUserAdmin()+"\n"
;
res.writer.write(text);
}
これてサーバ側の準備完了。
package org.kotemaru.test;
import java.io.*;
import java.net.*;
import java.util.*;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.EncodedKeySpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import net.oauth.client.OAuthClient;
import net.oauth.OAuthServiceProvider;
import net.oauth.OAuthConsumer;
import net.oauth.OAuthAccessor;
import net.oauth.OAuth;
import net.oauth.ParameterStyle;
import net.oauth.OAuthMessage;
import net.oauth.OAuthException;
import net.oauth.http.HttpClient;
import net.oauth.signature.*;
import net.oauth.client.httpclient3.HttpClient3;
//import net.oauth.client.URLConnectionClient;
public class OAuthTestClient {
public static final String APP_ID = "app_id";
public static final String CONSUMER_SECRET = "consumer_secret";
public static final String OAUTH_TOKEN = "oauth_token";
public static final String OAUTH_TOKEN_SECRET = "oauth_token_secret";
public static final String PRIVATE_KEY = "private_key";
public static final String APP_NAME = "application_name";
public static final String GET_REQ_TOKEN = "/_ah/OAuthGetRequestToken";
public static final String GET_AUTH_TOKEN = "/_ah/OAuthAuthorizeToken";
public static final String GET_ACCESS_TOKEN = "/_ah/OAuthGetAccessToken";
public static final String HTTPS = "https://";
public static final String APPSPOT_COM = "appspot.com";
private Properties props;
private String appId;
private String consumerSecret;
private String domain;
private String reqUrl ;
private String authUrl ;
private String accessUrl ;
private OAuthAccessor accessor;
private OAuthClient client;
public OAuthTestClient(Properties p) throws Exception {
props = p;
appId = props.getProperty(APP_ID);
consumerSecret = props.getProperty(CONSUMER_SECRET);
domain = appId + "." + APPSPOT_COM;
reqUrl = HTTPS + domain + GET_REQ_TOKEN;
authUrl = HTTPS + domain + GET_AUTH_TOKEN;
accessUrl = HTTPS + domain + GET_ACCESS_TOKEN;
accessor = getOAuthAccessor();
client = getOAuthClient();
}
protected OAuthAccessor getOAuthAccessor() throws Exception {
OAuthServiceProvider provider =
new OAuthServiceProvider(reqUrl, authUrl, accessUrl);
OAuthConsumer consumer =
new OAuthConsumer(null, domain, consumerSecret, provider);
String key = props.getProperty(PRIVATE_KEY);
if (key != null) {
EncodedKeySpec privKeySpec = new PKCS8EncodedKeySpec(
OAuthSignatureMethod.decodeBase64(key)
);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey privateKey = keyFactory.generatePrivate(privKeySpec);
consumer.setProperty(RSA_SHA1.PRIVATE_KEY, privateKey);
consumer.setProperty(OAuth.OAUTH_SIGNATURE_METHOD, OAuth.RSA_SHA1);
}
return new OAuthAccessor(consumer);
}
protected OAuthClient getOAuthClient() {
OAuthClient client = new OAuthClient(new HttpClient3());
client.getHttpParameters().put(HttpClient.FOLLOW_REDIRECTS, Boolean.TRUE);
return client;
}
protected OAuthMessage send(String url, String token) throws Exception {
List params = new ArrayList();
params.add(new OAuth.Parameter(APP_NAME, appId));
params.add(new OAuth.Parameter(OAUTH_TOKEN, token));
OAuthMessage response = client.invoke(accessor, "GET", url, params);
return response;
}
public String getAuthUrl() throws Exception {
client.getRequestToken(accessor);
OAuthMessage response = send(authUrl, accessor.requestToken);
props.setProperty(OAUTH_TOKEN, accessor.requestToken);
props.setProperty(OAUTH_TOKEN_SECRET, accessor.tokenSecret);
return response.URL;
}
public void access() throws Exception {
accessor.tokenSecret = props.getProperty(OAUTH_TOKEN_SECRET);
OAuthMessage response = send(accessUrl, props.getProperty(OAUTH_TOKEN));
props.setProperty(OAUTH_TOKEN, response.getParameter(OAUTH_TOKEN));
props.setProperty(OAUTH_TOKEN_SECRET,
response.getParameter(OAUTH_TOKEN_SECRET));
}
public OAuthMessage request(String url) throws Exception {
accessor.consumer.setProperty(OAuthClient.PARAMETER_STYLE,
ParameterStyle.AUTHORIZATION_HEADER);
accessor.tokenSecret = props.getProperty(OAUTH_TOKEN_SECRET);
List params = new ArrayList();
params.add(new OAuth.Parameter(APP_NAME, appId));
params.add(new OAuth.Parameter(OAUTH_TOKEN, props.getProperty(OAUTH_TOKEN)));
OAuthMessage response = client.invoke(accessor, "GET", url, params);
return response;
}
//------------------------------------------------------------------
public static Properties loadProps(String name) throws IOException {
Properties props = new Properties();
InputStream in = new FileInputStream(name);
try {
props.load(in);
} finally {
in.close();
}
return props;
}
public static void saveProps(String name, Properties props) throws IOException {
OutputStream out = new FileOutputStream(name);
try {
props.store(out, "");
} finally {
out.close();
}
}
public static void main(String[] args) throws Exception {
Properties props = loadProps(args[0]);
System.out.println(props);
OAuthTestClient test = new OAuthTestClient(props);
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
System.out.print("\n> ");
String line = reader.readLine();
while (line != null) {
line = line.trim();
if ("request".equals(line)) {
String url = test.getAuthUrl();
System.out.println("Auth URL:\n"+url);
saveProps(args[0], props);
} else if ("access".equals(line)) {
test.access();
saveProps(args[0], props);
System.out.println("Get access token.");
} else {
OAuthMessage response = test.request(line);
System.out.println(response.readBodyAsString());
}
System.out.print("\n> ");
line = reader.readLine();
}
}
}
通常の OAuth クライアントなので GAE としての特別な部分は以下の3行だけ。
public static final String GET_REQ_TOKEN = "/_ah/OAuthGetRequestToken";
public static final String GET_AUTH_TOKEN = "/_ah/OAuthAuthorizeToken";
public static final String GET_ACCESS_TOKEN = "/_ah/OAuthGetAccessToken";
使い方は最初に目的サーバの app-id と OAuth secret を持つ
プロパティファイルを作成する。
app_id=
consumer_secret=XXXXXXXXXXXXXXXXXXXX
oauth.props を引数に渡して org.kotemaru.test.OAuthTestClient
を起動するとプロンプトが出るのでコマンドを入力。
$ java -classpath $CP org.kotemaru.test.OAuthTestClient oauth.props
> access
Get access token.
アクセストークンは oauth.props に書き戻される。
oauth-client.zip 参考にしたページ:
http://blog.smartnetwork.co.jp/staff/node/53
OAuthService oauthService = OAuthServiceFactory.getOAuthService(); User user = oauthService.getCurrentUser();これだけで OAuth 認証されたユーザが取得できる。 問題はクライアント側で OAuth の 3-legged プロトコルを ちゃんと実装しなければいけない。 とっかかりはこの辺りから...
- http://code.google.com/intl/ja/appengine/docs/java/oauth/
- http://code.google.com/intl/ja/apis/accounts/docs/OAuth_ref.html
- http://code.google.com/p/oauth/
サーバ側の準備
まず最初に必要なのが GAE サーバの登録。- https://www.google.com/accounts/ManageDomains
- https://wsjs-gae.appspot.com/test/oauth.ssjs:
クライアントの実装
先に見付けたサイトからライブラリの jar を落す。- http://code.google.com/p/oauth/downloads/list
svn co http://oauth.googlecode.com/svn/code/java/サンプルコード jmeter/example/command-line を参考にクライアント側 のコードを書いてみる。
- OAuthTestClient.java:
- oauth.props:
- トークン取得:
> request Auth URL: https://www.google.com/accounts/OAuthAuthorizeToken?application_name=wsjs-gae&oauth_token=4%2FtdHeN0V_wnRNUBbLJYpgiU0GRUUJ&oauth_consumer_key=wsjs-gae.appspot.com&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1302937883&oauth_nonce=1302937883290438000&oauth_version=1.0&oauth_signature=XXXXXXXXXXXXXXXXXXX%3D&scope=appengine&hd=defaultこのURLをブラウザで開いて登録ユーザでログインしアクセス許可を行う。
- アクセストークンの取得:
- 目的URLにアクセス:
> https://wsjs-gae.appspot.com/test/oauth.ssjs user:xxxxx@gmail.com admin:trueちゃんと認証できました。 なんだかえらい大変でした。 趣味で使う分には固定パスワードで十分だと思います。 業務用だと管理ツールで必須とか言われるかもねー。 コード一式(Eclipseプロジェクト):
oauth-client.zip 参考にしたページ:
http://blog.smartnetwork.co.jp/staff/node/53
2011/03/06
GAE/Jのspinup
spin-up時間に Servlet と Filter でどれくらい違いがでるのか
ちょっと気になったので調べてみた。
いずれも中身は空っぽの NullServlet と NullFilter を用意して
実機で動かした。
結果:
NullServlet
NullFilter
100~200ms くらいだろうか。
通常のアプリの spin-up 時間は 3000~4000ms くらいかかると思われるので 誤差の範囲と言えなくも無い。 まぁキチキチを目指す場合は最初から Filter にしとけって事くらいかな。
後から変えようとすると結構面倒だし。 以下、ソース。 NullServlet.java:
package org.kotemaru.test;
import java.io.*;
import java.net.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class NullServlet extends HttpServlet {
public void init(ServletConfig config) throws ServletException {
System.out.println("Servlet init");
}
public void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
// nop.
}
}
NullFilter.java:
package org.kotemaru.test;
import java.io.*;
import java.net.*;
import javax.servlet.*;
public class NullFilter implements Filter {
public void init(FilterConfig config) throws ServletException {
System.out.println("Filter init");
}
public void destroy() {
// nop.
}
public void doFilter(ServletRequest _req, ServletResponse _res,
FilterChain chain)
throws java.io.IOException, ServletException
{
// nop.
}
}
関連記事:
1241ms 1260cpu_ms 1035ms 1306cpu_ms 1035ms 1306cpu_ms 1032ms 1516cpu_ms 1253ms 1400cpu_ms 1200ms 1540cpu_ms
1175ms 1306cpu_ms 900ms 1423cpu_ms 910ms 1190cpu_ms 1442ms 1353cpu_ms 1152ms 1610cpu_ms 982ms 1190cpu_msうーん、気持早くなってる。
100~200ms くらいだろうか。
通常のアプリの spin-up 時間は 3000~4000ms くらいかかると思われるので 誤差の範囲と言えなくも無い。 まぁキチキチを目指す場合は最初から Filter にしとけって事くらいかな。
後から変えようとすると結構面倒だし。 以下、ソース。
2011/02/27
GAE/Jのspinupとmemcache:クラスのキャッシュ化
リソースのmemcache化で効果が有ったのでクラスのロードでも
同じ効果が無いか試して見ることにした。
やり方としては自前のクラスローダからServletを生成して
以下は自前のクラスローダからクラスを読み込ませる。
memcacheにはクラスを byte[] で保存して置く。
但し、java.* や com.google.* のパッケージは自前のクラスローダ
からの読み込みを許可されないので避ける必要がある。
実験結果:
標準
memcache付き自前クラスローダ
まぁそうで有っても全然不思議じゃ無い。 効果無かったけど一応実験ソースは置いておく。 MemcacheClassLoader.java: memcacheを使うクラスローダ
package org.kotemaru.test;
import java.io.*;
import com.google.appengine.api.memcache.*;
public class MemcacheClassLoader extends ClassLoader {
private static final String NS_MEMCACHE = "MemcacheClassLoader";
private static MemcacheService memcache =
MemcacheServiceFactory.getMemcacheService(NS_MEMCACHE);
public MemcacheClassLoader(ClassLoader parent) {
super(parent);
}
protected synchronized Class> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
if (
name.startsWith("java")
|| name.startsWith("com.google.")
|| name.startsWith("org.xml.sax.")
|| name.startsWith("org.w3c.")
|| name.startsWith("sun.")
|| name.startsWith("org.mortbay.")
) {
try {
return getParent().loadClass(name);
} catch (ClassNotFoundException e) {
return loadClass0(name, resolve);
}
}
return loadClass0(name, resolve);
}
private Class loadClass0(String name, boolean resolve)
throws ClassNotFoundException
{
Class clazz = findLoadedClass(name);
if (clazz != null) return clazz;
clazz = findClass(name);
if (resolve) resolveClass(clazz);
return clazz;
}
protected Class findClass(String name) throws ClassNotFoundException {
try {
String path = name.replace('.','/')+".class";
//System.out.println("->"+path);
InputStream in = null;
try {
byte[] data = (byte[]) memcache.get(name);
if (data == null) {
in = getParent().getResourceAsStream(path);
data = readBytes(in);
memcache.put(name, data);
}
Class clazz = defineClass(name, data, 0, data.length);
return clazz;
} finally {
if (in != null) in.close();
}
} catch (Throwable t) {
throw new ClassNotFoundException(name, t);
}
}
private static byte[] readBytes(InputStream in) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte[] buff = new byte[4096];
int n;
while ((n=in.read(buff)) > 0) {
out.write(buff, 0, n);
}
return out.toByteArray();
}
}
MemcacheCLServlet.java: MemcacheClassLoaderを使わせる為のラッパーServlet
package org.kotemaru.test;
import java.io.*;
import java.net.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class MemcacheCLServlet extends HttpServlet {
private static final String ORIGIN = "org.kotemaru.test.Test2Servlet";
private HttpServlet origin;
public void init(ServletConfig config) throws ServletException {
try {
System.out.println("Initialize MemcacheCLServlet.");
MemcacheClassLoader ccl = new MemcacheClassLoader(this.getClass().getClassLoader());
origin = (HttpServlet) Class.forName(ORIGIN, true, ccl).newInstance();
origin.init(config);
} catch (Exception e) {
throw new ServletException(e);
}
}
public void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
origin.service(request, response);
}
}
Test2Servlet.java: ターゲットのServlet
package org.kotemaru.test;
import java.io.*;
import java.net.*;
import javax.servlet.*;
import javax.servlet.http.*;
import org.mozilla.javascript.*;
public class Test2Servlet extends HttpServlet {
public void init(ServletConfig config) throws ServletException {
try {
System.out.println("start init.");
Context cx = Context.enter();
try {
Scriptable scope = cx.initStandardObjects();
String src = "var x=1;";
Script script = cx.compileString(src, "test.js", 1, null);
script.exec(cx, scope);
} catch (RuntimeException e) {
e.printStackTrace();
throw e;
} finally {
Context.exit();
}
} catch (Exception e) {
e.printStackTrace();
throw new ServletException(e);
}
}
public void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
response.getWriter().write("test");
return;
}
}
関連記事:
3602ms 2290cpu_ms 38834ms 2290cpu_ms 1442ms 1586cpu_ms 7532ms 2163cpu_ms 17550ms 2364cpu_ms 13556ms 2273cpu_ms 3111ms 2200cpu_ms 1224ms 1633cpu_ms 5620ms 2346cpu_ms 6169ms 2530cpu_ms
8000ms 4490cpu_ms 3946ms 4435cpu_ms 21187ms 4362cpu_ms 7975ms 4215cpu_ms 6042ms 3978cpu_ms 3722ms 3996cpu_ms 5941ms 4656cpu_ms 7032ms 4362cpu_ms 13031ms 4106cpu_ms 14204ms 4070cpu_ms 15714ms 4307cpu_msう、変わって無いと言うか逆に遅くなってる。 コード自体かなり余分な処理が処理が入るのでCPU時間が 増えるのは仕方無いが I/O 待ちを減らすメリットは 出ていないみたいだ。 さすがにクラスロードに関しては GAE 側に何らかのチューニング が入っていると言うことだろうか。
まぁそうで有っても全然不思議じゃ無い。 効果無かったけど一応実験ソースは置いておく。
2011/02/20
GAE/Jのspinupとmemcache その3
感覚的な物だけでは何なので I/O -> memcacheの効果を検証してみる為にした。
init()の中で 85bytes のリソースを5つ読み込むだけのServletで試した。
テスト用に用意した環境が撃重で凄い結果になった。
- memcache無し
たまたま凄く重い環境に当たったのだと思うが実機での検証結果なので 何もしないServletが24秒とか実際に起こり得るらしい。
むしろ貴重な情報が取れてラッキーだったかもしれない。
spinup実時間のバラツキに対しては かなり効果が見込めそうなので試してみて欲しい。
できれば結果をコメントしてもらえると情報が蓄積できるのでお願いしまふ。
テストに使ったServlet:
package org.kotemaru.test;
import java.io.*;
import java.net.*;
import javax.servlet.*;
import javax.servlet.http.*;
import com.google.appengine.api.memcache.*;
public class TestServlet extends HttpServlet {
private static final String NS_MEMCACHE = "Test";
private static MemcacheService memcache =
MemcacheServiceFactory.getMemcacheService(NS_MEMCACHE);
public void init(ServletConfig config) throws ServletException {
try {
System.out.println("start init.");
/*
getResource(TestServlet.class, "test1.properties");
getResource(TestServlet.class, "test2.properties");
getResource(TestServlet.class, "test3.properties");
getResource(TestServlet.class, "test4.properties");
getResource(TestServlet.class, "test5.properties");
*/
getResourceWithCache(TestServlet.class, "test1.properties");
getResourceWithCache(TestServlet.class, "test2.properties");
getResourceWithCache(TestServlet.class, "test3.properties");
getResourceWithCache(TestServlet.class, "test4.properties");
getResourceWithCache(TestServlet.class, "test5.properties");
} catch (Exception e) {
e.printStackTrace();
throw new ServletException(e);
}
}
public void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
response.getWriter().write("test");
return;
}
public static String getResourceWithCache(Class clazz, String name) throws IOException {
String val = (String) memcache.get(name);
if (val != null) {
val = getResource(clazz, name);
memcache.put(name, val);
}
return val;
}
public static String getResource(Class clazz, String name) throws IOException {
InputStream in = clazz.getResourceAsStream(name);
try {
return readerToString(new InputStreamReader(in, "UTF-8"));
} finally {
in.close();
}
}
public static String readerToString(Reader reader) throws IOException {
StringBuffer sbuf = new StringBuffer();
int n = 0;
char[] buff = new char[4096];
while ((n=reader.read(buff)) > 0) {
sbuf.append(buff, 0, n);
}
return sbuf.toString();
}
}
関連記事:
init()の中で 85bytes のリソースを5つ読み込むだけのServletで試した。
テスト用に用意した環境が撃重で凄い結果になった。
- memcache無し
実行時間 CPU時間 17238ms 1778cpu_ms 24499ms 1832cpu_ms 17569ms 1870cpu_ms 11694ms 1777cpu_ms- memcache有り
6167ms 2015cpu_ms 9290ms 1905cpu_ms 6573ms 1833cpu_ms 1918ms 1376cpu_msCPU時間は変わらないのに実行時間は memcache有りの場合、半分以下になっている。
たまたま凄く重い環境に当たったのだと思うが実機での検証結果なので 何もしないServletが24秒とか実際に起こり得るらしい。
むしろ貴重な情報が取れてラッキーだったかもしれない。
spinup実時間のバラツキに対しては かなり効果が見込めそうなので試してみて欲しい。
できれば結果をコメントしてもらえると情報が蓄積できるのでお願いしまふ。
テストに使ったServlet:
2011/02/13
GAE/Jのspinupとmemcache その2
Google の中の人と話す機会があったのsinup/downについて聞いてきた。
でいくつか思い違いが有ったのでメモっとく。
それが無ければずっとインスタンスは生きているとの事。
実際試しているとアクセスが無いのに30分以上 spindown が起こらない事がある。
したがってCPUタイムと無関係にバラツキの有る処理時間になるとの事。
この話からするととにかく I/O は memcache にキャッシュすれば バラツキを押えて CPU 時間前後で安定する事になる。 それを前提に現在実験中なのだがログからはコード改修の結果なのか 単に環境の負荷が下がっているだけなのかの区別が付かないので結論が出ない。 少し、長い目で見るしか無いねこの件は。 ちなみに今のところはっきり効果を感じたのはクラスパス内の リソースファイルの読み込みを memcache 化した場合。 複数有ったため 1000ms くらいがっつり減った。
GAEのspinupとmemcache の記事で変化が無かったのはデシリアライズで失敗してた為、 memcache が効いていなかった。
これはクラスローダの問題が絡んでいたのだがこっちはこっちで根が深いのでまた別の機会に。 関連記事:
でいくつか思い違いが有ったのでメモっとく。
- spindownは時間じゃなくてリソース競合で起こる
それが無ければずっとインスタンスは生きているとの事。
実際試しているとアクセスが無いのに30分以上 spindown が起こらない事がある。
- sipnupで遅い時が有るのは主にI/O待ちが発生している。
したがってCPUタイムと無関係にバラツキの有る処理時間になるとの事。
この話からするととにかく I/O は memcache にキャッシュすれば バラツキを押えて CPU 時間前後で安定する事になる。 それを前提に現在実験中なのだがログからはコード改修の結果なのか 単に環境の負荷が下がっているだけなのかの区別が付かないので結論が出ない。 少し、長い目で見るしか無いねこの件は。 ちなみに今のところはっきり効果を感じたのはクラスパス内の リソースファイルの読み込みを memcache 化した場合。 複数有ったため 1000ms くらいがっつり減った。
GAEのspinupとmemcache の記事で変化が無かったのはデシリアライズで失敗してた為、 memcache が効いていなかった。
これはクラスローダの問題が絡んでいたのだがこっちはこっちで根が深いのでまた別の機会に。 関連記事:
2011/01/23
GAE から Apps のスプレッドシートにアクセスしてみた。
gdata API を使って GAE から Apps のスプレッドシートを参照してみた。
Apps のスプレッドシートがそもそも何かって言うとエクセルの
Webアプリ版だと思えば良い。
もちろん機能的にはエクセルに及ばないが複数の人が同時に編集出来るなど
WEbアプリならではの機能も有る。
エクセルのテンプレートで社内文書を提出させる会社は多いと思うが
Appsのスプレッドシート -> GAE の流れとすればクラウド上で完結することができる。
FORMと違って提出文書はそのまま残るのでサーバ側は処理結果だけ持っていれば
良く実装量も減るはず。
と言うわけでいつもの用に WSJS でお試し。
まずは gdata API のライブラリを落してくる。
http://code.google.com/intl/ja/apis/apps/libraries_and_samples.html
で、jar を自前クラスローダから読み込まそうとしただがエラーになった。
/* 自前クラスローダはダメっぽいのでコメントアウト
__ENV__.addClassPath("gdata-core-1.0.jar");
__ENV__.addClassPath("gdata-client-meta-1.0.jar");
__ENV__.addClassPath("gdata-client-1.0.jar");
__ENV__.addClassPath("gdata-spreadsheet-meta-3.0.jar");
__ENV__.addClassPath("gdata-spreadsheet-3.0.jar");
__ENV__.addClassPath("google-collect-1.0-rc1.jar ");
__ENV__.addClassPath("jsr305.jar");
*/
// imports.
var FeedURLFactory = Packages.com.google.gdata.client.spreadsheet.FeedURLFactory;
var ListQuery = Packages.com.google.gdata.client.spreadsheet.ListQuery;
var SpreadsheetQuery = Packages.com.google.gdata.client.spreadsheet.SpreadsheetQuery;
var SpreadsheetService = Packages.com.google.gdata.client.spreadsheet.SpreadsheetService;
var CustomElementCollection = Packages.com.google.gdata.data.spreadsheet.CustomElementCollection;
var ListEntry = Packages.com.google.gdata.data.spreadsheet.ListEntry;
var ListFeed = Packages.com.google.gdata.data.spreadsheet.ListFeed;
var SpreadsheetEntry = Packages.com.google.gdata.data.spreadsheet.SpreadsheetEntry;
var SpreadsheetFeed = Packages.com.google.gdata.data.spreadsheet.SpreadsheetFeed;
var WorksheetEntry = Packages.com.google.gdata.data.spreadsheet.WorksheetEntry;
function doGet(req, res) {
var html =
;
res.setContentType("text/html; charset=utf-8");
res.writer.write(html);
}
function doPost(req, res) {
var spreadsheet = req.getParameter("spreadsheet");
//var worksheet = req.getParameter("worksheet");
var username = req.getParameter("username");
var password = req.getParameter("password");
// サービス開始
var service = new SpreadsheetService("Sample");
service.setUserCredentials(username, password);
// spread-sheet 取得
var urlFactory = FeedURLFactory.getDefault();
var spreadsheetQuery = new SpreadsheetQuery(urlFactory.getSpreadsheetsFeedUrl());
spreadsheetQuery.setTitleQuery(spreadsheet); // 検索である事に注意。複数取れちゃうよ。
var spreadsheetFeed = service.query(spreadsheetQuery, SpreadsheetFeed);
var spreadsheetEntry = spreadsheetFeed.getEntries().get(0);
// work-sheet 取得。全部取るなら getWorksheets()。
worksheetEntry = spreadsheetEntry.getDefaultWorksheet();
// work-sheetの中身取得。
var listQuery = new ListQuery(worksheetEntry.getListFeedUrl());
var listFeed = service.query(listQuery, ListFeed);
var list = listFeed.getEntries();
// 項目名一覧取得。
var tags = listFeed.getEntries().get(0).getCustomElements().getTags();
// データ出力。
res.setContentType("text/plain; charset=utf-8");
var writer = res.writer;
var ite = tags.iterator();
while(ite.hasNext()){
var name = ite.next();
writer.write(name+",");
}
writer.write("\n");
for (var i=0; i
まず取得対象のスプレッドシートを "test" と言う名前で作成。
サンプルページにアクセスして スプレッドシート名/ユーザ/パスワード を入力。
スプレッドシートの内容が CSV で取れている。
スプレッドシートへの出力も可能なようなので結果ページをメールで送信して
ワークフローとかも作れちゃいそうだね。
参考ページ:
http://www.atmarkit.co.jp/fwcr/rensai2/spreadsheetsapi01/02.html
java.lang.NullPointerException com.google.appengine.runtime.Request.process-8c1503d338b4a612(Request.java) java.io.ByteArrayInputStream.com.google.** のパッケージはシステムのクラスローダしか受け付け無いらしい。 あきらめて WEB-INF/lib の下に以下の jar をコピーして Upload しなおし。(ByteArrayInputStream.java:106) org.kotemaru.wsjs.RepositoryClassLoader.getJarStream(RepositoryClassLoader.java:90) org.kotemaru.wsjs.RepositoryClassLoader.findClassForJarFile(RepositoryClassLoader.java:62) org.kotemaru.wsjs.RepositoryClassLoader.findClass(RepositoryClassLoader.java:33) java.lang.ClassLoader.loadClass(ClassLoader.java:114) :
gdata-core-1.0.jar gdata-client-meta-1.0.jar gdata-client-1.0.jar gdata-spreadsheet-meta-3.0.jar gdata-spreadsheet-3.0.jar google-collect-1.0-rc1.jar jsr305.jarスプレッドシートにアクセスする為には最低限、以下の情報が必要。
- スプレッドシートの名前
- アクセス権の有るユーザの名前
- アクセス権の有るユーザのパスワード
2011/01/08
GAE/J の OpenID を使ってみた。
参考にしたページはこの辺り
http://code.google.com/p/appengine-openid-test/
http://d.hatena.ne.jp/knj77/20100612/1276328107
http://d.hatena.ne.jp/hidemon/20100521/1274472966
http://d.hatena.ne.jp/int128/20100525/1274809086
openid4java で OpenID は実装した事があるが GAE/J の API
では試した事が無かったのでいつもの様に WSJS でお手軽に
試してみた。
まずは認証の必要なページの作成。
未ログインの場合は OpenID を入力する自前のページに飛ばす。
var UserServiceFactory = Packages.com.google.appengine.api.users.UserServiceFactory;
function doGet(req, res) {
var userService = UserServiceFactory.getUserService();
var user = userService.getCurrentUser();
if (user == null) {
res.sendRedirect("/openid/openid.ssjs?continue="+req.getRequestURI());
}
res.writer.write("This id target page. UserName="+user);
}
web.xml に <security-constraint> を設定しておけば自動で
/_ah/login_required に飛ばしてくれるみたいです。
/_ah/login_required には自前の Servlet が必要。
OpenID の入力画面とプロバイダのログイン画面への転送処理。
画面は流用なので無意味に凝っている。
continue パラメータをセッション変数で引き継いでいる事に注意。
var UserServiceFactory = Packages.com.google.appengine.api.users.UserServiceFactory;
// OpenIDの入力画面
function doGet(req, res) {
req.session.setAttribute("returnUrl", req.getParameter("continue"));
var styleUrl = ""
+"border:1px solid #A6B9CA;"
+"color: black;"
+"background-color: #f0f8ff;"
+"font-family:Arial,MS PGothic;"
+"font-size:12px;"
+"padding:3px;"
+"padding-bottom:3px;"
;
var html =
;
res.setContentType("text/html; charset=utf-8");
res.writer.write('\n');
res.writer.write(html);
}
// OpenID受け取り
function doPost(req, res) {
var returnTo = req.session.getAttribute("returnUrl");
if (returnTo.startsWith("/")) {
returnTo = "http://"+req.getServerName()+returnTo;
}
var domain = req.getServerName();
var openid_url = req.getParameter("openid_url");
var attributes = new java.util.HashSet();
var userService = UserServiceFactory.getUserService();
var loginUrl = userService.createLoginURL(
returnTo,
domain,
openid_url,
attributes
);
// プロバイダのログイン画面へ
res.sendRedirect(loginUrl);
}
動かしてみる。
まず、
http://wsjs-gae.appspot.com/openid/target-page.ssjs
にアクセスすると
http://wsjs-gae.appspot.com/openid/openid.ssjs?continue=/openid/target-page.ssjs
に転送され OpenID の入力を行う。
あれ?何かエラーが...
java.lang.IllegalArgumentException: The requested URL was not allowed: http://wsjs-gae.appspot.com/openid/target-page.ssjs com.google.appengine.api.users.UserServiceImpl.makeSyncCall(UserServiceImpl.java:131) com.google.appengine.api.users.UserServiceImpl.createLoginURL(UserServiceImpl.java:62)ググったらすぐ分かった。 GAEに認証で gmail.com 以外を許可してなかった。 管理画面から設定。 再度挑戦すると無事 プロバイダのログイン画面が現れた。 ログインするとターゲットのページに戻って OpenID の登録メアドが 表示されてOK。 思ったより簡単。 面倒なのは最初のURLを引き回すとこだけだね。