1 はじめに
Ratpack は、高速、効率的、進化可能で、十分にテストされた HTTP アプリケーションを容易にする Java ライブラリのセットです。これは、高性能で効率的な Netty イベント駆動型ネットワークエンジン上に構築されています。
Ratpack は純粋にランタイムです。インストール可能なパッケージや、密結合したビルドツール (例: Rails、Play、Grails) はありません。Ratpack アプリケーションをビルドするには、任意の JVM ビルドツールを使用できます。Ratpack プロジェクトは、プラグインを通じて Gradle の特定のサポートを提供していますが、他のものも使用できます。
Ratpack は、ライブラリ JAR のセットとして公開されています。ratpack-core
ライブラリは、厳密に必須の唯一のライブラリです。ratpack-groovy
、ratpack-guice
、ratpack-jackson
、ratpack-test
などの他のライブラリはオプションです。
1.1 目標
Ratpack の目標は次のとおりです。
- 高速、スケーラブル、効率的であること
- アプリケーションが妥協することなく複雑さを進化できるようにすること
- ノンブロッキングプログラミングの利点を活用し、コストを削減すること
- 他のツールやライブラリを統合する際に柔軟で偏見がないこと
- アプリケーションを簡単かつ徹底的にテストできるようにすること
Ratpack の目標は そうではない
- 完全に統合された「フルスタック」ソリューションであること
- 必要なすべての機能をきちんとしたボックスで提供すること
- 「ビジネスロジック」のアーキテクチャまたはフレームワークを提供すること
2.1 このドキュメントについて
Ratpack のドキュメントは、このマニュアルと Javadoc API リファレンスに分散しています。このマニュアルでは、トピックと概念をハイレベルで紹介し、詳細な API 情報については Javadoc にリンクしています。情報の大部分は Javadoc に含まれています。Ratpack のコアコンセプトを理解すれば、マニュアルはあまり役に立たなくなり、Javadoc がより役に立つようになると考えられます。
1.2.1 コードサンプル
ドキュメント内のすべてのコードサンプルはテストされており、ほとんどがコピー&ペーストして実行できる完全なプログラムです (適切なクラスパスなどが与えられた場合)。
サンプルのほとんどは、テスト中の小さな埋め込み Ratpack アプリケーションとして提供されています。以下は、Ratpack コードサンプルの「Hello World」です。
import ratpack.test.embed.EmbeddedApp;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class Example {
public static void main(String... args) throws Exception {
EmbeddedApp.fromHandler(ctx ->
ctx.render("Hello World!")
).test(httpClient ->
assertEquals("Hello World!", httpClient.getText())
);
}
}
import
ステートメントは、わかりやすくするためにデフォルトで折りたたまれています。クリックして表示/非表示を切り替えます。
この例は、完全な Ratpack アプリケーションです。ただし、EmbeddedApp
は、通常のアプリケーションで一般的に使用されるエントリポイントではありません (一般的なエントリポイントの詳細については、起動の章を参照してください)。EmbeddedApp
はテスト指向です。大規模なアプリケーション中に非常に小さい (または本格的な) アプリを簡単に開始/停止でき、アプリに対して HTTP リクエストを行う便利な方法を提供します。この例では、API を実演することに集中するために、ブートストラップの量を最小限に抑えるために使用されています。
この例では、すべての HTTP リクエストにプレーンテキスト文字列「Hello World」で応答するデフォルト構成で、一時的なポートで Ratpack サーバーを起動しています。ここで使用されている test()
メソッドは、テスト対象のサーバーにリクエストを行うように構成された TestHttpClient
を指定された関数に提供します。この例とその他すべては、Ratpack サーバーに HTTP リクエストを行っています。EmbeddedApp
と TestHttpClient
は、Ratpack の テストサポートの一部として提供されています。
多くの例で使用されているもう 1 つの主要なテストユーティリティは、ExecHarness
です。
import com.google.common.io.Files;
import ratpack.test.exec.ExecHarness;
import ratpack.exec.Blocking;
import java.io.File;
import java.nio.charset.StandardCharsets;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class Example {
public static void main(String... args) throws Exception {
File tmpFile = File.createTempFile("ratpack", "test");
Files.asCharSink(tmpFile, StandardCharsets.UTF_8).write("Hello World!");
tmpFile.deleteOnExit();
String content = ExecHarness.yieldSingle(e ->
Blocking.get(() -> Files.asCharSource(tmpFile, StandardCharsets.UTF_8).read())
).getValueOrThrow();
assertEquals("Hello World!", content);
}
}
EmbeddedApp
が Ratpack アプリケーション全体の作成をサポートしている場合、ExecHarness
は Ratpack の実行モデルのインフラストラクチャのみを提供します。これは通常、Promise
のような Ratpack コンストラクトを使用する非同期コードの単体テストに使用されます (実行モデルの詳細については、「非同期とノンブロッキング」の章を参照してください)。ExecHarness
も Ratpack の テストサポートの一部として提供されています。
1.1.2.1 Java 8 スタイル
Ratpack は Java 8 上に構築されており、Java 8 を必要とします。コードサンプルでは、ラムダ式やメソッド参照などの Java 8 コンストラクトを広範囲に使用しています。Java の経験はあるが、Java 8 の新しいコンストラクトの経験がない場合は、例が「エキゾチック」に見えるかもしれません。
2 クイックスタート
この章では、Ratpack アプリケーションを起動して実行する方法について説明します。
1.2 Groovy スクリプトの使用
Ratpack アプリケーションは、単一の Groovy スクリプトとして実装できます。これは、Ratpack と Groovy を試すのに便利な方法です。
まず、Groovy をインストールします。
次の内容でファイル ratpack.groovy
を作成します。
@Grapes([
@Grab('io.ratpack:ratpack-groovy:2.0.0-rc-1'),
@Grab('org.slf4j:slf4j-simple:1.7.36')
])
import static ratpack.groovy.Groovy.ratpack
ratpack {
handlers {
get {
render "Hello World!"
}
get(":name") {
render "Hello $pathTokens.name!"
}
}
}
コマンドラインで以下を実行して、アプリを起動できます。
groovy ratpack.groovy
サーバーは https://:5050/
で利用可能になります。
handlers()
メソッドは、GroovyChain
オブジェクトに委譲するクロージャを受け取ります。「Groovy ハンドラーチェーン DSL」は、応答処理戦略を構築するために使用されます。
開発中はファイルへの変更がライブになります。ファイルを編集すると、変更は次のリクエストで有効になります。
2.2 Gradle プラグインの使用
Ratpack アプリケーションをビルドするには、Gradle ビルドシステムの使用をお勧めします。Ratpack は Gradle を必要としません。任意のビルドシステムを使用できます。
次の手順では、Gradle がすでにインストールされていることを前提としています。インストール手順については、Gradle ユーザーガイドを参照してください。
Ratpack プロジェクトには、2 つの Gradle プラグインが用意されています。
- io.ratpack.ratpack-java - Java で実装された Ratpack アプリケーション用
- io.ratpack.ratpack-groovy - Groovy で実装された Ratpack アプリケーション用
Gradle ビルドサポートの詳細については、専用の章を参照してください。
1.2.2 Gradle Java プラグインの使用
次の内容で build.gradle
ファイルを作成します
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath "io.ratpack:ratpack-gradle:2.0.0-rc-1"
}
}
apply plugin: "io.ratpack.ratpack-java"
apply plugin: "idea"
repositories {
mavenCentral()
}
dependencies {
runtimeOnly "org.slf4j:slf4j-simple:1.7.36"
}
mainClassName = "my.app.Main"
次の内容でファイル src/main/java/my/app/Main.java
を作成します
package my.app;
import ratpack.core.server.RatpackServer;
public class Main {
public static void main(String... args) throws Exception {
RatpackServer.start(server -> server
.handlers(chain -> chain
.get(ctx -> ctx.render("Hello World!"))
.get(":name", ctx -> ctx.render("Hello " + ctx.getPathTokens().get("name") + "!"))
)
);
}
}
Gradle (つまり、コマンドラインで gradle run
) で run
タスクを実行するか、プロジェクトを IDE にインポートして my.app.Main
クラスを実行することで、アプリケーションを起動できます。
実行すると、サーバーは https://:5050/
で利用可能になります。
handlers()
メソッドは、Chain
オブジェクトを受け取る関数を受け取ります。「ハンドラーチェーン API」は、応答処理戦略を構築するために使用されます。
Ratpack Gradle プラグインは、Gradle の継続的ビルド機能をサポートしています。これを使用すると、ソースコードへの変更が実行中のアプリケーションに自動的に適用されます。
Groovy で Ratpack を使用する方法の詳細については、Gradle の章を参照してください。
2.2.2 Gradle Groovy プラグインの使用
次の内容で build.gradle
ファイルを作成します
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath "io.ratpack:ratpack-gradle:2.0.0-rc-1"
}
}
apply plugin: "io.ratpack.ratpack-groovy"
apply plugin: "idea"
repositories {
mavenCentral()
}
dependencies {
runtimeOnly "org.slf4j:slf4j-simple:1.7.36"
}
次の内容でファイル src/ratpack/ratpack.groovy
を作成します
import static ratpack.groovy.Groovy.ratpack
ratpack {
handlers {
get {
render "Hello World!"
}
get(":name") {
render "Hello $pathTokens.name!"
}
}
}
Gradle (つまり、コマンドラインで gradle run
) で run
タスクを実行するか、プロジェクトを IDE にインポートして ratpack.groovy.GroovyRatpackMain
クラスを実行することで、アプリケーションを起動できます。
実行すると、サーバーは https://:5050/
で利用可能になります。
handlers()
メソッドは、GroovyChain
オブジェクトに委譲するクロージャを受け取ります。「Groovy ハンドラーチェーン DSL」は、応答処理戦略を構築するために使用されます。
Ratpack Gradle プラグインは、Gradle の継続的ビルド機能をサポートしています。これを使用すると、ソースコードへの変更が実行中のアプリケーションに自動的に適用されます。
Groovy で Ratpack を使用する方法の詳細については、Groovy の章を参照してください。
Groovy で Ratpack を使用する方法の詳細については、Gradle の章を参照してください。
3 アーキテクチャ
この章では、Ratpack アプリケーションをハイレベルで説明します。
1.3 強く型付けされている
Ratpack は強く型付けされています。Java で実装されていることに加えて、その API は型を受け入れます。たとえば、Registry
の概念は Ratpack で広く使用されています。Registry
は、型をキーとして使用するマップと考えることができます。
これは、Groovy でアプリケーションを実装している Ratpack ユーザーにとって最も関心があるかもしれません。Ratpack の Groovy アダプターは、最新の Groovy 機能を使用して、静的型付けを完全にサポートし、イディオム的で簡潔な Groovy API を維持します。
2.3 ノンブロッキング
Ratpack は、コアとなるイベントベース (つまり、ノンブロッキング) の HTTP IO エンジンであり、応答ロジックを簡単に構成できる API です。ノンブロッキングであることは、「従来の」ブロッキング Java API とは異なるスタイルの API を強要します。API は 非同期である必要があります。
Ratpack は、HTTP アプリケーションのこのスタイルのプログラミングを大幅に簡素化することを目指しています。非同期コードを構造化するためのサポートを提供し (「非同期とノンブロッキング」の章を参照)、自己構築型で非同期的にトラバースされる関数のグラフにリクエスト処理を構造化するための革新的なアプローチを使用します (使用するのはそれほど複雑ではありません)。
3.3 各部分
次のセクションでは、「引用符」を使用して、主要な Ratpack 用語と概念を示します。
Ratpack アプリケーションは、「起動構成」から始まります。これは、アプリケーションの起動に必要な構成を提供すると想定されます。Ratpack「サーバー」は、「起動構成」のみから構築および起動できます。起動された「サーバー」は、リクエストのリスニングを開始します。この側面の詳細については、「起動」の章を参照してください。
「起動構成」に提供される構成の重要な部分は、「ハンドラーファクトリ」です。これは、「ハンドラー」を作成します。「ハンドラー」は、各リクエストに応答するように求められます。ハンドラーは、次の 3 つのいずれかのことを実行できます。
- リクエストに応答する
- 「次の」ハンドラーに委譲する
- ハンドラーを「挿入」して、すぐにハンドラーに委譲する
すべてのリクエスト処理ロジックは、単にハンドラーの構成です (この側面の詳細については、Handlers
の章を参照してください)。重要なのは、処理がスレッドにバインドされておらず、非同期的に完了できることです。「ハンドラー」API は、この非同期構成をサポートしています。
ハンドラーは「コンテキスト」上で動作します。「コンテキスト」は、ハンドラーグラフにおける特定時点でのリクエスト処理の状態を表します。その重要な機能の1つは「レジストリ」として機能することであり、型によってオブジェクトを取得するために使用できます。これにより、ハンドラーは公開型によって「コンテキスト」から戦略オブジェクト(通常はキーインターフェースを実装するオブジェクト)を取得できます。ハンドラーが他のハンドラーをハンドラーグラフに挿入すると、コンテキストレジストリに貢献できます。これにより、ハンドラーはダウンストリームのハンドラーにコード(戦略オブジェクトとして)を提供できます。詳細については「コンテキスト」の章を参照し、このコンテキストレジストリが実際にどのように使用されるかについては、次のセクションを参照してください。
これは、Ratpackアプリケーションの抽象的な概要説明でした。これがすべて実際のコードにどのように変換されるか、正確には不明な可能性があります。このマニュアルの残りの部分と付属のAPIリファレンスで、詳細を提供します。
4.3 レジストリを介したプラグインと拡張性
Ratpackにはプラグインの概念はありません。ただし、Google Guiceとのアドオン統合は、Guiceモジュールを通じて一種のプラグインシステムを促進します。Guiceは依存性注入コンテナです。Guiceモジュールは、依存性注入コンテナの一部となるオブジェクトを定義します。Guiceモジュールは、ハンドラーで使用されるキーとなるRatpackインターフェースの実装を提供することで、プラグインとして機能できます。Guice統合を使用する場合、Guiceが認識しているすべてのオブジェクト(通常はGuiceモジュール経由)は、「コンテキストレジストリ」を介して取得できます。つまり、ハンドラーは型によってオブジェクトを取得できます。
これがなぜ有用であるかを確認するために、オブジェクトをJSONとしてレスポンスにレンダリングするという要件を使用します。「ハンドラー」に渡される「コンテキスト」オブジェクトには、render(Object)メソッドがあります。このメソッドの実装は、指定された型のオブジェクトをレンダリングできるRenderer
の実装をコンテキストレジストリで検索するだけです。Guiceで使用可能なオブジェクトはレジストリから利用できるため、レンダリングに使用できます。したがって、目的の型のRenderer
実装を含むGuiceモジュールを追加すると、リクエスト処理に統合できます。これは、単純な依存性注入の概念と変わりません。
上記の例ではGuice統合を使用しましたが、このアプローチはGuiceに結び付けられていません(GuiceはRatpackのコアAPIの一部ではありません)。別の依存性注入コンテナ(Springなど)を簡単に使用できますし、コンテナをまったく使用しないこともできます。オブジェクトの任意のソースをRatpackのRegistry
インターフェースに適合させることができます(ビルダーもあります)。
5.3 サービスとビジネスロジック
Ratpackは、リクエスト処理(つまりビジネスロジック)に関連しないコードの構造化方法について意見を持っていません。「サービス」という用語は、何らかのビジネスロジックを実行するオブジェクトの包括的な用語として使用します。
ハンドラーは、もちろん、必要なサービスを自由に使用できます。ハンドラーからサービスにアクセスするための主なパターンは2つあります。
- ハンドラーの構築時にサービスをハンドラーに提供する
- コンテキストレジストリからサービスを取得する
4 起動
この章では、Ratpackアプリケーションの起動方法、つまりRatpack APIへのエントリポイントについて詳しく説明します。
1.4 RatpackServer
RatpackServer
型は、Ratpackのエントリポイントです。このAPIを使用してアプリケーションを起動する独自のメインクラスを作成します。
package my.app;
import ratpack.core.server.RatpackServer;
import ratpack.core.server.ServerConfig;
import java.net.URI;
public class Main {
public static void main(String... args) throws Exception {
RatpackServer.start(server -> server
.serverConfig(ServerConfig.embedded().publicAddress(new URI("http://company.org")))
.registryOf(registry -> registry.add("World!"))
.handlers(chain -> chain
.get(ctx -> ctx.render("Hello " + ctx.get(String.class)))
.get(":name", ctx -> ctx.render("Hello " + ctx.getPathTokens().get("name") + "!"))
)
);
}
}
アプリケーションは、このインターフェースのof()
またはstart()
静的メソッドに渡される関数として定義されます。この関数は、Ratpackアプリケーションの3つの基本側面(つまり、サーバー構成、ベースレジストリ、ルートハンドラー)を指定するために使用できるRatpackServerSpec
を受け取ります。
このマニュアルとAPIリファレンスのほとんどの例では、
RatpackServer
の代わりにEmbeddedApp
を使用してアプリケーションを作成しています。これは、例の「テスト」の性質によるものです。コード例の詳細については、このセクションを参照してください。
1.1.4 サーバー構成
ServerConfig
は、サーバーを起動するために必要な構成設定を定義します。ServerConfig
の静的メソッドを使用してインスタンスを作成できます。
1.1.1.4 ベースディレクトリ
サーバー構成の重要な側面は、ベースディレクトリです。ベースディレクトリは、実質的にアプリケーションのファイルシステムのルートであり、移植可能なファイルシステムを提供します。実行時にファイルに解決されるすべての相対パスは、ベースディレクトリに対する相対パスとして解決されます。静的アセット(例:画像、スクリプト)は、通常、相対パスを使用してベースディレクトリ経由で提供されます。
baseDir(Path)メソッドを使用すると、ベースディレクトリを既知の場所に設定できます。必要に応じて、環境間で移植性を実現するために、これを呼び出すコードは、特定の実行時にベースディレクトリが何であるかを決定する責任があります。
環境間の移植性を向上させるために、クラスパス上でベースディレクトリを見つけることをサポートするBaseDir.find()を使用するのがより一般的です。このメソッドは、パス"/.ratpack"
でクラスパス上のリソースを検索します。
/.ratpack
のデフォルトとは異なるパスを使用するには、BaseDir.find(String)メソッドを使用します。
マーカーファイルの内容は完全に無視されます。これは、囲みディレクトリを見つけるためにのみ使用され、それがベースディレクトリとして使用されます。ファイルは、クラスパス上にあるJAR内、またはクラスパス上にあるディレクトリ内にある場合があります。
次の例は、BaseDir.find()
を使用してクラスパスからベースディレクトリを検出する方法を示しています。
import ratpack.core.server.ServerConfig;
import ratpack.test.embed.EphemeralBaseDir;
import ratpack.test.embed.EmbeddedApp;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Path;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class Example {
public static void main(String... args) throws Exception {
EphemeralBaseDir.tmpDir().use(baseDir -> {
baseDir.write("mydir/.ratpack", "");
baseDir.write("mydir/assets/message.txt", "Hello Ratpack!");
Path mydir = baseDir.getRoot().resolve("mydir");
ClassLoader classLoader = new URLClassLoader(new URL[]{mydir.toUri().toURL()});
Thread.currentThread().setContextClassLoader(classLoader);
EmbeddedApp.of(serverSpec -> serverSpec
.serverConfig(c -> c.baseDir(mydir))
.handlers(chain ->
chain.files(f -> f.dir("assets"))
)
).test(httpClient -> {
String message = httpClient.getText("message.txt");
assertEquals("Hello Ratpack!", message);
});
});
}
}
上記の例でのEphemeralBaseDir
の使用と新しいコンテキストクラスローダーの構築は、単に例を自己完結型にするためです。実際のメインメソッドは、単にBaseDir.find()
を呼び出し、RatpackアプリケーションJVMを起動したものが適切なクラスパスで起動したことに依存します。
Ratpackは、Java 7のPath APIを介してベースディレクトリにアクセスし、JARの内容をファイルシステムとして透過的に使用できるようにします。
2.1.1.4 ポート
port(int)メソッドを使用すると、サーバーへの接続に使用されるポートを設定できます。構成されていない場合、デフォルト値は5050です。
3.1.1.4 SSL
デフォルトでは、Ratpackサーバーは構成ポートでHTTPトラフィックをリッスンします。HTTPSトラフィックを有効にするには、ssl(SslContext)メソッドを使用してSSL証明書とキーを使用できます。
v2.0以降、Ratpackは、Server Name Indicators(SNI)を使用して、リクエストされたホストに基づいてSSL構成を選択することもサポートしています。ssl(SslContext, Action)は、デフォルトのSSL構成と、代替SSL構成を使用した追加のドメインマッピングを指定するために使用されます。マッピングで指定されたドメインはDNSワイルドカードをサポートしており、ドメイン階層の最大1レベルの深さで一致します(例:*.ratpack.io
はapi.ratpack.io
に一致しますが、docs.api.ratpack.io
には一致しません)。
システムプロパティまたは環境変数を介してSSL設定を構成するには、ドメイン名を指定するための特別な処理が必要です。次の表は、デフォルトのSSL構成とサブドメインの構成を指定する方法を示しています。
| システムプロパティ | 環境変数 | 説明 | |————————————————|————————————————–|———————————————————————————| | ratpack.server.ssl.keystoreFile
| RATPACK_SERVER__SSL__KEYSTORE_FILE
| サーバー証明書と秘密キーを含むJKSへのパスを指定します | | ratpack.server.ssl.keystorePassword
| RATPACK_SERVER__SSL__KEYSTORE_PASSWORD
| キーストアJKSのパスワードを指定します | | ratpack.server.ssl.truststoreFile
| RATPACK_SERVER__SSL__TRUSTSTORE_FILE
| 信頼できる証明書を含むJKSへのパスを指定します | | ratpack.server.ssl.truststorePassword
| RATPACK_SERVER__SSL__TRUSTSTORE_PASSWORD
| トラストストアJKSのパスワードを指定します | | ratpack.server.ssl.ratpack_io.keystoreFile
| RATPACK_SERVER__SSL__RATPACK_IO__KEYSTORE_FILE
| ドメインratpack.io
のキーストアへのパスを指定します | | ratpack.server.ssl.*_ratpack_io.kyestoreFile
| RATPACK_SERVER__SSL___RATPACK_IO_KEYSTORE_FILE
| ドメイン*.ratpack.io
のキーストアへのパスを指定します |
次の特別な規則に注意してください。1.システムプロパティと環境変数の両方で、ドメイン名セパレーター(.
)はアンダースコア(_
)に変換されます。2.環境変数では、ドメインワイルドカード文字(*
)はアンダースコア(_
)を使用して指定されます。これにより、ドメイン名の前に3つのアンダースコア(___RATPACK_IO
)が付きます。
2.1.4 レジストリ
レジストリ
は、型によって格納されたオブジェクトのストアです。アプリケーション内に多数の異なるレジストリが存在する可能性がありますが、すべてのアプリケーションは「サーバーレジストリ」によってバックアップされます。サーバーレジストリは、アプリケーションをバックアップし、起動時に定義されるレジストリに与えられた名前です。
3.1.4 ハンドラー
サーバーハンドラーは、すべての受信HTTPリクエストを受信します。ハンドラーは構成可能であり、実際には1つのハンドラーだけで構成されているアプリケーションはほとんどありません。ほとんどのアプリケーションのサーバーハンドラーは、handlers(Action)
メソッドを使用して作成されたコンポジットハンドラーであり、Chain
DSLを使用してコンポジットハンドラーを作成します。
4.1.4 起動および停止アクション
Service
インターフェースを使用すると、アプリケーションライフサイクルにフックできます。リクエストを受け入れる前に、Ratpackはすべてのサービスに通知し、初期化を実行できるようにします。逆に、アプリケーションが停止すると、Ratpackはすべてのサービスに通知し、クリーンアップまたは終了を実行できるようにします。
5 ハンドラー
この章では、Ratpackアプリケーションの基本コンポーネントであるハンドラーについて説明します。
1.5 ハンドラーとは
概念的には、ハンドラー(Handler
)は、処理コンテキスト(Context
)に対して動作する単なる関数です。
「hello world」ハンドラーは次のようになります。
import ratpack.core.handling.Handler;
import ratpack.core.handling.Context;
public class Example implements Handler {
public void handle(Context context) {
context.getResponse().send("Hello world!");
}
}
前の章で見たように、必須の起動構成プロパティの1つは、プライマリハンドラーを提供するHandlerFactoryの実装です。このファクトリが作成するハンドラーは、事実上アプリケーションです。
これは制約があるように思えるかもしれませんが、ハンドラーがエンドポイントである必要はない(つまり、HTTPレスポンスを生成する以外のこともできる)ということを認識すれば、そうではありません。ハンドラーは、複数の方法で他のハンドラーに委譲することもでき、よりルーティングの役割を果たします。ルーティングステップとエンドポイントの間にフレームワークレベル(つまり型)の区別がないという事実は、非常に高い柔軟性を提供します。これは、カスタムリクエスト処理のあらゆる種類のパイプラインが、ハンドラーを構成することによって構築できることを意味します。この構成的なアプローチは、Ratpackの哲学である、魔法のようなフレームワークではなくツールキットであるという典型的な例です。
この章の残りの部分では、HTTPレベルの懸念事項(ヘッダーの読み取り、レスポンスの送信など)を超えたハンドラーの側面について説明します。これについては、HTTPの章で説明します。
2.5 ハンドラーの委譲
ハンドラーがレスポンスを生成しない場合、別のハンドラーに委譲する必要があります。1つ以上のハンドラーを挿入するか、単に次のハンドラーに委ねることができます。
リクエストパスに基づいて2つの異なるハンドラーのいずれかにルーティングするハンドラーを考えてみましょう。これは次のように実装できます…
import ratpack.core.handling.Handler;
import ratpack.core.handling.Context;
public class FooHandler implements Handler {
public void handle(Context context) {
context.getResponse().send("foo");
}
}
public class BarHandler implements Handler {
public void handle(Context context) {
context.getResponse().send("bar");
}
}
public class Router implements Handler {
private final Handler fooHandler = new FooHandler();
private final Handler barHandler = new BarHandler();
public void handle(Context context) {
String path = context.getRequest().getPath();
if (path.equals("foo")) {
context.insert(fooHandler);
} else if (path.equals("bar")) {
context.insert(barHandler);
} else {
context.next();
}
}
}
委譲の鍵は、1つ以上のリンクされたハンドラーに制御を渡すcontext.insert()
メソッドです。context.next()
メソッドは、次のリンクされたハンドラーに制御を渡します。
以下を検討してください…
import ratpack.core.handling.Handler;
import ratpack.core.handling.Context;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class PrintThenNextHandler implements Handler {
private final String message;
private final static Logger LOGGER = LoggerFactory.getLogger(PrintThenNextHandler.class);
public PrintThenNextHandler(String message) {
this.message = message;
}
public void handle(Context context) {
LOGGER.info(message);
context.next();
}
}
public class Application implements Handler {
public void handle(Context context) {
context.insert(
new PrintThenNextHandler("a"),
new PrintThenNextHandler("b"),
new PrintThenNextHandler("c")
);
}
}
Application
がプライマリハンドラー(つまり、起動構成のHandlerFactory
によって返されるハンドラー)であるとすると、このアプリケーションがリクエストを受信すると、次の内容がSystem.out
に書き込まれます…
a
b
c
では、次はどうなるのでしょうか?「c」ハンドラーが次のハンドラーに委譲するとどうなるのでしょうか?最後のハンドラーは常に、HTTP 404クライアントエラーを発行する内部ハンドラーです(これは、後で説明するcontext.clientError(404)
を介して行われます)。
挿入されたハンドラー自体がさらにハンドラーを挿入できることを考慮してください…
import ratpack.core.handling.Handler;
import ratpack.core.handling.Context;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class PrintThenInsertOrNextHandler implements Handler {
private final String message;
private final Handler[] handlers;
private final static Logger LOGGER = LoggerFactory.getLogger(PrintThenInsertOrNextHandler.class);
public PrintThenInsertOrNextHandler(String message, Handler... handlers) {
this.message = message;
this.handlers = handlers;
}
public void handle(Context context) {
LOGGER.info(message);
if (handlers.length == 0) {
context.next();
} else {
context.insert(handlers);
}
}
}
public class Application implements Handler {
public void handle(Context context) {
context.insert(
new PrintThenInsertOrNextHandler("a",
new PrintThenInsertOrNextHandler("a.1"),
new PrintThenInsertOrNextHandler("a.2"),
),
new PrintThenInsertOrNextHandler("b",
new PrintThenInsertOrNextHandler("b.1",
new PrintThenInsertOrNextHandler("b.1.1")
),
),
new PrintThenInsertOrNextHandler("c")
);
}
}
これにより、次の内容がSystem.out
に書き込まれます…
a
a.1
a.2
b
b.1
b.1.1
c
これは、ハンドラーを挿入するハンドラーの次のハンドラーが、挿入されたハンドラーの最後のハンドラーの次のハンドラーになることを示しています。この文章を複数回読む必要があるかもしれません。
ある種のネスト機能が現れていることがわかるはずです。これは、構成可能性にとって重要であり、この章の後半でレジストリコンテキストを検討する際に重要となるスコープ設定にとっても重要です。
この時点で、一般的なWebアプリケーション(つまり、特定のリクエストパスに一致するリクエストをエンドポイントにディスパッチするアプリケーション)のハンドラー構造を構築するのは大変な作業のように思えるかもしれません。読み進めてください。
3.5 ハンドラーチェーンの構築
チェーン(Chain
)は、ハンドラーを構成(またはチェーン化)するためのビルダーです。チェーン自体はリクエストに応答しませんが、代わりに、リクエストを添付されたハンドラーに渡します。
Foo-Barルーターの例をもう一度考えてみましょう…
import ratpack.core.handling.Chain
import ratpack.core.handling.Handler;
import ratpack.core.handling.Context;
import ratpack.func.Action;
public class FooHandler implements Handler {
public void handle(Context context) {
context.getResponse().send("foo");
}
}
public class BarHandler implements Handler {
public void handle(Context context) {
context.getResponse().send("bar");
}
}
public class RouterChain implements Action<Chain> {
private final Handler fooHandler = new FooHandler();
private final Handler barHandler = new BarHandler();
@Override
void execute(Chain chain) throws Exception {
chain.path("foo", fooHandler)
chain.path("bar", barHandler)
}
}
今回は、パスを手動で確認し、各コード分岐を処理する必要はありませんでした。ただし、結果は同じです。このチェーンは最終的にハンドラーとして扱われます。このハンドラーは、リクエストからパスを読み取り、最初に「foo」、次に「bar」と比較するように設定されます。どちらかが一致した場合、指定されたハンドラーをcontext.insert()
します。それ以外の場合は、context.next()
を呼び出します。
ハンドラーと同様に、コンテキストは魔法の塊ではなく、より柔軟なツールであるハンドラーから構築された強力なツールを目指しています。
1.3.5 ハンドラーとチェーンの追加
したがって、チェーンは最も簡単に、ハンドラーのリストとして考えることができます。チェーンのリストにハンドラーを追加する最も基本的な方法は、all(Handler)
メソッドです。「all」という単語は、チェーンのこの時点に到達するすべてのリクエストが、指定されたハンドラーを通過することを表します。
チェーンをハンドラー(単にハンドラーの挿入に特化したハンドラー)として少し考えを広げると、チェーンに別のチェーンを追加できることも理にかなっています。実際、追加できます。そして、all(Handler)
メソッドに合わせて、insert(Action<Chain>)
メソッドを使用できます。同様に、これにより、すべてのリクエストがルーティングされるチェーンが挿入されます。
さて、チェーンが単にハンドラーのリストを処理し、各ハンドラーを順番に呼び出すだけでは、あまり役に立たないため、ハンドラーとチェーンの条件付き挿入を実行できるメソッドもいくつかあります。
- 前の例で使用した
path(String,Handler)
は、リクエストパスに基づいて異なるハンドラーにルーティングする場合に特に役立ちます。また、空の""パスに簡単に一致させるためのpath(Handler)
フレーバーも用意されています。 onlyIf(Predicate<Context>, Handler)
を使用して、プログラムによる動作に基づいてルーティングできます。host(String, Action<Chain>)
は、リクエストに特定のHostヘッダー値がある場合に別のチェーンを挿入します。when(Predicate<Context>, Action<Chain>)
は、プログラムによる動作が満たされたときにチェーンを挿入します。
2.3.5 レジストリ
TODO(技術的な定義は、Chain
javadocsにあります)
3.3.5 パスバインディング
(例:/player/:id)
TODO(技術的な定義は、Chain
javadocsにあります)
4.3.5 パスとメソッドのバインディング
TODO(技術的な定義は、Chain
javadocsにあります)
6 コンテキスト
Context
型は、Ratpackの中核です。
次の機能を提供します。
- HTTP
Request
およびResponse
へのアクセス - 委譲とフロー制御(
next()
およびinsert()
メソッドを介して) - コンテキストオブジェクトへのアクセス
- 一般的なハンドラー操作の利便性
リクエスト/レスポンスを直接操作する方法については、HTTPの章を参照してください。
委譲の詳細については、ハンドラーの章を参照してください。
1.6 コンテキストオブジェクト
コンテキストはレジストリです。ハンドラーパイプラインの上流で利用可能になったオブジェクトの型検索を介してアクセスを提供します。これが、Ratpackでのハンドラー間のコラボレーションのメカニズムです。
次の例を検討してください。
import ratpack.test.embed.EmbeddedApp;
import ratpack.exec.registry.Registry;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class Example {
public static interface Person {
String getId();
String getStatus();
String getAge();
}
public static class PersonImpl implements Person {
private final String id;
private final String status;
private final String age;
public PersonImpl(String id, String status, String age) {
this.id = id;
this.status = status;
this.age = age;
}
@Override
public String getId() {
return id;
}
@Override
public String getStatus() {
return status;
}
@Override
public String getAge() {
return age;
}
}
public static void main(String... args) throws Exception {
EmbeddedApp
.fromHandlers(chain -> chain
.prefix("person/:id", (personChain) -> personChain
.all(ctx -> {
String id = ctx.getPathTokens().get("id"); // (1)
Person person = new PersonImpl(id, "example-status", "example-age");
ctx.next(Registry.single(Person.class, person)); // (2)
})
.get("status", ctx -> {
Person person = ctx.get(Person.class); // (3)
ctx.render("person " + person.getId() + " status: " + person.getStatus());
})
.get("age", ctx -> {
Person person = ctx.get(Person.class); // (4)
ctx.render("person " + person.getId() + " age: " + person.getAge());
}))
)
.test(httpClient -> {
assertEquals("person 10 status: example-status", httpClient.get("person/10/status").getBody().getText());
assertEquals("person 6 age: example-age", httpClient.get("person/6/age").getBody().getText());
});
}
}
(2)
では、ダウンストリームハンドラーが使用できるように、Person
インスタンスをレジストリにプッシュしています。また、(3)
と(4)
では、それらを取得する方法を示しています。作成の詳細を使い分けから切り離し、status
およびage
ハンドラーで作成コードを複製することを回避しています。重複を避けることの利点は明らかです。わずかに微妙なのは、ダウンストリームハンドラーが匿名クラスとして実装されていない場合、分離によってテストが容易になることです(詳細については、テストの章を参照してください)。
(1)
では、コンテキストオブジェクトも使用しています。prefix()
チェーンメソッドは、リクエストパスにバインドし、トークンをキャプチャする可能性があります。バインディングが成功した場合、バインディングの結果を記述するPathBinding
オブジェクトがコンテキストに登録されます。これには、バインディングの一部としてキャプチャされたパストークンが含まれます。上記のケースでは、2番目のパスコンポーネントをid
としてキャプチャしています。コンテキストのgetPathTokens()
メソッドは、文字通り同じコンテキスト上のget(PathBinding.class).getPathTokens()
の短縮形です。これは、ハンドラー間の通信にコンテキストオブジェクトメカニズムを使用する別の例です。
コンテキストオブジェクトを使用する別の例は、ファイルシステムからファイルにアクセスするための短縮形です。次のスクリプトを検討してください。このスクリプトでは、コンテキストのfile
メソッドを使用して、ファイルシステムから静的アセットを取得しています。
import static ratpack.groovy.Groovy.ratpack
ratpack {
handlers {
get {
def f = file('../')
render f ?: "null-value"
}
}
}
上記の例では、コンテキストのfile()
メソッドが呼び出され、指定されたパスのjava.io.File
インスタンスを取得しています。コンテキストのfile()
メソッドは、レジストリからFileSystemBinding
オブジェクトを取得するための短縮形であり、文字通りget(FileSystemBinding.class).file(path/to/file)
の短縮形です。コンテキストは、常にアプリケーションルートを基準にしてファイルアセットを解決するため、絶対パスが指定されている場合は、アセットへのパスにアプリケーションが存在するパスがプレフィックスとして付けられることに注意してください。たとえば、アプリケーションが/home/ratpack/app
に存在し、ハンドラーがfile
メソッドを使用して/etc/passwd
を解決する場合、解決される実際のパスは/home/ratpack/app/etc/passwd
になります。アプリケーションのルート内からファイルを解決できない場合、file()
メソッドはnull値を返す可能性があります。これは上記の例で示されています。開発者は、ファイルへのアクセスがnullオブジェクトを返す可能性のあるシナリオを処理する責任があります。
1.1.6 パーティショニング
コンテキストオブジェクトメカニズムは、異なるパーティションに異なるオブジェクトを提供することで、アプリケーションロジックのパーティショニングをサポートします。これは、コンテキストに登録されたオブジェクトが、登録方法に応じて暗黙的にスコープ設定されるためです。next()
メソッドで登録されたオブジェクトは、同じ挿入の一部であった(つまり、context.insert()
を含み、ネストされた挿入を含む)すべてのダウンストリームハンドラーで使用できます。insert()
メソッドで登録されたオブジェクトは、挿入されたハンドラーとネストされた挿入で使用できます。
この一般的な使用法は、アプリケーションのさまざまな部分に異なるエラー処理戦略を使用することです。
import ratpack.core.error.ServerErrorHandler;
import ratpack.test.embed.EmbeddedApp;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class Example {
public static void main(String... args) throws Exception {
EmbeddedApp.fromHandlers(chain -> chain
.prefix("api", api -> api
.register(r -> r.add(ServerErrorHandler.class, (context, throwable) ->
context.render("api error: " + throwable.getMessage())
)
)
.all(ctx -> {
throw new Exception("in api - " + ctx.getRequest().getPath());
})
)
.register(r -> r.add(ServerErrorHandler.class, (ctx, throwable) ->
ctx.render("app error: " + throwable.getMessage())
)
)
.all(ctx -> {
throw new Exception("in app - " + ctx.getRequest().getPath());
})
).test(httpClient -> {
assertEquals("api error: in api - api/foo", httpClient.get("api/foo").getBody().getText());
assertEquals("app error: in app - bar", httpClient.get("bar").getBody().getText());
});
}
}
7 基本的なHTTP
この章では、リクエストの解析、レスポンスのレンダリング、コンテンツネゴシエーション、ファイルアップロードなどの基本的なHTTPの懸念事項を処理する方法を紹介します。
1.7 リクエストとレスポンス
ハンドラーが動作するコンテキストオブジェクトは、getRequest()
とgetResponse()
メソッドを提供し、それぞれRequest
とResponse
にアクセスします。これらのオブジェクトは、ほぼ予想どおりの機能を提供します。
例えば、どちらもリクエストとともに送信された HTTP ヘッダーのモデルと、レスポンスとともに送信される HTTP ヘッダーのモデルを返す getHeaders()
メソッドを提供します。Request
は、HTTP メソッド、URI、および クエリ文字列パラメーターのキー/値モデルなど、その他のメタデータ属性も公開します。
2.7 リダイレクト
redirect(int, Object)
コンテキストメソッドは、リダイレクトの発行をサポートします。このメソッドは、コンテキストレジストリから Redirector
を取得し、引数を転送します。
Ratpack は、次のものをサポートするデフォルトの実装を提供します。
- リテラル URL 値
- プロトコル相対 URL 値
- 現在のアプリケーション内の絶対パス
- 現在のアプリケーション内の相対パス
ほとんどのアプリケーションでは、デフォルトの動作で十分であるため、カスタム Redirector
実装を提供する必要はありません。カスタムリダイレクタの実装を提供する理由の 1 つは、ドメインオブジェクトをリダイレクト先の場所として解釈することです。
3.7 リクエストの読み取り
リクエストのボディを取得するために、いくつかのメカニズムが利用可能です。簡単なユースケースでは、Context.parse(Class<T>)
がクラス全体をメモリにバッファリングし、指定された型のオブジェクトを生成します。リクエスト全体のテキストまたはバイトビューが必要な場合は、下位レベルの Request.getBody()
メソッドを使用できます。高度な使用法や非常に大きなリクエストを処理する場合は、[Request.getBodyStream()
] を使用すると、受信した個々のバイトチャンクにアクセスできます。
1.3.7 パーサー
パーサーメカニズムは、リクエストボディをオブジェクト表現に変換します。これは、コンテキストレジストリから Parser
実装を選択することで機能します。詳細および追加のバリアントについては、Context.parse(Class<T>)
を参照してください。
1.1.3.7 JSON
JSON リクエストボディの処理のサポートは、Jackson に基づいてすぐに利用できます。例については、Jackson parsing
を参照してください。
2.1.3.7 フォーム
Ratpack は、コアで Form
オブジェクト用のパーサーを提供します。これは、URL エンコードされたフォームとマルチパート (ファイルアップロードを含む) の両方で、POST (または PUT など) されたフォームを読み取るために使用できます。
import ratpack.core.handling.Handler;
import ratpack.core.handling.Context;
import ratpack.core.form.Form;
import ratpack.core.form.UploadedFile;
public class MyHandler implements Handler {
public void handle(Context context) {
Promise<Form> form = context.parse(Form.class);
form.then(f -> {
// Get the first attribute sent with name “foo”
String foo = form.get("foo");
// Get all attributes sent with name “bar”
List<String> bar = form.getAll("bar");
// Get the file uploaded with name “myFile”
UploadedFile myFile = form.file("myFile");
// Send back a response …
});
}
}
詳細と例については、Form
および UploadedFile
を参照してください。
2.3.7 バイトとテキスト
Request.getBody()
は、リクエスト全体をメモリに読み込み、データにバイトまたは文字列としてアクセスできるようにします。
このメソッドは、デフォルトでは、サーバーで構成された 最大コンテンツ長を超えるリクエストを拒否します。拒否アクションと 最大サイズを構成するための追加のフレーバーが利用可能です。
import ratpack.core.http.client.ReceivedResponse;
import ratpack.test.embed.EmbeddedApp;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class Example {
public static void main(String... args) throws Exception {
EmbeddedApp
.fromHandler(ctx -> {
ctx.getRequest().getBody().then(data -> ctx.render("hello: "+data.getText()));
})
.test(httpClient -> {
ReceivedResponse response = httpClient.request(req->{
req.method("POST");
req.getBody().text("world");
});
assertEquals("hello: world", response.getBody().getText());
});
}
}
3.3.7 バイトチャンクストリーム
Request.getBodyStream()
は、受信した個々のチャンクのストリームを返します。
このメソッドは、デフォルトでは、サーバーで構成された 最大コンテンツ長を超えるリクエストを拒否します。最大サイズを構成するための追加のフレーバーが利用可能です。
リクエストボディをファイルにストリーミングする方法の例については、Java ドキュメントを参照してください。
4.7 レスポンスの送信
Ratpack での HTTP レスポンスの送信は、簡単で効率的かつ柔軟です。Ratpack のほとんどのことと同様に、クライアントへのレスポンスの送信はノンブロッキング方式で行われます。Ratpack は、レスポンスを送信するためのいくつかのメカニズムを提供します。レスポンスを操作するために公開されているメソッドは、Response
および Context
オブジェクトにあります。
1.4.7 レスポンスステータスの設定
レスポンスのステータスを設定するのは、Response#status(int)
または Response#status(ratpack.core.http.Status)
を呼び出すのと同じくらい簡単です。
import ratpack.core.http.Response;
import ratpack.core.http.Status;
import ratpack.test.embed.EmbeddedApp;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
public class Example {
public static void main(String... args) throws Exception {
EmbeddedApp.fromHandlers(chain -> chain
.all(ctx -> ctx.getResponse().status(202).send("foo"))
)
.test(httpClient ->
assertEquals(202, httpClient.get().getStatusCode())
);
}
}
2.4.7 レスポンスの送信
レスポンスボディをクライアントに送信する方法はいくつかあります。
レスポンスを送信する最も簡単な方法は、Response#send()
を呼び出すだけです。これにより、レスポンスボディなしでレスポンスが送信されます。
import ratpack.core.http.client.ReceivedResponse;
import ratpack.test.embed.EmbeddedApp;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class Example {
public static void main(String... args) throws Exception {
EmbeddedApp
.fromHandler(ctx -> ctx.getResponse().send())
.test(httpClient -> {
ReceivedResponse response = httpClient.get();
assertEquals("", response.getBody().getText());
});
}
}
プレーンテキストのレスポンスを送信する場合は、Response#send(String)
を使用できます。
import ratpack.core.http.client.ReceivedResponse;
import ratpack.test.embed.EmbeddedApp;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class Example {
public static void main(String... args) throws Exception {
EmbeddedApp
.fromHandler(ctx -> ctx.getResponse().send("Ratpack is rad"))
.test(httpClient -> {
ReceivedResponse response = httpClient.get();
assertTrue(response.getHeaders().get("Content-type").startsWith("text/plain;"));
assertEquals("Ratpack is rad", response.getBody().getText());
});
}
}
レスポンスボディペイロード (つまり、String
、byte[]
、ByteBuf
) を送信したり、Content-type
ヘッダーを設定したりできる追加の send()
メソッドがあります。レスポンスの送信の詳細については、Response
を参照してください。
3.4.7 レンダラーによる代替アプローチ
空のレスポンスや単純なテキストレスポンスの送信は問題ないかもしれませんが、より複雑なレスポンスをクライアントに送信したい場合があります。Renderer
は、特定の型をクライアントにレンダリングできるメカニズムです。より具体的には、コンテキストオブジェクトにある render(Object)
メソッドを駆動する基盤となるメカニズムです。
次の例では、コンテキストの render(Object)
メソッドを使用して、型 String
のオブジェクトをレンダリングします。
import ratpack.core.http.client.ReceivedResponse;
import ratpack.test.embed.EmbeddedApp;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class Example {
public static void main(String... args) throws Exception {
EmbeddedApp
.fromHandler(ctx -> ctx.render("Sent using render(Object)!"))
.test(httpClient -> {
ReceivedResponse response = httpClient.get();
assertEquals("Sent using render(Object)!", response.getBody().getText());
});
}
}
String
は型 CharSequence
であるため、Ratpack は String
をレンダリングするために CharSequenceRenderer
を見つけて使用します。この CharSequenceRenderer
はどこから来たのですか? Ratpack は、CharSequenceRenderer
、RenderableRenderer
、PromiseRenderer
、DefaultFileRenderer
を含みますが、これらに限定されない多数の Renderer
を提供します。
登録されていない型をレンダリングしようとすると、サーバーエラーが発生します。
import ratpack.core.http.client.ReceivedResponse;
import ratpack.test.embed.EmbeddedApp;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class Example {
static class Foo {
public String value;
}
public static void main(String... args) throws Exception {
EmbeddedApp
.fromHandler(ctx -> {
Foo foo = new Foo();
foo.value = "bar";
ctx.render(foo);
})
.test(httpClient -> {
ReceivedResponse response = httpClient.get();
assertEquals(500, response.getStatusCode());
});
}
}
独自の Renderer
を実装したい場合は、Ratpack が提供する RendererSupport
を使用すると、独自のものを簡単に実装できます。また、Ratpack が使用できるように、Renderer
を登録する必要があることに注意してください。
import ratpack.core.handling.Context;
import ratpack.exec.registry.Registry;
import ratpack.core.http.client.ReceivedResponse;
import ratpack.core.render.RendererSupport;
import ratpack.test.embed.EmbeddedApp;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class Example {
static class Foo {
public String value;
}
static class FooRenderer extends RendererSupport<Foo> {
@Override
public void render(Context ctx, Foo foo) throws Exception {
ctx.getResponse().send("Custom type: Foo, value=" + foo.value);
}
}
public static void main(String... args) throws Exception {
EmbeddedApp
.fromHandlers(chain -> chain
.register(Registry.single(new FooRenderer()))
.all(ctx -> {
Foo foo = new Foo();
foo.value = "bar";
ctx.render(foo);
})
)
.test(httpClient -> {
ReceivedResponse response = httpClient.get();
assertEquals(200, response.getStatusCode());
assertEquals("Custom type: Foo, value=bar", response.getBody().getText());
});
}
}
4.4.7 JSON の送信
任意のオブジェクトを JSON としてレンダリングするためのサポートは、Jackson に基づいています。例については、Jackson rendering
を参照してください。
5.4.7 ファイルの送信
ファイルなどの静的リソースの送信は、sendFile(Path)
で行うことができます。
TODO sendFile メソッドを紹介する (代わりに render(file(«path»))
の使用を指示する)。
TODO assets メソッドを紹介する
6.4.7 送信前
Response
オブジェクトには、レスポンスがクライアントに送信される直前に呼び出されるメソッド beforeSend(Action<? super Response> responseFinalizer)
が含まれています。
responseFinalizer
コールバック内で Response
の .send()
メソッドを呼び出すことはできません。
このメソッドは、次を修正する場合に特に役立ちます。
- ヘッダー
- クッキー
- ステータス
- content-type
最後の瞬間に。
これの実際的なユースケースは、StreamedResponse.forwardTo()
を使用するときに、ステータスコードまたはヘッダーを修正することです。
たとえば、
import io.netty.handler.codec.http.HttpHeaderNames;
import ratpack.core.http.client.ReceivedResponse;
import ratpack.test.embed.EmbeddedApp;
import ratpack.core.http.Headers;
import ratpack.core.http.Request;
import ratpack.core.http.Status;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class Example {
public static void main(String... args) throws Exception {
EmbeddedApp
.fromHandler(ctx -> {
ctx.getResponse()
.contentType("application/json")
.status(Status.OK)
.beforeSend(response -> {
response.getHeaders().remove(HttpHeaderNames.CONTENT_LENGTH);
response.cookie("DNT", "1");
response.status(Status.of(451, "Unavailable for Legal Reasons"));
response.contentType("text/plain");
}).send();
})
.test(httpClient -> {
ReceivedResponse receivedResponse = httpClient.get();
Headers headers = receivedResponse.getHeaders();
assertEquals(451, receivedResponse.getStatusCode());
assertEquals("text/plain", headers.get(HttpHeaderNames.CONTENT_TYPE));
assertTrue(headers.get(HttpHeaderNames.SET_COOKIE).contains("DNT"));
});
}
}
5.7 ヘッダー
HTTP ヘッダー情報は、受信リクエストから、送信レスポンスの場合と同様に利用できます。
1.5.7 リクエストヘッダー
Headers
インターフェイスを使用すると、受信リクエストに関連付けられたヘッダー情報を取得できます。
import ratpack.core.http.client.ReceivedResponse;
import ratpack.test.embed.EmbeddedApp;
import ratpack.core.http.Headers;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class Example {
public static void main(String... args) throws Exception {
EmbeddedApp
.fromHandler(ctx -> {
Headers headers = ctx.getRequest().getHeaders();
String clientHeader = headers.get("Client-Header");
ctx.getResponse().send(clientHeader);
})
.test(httpClient -> {
ReceivedResponse receivedResponse = httpClient
.requestSpec(requestSpec ->
requestSpec.getHeaders().set("Client-Header", "From Client")
).get();
assertEquals("From Client", receivedResponse.getBody().getText());
});
}
}
2.5.7 レスポンスヘッダー
MutableHeaders
は、レスポンスオブジェクトの Response#getHeaders()
を介してレスポンスヘッダーを操作できるようにする機能を提供します。
import ratpack.core.http.MutableHeaders;
import ratpack.core.http.client.ReceivedResponse;
import ratpack.test.embed.EmbeddedApp;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class Example {
public static void main(String... args) throws Exception {
EmbeddedApp
.fromHandler(ctx -> {
MutableHeaders headers = ctx.getResponse().getHeaders();
headers.add("Custom-Header", "custom-header-value");
ctx.getResponse().send("ok");
})
.test(httpClient -> {
ReceivedResponse receivedResponse = httpClient.get();
assertEquals("custom-header-value", receivedResponse.getHeaders().get("Custom-Header"));
});
}
}
さらに、set(CharSequence, Object)
、remove(CharSequence)
、clear()
などが可能です。
その他のメソッドについては、MutableHeaders
を参照してください。
クッキー
HTTP ヘッダーと同様に、クッキーは、インバウンドリクエストからの検査に利用できるほか、アウトバウンドレスポンスの操作にも利用できます。
インバウンドリクエストからのクッキー
クッキーの値を取得するには、Request#oneCookie(String)
を使用できます。
import ratpack.core.http.client.ReceivedResponse;
import ratpack.test.embed.EmbeddedApp;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class Example {
public static void main(String... args) throws Exception {
EmbeddedApp.fromHandler(ctx -> {
String username = ctx.getRequest().oneCookie("username");
ctx.getResponse().send("Welcome to Ratpack, " + username + "!");
}).test(httpClient -> {
ReceivedResponse response = httpClient
.requestSpec(requestSpec -> requestSpec
.getHeaders()
.set("Cookie", "username=hbogart1"))
.get();
assertEquals("Welcome to Ratpack, hbogart1!", response.getBody().getText());
});
}
}
また、Request#getCookies()
を介してクッキーのセットを取得することもできます。
import io.netty.handler.codec.http.cookie.Cookie;
import ratpack.core.http.client.ReceivedResponse;
import ratpack.test.embed.EmbeddedApp;
import java.util.Set;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class Example {
public static void main(String... args) throws Exception {
EmbeddedApp.fromHandler(ctx -> {
Set<Cookie> cookies = ctx.getRequest().getCookies();
assertEquals(1, cookies.size());
Cookie cookie = cookies.iterator().next();
assertEquals("username", cookie.name());
assertEquals("hbogart1", cookie.value());
ctx.getResponse().send("Welcome to Ratpack, " + cookie.value() + "!");
}).test(httpClient -> {
ReceivedResponse response = httpClient
.requestSpec(requestSpec -> requestSpec
.getHeaders()
.set("Cookie", "username=hbogart1"))
.get();
assertEquals("Welcome to Ratpack, hbogart1!", response.getBody().getText());
});
}
}
アウトバウンドレスポンスのクッキーの設定
レスポンスとともに送信されるクッキーを設定するには、Response#cookie(String, String)
を使用します。レスポンスとともに設定されるクッキーのセットを取得するには、Response#getCookies()
を使用できます。
import ratpack.core.http.client.ReceivedResponse;
import ratpack.test.embed.EmbeddedApp;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class Example {
public static void main(String... args) throws Exception {
EmbeddedApp.fromHandler(ctx -> {
assertTrue(ctx.getResponse().getCookies().isEmpty());
ctx.getResponse().cookie("whiskey", "make-it-rye");
assertEquals(1, ctx.getResponse().getCookies().size());
ctx.getResponse().send("ok");
}).test(httpClient -> {
ReceivedResponse response = httpClient.get();
assertEquals("whiskey=make-it-rye", response.getHeaders().get("Set-Cookie"));
});
}
}
クッキーを期限切れにしたい場合は、Response#expireCookie()
を使用して行うことができます。
import ratpack.core.http.client.ReceivedResponse;
import ratpack.test.embed.EmbeddedApp;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class Example {
public static void main(String... args) throws Exception {
EmbeddedApp.fromHandler(ctx -> {
ctx.getResponse().expireCookie("username");
ctx.getResponse().send("ok");
}).test(httpClient -> {
ReceivedResponse response = httpClient
.requestSpec(requestSpec -> requestSpec
.getHeaders().set("Cookie", "username=lbacall1")
)
.get();
String setCookie = response.getHeaders().get("Set-Cookie");
assertTrue(setCookie.startsWith("username=; Max-Age=0"));
});
}
}
7.7 コンテンツネゴシエーション
リソースのさまざまな表現 (JSON/XML/HTML、GIF/PNG など) のレンダリングのサポートは、byContent(Action)
を介して提供されます。
8.7 セッション
同じクライアントによる複数の呼び出し間で一貫性を維持する必要がある場合 (つまり、ステートフル操作)、セッションを使用できます。Ratpack は、セッション処理を処理するモジュールを提供しています。セッションモジュールをプロジェクトに追加して、Ratpack が管理するセッションの使用を開始できます。
1.8.7 準備
まず、必要な依存関係をプロジェクトに追加する必要があります。gradle を使用している場合は、compile 'io.ratpack:ratpack-session:2.0.0-rc-1'
を依存関係に追加することで依存関係を追加できます。始めたばかりの gradle ファイルは次のようになります。
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath "io.ratpack:ratpack-gradle:2.0.0-rc-1"
}
}
apply plugin: "io.ratpack.ratpack-groovy"
repositories {
mavenCentral()
}
dependencies {
runtimeOnly 'org.slf4j:slf4j-simple:1.7.36'
implementation group: 'io.ratpack', name: 'ratpack-session', version: '2.0.0-rc-1'
testImplementation "org.spockframework:spock-core:2.1-groovy-3.0"
}
Ratpack でモジュールをロードすることを忘れないでください。
import static ratpack.groovy.Groovy.ratpack
import ratpack.session.SessionModule
ratpack {
bindings {
module(SessionModule)
}
/* ... */
}
2.8.7 セッションの使用
これでセッションの準備ができました。以下は、セッションを使用するアプリケーションの簡単な例です。
import ratpack.session.Session
import static ratpack.groovy.Groovy.ratpack
import ratpack.session.SessionModule
ratpack {
bindings {
module(SessionModule)
}
handlers {
get('start') { Session session ->
session.terminate().flatMap {
session.set('keyForDataStoredInSession', 'Hello Session!').promise()
}.then {
response.send('I started a session for you.')
}
}
get { Session session ->
session.get('keyForDataStoredInSession').then {
if (it.present) {
response.send("Message: ${it.get()}")
} else {
response.send('I have nothing to say to you')
}
}
}
get('stop') { Session session ->
session.terminate().then {
response.send('The session is dead, dead, dead.')
}
}
}
}
すべてのセッション操作は、Promise または Operation のいずれかを返します。示されているように、変換フローでそれらを使用できます。
新しいセッションを開始する前に、古いセッションを終了してください (get('start')
-ハンドラーを参照)。このようにして、実際に新しいセッションを取得し、既存のセッションにデータを追加しないようにします。
3.8.7 制限事項
Ratpackのセッションモジュールは、デフォルトではインメモリセッションを使用します。一度に最大1000個のセッションを保持でき、新しいセッションが開かれると最も古いセッションが破棄されます。1000個を超えるセッションが予想される場合は、デフォルトのモジュールとは異なるセッションストアの使用を検討する必要があります。たとえば、Redisサーバーが手元にある場合は、ratpack-session-redis
モジュールを使用できます。ペイロードが小さい場合は、クライアントサイドセッションモジュールを使用して、セッションデータの一部をCookie自体に保存することもできます。Webサイト(ドメイン)のすべてのCookieを合わせたサイズは4Kを超えることはできないため、ペイロードは小さくする必要があります。
4.8.7 ratpack-session-redis
モジュール
Redisセッションモジュールを使用するには、依存関係(compile 'io.ratpack:ratpack-session-redis:2.0.0-rc-1'
)をプロジェクトに追加します。
次に、セッションモジュールをロードした後、Redisを構成します。
bindings {
module(SessionModule)
RedisSessionModule redisSessionModule = new RedisSessionModule()
redisSessionModule.configure {
it.host = 'localhost'
it.port = 6379
it.password = 'secret'
}
module(redisSessionModule)
}
Guiceまたは他のメカニズムを使用して、Redisの構成を注入することができます。それ以外は、セッションを保存するためにRedisを使用するようにRatpackを正常に構成しました。Redisサーバーが実行中で利用可能であることを確認し、Ratpackアプリケーションを起動します。
5.8.7 コメントと役立つリンク
これで、Ratpackでセッションを実装する方法の大まかな概要がわかりました。
Ratpackは、UUIDを含むJSESSIONID Cookieを使用して、セッションの一貫性を処理します。最初にセッションにデータを追加すると、IDが生成され、レスポンスのAdd-Cookie
ヘッダーを使用してCookieがクライアントに送信されます。そのため、クライアントに応答が送信されることを確認する必要があります。そうしないと、セッションが失われます。後続のクライアントリクエストには、Cookie
ヘッダーにCookieが含まれているため、Ratpackはクライアントのセッションを特定できます。
Ratpackのセッション処理について深く掘り下げるための役立つリンクをいくつか紹介します。- セッションがどのように機能するかのより多くのサンプルについては、ratpack-sessionモジュールのテストを参照してください。- ratpack javadocには、多くの例と情報が含まれています。- セッションモジュールにカスタムSessionCookieConfigを提供することで、Cookieの動作(Cookieの名前の変更、カスタムID/有効期限の使用など)をカスタマイズできます。
6.8.7 デフォルトのセッションストア(インメモリ)を使用する場合の注意点:
デフォルトのセッションストアは、おそらく本番環境では役に立ちませんが、セッションを使用したローカルテストには役立ちます。session.terminate()
を呼び出すと、セッションCookieが空の値に設定されます。したがって、後続の呼び出しには、JSESSIONID=
のようなCookieが含まれます。少なくとも、インメモリセッションストアは空の値を有効なセッションとして受け入れます。したがって、セッションを終了する前に値を追加して新しいセッションを作成しようとする場合、空のUUIDを持つ既存のセッションにセッションデータが追加されます。
8 非同期とノンブロッキング
Ratpackは、「非同期」および「ノンブロッキング」のリクエスト処理用に設計されています。内部IO(たとえば、HTTPリクエストとレスポンスの転送)はすべて、ノンブロッキング方式で実行されます(Nettyのおかげです)。このアプローチは、スループットの向上、リソース使用量の削減、そして重要なことに、負荷がかかった場合でもより予測可能な動作をもたらします。このプログラミングモデルは、Node.jsプラットフォームのために最近ますます人気が高まっています。Ratpackは、Node.jsと同じノンブロッキングでイベント駆動のモデルに基づいて構築されています。
非同期プログラミングは、非常にトリッキーです。Ratpackの主要な価値提案の1つは、非同期の獣を飼いならすための構造と抽象化を提供し、実装をシンプルに保ちながら、より良いパフォーマンスを生み出すことです。
1.8 ブロッキングフレームワークとコンテナとの比較
ほとんどのJVM Webフレームワークとコンテナの基礎となっているJava Servlet APIは、JDKの大部分とともに、基本的に同期プログラミングモデルに基づいています。ほとんどのJVMプログラマーは、このプログラミングモデルに非常に慣れており、快適に感じています。このモデルでは、IOを実行する必要がある場合、呼び出しスレッドは操作が完了して結果が利用可能になるまで単にスリープします。このモデルでは、かなり大きなスレッドプールが必要です。Webアプリケーションのコンテキストでは、これは通常、各リクエストが大きなプールからのスレッドにバインドされており、アプリケーションが「X」個の並列リクエストを処理できることを意味します。ここで、「X」はスレッドプールのサイズです。
Servlet APIのバージョン3.0では、非同期リクエスト処理が容易になりました。ただし、オプションの非同期サポートを後付けすることは、完全に非同期のアプローチとは異なります。Ratpackは、最初から非同期です。
このモデルの利点は、同期プログラミングが間違いなく「よりシンプル」であることです。このモデルの欠点は、ノンブロッキングモデルとは対照的に、より多くのリソース使用量を必要とし、スループットが低下することです。より多くのリクエストを並行して処理するためには、スレッドプールのサイズを大きくする必要があります。これにより、コンピューティングリソースの競合が増え、スレッドのスケジューリングの管理にさらに多くのサイクルが失われ、メモリ消費量が増加します。最新のオペレーティングシステムとJVMは、この競合の管理に非常に優れています。ただし、それでもスケーリングのボトルネックです。さらに、より多くのリソース割り当てが必要になり、これは、最新の従量制課金デプロイメント環境では深刻な考慮事項です。
非同期、ノンブロッキングモデルでは、大きなスレッドプールは必要ありません。これは、スレッドがIOを待機してブロックされることがないために可能です。IOを実行する必要がある場合、呼び出しスレッドは、IOが完了したときに呼び出される何らかの種類のコールバックを登録します。これにより、スレッドはIOの実行中に他の処理に使用できます。このモデルでは、スレッドプールは使用可能な処理コアの数に応じてサイズが設定されます。スレッドは常に計算でビジー状態であるため、それ以上のスレッドを用意する意味はありません。
多くのJava API(
InputStream
、JDBC
など)は、ブロッキングIOモデルに基づいています。Ratpackは、ブロッキングコストを最小限に抑えながら、そのようなAPIを使用するためのメカニズムを提供します(以下で説明します)。
Ratpackは、基本的に2つの重要な点で非同期です。
- HTTP IOは、イベント駆動/ノンブロッキングです(Nettyのおかげです)。
- リクエスト処理は、非同期関数のパイプラインとして編成されます。
HTTP IOがイベント駆動であることは、Ratpackを使用する際にはほとんど透過的です。Nettyはただその動作をします。
2番目のポイントは、Ratpackの主要な特性です。コードが同期であることを期待していません。オプションの非同期サポートを備えた多くのWebフレームワークには、複雑な(つまり、現実世界の)非同期操作を実行しようとすると明らかになる重大な制約と落とし穴があります。Ratpackは最初から非同期です。さらに、複雑な非同期処理を容易にする構造と抽象化を提供します。
2.8 ブロッキング操作の実行(例:IO)
ほとんどのアプリケーションは、何らかの種類のブロッキングIOを実行する必要があります。多くのJava APIは、非同期オプションを提供していません(例:JDBC)。Ratpackは、別のスレッドプールでブロッキング操作を実行するための簡単なメカニズムを提供します。これにより、(良いことですが)リクエスト処理(つまり、コンピューティング)スレッドのブロッキングを回避できますが、スレッドの競合によるオーバーヘッドが発生します。ブロッキングIO APIを使用する必要がある場合は、残念ながら他のオプションはありません。
架空のデータストアAPIを考えてみましょう。実際のデータストアとの通信にはIOが必要になる可能性があります(またはメモリ内にある場合は、そのアクセスには、同じブロッキング効果を持つ1つ以上のロックでの待機が必要です)。APIメソッドは、ブロックするため、リクエスト処理スレッドでは呼び出すことができません。代わりに、「ブロッキング」APIを使用する必要があります。
import ratpack.core.handling.InjectionHandler;
import ratpack.core.handling.Context;
import ratpack.exec.Blocking;
import ratpack.test.handling.RequestFixture;
import ratpack.test.handling.HandlingResult;
import java.util.Collections;
import java.io.IOException;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class Example {
// Some API that performs blocking operations
public static interface Datastore {
int deleteOlderThan(int days) throws IOException;
}
// A handler that uses the API
public static class DeletingHandler extends InjectionHandler {
void handle(final Context context, final Datastore datastore) {
final int days = context.getPathTokens().asInt("days");
Blocking.get(() -> datastore.deleteOlderThan(days))
.then(i -> context.render(i + " records deleted"));
}
}
// Unit test
public static void main(String... args) throws Exception {
HandlingResult result = RequestFixture.handle(new DeletingHandler(), fixture -> fixture
.pathBinding(Collections.singletonMap("days", "10"))
.registry(r -> r.add(Datastore.class, days -> days))
);
assertEquals("10 records deleted", result.rendered(String.class));
}
}
ブロッキング操作として送信された関数は、別のスレッドプールで非同期的に実行されます(つまり、Blocking.get()
メソッドはすぐにpromiseを返します)。それが返す結果は、リクエスト処理(つまり、コンピューティング)スレッドで再度処理されます。
詳細については、Blocking#get()メソッドを参照してください。
3.8 非同期操作の実行
Promise#async(Upstream
import ratpack.test.embed.EmbeddedApp;
import ratpack.exec.Promise;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class Example {
public static void main(String... args) throws Exception {
EmbeddedApp.fromHandler(ctx ->
Promise.async((f) ->
new Thread(() -> f.success("hello world")).start()
).then(ctx::render)
).test(httpClient -> {
assertEquals("hello world", httpClient.getText());
});
}
}
4.8 非同期構成とコールバック地獄の回避
非同期プログラミングの課題の1つは、構成にあります。重要でない非同期プログラミングは、すぐに「コールバック地獄」として知られる現象に陥る可能性があります。これは、多くのネストされたコールバックの理解不能さを説明するために使用される用語です。
非同期操作をエレガントかつクリーンに組み合わせて複雑なワークフローにすることは、現時点で急速に革新が進んでいる分野です。Ratpackは、非同期構成のためのフレームワークを提供しようとはしません。代わりに、このタスク専用のツールを統合およびアダプターを提供することを目指しています。このアプローチの例としては、RatpackのRxJavaとの統合があります。
一般に、統合とは、RatpackのPromise
型をターゲットフレームワークの構成プリミティブに適応させることです。
9 ストリーム
Ratpackは、さまざまな方法でデータのストリーミングをサポートしています。この章では、Ratpackでデータストリームを操作するための基本と、データをストリーミングするさまざまな方法について概説します。
1.9 Reactive Streams API
一般的に、Ratpackでのストリーミングは、新興のReactive Streams API標準に基づいています。
Reactive Streamsサイトから
Reactive Streamsは、JVMでのノンブロッキングバックプレッシャーによる非同期ストリーム処理の標準を提供するためのイニシアチブです。
Ratpackは、独自のAPIではなく、Reactive Streams APIを使用して、ユーザーが選択したリアクティブツールキットを選択できるようにします。RxJavaやReactorなどのリアクティブツールキットは、近い将来にReactive Streams APIへのブリッジをサポートする予定です。ただし、ニーズが控えめな場合は、専門的なリアクティブライブラリを使用する必要はありません。Ratpackは、Streams
クラスを介してストリームを処理するための便利なユーティリティをいくつか提供しています。
1.1.9 バックプレッシャー
Reactive Streams API の重要な原則は、バックプレッシャーによるフロー制御のサポートです。これにより、ストリームのサブスクライバー(HTTPサーバーアプリの場合、通常はHTTPクライアント)は、処理できるデータ量をパブリッシャーに伝えることができます。極端な場合、バックプレッシャーがないと、消費速度の遅いクライアントは、データプロデューサーが消費速度よりも速くデータを生成することで、サーバーのリソースを使い果たし、メモリバッファーが満杯になる可能性があります。バックプレッシャーにより、データプロデューサーは、クライアントが処理できる速度に合わせてデータ生成速度を調整できます。
バックプレッシャーの重要性に関する詳細は、Reactive Streamsプロジェクトのドキュメントを参照してください。
レスポンスのストリーミングは、常にResponse.sendStream()
メソッドを介して行われます。データのストリーミング時のバックプレッシャーの意味の正確なセマンティクスについては、このメソッドのドキュメントを参照してください。
2.9 チャンク転送エンコーディング
Ratpackは、ResponseChunks
レンダリング可能な型を使用して、任意のデータストリームに対するチャンク転送エンコーディングをサポートしています。
3.9 Server-sent events
Ratpackは、ServerSentEvents
レンダリング可能な型を使用して、主にJavaScriptベースのクライアントにデータをストリーミングするためのServer-sent eventsをサポートしています。
4.9 Websockets
Ratpackは、WebSockets.websocketBroadcast()
メソッドを使用して、Websocket経由でのデータストリーミングをサポートしています。
Ratpackはまた、WebSockets
クラスの他のwebsocketオープンメソッドを介して双方向websocket通信をサポートしています。
10 Ratpackアプリケーションのテスト
テストはRatpackにおける第一級市民です。ratpack-test
ライブラリにはコアサポートが含まれており、ratpack-groovy-test
はこれらの型にいくつかのGroovyシュガーを提供します。
ratpack
およびratpack-groovy
Gradleプラグインは、これらのライブラリをテストコンパイルクラスパスに暗黙的に追加します。
Ratpackテストサポートは、使用中のテストフレームワークに依存しません。任意のフレームワークを使用できます。
多くのRatpackユーザーは、Spockテストフレームワークを使用しています。SpockではGroovyでテストを記述する必要がありますが、Javaコードを効果的にテストするために簡単に使用できます。
1.10 ユニットテスト
1.1.10 RequestFixture
RequestFixture
クラスは、Handler
実装をテストするためのモックされたリクエスト環境の作成を容易にします。ただし、Parser
実装など、他のコンポーネントと統合されたアドホックハンドラーをリクエストフィクスチャで使用することも一般的です。
注:
GroovyRequestFixture
クラスは、リクエストフィクスチャを操作するためのGroovyシュガーを提供します。
2.1.10 ExecHarness
ExecHarness
フィクスチャは、アプリケーション外でRatpackの実行メカニズムを利用するコードのテストを容易にします。Promise
を使用するコードをユニットテストする必要がある場合は、execハーネスが必要です。
2.10 統合テスト
Ratpackの統合テストは、HTTPインターフェースを介してアプリケーションコンポーネントのサブセットをテストするテストです。
EmbeddedApp
フィクスチャは、実際のリクエストに応答するアドホックアプリケーションの構築を容易にします。統合テストのコンテキストでは、通常、テストするアプリケーションコンポーネントの特定の組み合わせをまとめるために使用されます。
実際Ratpackアプリケーションを構築するため、Renderer
、Parser
、ConfigurableModule
などのRatpack拡張ポイントの実装をテストするためにも使用できます。
EmbeddedApp
フィクスチャは、アプリケーションの起動と停止を管理し、埋め込みアプリケーションのリクエストを行うTestHttpClient
も提供します。
重要なのは、リソースを解放するために、埋め込みアプリケーションが不要になったらクローズする必要があるということです。EmbeddedApp
型は、java.io.AutoCloseable
インターフェースを実装しており、そのclose()
メソッドを使用してサーバーを停止できます。これは、JUnitの@After
メソッドなど、使用されているテストフレームワークの「テスト後」ライフサイクルイベントと組み合わせることがよくあります。
注:
EmbeddedApp
フィクスチャは、他のタイプのRatpack以外のアプリケーションをテストするときにモックされたHTTPサービスを作成するために「スタンドアロン」で使用することもできます。
3.10 機能テスト
Ratpackの機能テストは、HTTPインターフェースを介してアプリケーション全体をテストするテストです。
Javaのメインクラスとして定義されているRatpackアプリの場合、MainClassApplicationUnderTest
フィクスチャを使用できます。Groovyスクリプトとして定義されているRatpackアプリの場合、GroovyRatpackMainApplicationUnderTest
フィクスチャを使用できます。
カスタムエントリポイントがある場合は、ServerBackedApplicationUnderTest
抽象スーパークラスをニーズに合わせて拡張できます。
これらのフィクスチャは、アプリケーションの起動と停止を管理し、埋め込みアプリケーションのリクエストを行うTestHttpClient
も提供します。
重要なのは、リソースを解放するために、テスト対象のアプリケーションが不要になったらクローズする必要があるということです。CloseableApplicationUnderTest
型は、java.io.AutoCloseable
インターフェースを実装しており、そのclose()
メソッドを使用してサーバーを停止できます。これは、JUnitの@After
メソッドなど、使用されているテストフレームワークの「テスト後」ライフサイクルイベントと組み合わせることがよくあります。
1.3.10 Impositions
Ratpackは、impositions
と呼ばれる、テスト容易性のためテスト対象のアプリケーションを拡張するメカニズムを提供します。
通常、impositionはMainClassApplicationUnderTest
などをサブクラス化し、addImpositions(ImpositionsSpec)
メソッドをオーバーライドすることで指定されます。
2.3.10 ブラウザテスト
ブラウザテストは、ここで以前に機能テストと名付けられたものと同様に動作しますが、RatpackのTestHttpClient
の使用がブラウザの自動化に置き換えられる点が異なります。これには通常、MainClassApplicationUnderTest
を使用してアプリを起動および停止し、getAddress()
メソッドを介してテスト対象のアプリケーションのアドレスを提供することが含まれます。
Ratpackユーザーは、その表現力豊かなスタイルとSpockとの相性の良さから、ブラウザテストにGebをよく使用します。ratpack.io
サイトのRatpack/Gebベースのテストの例は、参考として利用できます。
11 HTTPクライアント
Ratpackは、リモートHTTP呼び出しを行うために使用できる独自のHttpClient
を提供します。Ratpackが提供するHttpClient
は完全にノンブロッキングであり、コアRatpackライブラリの一部です。Ratpackサーバーと同様に、HttpClient
も内部でNettyを使用しており、実際にはNettyのベストプラクティスに従って同じEventLoopGroup
を共有します。
1.11 基本的なGETリクエスト
import ratpack.core.http.client.HttpClient;
import ratpack.test.embed.EmbeddedApp;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class Example {
public static void main(String... args) throws Exception {
try (EmbeddedApp remoteApp = EmbeddedApp.fromHandler(ctx -> ctx.render("Hello from remoteApp"))) {
EmbeddedApp.fromHandler(ctx -> ctx
.render(
ctx
.get(HttpClient.class)
.get(remoteApp.getAddress())
.map(response -> response.getBody().getText())
)
).test(httpClient ->
assertEquals("Hello from remoteApp", httpClient.getText())
);
}
}
}
12 静的アセット
Ratpackは、静的ファイルをレスポンスとして提供するサポートを提供します。
1.12 ディレクトリから
Ratpackアプリケーションには、起動時に指定される「ベースディレクトリ」の概念があります。これは、アプリケーションに関する限り、事実上ファイルシステムのルートです。ベースディレクトリのファイルは、Chain.files()
メソッドを使用して提供できます。
import ratpack.test.embed.EmbeddedApp;
import ratpack.test.embed.EphemeralBaseDir;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class Example {
public static void main(String... args) throws Exception {
EphemeralBaseDir.tmpDir().use(baseDir -> {
baseDir.write("public/some.text", "foo");
baseDir.write("public/index.html", "bar");
EmbeddedApp.of(s -> s
.serverConfig(c -> c.baseDir(baseDir.getRoot()))
.handlers(c -> c
.files(f -> f.dir("public").indexFiles("index.html"))
)
).test(httpClient -> {
assertEquals("foo", httpClient.getText("some.text"));
assertEquals("bar", httpClient.getText());
assertEquals(404, httpClient.get("no-file-here").getStatusCode());
});
});
}
}
ファイルは、ファイルの通知された最終変更タイムスタンプに基づいてLast-Modified
ヘッダーとともに提供されます。クライアントがIf-Modified-Since
ヘッダーを送信した場合、Ratpackは、ファイルが指定された値から変更されていない場合は304
レスポンスで応答します。提供されるファイルにはETagは含まれません。
デフォルトでは、クライアントが要求した場合、ファイルはネットワーク経由でGZIP圧縮されます。これは、Response.noCompress()
メソッドを呼び出すことで、リクエストごとに無効にできます。これは通常、リクエストパス(ファイル拡張子など)を検査し、圧縮を無効にするファイル提供ハンドラーの前にハンドラーを配置することで使用されます。
2.12 アドホックファイル
個々のファイルは、Context.file()
およびContext.render()
メソッドを使用して提供できます。
import ratpack.test.embed.EmbeddedApp;
import ratpack.test.embed.EphemeralBaseDir;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class Example {
public static void main(String... args) throws Exception {
EphemeralBaseDir.tmpDir().use(baseDir -> {
baseDir.write("some.text", "foo");
EmbeddedApp.of(s -> s
.serverConfig(c -> c.baseDir(baseDir.getRoot()))
.handlers(c -> c
.get("f", ctx -> ctx.render(ctx.file("some.text")))
)
).test(httpClient ->
assertEquals("foo", httpClient.getText("f"))
);
});
}
}
Context.file()
によって返されたファイルが存在しない場合は、404
が発行されます。
レスポンスには、Chain.files()
メソッドで説明したのとまったく同じ方法でタイムスタンプが付けられ、圧縮されます。
3.12 「アセットパイプライン」を使用した高度なアセット提供
Asset Pipeline
プロジェクトは、Ratpackとの統合を提供します。これにより、高度なアセットバンドル、コンパイル、および提供が提供されます。
13 Google Guiceの統合
ratpack-guice
拡張機能は、Google Guiceとの統合を提供します。この拡張機能の主な機能は、Guiceによってサーバーレジストリを構築できるようにすることです。つまり、GuiceのInjector
をRatpack Registryとして提示できます。これにより、アプリケーションの配線をGuiceモジュールとバインディングで指定できますが、レジストリは引き続き実行時に異なるRatpack拡張機能間の共通統合レイヤーとなります。
ratpack-guice
モジュールは、2.0.0-rc-1の時点で、Guice 5.1.0(およびマルチバインディング拡張機能)に対してビルドされ(依存関係があります)、依存しています。
1.13 モジュール
Guiceは、オブジェクトを提供するためのレシピの一種であるモジュールの概念を提供します。詳細については、Guiceの「Getting Started」ドキュメントを参照してください。
ratpack-guice
ライブラリは、アプリケーションのバインディングを指定するためのBindingsSpec
型を提供します。
2.13 依存性注入されたハンドラー
Guice連携により、アプリケーションのコンポーネントを疎結合にする方法が得られます。機能をスタンドアロン(つまり、Handler
ではない)オブジェクトに分離し、ハンドラーからこれらのオブジェクトを使用できます。これにより、コードの保守性とテスト容易性が向上します。これは標準的な「依存性注入」または「制御の反転」パターンです。
Guiceクラスは、アプリケーションの基礎となるルートハンドラーを作成するための静的なhandler()
ファクトリメソッドを提供します。これらのメソッド(一般的に使用されるもの)は、アプリケーションのハンドラーチェーンを構築するために使用できるChain
インスタンスを公開します。これらのメソッドによって公開されるインスタンスは、依存性注入されたハンドラーインスタンスを構築するために使用できるレジストリ(getRegistry()
メソッド経由)を提供します。
サンプルコードについては、Guiceクラスのドキュメントを参照してください。
3.13 Guiceとコンテキストレジストリ
TODO guice をバックアップしたレジストリの実装
これにより、オブジェクトをコンテキストからオンデマンドで取得できるため、依存性注入されたハンドラーの代替手段が提供されます。
さらに有用なのは、これにより、RatpackインフラストラクチャをGuiceモジュールを介して統合できることです。たとえば、ServerErrorHandler
の実装は、Guiceモジュールによって提供できます。Guiceによってバインドされたオブジェクトはコンテキストレジストリの検索メカニズムに統合されるため、この実装はエラー処理インフラストラクチャに参加します。
これは、Rendererの実装など、コンテキストレジストリの検索を介して動作するすべてのRatpackインフラストラクチャに当てはまります。
14 Groovy
Groovyは、Javaの代替となるJVMプログラミング言語です。Javaとの強力な相乗効果があり、多くの言語およびライブラリ機能により、魅力的なプログラミング環境となっています。Ratpackは、ratpack-groovy
およびratpack-groovy-test
ライブラリを介してGroovyとの強力な統合を提供します。GroovyでRatpackアプリケーションを作成すると、一般的に、Javaと比較してGroovyの簡潔な構文を通じてコードが少なくなり、より生産的で楽しい開発体験が得られます。ただし、明確にしておくと、RatpackアプリケーションをGroovyで作成する必要はありません。
Groovyは一般的に動的言語として知られています。ただし、Groovy 2.0では、オプションとして完全な静的型付けと静的コンパイルが追加されました。RatpackのGroovyサポートは、厳密に「静的Groovy」を完全にサポートするように設計されており、この目標を達成するために定型コードを導入しないようにGroovyの最新機能も活用しています。言い換えれば、RatpackのGroovyサポートは動的言語機能を使用しておらず、強く型付けされたAPIを持っています。
TODO: 静的Groovyを説明する適切なリンクを見つけて、上記で使用する
1.14 前提条件
Groovyを初めて使用する場合は、続行する前に、次の基本的なGroovyトピックを調べてください。
- クロージャ
def
キーワード
TODO: このリストには他に何を入れるべきですか? また、リンクが必要です
何か他のもの
2.14 Ratpack Groovy API
TODO: Groovy APIがJava APIをラップし、対応するratpack.groovy.*でミラーリングすることを説明する
1.2.14 @DelegatesTo
@DelegatesTo
は、コードを文書化し、IDEおよび静的型チェッカー/コンパイラーにコンパイル時により多くの型情報を提供することを目的としています。アノテーションの適用は、特にDSL作成者にとって興味深いものです。
次のRatpackコードスニペットを検討しましょう
ratpack {
handlers {
get {
render "Hello world!"
}
}
}
このコードは、Ratpackの「GroovyChain DSL」で記述されているとも呼ばれます。これは基本的に次と同じです
ratpack({
handlers({
get({
render("Hello world!")
})
})
})
ratpack
、handlers
、get
、render
メソッドが呼び出されます。実際、各メソッドを1回だけ呼び出すという制限はなく、たとえば、コードに異なるリクエストURIに応答するget
の複数のメソッド呼び出しがあった可能性があります
ratpack {
handlers {
get {
render "Hello world!"
}
get('foo') {
render "bar"
}
}
}
ratpack
、handlers
、get
の呼び出しがどのように階層構造を構築しているかに注目してください。しかし、実際にはどのようにGroovy/Javaオブジェクトの呼び出しに変換されるのでしょうか。
それがまさに委譲が役割を果たす場所です。Groovyでは、Closure
コードブロック内のメソッド呼び出しのターゲットを変更できます。非常に基本的な例を見てみましょう
class Settings {
String host
Integer port
def host(String h) { this.host = h }
def port(Integer p) { this.port = p }
}
Settings settings(Closure dsl) {
def p = new Settings()
def code = dsl.clone() // better use: dsl.rehydrate(p, this, this)
code.delegate = p
code()
return p
}
// our DSL starts here and returns a Settings instance
Settings config = settings {
port 1234
host 'localhost'
}
assert config.host == 'localhost'
assert config.port == 1234
settings
DSLブロックのコードは、現在の語彙スコープに存在しないメソッドを呼び出します。ただし、実行時にdelegate
プロパティが設定されると、Groovyはさらに、指定されたdelegateオブジェクト(この場合はSettings
インスタンス)に対してメソッドを解決します。
委譲は、Ratpack DSLの場合と同様に、Groovy DSLで、DSLコードを基になるオブジェクトから分離するために一般的に使用されます。
この手法には、IDEでのコード補完や、Groovy 2で追加された静的型チェッカー/コンパイラーに問題があります。次のコードをgroovyConsole
で実行すると
class Settings {
String host
Integer port
def host(String h) { this.host = h }
def port(Integer p) { this.port = p }
}
Settings settings(Closure dsl) {
def p = new Settings()
def code = dsl.clone() // better use: dsl.rehydrate(p, this, this)
code.delegate = p
code()
return p
}
@groovy.transform.TypeChecked
void createConfig() {
Settings config = settings {
port 1234
host 'localhost'
}
assert config.host == 'localhost'
assert config.port == 1234
}
が得られます
[Static type checking] - Cannot find matching method ConsoleScript23#port(int). Please check if the declared type is right and if the method exists.
at line: 20, column: 7
[Static type checking] - Cannot find matching method ConsoleScript23#host(java.lang.String). Please check if the declared type is right and if the method exists.
at line: 21, column: 7
型チェッカーは、コンパイル時にdelegate型Settings
に関する情報を欠落しています。
これが、@DelegatesTo
が最終的に登場するポイントです。これは、この型情報をClosure
メソッドパラメーターに指定する必要がある場合に正確に使用されます
// ...
// let's tell the compiler we're delegating to the Settings class
Settings settings(@DelegatesTo(Settings) Closure dsl) {
def p = new Settings()
def code = dsl.clone() // better use: dsl.rehydrate(p, this, this)
code.delegate = p
code()
return p
}
// ...
Ratpackは、Closure
メソッドパラメーターが使用される場合は常に@DelegatesTo
を使用します。これは、より良いコード補完や静的型チェッカーだけでなく、ドキュメントの目的にも役立ちます。
3.14 ratpack.groovyスクリプト
TODO: このファイルで使用されるDSLを紹介し、開発モード時のリロードについて説明する
4.14 handlers {} DSL
TODO:
GroovyChain
DSLと、ハンドラーとしてのクロージャを紹介する
5.14 テスト
Groovyには、テストを記述するための組み込みサポートが付属しています。JUnitの統合サポートに加えて、このプログラミング言語には、テスト駆動開発に非常に役立つことが証明されている機能が付属しています。その1つが、次のセクションで詳しく見ていく拡張されたassert
キーワードです。
Ratpack固有のテストサポートドキュメントをお探しの場合は、このマニュアルに専用のRatpackテストガイドセクションがあります。
1.5.14 パワーアサーション
テストを記述することは、アサーションを使用して仮定を策定することを意味します。Javaでは、これはJ2SE 1.4で追加されたassert
キーワードを使用して行うことができます。Javaのアサーションステートメントは、デフォルトで無効になっています。
Groovyには、パワーアサーションステートメントとしても知られる、強力なassert
バリアントが付属しています。Groovyのパワーassert
は、ブール式がfalse
に検証された場合に表示される出力が、Javaバージョンとは異なります
def x = 1
assert x == 2
// Output:
//
// Assertion failed:
// assert x == 2
// | |
// 1 false
java.lang.AssertionError
には、元のAssertionエラーメッセージの拡張バージョンが含まれています。その出力は、外側の式から最も内側の式まで、すべての変数と式の値を示しています。
表現力が優れているため、Groovyコミュニティでは、選択したテストライブラリが提供するassert*
メソッドの代わりに、assert
ステートメントを使用してテストケースを記述することが一般的になっています。
assert
ステートメントは、テストを記述するための多くの便利なGroovy機能の1つにすぎません。すべてのGroovy言語テスト機能の包括的なガイドをお探しの場合は、Groovyテストガイドを参照してください。
2.5.14 JUnit 3のサポート
Groovyには、埋め込まれたJUnit 3サポートと、カスタムのTestCase
ベースクラスであるgroovy.util.GroovyTestCase
が付属しています。GroovyTestCase
はTestCase
を拡張し、便利なユーティリティメソッドを追加します。
1つの例は、shouldFail
メソッドファミリです。各shouldFail
メソッドはClosure
を受け取り、それを実行します。例外がスローされてコードブロックが失敗した場合、shouldFail
は例外をキャッチし、テストメソッドは失敗しません
import groovy.util.GroovyTestCase
class ListTest extends GroovyTestCase {
void testInvalidIndexAccess1() {
def numbers = [1,2,3,4]
shouldFail {
numbers.get(4)
}
}
}
実際には、shouldFail
はキャッチされた例外を返すため、例外メッセージまたは型をアサートできます
import groovy.util.GroovyTestCase
class ListTest extends GroovyTestCase {
void testInvalidIndexAccess1() {
def numbers = [1,2,3,4]
def msg = shouldFail {
numbers.get(4)
}
assert msg == 'Index: 4, Size: 4'
}
}
さらに、shouldFail
メソッドには、Closure
パラメーターの前にjava.lang.Class
パラメーターが付属するバリアントがあります
import groovy.util.GroovyTestCase
class ListTest extends GroovyTestCase {
void testInvalidIndexAccess1() {
def numbers = [1,2,3,4]
def msg = shouldFail(IndexOutOfBoundsException) {
numbers.get(4)
}
assert msg == 'Index: 4, Size: 4'
}
}
スローされた例外が別の型の場合、shouldFail
は例外を再スローして、テストメソッドを失敗させます
すべてのGroovyTestCase
メソッドの完全な概要は、JavaDocドキュメントにあります。
3.5.14 JUnit 4のサポート
Groovyには、埋め込まれたJUnit 4サポートが付属しています。Groovy 2.3.0以降、groovy.test.GroovyAssert
クラスは、GroovyのGroovyTestCase
JUnit 3ベースクラスを補完するものと見なすことができます。GroovyAssert
は、GroovyでJUnit 4テストを記述するための静的ユーティリティメソッドを追加することにより、org.junit.Assert
を拡張します。
import static groovy.test.GroovyAssert.shouldFail
class ListTest {
void testInvalidIndexAccess1() {
def numbers = [1,2,3,4]
shouldFail {
numbers.get(4)
}
}
}
すべてのGroovyAssert
メソッドの完全な概要は、JavaDocドキュメントにあります。
4.5.14 Spock
Spockは、JavaおよびGroovyアプリケーション用のテストおよび仕様フレームワークです。群を抜いて優れているのは、その美しく表現力の高い仕様DSLです。Spock仕様は、Groovyクラスとして記述されます。
Spockは、ユニットテスト、統合テスト、またはBDD(ビヘイビア駆動開発)テストに使用でき、特定のテストフレームワークまたはライブラリのカテゴリには分類されません。
次の段落では、Spock仕様の構造を最初に見ていきます。これは完全なドキュメントではなく、Spockが何をしようとしているのかをかなりよく理解するためのものです。
1.4.5.14 最初のステップ
Spockを使用すると、対象システムの機能(プロパティ、側面)を記述する仕様を記述できます。「システム」は、単一のクラスからアプリケーション全体まで何でもかまいません。これのより高度な用語は、仕様下のシステムです。機能の説明は、システムの特定のスナップショットとそのコラボレーターから始まります。このスナップショットは、機能のフィクスチャと呼ばれます。
Spock仕様クラスは、spock.lang.Specification
から自動的に派生します。具体的な仕様クラスは、フィールド、フィクスチャメソッド、フィーチャーメソッド、およびヘルパーメソッドで構成される場合があります。
Ratackユニットテスト仕様を見てみましょう
import ratpack.groovy.test.GroovyRatpackMainApplicationUnderTest
import ratpack.test.http.TestHttpClient
import ratpack.test.ServerBackedApplicationUnderTest
class SiteSpec {
ServerBackedApplicationUnderTest aut = new GroovyRatpackMainApplicationUnderTest()
@Delegate TestHttpClient client = TestHttpClient.testHttpClient(aut)
def "Check Site Index"() {
when:
get("index.html")
then:
response.statusCode == 200
response.body.text.contains('<title>Ratpack: A toolkit for JVM web applications</title>')
}
}
Spockフィーチャー仕様は、spock.lang.Specification
クラス内のメソッドとして定義されます。これらは、メソッド名の代わりに文字列リテラルを使用して機能を記述します。上記の仕様では、"Check Site Index"
を使用して、index.html
へのリクエストの結果をテストします。
フィーチャー仕様は、when
ブロックとthen
ブロックを使用します。when
ブロックはいわゆる刺激を作成し、刺激に対する応答を記述するthen
ブロックのコンパニオンです。then
ブロックでassert
ステートメントも省略できることに注意してください。Spockはそこでブール式を正しく解釈します。setup
ブロックは、フィーチャーメソッド内でのみ表示されるローカル変数を構成するために使用できた可能性があります。
2.4.5.14 Spockの詳細
Spockは、このセクションでは見てこなかったデータテーブルやモックなどのより高度な機能を提供します。詳細については、Spock GitHubページを参照してください。
15 RxJava
優れたRxJavaをRatpackアプリケーションで使用して、非同期操作をエレガントに構成できます。
ratpack-rx2
モジュールは、RatpackのPromiseをRxJavaのObservableに適応させるための静的メソッドを提供するRxRatpack
クラスを提供します。
2.0.0-rc-1時点でのratpack-rx2
モジュールは、RxJava 2.2.21に対してビルドされ(依存関係があります)、依存しています。
1.15 初期化
統合を完全に有効にするには、RxRatpack.initialize()
を呼び出す必要があります。このメソッドは、JVMのライフタイム中に一度だけ呼び出す必要があります。
2.15 Ratpackの監視
この統合は、RxRatpack.single()
およびRxRatpack.observe()
静的メソッドに基づいています。これらのメソッドは、RatpackのPromise型をObservableに適応させ、RxJavaが提供するすべてのObservable演算子で使用できるようになります。
たとえば、ブロッキング操作を簡単に監視できます。
import ratpack.exec.Promise;
import ratpack.exec.Blocking;
import ratpack.test.handling.HandlingResult;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static ratpack.rx2.RxRatpack.single;
import static ratpack.test.handling.RequestFixture.requestFixture;
public class Example {
public static void main(String... args) throws Exception {
HandlingResult result = requestFixture().handle(context -> {
Promise<String> promise = Blocking.get(() -> "hello world");
single(promise).map(String::toUpperCase).subscribe(context::render);
});
assertEquals("HELLO WORLD", result.rendered(String.class));
}
}
3.15 暗黙的なエラー処理
RxJava統合の重要な機能は、暗黙的なエラー処理です。すべてのObservableシーケンスには、例外を実行コンテキストのエラーハンドラーに転送する暗黙的なデフォルトのエラー処理戦略があります。実際には、これはObservableシーケンスに対してエラーハンドラーを定義する必要がほとんどないことを意味します。
import ratpack.core.error.ServerErrorHandler;
import ratpack.rx2.RxRatpack;
import ratpack.test.handling.RequestFixture;
import ratpack.test.handling.HandlingResult;
import io.reactivex.Observable;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class Example {
public static void main(String... args) throws Exception {
RxRatpack.initialize(); // must be called once per JVM
HandlingResult result = RequestFixture.requestFixture().handleChain(chain -> {
chain.register(registry ->
registry.add(ServerErrorHandler.class, (context, throwable) ->
context.render("caught by error handler: " + throwable.getMessage())
)
);
chain.get(ctx -> Observable.<String>error(new Exception("!")).subscribe((s) -> {}));
});
assertEquals("caught by error handler: !", result.rendered(String.class));
}
}
この場合、ブロッキング操作中にスローされたThrowableは、現在のServerErrorHandler
に転送されます。これにより、おそらくエラーページがレスポンスに表示されます。サブスクライバーがエラー処理戦略を実装している場合、暗黙的なエラーハンドラーの代わりにそれが使用されます。
暗黙的なエラー処理は、Ratpackが管理するスレッドで作成されたすべてのObservableに適用されます。RatpackのPromiseによってバックアップされるObservableに限定されません。
16 Jackson
Jackson JSONマーシャリングライブラリとの統合により、JSONを操作する機能が提供されます。これはratpack-core
の一部として提供されます。
Ratpack 2.0.0-rc-1時点では、Jackson Core 2.13.1に対してビルドされ(依存関係があります)、依存しています。
ratpack.core.jackson.Jackson
クラスは、Jackson関連機能のほとんどを提供します。
1.16 JSONレスポンスの書き込み
Jackson統合により、オブジェクトをJSONとしてレンダリングするためのRendererが追加されます。
Jackson.json()
メソッドを使用して、Context.render()
メソッドで使用するために、任意のオブジェクト(Jacksonでシリアル化可能)をラップできます。
import ratpack.test.embed.EmbeddedApp;
import ratpack.core.http.client.ReceivedResponse;
import static ratpack.core.jackson.Jackson.json;
import static org.junit.jupiter.api.Assertions.*;
public class Example {
public static class Person {
private final String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
public static void main(String... args) throws Exception {
EmbeddedApp.of(s -> s
.handlers(chain ->
chain.get(ctx -> ctx.render(json(new Person("John"))))
)
).test(httpClient -> {
ReceivedResponse response = httpClient.get();
assertEquals("{\"name\":\"John\"}", response.getBody().getText());
assertEquals("application/json", response.getBody().getContentType().getType());
});
}
}
ストリーミングやJSONイベントなど、その他の例については、Jackson
クラスのドキュメントを参照してください。
2.16 JSONリクエストの読み込み
Jackson統合により、JSONリクエストボディをオブジェクトに変換するためのParserが追加されます。
Jackson.jsonNode()
およびJackson.fromJson()
メソッドを使用して、Context.parse()
メソッドで使用するオブジェクトを作成できます。
import ratpack.guice.Guice;
import ratpack.test.embed.EmbeddedApp;
import ratpack.core.http.client.ReceivedResponse;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.reflect.TypeToken;
import java.util.List;
import static ratpack.func.Types.listOf;
import static ratpack.core.jackson.Jackson.jsonNode;
import static ratpack.core.jackson.Jackson.fromJson;
import static org.junit.jupiter.api.Assertions.*;
public class Example {
public static class Person {
private final String name;
public Person(@JsonProperty("name") String name) {
this.name = name;
}
public String getName() {
return name;
}
}
public static void main(String... args) throws Exception {
EmbeddedApp.of(s -> s
.handlers(chain -> chain
.post("asNode", ctx -> {
ctx.render(ctx.parse(jsonNode()).map(n -> n.get("name").asText()));
})
.post("asPerson", ctx -> {
ctx.render(ctx.parse(fromJson(Person.class)).map(p -> p.getName()));
})
.post("asPersonList", ctx -> {
ctx.render(ctx.parse(fromJson(listOf(Person.class))).map(p -> p.get(0).getName()));
})
)
).test(httpClient -> {
ReceivedResponse response = httpClient.requestSpec(s ->
s.body(b -> b.type("application/json").text("{\"name\":\"John\"}"))
).post("asNode");
assertEquals("John", response.getBody().getText());
response = httpClient.requestSpec(s ->
s.body(b -> b.type("application/json").text("{\"name\":\"John\"}"))
).post("asPerson");
assertEquals("John", response.getBody().getText());
response = httpClient.requestSpec(s ->
s.body(b -> b.type("application/json").text("[{\"name\":\"John\"}]"))
).post("asPersonList");
assertEquals("John", response.getBody().getText());
});
}
}
この統合により、オプションなしパーサーが追加され、Context.parse(Class)
メソッドおよびContext.parse(TypeToken)
メソッドを使用できるようになります。
import ratpack.test.embed.EmbeddedApp;
import ratpack.core.http.client.ReceivedResponse;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.reflect.TypeToken;
import java.util.List;
import static ratpack.func.Types.listOf;
import static org.junit.jupiter.api.Assertions.*;
public class Example {
public static class Person {
private final String name;
public Person(@JsonProperty("name") String name) {
this.name = name;
}
public String getName() {
return name;
}
}
public static void main(String... args) throws Exception {
EmbeddedApp.of(s -> s
.handlers(chain -> chain
.post("asPerson", ctx -> {
ctx.parse(Person.class).then(person -> ctx.render(person.getName()));
})
.post("asPersonList", ctx -> {
ctx.parse(listOf(Person.class)).then(person -> ctx.render(person.get(0).getName()));
})
)
).test(httpClient -> {
ReceivedResponse response = httpClient.requestSpec(s ->
s.body(b -> b.type("application/json").text("{\"name\":\"John\"}"))
).post("asPerson");
assertEquals("John", response.getBody().getText());
response = httpClient.requestSpec(s ->
s.body(b -> b.type("application/json").text("[{\"name\":\"John\"}]"))
).post("asPersonList");
assertEquals("John", response.getBody().getText());
});
}
}
3.16 Jacksonの構成
Jackson APIは、ObjectMapper
を中心に構築されています。Ratpackは、デフォルトインスタンスをベースレジストリに自動的に追加します。Jacksonの動作を構成するには、このインスタンスをオーバーライドします。
Jacksonのフィーチャーモジュールを使用すると、Jacksonを拡張して、追加のデータ型と機能をサポートできます。たとえば、JDK8モジュールでは、OptionalのようなJDK8型がサポートされています。
このようなモジュールを使用するには、適切に構成されたObjectMapper
をレジストリに追加するだけです。
import ratpack.test.embed.EmbeddedApp;
import ratpack.core.http.client.ReceivedResponse;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import java.util.Optional;
import static ratpack.core.jackson.Jackson.json;
import static org.junit.jupiter.api.Assertions.*;
public class Example {
public static class Person {
private final String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
public static void main(String... args) throws Exception {
EmbeddedApp.of(s -> s
.registryOf(r -> r
.add(ObjectMapper.class, new ObjectMapper().registerModule(new Jdk8Module()))
)
.handlers(chain ->
chain.get(ctx -> {
Optional<Person> personOptional = Optional.of(new Person("John"));
ctx.render(json(personOptional));
})
)
).test(httpClient -> {
ReceivedResponse response = httpClient.get();
assertEquals("{\"name\":\"John\"}", response.getBody().getText());
assertEquals("application/json", response.getBody().getContentType().getType());
});
}
}
17 Resilience4j
Resilience4jは、Java 8用に設計された軽量なフォールトトレランスライブラリです。これは、メンテナンスモードになっているNetflixのHystrixに触発されています。
resilience4j-ratpackモジュールは、Resilience4jプロジェクトによって保守されています。詳細については、スタートガイドを参照してください。
1.17 デモアプリケーション
Resilience4jチームは、RatpackでのResilience4jの使用方法を示すデモアプリケーションを公開しています。
18 構成
ほとんどのアプリケーションでは、何らかのレベルの構成が必要です。これは、使用する正しい外部リソース(データベース、その他のサービスなど)を指定したり、パフォーマンスを調整したり、特定の環境の要件に合わせて調整したりするために使用できます。Ratpackは、Ratpackアプリケーションで構成情報にアクセスするための簡単で柔軟なメカニズムを提供します。
構成データには、Jacksonを使用したオブジェクトバインディングを介してアクセスします。Ratpackによって提供される構成オブジェクトは、すぐに使用できるように設計されています。構成データは、YAMLファイル、JSONファイル、プロパティファイル、環境変数、システムプロパティなど、複数のソースからロードできます。
1.18 クイックスタート
開始するには
RatpackServer
定義関数内で、ConfigData
インスタンスを構築します(例については、クラスドキュメントを参照)。- 構成データからバインドされた構成オブジェクトを取得します。
2.18 構成ソース
ConfigDataBuilder
は、最も一般的なソースからデータを簡単にロードするためのメソッドを提供します。
一般的なファイル形式は、yaml
、json
、およびprops
メソッドを使用して使用できます。提供されているシグネチャを使用して、ローカルファイル(String
またはPath
)、ネットワーク経由(URL
)、クラスパス(Resources.getResource(String)
を使用してURL
を取得)から、またはByteSource
として扱える場所からデータをロードできます。さらに、Map
/Properties
オブジェクト(特にデフォルト値に役立ちます。例を参照)、システムプロパティ、環境変数などの非ファイルソースからデータをロードすることもできます。object
メソッドを使用して、既存のオブジェクトから構成データをロードすることもできます。さらに柔軟性が必要な場合は、独自のConfigSource
実装を提供できます。
1.2.18 フラット構成ソース
環境変数、Properties
、およびMap
はフラットなデータ構造ですが、Ratpackで使用されるバインディングモデルは階層型です。このギャップを埋めるために、これらの構成ソース実装では、フラットなキーと値のペアを有用なデータに変換するための規則が適用されます。
1.1.2.18 環境変数
デフォルトの環境変数構成ソースでは、次の規則が使用されます。
- 特定のプレフィックス(デフォルトでは
RATPACK_
)に一致する環境変数のみが考慮されます。このプレフィックスは、それ以上の処理の前に削除されます。 - 環境変数名は、オブジェクト境界として二重アンダースコアを使用して、オブジェクトごとのセグメントに分割されます。
- セグメントは、単一のアンダースコアを単語境界として使用して、キャメルケースのフィールド名に変換されます。
カスタム環境解析が必要な場合は、EnvironmentParser
実装を提供できます。
2.1.2.18 プロパティ/マップ
デフォルトのProperties
/Map
構成ソースでは、次の規則が使用されます。
- システムプロパティからロードする場合、特定のプレフィックス(デフォルトでは
ratpack.
)に一致するキーのみが考慮されます。このプレフィックスは、それ以上の処理の前に削除されます。 - キーは、オブジェクト境界としてドット(「.」)を使用して、オブジェクトごとのセグメントに分割されます。
- 角かっこで囲まれた整数インデックスを使用して、リストを設定できます。
- これは、単純な値(文字列)とオブジェクト(インデックスの後にさらにセグメントが続く)の両方でサポートされています。
- この構文は、環境変数ではサポートされていません。
3.18 使用法
1.3.18 順序付け
複数の構成ソースがある場合は、重要度の低いものから重要なものの順にビルダーに追加します。たとえば、システムプロパティを介してオーバーライドできるようにしたい構成ファイルがある場合は、最初に構成ファイルソースを追加し、次にシステムプロパティソースを追加します。同様に、環境変数を介してオーバーライドできるようにしたいデフォルト設定がある場合は、最初にデフォルト設定ソース(おそらくprops
経由)を追加し、次に環境変数ソースを追加します。
2.3.18 エラー処理
ConfigDataBuilderドキュメントに示されているように、構成ソースからデータをロード中にエラーが発生した場合に動作をカスタマイズするために、onError
を使用できます。最も一般的なのは、ロード例外を無視して構成ソースをオプションにすることです。
3.3.18 オブジェクトマッパー
Ratpackは、構成オブジェクトのバインディングにJacksonを使用します。使用されるデフォルトのObjectMapper
は、一般的に使用されるJacksonモジュールがあらかじめロードされた状態で構成されており、引用符なしのフィールド名、単一引用符の使用、および不明なフィールド名の無視を許可するように設定されています。これは、すぐに使用できるようにすることを目的としています。ただし、Jackson構成設定を変更したり、追加のJacksonモジュールを追加したりしたい場合があるかもしれません。その場合は、ConfigData.of(...)
のさまざまなシグネチャを使用するか、ConfigDataBuilder.configureObjectMapper(...)
を使用することで実現できます。
4.3.18 バインディング
ConfigData
インスタンスを構築したら、データを構成オブジェクトにバインドできます。最も簡単なオプションは、アプリケーションの構成全体を表すクラスを定義し、ConfigData.get(Class)
を使用して一度にすべてをバインドすることです。または、ConfigData.get(String, Class)
を使用して、データ内の指定されたパスでオブジェクトを1つずつバインドできます。一般的なServerConfig
オブジェクトにバインドする場合は、便利なようにConfigData.getServerConfig(...)
シグネチャが用意されています。
19 Spring Boot
ratpack-spring-boot
拡張機能は、Spring Bootとの統合を提供します。このライブラリには主に2つの機能があります。1つはSpringのApplicationContext
からRatpackサーバーレジストリを作成する機能、もう1つはRatpack自体をSpring Bootアプリケーションに埋め込む機能です(これによりApplicationContext
が自動的にサーバーレジストリの一部になります)。
1.19 Springの便利なクラス
標準的なRatpackアプリケーションでは、Spring
という便利なクラスを使って簡単にレジストリを作成できます。これはGuiceの依存性注入の代替または補完として機能します。なぜなら、ほぼ同じように機能し、Ratpackではレジストリを非常に便利に連結できるからです。
以下は、このAPIを使ってアプリケーションを構成するメインクラスの例です。
package my.app;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import ratpack.core.server.RatpackServer;
import static ratpack.spring.Spring.spring;
public class Main {
public static void main(String... args) throws Exception {
RatpackServer.start(server -> server
.registry(spring(MyConfiguration.class))
.handlers(chain -> chain
.get(ctx -> ctx
.render("Hello " + ctx.get(Service.class).message()))
.get(":message", ctx -> ctx
.render("Hello " + ctx.getPathTokens().get("message") + "!")
)
)
);
}
}
@Configuration
class MyConfiguration {
@Bean
public Service service() {
return () -> "World!";
}
}
interface Service {
String message();
}
Spring.spring()
メソッドは、ApplicationContext
を作成し、それをRatpackのRegistry
インターフェースに適合させます。
注:SpringのListableBeanFactory
APIは現在、パラメータ化された型を持つBeanの検索をサポートしていません。そのため、適合されたRegistry
インスタンスはこの制限のため、これをサポートしていません。SpringのListableBeanFactory
APIにジェネリックス機能を追加するための機能リクエストがあります。
2.19 Spring BootアプリケーションへのRatpackの埋め込み
Ratpackアプリケーションに(Registry
として)Springを埋め込む代わりに、逆のことを行うこともできます。つまり、RatpackをSpring Bootにサーバーとして埋め込み、Spring Bootがサポートするサーブレットコンテナの優れた代替手段を提供できます。この機能セットの中核は、Ratpackを起動するためにSpring構成クラスに追加するアノテーション@EnableRatpack
です。その後、ハンドラーをAction<Chain>
型の@Beans
として宣言できます。例:
package my.app;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import ratpack.func.Action;
import ratpack.core.handling.Chain;
import ratpack.spring.config.EnableRatpack;
@SpringBootApplication
@EnableRatpack
public class Main {
@Bean
public Action<Chain> home() {
return chain -> chain
.get(ctx -> ctx
.render("Hello " + service().message())
);
}
@Bean
public Service service() {
return () -> "World!";
}
public static void main(String... args) throws Exception {
SpringApplication.run(Main.class, args);
}
}
interface Service {
String message();
}
ヒント:Ratpackは、classpath内の"/public"または"/static"にある静的コンテンツのハンドラーを自動的に登録します(通常のSpring Bootアプリケーションと同様)。
1.2.19 既存のGuiceモジュールの再利用
RatpackがSpringアプリケーションに埋め込まれている場合、例えばテンプレートレンダリングのために既存のGuiceモジュールを再利用すると便利な場合があります。そのためには、Module
型の@Bean
を含めるだけです。例えば、ThymeleafをサポートするThymeleafModule
の場合:
package my.app;
import java.util.Collections;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import ratpack.func.Action;
import ratpack.core.handling.Chain;
import ratpack.thymeleaf.ThymeleafModule;
import ratpack.spring.config.EnableRatpack;
import static ratpack.thymeleaf3.Template.thymeleafTemplate;
@SpringBootApplication
@EnableRatpack
public class Main {
@Bean
public Action<Chain> home(Service service) {
return chain -> chain.get(ctx -> ctx
.render(thymeleafTemplate("myTemplate",
m -> m.put("key", "Hello " + service.message())))
);
}
@Bean
public ThymeleafModule thymeleafModule() {
return new ThymeleafModule();
}
@Bean
public Service service() {
return () -> "World!";
}
public static void main(String... args) throws Exception {
SpringApplication.run(Main.class, args);
}
}
20 pac4j
pac4jライブラリは、OAuth、CAS、OpenID(Connect)、SAML、Google App Engine、HTTP(フォーム認証、基本認証)などのさまざまな認証プロトコルやカスタム認証メカニズム(データベース認証など)を抽象化したセキュリティエンジンです。また、ロール/権限チェック、CSRFトークン、セキュリティヘッダーなど、さまざまな認可メカニズムもサポートしています。Ratpackとの統合は、pac4jコミュニティによって維持されているpac4j/ratpack-pac4jを通じて提供されます。
Gradleの依存関係
implementation 'org.pac4j:ratpack-pac4j:3.0.0'
1.20 セッションの使用
前述のように、ratpack-pac4j
を使用するにはratpack-session
によるセッションサポートが必要です。認証されると、ユーザーのプロファイルがセッションに保存されます。したがって、セッションを終了すると、事実上ユーザーはログアウトされます。
2.20 デモアプリケーション
Ratpackでpac4jを使用する方法を示す完全なアプリケーションについては、ratpack-pac4j-demoアプリケーションを参照してください。
21 Retrofit型安全クライアント
ratpack-retrofit2
拡張機能は、retrofit2ライブラリ(v2.9.0)を使用した宣言的な型安全HTTPクライアントの統合を提供します。
retrofitライブラリを使用すると、型安全なインターフェースを介してHTTP APIを表現できます。これにより、アプリケーションコードは基盤となるAPI設計と実装に依存せず、APIとのインターフェースの動作側面のみに集中できます。
RatpackRetrofit
クラスを使用して生成されたRetrofitクライアントは、RatpackのHttpClient
によってサポートされており、RatpackのPromise
構造を戻り型としてインターフェースすることができます。
ratpack-retrofit2
統合を使用することにより、開発者はAPI構造をクラスとメソッドのアノテーションとして分離するというメリットを享受しつつ、NettyによってサポートされているHttpClient
のノンブロッキングな性質とRatpackの実行モデルを活用できます。
1.21 使用法
RatpackRetrofit.client(URI endpoint)
およびRatpackRetrofit.client(String endpoint)
メソッドは、APIクライアントを作成するためのエントリポイントを提供します。
提供されたURI
またはString
は、Builder
によって生成されたすべてのクライアントのベースURLを指定します。
基盤となるRetrofit.Builder
は、RatpackRetrofit.Builder.configure(Action<? super Retrofit.Builder> spec)
メソッドで構成できます。
構成が完了すると、build(Class<T> api)
をAPIインターフェースとともに呼び出すことでクライアントが構築されます。このメソッドは、HTTPリクエストを発行し、応答を構成された戻り型に適合させる、生成されたインターフェースのインスタンスを返します。
import ratpack.exec.Promise;
import ratpack.retrofit.RatpackRetrofit;
import ratpack.test.embed.EmbeddedApp;
import retrofit2.http.GET;
import static org.junit.jupiter.api.Assertions.*;
public class Example {
public static interface HelloApi {
@GET("hi") Promise<String> hello();
}
public static void main(String... args) throws Exception {
EmbeddedApp api = EmbeddedApp.of(s -> s
.handlers(chain -> chain
.get("hi", ctx -> ctx.render("hello"))
)
);
EmbeddedApp.of(s -> s
.registryOf(r ->
r.add(HelloApi.class,
RatpackRetrofit
.client(api.getAddress())
.build(HelloApi.class))
)
.handlers(chain -> {
chain.get(ctx -> {
HelloApi helloApi = ctx.get(HelloApi.class);
ctx.render(helloApi.hello());
});
})
).test(httpClient -> {
assertEquals("hello", httpClient.getText());
api.close();
});
}
}
2.21 Retrofit APIでのRatpack Promiseの使用
ratpack-retrofit2
統合は、クライアントインターフェースの戻り型としてRatpackのPromise
を利用するためのサポートを提供します。プロミスされた値が次の型である場合、Promise
への適合をサポートします。
- 単純なスカラー型(
Integer
、String
、Long
など) - Retrofit
Response
- Ratpack
ReceivedResponse
次の例は、同じエンドポイントに対して構成された3つのバリエーションを示しています。
import ratpack.exec.Promise;
import ratpack.core.http.client.ReceivedResponse;
import retrofit2.Response;
import retrofit2.http.GET;
public interface Example {
@GET("hi") Promise<String> hello();
@GET("hi") Promise<Response<String>> helloResponse();
@GET("hi") Promise<ReceivedResponse> helloRaw();
}
3.21 複数のAPI実装の作成
多くのAPIは、異なる機能に対して個別のインターフェースを使用して表現できます。複数のクライアントを作成するには、基盤となるRetrofit
クラスを取得できます。
import ratpack.exec.Promise;
import ratpack.retrofit.RatpackRetrofit;
import ratpack.test.embed.EmbeddedApp;
import retrofit2.http.GET;
import retrofit2.Retrofit;
import static org.junit.jupiter.api.Assertions.*;
public class Example {
public static interface HelloApi {
@GET("hi") Promise<String> hello();
}
public static interface GoodbyeApi {
@GET("bye") Promise<String> bye();
}
public static void main(String... args) throws Exception {
EmbeddedApp api = EmbeddedApp.of(s -> s
.handlers(chain -> chain
.get("hi", ctx -> ctx.render("hello"))
.get("bye", ctx -> ctx.render("goodbye"))
)
);
EmbeddedApp.of(s -> s
.registryOf(r -> {
Retrofit retrofit = RatpackRetrofit
.client(api.getAddress())
.retrofit();
r.add(HelloApi.class, retrofit.create(HelloApi.class));
r.add(GoodbyeApi.class, retrofit.create(GoodbyeApi.class));
})
.handlers(chain -> {
chain.get(ctx -> {
HelloApi hiApi = ctx.get(HelloApi.class);
GoodbyeApi byeApi = ctx.get(GoodbyeApi.class);
ctx.render(hiApi.hello().right(byeApi.bye()).map(p -> p.left() + " and " + p.right()));
});
})
).test(httpClient -> {
assertEquals("hello and goodbye", httpClient.getText());
api.close();
});
}
}
4.21 Retrofitコンバーターの使用
デフォルトでは、ratpack-retrofit2
はScalarsConverterFactory
を登録します。これにより、API応答をJavaのString
、プリミティブ型、およびそれらのボックス型に変換できます。
リモートAPIがJSONで応答している場合は、JacksonConverterFactory
を登録する必要があります。
import com.fasterxml.jackson.databind.ObjectMapper;
import ratpack.exec.Promise;
import ratpack.retrofit.RatpackRetrofit;
import ratpack.exec.registry.Registry;
import ratpack.test.embed.EmbeddedApp;
import retrofit2.converter.jackson.JacksonConverterFactory;
import retrofit2.http.GET;
import retrofit2.Retrofit;
import java.util.List;
import static ratpack.core.jackson.Jackson.json;
import static org.junit.jupiter.api.Assertions.*;
public class Example {
public static interface NameApi {
@GET("names") Promise<List<String>> names();
}
public static void main(String... args) throws Exception {
EmbeddedApp api = EmbeddedApp.of(s -> s
.handlers(chain -> chain
.get("names", ctx -> ctx.render(json(new String[]{"John", "Jane"})))
)
);
EmbeddedApp.of(s -> s
.registry(r ->
Registry.single(NameApi.class,
RatpackRetrofit
.client(api.getAddress())
.configure(b ->
b.addConverterFactory(
JacksonConverterFactory.create(
r.get(ObjectMapper.class)
)
)
)
.build(NameApi.class))
)
.handlers(chain -> {
chain.get(ctx -> {
ctx.get(NameApi.class).names().then(nameList -> ctx.render(json(nameList)));
});
})
).test(httpClient -> {
assertEquals("[\"John\",\"Jane\"]", httpClient.getText());
api.close();
});
}
}
22 Dropwizard Metrics
ratpack-dropwizard-metrics
jarは、Dropwizard Metricsライブラリとの統合を提供します。
Dropwizard Metricsは、JVMで最も優れたメトリクスライブラリの1つです。開発時または本番環境でのリアルタイムを問わず、アプリケーションのパフォーマンスに関する深い洞察を提供するメトリックタイプとメトリックレポートのツールキットを提供します。これにより、処理されたリクエスト数や応答時間などの統計、内部コレクションの状態、キュー、またはコードの一部が実行された回数などのより一般的な情報を簡単に取得できます。コードを測定することで、コードが実行時に何をしているのかを正確に把握し、情報に基づいた最適化の決定を行うことができます。
RatpackとDropwizard Metricsの統合により、Guiceモジュールを登録するだけで、これらの主要なメトリックの多くがすでに自動的にキャプチャされます。さらに洞察が必要な場合は、Ratpackを使用すると、ライブラリの多くのメトリックタイプを使用して追加のメトリックを簡単にキャプチャし、ライブラリのメトリックレポートを使用してこれらのすべてのメトリックを必要な出力にレポートすることもできます。
詳細な使用情報については、DropwizardMetricsModule
を参照してください。
1.22 組み込みメトリック
Ratpackは、主要なメトリックに対する組み込みのメトリックコレクターを提供します。DropwizardMetricsModule
を使用してアプリケーション内でメトリックが有効になっている場合、組み込みのメトリックコレクターも自動的に有効になります。
Ratpackには、次のための組み込みメトリックコレクターがあります。
- リクエストタイミング
- バックグラウンド操作タイミング
Ratpackは、Dropwizard MetricのJVM計測もサポートしています。使用情報については、DropwizardMetricsConfig.jvmMetrics(boolean)
を参照してください。
2.22 カスタムメトリック
Ratpackでは、次の2つの方法で独自のアプリケーションメトリックをキャプチャできます。
- 依存性注入またはコンテキストレジストリのルックアップによって
MetricRegistry
を取得し、独自のメトリックを登録します。 - Guiceで注入されたクラスにメトリックアノテーションを追加します。
詳細については、DropwizardMetricsModule
を参照してください。
3.22 メトリックのレポート
Ratpackは、次の出力に対するメトリックレポートをサポートします。
Webソケットでリアルタイムメトリックを使用する方法の例については、example-booksプロジェクトを参照してください。
23 Gradleでのビルド
Ratpackアプリケーションをビルドする推奨方法は、Ratpackプロジェクトが提供するGradleプラグインを介して、Gradleビルドシステムを使用することです。
Ratpackは純粋にランタイムツールキットであり、Ruby on RailsやGrailsのような開発時ツールではありません。つまり、Ratpackアプリケーションをビルドするために好きなものを使用できます。提供されているGradleプラグインは、単に利便性を提供するだけで、Ratpack開発に不可欠なものではありません。
1.23 設定
最初の要件は、GradleプラグインをGradleプロジェクトに適用することです…
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath "io.ratpack:ratpack-gradle:2.0.0-rc-1"
}
}
apply plugin: "io.ratpack.ratpack-java"
repositories {
mavenCentral()
}
または、GroovyベースのRatpackプロジェクトの場合…
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath "io.ratpack:ratpack-gradle:2.0.0-rc-1"
}
}
apply plugin: "io.ratpack.ratpack-groovy"
repositories {
mavenCentral()
}
'io.ratpack.ratpack-java'
プラグインは、コア Gradle の 'java'
プラグインを適用します。'io.ratpack.ratpack-groovy'
プラグインは、コア Gradle の 'groovy'
プラグインを適用します。これは、標準的な Gradle ベースのプロジェクトのように、コードや依存関係をアプリケーションに追加できることを意味します (例えば、ソースを src/main/[groovy|java]
に配置するなど)。'io.ratpack.ratpack-groovy'
プラグインは、暗黙的に 'io.ratpack.ratpack-java'
プラグインを適用することに注意してください。
2.23 Ratpack の依存関係
Ratpack 拡張ライブラリに依存するには、通常の実装依存関係として追加するだけです…
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath "io.ratpack:ratpack-gradle:2.0.0-rc-1"
}
}
apply plugin: "io.ratpack.ratpack-groovy"
repositories {
mavenCentral()
}
dependencies {
implementation ratpack.dependency("dropwizard-metrics")
}
ratpack.dependency("dropwizard-metrics")
を使用することは、"io.ratpack:ratpack-dropwizard-metrics:«ratpack-gradle 依存関係のバージョン»"
と同等です。これは、コアディストリビューションの一部である依存関係を追加する推奨方法です。
'io.ratpack.ratpack-java'
プラグインは、次の暗黙的な依存関係を追加します
ratpack-core
- implementationratpack-test
- testImplementation
'io.ratpack.ratpack-groovy'
プラグインは、次の暗黙的な依存関係を追加します
ratpack-groovy
- implementation (ratpack-core
に依存)ratpack-groovy-test
- testImplementation (ratpack-test
に依存)
利用可能なライブラリは、search.maven.org から参照できます。すべての Ratpack jar は Maven Central に公開されています。
3.23 「application」プラグイン
'ratpack-java'
と 'ratpack-groovy'
の両方のプラグインは、コア Gradle の 'application'
プラグインも適用します。このプラグインは、ソフトウェアのスタンドアロン実行可能ディストリビューションを作成する機能を提供します。これは、Ratpack アプリケーションの推奨されるデプロイ形式です。
'application'
プラグインは、アプリケーションのメインクラス (つまり、エントリポイント) を指定する必要があります。Gradle ビルドファイルで 'mainClassName'
属性を、Ratpack サーバーを構成する 'static void main(String[] args)'
メソッドを含むクラスの完全修飾クラス名に設定する必要があります。これは、'ratpack-groovy'
プラグインによって GroovyRatpackMain
に事前構成されています。カスタムのエントリポイントを使用したい場合は、これを変更できます ('application'
プラグインのドキュメントを参照してください)。
4.23 「shadow」プラグイン
'ratpack-java'
と 'ratpack-groovy'
の両方のプラグインは、サードパーティの 'shadow'
プラグインの統合サポートを提供しています。このプラグインは、Ratpack アプリケーションとコンパイル時および実行時の依存関係を含む自己完結型の「fat-jar」を作成する機能を提供します。
プラグインは、'shadow'
プラグインの適用に反応し、追加のタスク依存関係を構成します。プラグインは 'shadow'
プラグインを適用せず、互換性の理由から、依存関係として 'shadow'
のバージョンを提供しません。
'shadow'
統合を使用するには、プロジェクトに依存関係を含め、プラグインを適用する必要があります。
buildscript {
repositories {
mavenCentral()
gradlePluginPortal()
}
dependencies {
classpath "io.ratpack:ratpack-gradle:2.0.0-rc-1"
classpath 'gradle.plugin.com.github.johnrengelman:shadow:7.1.2'
}
}
apply plugin: "io.ratpack.ratpack-java"
apply plugin: 'com.github.johnrengelman.shadow'
repositories {
mavenCentral()
}
'shadow'
プラグインの最新バージョンは、プロジェクトの Github ページにあります。
これで、ビルドを実行して fat-jar を生成できるようになりました。
./gradlew shadowJar
5.23 ベースディレクトリ
ベースディレクトリは、事実上アプリケーションのファイルシステムのルートです。ビルド時、これは事実上アプリケーションのメインリソースのセット (つまり、src/main/resources
) です。Ratpack プラグインは、補完的なメインリソースのソース src/ratpack
を追加します。このディレクトリを使用しない場合は、代わりに src/main/resources
を使用するか、Gradle ビルドの ratpack.baseDir
プロパティを使用して場所を変更できます。
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath "io.ratpack:ratpack-gradle:2.0.0-rc-1"
}
}
apply plugin: "io.ratpack.ratpack-groovy"
repositories {
mavenCentral()
}
ratpack.baseDir = file('ratpack')
ディストリビューションとしてパッケージ化する場合、プラグインは、プロジェクトのメインリソース *すべて* を含む app
というディレクトリをディストリビューション内に作成します。このディレクトリは、起動スクリプトを介してアプリが起動されるときにクラスパスに *prepend* されます。これにより、アプリケーションは、JAR からその場で解凍するのではなく、ベースディレクトリからファイルをディスクから直接読み取ることができます。これはより効率的です。
詳細については、起動を参照してください。
1.5.23 「ratpack.groovy」スクリプト
'ratpack-groovy'
プラグインは、メインアプリケーション定義が ベースディレクトリ内 の ratpack.groovy
または Ratpack.groovy
にあることを想定しています。デフォルトでは、src/main/resources
および src/ratpack
を効果的に検索します。このファイルは、アプリケーションがこのファイルのコンパイルを管理するため、src/main/groovy
に配置する *べきではありません*。したがって、コンパイルされた形式ではなく、ソース形式 (つまり、.groovy
ファイル) でクラスパスにある必要があります。
このファイルの内容の詳細については、Groovy を参照してください。
6.23 アプリケーションの実行
'application'
プラグインは、Ratpack アプリケーションを起動するための 'run'
タスクを提供します。これは、コア Gradle の JavaExec
タイプのタスクです。'ratpack-java'
プラグインは、この 'run'
タスクを、システムプロパティ 'ratpack.development'
を true
に設定して起動するように構成します。
開発時の実行のために追加のシステムプロパティを設定する場合は、このタスクを構成できます…
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath "io.ratpack:ratpack-gradle:2.0.0-rc-1"
}
}
apply plugin: "io.ratpack.ratpack-java"
repositories {
mavenCentral()
}
run {
systemProperty "app.dbPassword", "secret"
}
1.6.23 開発時のリロード
Ratpack Gradle プラグインは、Gradle の Continuous Build 機能と統合されています。これを活用するには、--continuous
(または -t
) 引数を指定して run
タスクを実行できます。
ソースまたはリソースに加えられた変更は、コンパイルおよび処理され、その結果としてアプリケーションが *リロード* されます。
2.6.23 「shadow」プラグインを使用した実行
プロジェクトに適用されている場合、'shadow'
プラグインは、fat-jar から Ratpack アプリケーションを起動するための 'runShadow'
タスクを提供します。'run'
タスクと同様に、これはコア Gradle の JavaExec
タイプのタスクです。'shadow'
プラグインは、java -jar <path/to/shadow-jar.jar>
コマンドを使用してプロセスを開始するようにこの 'runShadow'
タスクを構成します。
アプリケーションはパッケージ化された jar ファイルから実行されているため、'runShadow'
タスクによるクラスのリロードはサポートされていません。
追加のシステムプロパティまたは JVM オプションをこのタスクで構成できます…
buildscript {
repositories {
mavenCentral()
gradlePluginPortal()
}
dependencies {
classpath "io.ratpack:ratpack-gradle:2.0.0-rc-1"
classpath "gradle.plugin.com.github.johnrengelman:shadow:7.1.2"
}
}
apply plugin: "io.ratpack.ratpack-java"
apply plugin: "com.github.johnrengelman.shadow"
repositories {
mavenCentral()
}
runShadow {
systemProperty "app.dbPassword", "secret"
}
24 Heroku へのデプロイ
Heroku は、スケーラブルな多言語クラウドアプリケーションプラットフォームです。これにより、選択した言語でアプリケーションを作成することに集中し、サーバー、ロードバランシング、ログ集約などを手動で管理することなく、簡単にクラウドにデプロイできます。Heroku は、JVM アプリケーションに対する一般的なサポートを超える、特別な Ratpack サポートを持っておらず、必要もありません。Heroku は、Postgres、Redis、Memcache、RabbitMQ、New Relic など、多くの要素を備えた豊富なプラットフォームです。これは、Ratpack アプリケーションを提供するための魅力的なオプションです。
Heroku へのデプロイは通常、ソース形式で行われます。デプロイは、CI パイプラインの最後に Git プッシュを実行するだけで簡単に行えます。drone.io や Travis-CI (その他) など、多くの一般的なクラウド CI ツールは、Heroku へのプッシュを便利にサポートしています。
Heroku を初めて使用する場合は、Heroku クイックスタートと Buildpack ドキュメントを読むことをお勧めします。この章の残りの部分では、Ratpack アプリケーションを Heroku にデプロイするための要件と必要な構成について概説します。
1.24 Gradle ベースのビルド
Ratpack アプリケーションは任意のビルドシステムでビルドできますが、Ratpack チームは Gradle を推奨しています。Heroku は、Gradle buildpack を介して Gradle をネイティブにサポートしており、Ratpack Gradle プラグインとうまく連携します。
すべての Gradle プロジェクトは、Gradle Wrapper を使用する必要があります。ラッパースクリプトがプロジェクトに存在する場合、Heroku はプロジェクトが Gradle でビルドされていることを検出します。
1.1.24 ビルド
Gradle buildpack は、Ratpack が使用されていることを検出すると、デフォルトで ./gradlew installDist -x test
を呼び出します。installDist
タスクは、Ratpack Gradle プラグイン (Gradle 2.3 より前の installApp
) によって追加され、デフォルトで動作するはずです。これにより、アプリケーションがビルドされ、ディレクトリ build/install/«project name»
にインストールされます。
別のタスクを実行する必要がある場合は、build.gradle
に stage
タスクを追加できます。一般的な stage
タスクは次のようになります
task stage {
dependsOn clean, installDist
}
stage
タスクが存在する場合、Heroku はデフォルトのタスクの代わりにこれを実行します。
1.1.1.24 プロジェクト名の設定
デフォルトでは、Gradle はプロジェクトのディレクトリ名をプロジェクトの名前として使用します。Heroku (および一部の CI サーバー) では、プロジェクトはランダムに割り当てられた名前の一時ディレクトリにビルドされます。プロジェクトが一貫した名前を使用するようにするには、プロジェクトのルートにある settings.gradle
に宣言を追加します
rootProject.name = "«project name»"
これは、すべての Gradle プロジェクトに適した方法です。
2.1.24 実行 (Procfile)
デフォルトでは、Heroku は次のスクリプトを実行してアプリを起動します
build/install/«project name»/bin/«project name»
アプリケーションのルートに Procfile
を作成し、Heroku がアプリケーションの起動に使用する必要があるコマンドを web:
で始まるように指定することで、このコマンドをカスタマイズできます。
3.1.24 構成
Heroku にデプロイされたアプリケーションの環境を構成するには、いくつかの方法があります。これらのメカニズムを使用して、環境変数や JVM システムプロパティを設定し、アプリケーションを構成することができます。
ratpack
および ratpack-groovy
Gradle プラグインを使用する場合に使用されるアプリケーションのエントリポイントは、JVM システムプロパティを使用して ServerConfig
に貢献することをサポートしています (詳細については、起動の章を参照してください)。Ratpack Gradle プラグインによって作成されたスタータースクリプトは、標準の JAVA_OPTS
環境変数とアプリ固有の «PROJECT_NAME»_OPTS
環境変数をサポートしています。アプリケーション名が foo-Bar
の場合、環境変数は FOO_BAR_OPTS
という名前になります。
これをすべてまとめる 1 つの方法は、env
を介してアプリケーションを起動することです
web: env "FOO_BAR_OPTS=-Dapp.dbPassword=secret" build/install/«project name»/bin/«project name»
Heroku はプラットフォームの 便利なデフォルト を設定するため、JAVA_OPTS
を使用しないことをお勧めします。
別の方法は、config vars を使用することです。Procfile を介して環境を設定する利点は、この情報がバージョン管理されたソースツリーにあることです。config vars を使用する利点は、Heroku で表示/変更する権限を持つユーザーのみが使用できることです。パスワードのような機密情報には config vars を設定し、Procfile でそれらを参照することで、両方の方法を組み合わせることが可能です。
web: env "FOO_BAR_OPTS=-Dapp.dbPassword=$SECRET_DB_PASSWORD" build/install/«project name»/bin/«project name»
これで、ソースツリーでどのプロパティと環境変数が設定されているかを簡単に確認できますが、機密値は Heroku 管理ツールを介してのみ表示されます。
2.24 その他のビルドツールとバイナリデプロイメント
Ratpack プロジェクトは、他のビルドツールとの「公式な」統合を提供していません。ただし、Heroku 用の Ratpack アプリケーションをビルドするために好きなツールを使用したり、バイナリ形式でデプロイしたりすることは非常に可能です。
Heroku 環境でコンパイル済みの Ratpack アプリケーション (別のビルドツールでビルドするか、バイナリデプロイメントによるか) が得られたら、java
を直接使用してアプリケーションを起動できます。
web: java ratpack.groovy.GroovyRatpackMain
Ratpack アプリケーションの起動の詳細については、起動の章を参照してください。
3.24 一般的な考慮事項
1.3.24 ポート
Herokuは、各アプリケーションに一時的なポート番号を割り当て、PORT
環境変数で利用できるようにします。Ratpackは、ratpack.port
システムプロパティが設定されていない場合、デフォルトでこの環境変数を尊重します。
25 ロギング
RatpackはロギングにSLF4Jを使用しており、これにより、コンパイル時に好みのロギングライブラリを簡単にバインドできます。
ライブラリのオプションには以下が含まれます。
- No-Op - すべてのログを破棄します(デフォルト)
- Log4J
Log4Jでは、すべてのロガーを非同期にすることで、完全なノンブロッキング構成が可能です。これを実現する最も一貫した方法は、システムプロパティ
-Dlog4j2.contextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
を提供することです。Log4Jのシステムプロパティは、アプリケーションのクラスパスにあるlog4j2.component.properties
ファイルでも定義できます。このプロパティを設定する前に、トレードオフを慎重に確認してください。Log4Jでは、同期ロガーと非同期ロガーの混在構成も可能です。 - Logback - 「メモリと計算のオーバーヘッドがゼロ」のネイティブSLF4J実装
Logbackは、ブロッキングアペンダーの周りにノンブロッキングアペンダーを提供します。それらを適切に構成する方法の詳細については、Logbackマニュアルを確認してください。
- Java Util Logging
- Simple - INFOレベル以上のメッセージをSystem.errに出力します
- Jakarta Commons Logging
依存関係として1つのロギングライブラリを追加し、SLF4J構文を使用してログを記録します。現在別のロギングライブラリを使用している場合は、SLF4Jが移行を自動化する移行ツールを提供しています。
JavaおよびGroovyの例を以下に示します。詳細については、SLF4Jマニュアルを参照してください。
1.25 Java
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LogExample {
private final static Logger LOGGER = LoggerFactory.getLogger(LogExample.class);
public void log() {
LOGGER.info("Start logging");
LOGGER.warn("Logging with a {} or {}", "parameter", "two");
LOGGER.error("Log an exception", new Exception("Example exception"));
LOGGER.info("Stop logging");
}
}
2.25 Groovy
import groovy.util.logging.Slf4j
@Slf4j
class LogExample {
void log() {
log.info "Start logging"
log.warn "Logging with a {} or {}", "parameter", "two"
log.debug "Detailed information"
log.info "Stop logging"
}
}
3.25 リクエストロギング
Ratpackは、各リクエストに関する情報をログに記録するメカニズム、RequestLogger
を提供します。リクエストロガーはハンドラーです。リクエストが完了すると、リクエストを通過する各リクエストがログに記録されます。通常、ハンドラーチェーンの早い段階に配置され、すべてのリクエストがログに記録されるようにChain.all(Handler)
メソッドで追加されます。
Ratpackは、RequestLogger.ncsa()
メソッドを提供しており、これはNCSA共通ログ形式でログを記録します。この実装は、ratpack.requests
という名前のslf4jロガーにログを記録します(RequestLogger.ncsa(Logger)
メソッドを使用すると、代替ロガーを指定できます)。
import ratpack.core.handling.RequestLogger;
import ratpack.core.http.client.ReceivedResponse;
import ratpack.test.embed.EmbeddedApp;
import static org.junit.jupiter.api.Assertions.*;
public class Example {
public static void main(String... args) throws Exception {
EmbeddedApp.fromHandlers(c -> c
.all(RequestLogger.ncsa())
.all(ctx -> ctx.render("ok"))
).test(httpClient -> {
ReceivedResponse response = httpClient.get();
assertEquals("ok", response.getBody().getText());
// Check log output: [ratpack-compute-1-1] INFO ratpack.requests - 127.0.0.1 - - [30/Jun/2015:11:01:18 -0500] "GET / HTTP/1.1" 200 2
});
}
}
代替形式のロガーの作成に関する情報については、RequestLogger
のドキュメントを参照してください。
26 Java 9のサポート
これは、初期のJava 9以降のサポートを使用するための既知の問題、注意点、および回避策のリストです。Java 9のサポートはまだ完全ではありませんが、コア機能はいくつかの警告やメッセージが表示される程度で動作するようです。
1.26 既知のライブラリに関する注意点
以下は、RatpackがJava 9でシームレスに実行されるための既知のバージョンの競合または要件です。
ratpack-groovy
には、#1411の問題ごとに、Groovy 2.5.2以上が必要です。ratpack-groovy-test
には、#1411の問題ごとに、Groovy 2.5.2以上が必要です。
2.26 既知の問題
以下は、現時点でのJava 9およびRatpackの基盤となるコンポーネントによって引き起こされる問題です。回避策はそれぞれの場合に提供されます。
3.26 既知のJava 9のエラー/警告メッセージ
以下は、予期されるJava 9以降によって出力されるメッセージです。
- google/guice#1133でのGuiceの不正なリフレクションアクセス
警告:不正なリフレクションアクセス操作が発生しました
警告:com.google.inject.internal.cglib.core.$ReflectUtils$1(file:[ユーザーGradleキャッシュディレクトリ]/modules-2/files-2.1/com.google.inject/guice/[バージョンハッシュ]/[guiceバージョンjar]]による不正なリフレクションアクセス
警告:これをcom.google.inject.internal.cglib.core.$ReflectUtils$1のメンテナーに報告することを検討してください
警告:さらなる不正なリフレクションアクセス操作の警告を有効にするには、–illegal-access=warnを使用してください
警告:今後のリリースでは、すべての不正なアクセス操作が拒否されます
- #1410の問題で説明されているNettyデバッグメッセージ
11:04:44.477 [main] DEBUG i.n.u.i.PlatformDependent0 - -Dio.netty.noUnsafe: false
11:04:44.477 [main] DEBUG i.n.u.i.PlatformDependent0 - Java version: 11
11:04:44.479 [main] DEBUG i.n.u.i.PlatformDependent0 - sun.misc.Unsafe.theUnsafe: available
11:04:44.479 [main] DEBUG i.n.u.i.PlatformDependent0 - sun.misc.Unsafe.copyMemory: available
11:04:44.480 [main] DEBUG i.n.u.i.PlatformDependent0 - java.nio.Buffer.address: available
11:04:44.483 [main] DEBUG i.n.u.i.PlatformDependent0 - direct buffer constructor: unavailable
java.lang.UnsupportedOperationException: Reflective setAccessible(true) が無効です
at io.netty.util.internal.ReflectionUtil.trySetAccessible(ReflectionUtil.java:31)
at io.netty.util.internal.PlatformDependent0$4.run(PlatformDependent0.java:224)
at java.base/java.security.AccessController.doPrivileged(Native Method)
at io.netty.util.internal.PlatformDependent0.(PlatformDependent0.java:218)
at io.netty.util.internal.PlatformDependent.isAndroid(PlatformDependent.java:212)
at io.netty.util.internal.PlatformDependent.(PlatformDependent.java:80)
at io.netty.util.ConstantPool.(ConstantPool.java:32)
at io.netty.util.AttributeKey$1.(AttributeKey.java:27)
at io.netty.util.AttributeKey.(AttributeKey.java:27)
at ratpack.core.server.internal.DefaultRatpackServer.(DefaultRatpackServer.java:69)
at ratpack.core.server.RatpackServer.of(RatpackServer.java:81)
at ratpack.core.server.RatpackServer.start(RatpackServer.java:92)
at ratpack.groovy.GroovyRatpackMain.main(GroovyRatpackMain.java:38)
11:04:44.484 [main] DEBUG i.n.u.i.PlatformDependent0 - java.nio.Bits.unaligned: available, true
11:04:44.485 [main] DEBUG i.n.u.i.PlatformDependent0 - jdk.internal.misc.Unsafe.allocateUninitializedArray(int): unavailable
java.lang.IllegalAccessException: class io.netty.util.internal.PlatformDependent0$6 はモジュール java.base が jdk.internal.misc を名前のないモジュール @366647c2 にエクスポートしないため、クラス jdk.internal.misc.Unsafe(モジュール java.base 内)にアクセスできません
at java.base/jdk.internal.reflect.Reflection.newIllegalAccessException(Reflection.java:361)
at java.base/java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:591)
at java.base/java.lang.reflect.Method.invoke(Method.java:558)
at io.netty.util.internal.PlatformDependent0$6.run(PlatformDependent0.java:334)
at java.base/java.security.AccessController.doPrivileged(Native Method)
at io.netty.util.internal.PlatformDependent0.(PlatformDependent0.java:325)
at io.netty.util.internal.PlatformDependent.isAndroid(PlatformDependent.java:212)
at io.netty.util.internal.PlatformDependent.(PlatformDependent.java:80)
at io.netty.util.ConstantPool.(ConstantPool.java:32)
at io.netty.util.AttributeKey$1.(AttributeKey.java:27)
at io.netty.util.AttributeKey.(AttributeKey.java:27)
at ratpack.core.server.internal.DefaultRatpackServer.(DefaultRatpackServer.java:69)
at ratpack.core.server.RatpackServer.of(RatpackServer.java:81)
at ratpack.core.server.RatpackServer.start(RatpackServer.java:92)
at ratpack.groovy.GroovyRatpackMain.main(GroovyRatpackMain.java:38)
11:04:44.485 [main] DEBUG i.n.u.i.PlatformDependent0 - java.nio.DirectByteBuffer.(long, int): unavailable
11:04:44.485 [main] DEBUG i.n.u.internal.PlatformDependent - sun.misc.Unsafe: available
関連プロジェクト
これは、Ratpackに関連するサードパーティプロジェクトの網羅的なリストではありません。
1.27 アプリケーションの例
以下のプロジェクトは、Ratpackコアチームによってシンプルな例として管理されています。
- Books - イディオム的なRatpackデモアプリ
2.27 代替言語実装
以下は、JavaおよびGroovy以外の言語を使用して実装されたRatpackのプロジェクトと例です。
- Catacumba - Ratpack/Netty上に構築されたClojure用のノンブロッキングWebツールキット。
- Kotlinの例 - Kotlinで記述されたアプリケーションの例。
- Ratpack JRubyの例 - JRubyで記述されたRatpackアプリケーションの例
3.27 サードパーティモジュール
以下のサードパーティプロジェクトは、Ratpackプロジェクトに追加機能を提供します。
- ベアラートークン認証 - ベアラートークン認証を実行するための標準ハンドラー。
- Cassandraモジュール - Cassandraデータストアを操作するための統合。
- Ratpack Pac4j - Pac4j 2.x+を使用した認証と承認。
28 Ratpackプロジェクト
1.28 クレジット
Ratpackは、以下の人々の協力によって実現しました。
1.1.28 アクティブなプロジェクトメンバー
以下の人々は、現在Ratpackに積極的に時間を費やしています。
- David Carr
- Luke Daley
- Marcin Erdmann
- Rob Fletcher
- Rus Hart
- Danny Hyun
- Dan Woods
- Jeff Beck
- John Engelman
2.1.28 コントリビューター
以下の人々は、多大な貢献をしてくれました。
- Tim Berglund
- Peter Ledbrook
- Marco Vermeulen
- Paul Fairless
- Tomás Lin
- Kevin Marquardsen
- Stefano Gualdi
- Ben Navetta
- Bobby Warner
- Jim White
- Dimitris Zavaliadis
- Craig Burke
- Andrey Adamovich
- Tim Yates
- Brett Wooldridge
- Roy Schumacher
- Chip McCormick
- Álvaro Sánchez-Mariscal
- Cihat Keser
- Cédric Champeau
- Kevin Greene
- David Tiselius
- Andre Steingress
- Gareth Davis
- Lari Hotari
- Massimo Lusetti
- Rob Zienert
- Tom Dunstan
- Wilson MacGyver
- Jörn Huxhorn
- Robert Zakrzewski
- Glenn Saqui
- Jeff Blaisdell
- Kyle Boon
- Joe Kutner
- David Estes
- Glen Schrader
- Jason Winnebeck
- Victor Vu
- Danny Kirchmeier
- Tom Akehurst
- Steve Anderson
- Dave Syer
- João Cabrita
- Matt Yott
- Daniel Lopes
- Daniel Raniz
- Martin Lundvall
- Fabian Förster
- Guillaume Laforge
- Ari Becker
- James Lee
- Bob wen
- Edinson Padrón
- Dan Maas
- Gus Power
- Rahul Somasunderam
- Spencer Allain
- Phill Barber
- Sebastien Bonnet
- Alex Edwards
- Ho Yan Leung
- Felipe Fernández
- Nick Pocock
- Timur Salyakhutdinov
- Matthew Kavanagh
- Jason Terhune-Wold
- Jon Bevan
- Szymon Stępniak
- James Westover
- Javier Salinas
- Jordi Gerona
- Kristin Clemens
- Jonathan Leitschuh
- Roger Pereira
3.1.28 過去のプロジェクトメンバー
以下の人々は、過去にRatpackに時間を費やしていましたが、現在は積極的に取り組んでいません。
2.28 このマニュアルについて
1.2.28 リソース
1.1.2.28 ライブラリ
- Prism by Lea Verou
- Normalize.css by Nicolas Gallagher
- Modernizr
- Breakpoint by Mason Wendell and Sam Richard
2.1.2.28 フォント
Webフォントは、Google Web Fontsによって提供されています。
- Merriweather and Merriweather Sans by Eben Sorkin
- Engagement by Astigmatic One Eye Typographic Institute
3.1.2.28 画像
- Hat designed by Adhara Garcia from The Noun Project
- Microphone designed by Cris Dobbins from The Noun Project
- ウイスキー (デザイン: Anthony Lui、The Noun Projectより)
- レトロスターバースト3 (Velma’s Retro Clip Art and Imagesより、最終確認URL: http://free-retro-graphics.com/2011/02/retro-starburst-3/)
- ギザギザのエッジのソーシャルメディアアイコン (Geek Fairyより)