ちょっとした Web アプリケーションを作るのに便利な Spark の使い方メモ。
Apache Spark ではない(紛らわしい...)。
#インストール
dependencies {
compile 'com.sparkjava:spark-core:2.0.0'
}
#Hello World
package sample.spark;
import static spark.Spark.*;
public class Main {
public static void main(String[] args) {
get("/hello", (request, response) -> "<h1>Hello Spark!!</h1>");
}
}
実行したら、 Web ブラウザを立ち上げて http://localhost:4567/hello
にアクセスする。
#ポートを変更する
package sample.spark;
import static spark.Spark.*;
public class Main {
public static void main(String[] args) {
setPort(7654);
get("/hello", (request, response) -> "<h1>Hello Spark!!</h1>");
}
}
-
Spark.setPort()
でポートを変更できる。 - デフォルトは
4567
ポート。
#サーバーを停止する
package sample.spark;
import static spark.Spark.*;
public class Main {
public static void main(String[] args) {
get("/stop", (request, response) -> {
stop();
return null;
});
}
}
-
Spart.stop()
メソッドでサーバーを停止できる。 -
stop()
メソッドを実行してもすぐには停止せず、少ししてからInterruptedException
がスローされて終了する。
#ルーティングの定義
##3つの要素
get("/hello", (request, response) -> "Hello Spark!!");
- ルーティングの定義は、以下の3つの要素で構成されている。
- HTTP メソッド(
get
,post
,put
,delete
,head
,trace
,options
,connect
) - パス(
/hello
,/stop
) - コールバック(
(request, response) -> "Hello Spark!!"
)
- HTTP メソッド(
##パスパラメータ
package sample.spark;
import static spark.Spark.*;
public class Main {
public static void main(String[] args) {
get("/hello/:param", (request, response) -> "param = " + request.params("param"));
}
}
-
:<名前>
という形でパスにパラメータを定義することができる。 - パラメータは、リクエストの
params()
メソッドで取得できる。
##パスにワイルドカードを指定する
package sample.spark;
import static spark.Spark.*;
public class Main {
public static void main(String[] args) {
get("/hello/*/sample/*", (request, response) -> {
String[] splat = request.splat();
String text = "splat().length = " + splat.length + "<br>"
+ "splat()[0] = " + splat[0] + "<br>"
+ "splat()[1] = " + splat[1];
return text;
});
}
}
ブラウザを立ち上げて、 http://localhost:4567/hello/hoge/sample/test
にアクセスする。
- ワイルドカード(
*
)を使ってパスを定義できる。 - ワイルドカードにあたる部分のパスは、リクエストの
splat()
メソッドでString
の配列として取得できる。
#リクエスト
request.body(); // リクエストボディを取得する(String)
request.cookies(); // クッキーを取得する(Map<String, String>)
request.contentLength(); // リクエストボディの長さを取得する(int)
request.contentType(); // リクエストボディのコンテツタイプを取得する(String)
request.headers(); // HTTP ヘッダーの一覧を取得する(Set<String>)
request.headers("BAR"); // HTTP ヘッダーの "BAR" の値を取得する(String)
request.attributes(); // attribute の一覧を取得する(Set<String>)
request.attribute("foo"); // attribute の "foo" の値を取得する(String)
request.attribute("A", "V"); // attribute の "A" に "V" を設定する
request.host(); // サーバーのホスト名(localhost:4567)を取得する(String)
request.ip(); // クライアントの IP アドレスを取得する(String)
request.pathInfo(); // リクエストのあったパスを取得する(String)
request.params("foo"); // パスパラメータの "foo" の値を取得する(String)
request.params(); // 全てのパスパラメータを Map で取得する(Map<String, String>)
request.port(); // サーバーのポートを取得する(int)
request.queryMap(); // クエリマップを取得する(QueryParamsMap)
request.queryMap("foo"); // クエリマップの "foo" を取得する(QueryParamsMap)
request.queryParams("FOO"); // クエリパラメータの "FOO" の値を取得する(String)
request.queryParams(); // クエリパラメータの一覧を取得する(Set<String>)
request.raw(); // 生の HttpServletRequest を取得する(HttpServletRequest)
request.requestMethod(); // HTTP リクエストメソッドを取得する(String)
request.scheme(); // URL のスキーム(http)を取得する(String)
request.session(); // セッション情報を取得する(spark.Session)
request.splat(); // パスのワイルドカード部分の情報を取得する(String[])
request.url(); // リクエストされた URL 全体を取得する(String)
request.userAgent(); // ユーザーエージェントを取得する(String)
クエリマップについては後述。
#レスポンス
response.body("Hello"); // コンテンツに "Hello" をセットする
response.header("FOO", "bar"); // ヘッダーの "FOO" に "bar" をセットする
response.raw(); // 生の HttpServletResponse を取得する(HttpServletResponse)
response.redirect("/example"); // 指定した URL にリダイレクトさせる
response.status(401); // ステータスコードに 401 を設定する
response.type("text/xml"); // コンテンツタイプを設定する
#クエリマップ
package sample.spark;
import static spark.Spark.*;
import spark.QueryParamsMap;
public class Main {
public static void main(String[] args) {
get("/hello", (request, response) -> {
QueryParamsMap map = request.queryMap();
String html = "user[name] = " + map.get("user", "name").value() + "<br>"
+ "user[age] = " + map.get("user", "age").integerValue();
return html;
});
}
}
ブラウザで http://localhost:4567/hello?user[name]=hoge&user[age]=14
にアクセスする
- クエリマップとは、
user[name]
のように、マップ形式で指定されたクエリパラメータのことを指している。 -
QueryParamsMap
を使うと、この形式のクエリパラメータに簡単にアクセスできる。
#クッキー
request.cookies(); // 全てのクッキーを Map で取得する(Map<String, String>)
request.cookie("foo"); // クッキーの "foo" の値を取得する(String)
response.cookie("foo", "bar"); // "foo" という名前で "bar" という値をクッキーに設定する
response.cookie("foo", "bar", 3600); // 有効期限(秒)付きでクッキーを設定する
response.cookie("foo", "bar", 3600, true); // セキュア属性を true にする(https の時にだけクッキーを送信する)
response.removeCookie("foo"); // "foo" をクッキーから削除する
#セッション
request.session(true) // HttpServletRequest.getSession(true) と同じ
request.session().attribute("user") // "user" 属性を取得する(T : 自動でキャストされる)
request.session().attribute("user", "foo") // "user" 属性に "foo" を設定する
request.session().removeAttribute("user") // "user" 属性を削除する
request.session().attributes() // 全ての属性を取得する(Set<String>)
request.session().id() // セッションIDを取得する(String)
request.session().isNew() // セッションが新規に作成されたものかどうかを確認する(boolean)
request.session().raw() // 生の HttpSession を取得する
#リクエストの処理を直ちに中断する
package sample.spark;
import static spark.Spark.*;
public class Main {
public static void main(String[] args) {
get("/hello", (request, response) -> {
System.out.println("実行される");
halt();
System.out.println("実行されない");
return null;
});
}
}
実行される
-
halt()
メソッドを実行することで、そこで処理を中断できる。 -
halt(404)
やhalt(404, "File Not Found")
とすることで、ステータスコードとレスポンスボディを設定することもできる。
#リクエストの前後に処理を挟む
package sample.spark;
import static spark.Spark.*;
public class Main {
public static void main(String[] args) {
before((request, response) -> {
System.out.println("before");
});
get("/hello", (request, response) -> {
System.out.println("/hello");
return "Hello Spark!!";
});
after((request, response) -> {
System.out.println("after");
});
}
}
before
/hello
after
-
before()
とafter()
でリクエストの前後に処理を挟むことができる。 -
before()
で認証チェックをして、認証エラーの場合はhalt()
で処理を中断させる、といった実装ができる。
##処理を挟むパスを限定させる
package sample.spark;
import static spark.Spark.*;
public class Main {
public static void main(String[] args) {
before("/hoge", (request, response) -> {
System.out.println("before hoge");
});
get("/hoge", (request, response) -> {
System.out.println("/hoge");
return "Hoge";
});
get("/fuga", (request, response) -> {
System.out.println("/fuga");
return "Fuga";
});
after("/fuga", (request, response) -> {
System.out.println("after fuga");
});
}
}
before hoge
/hoge
/fuga
after fuga
- パスを指定して、そのパスのときだけ処理を挟むようにできる。
- ワイルドカードの指定も可能。
#例外発生時の処理を定義する
package sample.spark;
import static spark.Spark.*;
public class Main {
public static void main(String[] args) {
get("/null", (request, response) -> {
throw new NullPointerException("Test NullpointerException");
});
exception(NullPointerException.class, (e, request, response) -> {
response.status(200);
response.body(e.getMessage());
});
}
}
-
exception()
で、指定した例外が発生した場合の処理を定義できる。
#静的ファイルを公開する
##クラスパス上のファイルを公開する
`-src/main/java/
|-sample/spark/
| `-Main.java
`-webapp/
`-index.html
package sample.spark;
import static spark.Spark.*;
public class Main {
public static void main(String[] args) {
staticFileLocation("/webapp");
get("/hello", (request, response) -> "Hello Spark!!");
}
}
<html>
<head>
<title>Sample Spark Framework</title>
</head>
<body>
<h1>Hello Spark!!</h1>
</body>
</html>
ブラウザで http://localhost:4567/
にアクセス。
-
staticFileLocation()
で、クラスパス上の場所を指定して静的ファイルとして公開できる。
##クラスパス外のディレクトリを公開する
package sample.spark;
import static spark.Spark.*;
public class Main {
public static void main(String[] args) {
externalStaticFileLocation("F:/tmp/spark");
get("/hello", (request, response) -> "Hello Spark!!");
}
}
<html>
<head>
<title>External Directory</title>
</head>
<body>
<h1>F:\tmp\spark\index.html</h1>
</body>
</html>
-
externalStaticFileLocation()
で、クラスパス外のローカルのディレクトリを指定して静的ファイルとして公開できる。
#レスポンスの形式を変換する
Jackson を使って、レスポンスを JSON 形式に変換する。
package sample.spark;
import static spark.Spark.*;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import spark.ResponseTransformer;
import com.fasterxml.jackson.databind.ObjectMapper;
public class Main {
public static void main(String[] args) {
get("/json", (request, response) -> {
Map<String, Object> map = new HashMap<>();
map.put("str", "Hoge");
map.put("bool", true);
map.put("list", Arrays.asList(1, 2, 3, 4));
return map;
}, new MyJsonTranformer());
}
private static class MyJsonTranformer implements ResponseTransformer {
private ObjectMapper mapper = new ObjectMapper();
@Override
public String render(Object model) throws Exception {
return this.mapper.writeValueAsString(model);
}
}
}
-
ResponseTransformer
を実装したクラスを作成し、ルーティングを定義する関数の最後の引数に渡すことで、レスポンスの形式を変換する処理を挟むことができる。
#ビューにテンプレートエンジンを使用する
Mustache をテンプレートエンジンとして使用して、ビューを表示させてみる。
##Mustache 用のプラグインを追加する
dependencies {
compile 'com.sparkjava:spark-template-mustache:1.0.0'
}
##実装
`-src/main/
|-java/sample/spark/
| `-Main.java
`-resources/templates/
`-hello.mustache
package sample.spark;
import static spark.Spark.*;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import spark.ModelAndView;
import spark.template.mustache.MustacheTemplateEngine;
public class Main {
public static void main(String[] args) {
get("/mustache", (request, response) -> {
List<User> users = Arrays.asList(new User("sato", 19), new User("suzuki", 18), new User("tanaka", 20));
Map<String, Object> model = new HashMap<>();
model.put("users", users);
return new ModelAndView(model, "hello.mustache");
}, new MustacheTemplateEngine());
}
public static class User {
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
}
<html>
<head>
<title>Mustache Template</title>
</head>
<body>
<h1>Mustache Template</h1>
{{#users}}
<h2>Name</h2>
<p>{{name}}</p>
<h2>Age</h2>
<p>{{age}}</p>
<hr>
{{/users}}
</body>
</html>
-
hello.mustache
は、クラスパス上の/templates
の下に配置する。
##説明
- テンプレートは、 Freemarker, Verocity, Mustache の3つがサポートされている。
- 各テンプレートエンジンごとにプラグインの jar が用意されているので、使用するテンプレートエンジンに合わせてクラスパスに追加する。
- ルーティング関数では、以下のように実装する。
- 最後の引数に、各テンプレートごとの
TemplateEngine
インスタンスを渡す。 - ルーティング関数は、
ModelAndView
インスタンスを返すようにする。
- 最後の引数に、各テンプレートごとの
#Web アプリとしてサーブレットコンテナに配備する
##実装
spark/
|-src/main/
| |-java/sample/spark/
| | `-MySparkApplication.java
| `-webapp/WEB-INF/
| `-web.xml
`-build.gradle
apply plugin: 'jetty'
apply plugin: 'war'
sourceCompatibility = '1.8'
targetCompatibility = '1.8'
repositories {
mavenCentral()
}
dependencies {
compile 'com.sparkjava:spark-core:2.0.0'
}
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<filter>
<filter-name>SparkFilter</filter-name>
<filter-class>spark.servlet.SparkFilter</filter-class>
<init-param>
<param-name>applicationClass</param-name>
<param-value>sample.spark.MySparkApplication</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>SparkFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
package sample.spark;
import static spark.Spark.*;
import spark.servlet.SparkApplication;
public class MySparkApplication implements SparkApplication {
@Override
public void init() {
get("/hello", (request, response) -> "Hello Spark as WebApp.");
}
}
##動作確認
> gradle jettyRun
:compileJava
:processResources UP-TO-DATE
:classes
:jettyRun
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
> Building 75% > :jettyRun > Running at http://localhost:8080/spark
ブラウザで http://localhost:8080/spark/hello
にアクセス。
##説明
-
SparkApplication
を実装したクラスを作成し、 web.xml でSparkFilter
の設定を記述することで、通常の Web アプリケーションとして Spark を利用することができる。
#参考