2010年12月31日金曜日

[Lift] Lift2.2でCookieが無い場合にjsessionidがリンクに付加されない

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
#658 Supressing URL rewriting for jesessionid from Lift の修正により、デフォルトでjsessionidが付かないように動作が変わっています。

Boot.scalaで、
LiftRules.encodeJSessionIdInUrl_? = true
してやればOK。

Liftで携帯サイト作ってる人とかはアップデートの際に要注意!

2010年12月30日木曜日

[Scala] dispatchでContent-Length付きのmultipartなPOSTする方法

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
とある写真投稿サービスのAPIにdispatch(0.7.8)を使って実装しようとした時のこと。APIのレスポンスでContent-Lengthが無いって怒られました。

dispatch-mimeを使うとmultipart/form-data形式でPOSTができるのですが、普通に実装するとContent-Lengthを送ってくれないようです。

ソースコメントにも書いてあるし。
Note that when using an InputStream generator, chuncked encoding will be used with no Content-Length header

Fileが引数の方を使えば良さげだけどGAEからだとFile使えません。dispatchのソースとapache-httpclientのjavadoc眺めて工夫したらできました。

import dispatch._
import dispatch.Http._
import dispatch.mime.Mime._
import org.apache.http.entity.mime.content.ByteArrayBody

var req = ... //普通にdispatchのRequest作る
req = req next req.add(paramName, new ByteArrayBody(data, contentType, filename)) //ByteArrayBodyを指定

※ただし、ByteArrayBodyはhttpclient 4.1(現在BETA1)から追加されるクラスなので4.1のjarを使わないとダメ(dispatch 0.7.8のdependencyはhttpclient 4.0になってた)。
sbtならこんな感じで。
val httpmime = "org.apache.httpcomponents" % "httpmime" % "4.1-beta1"

2010年9月6日月曜日

[Lift][GAE] Scala座01でLTしてきました

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
第1回Scala座に参加してきました。実はスタッフとして少々お手伝いもしたのですが大した事ないので(ry

収穫は…
  • いろいろと勉強不足だなぁと刺激を受けた(FP的な意味で)
  • Twitter等で気になっていた人々にリアルで会えた
  • ワラビモチ!←マイブーム
といったとこでしょうか。

「Lift on GAE/J」ネタでLTさせてもらったのですが、先にまとめてしまうと、「LiftはGoogle AppEngineには向いてない」てことです。詳しくは「Lift on GAE/J」のスライド見てください。

LTやろうと思ったきっかけは、自分でLiftも(Scalaも)GAEもよく分かってないうちにLift+GAEという組み合わせを選んでしまったのですが、いろいろと苦労したので… 最近TwitterでLiftをGAEで動かしてみるとかのtweetを時々見かけ、危険だよー、ということを広めたかっただけです。

ただ、勘違いしないで欲しいのは、Liftがダメと言っている訳ではなく、LiftはフルスタックのWebフレームワークとして、慣れれば生産性よくAJAX/Cometを使ったリッチなWebアプリを作れる素晴らしいフレームワークだと思っています。クラウドという性質上、GAEには様々な制限があるので、GAE上で動かしたいのであれば他のもう少し軽量なフレームワークが向いていると思います(まだあまり試せていませんが…)。

Scala座の企画をして下さった@keisuke_nさん、Scalaを勉強していく上で参考にさせていただいたり助けていただいたりで本当にいろいろとお世話になっている@yuroyoroさん、@kmizuさん、@takedasoftさん、会場・懇親会でお話させていただいたみなさま、本当にありがとうございました。ぜひ第2回でもよろしくお願いします!

2010年5月15日土曜日

[OAuth] 携帯でTwitterのOAuthを無理やりやってみた

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
2011-07-04追記: 今はTwitterのOAuth画面もガラケー対応されてます。自動でリダイレクトはされないのでユーザによるクリックは相変わらず必要ですが。

モバイルフォースクエアの開発初期に、OAuthでTwitterの認証をやろうとしていた時に試した結果です。手元にあったAU,docomoの端末で確認しています。

AU(W61CA)でTwitterのOAuthをやってみた

ログイン画面 承認画面
特に問題なし! 自動でリダイレクトはされないが、リンクを手動でクリックすれば動作はOK!

docomo Cookie対応機種(N-07A)でTwitterのOAuthをやってみた

ログイン画面 承認画面
文字化けがひどいが、ID・パスワード欄っぽいところに入れて1つ目のボタン(ログイン)を押せば承認画面に飛ぶ。 自動でリダイレクトはされないが、リンクを手動でクリックすれば動作はOK!

docomo Cookie非対応機種(SH704i)でTwitterのOAuthをやってみた

ログイン画面 承認画面
両方とも送信ボタン…。1つ目を押せば承認画面に飛ぶ。 文字化けがひどいが、リンクを手動でクリックすれば動作はOK!

まとめ

 docomoだと文字化けがひどいが、OAuthの機能的には動作する。ただし最後のリダイレクトを手動でクリックしないといけないのでユーザにこれを強いるのは難しい。
※実際、OAuth開始前の画面に注意書きを入れたにもかかわらず、最後のリダイレクトのクリックが分からず、トークンが取得できていないユーザ多数いた
 PIN入力方式にすればよいが、PC・スマートフォンでも同じコンシューマキーを使いたいので、PC・スマートフォンでのユーザビリティが落ちるので却下。PCでOAuth認証だけ先にさせるのも携帯のみのユーザのことを考えると却下。
→結果、xAuthを使うことにした。

[OAuth] 携帯でfoursquareのOAuthを無理やりやってみた

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
モバイルフォースクエアの開発初期に、OAuthでfoursquareの認証をやろうとしていた時に試した結果です。手元にあったAU,docomoの端末で確認しています。

AU(W61CA)でfoursquareのOAuthをやってみた

ログイン画面 承認画面
日本語文字化け… 文字化け&Denyが押せないが、動作はOK!

docomo Cookie対応機種(N-07A)でfoursquareのOAuthをやってみた

ログイン画面 承認画面


デザインは崩れるが、動作はOK! デザインは崩れるが、動作はOK!

docomo Cookie非対応機種(SH704i)でfoursquareのOAuthをやってみた

ログイン画面→表示が崩れ、ログインボタンを押しても同じ画面が繰り返し表示されるだけ…。
※当時はログインできていた記憶が…、サーバ側の実装が変わったかもしれないです


まとめ

Cookie対応機なら文字化けしたりデザインは崩れるが、OAuthの処理自体は正常。文字化けがひどい状態ではサービス提供できないので、foursquareの中の人に日本の携帯ブラウザ事情を説明し、Auth Exchange採用。

2010年4月27日火曜日

[Scala][GAE] dispatchのOAuthをGAEで使う

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
dispatchが内部でcommons-httpclientを使っていて、GAEでの禁止クラスを使っているのでそのままだとエラーになった。ので無理やりimplicit defでURLFetchのHTTPRequestに変換することで回避してます。こんな感じ。

import dispatch._
import com.google.appengine.api.urlfetch._
import java.net.URL

object DispatchHelper {
  /**
   * convert dispatch.Request -> String
   */
  implicit def r2s(r: Request) = {
    r.host.get + r.req.getRequestLine.getUri
  }

  /**
   * convert dispatch.Request -> com.google.appengine.api.urlfetch.HTTPRequest
   */
  implicit def dispatch2gae(r: Request): HTTPRequest = {
   val isPost = r.req.getMethod == "POST"
   val g = new HTTPRequest(new URL(r), if (isPost) HTTPMethod.POST else HTTPMethod.GET, FetchOptions.Builder.withDeadline(10))
   r.req.getAllHeaders.foreach(h => {g.addHeader(h); println(h)})
   r.req match {
    case p: org.apache.http.client.methods.HttpPost =>
     val payload = new java.io.ByteArrayOutputStream
     p.getEntity.writeTo(payload)
     g.setPayload(payload.toByteArray)
     println(p.getEntity.getContentType)
    case _ =>
   }
   g
  }
}

こんな感じでimplicit defを宣言してimport DispatchHelper._すれば、dispatchでリクエストを作って、URLFetchService.fetch()の引数にそのまま渡せばOK。

dispatchの解説はこちら

2010-06-26追記

2chで質問受けてコードが一部足りなかったので追記しました。

2010年3月25日木曜日

[Scala] Elemのlabel(要素名)を書き換える

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
こんな感じか?
def changeTagName[T >: Node](from: String, to: String)(in: T): T = in match {
  case Elem(p, l, a, ns, child @ _*) => 
    val label = if (l == from) to else l
    Elem(p, label, a, ns, child.map(changeTagName(from, to)(_)): _*)
  case x => x
}

使用例

scala> val hoge2moge = changeTagName("hoge", "moge") _
scala> hoge2moge: (scala.xml.Node) => scala.xml.Node = <function>

scala> hoge2moge(<pre:hoge attr="value">parentText<child:hoge>childtext</child:hoge></pre:hoge>)
scala> res17: scala.xml.Node = <pre:moge attr="value">parentText<child:moge>childtext</child:moge></pre:moge>

追記:さらに汎用化してみた

高階関数化して、引数に名前を変更するための関数を渡せるようにした。
def changeTagName[T >: Node](changeFunc: (String) => String)(in: T): T = in match {  
  case Elem(p, l, a, ns, child @ _*) =>   
    Elem(p, changeFunc(l), a, ns, child.map(changeTagName(changeFunc)(_)): _*)  
  case x => x  
}

使用例

お題はこちら。
scala> val c = changeTagName(s => if (List("moge", "koge").contains(s)) "bege" else s) _
c: (scala.xml.Node) => scala.xml.Node = <function>

scala> c(x)
res6: scala.xml.Node =
<lage>
         <hoge>
           <bege></bege>
           <bege></bege>
         </hoge>
         <hoge>
           <bege></bege>
           <bege></bege>
         </hoge>
       </lage>

23:52 さらにちょっと修正

flatMap使えば別にNodeSeq渡せることに気がついた。
def changeTagName(changeFunc: (String) => String)(in: NodeSeq): NodeSeq = in match {  
  case Elem(p, l, a, ns, child @ _*) =>   
    Elem(p, changeFunc(l), a, ns, child.flatMap(changeTagName(changeFunc)(_)): _*)  
  case x => x  
}