Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

Spark Framework使い方メモ

More than 5 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 にアクセスする。

spark.JPG

ポートを変更する

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

spark.JPG

  • :<名前> という形でパスにパラメータを定義することができる。
  • パラメータは、リクエストの 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 にアクセスする。

spark.JPG

  • ワイルドカード(*)を使ってパスを定義できる。
  • ワイルドカードにあたる部分のパスは、リクエストの 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 にアクセスする

spark.JPG

  • クエリマップとは、 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());
        });
    }
}

spark.JPG

  • 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/ にアクセス。

spark.JPG

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

spark.JPG

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

spark.JPG

  • 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 の下に配置する。

動作確認

spark.JPG

説明

  • テンプレートは、 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 にアクセス。

spark.jpg

説明

  • SparkApplication を実装したクラスを作成し、 web.xml で SparkFilter の設定を記述することで、通常の Web アプリケーションとして Spark を利用することができる。

参考

opengl-8080
ただのSE。Java好き。
tis
創業40年超のSIerです。
https://www.tis.co.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away