2011年2月9日水曜日

[Scala][sbt]DefaultWebProjectでwarではなくjarも作りたい

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
DefaultWebProjectだとsbt packageタスクでwarが作られますが、jarも作りたい時があった場合、こんな感じで。
lazy val jar = packageTask(packagePaths, jarPath, packageOptions).dependsOn(compile) describedAs "Creates a jar file."
sbt jarでjarファイルが作られます。

2011年2月7日月曜日

[GAE] bulkloaderをGAE/Jで使う

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
python版appengine-sdkを使ってcsvファイルからデータをDatastoreにロードすることができます。

準備

web.xmlにremote_api用のServletの設定を書く

<servlet>
  <servlet-name>remoteapi</servlet-name>
  <servlet-class>com.google.apphosting.utils.remoteapi.RemoteApiServlet</servlet-class>
</servlet>
<servlet-mapping>
  <servlet-name>remoteapi</servlet-name>
  <url-pattern>/remote_api</url-pattern>
</servlet-mapping>
security-constraintも追加しておいた方が良さげ。
<security-constraint>
  <web-resource-collection>
    <url-pattern>/remote_api</url-pattern>
  </web-resource-collection>
  <auth-constraint>
    <role-name>admin</role-name>
  </auth-constraint>
</security-constraint>

ロード設定用のyamlファイルを書く

python_preamble:
- import: base64
- import: re
- import: google.appengine.ext.bulkload.transform
- import: google.appengine.ext.bulkload.bulkloader_wizard
- import: google.appengine.api.datastore
- import: google.appengine.api.users

transformers:

- kind: Scores
  connector: csv

  connector_options:
    encoding: ms932

  property_map:
    - property: __key__
      external_name: id
      import_transform: int

    - property: name
      external_name: name

    - property: score
      external_name: score
      import_transform: transform.none_if_empty(float)
  • キーにidではなく文字列(name)を使用する場合は、import_transform: intは不要。
  • import_transformに他に指定できる例
    日付: transform.import_date_time('%Y/%m/%d %H:%M')
    固定値: import_transform: lambda x:1
    数値変換: import_transform: lambda x:int(x)*100
    ※lambdaは引数の:の後にスペースを空けるとダメな様です

    ロード用CSV
    id,name,score
    1,Alice,5.0
    2,Bob,2.5
    3,Caroline,
    

    ロード用コマンド

    appcfg.py upload_data --filename=test.csv --config_file=test.yml --url=http://{appid}.appspot.com/remote_api --application={appid} --kind=Scores -v
    
    ※{appid}はアプリケーションIDに置き換える

    security-constraintの設定をしている場合、毎回管理者アドレス/パスワードの入力が必要です。
    ローカルのdev_appserverだとエラーが出て動きませんでした…

    こんな感じに反映されます。
    ※1万件だと約500秒(8分ちょっと)くらいかかりました(CPU Timeを無料Quotaの10%ほど消費します)

    その他注意点

  • slim3を使っている場合、filterの設定があると、"ProtocolBufferDecodeError: corrupted"の様なエラーが出てしまうので一時的にコメントアウトして回避しました。(※他にいい方法があれば教えて下さい…

    2/11追記: bulkload用にfilterをコメントアウトしたversionをデプロイしておいて、--url=http://latest.bulkload.{appid}.appspot.com/remote_api みたいに指定するといいかも

    3/22追記:slim3ではなくscenic3の方の設定でした。matcherで/remote_apiを除外したらOK。

    参考: Ikai Lan says / Using the bulkloader with Java App Engine
  • 2011年1月15日土曜日

    [Blogger] スマートフォン用テンプレートを有効にする

    このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
    いつの間にかβ版としてスマートフォン用テンプレートの機能が追加されてたので有効にしてみた。

    draft.blogger.com の方からログインするとβ版機能が使えます。

    有効にするには、設定 > メールとモバイル > モバイル テンプレートを表示する で はい を選ぶだけ。スマートフォンの場合は自動でリダイレクトしてくれるようです。

    ガラケーからもurlの最後に?m=1を付けるとモバイル用テンプレートで表示してくれましたが、ページ遷移にjavascriptが使われているようで残念ながら記事一覧以降進めませんでした…。

    2011年1月12日水曜日

    [Lift] Liftで処理したくないリクエストの設定

    このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
    web.xmlで全てのリクエストがLiftで処理されるようになっているので、Lift以外のサーブレット等で処理したい場合に困ります…
    <filter-mapping>
      <filter-name>LiftFilter</filter-name>
      <url-pattern>/*</url-pattern>
    </filter-mapping>
    

    Boot.scalaのdef bootにこんな感じで書けばOK。
    LiftRules.passNotFoundToChain = true
    LiftRules.liftRequest.append {
      case Req("_ah" :: _, _, _) => false
      case Req("remote_api" :: _, _, _) => false
    }
    

    ※filter-mappingで除外ルールが書ければいいんだけど…

    2011年1月1日土曜日

    [Scala] sbt-appengineなプロジェクトをIntelliJ IDEAでデバッグ

    このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
    @teaplanetさんにヒントいただいてIDEAのsbt consoleでdev-appserver-startできるようになったので、IDEA上でブレークポイントで止めたりできるように設定してみた。

    class MyProject(info: ProjectInfo) extends AppengineProject(info) {
      //... 中略
      //APPENGINE_SDK_HOME設定の代わり
      override val appengineSdkPath = Path.fromFile("/Users/pomu0325/dev/appengine-java-sdk-1.4.0")  
      
      //dev-appserver-startの際のJVMオプション
      override val devAppserverJvmOptions = List("-Xdebug", "-Xrunjdwp:transport=dt_socket,server=y,address=2011,suspend=y") ++ super.devAppserverJvmOptions 
    }
    

    この設定でsbt consoleからdev-appserver-startするとリモートデバッガで接続できる状態で起動するので、後はIDEAからデバッガ接続する。

    IDEAのメニューからRun -> Edit Configurations -> [+] -> Remote を選んで、Nameは適当に、PortにdevAppserverJvmOptionsのaddress=xxxxで指定したポートを入れる(↑の例だと2011。適当に開いてるポート設定すればOK)。

    設定したら、Run -> Debug... で選んでデバッグ開始するとdev-appserverのJVMにアタッチされてブレークポイントで止まってくれる。

    ※Before launchの"Run sbt action"でdev-appserver-startが指定できればsbt consoleからいちいちdev-appserver-startしなくても一発で起動できそうだけど一覧に出てこないし、直接指定してもダメでした…

    参考:sbt-appengineの基本的な設定等は"sbtでGAEをする場合の注意点など色々。"が参考になります。

    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"