ラベル Google app engine の投稿を表示しています。 すべての投稿を表示
ラベル Google app engine の投稿を表示しています。 すべての投稿を表示

2009/12/21

Clojure で Google App Engine するためのメモページ

Clojure で GAE(Google App Engine)するためのメモ。

逐次追記する予定。

SLIME

git clone git://github.com/technomancy/swank-clojure.git 現在かなり不安定っぽい。 -> @hchbaw さんに教えてもらって幸せになりました。http://twitter.com/hchbaw/statuses/6895804697

hackers-with-attitude: Interactive Programming with Clojure, Compojure, Google App Engine and Emacs のとおりにすれば、SLIME 上からインタラクティブに開発できる。さいこー!! Lisp たる必須条件だよね。

Compojure

weavejester's compojure at master - GitHub Compojure - Wikibooks, collection of open-content textbooks

メソッドマクロ

  • GET
  • POST
  • PUT
  • DELETE
  • HEADE
  • ANY どれでも ok

URL に対するバインドは : で、取得は :route-params で行う。複数のパラメータの場合は * を使う。ドメイン名でもルーティングできるらしい。

(GET "/posts/:id" ...)
(-> request :route-params :id)

(GET "/foo/*" ...)
(-> request :route-params :*)

静的ファイルをレスポンスするには serve-file を使う。ファイルは public に置く。 404 を返すには page-not-found を使い、public/404.html を用意しておく。

(defroutes main-routes
...
(GET "/*"
(or (serve-file (params :*)) :next))
(ANY "*"
(page-not-found)))

でも Google App Engine ではスタティックファイルが存在するとそちらが優先されること、サーブレットの呼び出しでは MIME タイプを web.xml で指定する必要があることから次のようにしておくのがよいと思う。

(defroutes main-routes
...
(GET "/*"
(or (serve-file "./" (params :*)) :next))
(ANY "*"
(page-not-found "404.html")))
war/
css/
style.css
img/
image.png
404.html

request が持っているもの。

  • params → (:params request)
  • cookies → (:cookies request)
  • session → (:session request)
  • flash → (:flash request)

HTML はベクタで書く。属性はマップ。 id と クラスはタグにくっつけられる。

[:h1#title "こんにちは"]
[:form#main.list
[:div.foo.bar "..."]
[:input {:type "text" :name "baz"}]]

appengine-clj

duelinmarkers's appengine-clj at master - GitHub

DataStore, UserService の簡単なライブラリ。これだけじゃ足りないので、自分で実装を追加するか、Java の API をコールすることになる。

Datastore 低レベル API

Key は他のエンティティの参照としてプロパティに保持可能。

KeyFactory.keyToString と KeyFactory.stringToKey によって Key と Web セーフな String を相互に変換できる。ポストパラメータ等には keyToString した Key を使う。

2009/10/31

クエリストリングの取得と cl-who(ABLC で Google app engine)

前回 ABCL で Google app engine が動いたので、今回はクエリスティングの取得と cl-who の使用をやってみた。

Sevlet クラスで currentThread.execute するときに req と resp を JavaObject でくるんでわたしてやる。

package abcl_ae;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.armedbear.lisp.ConditionThrowable;
import org.armedbear.lisp.JavaObject;
import org.armedbear.lisp.Lisp;
import org.armedbear.lisp.LispThread;
import org.armedbear.lisp.SpecialBinding;
import org.armedbear.lisp.Symbol;

@SuppressWarnings("serial")
public class NextServlet extends HttpServlet {

static private Symbol doGet2 = null;

public void init() throws ServletException {
AbclInit.init();
try {
doGet2 = Lisp.internInPackage("DO-GET2", "FIRST-SERVLET");
} catch (ConditionThrowable ct) {
}
}

public void doGet(HttpServletRequest req, HttpServletResponse resp)
throws IOException {

LispThread currentThread = LispThread.currentThread();

SpecialBinding lastSpecialBinding = currentThread.lastSpecialBinding;
currentThread.bindSpecial(Symbol.STANDARD_OUTPUT,
new org.armedbear.lisp.Stream(resp.getOutputStream(),
Symbol.CHARACTER, Symbol.internKeyword("UTF-8")));

resp.setContentType("text/html; charset=utf-8");

try {
currentThread.execute(doGet2, new JavaObject(req), new JavaObject(resp));
} catch (ConditionThrowable condition) {
resp.setContentType("text/plain");
resp.getWriter().println(condition.toString());
} finally {
currentThread.lastSpecialBinding = lastSpecialBinding;
}
}
}

lisp ファイルの方は関数(go-get2)に引数を2つ追加。 java:jcall で getParameter メソッドを呼び出せばクエリストリングが取得できる。

あと cl-who を使うために asdf を rquire し、fasls/cl-who/ を asdf:*central-registry* に追加。 (asdf:oos 'asdf:load-op :cl-who) で cl-who が使えるようになる。

(require 'asdf)
(pushnew "fasls/cl-who/" asdf:*central-registry* :test #'equal)
(asdf:oos 'asdf:load-op :cl-who)

(defun do-get2 (request response)
(declare (ignore response))
(let ((foo (java:jcall "getParameter" request "foo"))
(bar (java:jcall "getParameter" request "bar")))
(cl-who:with-html-output (*standard-output*)
(:html (:head (:title "cl-who"))
(:body (:h1 "CL-WHO")
(:form :method :get :action "next"
(:div "foo " (:input :type :text :name "foo"))
(:div "bar " (:input :type :text :name "bar"))
(:input :type :submit :value "クリック"))
(:div (cl-who:esc (format nil "foo => ~a" foo)))
(:div (cl-who:esc (format nil "bar => ~a" bar))))))
(force-output)))

cl-who はあらかじめ ABCL でコンパイルしておいて、*.asd と *.abcl を war/fasls/cl-who にコピーしておく必要がある。これがうまい方法かどうかわまだ分からないけど。そのための build.xml を書いた(コピペした)。

<project basedir="." default="all">

<property name="sdk.dir" location="/home/ancient/local/opt/appengine-java-sdk" />
<property name="clbuild.dir" location="/home/ancient/letter/lisp/clbuild/source" />

<import file="${sdk.dir}/config/user/ant-macros.xml" />

<path id="project.classpath">
<pathelement path="war/WEB-INF/classes" />
<fileset dir="war/WEB-INF/lib">
<include name="**/*.jar" />
</fileset>
<fileset dir="${sdk.dir}/lib">
<include name="shared/**/*.jar" />
</fileset>
</path>

<target name="all" depends="clean,copy-files" />

<target name="clean">
<delete dir="war/fasls" />
</target>

<target name="copy-files">
<copy todir="war/fasls">
<fileset dir="src/lisp">
<include name="**/*.abcl"/>
</fileset>
</copy>
<copy todir="war/fasls/cl-who">
<fileset dir="${clbuild.dir}/cl-who">
<exclude name="**/_darcs/**"/>
<include name="**/*.abcl"/>
<include name="**/*.asd"/>
</fileset>
</copy>
</target>

<target name="devserver" description="run local dev appserver" depends="all">
<dev_appserver war="war" />
</target>

<target name="deploy" description="deploy to appspot" depends="all">
<appcfg action="update" war="war" />
</target>
</project>

けっこういける気がしてきた。まだ Servlet とのつながり(レスポンスやポストとか)とかいろいろ問題ありそうだけど。あと開発サイクルとかも。ソース修正のたびに開発サーバ再起動はきつい。でも解決方法はある気がする。

MOP で Google app engine のデータストアうまく扱えないかしらん。どうせならちゃんと Common Lisp っぽくいきたいよね。

ABCL で Google app engine

前回は Clojure だったので今回は ABCL。 ABCL on Google App Engine を頼りにやってみる。

まずは Eclipse でプロジェクトを作る(手抜き)。

abcl.jar を war/WEB-INF/lib に ln -s して Eclipse の build pass に abcl.jar を追加。

ABCL のリポジトリにあるサンプルから AbclInit.java, HelloWorldServlet.java, first-servlet.lisp をプロジェクトのソースディレクトリにコピー。

first-servlet.lisp は Slime で C-c C-k してコンパイル。 war/fasls ディレクトリを作ってコンパイルした first-servlet.abcl をコピー。

war/WEB-INF/appengine-web.xml の application タグにアプリケーション名(ID?)を設定。

/war/WEB-INF/web.xml の servlet と servlet-mapping を設定(ABCL のリポジトリにあるサンプルそのまんま)。

だいたいこんなもんで(説明も手抜きだ)、Eclipse からプロジェクトを右クリックして Run As -> Web Application する。 http://localhost:8080/hello にアクセス。動いた。

Eclipse のエンジンボタン(?)をクリックしてデプロイしてみる。パスワード入力すればファイルがアップロードされて完了。 http://hello-abcl.appspot.com/hello 動いてる♪

日本語(というよりむしろ UTF-8)を表示したい場合は、 HelloWorldServlet.java の doGet メソッド中で、 org.armedbear.lisp.Stream を new するときに Symbol.internKeyword("UTF-8") を渡してやり、ついでに resp.setContentType("text/html; charset=utf-8"); もしておく。

public void doGet(HttpServletRequest req, HttpServletResponse resp)
throws IOException {

LispThread currentThread = LispThread.currentThread();

SpecialBinding lastSpecialBinding = currentThread.lastSpecialBinding;
currentThread.bindSpecial(Symbol.STANDARD_OUTPUT,
new org.armedbear.lisp.Stream(resp.getOutputStream(),
Symbol.CHARACTER, Symbol.internKeyword("UTF-8")));

resp.setContentType("text/html; charset=utf-8");

try {
currentThread.execute(doGet);
} catch (ConditionThrowable condition) {
resp.setContentType("text/plain");
resp.getWriter().println(condition.toString());
} finally {
currentThread.lastSpecialBinding = lastSpecialBinding;
}
}

この例では resp.getOutputStream() を *standard-output* にバインドしているだけだが、 currentThread.execute(doGet); のことで req と resp を渡すようすれば、クエリパラメータとかもいじれるようになるよね。きっと。また今度やってみよう。

なにはともあれ Common Lisp で Google app engine できそうだ。 ABCL ありがとう。

追記。 lisp では最後に ~% とかで改行を出力するか force-output しとく必要があるような気がする。

2009/10/29

Clojure で Google app engine

基本的に Clojure on Google AppEngine - El Humidor のとおりやれば動いた。

ちょっとひっかかったところがいくつか

  • clojure.jar, clojure-contrib.jar は Compojure の ant deps のものではなく、ちゃんとそれぞれの github から持ってきたものを使う。
  • commons-codec-1.3.jar, commons-fileupload-1.2.1.jar, commons-io-1.4.jar も war/WEB-INF/lib にコピー(or ln -s)する。
  • (GET "/" だと何故がうまく動かなかったので (GET "/foo" とかした。