Jetty組み込み方メモ

組み込み利用での Jetty の使い方をメモ。

#Hello World
##build.gradle
```groovy:build.gradle
apply plugin: 'java'

repositories {
    mavenCentral()
}

dependencies {
    compile 'org.eclipse.jetty.aggregate:jetty-all:9.1.4.v20140401'
}
```

##実装
```java:HelloJetty.java
package sample.jetty;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.AbstractHandler;

public class HelloJetty extends AbstractHandler {
    
    public static void main(String[] args) throws Exception {
        Server server = new Server(1234);
        server.setHandler(new HelloJetty());
        server.start();
        server.join();
    }

    @Override
    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
        System.out.println("target = " + target);
        
        response.setContentType("text/html;charset=utf-8");
        response.setStatus(HttpServletResponse.SC_OK);
        baseRequest.setHandled(true);
        response.getWriter().println("<h1>Hello Jetty!!</h1>");
    }
}
```

##動作確認
実行して、 Web ブラウザから `http://localhost:1234` にアクセスする。

![jetty.JPG](https://qiita-image-store.s3.amazonaws.com/0/28302/0c748d5b-57ac-848a-c023-3a3054ed7887.jpeg)

```text:コンソール出力
target = /
```

##説明
###Server
- Jetty を組み込みで使うときは、 Server クラスを使用する。
- コンストラクタの引数で、ポート番号を指定できる。

###Handler
- Handler を Server に登録しておくと、全てのリクエストがこの Handler に渡される。
- リクエストが届くと、 Handler の `handle()` メソッドが実行される。
- `handle()` メソッドの第一引数には、リクエストのあった URI が渡される。

#Servlet を登録する
##実装
```java:SampleServlet.java
package sample.jetty;

import java.io.IOException;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler;

public class SampleServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
    
    public static void main(String[] args) throws Exception {
        ServletHandler handler = new ServletHandler();
        handler.addServletWithMapping(SampleServlet.class, "/sample");
        
        Server server = new Server(1234);
        server.setHandler(handler);
        server.start();
        server.join();
    }
    
    @Override
    public void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {
        res.getWriter().println("Servlet on Jetty.");
    }
}
```

##動作確認
実行して、 Web ブラウザから `http://localhost:1234/sample` にアクセスする。

![jetty.JPG](https://qiita-image-store.s3.amazonaws.com/0/28302/8913c113-cac3-20a0-ba26-37746ff93d4c.jpeg)

##説明
- `ServletHandler#addServletWithMapping()` メソッドで Servlet と URI のマッピングを定義する。

#war ファイルをデプロイする
組み込みで起動する jetty に war ファイルをデプロイする。

##デプロイする war プロジェクト
何の変哲もない war プロジェクト。

###build.gradle
```groovy:build.gradle
apply plugin: 'war'

repositories {
    mavenCentral()
}

dependencies {
    providedCompile 'javax.servlet:javax.servlet-api:3.1.0'
}

war.baseName = 'sample'
```

###実装
```java:sample.jetty.SampleServlet.java
package sample.jetty;

import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/servlet")
public class SampleServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    @Override
    public void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {
        res.getWriter().println("war on jetty.");
    }
}
```

##起動側プロジェクト
###build.gradle
```groovy:build.gradle
apply plugin: 'java'

repositories {
    mavenCentral()
}

dependencies {
    compile 'org.eclipse.jetty:jetty-webapp:9.1.4.v20140401'
    compile 'org.eclipse.jetty:jetty-annotations:9.1.4.v20140401'
}
```

- `jetty-all` だと、 `@WebServlet` のような Servlet 3.0 流の実装を使ったときに `ClassNotFoundException` が発生するので、 `dependencies` を若干変えてる（推移的な依存関係の管理とは何だったのか...）。

###実装
```java:Main.java
package sample.jetty;

import org.eclipse.jetty.annotations.AnnotationConfiguration;
import org.eclipse.jetty.plus.webapp.EnvConfiguration;
import org.eclipse.jetty.plus.webapp.PlusConfiguration;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.webapp.Configuration;
import org.eclipse.jetty.webapp.FragmentConfiguration;
import org.eclipse.jetty.webapp.JettyWebXmlConfiguration;
import org.eclipse.jetty.webapp.MetaInfConfiguration;
import org.eclipse.jetty.webapp.WebAppContext;
import org.eclipse.jetty.webapp.WebInfConfiguration;
import org.eclipse.jetty.webapp.WebXmlConfiguration;

public class Main {
    public static void main(String[] args) throws Exception {
        // 1. war ファイルの設定
        WebAppContext war = new WebAppContext();
        war.setContextPath("/sample");
        war.setWar("./sample.war");
        
        // 2. @WebServlet とかを有効にしている
        Configuration[] configurations = {
            new AnnotationConfiguration(),
            new WebInfConfiguration(),
            new WebXmlConfiguration(),
            new MetaInfConfiguration(),
            new FragmentConfiguration(),
            new EnvConfiguration(),
            new PlusConfiguration(),
            new JettyWebXmlConfiguration()
        };
        
        war.setConfigurations(configurations);

        Server server = new Server(1234);
        server.setHandler(war);
        server.start();
        server.join();
    }
}
```

1. `WebAppContext` の `setContextPath()` でコンテキストパスを、 `setWar()` でデプロイする war のパスを指定している。
2. 上記のような感じで `setConfigurations()` を設定しないと、 `@WebServlet` の設定は無視され、 Servlet 3.0 流の実装ができない。

##動作確認
起動したら Web ブラウザで `http://localhost:1234/sample/servlet` にアクセスする。

![jetty.JPG](https://qiita-image-store.s3.amazonaws.com/0/28302/226870e5-7e84-9ba1-fbfa-fa0f7728c4c6.jpeg)

#実行可能な war を作成する
jenkins みたいに

```text:
>java -jar hoge.war
```

でサーバーの起動と war の配備ができたら cool ですよね。

ということで、やってみる。

##build.gradle
```groovy:build.gradle
apply plugin: 'war'

repositories {
    mavenCentral()
}

dependencies {
    providedCompile 'org.eclipse.jetty:jetty-webapp:9.1.4.v20140401'
    providedCompile 'org.eclipse.jetty:jetty-annotations:9.1.4.v20140401'
}

war {
    baseName = 'jetty-sample'
    
    from {
        configurations.providedCompile.collect {
            it.isDirectory() ? it : project.zipTree(it)
        }
    }
    from fileTree(dir: 'build/classes/main', include: '**/Main.class')
    
    manifest.attributes('Main-Class': 'sample.jetty.Main')
}
```

##実装
```java:sample.jetty.Main.java
package sample.jetty;

import java.net.URL;
import java.security.ProtectionDomain;

import org.eclipse.jetty.annotations.AnnotationConfiguration;
import org.eclipse.jetty.plus.webapp.EnvConfiguration;
import org.eclipse.jetty.plus.webapp.PlusConfiguration;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.webapp.Configuration;
import org.eclipse.jetty.webapp.FragmentConfiguration;
import org.eclipse.jetty.webapp.JettyWebXmlConfiguration;
import org.eclipse.jetty.webapp.MetaInfConfiguration;
import org.eclipse.jetty.webapp.WebAppContext;
import org.eclipse.jetty.webapp.WebInfConfiguration;
import org.eclipse.jetty.webapp.WebXmlConfiguration;

public class Main {
    public static void main(String[] args) throws Exception {
        
        WebAppContext war = new WebAppContext();
        war.setContextPath("/sample");
        
        // ここで war ファイルの場所を取得している
        ProtectionDomain domain = Main.class.getProtectionDomain();
        URL warLocation = domain.getCodeSource().getLocation();
        war.setWar(warLocation.toExternalForm());
        
        Configuration[] configurations = {
            new AnnotationConfiguration(),
            new WebInfConfiguration(),
            new WebXmlConfiguration(),
            new MetaInfConfiguration(),
            new FragmentConfiguration(),
            new EnvConfiguration(),
            new PlusConfiguration(),
            new JettyWebXmlConfiguration()
        };
        
        war.setConfigurations(configurations);

        Server server = new Server(1234);
        server.setHandler(war);
        server.start();
        server.join();
    }
}
```

```java:sample.jetty.SampleServlet.java
package sample.jetty;

import java.io.IOException;

import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet("/servlet")
public class SampleServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
    
    @Override
    public void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {
        res.getWriter().println("executable war.");
    }
}
```

##動作確認
war を作って起動する。

```text:
>gradle war

>java -jar build\libs\jetty-sample.war
2014-04-29 23:39:57.074:INFO::main: Logging initialized @105ms
2014-04-29 23:39:57.130:INFO:oejs.Server:main: jetty-9.1.z-SNAPSHOT
2014-04-29 23:39:57.840:INFO:oejw.StandardDescriptorProcessor:main: NO JSP Support for /sample, did not find org.apache.jasper.servlet.JspServlet
2014-04-29 23:39:57.869:INFO:oejsh.ContextHandler:main: Started o.e.j.w.WebAppContext@483bf400{/sample,file:/<省略>/jetty-0.0.0.0-1234-jetty-sample.war-_sample-any-951802741547862236.dir/webapp/,AVAILABLE}{file:<省略>/build/libs/jetty-sample.war}
2014-04-29 23:39:57.888:INFO:oejs.ServerConnector:main: Started ServerConnector@5d624da6{HTTP/1.1}{0.0.0.0:1234}
2014-04-29 23:39:57.888:INFO:oejs.Server:main: Started @938ms
```

Web ブラウザを開いて、 `http://localhost:1234/sample/servlet` にアクセスする。

![jetty.JPG](https://qiita-image-store.s3.amazonaws.com/0/28302/f5744ea4-8410-c1dd-c8c9-d1ba31dc343e.jpeg)

停止はコマンドライン上で `Ctrl + C` 。

##説明
###war ファイルの場所を取得する方法
```java:Main.java
ProtectionDomain domain = Main.class.getProtectionDomain();
URL warLocation = domain.getCodeSource().getLocation();
war.setWar(warLocation.toExternalForm());
```

仕組みはよくわかってないけど、こうすれば jar として実行している「この war ファイルの場所」が取得できるらしい。

###war ファイルを「実行可能 jar 形式」にする方法
```groovy:build.gradle
war {
    // 1
    from {
        configurations.providedCompile.collect {
            it.isDirectory() ? it : project.zipTree(it)
        }
    }
    
    // 2
    from fileTree(dir: 'build/classes/main', include: '**/Main.class')
    
    // 3
    manifest.attributes('Main-Class': 'sample.jetty.Main')
}
```

1. jetty およびその依存対象を war の直下に展開している。
2. 起動用の Main クラスを war の直下に展開している。
3. マニフェストファイルに Main クラスを定義している。

war ファイルを解凍すると、以下のような構成になっている。

```text:
jetty-sample/
│
│  about.html
│  jetty-dir.css
│  jndi.properties
├javax/
│  ├annotation/
│  └servlet/
├org/
│  ├eclipse/
│  │  └jetty/
│  └objectweb/
│      └asm/
├META-INF/
│  └MANIFEST.MF
├sample/
│  └jetty/
│     Main.class
│
└WEB-INF/
    └classes/
        └sample/
            └jetty/
               Main.class
               SampleServlet.class
```

実行可能 jar としての構成と war としての構成がごちゃ混ぜになった構造になっている。

つまり、当然 `http://localhost:1234/sample/abount.html` とか `http://localhost:1234/sample/org/eclipse/jetty/server/Server.class` にアクセスすると、当該ファイルが丸見えになっている。

![jetty.JPG](https://qiita-image-store.s3.amazonaws.com/0/28302/29c56b3a-e083-6954-cacb-d2fee53e1c3f.jpeg)

やや格好悪いけど、我慢するか、どうしても嫌なら Filter を作って、見られたくないパスへのアクセスは全部 404 にしてしまうという方法もありかなと。

#web フォルダを指定する
```java:Main.java
package sample.jetty;

import java.net.URL;
import java.security.ProtectionDomain;

import org.eclipse.jetty.annotations.AnnotationConfiguration;
import org.eclipse.jetty.plus.webapp.EnvConfiguration;
import org.eclipse.jetty.plus.webapp.PlusConfiguration;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.webapp.Configuration;
import org.eclipse.jetty.webapp.FragmentConfiguration;
import org.eclipse.jetty.webapp.JettyWebXmlConfiguration;
import org.eclipse.jetty.webapp.MetaInfConfiguration;
import org.eclipse.jetty.webapp.WebAppContext;
import org.eclipse.jetty.webapp.WebInfConfiguration;
import org.eclipse.jetty.webapp.WebXmlConfiguration;

public class Main {
    public static void main(String[] args) throws Exception {
        WebAppContext war = new WebAppContext();
        war.setContextPath("/sample");
        war.setResourceBase("src/main/webapp");

        Configuration[] configurations = {
            new AnnotationConfiguration(),
            new WebInfConfiguration(),
            new WebXmlConfiguration(),
            new MetaInfConfiguration(),
            new FragmentConfiguration(),
            new EnvConfiguration(),
            new PlusConfiguration(),
            new JettyWebXmlConfiguration()
        };

        war.setConfigurations(configurations);

        Server server = new Server(1234);
        server.setHandler(war);
        server.start();
        server.join();
    }
}
```

- `WebAppContext#setResourceBase(path)` で、 web フォルダを指定して起動できる。

__参考__

- [TomcatからJettyへ - Narwhal](http://d.hatena.ne.jp/nosa1/20120124/1327397386)



#参考
- [Chapter 25. Embedding | Jetty : The Definitive Reference](http://www.eclipse.org/jetty/documentation/current/embedding-jetty.html)
- [java - Embedding Jetty as a Servlet Container - Stack Overflow](http://stackoverflow.com/questions/17246512/embedding-jetty-as-a-servlet-container)
- [jetty-project/embedded-servlet-3.1 | GitHub](https://github.com/jetty-project/embedded-servlet-3.1)
- [Linked Java: Executable Wars with Gradle and Jetty](http://linkedjava.blogspot.jp/2012/05/executable-wars-with-gradle-and-jetty.html)
- [Executable WARs with Jetty | EclipseSource Blog](http://eclipsesource.com/blogs/2009/10/02/executable-wars-with-jetty/)