Edited at

Apache Camelで簡単にRESTサービス作成とドキュメント化(Swagger+Swagger-UI)


はじめに

今回はApache CamelのRest DSLを紹介します。

REST DSLを使用することで、RESTサービスを簡単に定義することができます。

CamelではCXFを用いてRESTサービスを作成することもできるのですが、CXFは複雑で面倒です。そのため、Camelでは容易に利用できるRest DSLが開発されました。Rest DSLではCXFと同等の機能は提供していませんが、通常利用する機能はほぼサポートされています(らしいです)。

たまたまですが、最近投稿された以下の記事と一部重複しています。こちらも参考に。(まさか記事の少ないCamelでかぶるとは・・・)


Rest DSLの簡単な例

まずは、Rest DSLを使用した簡単なRESTサービスのサンプルプログラムを紹介します。


POMの編集

最初にRest DSLを使用するためにpom.xmlを以下のように編集します。

Rest DSLを利用するためにcamel-jetty9を追加しています。後述しますが、camel-jetty9以外のコンポーネントでもRest DSLを利用することができます。

また、今回のサンプルはSpring XML DSLを用いてルートを定義するため、camel-springも追加しています。


pom.xml

    <properties>

<camel.version>2.23.0</camel.version>
</properties>

<dependencies>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-core</artifactId>
<version>${camel.version}</version>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-spring</artifactId>
<version>${camel.version}</version>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-jetty9</artifactId>
<version>${camel.version}</version>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-jackson</artifactId>
<version>${camel.version}</version>
</dependency>
</dependencies>



Rest DSLの設定

次にRest DSLの設定を行うため、restConfiguration要素をXML DSLに記述します。

restConfigurationの例です。


camel-context.xml

    <camelContext

xmlns="http://camel.apache.org/schema/spring">

<restConfiguration
component="jetty" - (1)
port="9091" - (2)
scheme="http" - (3)
contextPath="/restsample" - (4)
bindingMode="off" - (5)
>
<componentProperty key="minThread" value="1"/> - (6)
<componentProperty key="maxThread" value="8"/> - (6)
</restConfiguration>
</camelContext>


(1) 使用するコンポーネント


component="jetty"


Rest DSLを使用する場合、最初にどのRESTコンポーネントを使用するかを設定します。

RESTコンポーネントは次の6つから選ぶことになります。アプリケーションにサーバを組み込むのであれば、jettyを使用するのがオーソドックスかと思います。そのため、今回の例ではjettyを使用しています。


  • camel-jetty

  • camel-netty4-http

  • camel-restlet

  • camel-servlet

  • camel-spark-rest

  • camel-undertow

(2) 使用するポート


port="9091"


RESTサービスが使用するポートを指定します。例ではポート"9091"を指定しています。

(3) スキーマ指定


scheme="http"


プロトコルを指定します。"http"か"https"が使用できます。

(4) コンテキストパス指定


contextPath="/restsample"


RESTサービスのコンテキストパスを指定します。

例のコンテキストパスを指定した場合、URLは以下のようになります。

http://serverのIP/restsample/~~

(5) バインディング

Rest DSLではPOJOとXML/JSONの自動バインディングをサポートしています。


bindingMode="off"


デフォルトは"off"で、off, auto, json, xml, json_xmlを指定できます。

(6) コンポーネントのプロパティを指定

RESTコンポーネントのプロパティを指定します。今回はjettyコンポーネントを利用するため、以下のように最小スレッド数(minThread)や最大スレッド数(maxThread)を指定しています。

このプロパティは使用するRESTコンポーネントごとに異なります。


      <componentProperty key="minThread" value="1"/>

<componentProperty key="maxThread" value="8"/>


RESTサービスの定義

RESTサービスの設定が終わったので、いよいよ実際に動くRESTサービスを作成してみます。

名前をパラメータで渡すと、Hello+名前で応答するRESTサービスを作成してみます。

作成したRESTサービスは以下のようになります。


camel-context.xml

    <camelContext

xmlns="http://camel.apache.org/schema/spring">

<restConfiguration>
~省略~
</restConfiguration>

<rest path="/say" produces="text/html"><!-- (1) -->
<get uri="/hello?name={name}"><!-- (2) -->
<to uri="direct:hello"/>
</get>
</rest>

<route>
<from uri="direct:hello"/>
<transform>
<simple>Hello ${header.name}</simple><!-- (3) -->
</transform>
</route>

</camelContext>


(1)(2) path="/say"、uri="/hello"でRESTサービスのURLとなり、以下のURLでアクセスできるようになります。


  • http://サーバのIP:9091/restsample/say/hello

uri="/hello"の後のname={name}(2)で、パラメータnameをリクエスト時に渡すことで、nameというヘッダー名でメッセージ中に格納されます。

(3) simpleタグで括られたHello ${header.name}で、レスポンスを返しています。


アクセス

実際に、作成したRESTサービスにアクセスしてみましょう。

ローカルPCからアプリケーションを起動した場合、以下のURIでRESTサービスにアクセスすることができます。

ブラウザで開くと、"name=hoge"と指定しているため、「Hello hoge」と表示されます。


色々なRESTサービスを定義してみる

次はPOSTのRESTサービスを作成してみます。

POSTするのはXML、JSONのいずれのデータ形式でも良いですが、今回はJSON形式を利用しました。

まず、pom.xmlにJSONをバインディングできるようにcamel-jacksonを追加します。


pom.xml

        <dependency>

<groupId>org.apache.camel</groupId>
<artifactId>camel-jackson</artifactId>
<version>${camel.version}</version>
</dependency>

CamelContextは以下のように記述しました。

/say/item宛にJSONをポストすると、それをログに出力し、送ったJSONをそのままレスポンスで返すだけのものです。

        <rest path="/say" produces="text/html">

<post uri="/item" type="example.camelbegginer.restdsl.Item" outType="example.camelbegginer.restdsl.Item"
consumes="application/json">
<to uri="direct:item" />
</post>
</rest>

<route>
<from uri="direct:item" />
<log message="body: ${body}" />
</route>

type,outTypeでバインディングするPOJO(Item.java)を指定しています。

Item.javaは以下のとおり。


Item.java

package example.camelbegginer.restdsl;

public class Item {
private String name;
private String value;
public Item() {

}

public Item(String name, String value) {
this.name = name;
this.value = value;
}

public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}

@Override
public String toString() {
return String.format("name = %s, value = %s", this.name, this.value);
}
}


"http://localhost:9091/restsample/say/item"に以下のJSONをPOSTします。

{

"name": "test name",
"value": "test value"
}

次のログが出力されます。

[2019-06-02 11:14:01.564], [INFO ], route1, CamelJettyServer(0x713ec32d)-19, route1, body: name = test name, value = test value


Swaggerでドキュメント生成

SwaggerはREST APIを記述するための仕様で、作成した仕様からAPIやドキュメントを生成するなど、REST APIを利用するための便利なツールがたくさん用意されています。

Rest DSLでもSwaggerに対応しており、Rest DSLで作成したREST APIからドキュメントを生成することができます。

Swaggerの概要は以下の記事が参考になるかと思います。(私もRest DSLは初めてで、この記事等で学習しました。)

以降はCamel in Action, Second Edition(英語書籍)のサンプルを参考にしています。ソースコードは以下のサイトの「Source Code」リンクからダウンロードできます。



  • Camel in Action, Second Edition
    ※Camel in Actionは900ページぐらいあり、Camelのことが知りたければこれを読めば全てわかるようになっています。お勧めの一冊ですが、日本語版はなく値段も高いのがネックです。たまに半額セールもやっているのでそれを利用するのも良いかもしれません。

まずはMavenの依存関係にcamel-swagger-javaを追加します。


pom.xml

        <dependency>

<groupId>org.apache.camel</groupId>
<artifactId>camel-swagger-java</artifactId>
<version>${camel.version}</version>
</dependency>

次にCamel Contextで記載したrestConfigurationを編集します。


pom.xml

        <restConfiguration

component="jetty"
port="9091"
scheme="http"
contextPath="/restsample"
bindingMode="off"
apiContextPath="api-docs"
enableCORS="true"
>
<componentProperty key="minThreads" value="4"/>
<componentProperty key="maxThreads" value="8"/>
</restConfiguration>

以下の2行を追加しています。


pom.xml

            apiContextPath="api-docs"

enableCORS="true"

これにより、以下のURLでREST APIの仕様がJSONで取得できるようになります。

このJSONをSwagger Specと呼びます。

また、必須ではありませんが、APIの説明等の補足情報も以下のように設定することができます。


pom.xml

            <apiProperty key="api.version" value="1.0.0"/>

<apiProperty key="api.title" value="Rest DSLのサンプルAPI"/>
<apiProperty key="api.description" value="Rest DSLのサンプルAPIです。"/>

設定したプロパティの説明は下表のとおりです。この情報を指定することで、ドキュメント出力した際にこれらの情報も一緒に表示することができます。

項目
説明

api.version
APIのバージョンを指定します。

api.title
APIのタイトルを記述します。

api.description
APIの説明を記述します。


http://localhost:9091/restsample/api-docs


実際にブラウザ(FireFox)からアクセスして表示された情報は以下のようになります。

image.png

Swagger SpecをSwagger UIで読み込むとHTML形式でドキュメントを生成することができます。

次はこのSwagger UIを作成中のアプリケーションに組み込んでみたいと思います。


Swagger UIを起動して、HTMLドキュメントを生成する

Swagger UIはWebアプリケーションであるため、アプリケーションをWARファイルにまとめ、jetty pluginで起動させるようにします。

Mavenのpom.xmlに以下を追加します。


pom.xml

    <packaging>war</packaging>

<swagger-ui-version>3.20.9</swagger-ui-version>
<jetty9-plugin-version>9.4.15.v20190215</jetty9-plugin-version>


Swagger UIを依存性に追加します。


pom.xml

        <dependency>

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

web.xmlを以下のディレクトリに追加します。

src/main/webapp/WEB-INF/web.xml


web.xml

<?xml version="1.0" encoding="ISO-8859-1"?>

<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

<display-name>Swagger XML DSL</display-name>

<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>

</web-app>


jetty pluginを使い、"mvn jetty:run-war"と実行することでJettyを起動できるようにします。


pom.xml

    <build>

<finalName>${project.artifactId}</finalName>

<!-- Swagger UIを組み込む -->
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.1.1</version>
<executions>
<execution>
<phase>generate-resources</phase>
<goals>
<goal>unpack</goal>
</goals>
<configuration>
<artifactItems>
<artifactItem>
<groupId>org.webjars</groupId>
<artifactId>swagger-ui</artifactId>
<version>${swagger-ui-version}</version>
<overWrite>true</overWrite>
<outputDirectory>${project.build.directory}/swagger-ui</outputDirectory>
<excludes>**/*.gz</excludes>
</artifactItem>
</artifactItems>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>2.6</version>
<executions>
<execution>
<id>copy-resources</id>
<phase>generate-resources</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/${project.artifactId}</outputDirectory>
<resources>
<resource>
<directory>${project.build.directory}/swagger-ui/META-INF/resources/webjars/swagger-ui/${swagger-ui-version}</directory>
<filtering>true</filtering>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>

<!-- "mvn jetty:run-war"でJettyを起動できるようにする -->
<plugin>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>${jetty9-plugin-version}</version>
<configuration>
<httpConnector>
<port>8080</port>
</httpConnector>
<webApp>
<contextPath>/${project.artifactId}</contextPath>
</webApp>
</configuration>
</plugin>
</plugins>
</build>


Eclipseで「実行」か「デバッグ」でMavenのゴールを以下のように設定して実行すると、jettyが起動しSwagger UIへアクセスできるようになります。

ゴール: jetty:run-war

image.png

jettyが起動すると以下のようなログが出力されます。最後に「Started Jetty Server」と出力されれば成功です。

[INFO] Scanning elapsed time=957ms

[INFO] DefaultSessionIdManager workerName=node0
[INFO] No SessionScavenger set, using defaults
[INFO] node0 Scavenging every 660000ms
[INFO] Started o.e.j.m.p.JettyWebAppContext@48ccbb32{Swagger XML DSL,/camel-begginer-restdsl,file:///C:/pleiades/workspace/camel-begginer-restdsl-xml/target/camel-begginer-restdsl/,AVAILABLE}{C:\pleiades\workspace\camel-begginer-restdsl-xml\target\camel-begginer-restdsl.war}
[INFO] Started ServerConnector@48e74764{HTTP/1.1,[http/1.1]}{0.0.0.0:8080}
[INFO] Started @15280ms
[INFO] Started Jetty Server

コマンドラインから実行する場合は、コマンドプロンプト(Windowsの場合)から以下のように入力しjettyを起動します。

> mvn compile jetty:run-war

jettyが起動すると、以下のURLでSwagger UIにアクセスできます。"camel-begginer-restdsl"にはプロジェクト名が入ります。起動すると「Swagger Pet」というサンプルのSwagger Specが自動で読み込まれています。

http://localhost:8080/camel-begginer-restdsl

image.png

今回作成したアプリケーションのSwagger Specを参照します。参照先は以下のURIで、画面上部のテキストボックスに入力し、「Explore」ボタンを押すと読み込まれます。

http://localhost:9091/restsample/api-docs

image.png

表示されたSwagger UIのドキュメントは仕様を表示するだけではなく、実際にREST APIへリクエストし、レスポンスを表示することもできます。

また、以下のようにcurlでのコマンドラインも表示されます。

image.png

今回使用したCamelContextは以下のとおりです。

    <camelContext

xmlns="http://camel.apache.org/schema/spring">

<restConfiguration
component="jetty"
port="9091"
scheme="http"
contextPath="/restsample"
bindingMode="json"
apiContextPath="api-docs"
enableCORS="true"
>
<componentProperty key="minThreads" value="4"/>
<componentProperty key="maxThreads" value="8"/>

<apiProperty key="api.version" value="1.0.0"/>
<apiProperty key="api.title" value="Rest DSLのサンプルAPI"/>
<apiProperty key="api.description" value="Rest DSLのサンプルAPIです。"/>

</restConfiguration>

<rest path="/say" produces="text/html">
<description>テスト</description>
<get uri="/hello?name={name}">
<description>Hello+あなたの名前を返すAPIです。</description>
<param name="name" type="query" description="あなたの名前を設定します。" required="true" defaultValue="名無しさん" dataType="string" />
<responseMessage code="200" message="成功" />
<to uri="direct:hello" />
</get>

<post uri="/item" type="example.camelbegginer.restdsl.Item" outType="example.camelbegginer.restdsl.Item"
consumes="application/json">
<responseMessage code="200" message="aaaaa" responseModel="example.camelbegginer.restdsl.Item" />
<to uri="direct:item" />
</post>

<get uri="/hello/{id}">
<description>test get</description>
<param name="id" type="path" description="asgdsfe" required="true" defaultValue="1234" dataType="integer" />
<to uri="direct:helloid" />
</get>
</rest>

<route>
<from uri="direct:hello" />
<transform>
<simple>Hello ${header.name}</simple>
</transform>
</route>

<route>
<from uri="direct:item" />
<log message="body: ${body}" />
<setBody>
<simple>${body}</simple>
</setBody>
<transform>
<simple>${body}</simple>
</transform>
</route>

<route>
<from uri="direct:helloid" />
<setHeader headerName="CamelHttpResponseCode">
<constant>404</constant>
</setHeader>
</route>
</camelContext>


HTTPステータスコードを設定する

デフォルトでは、正常にRESTサービスの処理が完了すれば、レスポンスのHTTPステータスコードには"200"(OK)が設定されます。RESTfulであれば、HTTPステータスコードは"201"(Created)や"202 Accepted"であったり、処理結果に応じて設定します。

ここでは、HTTPステータスコードを指定する例として、GETで特定のリソースをリクエストしたが、リソースが存在しなかったとして、"404"(Not Found)を返すサービスで説明します。

        <rest path="/say" produces="text/html">

<get uri="/hello/{id}"><!-- (1) -->
<to uri="direct:helloid" />
</get>
</rest>

<route>
<from uri="direct:helloid" />
<setHeader headerName="CamelHttpResponseCode"><!-- (2) -->
<constant>404</constant>
</setHeader>
</route>

(1) まず、getタグのuri属性で"/hello/{id}"と指定しています。{id}がGETメソッドでリクエストするリソースのIDになります。このIDはメッセージのヘッダーに自動で格納されます。

(2) HTTPステータスコードは"CamelHttpResponseCode"というヘッダー名に設定すると、レスポンスに指定したことになります。

本来ならリソースのID{id}でDBを検索し、リソースが見つからない場合は"404"を返すのが普通の処理ですが、この例ではHTTPステータスコードの設定例を示したかっただけですので、その処理省略しどんなリクエストがあっても"404"を返すようにしています。


参考