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。