Groovy の @Grab で Spark Framework を実行
Spark Framework - A tiny Java web framework を Groovy の @Grab
を使って実行してみました。
- Spark Framework 2.5
- Groovy 2.4.7
今回のソースは http://github.com/fits/try_samples/tree/master/blog/20160801/
はじめに
以下のように @Grab を使った Spark の Groovy スクリプトを groovy コマンドで実行し、Web クライアントでアクセスしてみると、java.lang.NoSuchMethodError: javax.servlet.http.HttpServletResponse.getHeaders
エラーが発生してしまいました。
now.groovy
@Grab('com.sparkjava:spark-core:2.5') @Grab('org.slf4j:slf4j-simple:1.7.21') import static spark.Spark.* get('/now') { req, res -> new Date().format('yyyy/MM/dd HH:mm:ss') }
groovy コマンドで上記スクリプトを実行。
実行例
> groovy now.groovy ・・・ [Thread-1] INFO org.eclipse.jetty.server.Server - Started @3213ms
/now
へアクセスすると、以下のようなエラーが発生。
エラー例(クライアント側)
$ curl http://localhost:4567/now <html> <head> <meta http-equiv="Content-Type" content="text/html;charset=ISO-8859-1"/> <title>Error 500 </title> </head> <body> <h2>HTTP ERROR: 500</h2> <p>Problem accessing /now. Reason: <pre> java.lang.NoSuchMethodError: javax.servlet.http.HttpServletResponse.getHeaders(Ljava/lang/String;)Ljava/util/Collection;</pre></p> <hr /><a href="http://eclipse.org/jetty">Powered by Jetty:// 9.3.6.v20151106</a><hr/> </body> </html>
エラー例(サーバー側)
> groovy now.groovy ・・・ java.lang.NoSuchMethodError: javax.servlet.http.HttpServletResponse.getHeaders(Ljava/lang/String;)Ljava/util/Collection; at spark.utils.GzipUtils.checkAndWrap(GzipUtils.java:67) at spark.http.matching.Body.serializeTo(Body.java:69) at spark.http.matching.MatcherFilter.doFilter(MatcherFilter.java:158) at spark.embeddedserver.jetty.JettyHandler.doHandle(JettyHandler.java:50) at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:189) ・・・
このエラーは、groovy コマンドの実行時に $GROOVY_HOME/lib/servlet-api-2.4.jar (Servlet 2.4) を先にロードする事が原因で発生しているようです。
HttpServletResponse.getHeaders
は Servlet 3.0 から追加されたメソッドのため、Servlet 2.4 の API が適用されていると該当メソッドが存在せず NoSuchMethodError
になります。
回避策
エラー原因は、groovy コマンドが $GROOVY_HOME/lib/servlet-api-2.4.jar をロードする事なので、Gradle 等で実行すれば上記のようなエラーは発生しません。
しかし、今回は groovy コマンドで実行する場合の回避策をいくつか検討してみました。
- (a) -cp オプションを使用
- (b) Groovy 設定ファイル (groovy-starter.conf) を編集
- (c) $GROOVY_HOME/lib/servlet-api-2.4.jar を削除
(a) と (b) は Servlet 2.4 より先に Servlet 3.1 を適用させる方法で、(c) は servlet-api-2.4.jar をロードさせない方法です。
(a) -cp オプションを使用
Servlet 3.1 の JAR (下記では javax.servlet-api-3.1.0.jar)を入手し、groovy コマンドの -cp オプションでその JAR を指定して実行します。
こうする事でエラーは出なくなりました。
実行例(サーバー)
> groovy -cp lib_a/javax.servlet-api-3.1.0.jar now.groovy ・・・
実行例(クライアント)
$ curl http://localhost:4567/now 2016/07/31 20:46:42
(b) Groovy 設定ファイル (groovy-starter.conf) を編集
$GROOVY_HOME/lib/servlet-api-2.4.jar は groovy-starter.conf の設定 (load !{groovy.home}/lib/*.jar
) によりロードされています。
つまり、$GROOVY_HOME/lib/*.jar よりも先に、別のディレクトリ内の JAR をロードするように groovy-starter.conf を書き換え、そのディレクトリへ Servlet 3.1 の JAR を配置すれば、(a) と同様に回避できるはずです。
groovy-starter.conf 変更例
# 以下を追加 load lib_a/*.jar # load required libraries load !{groovy.home}/lib/*.jar ・・・
lib_a/javax.servlet-api-3.1.0.jar を配置して実行すると、エラーは出なくなりました。
実行例(サーバー)
> groovy now.groovy ・・・
実行例(クライアント)
$ curl http://localhost:4567/now 2016/07/31 20:48:05
備考. Groovy 設定ファイル (groovy-starter.conf) の指定方法
groovy-starter.conf を直接書き換えるのはイマイチなので、任意の Groovy 設定ファイルを使いたいところです。
startGroovy
スクリプトの内容を見ると GROOVY_CONF
環境変数で指定できそうです。
ただし、startGroovy.bat
の方は今のところ GROOVY_CONF
環境変数を考慮しておらず、Windows 環境 (groovy.bat を使う場合) では使えません。
そこで今回は、下記 postinit.bat を用意し、Windows 環境で GROOVY_CONF
に対応してみました。
%USERPROFILE%/.groovy/postinit.bat の例
if not "%GROOVY_CONF%" == "" ( set GROOVY_OPTS=%GROOVY_OPTS% -Dgroovy.starter.conf="%GROOVY_CONF%" set STARTER_CONF=%GROOVY_CONF% )
上記を配置した後、以下のように実行します。
GROOVY_CONF 環境変数の利用例 (Windows)
> set GROOVY_CONF=groovy-starter_custom.conf > groovy now.groovy ・・・
(c) $GROOVY_HOME/lib/servlet-api-2.4.jar を削除
$GROOVY_HOME/lib/servlet-api-2.4.jar を一時的に削除(拡張子を変える等)して実行します。
備考. Gradle で実行する場合
Gradle で実行する場合は、src/main/groovy/now.groovy を配置して(@Grab の箇所は削除しておく)、以下のような build.gradle を使います。
build.gradle 例
apply plugin: 'groovy' apply plugin: 'application' repositories { jcenter() } dependencies { compile 'com.sparkjava:spark-core:2.5' compile 'org.codehaus.groovy:groovy:2.4.7' runtime 'org.slf4j:slf4j-simple:1.7.21' } mainClassName = 'now'
実行例
> gradle run ・・・ :run ・・・ [Thread-1] INFO org.eclipse.jetty.server.Server - Started @1035ms