HerokuでGrailsを使うとクエリパラメータが文字化けするのでbuildpack直しました。
TL;DR
HerokuではGrailsを簡単に(普通のGrailsプロジェクトをgit pushするだけで)デプロイすることができます。ですが、(少なくともHerokuがデフォルトで使う)Tomcat7ではクエリパラメータが文字化けします。Grailsで使うTomcat7のクエリパラメータをUTF-8でエンコードするようにするビルドパックをつくりました。(フォークして一行変えただけだけど)
問題
ローカルでGrailsを使って開発しているときには気づかなかったのですが、Herokuにデプロイするとクエリパラメータに日本語を使うとめっちゃ化けてしまいました。もうTomcatのことなんてすっかりわすれていたので、原因調査にめっちゃ時間かかりました。。。
それに付随していろいろわかったのでまとめておきます。
調査方針
次の方針で調査しました。結果としては最後の他の何かで、それはTomcatの設定だったんですけど、これをHerokuの場合にはどうするか?というのがHeroku初心者ながらにがんばった。
Viewの文字コード
Grails2.3.11でプロジェクトを新規作成すると、すでにConfig.groovyにUTF-8指定されています!
grails { views { gsp { encoding = 'UTF-8' htmlcodec = 'xml' // use xml escaping instead of HTML4 escaping codecs { expression = 'html' // escapes values inside ${} scriptlet = 'html' // escapes output from scriptlets in GSPs taglib = 'none' // escapes output from taglibs staticparts = 'none' // escapes output from static template parts } } // escapes all not-encoded output at final stage of outputting filteringCodecForContentType { //'text/html' = 'html' } } }
また、念のためgspのmetaタグにUTF-8を入れてみたけど変わりませんでした。
サーバーサイドでクエリパラメータをとってくるときにUTF-8になっていないのではないか
これもGrails2.3.11でプロジェクトを新規作成すると、既にConfig.groovyにUTF-8指定されています。
grails.converters.encoding = "UTF-8"
他のなにか
GrailsではクエリパラメータはContoller内でparamsというプロパティに格納されています。そこで次のような取得をすると文字化けのタイミングがわかりました。
- params → 文字化けしている
- request.getQueryString() → 文字化けしていない(requestから直接クエリーパラメータ部分の文字列を取得する)
paramsはGrailsParameterMapというクラスなのですが、コンストラクタでHttpServletRequestクラスのgetParameterMapメソッドから値を取得しています。具象クラスはこの場合は実際に利用しているサーバになってくるので、この場合はTomcatです。
ということで、Tomcatのクエリパラメータのエンコードをいじればよさそうです。
ここで、Tomcatの設定を変更する方法なのですが、組み込みTomcatの場合には次の方法がありました。
- org.grails.plugins.tomcat.ForkedTomcatCustomizerというクラスをつくる。 つくる場所はsrc/main/groovy配下になります。イメージとしてはこんな感じで、void customize(Tomcat tomcat) というメソッドを実装して設定を変更します。
package org.grails.plugins.tomcat import org.apache.catalina.connector.Connector import org.apache.catalina.startup.Tomcat class ForkedTomcatCustomizer{ void customize(Tomcat tomcat) { def c = new Connector(protocol: "HTTP/1.1", port: 8080, URIEncoding: "utf-8", redirectPort: 8443) tomcat.service.findConnectors().each {println "${it.protocol} ${it.URIEncoding}"} tomcat.service.addConnector(c) } }
前まではEvents.groovyで出来た気がしたのですが、どうもEvents.groovyに以前のようなTomcat用のイベントがこなくなったらしく、上記のような方法になったようです。
でも、私がやりたいのはHerokuのTomcatでした。
最初、この方法で組み込みTomcat以外の設定もオーバーライドされるのかと思ったのですが、そんな感じではなかったようです。で、ローカルにあるTomcatならserver.xmlを変更すればいいのですが、HerokuにあるTomcatを変更するとなると。。。って思い、buildpackを変更することにしました。
HerokuのGrailsビルドパックはJettyもしくはTomcatを利用するようになっていて、Tomcatの場合には別の場所からwebapp-runnerという別のリポジトリで管理されているtomcatをまるっとくるめたjarをダウンロードしてきて使っています。tomcatの設定を変更するにはこのwebapp-runnerをいじればよさそうです。
最新版である7.0.40.1をダウンロードしてきてヘルプを見てみます。
curl http://repo2.maven.org/maven2/com/github/jsimone/webapp-runner/7.0.40.1/webapp-runner-7.0.40.1.jar > webapp-runner.jar java -jar webapp-runner.jar --help
するとなんと--uri-encodigっていうオプションで指定できるよ!とか書いてあるじゃないですか!! やったね!
ということで、Grailsのbuildpackで指定しているRUNNER_OPTSという環境変数に--uri-encoding utf-8って指定をすればいいのですが、buildpack見てみると、ダウンロードしているwebapp-runner.jarが7.0.40.0で古くて、--uri-encodingに対応していない。。。
ということで、これをフォークして7.0.40.1のjarをダウンロードするようにしました。あとはフォークしたbuildpackを使えば問題ない感じです。
forkしたプロジェクトはこちら。(Pull Reqしたほうがいい気はしているが、フォークもとのブランチ戦略がよくわかっていなくっていま出していいのかよくわからん。
まとめ
2014/10/13現在で、Grails + Tomcatで出来るだけ簡単にHerokuにデプロイしてクエリパラメータを文字化けさせないためには次をする。
環境変数を追加する(コマンドで追加する場合)
heroku config:add BUILDPACK_URL=https://github.com/kyonmm/heroku-buildpack-grails heroku config:add RUNNER_OPTS=--uri-encoding utf-8
Githubのherokuボタンを利用しているなら、app.jsonにも同じように追加しましょう。
{ "env": { "BUILDPACK_URL": "https://github.com/kyonmm/heroku-buildpack-grails", "RUNNER_OPTS": "--uri-encoding utf-8", } }
補足
Jettyを使えばこんな問題とはおさらば出来ます!!ですが、Grailsのビルドパックで利用しているjetty-runnerの実装がイケていなくって、起動時にタイムアウトが頻発してしまいます(Herokuではアプリケーション起動に60秒以上かかってはいけない)。これは、環境変数PORTがjetty-runnerなかなか割り当てられないためのようです。これを解決するrunnerを書いている人もいるのですが、いまいち最新に追いついているのかわからなかったので今回は利用を見送りました。
そのうちJettyを使うならこうやるよ!っていうのを試してみたいです。
超補足
Heroku本買いました。わかりやすくて助かります。
プロフェッショナルのための 実践Heroku入門 プラットフォーム・クラウドを活用したアプリケーション開発と運用 (書籍)
- 作者: 相澤歩,arton,鳥井雪,織田敬子
- 出版社/メーカー: KADOKAWA/アスキー・メディアワークス
- 発売日: 2014/09/19
- メディア: 大型本
- この商品を含むブログ (2件) を見る
- 作者: Glen Smith,Peter Ledbrook
- 出版社/メーカー: Manning Pubns Co
- 発売日: 2014/07/24
- メディア: ペーパーバック
- この商品を含むブログを見る
- 作者: Burt Beckwith
- 出版社/メーカー: O'Reilly Media
- 発売日: 2013/04/23
- メディア: Kindle版
- この商品を含むブログを見る
Spockの知られざる機能
僕だけが知らなかったのかもしれません。。。
Spockではテストメソッドに@Unrollとつけると、パラメタライズがテストメソッド名に反映されます。これとっても見やすくてよいです。
こんな感じ。。。
class WhenWatchingUstream extends Specification { @Unroll def "should show #channel when click #channel window"(){ expect: // click channel where: channel << ["Cooking", "Music", "Animation"] } }
にしておくと
ところがです。テストメソッドが大量になってくると、テストメソッドのたびに@Unrollとつけるのが鬱陶しくなります。JUnit4の@Testから逃れたと思ったら、今度は@Unrollがおってくる><
Unrollの定義をみるとだ
ところでUnrollの定義を見ると次のようになっています。
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD}) @ExtensionAnnotation(UnrollExtension.class) public @interface Unroll { String value() default ""; }
つまり、クラス宣言につけられると。つまり、クラスにつけておけばそのクラスのテストメソッドは全てUnrollしたことと一緒になります!!!!(今日初めて知った
つまりこんな感じにできます。
@Unroll class WhenWatchingUstream extends Specification { def "should show #channel when click #channel window"(){ expect: // click channel where: channel << ["Cooking", "Music", "Animation"] } def "should show #sns message when send #sns message"(){ expect: // send sns message where: sns << ["Facebook", "Twitter"] } def "Not Parameterize Feature"(){ when: new Object() then: assert true } }
まとめ
@Unrollはクラス宣言につけましょう。
Spockのテストレポートが想像以上に凄い件について
タイトルはホッテントリメーカーを使いました。http://pha22.net/hotentry/tb/r?word=Spock%E3%81%AE%E3%83%86%E3%82%B9%E3%83%88%E3%83%AC%E3%83%9D%E3%83%BC%E3%83%88&phrase=9
全国49万のSpockユーザのみなさま。SpockのMLを見ていると思うので、ご存知かもしれませんがSpockのテストレポートをご存知でない方もいると思うので紹介します。
Spockのテスト結果はだいたいみんなGradleで見ている
Spockは言わずと知れたUnitTestingFramework界最強といわれるテスティングフレームワークですが、これのテストレポートは通常はJUnitのテストレポートXMLであり、多くのSpockユーザはGradleでビルドをしてGradleが生成するテストレポートを見ている事かと思います。
ですが、私は常々困り果てていて、つまりSpockはBDDを強く支援するテスティングフレームワークであるのに、テストレポートには活かされていないということでした。(JUnitのテストレポートXMLがクソだから仕方ない。)
Spock-Reports
そこで私はいつもオリジナルテストランナーを書いて、状態遷移図やテストレポートを生成していたのですが、実は素晴らしいOSSがあったのです。
それはSpock-Reportsです。見たほうが早いでしょう。ということで、実行結果のスクリーンショットは次の通りです。
renatoathaydes/spock-reports · GitHub
サマリー画面
テストクラス毎の詳細画面
使い方
ビルドスクリプトから使うことを前提としてます。
Gradleならこんな感じです。
repositories { jcenter() } dependencies { compile "org.codehaus.groovy:groovy-all:2.3.+:indy" testCompile "org.spockframework:spock-core:0.7-groovy-2.0" testCompile "com.athaydes:spock-reports:1.2" // ←これを追加する感じ }
Spock-ReportsはGroovy2.0以上、Spock0.7以上が必須です。
今回のテストコードはだいたいこんな感じです。(クソなコードだけど、出力用なのでゆるして。)
package org.kyonmm import spock.lang.Specification class WhenAddingProductBacklogItem extends Specification { def "All ProductBacklogItem should be sorted by Priority"() { given: "Product Backlog has 3 PBIs" when: "Product Owner add 1 PBI into Product Backlog" then: "All PBI is sorted by Priority " (PB + PBI).sort().last() == "c" where: PB | PBI ["a", "b", "c"] | "bb" ["a", "b", "c"] | "cc" } def "High Priority ProductBacklogItems should be estimated"() { given: "Product Backlog has 3 PBIs" when: "Product Owner add 1 PBI into Product Backlog" then: "Half of All PBIs is estimated" where: PB | PBI ["a":1, "b":null, "c":null] | ["ab":5] ["a":1, "b":null, "c":null] | ["ab":5] } }
だいたいの説明
各画面を見るとわかると思いますが、言葉としては次のようなイメージです。(つまりこれはBDDを強く意識している)
まとめ
BDDスタイルにSpockを使うことやレビューしやすさをあげるなら、Spock-Reportsを使わないと損です。
- 作者: 関谷和愛,上原潤二,須江信洋,中野靖治
- 出版社/メーカー: 技術評論社
- 発売日: 2011/07/06
- メディア: 単行本(ソフトカバー)
- 購入: 6人 クリック: 392回
- この商品を含むブログ (155件) を見る
Bdd in Action: Behavior-driven Development for the Whole Software Lifecycle
- 作者: John Ferguson Smart
- 出版社/メーカー: Manning Pubns Co
- 発売日: 2014/08/31
- メディア: ペーパーバック
- この商品を含むブログ (1件) を見る
The RSpec Book (Professional Ruby Series)
- 作者: David Chelimsky,Dave Astels,Zach Dennis,角谷 信太郎,豊田 祐司,株式会社クイープ
- 出版社/メーカー: 翔泳社
- 発売日: 2012/02/22
- メディア: 大型本
- 購入: 7人 クリック: 141回
- この商品を含むブログ (18件) を見る
GradleでREPLするプラグインを紹介しました #JGGUG
一ヶ月前になりますが、JGGUGが主催しているG* WorkShopでGradleのプラグインについて発表しました。LTとして。
わいわいGroovy ~ 教えてG*小ネタ大会 - JGGUG G*ワークショップZ Jun 2014 - 日本Grails/Groovyユーザーグループ | Doorkeeper
朗報
発表当初はGradle2.0で動作しませんでしたが、groovyshプラグインが1.0になったことでGradle2.0で動作するようになりました。スライドでは0.4.0を使っていますが、1.0を使いましょう。
スライドはこちらです。
プラグイン自体の設定方法はここを見ましょう。
Gradle Plugin: com.github.tkruse.groovysh
ちなみに
Gradleは今後上記のプラグインポータルサイトで管理されるようになるし、たった1行でサードパーティpluginを使えるようになりました。(今までは本当に大変だった)
Gradle自体のコード補完はまだIDEでは貧弱なので、こういったREPLを使う方が、マルチプロジェクトの設定やプラグイン自体の開発や、タスク定義のトライアンドエラーに役立つと思います。
いまIDEで補完してくれるのは、ライブラリのIDとかですね。(groupid, artifactid, version とか)
Have a nice build !
- 作者: Benjamin Muschko,Hans Dockter
- 出版社/メーカー: Manning Pubns Co
- 発売日: 2014/03/09
- メディア: ペーパーバック
- この商品を含むブログを見る
- 作者: Tim Berglund
- 出版社/メーカー: O'Reilly Media
- 発売日: 2013/07/16
- メディア: Kindle版
- この商品を含むブログを見る
Gradle Effective Implementation Guide
- 作者: Hubert Klein Ikkink
- 出版社/メーカー: Packt Publishing
- 発売日: 2012/10/25
- メディア: Kindle版
- この商品を含むブログ (1件) を見る
Groovy言語のWebサイトと言語リファレンスが新しくなりました
The Groovy programming language
Groovy言語のWebサイトが新しくなりました。 また以前から少しずつ書かれていた言語リファレンスについては刷新されてだいぶ体系的で見やすくなりました。(まだTBDな部分は多いですが、かなりマシになりました)
The Groovy programming language - Documentation
面白いのは比較的簡単にこのWebサイトにパッチを送れるようになっていることです。
DocumentationのWebページの右上には「improve this doc」としてボタンがあってクリックするとgithubのリンクになっていたりと。既にいくつかpull requestが取り入れられたと聞いています。
ということで、今後はGroovy言語についてこちらのWebサイトを参照するようにしましょう!!
- 作者: 関谷和愛,上原潤二,須江信洋,中野靖治
- 出版社/メーカー: 技術評論社
- 発売日: 2011/07/06
- メディア: 単行本(ソフトカバー)
- 購入: 6人 クリック: 392回
- この商品を含むブログ (155件) を見る
- 作者: Dierk Konig,Guillaume Laforge,Paul King,Cédric Champeau,Hamlet D'arcy
- 出版社/メーカー: Manning Pubns Co
- 発売日: 2014/10/31
- メディア: ペーパーバック
- この商品を含むブログを見る
Groovy基礎勉強会開催報告 #GroovyBase
イベント募集ページ:【Groovy基礎勉強会 - connpass】
Togetter:【2013/03/09(#GroovyBase)Groovy基礎勉強会 - Togetterまとめ】
スライド
感想
名古屋以外での基礎勉強会は初めてだったのですけれど、無事におわってよかったです。
JGGUGの方達をはじめとして、たくさんの方にご支援いただいてうまくいきました。
僕はGroovyのザックリとしたお話を発表という形にして、個々のGroovyの基礎について他の発表者が担当してくださいました。
僕はちょこちょことGroovyを2年くらいつかっている感じなので歴史と言っても経験があるのは1.7.xとか1.8Betaくらいで、それ以前の情報はブログとかがメインで調べた感じです。
実際に1.7.xの頃にググると1.5以前のものがそれなりに多かった印象です。
Groovyはそのツールの利便性もあって、ASTと実装を比較しながら追えるのがいいところなのですが、発表者がほとんどASTについてふれながら仕組みの解説をしていたのがGroovyならではだなぁと思いました。
わけわからない系の話は結構少なかったとは思うのですが、知らないところもたくさんあったり、他の方がこれからよりGroovyを使いこなすエントリポイントにはなったなら幸いです。
資料リンク
id:orange_clover さんのブログがいい具合にまとめてくれています。
【Groovy基礎勉強会 に参加してきました。 #GroovyBase - みちしるべ】
最後に
発表してくださった @uehaj @pocketberserker @mike_neck @kiy0taka @nobeans @nobusue @ touchez_du_bois
準備を手伝ってくださった @inda_re @y_sumida @orange_clover
ありがとうございました!
また、翌日のNagoya.Testing にも会場をお貸しいただいたOracleさん本当にありがとうございました。当日もたくさんお手伝いいただきまして。
また、何かの機会にGroovyのイベントを開催できたらいいなって思います。
ネットに転がっているGroovyスクリプトを直接実行する
ネットワーク上にあるGroovyスクリプトを直接実行できます。
groovy https://gist.github.com/kyonmm/5187373/raw/ghello.groovy
Groovyがインストールされている環境で上のコードを実行すると、SwingでHello Worldがでてくるはずです。
(実はこれは100文字程度でSwingが書けるというGroovyの素晴らしさの宣伝でもある。
Groovy1.8.3からの機能のようです。
Windows7とMac OSXで動作を確認しました。
動作するときは、一旦ダウンロードしてきたGroovyスクリプトをローカルで実行するイメージです。
なので、カレントディレクトリは実行時のディレクトリになります。
これ、めっちゃ便利なのでJenkinsの設定どころか、いろんな設定をGroovy化しておけばいいのではないかと思ってきました。
なにがいいたいかというと、Chefクローンとかつくれるのではなかろうかという。
個人的にはJenkins系ですね。はい。