jetty-maven-pluginがwindows環境で動かないので調べてみた

jetty-maven-pluginのバージョンが7.0.1.v20091125だとエラーが出てjettyは起動するものの、アプリケーションが動いてない状態になる。バージョンを6あたりに下げると動く。

現象

現象は、JETTY-1166と同じ。

以下はJIRAに登録されているスタックトレース

2010-01-01 20:54:50.593:INFO::jetty-7.0.1.v20091125
2010-01-01 20:54:50.640:WARN::Failed startup of context JettyWebAppContext@8825a
5@8825a5/zipcodeservice,file:/C:/projects/ext/davidkarlsen.com/zipcodeservice/zi
pcodeservice-war/src/main/webapp/,file:/C:/projects/ext/davidkarlsen.com/zipcode
service/zipcodeservice-war/src/main/webapp/
java.net.URISyntaxException: Illegal character in path at index 18: file:/C:/Doc
uments and Settings/karltdav/.m2/repository/org/mortbay/jetty/jetty-maven-plugin
/7.0.1.v20091125/jetty-maven-plugin-7.0.1.v20091125.jar
        at java.net.URI$Parser.fail(URI.java:2809)
        at java.net.URI$Parser.checkChars(URI.java:2982)
        at java.net.URI$Parser.parseHierarchical(URI.java:3066)
        at java.net.URI$Parser.parse(URI.java:3014)
        at java.net.URI.(URI.java:578)
        at java.net.URL.toURI(URL.java:918)
        at org.eclipse.jetty.webapp.WebInfConfiguration.preConfigure(WebInfConfi
guration.java:81)
        at org.mortbay.jetty.plugin.MavenWebInfConfiguration.preConfigure(MavenW
ebInfConfiguration.java:117)
        at org.eclipse.jetty.webapp.WebAppContext.doStart(WebAppContext.java:347
)
        at org.mortbay.jetty.plugin.JettyWebAppContext.doStart(JettyWebAppContex
t.java:102)
(以下省略)

エラーメッセージから、「file:/C:/Documents and Settings/」という部分に含まれるスペースがまずいらしいことが分かる。
URLにはスペースを含めることができるが、URIにはスペースを含めることが出来ないみたいだ。

問題の箇所のコードを見てみる

与えているURLが悪いのかとjetty-maven-plugin側のURIとURLを変換している箇所を修正してみたが、問題が解決しなかったので、どうもjetty-server側の問題らしい事が分かった。

jetty-serverで例外が投げられている箇所のコードは次の通り。
http://download.eclipse.org/jetty/stable-7/xref/org/eclipse/jetty/webapp/WebInfConfiguration.html#71

71          ClassLoader loader = context.getClassLoader();
72          while (loader != null && (loader instanceof URLClassLoader))
73          {
74              URL[] urls = ((URLClassLoader)loader).getURLs();
75              if (urls != null)
76              {
77                  URI[] containerUris = new URI[urls.length];
78                  int i=0;
79                  for (URL u : urls)
80                  {
81                      containerUris[i++] = u.toURI();
82                  }
83                  containerJarNameMatcher.match(containerPattern, containerUris, false);
84              }
85              loader = loader.getParent();
86          }

81行目の「u.toURI()」という処理でURISyntaxExceptionが投げられている。
jetty6では問題無く使えていたので、何が変わったのか見てみたら、toURI()を使わないで処理していたらしい。

対策

81行目の「u.toURI()」で問題が起きているのは、URIに含める事が出来ないスペースを含むためだから、それをエスケープしてやれば問題なさそうだ。

で次のように修正したら、jetty-maven-pluginから無事起動できた。

71          ClassLoader loader = context.getClassLoader();
72          while (loader != null && (loader instanceof URLClassLoader))
73          {
74              URL[] urls = ((URLClassLoader)loader).getURLs();
75              if (urls != null)
76              {
77                  URI[] containerUris = new URI[urls.length];
78                  int i=0;
79                  for (URL u : urls)
80                  {
81                      containerUris[i++] = new URI(URIUtil.encodePath(u.toString()));
82                  }
83                  containerJarNameMatcher.match(containerPattern, containerUris, false);
84              }
85              loader = loader.getParent();
86          }
URIUtil.encodePath()を使った理由

ここで使っているhttp://download.eclipse.org/jetty/stable-7/xref/org/eclipse/jetty/util/URIUtil.htmlというクラスはjetty-server内のものなんだけど、URLEncoderではなく、これを使ったのには理由がある。

Oracle Technology Network for Java Developers | Oracle Technology Network | Oracleに以下のように記載されていたからだ。

URI クラスは特定の状況において、そのコンポーネントフィールドに対してエスケープ処理を実行することに注意してください。.URL のエンコードとデコードを管理する際の推奨の方法は、URI を使用して、これら 2 つのクラス間の変換を toURI() と URI.toURL() を使って行うことです。

URLEncoder クラスと URLDecoder クラスを使用することもできますが、これらは HTML 形式のエンコーディング専用です。また、このエンコーディングは、RFC2396 で定義されているエンコーディング方式と同じものではありません。

これだけだと、よく分からなかったのでさらに調べてみた。

この違いの詳細は、
studyinghttp.net - このウェブサイトは販売用です! - 解説 仕様書 利用 技術 である 手法 日本語訳 プログラミング リソースおよび情報

Subbu’s Blog
に記述されていた。

ごくごくおおざっぱにまとめると、

HTML形式のエンコードでは、スペースが"+"になり、
URI形式のパーセントエンコード(RFC3986)では、スペースが"%20"にエンコードされる。

とのことらしい。だから、HTML形式のエンコードを行うURLEncoderはこの場合は使えない。
幸いにもJettyにはhttp://download.eclipse.org/jetty/stable-7/xref/org/eclipse/jetty/util/URIUtil.htmlというクラスがあり、
ソースコードを見たところ、ちょうどよく利用出来そうな感じだったのでそれを利用した。

URLEncoderとURIUtilの違いを見てみる。
確認用コード

	public void testURLEncode() throws UnsupportedEncodingException, MalformedURLException {
		File file = new File("C:/Documents and Settings/cnaos/My Documents/tmp");
		String urlEncodedStr = URLEncoder.encode(file.toURL().toString(), "UTF-8");
		String uriUtilStr = URIUtil.encodePath(file.toURL().toString());
		System.out.println("URLEncoder result="+urlEncodedStr);
		System.out.println("URIUtil result="+uriUtilStr);
	}

結果

URLEncoder result=file%3A%2FC%3A%2FDocuments+and+Settings%2Fcnaos%2FMy+Documents%2Ftmp%2F
URIUtil result=file:/C:/Documents%20and%20Settings/cnaos/My%20Documents/tmp/

だいぶ違いますね。

ローカルビルドして試してみたい人向けの情報

※jetty-7.0.1.v20091125を上書きするので、すでに何かにjetty7を使っているのであれば、やらない方が無難です。

1.まずjetty-serverを以下の場所からチェックアウトします。

http://dev.eclipse.org/svnroot/rt/org.eclipse.jetty/jetty/tags/jetty-7.0.1.v20091125

本来ならtrunkからチェックアウトするところなんでしょうけど、jetty-maven-pluginのビルドを省くためにこうしてます。

2.次のパッチを当てます。
Index: jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebInfConfiguration.java
===================================================================
--- jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebInfConfiguration.java (リビジョン 1374)
+++ jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebInfConfiguration.java (作業コピー)
@@ -78,7 +78,7 @@
                 int i=0;
                 for (URL u : urls)
                 {
-                    containerUris[i++] = u.toURI();
+                    containerUris[i++] = new URI(URIUtil.encodePath(u.toString()));
                 }
                 containerJarNameMatcher.match(containerPattern, containerUris, false);
             }
3.ビルドして、ローカルのmavenレポジトリにインストールします。
mvn -Dmaven.test.skip=true install

「mvn -Dmaven.test.skip=true」でテストをスキップしてしまっているのは、どうしても通らないテストがあったためです。
ホントはやっちゃいけないんだけど。

org.eclipse.jetty.server.AbstractConnectorTestのtestMultipleRequests()や
org.eclipse.jetty.server.handler.StatisticsHandlerTestのtestSuspendExpire()やtestSuspendComplete()
あたりのテストが成功したり、しなかったりしてました。

テストコードを見てみると、sleep(10) = 10ミリ秒とかやたらとsleep期間が短いのが気になって、ちょっとsleepを長めにしてみたけど、けっきょく変わらなかった。
下手にテストコードをいじるのも怖いので、そのままにorz。

まとめ

  • jetty-maven-pluginでjetty7をwidows環境で動かすことができるようになった。
  • URLとURIの変換には注意。
  • 似ているけど、ちょっと違うものはバグの温床になりやすい。

さて、これからバグトラッカに登録しなければいけないんだが、どうしたものか。
jetty-serverのバグトラッカに登録したらいいのか、
jetty-maven-pluinのバグトラッカに登録したらいいのか。