<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-7854445846272488112</id><updated>2011-12-22T19:15:23.965+09:00</updated><category term='GAE'/><category term='Twitter'/><category term='Scala'/><category term='sbt'/><category term='Joomla'/><category term='Lift'/><category term='OAuth'/><category term='Titanium'/><category term='foursquare'/><category term='force.com'/><title type='text'>pomu0325</title><subtitle type='html'>ScalaとかLiftとかGAEとか。</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://pomu0325.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7854445846272488112/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://pomu0325.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>pomu0325</name><uri>http://www.blogger.com/profile/07153996876142338944</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://2.bp.blogspot.com/_IngpC7Xbw78/S3OLp5iNDyI/AAAAAAAAAIc/R5UH2AMmiAo/S220/RIMG0357.JPG'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>34</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-7854445846272488112.post-7159493199966679835</id><published>2011-12-18T01:05:00.004+09:00</published><updated>2011-12-18T01:14:20.195+09:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='force.com'/><category scheme='http://www.blogger.com/atom/ns#' term='Titanium'/><title type='text'>Titanium Salesforce module(SalesForce Toolkit for Appcelerator)の使い方 (2)</title><content type='html'>前回のサンプルアプリをちょっと時間が経ってから動かしてみると、&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;[ERROR] In the error handler looking for a 401, and have a 401&lt;br /&gt;&lt;br /&gt;[ERROR] Handleing the 401 error...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;とかエラーが出てしまい動きません…&lt;br /&gt;&lt;br /&gt;force.comからOAuth2で取得したaccess tokenには有効期限があるのでrefresh tokenを使って再度取得する必要があるのですが…そこが動作していないようです。&lt;br /&gt;※&lt;a href="http://wiki.developerforce.com/page/JP:Digging_Deeper_into_OAuth_2.0_at_Salesforce.com" target="_blank"&gt;force.comのOAuthについて詳しくはこちらで&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;エラーのコールバックも呼んでくれないので有効期限切れたかどうかも検出できず…&lt;br /&gt;&lt;br /&gt;なんとかhackできないかと、こんな感じでモジュールの中身をダンプしてみます。&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="javascript"&gt;var FDC = require('com.salesforce');&lt;br /&gt;for (var i in FDC.ForceOAuth) {&lt;br /&gt; Ti.API.debug(i + ':' + FDC.ForceOAuth[i]);&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;最終的にREST APIの呼び出しはここに来るようです。&lt;br /&gt;&lt;pre name="code" class="javascript"&gt;[DEBUG] makeRestCall:function (path, callback, error, method, payload, retry) {var restUrl=Ti.Network.decodeURIComponent(fa.instanceUrl)+'/services/data'+path;var xhr=Ti.Network.createHTTPClient();xhr.onload=function(){Ti.API.info("REST Response: "+this.responseText);var data="";if(this.responseText){data=this.responseText;}&lt;br /&gt;&lt;br /&gt;callback(data);};xhr.onerror=function(e){Ti.API.error("XHR, error handler..."+"\nDbDotCom.REST.OAuth.refreshToken: "+fa.refreshToken+"\nretry: "+retry+"\n e: "+e.error+"\nXHR status: "+this.status);if(!fa.refreshToken||retry){error(e.error);}else{Ti.API.error("In the error handler looking for a 401, and have a "+xhr.status);if(xhr.status===401){Ti.API.error("Handleing the 401 error...");exports.refreshAccessToken(function(oauthResponse){Ti.API.error("Refresh response... "+oauthResponse);fa.makeRestCall(path,callback,error,method,payload,true);},error);}else{Ti.API.error("Not a 401 error, re-throwing...");error(e);}}};if(fa.usePostBin===true){restUrl="http://www.postbin.org/135onm5";}&lt;br /&gt;&lt;br /&gt;xhr.open(method||"GET",restUrl,true)&lt;br /&gt;&lt;br /&gt;Ti.API.info("Rest url: "+restUrl);xhr.setRequestHeader("Authorization","OAuth "+Ti.Network.decodeURIComponent(fa.accessToken));xhr.setRequestHeader("Content-Type","application/json");xhr.send(payload);}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;出てるログから、exports.refreshAccessToken()の呼び出しの中でエラーが起きてコールバックまで戻って来ないようです。&lt;br /&gt;&lt;br /&gt;よく考えたら、&lt;b&gt;OAuth2のrefresh tokenによるaccess token再取得時にはclient secretが必要&lt;/b&gt;なはずなのに、モジュールのパラメータなどでどこにもセットしていないのでrefreshできるわけないですね… 未実装なんでしょうか？&lt;br /&gt;&lt;br /&gt;ダンプしたソースを参考に、&lt;a href="https://gist.github.com/1490519" target="_blank"&gt;こんな感じ&lt;/a&gt;でhackしてみました。&lt;br /&gt;※モジュール内で定義されてるobjectのプロパティは動的に書き換えられない？＆スコープ的にアクセスできない変数があったので結構無理矢理&lt;br /&gt;&lt;br /&gt;使い方は、requireした後にこのファイルをincludeしてパッチを当て、ForceOAuthの代わりにForceOAuth2を使うようにします。ForceOAuth2.openのパラメータにはclient idとclient secretを渡す様にします。&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="javascript"&gt;var FDC = require('com.salesforce');&lt;br /&gt;Ti.include('fdc-patch.js');&lt;br /&gt;FDC.ForceOAuth2.open('CLIENT_ID', 'CLIENT_SECRET');&lt;/pre&gt;&lt;br /&gt;パッチしたポイントは2つ。refreshAccessTokenをclient secretを使用して動作する様にしたのと、&lt;b&gt;REST APIのパスの固定部分に /data が含まれていたのを /apexrest が呼べる様に /services までとした&lt;/b&gt;こと。&lt;br /&gt;&lt;br /&gt;Winter '12でリリースされたApex RESTを使って公開したAPIのURLは、$instance_url/services/apexrest/... となるので、FDC.ForceOAuth2.makeRestCall('/apexrest/myapi', callback) の様な感じで使える様になります。&lt;br /&gt;&lt;br /&gt;&lt;a href="https://marketplace.appcelerator.com/apps/836#support" target="_blank"&gt;marketplaceのモジュールのページ&lt;/a&gt;には、"This toolkit is maintained by the community and sponsored by salesforce.com. Salesforce.com does not officially support this product."とか書いてあるんですが、&lt;b&gt;パッチとか提供したい場合どこに連絡すればいいんでしょうか&lt;/b&gt;… githubとかにソース上がってればforkするのに…&lt;br /&gt;&lt;br /&gt;この記事は&lt;a href="http://atnd.org/events/22909" target="_blank"&gt;Force.com Advent Calendar 2011&lt;/a&gt;に参加しています。&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7854445846272488112-7159493199966679835?l=pomu0325.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://pomu0325.blogspot.com/feeds/7159493199966679835/comments/default' title='コメントの投稿'/><link rel='replies' type='text/html' href='http://pomu0325.blogspot.com/2011/12/titanium-salesforce-modulesalesforce_18.html#comment-form' title='4 件のコメント'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7854445846272488112/posts/default/7159493199966679835'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7854445846272488112/posts/default/7159493199966679835'/><link rel='alternate' type='text/html' href='http://pomu0325.blogspot.com/2011/12/titanium-salesforce-modulesalesforce_18.html' title='Titanium Salesforce module(SalesForce Toolkit for Appcelerator)の使い方 (2)'/><author><name>pomu0325</name><uri>http://www.blogger.com/profile/07153996876142338944</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://2.bp.blogspot.com/_IngpC7Xbw78/S3OLp5iNDyI/AAAAAAAAAIc/R5UH2AMmiAo/S220/RIMG0357.JPG'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7854445846272488112.post-652911407185917958</id><published>2011-12-08T02:31:00.000+09:00</published><updated>2011-12-08T02:31:56.923+09:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='force.com'/><category scheme='http://www.blogger.com/atom/ns#' term='Titanium'/><title type='text'>Titanium Salesforce module(SalesForce Toolkit for Appcelerator)の使い方</title><content type='html'>最近、Titanium Mobileでforce.comのREST APIを使用したiPhoneアプリを作ってます。&lt;br /&gt;&lt;br /&gt;Titanium Mobileについての基本的な知識やTitanium Studioの使い方はある程度知ってる前提で書いているので、Titaniumについて知りたい方は「&lt;a href="http://gihyo.jp/dev/serial/01/titanium/0001" target="_blank"&gt;Titanium Mobileで作る！ iPhone／Androidアプリ&lt;/a&gt;」などを参考にどうぞ。&lt;br /&gt;&lt;br /&gt;&lt;a href="https://marketplace.appcelerator.com/" target="_blank"&gt;Titaniumにはmarketplace&lt;/a&gt;があり、ソフトウェア部品としてのモジュールや、アプリのテンプレートなどをダウンロードできます（有償のもアリ）。&lt;br /&gt;うまいこと目的に合ったモジュールを見つけて利用すれば開発スピードは向上する…はず…&lt;br /&gt;&lt;br /&gt;&lt;a href="https://marketplace.appcelerator.com/apps/836" target="_blank"&gt;Salesforce用のモジュール&lt;/a&gt;も用意されているので、今回の開発にはこれを利用しました。が、ドキュメントが大して無かったりで取っ掛かり苦労したのでメモとして残しておきます。&lt;br /&gt;&lt;div class="p1"&gt;今回は、モジュールを使うまでの準備と、サンプルコードの実行まで。&lt;br /&gt;&lt;a name='more'&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;h4&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;まずはモジュールのダウンロードと設定&lt;/span&gt;&lt;/h4&gt;&lt;div class="p3"&gt;&lt;br /&gt;marketplaceへログイン後、&lt;a href="https://marketplace.appcelerator.com/apps/836"&gt;https://marketplace.appcelerator.com/apps/836&lt;/a&gt;からSalesforce用モジュールをダウンロードすることができます。&lt;br /&gt;&lt;br /&gt;ダウンロードしたzipを解凍すると、&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;com.salesforce-iphone-1.0.zip&lt;/li&gt;&lt;li&gt;documentation/&lt;/li&gt;&lt;li&gt;example/&lt;/li&gt;&lt;li&gt;screenshots/&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;の4つが入ってるはず。&lt;br /&gt;&lt;br /&gt;&lt;a href="http://2.bp.blogspot.com/-AR5TajWPBvY/Tt-cABPKv6I/AAAAAAAAAkU/JQ00VBoPSoc/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588%25EF%25BC%25882011-12-08+0.59.16%25EF%25BC%2589.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" height="320" src="http://2.bp.blogspot.com/-AR5TajWPBvY/Tt-cABPKv6I/AAAAAAAAAkU/JQ00VBoPSoc/s320/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588%25EF%25BC%25882011-12-08+0.59.16%25EF%25BC%2589.png" width="203" /&gt;&lt;/a&gt;Titanium Studioで新規プロジェクトを作り、&lt;b&gt;プロジェクトのルートディレクトリへ上記com.salesforce-iphone-1.0.zipをコピー&lt;/b&gt;します。&lt;br /&gt;&lt;br /&gt;とりあえず1回▶ボタンを押してビルドすると、zipファイルが消え、modulesフォルダが展開されます。&lt;br /&gt;&lt;br /&gt;iPhoneシミュレータが起動しますが一旦閉じます。&lt;br /&gt;&lt;br /&gt;tiapp.xmlの一番下、&amp;lt;modules/&amp;gt;となっているところを&lt;br /&gt;&lt;pre class="xml" name="code"&gt;&amp;lt;modules&amp;gt;&lt;br /&gt;  &amp;lt;module version="1.0"&amp;gt;com.salesforce&amp;lt;/module&amp;gt;&lt;br /&gt;&amp;lt;/modules&amp;gt;&lt;br /&gt;&lt;/pre&gt;&amp;nbsp;  に書き換えます。これ結構忘れる。Titanium Studioが自動でやってくれるといいのに。&lt;br /&gt;&lt;br /&gt;これでモジュールを使う準備は完了です。&lt;br /&gt;&lt;br /&gt;&lt;h4&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;consumer key/secretの準備&lt;/span&gt;&lt;/h4&gt;&lt;div&gt;force.comへの認証はOAuth 2.0を使用するのでconsumer key/secretの取得が必要です。&lt;/div&gt;&lt;br /&gt;force.com環境にログインし、&lt;b&gt;設定メニューから　開発 &amp;gt; リモートアクセス を選択&lt;/b&gt;します。&lt;br /&gt;コールバックURLは使わないので適当に。必須項目を適当に埋めて下さい。&lt;br /&gt;&lt;br /&gt;保存すると、コンシューマ鍵が表示されるのでこれを使用します。&lt;br /&gt;コンシューマの秘密(consumer secret)は今回は使いませんが後で使います。&lt;br /&gt;※コンシューマの秘密、とか翻訳が面白いですねw&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;h4&gt;&lt;span class="Apple-style-span" style="font-size: large;"&gt;サンプルコードの実行&lt;/span&gt;&lt;/h4&gt;ダウンロードしたzipに入っていた、example/app.jsをResources/app.jsに上書きコピーします。&lt;br /&gt;&lt;br /&gt;実は&lt;b&gt;exampleで付いてくるapp.jsこのままだとバグってる&lt;/b&gt;ので…、コールバックメソッドの先頭に&lt;br /&gt;&amp;nbsp; e = JSON.parse(e); &lt;br /&gt;を入れてやります。&lt;br /&gt;&lt;a href="https://gist.github.com/1443640" target="_blank"&gt;上記追加済みのapp.jsはgistに上げました&lt;/a&gt;。&lt;br /&gt;※コールバックメソッドに戻ってくるのはJSON文字列なのにそのままobjectとして使おうとしてる…&lt;br /&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;app.jsの下の方、&lt;b&gt;FDC.ForceOAuth.openの引数に先ほど発行したコンシューマ鍵を渡します&lt;/b&gt;。&lt;br /&gt;&lt;br /&gt;実行してみます。&lt;br /&gt;※エラーが出る場合は、buildディレクトリに古いのが残ってて差分ビルドがうまくいかない？ことがあるので Project &amp;gt; Clean すると治ると思います。&lt;br /&gt;&lt;br /&gt;&lt;a href="http://1.bp.blogspot.com/-szLGY3RDDRA/Tt-fi1wOrfI/AAAAAAAAAkc/vi06hsTMs9g/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588%25EF%25BC%25882011-12-08+1.28.31%25EF%25BC%2589.png" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" height="320" src="http://1.bp.blogspot.com/-szLGY3RDDRA/Tt-fi1wOrfI/AAAAAAAAAkc/vi06hsTMs9g/s320/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588%25EF%25BC%25882011-12-08+1.28.31%25EF%25BC%2589.png" width="170" /&gt;&lt;/a&gt;Salesforceへのログイン画面が出て、ログインして許可ボタンを押すと、こんな画面になります。&lt;br /&gt;&lt;br /&gt;Query User〜Describe User辺りのボタンを押すとそれぞれREST APIがコールされて結果がTableViewで表示されます。&lt;br /&gt;レスポンスJSONの内容はコンソールにも出力されるのでこれを参考にしてJSON.parseした結果を自由に扱えますね。&lt;br /&gt;&lt;br /&gt;Create Record以下のボタンはカスタムオブジェクトを先に作っておかないとエラーになりますので気になる人はサンプルコードを参考に作ってみると試せるかも。&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;実は&lt;b&gt;Apex RESTで公開したAPIが呼べなかったり、OAuth Tokenの有効期限が切れてしまうとエラーになったり&lt;/b&gt;と穴があるのですが…この辺りの対策は次回書く予定です（どうやらAdvent Calendarが2周目も回ってくる様なので…）。&lt;br /&gt;&lt;br /&gt;この記事は&lt;a href="http://atnd.org/events/22909" target="_blank"&gt;Force.com Advent Calendar 2011&lt;/a&gt;に参加しています。&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7854445846272488112-652911407185917958?l=pomu0325.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://pomu0325.blogspot.com/feeds/652911407185917958/comments/default' title='コメントの投稿'/><link rel='replies' type='text/html' href='http://pomu0325.blogspot.com/2011/12/titanium-salesforce-modulesalesforce.html#comment-form' title='0 件のコメント'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7854445846272488112/posts/default/652911407185917958'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7854445846272488112/posts/default/652911407185917958'/><link rel='alternate' type='text/html' href='http://pomu0325.blogspot.com/2011/12/titanium-salesforce-modulesalesforce.html' title='Titanium Salesforce module(SalesForce Toolkit for Appcelerator)の使い方'/><author><name>pomu0325</name><uri>http://www.blogger.com/profile/07153996876142338944</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://2.bp.blogspot.com/_IngpC7Xbw78/S3OLp5iNDyI/AAAAAAAAAIc/R5UH2AMmiAo/S220/RIMG0357.JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/-AR5TajWPBvY/Tt-cABPKv6I/AAAAAAAAAkU/JQ00VBoPSoc/s72-c/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588%25EF%25BC%25882011-12-08+0.59.16%25EF%25BC%2589.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7854445846272488112.post-2374349784355060874</id><published>2011-09-20T08:47:00.003+09:00</published><updated>2011-09-20T08:50:34.880+09:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='foursquare'/><title type='text'>foursquare global hackathon @ Tokyo</title><content type='html'>NYC, SF, Paris, Tokyoで同時開催の&lt;a href="https://github.com/foursquare/hackathon/wiki/Foursquare-Global-Hackathon"&gt;foursquare global hackathon&lt;/a&gt;、東京会場のオーガナイザ兼参加者として9/17-18の2日間約42時間ぶっつづけで楽しんできました。&lt;br /&gt;&lt;br /&gt;東京会場では参加者15名、応募作品9と他会場と比べて少ない数字でしたが、みんなでワイワイおやつ食べたり深夜居残り組はみんなで銭湯行ったり、いい雰囲気でした。&lt;br /&gt;NYC,SF,Parisの中継映像をスクリーンに映して、音声無しながらもParisがエッフェル塔切り抜いたのカメラの前に貼って遊んでたのに東京タワーで対抗？してみたりw&lt;br /&gt;&lt;a href="http://togetter.com/li/189663"&gt;Togetterまとめ&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;いきなりですが東京会場内での作品の投票結果発表！&lt;br /&gt;&lt;i&gt;&lt;span class="Apple-style-span" style="font-size: x-large;"&gt;日本時間9/23の正午まで、&lt;a href="http://fshackathon.appspot.com/"&gt;http://fshackathon.appspot.com/&lt;/a&gt;で一般投票が行われているので気に入った作品にぜひ投票をお願いします（何作品でも投票できます）！&lt;/span&gt;&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;1位（会場得票数4）： photosquare&lt;/b&gt;&lt;br /&gt;作者：中継カメラ設置等いろいろ手伝ってくれた&lt;a href="http://twitter.com/koogawa"&gt;@koogawa&lt;/a&gt;さん&lt;br /&gt;内容：近くのvenueに投稿されてる写真をスライドショー表示してくれるiPhone/iPad用アプリ。アプリ審査があるのでまだダウンロードできませんが、&lt;a href="http://twitvideo.jp/06TpC"&gt;http://twitvideo.jp/06TpC&lt;/a&gt;から映像見ることができます。&lt;br /&gt;&lt;a href="http://fshackathon.appspot.com/team?team_key=ag1zfmZzaGFja2F0aG9ucgsLEgRUZWFtGNQPDA"&gt;投票する&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;2位（会場得票数3）：The Journalist&lt;/b&gt;&lt;br /&gt;作者：今回素晴らしい会場を提供してくださったGaiaXの&lt;a href="http://twitter.com/aomushi510"&gt;@aomushi510&lt;/a&gt;さんと、直前に@aomushi510さんに誘われて急遽参加のTomoyuki Hisadaさんのチーム&lt;br /&gt;内容：4sqのチェックイン履歴を使用して、自分の行った場所の記事を投稿できる、ソーシャルニュース的なサービス。新聞的なデザインや、細かいところの動きがよくできていて完成度高くてびっくり！&lt;br /&gt;&lt;a href="http://journalist.azu.sh/"&gt;http://journalist.azu.sh/&lt;/a&gt;で見れる…はずが残念ながら今エラー出てます…&lt;br /&gt;&lt;a href="http://fshackathon.appspot.com/team?team_key=ag1zfmZzaGFja2F0aG9ucgsLEgRUZWFtGLoXDA"&gt;投票する&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;同着2位（会場得票数3）：4sqlist(仮)&lt;/b&gt;&lt;br /&gt;作者：pomu0325（すいません自分です。）&lt;br /&gt;内容：サブベニュー（駅のホームとか、モール内の店舗とか）をグループ化して探しやすくするiPhone/iPad用WEBアプリ。時間が無くて最低限の機能しか作り込めませんでした… 同じアイデアで作り始めた@ikawamotoさんとは違い、手動でサブベニューの情報を編集するアプローチです。&lt;a href="http://4sqlist.appspot.com/"&gt;http://4sqlist.appspot.com/&lt;/a&gt;&lt;br /&gt;&lt;a href="http://fshackathon.appspot.com/team?team_key=ag1zfmZzaGFja2F0aG9ucgsLEgRUZWFtGKEfDA"&gt;投票する&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;以下、順不動での掲載です。&lt;br /&gt;&lt;br /&gt;&lt;b&gt;チーム名：curly&lt;/b&gt;&lt;br /&gt;作者：@takochuuさん、@sunny_510さんのチーム&lt;br /&gt;内容：venue毎に、そこにチェックインした人のつぶやきが見えたりランキングが見れたりするサービス。デザインもよくまとまってましたが、公開はまだ…かな？&lt;br /&gt;&lt;a href="http://fshackathon.appspot.com/team?team_key=ag1zfmZzaGFja2F0aG9ucgsLEgRUZWFtGNIPDA"&gt;投票する&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Packed Venues&lt;/b&gt;&lt;br /&gt;作者：大阪から来てくれた@ikawamotoさん&lt;br /&gt;内容：住所が同じベニューをまとめて表示するiPhoneアプリ。&lt;a href="http://www.youtube.com/watch?v=t3ZSdOBYiXs"&gt;動かしているところの動画はこちら&lt;/a&gt;。住所表記のブレが解決できれば自動でまとめられるアプローチの方がいいですね！&lt;br /&gt;&lt;a href="http://fshackathon.appspot.com/team?team_key=ag1zfmZzaGFja2F0aG9ucgsLEgRUZWFtGNsPDA"&gt;投票する&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt; Squarecount &lt;/b&gt;&lt;br /&gt;作者：meetup主催等いつも手伝ってくれてる@jayjpn&lt;br /&gt;内容：4sqへのチェックイン回数を表示するだけw。エンジニアじゃないのに締め切りぎりぎりまで頑張ってました。&lt;a href="http://squarecount.appspot.com/index.html"&gt;http://squarecount.appspot.com/index.html&lt;/a&gt;&lt;br /&gt;&lt;a href="http://fshackathon.appspot.com/team?team_key=ag1zfmZzaGFja2F0aG9ucgsLEgRUZWFtGNUPDA"&gt;投票する&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;4sq Scouter&lt;/b&gt;&lt;br /&gt;作者：自分と同じく携帯用4sq「じゃぽすく」を作っている@jpfoursquareさん&lt;br /&gt;内容：スカウターと言ったらおなじみ、例のアレ風に「強さ」を数値化して表示してくれます。数値には、「複雑な足し算」が使用されているとのことですw &lt;a href="http://scouter.jpfoursquare.com/"&gt;http://scouter.jpfoursquare.com/&lt;/a&gt;から試せます。&lt;br /&gt;&lt;a href="http://fshackathon.appspot.com/team?team_key=ag1zfmZzaGFja2F0aG9ucgsLEgRUZWFtGLkXDA"&gt;投票する&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;fourSquare PopUp Utility&lt;/b&gt;&lt;br /&gt;作者：The Journalistもチームメンバーで作っているTomoyuki Hisadaさん&lt;br /&gt;内容：4sqのユーザやvenueの情報をマウスオーバーで表示してくれるjsライブラリ。ページ内のfoursquareへのリンクを自動で変換してくれるみたいです。twitter anywhereの4sq版、と言えば分かる人には分かる。&lt;a href="http://lab1.anotherbrain.co.jp/fsht/popup.html"&gt;使い方等&lt;/a&gt;&lt;br /&gt;&lt;a href="http://fshackathon.appspot.com/team?team_key=ag1zfmZzaGFja2F0aG9ucgsLEgRUZWFtGPEuDA"&gt;投票する&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Complete Japanese fried chicken delis in MusashiKoyama&lt;/b&gt;&lt;br /&gt;作者：@Suger1008 さん&lt;br /&gt;内容：むさこ（武蔵小山）の唐揚げ屋さんを制覇！ むさこ愛にあふれるスマートフォン用アプリをTitanium Mobileで作られていました。&lt;a href="http://musakoapp.blogspot.com/2011/09/complete-japanese-fried-chicken.html"&gt;公開はまだですが解説等はこちら&lt;/a&gt;。&lt;br /&gt;&lt;a href="http://fshackathon.appspot.com/team?team_key=ag1zfmZzaGFja2F0aG9ucgsLEgRUZWFtGPEuDA"&gt;投票する&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;応募があったのは以上9作品ですが、他にも時間が足りなかったり、既に作っているものを4sq対応したりと各自ガリガリがんばっていました。&lt;br /&gt;&lt;br /&gt;最後に、深夜含め2日連続と無理な条件にも関わらずすてきな会場を提供してくださった、&lt;a href="http://www.gaiax.co.jp/"&gt;ガイアックス&lt;/a&gt;さん、本当にありがとうございました！！！&lt;br /&gt;ハッカソンなどのイベントでよく会場提供をしているそうなので、またお邪魔させていただきます！&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7854445846272488112-2374349784355060874?l=pomu0325.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://pomu0325.blogspot.com/feeds/2374349784355060874/comments/default' title='コメントの投稿'/><link rel='replies' type='text/html' href='http://pomu0325.blogspot.com/2011/09/foursquare-global-hackathon-tokyo.html#comment-form' title='0 件のコメント'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7854445846272488112/posts/default/2374349784355060874'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7854445846272488112/posts/default/2374349784355060874'/><link rel='alternate' type='text/html' href='http://pomu0325.blogspot.com/2011/09/foursquare-global-hackathon-tokyo.html' title='foursquare global hackathon @ Tokyo'/><author><name>pomu0325</name><uri>http://www.blogger.com/profile/07153996876142338944</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://2.bp.blogspot.com/_IngpC7Xbw78/S3OLp5iNDyI/AAAAAAAAAIc/R5UH2AMmiAo/S220/RIMG0357.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7854445846272488112.post-528261902624625693</id><published>2011-03-29T19:34:00.001+09:00</published><updated>2011-03-29T19:34:58.061+09:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Lift'/><title type='text'>[Lift] 画像ファイルにjsessionidを付けない</title><content type='html'>&lt;a href="http://blog.takeda-soft.jp/blog/show/384"&gt;takedasoftさんがUrl Rewrite Filterを使った方法を書いてました&lt;/a&gt;が、Boot.scalaでLiftRule.urlDecorateにルール追加するだけで簡単にできたのでメモ。&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="scala"&gt;val ImgPtn = """(.*\.(?:png|gif|jpg));jsessionid=.+""".r&lt;br /&gt;LiftRules.urlDecorate.append {&lt;br /&gt;  case ImgPtn(url) =&gt; url&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;※Lift 2.2から&lt;a href="http://pomu0325.blogspot.com/2010/12/lift-lift22cookiejsessionid.html"&gt;デフォルトではjsessionid付かなくなったのはここに書いてある通り&lt;/a&gt;です。&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7854445846272488112-528261902624625693?l=pomu0325.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://pomu0325.blogspot.com/feeds/528261902624625693/comments/default' title='コメントの投稿'/><link rel='replies' type='text/html' href='http://pomu0325.blogspot.com/2011/03/lift-jsessionid.html#comment-form' title='0 件のコメント'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7854445846272488112/posts/default/528261902624625693'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7854445846272488112/posts/default/528261902624625693'/><link rel='alternate' type='text/html' href='http://pomu0325.blogspot.com/2011/03/lift-jsessionid.html' title='[Lift] 画像ファイルにjsessionidを付けない'/><author><name>pomu0325</name><uri>http://www.blogger.com/profile/07153996876142338944</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://2.bp.blogspot.com/_IngpC7Xbw78/S3OLp5iNDyI/AAAAAAAAAIc/R5UH2AMmiAo/S220/RIMG0357.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7854445846272488112.post-6293104703160360810</id><published>2011-03-22T13:55:00.000+09:00</published><updated>2011-03-22T13:55:45.827+09:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Scala'/><category scheme='http://www.blogger.com/atom/ns#' term='sbt'/><title type='text'>[Scala] sbt jetty-runでhttpsを使用したい</title><content type='html'>&lt;h3&gt;方法1: カスタムjetty.xmlを使う&lt;/h3&gt;sbt標準だとjetty.xmlを1から書けばOK。&lt;br /&gt;&lt;pre class="scala" name="code"&gt;override def jettyConfiguration = new CustomJettyConfiguration {&lt;br /&gt;  override def jettyConfigurationXML = List("/path/to/jetty.xml")&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;この方法だとjetty.xmlにConnectorとかHandlerとか全部自分で書かないといけないので面倒…&lt;br /&gt;参考：&lt;a href="http://wiki.eclipse.org/Jetty/Reference/jetty.xml_syntax"&gt;jetty.xmlのリファレンス&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;方法2: sbtにちょっと手を入れてjetty設定用のhookを使う&lt;/h3&gt;&lt;h4&gt;sbtのソースをcheckoutしてhookが設定できるようにする&lt;br /&gt;&lt;/h4&gt;&lt;br /&gt;参考：&lt;a href="http://code.google.com/p/simple-build-tool/wiki/Build"&gt;Build - simple-build-tool -&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;sbtのソースをcheckout&lt;br /&gt;&lt;pre class="scala" name="code"&gt;$ git clone git://github.com/harrah/xsbt.git&lt;br /&gt;$ cd xsbt&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;変更したのは下の2行だけ。jetty-runが呼ばれてデフォルトのhttpポートのConnectorの設定直後にcallbackで設定した関数が呼ばれるようにする。&lt;br /&gt;&lt;pre class="scala" name="code"&gt;diff --git a/sbt/src/main/scala/sbt/WebApp.scala b/sbt/src/main/scala/sbt/WebApp.scala&lt;br /&gt;index f5a3d09..997294e 100644&lt;br /&gt;--- a/sbt/src/main/scala/sbt/WebApp.scala&lt;br /&gt;+++ b/sbt/src/main/scala/sbt/WebApp.scala&lt;br /&gt;@@ -105,6 +105,8 @@ trait DefaultJettyConfiguration extends JettyConfiguration&lt;br /&gt;        def parentLoader: ClassLoader&lt;br /&gt;        def jettyEnv: Option[File]&lt;br /&gt;        def webDefaultXml: Option[File]&lt;br /&gt;+&lt;br /&gt;+       def callback: Option[AnyRef =&gt; Any] = None&lt;br /&gt; }&lt;br /&gt; abstract class CustomJettyConfiguration extends JettyConfiguration&lt;br /&gt; {&lt;br /&gt;diff --git a/sbt/src/main/scala/sbt/jetty/LazyJettyRun.scala.templ b/sbt/src/main/scala/sbt/jetty/LazyJettyRun.scala.templ&lt;br /&gt;index 263afa6..4ca4b0e 100644&lt;br /&gt;--- a/sbt/src/main/scala/sbt/jetty/LazyJettyRun.scala.templ&lt;br /&gt;+++ b/sbt/src/main/scala/sbt/jetty/LazyJettyRun.scala.templ&lt;br /&gt;@@ -54,6 +54,7 @@ private object LazyJettyRun${jetty.version} extends JettyRun&lt;br /&gt;                                case c: DefaultJettyConfiguration =&gt;&lt;br /&gt;                                        import c._&lt;br /&gt;                                        configureDefaultConnector(server, port)&lt;br /&gt;+                                       c.callback.foreach(_(server))&lt;br /&gt;                                        val webapp = new WebAppContext(war.absolutePath, contextPath)&lt;br /&gt;                                        webDefaultXml.foreach{webDefaultXml:File =&gt; webapp.setDefaultsDescriptor(webDefaultXml.toString)}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h4&gt;sbtのビルド&lt;/h4&gt;&lt;pre name="code" class="scala"&gt;$ sbt update generate-loader-compat proguard "project Simple Build Tool" publish-local&lt;br /&gt;&lt;/pre&gt;※generate-loader-compatしないとjetty6用のクラスが一部生成されなかった。(jettyは6-&gt;7でパッケージ名が変わったので、&lt;a href="https://github.com/harrah/xsbt/tree/master/sbt/src/main/scala/sbt/jetty"&gt;sbtのソースではテンプレート化して6,7,7.2用のscalaソースが吐かれるようになってる&lt;/a&gt;）&lt;br /&gt;&lt;br /&gt;&lt;h4&gt;ssl用証明書の準備&lt;/h4&gt;JDKの&lt;a href="http://java.sun.com/j2se/1.5.0/ja/docs/ja/tooldocs/solaris/keytool.html#selfcertCmd"&gt;keytool&lt;/a&gt;を使用。とりあえずオレオレ証明書で。&lt;br /&gt;&lt;pre name="code" class="scala"&gt;$ keytool -keystore src/test/resources/keystore -alias jetty -genkey -keyalg RSA&lt;br /&gt;$ keytool -selfcert -validity 1024 -keystore src/test/resources/keystore -alias jetty&lt;br /&gt;&lt;/pre&gt;※keystoreのパスワードを聞かれるので入力します&lt;br /&gt;&lt;br /&gt;&lt;h4&gt;sbt プロジェクト側の準備&lt;/h4&gt;build.propertiesの sbt.version=0.7.5.RC0 にしてsbt実行、先に標準の0.7.5.RC0を入れておく。&lt;br /&gt;&lt;br /&gt;ビルドしたsbt/target/scala_2.7.7/sbt_2.7.7-0.7.5.RC0.jar をプロジェクトのproject/boot/scala-2.7.7/org.scala-tools.sbt/sbt/0.7.5.RC0/sbt_2.7.7-0.7.5.RC0.jar に上書き。&lt;br /&gt;&lt;br /&gt;project/build/MyProject.scala の設定。callbackでjettyのServerインスタンスにSslSocketConnectorを追加。ClassLoaderが違うので普通に書くとClassCastExceptionが発生するのでリフレクションで。&lt;br /&gt;&lt;pre name="code" class="scala"&gt;  override def jettyConfiguration: JettyConfiguration =&lt;br /&gt;    new DefaultJettyConfiguration {&lt;br /&gt;      def classpath = jettyRunClasspath&lt;br /&gt;      def jettyClasspath = MyProject.this.jettyClasspath&lt;br /&gt;      def war = jettyWebappPath&lt;br /&gt;      def contextPath = jettyContextPath&lt;br /&gt;      def classpathName = "test"&lt;br /&gt;      def parentLoader = buildScalaInstance.loader&lt;br /&gt;      def scanDirectories = Path.getFiles(MyProject.this.scanDirectories).toSeq&lt;br /&gt;      def scanInterval = MyProject.this.scanInterval&lt;br /&gt;      def port = jettyPort&lt;br /&gt;      def log = MyProject.this.log&lt;br /&gt;      def jettyEnv = jettyEnvXml&lt;br /&gt;      def webDefaultXml = jettyWebDefaultXml&lt;br /&gt;      override def callback = Some((server: AnyRef) =&gt;  {&lt;br /&gt;        val cl = server.getClass.getClassLoader&lt;br /&gt;        val ssl = cl.loadClass("org.mortbay.jetty.security.SslSocketConnector").newInstance.asInstanceOf[{&lt;br /&gt;          def setPort(p: Int): Unit&lt;br /&gt;          def setMaxIdleTime(t: Int): Unit&lt;br /&gt;          def setKeystore(s: String): Unit&lt;br /&gt;          def setPassword(s: String): Unit&lt;br /&gt;          def setKeyPassword(s: String): Unit&lt;br /&gt;          def setTruststore(s: String): Unit&lt;br /&gt;          def setTrustPassword(s: String): Unit&lt;br /&gt;        }]&lt;br /&gt;        val addConnector: java.lang.reflect.Method = server.getClass.getMethod("addConnector", cl.loadClass("org.mortbay.jetty.Connector"))&lt;br /&gt;&lt;br /&gt;        ssl.setPort(8443)&lt;br /&gt;        ssl.setMaxIdleTime(30000)&lt;br /&gt;        ssl.setKeystore(info.projectPath + "/src/test/resources/keystore")&lt;br /&gt;        ssl.setPassword("password")&lt;br /&gt;        ssl.setKeyPassword("password")&lt;br /&gt;        ssl.setTruststore(info.projectPath + "/src/test/resources/keystore")&lt;br /&gt;        ssl.setTrustPassword("password")&lt;br /&gt;        addConnector.invoke(server, ssl)&lt;br /&gt;      })&lt;br /&gt;  }&lt;br /&gt;&lt;/pre&gt;※上記はjetty6の場合で、jetty7の場合はパッケージ・クラス名がちょっと違います。&lt;br /&gt;&lt;br /&gt;これで、jetty-runで8080でhttp、8443でhttpsが起動します。&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7854445846272488112-6293104703160360810?l=pomu0325.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://pomu0325.blogspot.com/feeds/6293104703160360810/comments/default' title='コメントの投稿'/><link rel='replies' type='text/html' href='http://pomu0325.blogspot.com/2011/03/scala-sbt-jetty-runhttps.html#comment-form' title='0 件のコメント'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7854445846272488112/posts/default/6293104703160360810'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7854445846272488112/posts/default/6293104703160360810'/><link rel='alternate' type='text/html' href='http://pomu0325.blogspot.com/2011/03/scala-sbt-jetty-runhttps.html' title='[Scala] sbt jetty-runでhttpsを使用したい'/><author><name>pomu0325</name><uri>http://www.blogger.com/profile/07153996876142338944</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://2.bp.blogspot.com/_IngpC7Xbw78/S3OLp5iNDyI/AAAAAAAAAIc/R5UH2AMmiAo/S220/RIMG0357.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7854445846272488112.post-3196661969517779309</id><published>2011-02-09T18:55:00.000+09:00</published><updated>2011-02-09T18:55:04.728+09:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Scala'/><category scheme='http://www.blogger.com/atom/ns#' term='sbt'/><title type='text'>[Scala][sbt]DefaultWebProjectでwarではなくjarも作りたい</title><content type='html'>DefaultWebProjectだとsbt packageタスクでwarが作られますが、jarも作りたい時があった場合、こんな感じで。&lt;br /&gt;&lt;pre name="code" class="scala"&gt;lazy val jar = packageTask(packagePaths, jarPath, packageOptions).dependsOn(compile) describedAs "Creates a jar file."&lt;br /&gt;&lt;/pre&gt;sbt jarでjarファイルが作られます。&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7854445846272488112-3196661969517779309?l=pomu0325.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://pomu0325.blogspot.com/feeds/3196661969517779309/comments/default' title='コメントの投稿'/><link rel='replies' type='text/html' href='http://pomu0325.blogspot.com/2011/02/scalasbtdefaultwebprojectwarjar.html#comment-form' title='0 件のコメント'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7854445846272488112/posts/default/3196661969517779309'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7854445846272488112/posts/default/3196661969517779309'/><link rel='alternate' type='text/html' href='http://pomu0325.blogspot.com/2011/02/scalasbtdefaultwebprojectwarjar.html' title='[Scala][sbt]DefaultWebProjectでwarではなくjarも作りたい'/><author><name>pomu0325</name><uri>http://www.blogger.com/profile/07153996876142338944</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://2.bp.blogspot.com/_IngpC7Xbw78/S3OLp5iNDyI/AAAAAAAAAIc/R5UH2AMmiAo/S220/RIMG0357.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7854445846272488112.post-120301490631994497</id><published>2011-02-07T21:19:00.010+09:00</published><updated>2011-03-22T13:07:40.511+09:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='GAE'/><title type='text'>[GAE] bulkloaderをGAE/Jで使う</title><content type='html'>python版appengine-sdkを使ってcsvファイルからデータをDatastoreにロードすることができます。&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;準備&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://code.google.com/appengine/downloads.html"&gt;python版appengine-sdk&lt;/a&gt;をインストールする&lt;br /&gt;&lt;/ul&gt;&lt;h3&gt;web.xmlにremote_api用のServletの設定を書く&lt;/h3&gt;&lt;pre name="code" class="xml"&gt;&amp;lt;servlet&amp;gt;&lt;br /&gt;  &amp;lt;servlet-name&amp;gt;remoteapi&amp;lt;/servlet-name&amp;gt;&lt;br /&gt;  &amp;lt;servlet-class&amp;gt;com.google.apphosting.utils.remoteapi.RemoteApiServlet&amp;lt;/servlet-class&amp;gt;&lt;br /&gt;&amp;lt;/servlet&amp;gt;&lt;br /&gt;&amp;lt;servlet-mapping&amp;gt;&lt;br /&gt;  &amp;lt;servlet-name&amp;gt;remoteapi&amp;lt;/servlet-name&amp;gt;&lt;br /&gt;  &amp;lt;url-pattern&amp;gt;/remote_api&amp;lt;/url-pattern&amp;gt;&lt;br /&gt;&amp;lt;/servlet-mapping&amp;gt;&lt;br /&gt;&lt;/pre&gt;security-constraintも追加しておいた方が良さげ。 &lt;pre name="code" class="xml"&gt;&amp;lt;security-constraint&amp;gt;&lt;br /&gt;  &amp;lt;web-resource-collection&amp;gt;&lt;br /&gt;    &amp;lt;url-pattern&amp;gt;/remote_api&amp;lt;/url-pattern&amp;gt;&lt;br /&gt;  &amp;lt;/web-resource-collection&amp;gt;&lt;br /&gt;  &amp;lt;auth-constraint&amp;gt;&lt;br /&gt;    &amp;lt;role-name&amp;gt;admin&amp;lt;/role-name&amp;gt;&lt;br /&gt;  &amp;lt;/auth-constraint&amp;gt;&lt;br /&gt;&amp;lt;/security-constraint&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;h3&gt;ロード設定用のyamlファイルを書く&lt;/h3&gt;&lt;pre name="code" class="js"&gt;python_preamble:&lt;br /&gt;- import: base64&lt;br /&gt;- import: re&lt;br /&gt;- import: google.appengine.ext.bulkload.transform&lt;br /&gt;- import: google.appengine.ext.bulkload.bulkloader_wizard&lt;br /&gt;- import: google.appengine.api.datastore&lt;br /&gt;- import: google.appengine.api.users&lt;br /&gt;&lt;br /&gt;transformers:&lt;br /&gt;&lt;br /&gt;- kind: Scores&lt;br /&gt;  connector: csv&lt;br /&gt;&lt;br /&gt;  connector_options:&lt;br /&gt;    encoding: ms932&lt;br /&gt;&lt;br /&gt;  property_map:&lt;br /&gt;    - property: __key__&lt;br /&gt;      external_name: id&lt;br /&gt;      import_transform: int&lt;br /&gt;&lt;br /&gt;    - property: name&lt;br /&gt;      external_name: name&lt;br /&gt;&lt;br /&gt;    - property: score&lt;br /&gt;      external_name: score&lt;br /&gt;      import_transform: transform.none_if_empty(float)&lt;br /&gt;&lt;/pre&gt;&lt;li&gt;キーにidではなく文字列(name)を使用する場合は、import_transform: intは不要。&lt;br /&gt;&lt;li&gt;import_transformに他に指定できる例&lt;br /&gt;日付: transform.import_date_time('%Y/%m/%d %H:%M')&lt;br /&gt;固定値: import_transform: lambda x:1&lt;br /&gt;数値変換: import_transform: lambda x:int(x)*100&lt;br /&gt;※lambdaは引数の:の後にスペースを空けるとダメな様です&lt;br /&gt;&lt;br /&gt;ロード用CSV&lt;br /&gt;&lt;pre name="code" class="js"&gt;id,name,score&lt;br /&gt;1,Alice,5.0&lt;br /&gt;2,Bob,2.5&lt;br /&gt;3,Caroline,&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h3&gt;ロード用コマンド&lt;/h3&gt;&lt;pre name="code" class="js"&gt;appcfg.py upload_data --filename=test.csv --config_file=test.yml --url=http://{appid}.appspot.com/remote_api --application={appid} --kind=Scores -v&lt;br /&gt;&lt;/pre&gt;※{appid}はアプリケーションIDに置き換える&lt;br /&gt;&lt;br /&gt;security-constraintの設定をしている場合、毎回管理者アドレス／パスワードの入力が必要です。&lt;br /&gt;ローカルのdev_appserverだとエラーが出て動きませんでした…&lt;br /&gt;&lt;br /&gt;こんな感じに反映されます。&lt;br /&gt;&lt;div class="separator"&gt;&lt;a href="http://3.bp.blogspot.com/_IngpC7Xbw78/TU_jTZAkNOI/AAAAAAAAAe8/ebMKwRJJQJo/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588%25EF%25BC%25882011-02-07%2B21.11.55%25EF%25BC%2589.png" imageanchor="1"&gt;&lt;img border="0" height="122" width="320" src="http://3.bp.blogspot.com/_IngpC7Xbw78/TU_jTZAkNOI/AAAAAAAAAe8/ebMKwRJJQJo/s320/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588%25EF%25BC%25882011-02-07%2B21.11.55%25EF%25BC%2589.png" /&gt;&lt;/a&gt;&lt;/div&gt;※1万件だと約500秒（8分ちょっと）くらいかかりました（CPU Timeを無料Quotaの10%ほど消費します）&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;その他注意点&lt;/h3&gt;&lt;strike&gt;&lt;li&gt;slim3を使っている場合、filterの設定があると、"ProtocolBufferDecodeError: corrupted"の様なエラーが出てしまうので一時的にコメントアウトして回避しました。（※他にいい方法があれば教えて下さい…&lt;/strike&gt;&lt;br /&gt;&lt;br /&gt;&lt;strike&gt;2/11追記: bulkload用にfilterをコメントアウトしたversionをデプロイしておいて、--url=http://latest.bulkload.{appid}.appspot.com/remote_api みたいに指定するといいかも&lt;/strike&gt;&lt;br /&gt;&lt;br /&gt;3/22追記：slim3ではなくscenic3の方の設定でした。matcherで/remote_apiを除外したらOK。&lt;br /&gt;&lt;br /&gt;参考: &lt;a href="http://ikaisays.com/2010/06/10/using-the-bulkloader-with-java-app-engine/"&gt;Ikai Lan says / Using the bulkloader with Java App Engine&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7854445846272488112-120301490631994497?l=pomu0325.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://pomu0325.blogspot.com/feeds/120301490631994497/comments/default' title='コメントの投稿'/><link rel='replies' type='text/html' href='http://pomu0325.blogspot.com/2011/02/gae-bulkloadergaej.html#comment-form' title='0 件のコメント'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7854445846272488112/posts/default/120301490631994497'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7854445846272488112/posts/default/120301490631994497'/><link rel='alternate' type='text/html' href='http://pomu0325.blogspot.com/2011/02/gae-bulkloadergaej.html' title='[GAE] bulkloaderをGAE/Jで使う'/><author><name>pomu0325</name><uri>http://www.blogger.com/profile/07153996876142338944</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://2.bp.blogspot.com/_IngpC7Xbw78/S3OLp5iNDyI/AAAAAAAAAIc/R5UH2AMmiAo/S220/RIMG0357.JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_IngpC7Xbw78/TU_jTZAkNOI/AAAAAAAAAe8/ebMKwRJJQJo/s72-c/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588%25EF%25BC%25882011-02-07%2B21.11.55%25EF%25BC%2589.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7854445846272488112.post-2279561979390358057</id><published>2011-01-15T11:02:00.001+09:00</published><updated>2011-01-15T11:02:46.342+09:00</updated><title type='text'>[Blogger] スマートフォン用テンプレートを有効にする</title><content type='html'>&lt;a href="http://bloggerindraft.blogspot.com/2010/12/new-mobile-templates-for-reading-on-go.html"&gt;いつの間にかβ版としてスマートフォン用テンプレートの機能が追加されてた&lt;/a&gt;ので有効にしてみた。&lt;br /&gt;&lt;br /&gt;draft.blogger.com の方からログインするとβ版機能が使えます。&lt;br /&gt;&lt;br /&gt;有効にするには、設定 ＞ メールとモバイル ＞ モバイル テンプレートを表示する で はい を選ぶだけ。スマートフォンの場合は自動でリダイレクトしてくれるようです。&lt;br /&gt;&lt;br /&gt;ガラケーからもurlの最後に?m=1を付けるとモバイル用テンプレートで表示してくれましたが、ページ遷移にjavascriptが使われているようで残念ながら記事一覧以降進めませんでした…。&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7854445846272488112-2279561979390358057?l=pomu0325.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://pomu0325.blogspot.com/feeds/2279561979390358057/comments/default' title='コメントの投稿'/><link rel='replies' type='text/html' href='http://pomu0325.blogspot.com/2011/01/blogger-smartphone-template.html#comment-form' title='0 件のコメント'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7854445846272488112/posts/default/2279561979390358057'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7854445846272488112/posts/default/2279561979390358057'/><link rel='alternate' type='text/html' href='http://pomu0325.blogspot.com/2011/01/blogger-smartphone-template.html' title='[Blogger] スマートフォン用テンプレートを有効にする'/><author><name>pomu0325</name><uri>http://www.blogger.com/profile/07153996876142338944</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://2.bp.blogspot.com/_IngpC7Xbw78/S3OLp5iNDyI/AAAAAAAAAIc/R5UH2AMmiAo/S220/RIMG0357.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7854445846272488112.post-540001932976413854</id><published>2011-01-12T16:04:00.004+09:00</published><updated>2011-02-07T21:23:58.708+09:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Lift'/><title type='text'>[Lift] Liftで処理したくないリクエストの設定</title><content type='html'>web.xmlで全てのリクエストがLiftで処理されるようになっているので、Lift以外のサーブレット等で処理したい場合に困ります…&lt;br /&gt;&lt;pre class="xml" name="code"&gt;&amp;lt;filter-mapping&amp;gt;&lt;br /&gt;  &amp;lt;filter-name&amp;gt;LiftFilter&amp;lt;/filter-name&amp;gt;&lt;br /&gt;  &amp;lt;url-pattern&amp;gt;/*&amp;lt;/url-pattern&amp;gt;&lt;br /&gt;&amp;lt;/filter-mapping&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Boot.scalaのdef bootにこんな感じで書けばOK。&lt;br /&gt;&lt;pre class="scala" name="code"&gt;LiftRules.passNotFoundToChain = true&lt;br /&gt;LiftRules.liftRequest.append {&lt;br /&gt;  case Req("_ah" :: _, _, _) =&amp;gt; false&lt;br /&gt;  case Req("remote_api" :: _, _, _) =&amp;gt; false&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;※filter-mappingで除外ルールが書ければいいんだけど…&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7854445846272488112-540001932976413854?l=pomu0325.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://pomu0325.blogspot.com/feeds/540001932976413854/comments/default' title='コメントの投稿'/><link rel='replies' type='text/html' href='http://pomu0325.blogspot.com/2011/01/lift-lift.html#comment-form' title='0 件のコメント'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7854445846272488112/posts/default/540001932976413854'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7854445846272488112/posts/default/540001932976413854'/><link rel='alternate' type='text/html' href='http://pomu0325.blogspot.com/2011/01/lift-lift.html' title='[Lift] Liftで処理したくないリクエストの設定'/><author><name>pomu0325</name><uri>http://www.blogger.com/profile/07153996876142338944</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://2.bp.blogspot.com/_IngpC7Xbw78/S3OLp5iNDyI/AAAAAAAAAIc/R5UH2AMmiAo/S220/RIMG0357.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7854445846272488112.post-933006122592139924</id><published>2011-01-01T18:50:00.005+09:00</published><updated>2011-01-01T20:19:39.562+09:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Scala'/><category scheme='http://www.blogger.com/atom/ns#' term='GAE'/><title type='text'>[Scala] sbt-appengineなプロジェクトをIntelliJ IDEAでデバッグ</title><content type='html'>&lt;a href="http://twitter.com/#!/teaplanet/status/20848674703675392"&gt;@teaplanetさんにヒントいただいてIDEAのsbt consoleでdev-appserver-startできるようになった&lt;/a&gt;ので、IDEA上でブレークポイントで止めたりできるように設定してみた。&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="scala"&gt;class MyProject(info: ProjectInfo) extends AppengineProject(info) {&lt;br /&gt;  //... 中略&lt;br /&gt;  //APPENGINE_SDK_HOME設定の代わり&lt;br /&gt;  override val appengineSdkPath = Path.fromFile("/Users/pomu0325/dev/appengine-java-sdk-1.4.0")  &lt;br /&gt;  &lt;br /&gt;  //dev-appserver-startの際のJVMオプション&lt;br /&gt;  override val devAppserverJvmOptions = List("-Xdebug", "-Xrunjdwp:transport=dt_socket,server=y,address=2011,suspend=y") ++ super.devAppserverJvmOptions &lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;この設定でsbt consoleからdev-appserver-startするとリモートデバッガで接続できる状態で起動するので、後はIDEAからデバッガ接続する。&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;&lt;a href="http://3.bp.blogspot.com/_IngpC7Xbw78/TR74ToQov2I/AAAAAAAAAeI/p9Gly1xM4b0/s1600/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588%25EF%25BC%25882011-01-01%2B18.45.38%25EF%25BC%2589.png" imageanchor="1" style="clear:left; float:left;margin-right:1em; margin-bottom:1em"&gt;&lt;img border="0" height="298" width="320" src="http://3.bp.blogspot.com/_IngpC7Xbw78/TR74ToQov2I/AAAAAAAAAeI/p9Gly1xM4b0/s320/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588%25EF%25BC%25882011-01-01%2B18.45.38%25EF%25BC%2589.png" /&gt;&lt;/a&gt;IDEAのメニューからRun -&gt; Edit Configurations -&gt; [+] -&gt; Remote を選んで、Nameは適当に、PortにdevAppserverJvmOptionsのaddress=xxxxで指定したポートを入れる(↑の例だと2011。適当に開いてるポート設定すればOK)。&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;設定したら、Run -&gt; Debug... で選んでデバッグ開始するとdev-appserverのJVMにアタッチされてブレークポイントで止まってくれる。&lt;br /&gt;&lt;br /&gt;※Before launchの"Run sbt action"でdev-appserver-startが指定できればsbt consoleからいちいちdev-appserver-startしなくても一発で起動できそうだけど一覧に出てこないし、直接指定してもダメでした…&lt;br /&gt;&lt;br /&gt;参考：sbt-appengineの基本的な設定等は"&lt;a href="http://kaitenn.blogspot.com/2010/10/sbt-sbtgae.html"&gt;sbtでGAEをする場合の注意点など色々。&lt;/a&gt;"が参考になります。&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7854445846272488112-933006122592139924?l=pomu0325.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://pomu0325.blogspot.com/feeds/933006122592139924/comments/default' title='コメントの投稿'/><link rel='replies' type='text/html' href='http://pomu0325.blogspot.com/2011/01/scala-sbt-appengineintellij-idea.html#comment-form' title='0 件のコメント'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7854445846272488112/posts/default/933006122592139924'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7854445846272488112/posts/default/933006122592139924'/><link rel='alternate' type='text/html' href='http://pomu0325.blogspot.com/2011/01/scala-sbt-appengineintellij-idea.html' title='[Scala] sbt-appengineなプロジェクトをIntelliJ IDEAでデバッグ'/><author><name>pomu0325</name><uri>http://www.blogger.com/profile/07153996876142338944</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://2.bp.blogspot.com/_IngpC7Xbw78/S3OLp5iNDyI/AAAAAAAAAIc/R5UH2AMmiAo/S220/RIMG0357.JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_IngpC7Xbw78/TR74ToQov2I/AAAAAAAAAeI/p9Gly1xM4b0/s72-c/%25E3%2582%25B9%25E3%2582%25AF%25E3%2583%25AA%25E3%2583%25BC%25E3%2583%25B3%25E3%2582%25B7%25E3%2583%25A7%25E3%2583%2583%25E3%2583%2588%25EF%25BC%25882011-01-01%2B18.45.38%25EF%25BC%2589.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7854445846272488112.post-5575718405505911918</id><published>2010-12-31T17:00:00.000+09:00</published><updated>2010-12-31T17:00:22.220+09:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Lift'/><title type='text'>[Lift] Lift2.2でCookieが無い場合にjsessionidがリンクに付加されない</title><content type='html'>&lt;a href="http://www.assembla.com/spaces/liftweb/tickets/658"&gt;#658 Supressing URL rewriting for jesessionid from Lift&lt;/a&gt; の修正により、デフォルトでjsessionidが付かないように動作が変わっています。&lt;br /&gt;&lt;br /&gt;Boot.scalaで、&lt;br /&gt;&lt;pre name="code" class="scala"&gt;LiftRules.encodeJSessionIdInUrl_? = true&lt;br /&gt;&lt;/pre&gt;してやればOK。&lt;br /&gt;&lt;br /&gt;Liftで携帯サイト作ってる人とかはアップデートの際に要注意！&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7854445846272488112-5575718405505911918?l=pomu0325.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://pomu0325.blogspot.com/feeds/5575718405505911918/comments/default' title='コメントの投稿'/><link rel='replies' type='text/html' href='http://pomu0325.blogspot.com/2010/12/lift-lift22cookiejsessionid.html#comment-form' title='0 件のコメント'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7854445846272488112/posts/default/5575718405505911918'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7854445846272488112/posts/default/5575718405505911918'/><link rel='alternate' type='text/html' href='http://pomu0325.blogspot.com/2010/12/lift-lift22cookiejsessionid.html' title='[Lift] Lift2.2でCookieが無い場合にjsessionidがリンクに付加されない'/><author><name>pomu0325</name><uri>http://www.blogger.com/profile/07153996876142338944</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://2.bp.blogspot.com/_IngpC7Xbw78/S3OLp5iNDyI/AAAAAAAAAIc/R5UH2AMmiAo/S220/RIMG0357.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7854445846272488112.post-8760372632393863611</id><published>2010-12-30T16:53:00.002+09:00</published><updated>2010-12-30T17:00:24.578+09:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Scala'/><title type='text'>[Scala] dispatchでContent-Length付きのmultipartなPOSTする方法</title><content type='html'>とある写真投稿サービスのAPIにdispatch(0.7.8)を使って実装しようとした時のこと。APIのレスポンスでContent-Lengthが無いって怒られました。&lt;br /&gt;&lt;br /&gt;dispatch-mimeを使うとmultipart/form-data形式でPOSTができるのですが、普通に実装するとContent-Lengthを送ってくれないようです。&lt;br /&gt;&lt;br /&gt;&lt;a href="http://sourced.implicit.ly/net.databinder/dispatch-mime/0.7.8/dispatch/Mime.scala.html"&gt;ソースコメント&lt;/a&gt;にも書いてあるし。&lt;br /&gt;&lt;blockquote&gt;Note that when using an InputStream generator, chuncked encoding will be used with no Content-Length header &lt;br /&gt;&lt;/blockquote&gt;&lt;br /&gt;Fileが引数の方を使えば良さげだけどGAEからだとFile使えません。dispatchのソースとapache-httpclientのjavadoc眺めて工夫したらできました。&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="scala"&gt;import dispatch._&lt;br /&gt;import dispatch.Http._&lt;br /&gt;import dispatch.mime.Mime._&lt;br /&gt;import org.apache.http.entity.mime.content.ByteArrayBody&lt;br /&gt;&lt;br /&gt;var req = ... //普通にdispatchのRequest作る&lt;br /&gt;req = req next req.add(paramName, new ByteArrayBody(data, contentType, filename)) //ByteArrayBodyを指定&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;※ただし、&lt;a href="http://hc.apache.org/httpcomponents-client-dev/httpmime/apidocs/org/apache/http/entity/mime/content/ByteArrayBody.html"&gt;ByteArrayBody&lt;/a&gt;はhttpclient 4.1(現在BETA1)から追加されるクラスなので4.1のjarを使わないとダメ（dispatch 0.7.8のdependencyはhttpclient 4.0になってた）。&lt;br /&gt;sbtならこんな感じで。&lt;br /&gt;&lt;pre name="code" class="scala"&gt;val httpmime = "org.apache.httpcomponents" % "httpmime" % "4.1-beta1"&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7854445846272488112-8760372632393863611?l=pomu0325.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://pomu0325.blogspot.com/feeds/8760372632393863611/comments/default' title='コメントの投稿'/><link rel='replies' type='text/html' href='http://pomu0325.blogspot.com/2010/12/scala-dispatchcontent-lengthpost.html#comment-form' title='0 件のコメント'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7854445846272488112/posts/default/8760372632393863611'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7854445846272488112/posts/default/8760372632393863611'/><link rel='alternate' type='text/html' href='http://pomu0325.blogspot.com/2010/12/scala-dispatchcontent-lengthpost.html' title='[Scala] dispatchでContent-Length付きのmultipartなPOSTする方法'/><author><name>pomu0325</name><uri>http://www.blogger.com/profile/07153996876142338944</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://2.bp.blogspot.com/_IngpC7Xbw78/S3OLp5iNDyI/AAAAAAAAAIc/R5UH2AMmiAo/S220/RIMG0357.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7854445846272488112.post-2371302853140695495</id><published>2010-09-06T01:20:00.000+09:00</published><updated>2010-09-06T01:20:49.318+09:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Scala'/><category scheme='http://www.blogger.com/atom/ns#' term='Lift'/><category scheme='http://www.blogger.com/atom/ns#' term='GAE'/><title type='text'>[Lift][GAE] Scala座01でLTしてきました</title><content type='html'>&lt;a href="http://scalaza.com/ScalaZa01/"&gt;第1回Scala座&lt;/a&gt;に参加してきました。実はスタッフとして少々お手伝いもしたのですが大した事ないので（ry&lt;br /&gt;&lt;br /&gt;収穫は…&lt;br /&gt;&lt;ul&gt;&lt;li&gt;いろいろと勉強不足だなぁと刺激を受けた（FP的な意味で）&lt;/li&gt;&lt;li&gt;Twitter等で気になっていた人々にリアルで会えた&lt;/li&gt;&lt;li&gt;ワラビモチ！←マイブーム&lt;/li&gt;&lt;/ul&gt;といったとこでしょうか。&lt;br /&gt;&lt;br /&gt;「Lift on GAE/J」ネタでLTさせてもらったのですが、先にまとめてしまうと、「LiftはGoogle AppEngineには向いてない」てことです。詳しくは&lt;a href="http://www.slideshare.net/pomu0325/lift-on-gaej"&gt;「Lift on GAE/J」のスライド&lt;/a&gt;見てください。&lt;br /&gt;&lt;br /&gt;LTやろうと思ったきっかけは、自分でLiftも(Scalaも)GAEもよく分かってないうちにLift+GAEという組み合わせを選んでしまったのですが、いろいろと苦労したので… 最近TwitterでLiftをGAEで動かしてみるとかのtweetを時々見かけ、危険だよー、ということを広めたかっただけです。&lt;br /&gt;&lt;br /&gt;ただ、勘違いしないで欲しいのは、Liftがダメと言っている訳ではなく、LiftはフルスタックのWebフレームワークとして、慣れれば生産性よくAJAX/Cometを使ったリッチなWebアプリを作れる素晴らしいフレームワークだと思っています。クラウドという性質上、GAEには様々な制限があるので、GAE上で動かしたいのであれば他のもう少し軽量なフレームワークが向いていると思います（まだあまり試せていませんが…）。&lt;br /&gt;&lt;br /&gt;Scala座の企画をして下さった&lt;a href="http://twitter.com/keisuke_n"&gt;@keisuke_n&lt;/a&gt;さん、Scalaを勉強していく上で参考にさせていただいたり助けていただいたりで本当にいろいろとお世話になっている&lt;a href="http://twitter.com/yuroyoro"&gt;@yuroyoro&lt;/a&gt;さん、&lt;a href="http://twitter.com/kmizu"&gt;@kmizu&lt;/a&gt;さん、&lt;a href="http://twitter.com/takedasoft"&gt;@takedasoft&lt;/a&gt;さん、会場・懇親会でお話させていただいたみなさま、本当にありがとうございました。ぜひ第2回でもよろしくお願いします！&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7854445846272488112-2371302853140695495?l=pomu0325.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://pomu0325.blogspot.com/feeds/2371302853140695495/comments/default' title='コメントの投稿'/><link rel='replies' type='text/html' href='http://pomu0325.blogspot.com/2010/09/liftgae-scala01lt.html#comment-form' title='0 件のコメント'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7854445846272488112/posts/default/2371302853140695495'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7854445846272488112/posts/default/2371302853140695495'/><link rel='alternate' type='text/html' href='http://pomu0325.blogspot.com/2010/09/liftgae-scala01lt.html' title='[Lift][GAE] Scala座01でLTしてきました'/><author><name>pomu0325</name><uri>http://www.blogger.com/profile/07153996876142338944</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://2.bp.blogspot.com/_IngpC7Xbw78/S3OLp5iNDyI/AAAAAAAAAIc/R5UH2AMmiAo/S220/RIMG0357.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7854445846272488112.post-7940288556724083989</id><published>2010-05-15T22:41:00.004+09:00</published><updated>2011-07-04T17:54:18.375+09:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='OAuth'/><category scheme='http://www.blogger.com/atom/ns#' term='Twitter'/><title type='text'>[OAuth] 携帯でTwitterのOAuthを無理やりやってみた</title><content type='html'>&lt;b&gt;2011-07-04追記： 今はTwitterのOAuth画面もガラケー対応されてます。自動でリダイレクトはされないのでユーザによるクリックは相変わらず必要ですが。&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.mb4sq.jp/"&gt;モバイルフォースクエア&lt;/a&gt;の開発初期に、OAuthでTwitterの認証をやろうとしていた時に試した結果です。手元にあったAU,docomoの端末で確認しています。&lt;br /&gt;&lt;h4&gt;AU(W61CA)でTwitterのOAuthをやってみた&lt;/h4&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;th&gt;ログイン画面&lt;/th&gt; &lt;th&gt;承認画面&lt;/th&gt; &lt;/tr&gt;&lt;tr&gt; &lt;td&gt;&lt;a href="http://1.bp.blogspot.com/_IngpC7Xbw78/S-6gEadv-xI/AAAAAAAAAN0/gvviZI0-zTU/s1600/w61ca-tw-login2.JPG"&gt;&lt;img border="0" src="http://1.bp.blogspot.com/_IngpC7Xbw78/S-6gEadv-xI/AAAAAAAAAN0/gvviZI0-zTU/s320/w61ca-tw-login2.JPG" /&gt;&lt;/a&gt;&lt;/td&gt; &lt;td&gt;&lt;a href="http://1.bp.blogspot.com/_IngpC7Xbw78/S-6gLMIYHHI/AAAAAAAAAN8/ozoVGY3Xz00/s1600/w61ca-tw-done.JPG"&gt;&lt;img border="0" src="http://1.bp.blogspot.com/_IngpC7Xbw78/S-6gLMIYHHI/AAAAAAAAAN8/ozoVGY3Xz00/s320/w61ca-tw-done.JPG" /&gt;&lt;/a&gt;&lt;/td&gt; &lt;/tr&gt;&lt;tr&gt; &lt;td&gt;特に問題なし！&lt;/td&gt; &lt;td&gt;自動でリダイレクトはされないが、リンクを手動でクリックすれば動作はOK！&lt;/td&gt; &lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;&lt;h4&gt;docomo Cookie対応機種(N-07A)でTwitterのOAuthをやってみた&lt;/h4&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;th&gt;ログイン画面&lt;/th&gt; &lt;th&gt;承認画面&lt;/th&gt; &lt;/tr&gt;&lt;tr&gt; &lt;td&gt;&lt;a href="http://3.bp.blogspot.com/_IngpC7Xbw78/S-6i4rKIxaI/AAAAAAAAAOE/Gn6ZPWOBI_U/s1600/n07a-tw-login2.JPG"&gt;&lt;img border="0" src="http://3.bp.blogspot.com/_IngpC7Xbw78/S-6i4rKIxaI/AAAAAAAAAOE/Gn6ZPWOBI_U/s320/n07a-tw-login2.JPG" /&gt;&lt;/a&gt;&lt;/td&gt; &lt;td&gt;&lt;a href="http://3.bp.blogspot.com/_IngpC7Xbw78/S-6jKA2mXXI/AAAAAAAAAOM/Nhdl1StPe3A/s1600/n07a-tw-done.JPG"&gt;&lt;img border="0" src="http://3.bp.blogspot.com/_IngpC7Xbw78/S-6jKA2mXXI/AAAAAAAAAOM/Nhdl1StPe3A/s320/n07a-tw-done.JPG" /&gt;&lt;/a&gt;&lt;/td&gt; &lt;/tr&gt;&lt;tr&gt; &lt;td&gt;文字化けがひどいが、ID・パスワード欄っぽいところに入れて1つ目のボタン（ログイン）を押せば承認画面に飛ぶ。&lt;/td&gt; &lt;td&gt;自動でリダイレクトはされないが、リンクを手動でクリックすれば動作はOK！&lt;/td&gt; &lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;&lt;h4&gt;docomo Cookie非対応機種(SH704i)でTwitterのOAuthをやってみた&lt;/h4&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;th&gt;ログイン画面&lt;/th&gt; &lt;th&gt;承認画面&lt;/th&gt; &lt;/tr&gt;&lt;tr&gt; &lt;td&gt;&lt;a href="http://1.bp.blogspot.com/_IngpC7Xbw78/S-6kBikWE5I/AAAAAAAAAOU/-RS-2UQvivI/s1600/sh704i-tw-login.JPG"&gt;&lt;img border="0" src="http://1.bp.blogspot.com/_IngpC7Xbw78/S-6kBikWE5I/AAAAAAAAAOU/-RS-2UQvivI/s320/sh704i-tw-login.JPG" /&gt;&lt;/a&gt;&lt;/td&gt; &lt;td&gt;&lt;a href="http://3.bp.blogspot.com/_IngpC7Xbw78/S-6kKLcGBSI/AAAAAAAAAOc/3gSaKmcizrY/s1600/sh704i-tw-done.JPG"&gt;&lt;img border="0" src="http://3.bp.blogspot.com/_IngpC7Xbw78/S-6kKLcGBSI/AAAAAAAAAOc/3gSaKmcizrY/s320/sh704i-tw-done.JPG" /&gt;&lt;/a&gt;&lt;/td&gt; &lt;/tr&gt;&lt;tr&gt; &lt;td&gt;両方とも送信ボタン…。1つ目を押せば承認画面に飛ぶ。&lt;/td&gt; &lt;td&gt;文字化けがひどいが、リンクを手動でクリックすれば動作はOK！&lt;/td&gt; &lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;&lt;h4&gt;まとめ&lt;/h4&gt;　docomoだと文字化けがひどいが、OAuthの機能的には動作する。ただし最後のリダイレクトを手動でクリックしないといけないのでユーザにこれを強いるのは難しい。&lt;br /&gt;※実際、OAuth開始前の画面に注意書きを入れたにもかかわらず、最後のリダイレクトのクリックが分からず、トークンが取得できていないユーザ多数いた&lt;br /&gt;　PIN入力方式にすればよいが、PC・スマートフォンでも同じコンシューマキーを使いたいので、PC・スマートフォンでのユーザビリティが落ちるので却下。PCでOAuth認証だけ先にさせるのも携帯のみのユーザのことを考えると却下。&lt;br /&gt;→結果、xAuthを使うことにした。&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7854445846272488112-7940288556724083989?l=pomu0325.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://pomu0325.blogspot.com/feeds/7940288556724083989/comments/default' title='コメントの投稿'/><link rel='replies' type='text/html' href='http://pomu0325.blogspot.com/2010/05/oauth-twitteroauth.html#comment-form' title='0 件のコメント'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7854445846272488112/posts/default/7940288556724083989'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7854445846272488112/posts/default/7940288556724083989'/><link rel='alternate' type='text/html' href='http://pomu0325.blogspot.com/2010/05/oauth-twitteroauth.html' title='[OAuth] 携帯でTwitterのOAuthを無理やりやってみた'/><author><name>pomu0325</name><uri>http://www.blogger.com/profile/07153996876142338944</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://2.bp.blogspot.com/_IngpC7Xbw78/S3OLp5iNDyI/AAAAAAAAAIc/R5UH2AMmiAo/S220/RIMG0357.JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_IngpC7Xbw78/S-6gEadv-xI/AAAAAAAAAN0/gvviZI0-zTU/s72-c/w61ca-tw-login2.JPG' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7854445846272488112.post-8226752926286962499</id><published>2010-05-15T22:19:00.005+09:00</published><updated>2010-05-15T23:25:26.331+09:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='OAuth'/><category scheme='http://www.blogger.com/atom/ns#' term='foursquare'/><title type='text'>[OAuth] 携帯でfoursquareのOAuthを無理やりやってみた</title><content type='html'>&lt;a href="http://www.mb4sq.jp/"&gt;モバイルフォースクエア&lt;/a&gt;の開発初期に、OAuthでfoursquareの認証をやろうとしていた時に試した結果です。手元にあったAU,docomoの端末で確認しています。&lt;br /&gt;&lt;h4&gt;AU(W61CA)でfoursquareのOAuthをやってみた&lt;/h4&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt; &lt;th&gt;ログイン画面&lt;/th&gt; &lt;th&gt;承認画面&lt;/th&gt; &lt;/tr&gt;&lt;tr&gt; &lt;td&gt;&lt;a href="http://4.bp.blogspot.com/_IngpC7Xbw78/S-6ZdSwmKrI/AAAAAAAAAM8/A2Unl5Z7vLA/s1600/w61ca-4sq-login.JPG"&gt;&lt;img border="0" src="http://4.bp.blogspot.com/_IngpC7Xbw78/S-6ZdSwmKrI/AAAAAAAAAM8/A2Unl5Z7vLA/s320/w61ca-4sq-login.JPG" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a href="http://4.bp.blogspot.com/_IngpC7Xbw78/S-6Zfi9JNHI/AAAAAAAAANE/Vkbi2IX6WWc/s1600/w61ca-4sq-allow.JPG"&gt;&lt;img border="0" src="http://4.bp.blogspot.com/_IngpC7Xbw78/S-6Zfi9JNHI/AAAAAAAAANE/Vkbi2IX6WWc/s320/w61ca-4sq-allow.JPG" /&gt;&lt;/a&gt;&lt;/td&gt; &lt;/tr&gt;&lt;tr&gt; &lt;td&gt;日本語文字化け…&lt;/td&gt; &lt;td&gt;文字化け＆Denyが押せないが、動作はOK！&lt;/td&gt; &lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;&lt;h4&gt;docomo Cookie対応機種(N-07A)でfoursquareのOAuthをやってみた&lt;/h4&gt;&lt;table&gt;&lt;tr&gt;&lt;th&gt;ログイン画面&lt;/th&gt; &lt;th&gt;承認画面&lt;/th&gt; &lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;a href="http://2.bp.blogspot.com/_IngpC7Xbw78/S-6cu__KY6I/AAAAAAAAANc/LHihPRc1q_g/s1600/n07a-4sq-login.JPG"&gt;&lt;img border="0" src="http://2.bp.blogspot.com/_IngpC7Xbw78/S-6cu__KY6I/AAAAAAAAANc/LHihPRc1q_g/s320/n07a-4sq-login.JPG" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/td&gt; &lt;td&gt;&lt;a href="http://3.bp.blogspot.com/_IngpC7Xbw78/S-6dQ2aToZI/AAAAAAAAANk/vV5WJ_N32ec/s1600/n07a-4sq-allow.JPG"&gt;&lt;img border="0" src="http://3.bp.blogspot.com/_IngpC7Xbw78/S-6dQ2aToZI/AAAAAAAAANk/vV5WJ_N32ec/s320/n07a-4sq-allow.JPG" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/td&gt; &lt;/tr&gt;&lt;tr&gt; &lt;td&gt;デザインは崩れるが、動作はOK!&lt;/td&gt; &lt;td&gt;デザインは崩れるが、動作はOK!&lt;/td&gt; &lt;/tr&gt;&lt;/table&gt;&lt;br /&gt;&lt;h4&gt;docomo Cookie非対応機種(SH704i)でfoursquareのOAuthをやってみた&lt;/h4&gt;ログイン画面→表示が崩れ、ログインボタンを押しても同じ画面が繰り返し表示されるだけ…。&lt;br /&gt;※当時はログインできていた記憶が…、サーバ側の実装が変わったかもしれないです&lt;br /&gt;&lt;a href="http://4.bp.blogspot.com/_IngpC7Xbw78/S-6eqFIXdoI/AAAAAAAAANs/gwCKxhYW7MI/s1600/sh704i-4sq-login.JPG"&gt;&lt;img border="0" src="http://4.bp.blogspot.com/_IngpC7Xbw78/S-6eqFIXdoI/AAAAAAAAANs/gwCKxhYW7MI/s320/sh704i-4sq-login.JPG" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;h4&gt;まとめ&lt;/h4&gt;Cookie対応機なら文字化けしたりデザインは崩れるが、OAuthの処理自体は正常。文字化けがひどい状態ではサービス提供できないので、foursquareの中の人に日本の携帯ブラウザ事情を説明し、&lt;a href="http://groups.google.com/group/foursquare-api/web/oauth"&gt;Auth Exchange&lt;/a&gt;採用。&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7854445846272488112-8226752926286962499?l=pomu0325.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://pomu0325.blogspot.com/feeds/8226752926286962499/comments/default' title='コメントの投稿'/><link rel='replies' type='text/html' href='http://pomu0325.blogspot.com/2010/05/oauth-foursquareoauth.html#comment-form' title='0 件のコメント'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7854445846272488112/posts/default/8226752926286962499'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7854445846272488112/posts/default/8226752926286962499'/><link rel='alternate' type='text/html' href='http://pomu0325.blogspot.com/2010/05/oauth-foursquareoauth.html' title='[OAuth] 携帯でfoursquareのOAuthを無理やりやってみた'/><author><name>pomu0325</name><uri>http://www.blogger.com/profile/07153996876142338944</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://2.bp.blogspot.com/_IngpC7Xbw78/S3OLp5iNDyI/AAAAAAAAAIc/R5UH2AMmiAo/S220/RIMG0357.JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_IngpC7Xbw78/S-6ZdSwmKrI/AAAAAAAAAM8/A2Unl5Z7vLA/s72-c/w61ca-4sq-login.JPG' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7854445846272488112.post-5465399327605215615</id><published>2010-04-27T13:16:00.004+09:00</published><updated>2010-06-26T21:19:18.326+09:00</updated><title type='text'>[Scala][GAE] dispatchのOAuthをGAEで使う</title><content type='html'>dispatchが内部でcommons-httpclientを使っていて、GAEでの禁止クラスを使っているのでそのままだとエラーになった。ので無理やりimplicit defでURLFetchのHTTPRequestに変換することで回避してます。こんな感じ。&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="scala"&gt;import dispatch._&lt;br /&gt;import com.google.appengine.api.urlfetch._&lt;br /&gt;import java.net.URL&lt;br /&gt;&lt;br /&gt;object DispatchHelper {&lt;br /&gt;  /**&lt;br /&gt;   * convert dispatch.Request -&gt; String&lt;br /&gt;   */&lt;br /&gt;  implicit def r2s(r: Request) = {&lt;br /&gt;    r.host.get + r.req.getRequestLine.getUri&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  /**&lt;br /&gt;   * convert dispatch.Request -&amp;gt; com.google.appengine.api.urlfetch.HTTPRequest&lt;br /&gt;   */&lt;br /&gt;  implicit def dispatch2gae(r: Request): HTTPRequest = {&lt;br /&gt;   val isPost = r.req.getMethod == "POST"&lt;br /&gt;   val g = new HTTPRequest(new URL(r), if (isPost) HTTPMethod.POST else HTTPMethod.GET, FetchOptions.Builder.withDeadline(10))&lt;br /&gt;   r.req.getAllHeaders.foreach(h =&amp;gt; {g.addHeader(h); println(h)})&lt;br /&gt;   r.req match {&lt;br /&gt;    case p: org.apache.http.client.methods.HttpPost =&amp;gt;&lt;br /&gt;     val payload = new java.io.ByteArrayOutputStream&lt;br /&gt;     p.getEntity.writeTo(payload)&lt;br /&gt;     g.setPayload(payload.toByteArray)&lt;br /&gt;     println(p.getEntity.getContentType)&lt;br /&gt;    case _ =&amp;gt;&lt;br /&gt;   }&lt;br /&gt;   g&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;こんな感じでimplicit defを宣言してimport DispatchHelper._すれば、dispatchでリクエストを作って、URLFetchService.fetch()の引数にそのまま渡せばOK。&lt;br /&gt;&lt;br /&gt;&lt;a href="http://pomu0325.blogspot.com/2010/02/scala-scalaoauth.html"&gt;dispatchの解説はこちら&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;h4&gt;2010-06-26追記&lt;/h4&gt;&lt;a href="http://pc12.2ch.net/test/read.cgi/tech/1275891974/199n-" target="_blank"&gt;2chで質問&lt;/a&gt;受けてコードが一部足りなかったので追記しました。&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7854445846272488112-5465399327605215615?l=pomu0325.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://pomu0325.blogspot.com/feeds/5465399327605215615/comments/default' title='コメントの投稿'/><link rel='replies' type='text/html' href='http://pomu0325.blogspot.com/2010/04/scalagae-dispatchoauthgae.html#comment-form' title='0 件のコメント'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7854445846272488112/posts/default/5465399327605215615'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7854445846272488112/posts/default/5465399327605215615'/><link rel='alternate' type='text/html' href='http://pomu0325.blogspot.com/2010/04/scalagae-dispatchoauthgae.html' title='[Scala][GAE] dispatchのOAuthをGAEで使う'/><author><name>pomu0325</name><uri>http://www.blogger.com/profile/07153996876142338944</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://2.bp.blogspot.com/_IngpC7Xbw78/S3OLp5iNDyI/AAAAAAAAAIc/R5UH2AMmiAo/S220/RIMG0357.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7854445846272488112.post-7120090001015866900</id><published>2010-03-25T20:04:00.010+09:00</published><updated>2010-03-25T23:53:52.130+09:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Scala'/><title type='text'>[Scala] Elemのlabel（要素名）を書き換える</title><content type='html'>こんな感じか？&lt;br /&gt;&lt;pre name="code" class="scala"&gt;def changeTagName[T &amp;gt;: Node](from: String, to: String)(in: T): T = in match {&lt;br /&gt;  case Elem(p, l, a, ns, child @ _*) =&amp;gt; &lt;br /&gt;    val label = if (l == from) to else l&lt;br /&gt;    Elem(p, label, a, ns, child.map(changeTagName(from, to)(_)): _*)&lt;br /&gt;  case x =&amp;gt; x&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h4&gt;使用例&lt;/h4&gt;&lt;pre name="code" class="scala"&gt;scala&amp;gt; val hoge2moge = changeTagName("hoge", "moge") _&lt;br /&gt;scala&amp;gt; hoge2moge: (scala.xml.Node) =&gt; scala.xml.Node = &amp;lt;function&amp;gt;&lt;br /&gt;&lt;br /&gt;scala&amp;gt; hoge2moge(&amp;lt;pre:hoge attr="value"&amp;gt;parentText&amp;lt;child:hoge&amp;gt;childtext&amp;lt;/child:hoge&amp;gt;&amp;lt;/pre:hoge&amp;gt;)&lt;br /&gt;scala&amp;gt; res17: scala.xml.Node = &amp;lt;pre:moge attr="value"&amp;gt;parentText&amp;lt;child:moge&amp;gt;childtext&amp;lt;/child:moge&amp;gt;&amp;lt;/pre:moge&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h4&gt;追記：さらに汎用化してみた&lt;/h4&gt;高階関数化して、引数に名前を変更するための関数を渡せるようにした。&lt;br /&gt;&lt;pre name="code" class="scala"&gt;def changeTagName[T &amp;gt;: Node](changeFunc: (String) =&amp;gt; String)(in: T): T = in match {  &lt;br /&gt;  case Elem(p, l, a, ns, child @ _*) =&amp;gt;   &lt;br /&gt;    Elem(p, changeFunc(l), a, ns, child.map(changeTagName(changeFunc)(_)): _*)  &lt;br /&gt;  case x =&amp;gt; x  &lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h4&gt;使用例&lt;/h4&gt;&lt;a href="http://kaitenn.blogspot.com/2010/03/scala-elemlabel.html"&gt;お題はこちら&lt;/a&gt;。&lt;br /&gt;&lt;pre name="code" class="scala"&gt;scala&amp;gt; val c = changeTagName(s =&amp;gt; if (List("moge", "koge").contains(s)) "bege" else s) _&lt;br /&gt;c: (scala.xml.Node) =&amp;gt; scala.xml.Node = &amp;lt;function&amp;gt;&lt;br /&gt;&lt;br /&gt;scala&amp;gt; c(x)&lt;br /&gt;res6: scala.xml.Node =&lt;br /&gt;&amp;lt;lage&amp;gt;&lt;br /&gt;         &amp;lt;hoge&amp;gt;&lt;br /&gt;           &amp;lt;bege&amp;gt;&amp;lt;/bege&amp;gt;&lt;br /&gt;           &amp;lt;bege&amp;gt;&amp;lt;/bege&amp;gt;&lt;br /&gt;         &amp;lt;/hoge&amp;gt;&lt;br /&gt;         &amp;lt;hoge&amp;gt;&lt;br /&gt;           &amp;lt;bege&amp;gt;&amp;lt;/bege&amp;gt;&lt;br /&gt;           &amp;lt;bege&amp;gt;&amp;lt;/bege&amp;gt;&lt;br /&gt;         &amp;lt;/hoge&amp;gt;&lt;br /&gt;       &amp;lt;/lage&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h4&gt;23:52 さらにちょっと修正&lt;/h4&gt;flatMap使えば別にNodeSeq渡せることに気がついた。&lt;br /&gt;&lt;pre name="code" class="scala"&gt;def changeTagName(changeFunc: (String) =&amp;gt; String)(in: NodeSeq): NodeSeq = in match {  &lt;br /&gt;  case Elem(p, l, a, ns, child @ _*) =&amp;gt;   &lt;br /&gt;    Elem(p, changeFunc(l), a, ns, child.flatMap(changeTagName(changeFunc)(_)): _*)  &lt;br /&gt;  case x =&amp;gt; x  &lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7854445846272488112-7120090001015866900?l=pomu0325.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://pomu0325.blogspot.com/feeds/7120090001015866900/comments/default' title='コメントの投稿'/><link rel='replies' type='text/html' href='http://pomu0325.blogspot.com/2010/03/scala-elemlabel.html#comment-form' title='0 件のコメント'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7854445846272488112/posts/default/7120090001015866900'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7854445846272488112/posts/default/7120090001015866900'/><link rel='alternate' type='text/html' href='http://pomu0325.blogspot.com/2010/03/scala-elemlabel.html' title='[Scala] Elemのlabel（要素名）を書き換える'/><author><name>pomu0325</name><uri>http://www.blogger.com/profile/07153996876142338944</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://2.bp.blogspot.com/_IngpC7Xbw78/S3OLp5iNDyI/AAAAAAAAAIc/R5UH2AMmiAo/S220/RIMG0357.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7854445846272488112.post-5776346151350511096</id><published>2010-03-22T00:50:00.007+09:00</published><updated>2010-03-22T00:59:31.761+09:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Lift'/><category scheme='http://www.blogger.com/atom/ns#' term='GAE'/><title type='text'>[Lift][GAE] spin-up時間を測定してみる</title><content type='html'>&lt;a href="http://d.hatena.ne.jp/yuroyoro/20100321/1269128061" target="_blank"&gt;@yuroyoroさんがGAE上のScalaのスピンアップ時間を測定していた&lt;/a&gt;ので、倣ってLiftのspin-up時間を測定してみた。前は数回適当に起動した時間を見ていて7-8秒って感じだったのを、もうちょっと正確に。&lt;br /&gt;&lt;br /&gt;測定したのはarchetype:blankで生成される、LiftテンプレートとSnippetを使用したHelloWorld。また、不要なjarを減らしたら早くなるのかも気になったのでいくつかパターンを用意してみた。&lt;br /&gt;&lt;h4&gt;結果&lt;/h4&gt;&lt;table border="1" style="border-collapse: collapse;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;th&gt;app&lt;/th&gt;&lt;th&gt;ave&lt;/th&gt;&lt;th&gt;max&lt;/th&gt;&lt;th&gt;min&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;lift-blank *1&lt;/td&gt;&lt;td&gt;6829&lt;/td&gt;&lt;td&gt;7338&lt;/td&gt;&lt;td&gt;5938&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;nomapper *2&lt;/td&gt;&lt;td&gt;6681&lt;/td&gt;&lt;td&gt;7105&lt;/td&gt;&lt;td&gt;6035&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;nomapper/nojson *3&lt;/td&gt;&lt;td&gt;6531&lt;/td&gt;&lt;td&gt;7124&lt;/td&gt;&lt;td&gt;5705&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;min *4&lt;/td&gt;&lt;td&gt;6481&lt;/td&gt;&lt;td&gt;7240&lt;/td&gt;&lt;td&gt;5957&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;ol&gt;&lt;li&gt;Lift2.0-M3のarchetype-blankをそのままGAEに載せたもの&lt;/li&gt;&lt;li&gt;GAE上ではあまり使わないと思われるlift-mapperのjarを取り除いたもの&lt;/li&gt;&lt;li&gt;↑からさらにlift-jsonのjarを取り除いたもの&lt;/li&gt;&lt;li&gt;↑からさらにHelloWorldには不要なjar（javax.mail, commons-codec）を全て取り除いたもの&lt;/li&gt;&lt;/ol&gt;&lt;h4&gt;考察&lt;/h4&gt;&lt;ul&gt;&lt;li&gt;簡単なLiftテンプレート+Snippetだと、Lift単品では7秒弱。&lt;/li&gt;&lt;li&gt;maxとminが前後しているのは気になるが、平均で見ると不要なjarを取り除くと若干早くなるっぽい。&lt;br /&gt;※前測って7-8秒だったのは、Lift以外のライブラリのjarがあったのでさらにオーバーヘッドがかかったのか？&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h4&gt;ほか気づいたこと&lt;/h4&gt;Liftがリクエストの処理にかかった時間をログに出してくれるのだが、初回がやたら遅くて3秒ほどかかっているのでこれがspin-upの半分を占めている。2回目のリクエストからは数ms…。&lt;br /&gt;&lt;pre&gt;INFO - Service request (GET) / took 3056 Milliseconds&lt;/pre&gt;次はこれを調べてみることにする。&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7854445846272488112-5776346151350511096?l=pomu0325.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://pomu0325.blogspot.com/feeds/5776346151350511096/comments/default' title='コメントの投稿'/><link rel='replies' type='text/html' href='http://pomu0325.blogspot.com/2010/03/liftgae-spin-up.html#comment-form' title='0 件のコメント'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7854445846272488112/posts/default/5776346151350511096'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7854445846272488112/posts/default/5776346151350511096'/><link rel='alternate' type='text/html' href='http://pomu0325.blogspot.com/2010/03/liftgae-spin-up.html' title='[Lift][GAE] spin-up時間を測定してみる'/><author><name>pomu0325</name><uri>http://www.blogger.com/profile/07153996876142338944</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://2.bp.blogspot.com/_IngpC7Xbw78/S3OLp5iNDyI/AAAAAAAAAIc/R5UH2AMmiAo/S220/RIMG0357.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7854445846272488112.post-3452001089503059642</id><published>2010-03-17T13:57:00.008+09:00</published><updated>2010-03-26T10:45:25.513+09:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='GAE'/><title type='text'>[GAE] JavaでAppstatsを使ってみる</title><content type='html'>3/26追記：&lt;a href="http://code.google.com/intl/ja/appengine/docs/java/tools/appstats.html"&gt;やっと公式にドキュメントが追加されました&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;GAE SDK 1.3.2より、JavaでもAppstatsが使えるようになりました。1.3.2-preの段階ではまだlabsのjarに入っているので、appengine-api-labs-1.3.2.jarをWEB-INF/libに含めて、web.xmlに↓こんな感じで設定するだけ。※この設定だとstats自身もフィルタ対象になってしまうので除外する方法調査中。&lt;br /&gt;&lt;pre class="xml" name="code"&gt;&amp;lt;filter&amp;gt;&lt;br /&gt;  &amp;lt;filter-name&amp;gt;AppstatsFilter&amp;lt;/filter-name&amp;gt;&lt;br /&gt;  &amp;lt;display-name&amp;gt;Appstats Filter&amp;lt;/display-name&amp;gt;&lt;br /&gt;  &amp;lt;description&amp;gt;Appstats Filter&amp;lt;/description&amp;gt;&lt;br /&gt;  &amp;lt;filter-class&amp;gt;com.google.appengine.tools.appstats.AppstatsFilter&amp;lt;/filter-class&amp;gt;&lt;br /&gt;&amp;lt;/filter&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;filter-mapping&amp;gt;&lt;br /&gt;  &amp;lt;filter-name&amp;gt;AppstatsFilter&amp;lt;/filter-name&amp;gt;&lt;br /&gt;  &amp;lt;url-pattern&amp;gt;/*&amp;lt;/url-pattern&amp;gt;&lt;br /&gt;&amp;lt;/filter-mapping&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;servlet&amp;gt;&lt;br /&gt;  &amp;lt;servlet-name&amp;gt;AppstatsServlet&amp;lt;/servlet-name&amp;gt;&lt;br /&gt;  &amp;lt;servlet-class&amp;gt;&lt;br /&gt;    com.google.appengine.tools.appstats.AppstatsServlet&lt;br /&gt;  &amp;lt;/servlet-class&amp;gt;&lt;br /&gt;&amp;lt;/servlet&amp;gt;&lt;br /&gt;&lt;br /&gt;&amp;lt;servlet-mapping&amp;gt;&lt;br /&gt;  &amp;lt;servlet-name&amp;gt;AppstatsServlet&amp;lt;/servlet-name&amp;gt;&lt;br /&gt;  &amp;lt;url-pattern&amp;gt;/stats/*&amp;lt;/url-pattern&amp;gt;&lt;br /&gt;&amp;lt;/servlet-mapping&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;ブラウザで/stats/でを開くと、↓こんな統計が見れます。&lt;br /&gt;※/stats て最後のスラッシュを抜かすとなぜかリダイレクトループしてしまいます…&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://2.bp.blogspot.com/_IngpC7Xbw78/S6BhIO63r4I/AAAAAAAAAK8/nUObeNocZro/s1600-h/appstats.jpg" imageanchor="1" style="clear: left; margin-bottom: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="276" src="http://2.bp.blogspot.com/_IngpC7Xbw78/S6BhIO63r4I/AAAAAAAAAK8/nUObeNocZro/s640/appstats.jpg" width="640" /&gt;&lt;/a&gt;&lt;a href="http://2.bp.blogspot.com/_IngpC7Xbw78/S6BhIO63r4I/AAAAAAAAAK8/nUObeNocZro/s1600-h/appstats.jpg" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"&gt;&lt;br /&gt;&lt;/a&gt;&lt;br /&gt;&lt;a href="http://3.bp.blogspot.com/_IngpC7Xbw78/S6BiOLT8ykI/AAAAAAAAALE/aaFQgGy9naI/s1600-h/appstats2.jpg" imageanchor="1" style="clear: left; margin-bottom: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="276" src="http://3.bp.blogspot.com/_IngpC7Xbw78/S6BiOLT8ykI/AAAAAAAAALE/aaFQgGy9naI/s640/appstats2.jpg" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;※IE8だと表示が崩れてました。&lt;br /&gt;&lt;br /&gt;これだけだとURLが分かれば誰でも見えてしまうので、管理者のみアクセスできるような設定もした方がよいでしょう。&lt;br /&gt;&lt;pre name="code" class="xml"&gt;&amp;lt;security-constraint&amp;gt;&lt;br /&gt;  &amp;lt;web-resource-collection&amp;gt;&lt;br /&gt;    &amp;lt;url-pattern&amp;gt;/stats/*&amp;lt;/url-pattern&amp;gt;&lt;br /&gt;  &amp;lt;/web-resource-collection&amp;gt;&lt;br /&gt;  &amp;lt;auth-constraint&amp;gt;&lt;br /&gt;    &amp;lt;role-name&amp;gt;admin&amp;lt;/role-name&amp;gt;&lt;br /&gt;  &amp;lt;/auth-constraint&amp;gt;&lt;br /&gt;&amp;lt;/security-constraint&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7854445846272488112-3452001089503059642?l=pomu0325.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://pomu0325.blogspot.com/feeds/3452001089503059642/comments/default' title='コメントの投稿'/><link rel='replies' type='text/html' href='http://pomu0325.blogspot.com/2010/03/gae-javaappstats.html#comment-form' title='0 件のコメント'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7854445846272488112/posts/default/3452001089503059642'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7854445846272488112/posts/default/3452001089503059642'/><link rel='alternate' type='text/html' href='http://pomu0325.blogspot.com/2010/03/gae-javaappstats.html' title='[GAE] JavaでAppstatsを使ってみる'/><author><name>pomu0325</name><uri>http://www.blogger.com/profile/07153996876142338944</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://2.bp.blogspot.com/_IngpC7Xbw78/S3OLp5iNDyI/AAAAAAAAAIc/R5UH2AMmiAo/S220/RIMG0357.JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_IngpC7Xbw78/S6BhIO63r4I/AAAAAAAAAK8/nUObeNocZro/s72-c/appstats.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7854445846272488112.post-8956904671961391854</id><published>2010-03-17T03:29:00.004+09:00</published><updated>2010-03-18T02:55:22.020+09:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Lift'/><category scheme='http://www.blogger.com/atom/ns#' term='GAE'/><title type='text'>[Lift][GAE] DataStoreに溜まったsessionをcronで削除する</title><content type='html'>↓こんなobjectを作っておき、cronを設定した。だいたい550件程度削除すると30秒経過のタイムアウトでエラーになるけど別に気にしない。&lt;br /&gt;&lt;pre name="code" class="scala"&gt;import net.liftweb.http._&lt;br /&gt;import net.liftweb.common._&lt;br /&gt;import com.google.appengine.api.datastore._&lt;br /&gt;&lt;br /&gt;object SessionCleaner {&lt;br /&gt;  // Iteratorのimplicit conversionは定義されていないようなので自前で定義。&lt;br /&gt;  implicit def j2s[A](j: java.util.Iterator[A]) = &lt;br /&gt;    new scala.collection.jcl.MutableIterator.Wrapper[A](j)&lt;br /&gt;  private lazy val DSS = DatastoreServiceFactory.getDatastoreService&lt;br /&gt;&lt;br /&gt;  def execute(): Box[LiftResponse] = {&lt;br /&gt;    var count = 0&lt;br /&gt;    try {&lt;br /&gt;      val q = new Query("_ah_SESSION")&lt;br /&gt;      q.addFilter("_expires", Query.FilterOperator.LESS_THAN_OR_EQUAL, System.currentTimeMillis)&lt;br /&gt;      DSS.prepare(q).asIterator.foreach(e =&amp;gt; {DSS.delete(e.getKey); count = count + 1})&lt;br /&gt;    } finally {&lt;br /&gt;      println(count + " sessions deleted.")&lt;br /&gt;    }&lt;br /&gt;    Full(OkResponse())&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Boot.scalaのdef boot内にはこんな感じ。&lt;br /&gt;&lt;pre name="code" class="scala"&gt;LiftRules.statelessDispatchTable.append {&lt;br /&gt;  case Req("cron" :: "sessionCleaner" :: Nil, _, _) =&amp;gt; () =&amp;gt; SessionCleaner.execute()&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;h4&gt;ソース全体はgithubに置きました&lt;/h4&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://github.com/pomu0325/lift_sandbox/blob/master/src/main/scala/net/pomu/lift_sandbox/lib/SessionCleaner.scala"&gt;SessionCleaner.scala&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://github.com/pomu0325/lift_sandbox/blob/master/src/main/scala/bootstrap/liftweb/Boot.scala"&gt;Boot.scala&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://github.com/pomu0325/lift_sandbox/blob/master/src/main/webapp/WEB-INF/cron.xml"&gt;cron.xml&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7854445846272488112-8956904671961391854?l=pomu0325.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://pomu0325.blogspot.com/feeds/8956904671961391854/comments/default' title='コメントの投稿'/><link rel='replies' type='text/html' href='http://pomu0325.blogspot.com/2010/03/liftgae-datastoresessioncron.html#comment-form' title='1 件のコメント'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7854445846272488112/posts/default/8956904671961391854'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7854445846272488112/posts/default/8956904671961391854'/><link rel='alternate' type='text/html' href='http://pomu0325.blogspot.com/2010/03/liftgae-datastoresessioncron.html' title='[Lift][GAE] DataStoreに溜まったsessionをcronで削除する'/><author><name>pomu0325</name><uri>http://www.blogger.com/profile/07153996876142338944</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://2.bp.blogspot.com/_IngpC7Xbw78/S3OLp5iNDyI/AAAAAAAAAIc/R5UH2AMmiAo/S220/RIMG0357.JPG'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7854445846272488112.post-5144767598378637151</id><published>2010-03-16T02:06:00.007+09:00</published><updated>2010-03-16T02:13:47.773+09:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Scala'/><category scheme='http://www.blogger.com/atom/ns#' term='Lift'/><title type='text'>[Lift] 無駄に新規セッションを生成させずに条件によってリダイレクトする方法</title><content type='html'>リクエストヘッダの条件によってリダイレクトを行うには、snippet内でS.redirectToを使ってリダイレクトするのが一番簡単な方法ですが、Liftでsnippetを使うと必ずセッションを生成してくれます。検索エンジンのクローラbotとかからアクセスがある度に無駄にセッションが生成されてしまってサーバリソースを無駄に使っていたので、こんな感じで不要な場合はセッションが生成される前にリダイレクトさせてみました。&lt;br /&gt;&lt;pre name="code" class="scala"&gt;// Boot.scala の def boot内&lt;br /&gt;LiftRules.statelessDispatchTable.prepend {  // *1&lt;br /&gt;  case MyReq("venue" :: vid :: _, r) if r.header("User-Agent") == Full("何か") =&amp;gt; // *2&lt;br /&gt;    () =&amp;gt; Full(RedirectResponse("http://foursquare.com/venue/" + vid))&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;object MyReq {  // *3&lt;br /&gt;  def unapply(in: Req): Option[(List[String], HTTPRequest)] = &lt;br /&gt;    Some((in.path.partPath, in.request))&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;ol&gt;&lt;li&gt;LiftRules.&lt;strong&gt;statelessDispatchTable&lt;/strong&gt;にPartialFunction[Req, () =&gt; Box[LiftResponse]]を追加しておくと、セッション生成前に条件にマッチした場合は戻り値のLiftResponseをクライアントに返してくれます。&lt;/li&gt;&lt;li&gt;通常は、Req(パス, 拡張子, リクエストタイプ)でパターンマッチさせますが、ここではリクエストの内容によって判断したいため、*3で独自の&lt;strong&gt;抽出子(extractor)&lt;/strong&gt;を定義し、抽出したHTTPRequestの中身（上記例ではリクエストヘッダ）を&lt;strong&gt;パターンガード&lt;/strong&gt;条件として使用しています。&lt;/li&gt;&lt;li&gt;unapplyメソッドを持つobjectが&lt;strong&gt;抽出子&lt;/strong&gt;です。参照：&lt;a href="http://www.amazon.co.jp/dp/4844327453?tag=withp-22&amp;camp=1027&amp;creative=7407&amp;linkCode=as4&amp;creativeASIN=4844327453&amp;adid=0D58W4YYBD2CB7QR1CQX&amp;"&gt;コップ本&lt;/a&gt;第24章(p.440)&lt;/li&gt;&lt;/ol&gt;抽出子とパターンガードを使うとパターンマッチが益々便利です。&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7854445846272488112-5144767598378637151?l=pomu0325.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://pomu0325.blogspot.com/feeds/5144767598378637151/comments/default' title='コメントの投稿'/><link rel='replies' type='text/html' href='http://pomu0325.blogspot.com/2010/03/lift.html#comment-form' title='0 件のコメント'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7854445846272488112/posts/default/5144767598378637151'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7854445846272488112/posts/default/5144767598378637151'/><link rel='alternate' type='text/html' href='http://pomu0325.blogspot.com/2010/03/lift.html' title='[Lift] 無駄に新規セッションを生成させずに条件によってリダイレクトする方法'/><author><name>pomu0325</name><uri>http://www.blogger.com/profile/07153996876142338944</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://2.bp.blogspot.com/_IngpC7Xbw78/S3OLp5iNDyI/AAAAAAAAAIc/R5UH2AMmiAo/S220/RIMG0357.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7854445846272488112.post-4865087998367154739</id><published>2010-03-08T02:27:00.007+09:00</published><updated>2010-03-09T02:22:50.127+09:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Lift'/><category scheme='http://www.blogger.com/atom/ns#' term='GAE'/><title type='text'>[Lift] GAEがスケールアウトするとLiftセッションが切れる…</title><content type='html'>LiftはServletSessionとは別に独自にセッションを実装しているので、StatefulSnippetはGAEのスケールアウトにうまく対応できないようです…。当然、StatefulSnippetじゃなくてもコールバックをクロージャで実装していると、スケールアウトにタイミング悪くもぶち当たるとアウトなはず。&lt;br /&gt;&lt;br /&gt;以下がスケールアウトしたときのログ。&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/_IngpC7Xbw78/S5Phf1czPBI/AAAAAAAAAK0/1yivW78qoTA/s1600-h/scaleout.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="235" src="http://4.bp.blogspot.com/_IngpC7Xbw78/S5Phf1czPBI/AAAAAAAAAK0/1yivW78qoTA/s640/scaleout.jpg" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;ロードバランサがCookie見てセッション同じなら同じサーバインスタンスに振り分けてくれればうれしいのに…。というわけで&lt;a href="http://code.google.com/p/googleappengine/issues/detail?id=1320" target="_blank"&gt;Lift on GAEな人は投票を&lt;/a&gt;。&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7854445846272488112-4865087998367154739?l=pomu0325.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://pomu0325.blogspot.com/feeds/4865087998367154739/comments/default' title='コメントの投稿'/><link rel='replies' type='text/html' href='http://pomu0325.blogspot.com/2010/03/lift-gaelift.html#comment-form' title='0 件のコメント'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7854445846272488112/posts/default/4865087998367154739'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7854445846272488112/posts/default/4865087998367154739'/><link rel='alternate' type='text/html' href='http://pomu0325.blogspot.com/2010/03/lift-gaelift.html' title='[Lift] GAEがスケールアウトするとLiftセッションが切れる…'/><author><name>pomu0325</name><uri>http://www.blogger.com/profile/07153996876142338944</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://2.bp.blogspot.com/_IngpC7Xbw78/S3OLp5iNDyI/AAAAAAAAAIc/R5UH2AMmiAo/S220/RIMG0357.JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_IngpC7Xbw78/S5Phf1czPBI/AAAAAAAAAK0/1yivW78qoTA/s72-c/scaleout.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7854445846272488112.post-1725316558330162461</id><published>2010-03-05T14:35:00.007+09:00</published><updated>2010-03-18T02:55:47.674+09:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Lift'/><category scheme='http://www.blogger.com/atom/ns#' term='GAE'/><title type='text'>[Lift] LiftFormを使わずにStatefulSnippetをステートフルに使う</title><content type='html'>LiftFormは大変便利なのですが、Lift流にsubmit時のコールバックをクロージャで書いてしまうと、セッションが切れた時にコールバックが動きません。Google App Engine上で動かしたりなんかすると、3分程度でスピンダウンしてしまうので、すぐ切れてしまってフォーム入力に時間がかかるようなアプリだと、セッション切れてまともに動きません…。（※実際は、ajax_requestが定期的にコールされるので、そんなにスピンダウンしない？と思うけど今作ってるのが携帯向けサイトでajax_requestをOFFにしてるので問題が顕在化）&lt;br /&gt;&lt;br /&gt;そこで、クラシックな通常のHTMLフォームを使い、なおかつStatefulSnippetとして同じインスタンスが呼ばれるようなコードを書いてみました。&lt;br /&gt;&lt;br /&gt;&lt;a href="http://github.com/pomu0325/lift_sandbox/blob/master/src/main/scala/net/pomu/lift_sandbox/snippet/StatefulTest.scala" target="_blank"&gt;ソースを見る&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;h4&gt;まずはLift流に&lt;/h4&gt;普通にLift流に簡単なフォームを書くとこんな感じです。&lt;br /&gt;&lt;pre name="code" class="html"&gt;&amp;lt;lift:StatefulTest.liftForm form="POST"&amp;gt;&lt;br /&gt;  &amp;lt;e:instance/&amp;gt;&lt;br /&gt;  &amp;lt;e:input/&amp;gt;&lt;br /&gt;  &amp;lt;e:submit/&amp;gt;&lt;br /&gt;&amp;lt;/lift:StatefulTest.liftForm&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Snippetはこんな感じ。&lt;br /&gt;&lt;pre name="code" class="scala"&gt;def liftForm(in: NodeSeq): NodeSeq = {&lt;br /&gt;var name = ""&lt;br /&gt;def sayHello() = {&lt;br /&gt;  S.notice("Hello, " + name + ". I'm " + this)&lt;br /&gt;  redirectTo("/liftSayHello")&lt;br /&gt;}&lt;br /&gt;bind("e", in,&lt;br /&gt;  "instance" -&amp;gt; Text("I'm " + this),&lt;br /&gt;  "input" -&amp;gt; SHtml.text(name, i =&amp;gt; name = i),&lt;br /&gt;  "submit" -&amp;gt; SHtml.submit("say hello", sayHello)&lt;br /&gt;)&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h4&gt;無理やり普通のformで&lt;/h4&gt;これと同様なフォームを、無理やり書くとこんな感じです。&lt;br /&gt;&lt;pre name="code" class="html"&gt;&amp;lt;lift:StatefulTest.myForm&amp;gt;&lt;br /&gt;  &lt;!-- Snippetタグにformを生成させるとactionが自分自身になってしまうので自分でform要素を書いてactionを別ページに指定している --&gt;&lt;br /&gt;  &amp;lt;form action="mySayHello" method="POST"&amp;gt; &lt;br /&gt;    &amp;lt;e:instance/&amp;gt;&lt;br /&gt;    &amp;lt;e:key/&amp;gt;&lt;!-- *1 コールバック時にregisterThisSnippet()を呼んでステートフルにするためのキー --&gt;&lt;br /&gt;    &amp;lt;e:input/&amp;gt;&lt;br /&gt;    &amp;lt;e:submit/&amp;gt;&lt;br /&gt;  &amp;lt;/form&amp;gt;&lt;br /&gt;&amp;lt;/lift:StatefulTest.myForm&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;ポイントは、registerThisSnippet()を呼ぶ関数ブロックを、S.fmapFuncで自分で関数マップに登録してやり、そのキーをhiddenで埋め込んでやること(*1)。&lt;br /&gt;&lt;pre name="code" class="scala"&gt;def myForm(in: NodeSeq): NodeSeq = {&lt;br /&gt;S.fmapFunc((a: List[String]) =&amp;gt; {registerThisSnippet()})(key =&amp;gt; {&lt;br /&gt;  bind("e", in,&lt;br /&gt;    "key" -&amp;gt; &amp;lt;input type="hidden" name={key} value="_"/&amp;gt;, // *1&lt;br /&gt;    "instance" -&amp;gt; Text("I'm " + this),&lt;br /&gt;    "input" -&amp;gt; &amp;lt;input type="text" name="name"/&amp;gt;,&lt;br /&gt;    "submit" -&amp;gt; &amp;lt;input type="submit" value="say hello"/&amp;gt;&lt;br /&gt;  )&lt;br /&gt;})&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;このやり方だと、セッションが続いていれば同じインスタンスのStatefulSnippetが呼ばれ、続いていない場合でも、少なくとも新しいインスタンスのStatefulSnippetで処理は行えます。（CSRFの問題がありますが…）&lt;br /&gt;&lt;br /&gt;ソースはgithubに置きました。&lt;a href="http://github.com/pomu0325/lift_sandbox"&gt;http://github.com/pomu0325/lift_sandbox&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7854445846272488112-1725316558330162461?l=pomu0325.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://pomu0325.blogspot.com/feeds/1725316558330162461/comments/default' title='コメントの投稿'/><link rel='replies' type='text/html' href='http://pomu0325.blogspot.com/2010/03/lift-liftformstatefulsnippet.html#comment-form' title='0 件のコメント'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7854445846272488112/posts/default/1725316558330162461'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7854445846272488112/posts/default/1725316558330162461'/><link rel='alternate' type='text/html' href='http://pomu0325.blogspot.com/2010/03/lift-liftformstatefulsnippet.html' title='[Lift] LiftFormを使わずにStatefulSnippetをステートフルに使う'/><author><name>pomu0325</name><uri>http://www.blogger.com/profile/07153996876142338944</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://2.bp.blogspot.com/_IngpC7Xbw78/S3OLp5iNDyI/AAAAAAAAAIc/R5UH2AMmiAo/S220/RIMG0357.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7854445846272488112.post-184210024108586033</id><published>2010-03-03T23:17:00.006+09:00</published><updated>2010-03-03T23:27:19.920+09:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Scala'/><category scheme='http://www.blogger.com/atom/ns#' term='Lift'/><title type='text'>[Scala] valの左辺にパターンマッチが使える</title><content type='html'>今日の&lt;a href="http://atnd.org/events/3161" target="_blank"&gt;第4回Scala言語仕様輪読会&lt;/a&gt;での収穫。&lt;br /&gt;&lt;br /&gt;&lt;i&gt;p&lt;/i&gt;がパターンの時、val &lt;i&gt;p&lt;/i&gt; = eは以下の様に展開される。&lt;br /&gt;&lt;pre name="code" class="scala"&gt;val $x = e match {case p =&amp;gt; (x1, . . . , xn)}&lt;br /&gt;val x1 = $x._1&lt;br /&gt;. . .&lt;br /&gt;val xn = $x._n&lt;br /&gt;&lt;/pre&gt;※&lt;a href="http://www.scala-lang.org/docu/files/ScalaReference.pdf" target="_blank"&gt;The Scala Language Specification(PDF)&lt;/a&gt;のp.36参照（タプルが{}になっているのは古い仕様？なので↑では()に直しています）。&lt;br /&gt;&lt;br /&gt;簡単な例だと、タプルを使って同時にvalに代入できる。&lt;br /&gt;&lt;pre name="code" class="scala"&gt;val (x, y) = (1, 2)&lt;br /&gt;// ↑は↓に展開され&lt;br /&gt;val tmp = (1, 2) match {case (a, b) =&amp;gt; (a, b)}&lt;br /&gt;val x = tmp._1&lt;br /&gt;val y = tmp._2&lt;br /&gt;// 結局↓と同じ&lt;br /&gt;val x = 1&lt;br /&gt;val y = 2&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Liftで↓なコードがあって、意味が分からなかったのだが、やっと理解できた。&lt;br /&gt;&lt;pre name="code" class="scala"&gt;// net/liftweb/http/Req.scala l.275&lt;br /&gt;case class ParamCalcInfo(paramNames: List[String],&lt;br /&gt;            params: Map[String, List[String]],&lt;br /&gt;            uploadedFiles: List[FileParamHolder],&lt;br /&gt;            body: Box[Array[Byte]])&lt;br /&gt;// l.284&lt;br /&gt;class Req(val path: ParsePath,&lt;br /&gt;          val contextPath: String,&lt;br /&gt;          val requestType: RequestType,&lt;br /&gt;          val contentType: Box[String],&lt;br /&gt;          val request: HTTPRequest,&lt;br /&gt;          val nanoStart: Long,&lt;br /&gt;          val nanoEnd: Long,&lt;br /&gt;          private[http] val paramCalculator: () =&amp;gt; ParamCalcInfo, // *2&lt;br /&gt;          private[http] val addlParams: Map[String, String]) extends HasParams&lt;br /&gt;{&lt;br /&gt;// (省略)&lt;br /&gt;// l.342&lt;br /&gt;  lazy val ParamCalcInfo(paramNames: List[String],  // *1&lt;br /&gt;            _params: Map[String, List[String]],&lt;br /&gt;            uploadedFiles: List[FileParamHolder],&lt;br /&gt;            body: Box[Array[Byte]]) = paramCalculator()&lt;br /&gt;// (省略)&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;ol&gt;&lt;li&gt;なぜ突然valの後にcase classが…？ と思ったらこれがパターンマッチ。コンストラクタで指定した関数paramCalculator(*2)がParamCalcInfoを返すので、ParamCalcInfoの各valがそれぞれparamNames, _params, uploadedFiles, bodyに束縛され、Req.body等として使える。つまり、↓の様に展開されているということと理解。&lt;br /&gt;&lt;pre name="code" class="scala"&gt;val tmp = paramCalculator() match {&lt;br /&gt;  case ParamCalcInfo(a, b, c, d) =&amp;gt; (a, b, c, d)&lt;br /&gt;}&lt;br /&gt;val paramNames = tmp._1&lt;br /&gt;val _params = tmp._2&lt;br /&gt;val uploadedFiles = tmp._3&lt;br /&gt;val body = tmp._4&lt;br /&gt;&lt;/pre&gt;&lt;/li&gt;&lt;/ol&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7854445846272488112-184210024108586033?l=pomu0325.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://pomu0325.blogspot.com/feeds/184210024108586033/comments/default' title='コメントの投稿'/><link rel='replies' type='text/html' href='http://pomu0325.blogspot.com/2010/03/scala-val.html#comment-form' title='0 件のコメント'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7854445846272488112/posts/default/184210024108586033'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7854445846272488112/posts/default/184210024108586033'/><link rel='alternate' type='text/html' href='http://pomu0325.blogspot.com/2010/03/scala-val.html' title='[Scala] valの左辺にパターンマッチが使える'/><author><name>pomu0325</name><uri>http://www.blogger.com/profile/07153996876142338944</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://2.bp.blogspot.com/_IngpC7Xbw78/S3OLp5iNDyI/AAAAAAAAAIc/R5UH2AMmiAo/S220/RIMG0357.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7854445846272488112.post-323323818535347065</id><published>2010-03-02T20:55:00.001+09:00</published><updated>2010-03-02T21:06:14.803+09:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Scala'/><title type='text'>[Scala] 文字列に含まれるURLをaタグにしてNodeSeqで返す</title><content type='html'>Liftでbindする時にNodeSeqが必要だったので書いてみた。はじめ、Regex.findAllInとfor式のyieldでできるんじゃないかと試行錯誤してたが、再帰を使った方が簡単なことに気づいてこんな感じになった。&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="scala"&gt;import scala.xml._&lt;br /&gt;implicit def c2s(c: CharSequence): String = c.toString&lt;br /&gt;val URLPATTERN = """http://[\d\w\-\./%?=#]+""".r&lt;br /&gt;def linkURL(s: String): NodeSeq = URLPATTERN.findFirstMatchIn(s) match {&lt;br /&gt; case None =&amp;gt; Text(s)&lt;br /&gt; case Some(m) =&amp;gt;&lt;br /&gt;  &amp;lt;xml:group&amp;gt;{Text(m.before)}&amp;lt;a href={m.matched}&amp;gt;{m.matched}&amp;lt;/a&amp;gt;{linkURL(m.after)}&amp;lt;/xml:group&amp;gt;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h4&gt;使用例&lt;/h4&gt;&lt;pre name="code" class="scala"&gt;scala&amp;gt; linkURL("aaaa http://pomu0325.blogspot.com/ bbbb http://twitter.com/pomu0325 cccc")&lt;br /&gt;res21: scala.xml.NodeSeq = aaaa &amp;lt;a href="http://pomu0325.blogspot.com/"&amp;gt;http://pomu0325.blogspot.com&lt;br /&gt;/&amp;lt;/a&amp;gt; bbbb &amp;lt;a href="http://twitter.com/pomu0325"&amp;gt;http://twitter.com/pomu0325&amp;lt;/a&amp;gt; cccc&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7854445846272488112-323323818535347065?l=pomu0325.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://pomu0325.blogspot.com/feeds/323323818535347065/comments/default' title='コメントの投稿'/><link rel='replies' type='text/html' href='http://pomu0325.blogspot.com/2010/03/scala-urlanodeseq.html#comment-form' title='0 件のコメント'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7854445846272488112/posts/default/323323818535347065'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7854445846272488112/posts/default/323323818535347065'/><link rel='alternate' type='text/html' href='http://pomu0325.blogspot.com/2010/03/scala-urlanodeseq.html' title='[Scala] 文字列に含まれるURLをaタグにしてNodeSeqで返す'/><author><name>pomu0325</name><uri>http://www.blogger.com/profile/07153996876142338944</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://2.bp.blogspot.com/_IngpC7Xbw78/S3OLp5iNDyI/AAAAAAAAAIc/R5UH2AMmiAo/S220/RIMG0357.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7854445846272488112.post-4416076855995656999</id><published>2010-02-22T20:31:00.008+09:00</published><updated>2010-02-22T20:55:32.354+09:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Lift'/><title type='text'>[Lift] "?"を含むURLがStatefulSnippet.linkメソッドでStatefulにならない</title><content type='html'>StatefulSnippetで、同じインスタンスのStatefulSnippetを呼び出すようなリンク（&amp;lt;a&amp;gt;タグ）を出力する際には、StatefulSnippet.linkメソッドを使用します。が、StatefulSnippet.linkを使ってもリンク後に別のインスタンスのSnippetが作られてしまう場合があり、困ったのでStatefulになる仕組みを追ってみました。&lt;br /&gt;&lt;br /&gt;&lt;h4&gt;仕組み&lt;/h4&gt;&lt;li&gt;StatefulSnippet.linkでリンク用URLを出力すると、ランダムなキーでコールバック関数がセッションに保持され、http://localhost/search?F869982852207GSN=_ の様にキーがクエリ文字列として付加されます。&lt;br /&gt;&lt;li&gt;このリンクをクリックすると、LiftSession.runParamsでこのキーで登録されているコールバック関数が実行されます。&lt;br /&gt;&lt;li&gt;コールバック関数内で、registerThisSnippet()が呼ばれ、ステートが維持される仕組みになっています。&lt;br /&gt;&lt;br /&gt;ソースを追ってみると、StatefulSnippet.linkはコールバック時に第2引数で指定した関数の前に、registerThisSnippet()を呼ぶようなブロックに差し替えて、SHtml.linkと同じ処理をしているだけです。&lt;br /&gt;&lt;pre name="code" class="scala:firstline[87]"&gt;// StatefulSnippet.scala&lt;br /&gt;def link(to: String, func: () =&amp;gt; Any, body: NodeSeq): Elem = SHtml.link(to, () =&amp;gt; {registerThisSnippet(); func()}, body)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;SHtml.linkは何をやっているかというと、fmapFuncでコールバック関数を登録し、そのキーをクエリ文字列としてURLに付加しています。&lt;br /&gt;&lt;pre name="code" class="scala:firstline[596]"&gt;// SHtml.scala&lt;br /&gt;def link(to: String, func: () =&amp;gt; Any, body: NodeSeq,&lt;br /&gt;         attrs: (String, String)*): Elem = {&lt;br /&gt;  fmapFunc((a: List[String]) =&amp;gt; {func(); true})(key =&amp;gt;&lt;br /&gt;          attrs.foldLeft(&amp;lt;a href={to + (if (to.indexOf("?") &amp;gt;= 0) "&amp;" else "?") + key + "=_"}&amp;gt;{body}&amp;lt;/a&amp;gt;)(_ % _))&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;ここで、既にクエリ文字列がある（＝"?"が含まれる）場合には"&amp;"でつなげてることに気づき、問題解決。&lt;br /&gt;&lt;br /&gt;&lt;h4&gt;問題解決&lt;/h4&gt;問題となったは、AU携帯で位置情報を付加してリクエストを送るための、以下の様なaタグを出力しようとした部分。&lt;br /&gt;&lt;pre name="code" class="html"&gt;&amp;lt;a href="device:location?url=http://www.mb4sq.jp/search"/&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;pre name="code" class="scala"&gt;def search(in: NodeSeq): NodeSeq =&lt;br /&gt;  bind("f", in, "link" -&gt; link("device:location?url=http://localhost/search", ()=&amp;gt;"", Text("search")))&lt;br /&gt;&lt;/pre&gt;こんなコードを書いてみたところ、本来欲しいURLは&lt;br /&gt;device:location?url=http://localhost/search&lt;b&gt;?&lt;/b&gt;F869982852207GSN=_ の様なものなんですが、元のURLに"?"が入ってしまっているので、"?"の代わりに"&amp;"が付加されてしまい、&lt;br /&gt;device:location?url=http://localhost/search&lt;b&gt;&amp;&lt;/b&gt;F869982852207GSN=_ になってしまってるわけですね…。これではクエリ文字列として取得できず、ステートを維持するためのregisterThiSnippet()のコールバックが呼ばれずに、ステートが切れてしまっていたと。&lt;br /&gt;&lt;br /&gt;結局、StatefulSnippet.linkをオーバーライドして、GPSのリンクの場合はハードコードで"?"を付けるようにして回避しました。&lt;br /&gt;&lt;pre name="code" class="scala"&gt;override def link(to: String, func: () =&amp;gt; Any, body: NodeSeq): Elem = {&lt;br /&gt;  def insert(s: String, i: String, p: Int) = List(s take p, i, s drop p).mkString&lt;br /&gt;  if (to.startsWith("device:")) &lt;br /&gt;    S.fmapFunc((a: List[String]) =&amp;gt; {registerThisSnippet(); func()})(key =&amp;gt; {&lt;br /&gt;    val u = if (to.indexOf("&amp;amp;ver=1") &amp;gt; 0) insert(to, ("?" + key + "=_"), to.indexOf("&amp;amp;ver=1"))&lt;br /&gt;        else to + ("?" + key + "=_")&lt;br /&gt;      &amp;lt;a href={u}&amp;gt;{body}&amp;lt;/a&amp;gt;&lt;br /&gt;    })&lt;br /&gt;   else super.link(to, func, body)&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7854445846272488112-4416076855995656999?l=pomu0325.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://pomu0325.blogspot.com/feeds/4416076855995656999/comments/default' title='コメントの投稿'/><link rel='replies' type='text/html' href='http://pomu0325.blogspot.com/2010/02/lift-statefulsnippetlinkstateful.html#comment-form' title='0 件のコメント'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7854445846272488112/posts/default/4416076855995656999'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7854445846272488112/posts/default/4416076855995656999'/><link rel='alternate' type='text/html' href='http://pomu0325.blogspot.com/2010/02/lift-statefulsnippetlinkstateful.html' title='[Lift] &quot;?&quot;を含むURLがStatefulSnippet.linkメソッドでStatefulにならない'/><author><name>pomu0325</name><uri>http://www.blogger.com/profile/07153996876142338944</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://2.bp.blogspot.com/_IngpC7Xbw78/S3OLp5iNDyI/AAAAAAAAAIc/R5UH2AMmiAo/S220/RIMG0357.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7854445846272488112.post-3365168568423612011</id><published>2010-02-20T13:44:00.002+09:00</published><updated>2010-02-20T16:34:20.209+09:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Scala'/><title type='text'>[Scala] Stringの指定位置にStringを挿入する</title><content type='html'>ありそうでなさそうなので作ってみた。RichStringがSeqなのでtakeとかdropとか使えて便利。&lt;br /&gt;&lt;pre name="code" class="scala"&gt;scala&amp;gt; def insert(s: String, i: String, p: Int) = List(s take p, i, s drop p).mkString&lt;br /&gt;insert: (String,String,Int)String&lt;br /&gt;&lt;br /&gt;scala&amp;gt; insert("abcd", "123", 2)&lt;br /&gt;res1: String = ab123cd&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7854445846272488112-3365168568423612011?l=pomu0325.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://pomu0325.blogspot.com/feeds/3365168568423612011/comments/default' title='コメントの投稿'/><link rel='replies' type='text/html' href='http://pomu0325.blogspot.com/2010/02/scala-stringstring.html#comment-form' title='0 件のコメント'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7854445846272488112/posts/default/3365168568423612011'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7854445846272488112/posts/default/3365168568423612011'/><link rel='alternate' type='text/html' href='http://pomu0325.blogspot.com/2010/02/scala-stringstring.html' title='[Scala] Stringの指定位置にStringを挿入する'/><author><name>pomu0325</name><uri>http://www.blogger.com/profile/07153996876142338944</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://2.bp.blogspot.com/_IngpC7Xbw78/S3OLp5iNDyI/AAAAAAAAAIc/R5UH2AMmiAo/S220/RIMG0357.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7854445846272488112.post-7167635062414657948</id><published>2010-02-20T12:06:00.003+09:00</published><updated>2010-02-20T13:45:09.540+09:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Scala'/><title type='text'>[Scala] untilループ</title><content type='html'>&lt;a href="http://atnd.org/events/2327" target="_blank"&gt;Scala Hack-a-thon&lt;/a&gt;で、Scalaにはuntilループが無いけど作れる、って@yuroyoroさんが言っていたので、作ってみた。&lt;br /&gt;&lt;pre name="code" class="scala"&gt;scala&amp;gt; def until(b: =&amp;gt;Boolean)(f: =&amp;gt;Any) = while(!b)f&lt;br /&gt;until: (=&gt; Boolean)(=&gt; Any)Unit&lt;br /&gt;&lt;/pre&gt;ポイントは=&amp;gt;で引数を名前渡しにするとこか。&lt;br /&gt;&lt;br /&gt;&lt;h4&gt;使用例&lt;/h4&gt;&lt;pre name="code" class="scala"&gt;scala&amp;gt; var i = 0&lt;br /&gt;i: Int = 0&lt;br /&gt;&lt;br /&gt;scala&amp;gt; until (i==10) {println(i);i=i+1}&lt;br /&gt;0&lt;br /&gt;1&lt;br /&gt;2&lt;br /&gt;3&lt;br /&gt;4&lt;br /&gt;5&lt;br /&gt;6&lt;br /&gt;7&lt;br /&gt;8&lt;br /&gt;9&lt;br /&gt;&lt;/pre&gt;できることは分かったけど、きっと使わないなぁ…。&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7854445846272488112-7167635062414657948?l=pomu0325.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://pomu0325.blogspot.com/feeds/7167635062414657948/comments/default' title='コメントの投稿'/><link rel='replies' type='text/html' href='http://pomu0325.blogspot.com/2010/02/scala-until.html#comment-form' title='0 件のコメント'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7854445846272488112/posts/default/7167635062414657948'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7854445846272488112/posts/default/7167635062414657948'/><link rel='alternate' type='text/html' href='http://pomu0325.blogspot.com/2010/02/scala-until.html' title='[Scala] untilループ'/><author><name>pomu0325</name><uri>http://www.blogger.com/profile/07153996876142338944</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://2.bp.blogspot.com/_IngpC7Xbw78/S3OLp5iNDyI/AAAAAAAAAIc/R5UH2AMmiAo/S220/RIMG0357.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7854445846272488112.post-388965024991253164</id><published>2010-02-18T21:44:00.007+09:00</published><updated>2010-02-18T21:58:09.997+09:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Scala'/><category scheme='http://www.blogger.com/atom/ns#' term='GAE'/><title type='text'>[Scala][GAE] URLFetchのリトライ</title><content type='html'>&lt;a href="http://sites.google.com/site/scalatohoku/dai66kaibennkyoukai" target="_blank"&gt;Scala勉強会@東北&lt;/a&gt;で取り上げていた&lt;a href="http://github.com/nkallen/querulous" target="_blank"&gt;querulous&lt;/a&gt;の&lt;a href="http://github.com/nkallen/querulous/blob/master/src/main/scala/com/twitter/querulous/query/RetryingQuery.scala" target="_blank"&gt;RetryingQuery&lt;/a&gt;にインスパイアされて、GAEのURLFetchをリトライするコードを書いてみた。&lt;br /&gt;&lt;br /&gt;URLFetchがIOExceptionを投げるか、ステータス500が返ってきた場合にリトライ。&lt;br /&gt;&lt;pre name="code" class="scala"&gt;import com.google.appengine.api.urlfetch._&lt;br /&gt;&lt;br /&gt;val UFS = URLFetchServiceFactory.getURLFetchService()&lt;br /&gt;def retryingURLFetch[A](r: HTTPRequest, retry: Int)(f: HTTPResponse =&gt; A): A = {&lt;br /&gt;  try {&lt;br /&gt;    val res = UFS.fetch(r)&lt;br /&gt;    res.getResponseCode match {&lt;br /&gt;      case 500 =&gt;&lt;br /&gt;        if (retry &amp;lt;= 0) f(res)&lt;br /&gt;        else retryingURLFetch(r, retry - 1)(f)&lt;br /&gt;      case _ =&gt; f(res)&lt;br /&gt;    }&lt;br /&gt;  } catch {&lt;br /&gt;    case e: java.io.IOException =&gt;&lt;br /&gt;      if (retry &amp;lt;= 0) throw e&lt;br /&gt;      retryingURLFetch(r, retry - 1)(f)&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h4&gt;使用例&lt;/h4&gt;&lt;pre name="code" class="scala"&gt;import java.net.URL&lt;br /&gt;import com.google.appengine.api.urlfetch._&lt;br /&gt;&lt;br /&gt;retryingURLFetch(new HTTPRequest(new URL("http://pomu0325.blogspot.com")), 2) {&lt;br /&gt;  res =&gt; println(new String(res.getContent, "utf-8"))&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;不安定なAPIを呼ぶ時とかに使えそう。&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7854445846272488112-388965024991253164?l=pomu0325.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://pomu0325.blogspot.com/feeds/388965024991253164/comments/default' title='コメントの投稿'/><link rel='replies' type='text/html' href='http://pomu0325.blogspot.com/2010/02/scalagae-urlfetch.html#comment-form' title='0 件のコメント'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7854445846272488112/posts/default/388965024991253164'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7854445846272488112/posts/default/388965024991253164'/><link rel='alternate' type='text/html' href='http://pomu0325.blogspot.com/2010/02/scalagae-urlfetch.html' title='[Scala][GAE] URLFetchのリトライ'/><author><name>pomu0325</name><uri>http://www.blogger.com/profile/07153996876142338944</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://2.bp.blogspot.com/_IngpC7Xbw78/S3OLp5iNDyI/AAAAAAAAAIc/R5UH2AMmiAo/S220/RIMG0357.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7854445846272488112.post-2271567222746791699</id><published>2010-02-16T20:00:00.013+09:00</published><updated>2010-02-16T20:33:30.917+09:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Joomla'/><title type='text'>[Joomla] mod_feedでrssの日付を表示する</title><content type='html'>Joomla!のRSS表示モジュール（mod_feed）で、日付を表示する設定がなかったので、デフォルトテンプレートを書き換えてみた。modules/mod_feed/tmpl/default.phpの、65行目が追加した行。&lt;br /&gt;&lt;pre name="code" class="php:firstline[59]"&gt;for ($j = 0; $j &amp;lt; $totalItems; $j ++)&lt;br /&gt;{&lt;br /&gt;  $currItem = &amp;amp; $feed-&amp;gt;items[$j];&lt;br /&gt;  // item title&lt;br /&gt;  ?&amp;gt;&lt;br /&gt;  &amp;lt;li&amp;gt;&lt;br /&gt;  &amp;lt;span style="padding-right:10px;"&amp;gt;&amp;lt;?php echo $currItem-&amp;gt;get_date('Y年n月d日'); ?&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;  &amp;lt;?php&lt;br /&gt;  if ( !is_null( $currItem-&amp;gt;get_link() ) ) {&lt;br /&gt;  ?&amp;gt;&lt;br /&gt;    &amp;lt;a href="&amp;lt;?php echo $currItem-&amp;gt;get_link(); ?&amp;gt;" target="_blank"&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7854445846272488112-2271567222746791699?l=pomu0325.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://pomu0325.blogspot.com/feeds/2271567222746791699/comments/default' title='コメントの投稿'/><link rel='replies' type='text/html' href='http://pomu0325.blogspot.com/2010/02/joomla-modfeedrss.html#comment-form' title='0 件のコメント'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7854445846272488112/posts/default/2271567222746791699'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7854445846272488112/posts/default/2271567222746791699'/><link rel='alternate' type='text/html' href='http://pomu0325.blogspot.com/2010/02/joomla-modfeedrss.html' title='[Joomla] mod_feedでrssの日付を表示する'/><author><name>pomu0325</name><uri>http://www.blogger.com/profile/07153996876142338944</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://2.bp.blogspot.com/_IngpC7Xbw78/S3OLp5iNDyI/AAAAAAAAAIc/R5UH2AMmiAo/S220/RIMG0357.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7854445846272488112.post-1804518186609658587</id><published>2010-02-13T12:53:00.005+09:00</published><updated>2010-02-13T13:00:18.053+09:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Scala'/><category scheme='http://www.blogger.com/atom/ns#' term='Lift'/><title type='text'>[Lift] Liftのテンプレートでutf-8以外のレスポンスを返す</title><content type='html'>Liftでテンプレートを使用したページを作ると、必ずutf-8で返してくれるようです。携帯向けサイトとかで、どうしてもShift_JISで返したい時に、これだと文字化けしてしまいます。&lt;br /&gt;&lt;br /&gt;例のごとくドキュメントが少ないので、Liftのソースを追ってみます。通常、テンプレートを使用したページは、XhtmlResponseとしてNodeが保持され、LiftServletの最後にtoResponseでInMemoryResponseに変換(*1)してから送られます。&lt;br /&gt;&lt;pre class="scala" name="code"&gt;// net/liftweb/http/LiftServlet.scala(l.183)&lt;br /&gt;resp match {&lt;br /&gt;  case Full(cresp) =&amp;gt;&lt;br /&gt;    val resp = cresp.toResponse // *1 LiftResponse.toResponseはInMemoryResponseを返す&lt;br /&gt;     logIfDump(req, resp)&lt;br /&gt;     sendResponse(resp, response, Full(req))&lt;br /&gt;//...後略...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h4&gt;問題１：文字コード&lt;/h4&gt;XhtmlResponseのtoResponseで何をやっているのか見てみると、mixinしているNodeResponse.toResponseで、utf-8とハードコードされています…。&lt;br /&gt;&lt;pre class="scala" name="code"&gt;// net/liftweb/http/LiftResponse.scala(l.415)&lt;br /&gt;InMemoryResponse(ret.getBytes("UTF-8"), headers, cookies, code)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h4&gt;問題２：Content-Type&lt;/h4&gt;次、Content-Typeにもcharset=utf-8が勝手に出てしまいます。どこでやっているかと調べると、LiftServlet.sendResponseでContent-Typeをセットしています。determineContentTypeはここでもまたutf-8がハードコードです。ただし、ここでは明示的にContent-Typeを指定しておけば、上書きはしないようです。&lt;br /&gt;&lt;pre class="scala" name="code"&gt;// LiftServlet.scala(l.482)&lt;br /&gt;// insure that certain header fields are set&lt;br /&gt;val header = insureField(fixHeaders(resp.headers), List(("Content-Type", // *2&lt;br /&gt;        LiftRules.determineContentType(pairFromRequest(request))),&lt;br /&gt;  ("Content-Length", len.toString)))&lt;br /&gt;&lt;br /&gt;// LiftRules.scala(l.168)&lt;br /&gt;@volatile var determineContentType: PartialFunction[(Box[Req], Box[String]), String] = {&lt;br /&gt;  case (_, Full(accept)) if this.useXhtmlMimeType &amp;amp;&amp;amp; accept.toLowerCase.contains("application/xhtml+xml") =&amp;gt;&lt;br /&gt;    "application/xhtml+xml; charset=utf-8"&lt;br /&gt;  case _ =&amp;gt; "text/html; charset=utf-8"&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;ol start="2"&gt;&lt;li&gt;insureFieldはnet.liftweb.util.HttpHelpersのメソッド。第1引数のList[Pair]に第2引数のList[Pair]の_1が含まれてない場合に、追加する。&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;&lt;h4&gt;問題３：XML宣言&lt;/h4&gt;さらに、&amp;lt;?xml version="1.0" encoding="UTF-8"?&amp;gt;のXML宣言も必ず付けてくれるようです。が、S.skipXmlHeaderをtrueにしておけば、何もしないようです。&lt;br /&gt;&lt;pre class="scala" name="code"&gt;// net.liftweb.http.LiftRules.scala(l.442)&lt;br /&gt;@volatile var calculateXmlHeader: (NodeResponse, Node, Box[String]) =&amp;gt; String = {&lt;br /&gt;  case _ if S.skipXmlHeader =&amp;gt; ""&lt;br /&gt;  case (_, up: Unparsed, _) =&amp;gt; ""&lt;br /&gt;&lt;br /&gt;  case (_, _, Empty) | (_, _, Failure(_, _, _)) =&amp;gt;&lt;br /&gt;    "\n"&lt;br /&gt;&lt;br /&gt;  case (_, _, Full(s)) if (s.toLowerCase.startsWith("text/html")) =&amp;gt;&lt;br /&gt;    "\n"&lt;br /&gt;&lt;br /&gt;  case (_, _, Full(s)) if (s.toLowerCase.startsWith("text/xml") ||&lt;br /&gt;      s.toLowerCase.startsWith("text/xhtml") ||&lt;br /&gt;      s.toLowerCase.startsWith("application/xml") ||&lt;br /&gt;      s.toLowerCase.startsWith("application/xhtml+xml")) =&amp;gt;&lt;br /&gt;    "\n"&lt;br /&gt;&lt;br /&gt;  case _ =&amp;gt; ""&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;h4&gt;対策&lt;/h4&gt;LiftRulesに、responseTransformersというものがあり、ここでLiftResponseが送られる前に書き換えることができます。最終的に、Boot.scalaで以下の様なことをしてやると、好みの文字コードでレスポンスを返すことができました。&lt;br /&gt;&lt;pre class="scala" name="code"&gt;def boot {&lt;br /&gt;  //...省略...&lt;br /&gt;  LiftRules.responseTransformers.append(conv2sjis)&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;private def conv2sjis(org: LiftResponse): LiftResponse = {&lt;br /&gt;  org match {&lt;br /&gt;    case x: XhtmlResponse =&amp;gt;&lt;br /&gt;      S.skipXmlHeader = true // *1&lt;br /&gt;      val m = x.toResponse // *2&lt;br /&gt;      val h = x.headers ::: ("Content-Type", "text/html; charset=Shift_JIS") :: Nil // *3&lt;br /&gt;      InMemoryResponse(new String(m.data, "utf-8").getBytes("Shift_JIS"), h, m.cookies, m.code) // *4&lt;br /&gt;    case _ =&amp;gt; org&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;ol&gt;&lt;li&gt;XML宣言を付けないようにS.skipXmlHeader=trueにセット。&lt;/li&gt;&lt;li&gt;一度、InMemoryResponseに変換&lt;/li&gt;&lt;li&gt;Content-TypeヘッダをListに追加。&lt;/li&gt;&lt;li&gt;SJISに変換し、新たなInMemoryResponseを返す。（LiftResponseはケースクラスなので書き換えられない）&lt;/li&gt;&lt;/ol&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7854445846272488112-1804518186609658587?l=pomu0325.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://pomu0325.blogspot.com/feeds/1804518186609658587/comments/default' title='コメントの投稿'/><link rel='replies' type='text/html' href='http://pomu0325.blogspot.com/2010/02/lift-liftutf-8.html#comment-form' title='0 件のコメント'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7854445846272488112/posts/default/1804518186609658587'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7854445846272488112/posts/default/1804518186609658587'/><link rel='alternate' type='text/html' href='http://pomu0325.blogspot.com/2010/02/lift-liftutf-8.html' title='[Lift] Liftのテンプレートでutf-8以外のレスポンスを返す'/><author><name>pomu0325</name><uri>http://www.blogger.com/profile/07153996876142338944</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://2.bp.blogspot.com/_IngpC7Xbw78/S3OLp5iNDyI/AAAAAAAAAIc/R5UH2AMmiAo/S220/RIMG0357.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7854445846272488112.post-5494295810920667162</id><published>2010-02-12T00:25:00.016+09:00</published><updated>2010-02-25T12:58:41.210+09:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Scala'/><category scheme='http://www.blogger.com/atom/ns#' term='Lift'/><title type='text'>[Lift] Lift2.0-scala280で日本語テンプレートが使えない</title><content type='html'>&lt;h4&gt;2010-02-25追記&lt;/h4&gt;Lift 2.0-M3で治るそうです(&lt;a href="http://groups.google.com/group/liftweb/browse_thread/thread/3d37898cef4c6f5e/d2592d1c85627a8b#d2592d1c85627a8b" target="_blank"&gt;google-group&lt;/a&gt;)。テンプレートは必ずutf-8で！&lt;br /&gt;&lt;br /&gt;&lt;h4&gt;元記事&lt;/h4&gt;先日、せっかくだから最新を使おうと、Lift2.0-scala280(beta)に手を出してみました。API周りの修正はarchetype-basicで生成しなおしたプロジェクトを元にマージし、ビルドできるようになったと思いきや、Lift1.0.2では動いてたページが、以下のエラーで動作しなくなりました。&lt;br /&gt;&lt;pre class="java" name="code"&gt;Message: java.nio.charset.UnmappableCharacterException: Input length = 2&lt;br /&gt;       java.nio.charset.CoderResult.throwException(CoderResult.java:261)&lt;br /&gt;       sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:319)&lt;br /&gt;       sun.nio.cs.StreamDecoder.read(StreamDecoder.java:158)&lt;br /&gt;       java.io.InputStreamReader.read(InputStreamReader.java:167)&lt;br /&gt;       java.io.BufferedReader.fill(BufferedReader.java:136)&lt;br /&gt;       java.io.BufferedReader.read(BufferedReader.java:157)&lt;br /&gt;       scala.io.BufferedSource$$anonfun$1$$anonfun$apply$1.apply(BufferedSource.scala:29)&lt;br /&gt;       scala.io.BufferedSource$$anonfun$1$$anonfun$apply$1.apply(BufferedSource.scala:29)&lt;br /&gt;       scala.io.Codec.wrap(Codec.scala:65)&lt;br /&gt;       scala.io.BufferedSource$$anonfun$1.apply(BufferedSource.scala:29)&lt;br /&gt;       scala.io.BufferedSource$$anonfun$1.apply(BufferedSource.scala:29)&lt;br /&gt;       scala.collection.Iterator$$anon$13.next(Iterator.scala:145)&lt;br /&gt;       scala.collection.Iterator$$anon$24.hasNext(Iterator.scala:435)&lt;br /&gt;       scala.collection.Iterator$$anon$19.hasNext(Iterator.scala:326)&lt;br /&gt;       scala.io.Source.hasNext(Source.scala:209)&lt;br /&gt;       net.liftweb.util.PCDataXmlParser$$anonfun$apply$2$$anonfun$apply$4.apply(PCDataMarkupParser.scala:184)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;スタックトレースの示すPCDataMarkupParser.scala:184近辺を見てみたところ、scala.io.Source.fromInputStream(in)が使われていますが、Scala2.7.7と2.8.0で、エンコードを指定しなかった場合の動作が変わっています。2.7.7では、utf-8が使われます(*1)が、2.8.0だとSource.fromInputStreamは新しく導入されたCodecを引数に取るようになり、省略した場合はCodec.defaultが使用されます(*2)。&lt;br /&gt;&lt;br /&gt;&lt;ol&gt;&lt;li&gt;&lt;a href="http://www.scala-lang.org/docu/files/api/scala/io/Source$object.html#fromInputStream%28java.io.InputStream%29" target="_blank"&gt;2.7.7のscaladoc&lt;/a&gt;: &lt;q&gt;same as BufferedSource.fromInputStream(is, "utf-8", Source.DefaultBufSize)&lt;/q&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://www.scala-lang.org/archives/beta-api/scala/io/Source$.html" target="_blank"&gt;2.8.0-Betaのscaladoc&lt;/a&gt;: &lt;q&gt;codec (implicit) a scala.io.Codec specifying behavior (defaults to Codec.default)&lt;/q&gt;&lt;br /&gt;&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;上記APIの仕様変更により、日本語Windowsなうちの環境だとutf-8のテンプレートをMS932で読もうとして失敗しているわけですね。&lt;br /&gt;&lt;br /&gt;Scala2.8.0のライブラリの、Codec.defaultのソースを見てみると、&lt;br /&gt;&lt;pre class="scala" name="code"&gt;def default = apply(Charset.defaultCharset)&lt;br /&gt;&lt;/pre&gt;としているだけ（Charsetはjava.nio.charset.Charset）なので、JVMのシステムプロパティで-Dfile.encodingを設定してやればOKそうです。Mavenでjettyを起動しているので、MVN_OPTSに-Dfile.encoding=utf-8を追加したら問題なく動きました。&lt;br /&gt;&lt;br /&gt;&lt;a href="http://twilog.org/pomu0325/date-100202/asc" target="_blank"&gt;関連Tweet 2010-02-02 20:06:36～&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;運用環境だとそう簡単にJVMのシステムプロパティなど変えられないと思うので、どこかでテンプレートのエンコーディングを指定するか、もう必ずutf-8で読んでくれるかしれくれないと困りますね。LiftのMLに投げました。&lt;br /&gt;&lt;br /&gt;&lt;h4&gt;追記&lt;/h4&gt;MVN_OPTSで-Dfile.encoding=utf-8にしてNetBeansからrun:jettyで起動すると、コンソールの日本語が化けます。おそらくNetBeansも自身のJVMのfile.encoding（この場合はMS932）を使用してコンソールに出力しているためと思われます。&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7854445846272488112-5494295810920667162?l=pomu0325.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://pomu0325.blogspot.com/feeds/5494295810920667162/comments/default' title='コメントの投稿'/><link rel='replies' type='text/html' href='http://pomu0325.blogspot.com/2010/02/lift-lift20-scala280.html#comment-form' title='0 件のコメント'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7854445846272488112/posts/default/5494295810920667162'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7854445846272488112/posts/default/5494295810920667162'/><link rel='alternate' type='text/html' href='http://pomu0325.blogspot.com/2010/02/lift-lift20-scala280.html' title='[Lift] Lift2.0-scala280で日本語テンプレートが使えない'/><author><name>pomu0325</name><uri>http://www.blogger.com/profile/07153996876142338944</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://2.bp.blogspot.com/_IngpC7Xbw78/S3OLp5iNDyI/AAAAAAAAAIc/R5UH2AMmiAo/S220/RIMG0357.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7854445846272488112.post-1729376659437048338</id><published>2010-02-11T14:36:00.011+09:00</published><updated>2010-02-12T10:12:13.116+09:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Scala'/><category scheme='http://www.blogger.com/atom/ns#' term='OAuth'/><title type='text'>[Scala] ScalaでOAuth</title><content type='html'>lift-oauthはサーバ側（サービスプロバイダ）とのことなので、Scalaで使えるOAuthライブラリを探してみた。&lt;a href="http://dispatch.databinder.net/"&gt;dispatch&lt;/a&gt;に含まれてることが分かったので、早速試してみるが、演算子オーバーロード(*1)やらimplicit defやら使いまくりで非常に難解…。（*1: Scalaでは識別子に記号が使えるので演算子オーバーロードってのはホントは正しくない）&lt;br /&gt;&lt;br /&gt;Specsのテストコード(dispatch/oauth/src/test/scala/OAuthSpec.scala)が一番良いサンプルだったので抜粋してコード読解。&lt;br /&gt;&lt;pre class="scala" name="code"&gt;import org.specs._&lt;br /&gt;&lt;br /&gt;object OAuthSpec extends Specification {&lt;br /&gt;  import dispatch._&lt;br /&gt;  import oauth._&lt;br /&gt;  import OAuth._&lt;br /&gt;  &lt;br /&gt;  val svc = :/("term.ie") / "oauth" / "example" // *1, *2&lt;br /&gt;  val consumer = Consumer("key", "secret") // *3&lt;br /&gt;  &lt;br /&gt;  "OAuth test host" should {&lt;br /&gt;    "echo parameters from protected service" in {&lt;br /&gt;      val h = new Http&lt;br /&gt;      val request_token = h(svc / "request_token.php" &amp;lt;@ consumer as_token) // *4, *5, *6&lt;br /&gt;      val access_token = h(svc / "access_token" &amp;lt;@ (consumer, request_token) as_token)&lt;br /&gt;      val payload = Map("identité" -&amp;gt; "caché", "identity" -&amp;gt; "hidden", "アイデンティティー" -&amp;gt; "秘密", &lt;br /&gt;        "pita" -&amp;gt; "-._~")&lt;br /&gt;      h(&lt;br /&gt;        svc / "echo_api.php" &amp;lt;&amp;lt;? payload &amp;lt;@ (consumer, access_token) &amp;gt;% { { // *7&lt;br /&gt;          _ must_== (payload)&lt;br /&gt;        }&lt;br /&gt;      )&lt;br /&gt;    }&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;ol&gt;&lt;li&gt;&lt;b&gt;:/&lt;/b&gt; はobject。:/() == :/.apply() メソッドで、dispatch.Requestを返す。（http://っぽいから:/にしたと思われ）&lt;/li&gt;&lt;li&gt;&lt;b&gt;/&lt;/b&gt; はRequestクラスのメソッド。パスをつなげて新しいRequestを返す。&lt;/li&gt;&lt;li&gt;OAuthのコンシューマ・キー、コンシューマ・シークレットを保持するケースクラス。&lt;/li&gt;&lt;li&gt;Http.apply()メソッドにRequestインスタンスを渡してHTTPリクエストを投げている。&lt;/li&gt;&lt;li&gt;&lt;b&gt;&amp;lt;@&lt;/b&gt; はRequestSignerクラスのメソッド。Requestクラスをコンシューマ・シークレットで署名したりする。Request → RequestSignerはimplicit defされているのでそのまま呼べる。&lt;br /&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;as_token&lt;/b&gt; はHttp.apply()の第2引数として渡されるハンドラ。レスポンスのbodyに含まれるトークンをTokenインスタンスにして返す。&lt;/li&gt;&lt;li&gt;&lt;b&gt;&amp;lt;&amp;lt;?&lt;/b&gt; はRequestクラスのメソッドで、クエリ文字列を付加した新しいRequestを返す。&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;参考文献など：&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://databinder.net/sxr/dispatch-http/0.6.6/main/Http.scala.html" target="_blank"&gt;dispatch.{Http, Request}のソース&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://databinder.net/sxr/dispatch-oauth/0.6.6/main/OAuth.scala.html" target="_blank"&gt;dispatch.oauth._のソース&lt;/a&gt;&lt;/li&gt;&lt;li&gt;OAuthについては、&lt;a href="http://www.atmarkit.co.jp/fsecurity/special/106oauth/oauth01.html" target="_blank"&gt;@ITの記事&lt;/a&gt;が非常に参考になりました。&lt;/li&gt;&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7854445846272488112-1729376659437048338?l=pomu0325.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://pomu0325.blogspot.com/feeds/1729376659437048338/comments/default' title='コメントの投稿'/><link rel='replies' type='text/html' href='http://pomu0325.blogspot.com/2010/02/scala-scalaoauth.html#comment-form' title='0 件のコメント'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7854445846272488112/posts/default/1729376659437048338'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7854445846272488112/posts/default/1729376659437048338'/><link rel='alternate' type='text/html' href='http://pomu0325.blogspot.com/2010/02/scala-scalaoauth.html' title='[Scala] ScalaでOAuth'/><author><name>pomu0325</name><uri>http://www.blogger.com/profile/07153996876142338944</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://2.bp.blogspot.com/_IngpC7Xbw78/S3OLp5iNDyI/AAAAAAAAAIc/R5UH2AMmiAo/S220/RIMG0357.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7854445846272488112.post-6155189591285412340</id><published>2010-02-11T12:58:00.011+09:00</published><updated>2010-02-11T13:49:54.092+09:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Scala'/><title type='text'>[Scala] ListをMapに変換するコード</title><content type='html'>List("a", "1", "b", "2")をMap("a" -&amp;gt; "1", "b" -&amp;gt; "2")にする必要があって、ListをMapに変換するコードを再帰で書いてみた。&lt;br /&gt;&lt;pre class="scala" name="code"&gt;def l2m[T](l: List[T]): Map[T,T] = l match {case x::x2::xs =&amp;gt; Map(x -&amp;gt; x2) ++ l2m(xs) case _ =&amp;gt; Map()}&lt;br /&gt;&lt;/pre&gt;実行例：&lt;br /&gt;&lt;pre class="scala" name="code"&gt;scala&amp;gt; val l = List("a", "1", "b", "2")&lt;br /&gt;l: List[java.lang.String] = List(a, 1, b, 2)&lt;br /&gt;&lt;br /&gt;scala&amp;gt; def l2m[T](l: List[T]): Map[T,T] = l match {case x::x2::xs =&amp;gt; Map(x -&amp;gt; x2) ++ l2m(xs) case _ =&amp;gt; Map()}&lt;br /&gt;l2m: [T](List[T])Map[T,T]&lt;br /&gt;&lt;br /&gt;scala&amp;gt; l2m(l)&lt;br /&gt;res0: Map[java.lang.String,java.lang.String] = Map(a -&amp;gt; 1, b -&amp;gt; 2)&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/7854445846272488112-6155189591285412340?l=pomu0325.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://pomu0325.blogspot.com/feeds/6155189591285412340/comments/default' title='コメントの投稿'/><link rel='replies' type='text/html' href='http://pomu0325.blogspot.com/2010/02/test.html#comment-form' title='2 件のコメント'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7854445846272488112/posts/default/6155189591285412340'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7854445846272488112/posts/default/6155189591285412340'/><link rel='alternate' type='text/html' href='http://pomu0325.blogspot.com/2010/02/test.html' title='[Scala] ListをMapに変換するコード'/><author><name>pomu0325</name><uri>http://www.blogger.com/profile/07153996876142338944</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='24' src='http://2.bp.blogspot.com/_IngpC7Xbw78/S3OLp5iNDyI/AAAAAAAAAIc/R5UH2AMmiAo/S220/RIMG0357.JPG'/></author><thr:total>2</thr:total></entry></feed>
