Swaggerの環境セットアップ

LastaFluteでSwaggerを使うためのセットアップ手順について

maihama-showbaseを参考に

LastaFluteのマルチプロジェクトのExample, maihama の maihama-showbase にて、Swaggerが利用できる状態になっているで、そちらを参考にしながら説明していきます。

maihama-showbase からプロジェクトをスタートアップさせたときは、すでにセットアップ済みになります。 (JavaScript対抗ではなく動作確認のしづらい) JSON API を作成するのであれば、maihama-showbase でスタートアップすると良いでしょう。

Swaggerの環境セットアップ方法

【注意】JettyBootだと、ローカル環境で Swagger が動かない状態です。ローカルで Swagger を使うのであれば、TomcatBootを使ってください。 (Jettyが、どうやら WEB-INF/lib の jar しか探してくれない!?) (また、ローカルで動かないことと無関係ですが、Jetty の場合は web.xml の metadata-complete="true" になっていると動きません)

1. pom.xmlにて、lasta-meta.jarを

LastaFluteでSwaggerを使うために最適化されたライブラリである lasta-meta.jar を pom.xml にて依存ライブラリに追加します。 (Exampleで書いてるバージョンが古いかもしれないので、必ず最新版の確認を)

e.g. add lasta-meta.jar library @pom.xml
        ...

        <lasta.meta.version>0.6.0</lasta.meta.version>
        <swagger.ui.version>3.35.0</swagger.ui.version>

        ...

        <dependency> <!-- utflute depends but runtime scope here for swagger -->
            <groupId>org.lastaflute.meta</groupId>
            <artifactId>lasta-meta</artifactId>
            <version>${lasta.meta.version}</version>
            <scope>runtime</scope>
        </dependency>

        ...

        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>swagger-ui</artifactId>
            <version>${swagger.ui.version}</version>
            <scope>runtime</scope>
        </dependency>

        ...

Exampleで書いてるバージョンが古いかもしれないので、必ず最新版の確認をしましょう。

実際には、UTFluteが依存していますが、UTFlute自体はtestスコープなので、そのままだとアプリ実行環境で使えないので、明示的に宣言をしてスコープを変えています。 (また、UTFluteが依存しているバージョンが古いかもしれないですし)

2. [app]_env.propertiesにて、swagger.enabled を

Swaggerの有効、無効を環境ごとに切り替えるために、[app]_env.properties でプロパティを設定します。 例えば、ローカルでしか使わないとか、本番だけは無効にするとか、そういう制御ができるように。 (セキュリティ上、本番では無効にすべきです)

e.g. add swagger.enabled property @[app]_env.properties

# ========================================================================================
#                                                                                     Web
#                                                                                    =====
...

# ----------------------------------------------------------
#                                                    Swagger
#                                                    -------
# is it use swagger in this environment?
swagger.enabled = true
...

3. デプロイ環境の properties にも忘れず

[app]_env_integration.properties や [app]_env_production.properties など、デプロイ環境の properties にも忘れずに swagger.enabled を設定しましょう。

DBFlute の Doc タスクを叩けば、PropertiesHTML で抜けがないか確認できます。

4. FreeGenを叩きましょう

プロパティを追加したら、FreeGenを叩きましょう。

この後の SwaggerAction にて、このプロパティを利用します。

5. BootクラスでSwaggerを読み込めるように

Swaggerは、Swaggerのjarファイルの中にリソースが入っていますので、それを起動時に読み込むように Boot クラスを調整します。

useMetaInfoResourceDetect() と useWebFragmentsDetect() を使います。

e.g. use Swagger resources, by TomcatBoot @Java
public static void main(String[] args) { // e.g. java -Dlasta.env=production -jar maihama-showbase.war
    TomcatBoot boot = new TomcatBoot(8098, "/showbase").asDevelopment(isDevelopment());
    boot.useMetaInfoResourceDetect().useWebFragmentsDetect(jarName -> { // both for swagger
        return jarName.contains("swagger-ui"); // meanwhile, restricted by [app]_env.properties
    });
    boot.bootAwait();
}

private static boolean isDevelopment() {
    return System.getProperty("lasta.env") == null;
}

useWebFragmentsDetect() の引数では、swagger-uiのリソースだけを読み込むように絞っています。 すべてのリソースを読み込むと不要な処理が走り起動コストが増えてしまうためです。 (JettyBootだと、まだ絞込みのオプションがないかも...)

6. ActionAdjustmentを作成

セキュリティ強化のために、ActionAdjustmentProvider でリクエスト制限を掛けます。 この後出てくる SwaggerAction でも制限掛けていますが、本番で SwaggerAction 自体に到達してしまうこと自体が気持ち悪いので、もっと前の段階で弾きます。

isForced404NotFoundRouting()をオーバーライドして、swaggerにアクセスしてもOKかどうかの判定をしましょう。 先ほど追加した swagger.enabled プロパティもここで参照します。

e.g. ActionAdjustmentProvider of showbase @Java
/**
 * @author jflute
 */
public class ShowbaseActionAdjustmentProvider extends MaihamaActionAdjustmentProvider {

    private final ShowbaseConfig config;

    public ShowbaseActionAdjustmentProvider(ShowbaseConfig config) {
        this.config = config;
    }

    @Override
    public boolean isForced404NotFoundRouting(HttpServletRequest request, String requestPath) {
        if (!config.isSwaggerEnabled() && isSwaggerRequest(requestPath)) { // e.g. swagger's html, css
            return true; // to suppress direct access to swagger resources at e.g. production
        }
        return super.isForced404NotFoundRouting(request, requestPath);
    }

    private boolean isSwaggerRequest(String requestPath) {
        return requestPath.startsWith("/webjars/swagger-ui") || requestPath.startsWith("/swagger");
    }
}

7. SwaggerActionを作成

Swaggerの画面を表示するための SwaggerAction を作成します。

app.web 直下に、ほぼ以下の通りに作成しましょう。 スーパークラスの名前やConfigクラスの名前は、自分のアプリ名に合わせます。ここでも、swagger.enabled プロパティを参照しています(実質的に、ここでは二重チェックの役割として)

e.g. SwaggerAction of showbase @Java
/**
 * The action to show swaggar-ui.
 * @author awaawa
 * @author jflute
 */
@AllowAnyoneAccess
public class SwaggerAction extends ShowbaseBaseAction implements LaActionSwaggerable {

    // ===================================================================================
    //                                                                           Attribute
    //                                                                           =========
    @Resource
    private RequestManager requestManager;
    @Resource
    private ShowbaseConfig config;

    // ===================================================================================
    //                                                                             Execute
    //                                                                             =======
    @Execute
    public HtmlResponse index() {
        verifySwaggerAllowed();
        String swaggerJsonUrl = toActionUrl(SwaggerAction.class, moreUrl("json"));
        return new SwaggerAgent(requestManager).prepareSwaggerUiResponse(swaggerJsonUrl);
    }

    @Execute
    public JsonResponse<Map<String, Object>> json() {
        verifySwaggerAllowed();
        return asJson(new SwaggerGenerator().generateSwaggerMap());
    }

    private void verifySwaggerAllowed() { // also check in ActionAdjustmentProvider
        verifyOrClientError("Swagger is not enabled.", config.isSwaggerEnabled());
    }
}	

8. LastaDocTest に付加情報を保存するテスト追加

デプロイ環境でも、JavaDocのe.g.値などの付加情報を利用するために、swagger.json を保存する UnitTest を [App]LastaDocTest に追加します。

test_swaggerJson() を追加し、さらに prepareMockContextPath() もオーバーライドしましょう。

e.g. swaggerJson test in LastaDocTest of showbase @Java
/**
 * @author jflute
 */
public class ShowbaseLastaDocTest extends UnitShowbaseTestCase {

    @Override
    protected String prepareMockContextPath() {
        return ShowbaseBoot.CONTEXT; // basically for swagger
    }

    public void test_document() throws Exception {
        saveLastaDocMeta();
    }

    public void test_swaggerJson() throws Exception {
        saveSwaggerMeta(new SwaggerAction());
    }
}

コンテキストパス (contextPath) も Swagger で利用したいので、prepareMockContextPath() をオーバーライドして、Bootクラスで利用しているコンテキストパスをそのまま利用します。 Bootクラスでハードコードしているコンテキストパスを定数化して、利用できるようにしましょう。

e.g. define CONTEXT in boot of showbase @Java
/**
 * @author jflute
 */
public class ShowbaseBoot { // #change_it_first

    public static final String CONTEXT = "/showbase";

    public static void main(String[] args) { // e.g. java -Dlasta.env=production -jar maihama-showbase.war
        TomcatBoot boot = new TomcatBoot(8098, CONTEXT).asDevelopment(isDevelopment());
...

9. swagger.jsonがwarファイルに含まれるように

test_swaggerJson() で作成された swagger.json が war ファイルに含まれるように、Maven もしくは Gradle のビルド設定を調整します。

Maven であれば、maven-resources-plugin で 強引に コピーします。 ("for boot configuration" の部分は今回の件とは無関係ですが、本来あった方が良い設定なので入れておいても問題ありません)

e.g. embed swagger.json in Maven @Java
    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-resources-plugin</artifactId>
        <executions>
            <execution>
                <id>copy-common-resources</id>
                <phase>generate-resources</phase>
                <goals>
                    <goal>copy-resources</goal>
                </goals>
                <configuration>
                    <outputDirectory>${basedir}/target/${project.build.finalName}</outputDirectory>
                    <overwrite>true</overwrite>
                    <resources>
                        <resource> <!-- for boot configuration -->
                            <directory>${basedir}/../maihama-common/src/main/resources</directory>
                            <includes>
                                <include>*_config.properties</include>
                                <include>*_env*.properties</include>
                                <include>tomcat_*.properties</include>
                            </includes>
                        </resource>
                        <resource> <!-- for swagger -->
                            <directory>${basedir}/target/lastadoc</directory>
                            <includes>
                                <include>swagger.json</include>
                            </includes>
                        </resource>
                    </resources>
                </configuration>
            </execution>
        </executions>
    </plugin>

Gradle であれば、こんな感じ!?

e.g. embed swagger.json in Gradle @Java
into('WEB-INF/classes') {
    from fileTree(dir: new File(project.buildDir, 'lastadoc/'), includes: ['swagger.json'])
}

10. /swagger にアクセスして表示されたら完了

です。

デプロイ環境でのSwagger

基本的には、特に変わりなく利用できます。(プロパティでtrueになっている環境であれば)

ひとつ特徴的なのが、JavaDocからデフォルト値を取得する "e.g." 情報の取得の仕方が変わります。

ローカル
ソースコードを実行時に解決している
デプロイ環境
ビルド時にソースコードを解析した情報をwarの中に含める

デプロイ環境では、UnitTest の [App]LastaDocTest にて、ソースコードを解析した情報をファイルとして出力します。 そのファイルを war ファイルの中に含めます。ゆえに、テストをスキップして war を作成すると、その情報が入りません。 テストはスキップしないようにしましょう。

SSL環境でのSwagger利用

Swagger UI の HTTP? or HTTPS?

LastaFluteでの Swagger は、HttpServletRequest の getScheme() を元に、HTTP か HTTPS かを判断しています。 ゆえに、例えば Tomcat が HTTPS であることを知らなければ、Swagger UI でのリクエストは HTTP になります。

Tomcat に schema を教えてあげる

解決方法はいくつかありますが、ひとつの方法として、Tomcat自身に HTTPS を教えてあげると良いでしょう。 インストールTomcatであれば、Tomcat自身の設定ファイルで scheme が指定できるはずです。 また、TomcatBoot であれば、[app]_env.properties 経由で設定できます。

まず、[App]Bootにて、TomcatBootと.propertiesを連動させます。

e.g. [App]Bootにて、TomcatBootと.propertiesを連動させる @Java
...
boot.configure("fortress_config.properties", "fortress_env.properties"); // e.g. URIEncoding

そして、[app]_env.propertiesにてTomcatBootのschema関連の設定をします。 (secureやproxyPortは要件に応じて)

e.g. [app]_env.propertiesにて、TomcatBootのschema関連の設定 @Properties
# ----------------------------------------------------------
#                                                     Tomcat
#                                                     ------
# The scheme of tomcat
tomcat.scheme = https

# The secure of tomcat
tomcat.secure = true

# The proxy port of tomcat
tomcat.proxyPort = 443