Edited at

Spark Framework使い方メモ

More than 3 years have passed since last update.

ちょっとした Web アプリケーションを作るのに便利な Spark の使い方メモ。

Apache Spark ではない(紛らわしい...)。


インストール


build.gradle

dependencies {

compile 'com.sparkjava:spark-core:2.0.0'
}


Hello World


Main.java

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!!"




パスパラメータ

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


Main.java

package sample.spark;

import static spark.Spark.*;

public class Main {

public static void main(String[] args) {
staticFileLocation("/webapp");

get("/hello", (request, response) -> "Hello Spark!!");
}
}



index.html

<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!!");
}
}


F:/tmp/spark/index.html

<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 用のプラグインを追加する


build.gradle

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;
}
}
}


hello.mustache

<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


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'
}



web.xml

<?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>



MySparkApplication.java

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 を利用することができる。


参考