うさぎ組

ソフトウェア開発、チームによる製品開発、アジャイル、ソフトウェアテスト

HerokuでGrailsを使うとクエリパラメータが文字化けするのでbuildpack直しました。

TL;DR

HerokuではGrailsを簡単に(普通のGrailsプロジェクトをgit pushするだけで)デプロイすることができます。ですが、(少なくともHerokuがデフォルトで使う)Tomcat7ではクエリパラメータが文字化けします。Grailsで使うTomcat7のクエリパラメータをUTF-8エンコードするようにするビルドパックをつくりました。(フォークして一行変えただけだけど)

問題

ローカルでGrailsを使って開発しているときには気づかなかったのですが、Herokuにデプロイするとクエリパラメータに日本語を使うとめっちゃ化けてしまいました。もうTomcatのことなんてすっかりわすれていたので、原因調査にめっちゃ時間かかりました。。。

それに付随していろいろわかったのでまとめておきます。

調査方針

次の方針で調査しました。結果としては最後の他の何かで、それはTomcatの設定だったんですけど、これをHerokuの場合にはどうするか?というのがHeroku初心者ながらにがんばった。

  • Viewの文字コードUTF-8になっていないのではないか
  • サーバーサイドでクエリパラメータをとってくるときにUTF-8になっていないのではないか
  • 他のなにか

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本買いました。わかりやすくて助かります。

Grails in Action

Grails in Action

Programming Grails

Programming Grails

Spockの知られざる機能

僕だけが知らなかったのかもしれません。。。

Spockではテストメソッドに@Unrollとつけると、パラメタライズがテストメソッド名に反映されます。これとっても見やすくてよいです。

こんな感じ。。。

class WhenWatchingUstream extends Specification {

    @Unroll
    def "should show #channel when click #channel window"(){
        expect:
        // click channel
        where:
        channel << ["Cooking", "Music", "Animation"]
    }

}

にしておくと

f:id:kyon_mm:20140807214827p:plain

ところがです。テストメソッドが大量になってくると、テストメソッドのたびに@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
    }
}

f:id:kyon_mm:20140807215837p:plain

まとめ

@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

サマリー画面

f:id:kyon_mm:20140806131536p:plain

テストクラス毎の詳細画面

f:id:kyon_mm:20140806131540p:plain

使い方

ビルドスクリプトから使うことを前提としてます。

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を強く意識している)

  • 各テストクラス -> Specification
  • 各テストメソッド -> Feature
  • テストメソッド内テーブル -> examples

まとめ

BDDスタイルにSpockを使うことやレビューしやすさをあげるなら、Spock-Reportsを使わないと損です。

プログラミングGROOVY

プログラミングGROOVY

Bdd in Action: Behavior-driven Development for the Whole Software Lifecycle

Bdd in Action: Behavior-driven Development for the Whole Software Lifecycle

The RSpec Book (Professional Ruby Series)

The RSpec Book (Professional Ruby Series)

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 !

Gradle in Action

Gradle in Action

Gradle Beyond the Basics

Gradle Beyond the Basics

Gradle Effective Implementation Guide

Gradle Effective Implementation Guide

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サイトを参照するようにしましょう!!

プログラミングGROOVY

プログラミングGROOVY

Groovy in Action

Groovy in Action

  • 作者: 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系ですね。はい。