Ratpack入門シリーズ
- Ratpack入門 (1) - Ratpackとは
- Ratpack入門 (2) - アーキテクチャー
- Ratpack入門 (3) - hello world 詳解
- Ratpack入門 (4) - ルーティング & 静的コンテンツ
- Ratpack入門 (5) - Json & Registry
- Ratpack入門 (6) - Promise
- Ratpack入門 (7) - Guice & Spring
- Ratpack入門 (8) - セッション
- Ratpack入門 (9) - Thymeleaf
Json
RatpackはJacksonを使った組み込みのJSONサポート機能を持っています。JSONは標準で利用でき、ratpack-jackson
モジュールを別途追加する必要はありません。
@Data
@AllArgsConstructor
@NoArgsConstructor
public static final class User {
private int id;
private String name;
}
public static void main( String[] args ) throws Exception {
Action<Chain> handlers = chain -> {
chain.all( ctx -> {
ctx.byMethod( m -> {
m.get( iCtx -> iCtx.render( Jackson.json( new User( 123, "John" ) ) ) );
m.post( iCtx -> iCtx.parse( Jackson.fromJson( User.class ) ).then( user -> {
iCtx.render( "Received user: " + user );
} ) );
// m.post( iCtx -> iCtx.parse( User.class ).then( user -> {
// iCtx.render( "Received user: " + user );
// } ) );
} );
} );
};
RatpackServer.start( server -> server
.serverConfig( ServerConfig.builder().development( true ).build() )
.handlers( handlers ) );
}
(lombokを使っています)
使い方は簡単で、ratpack.jackson.Jackson
の各メソッドでラップしてあげるだけです。Jacksonを通して、オブジェクトをマーシャル/アンマーシャルしてくれます。Jackson.fromJson()
は省略でき、コメントアウトされている部分で代替できます。これは、生のClass
が渡されたとき用のNoOptParserSupport
が自動的に追加されているためです。
Registry
とRenderer
、Parser
の関係
render()
はObject
を引数にとりますし、parse()
は任意のClass
を引数にとります。このとき、Object
から適切なHTTPレスポンスへ変換される必要があります。以前にも少し述べたように、この変換にはRegistry
に登録されたRenderer
クラス、Parser
クラスが、その要求された方に応じて呼び出されることで、適切に変換されます。ですから、自分で設定したカスタムレンダラーをレジストリに登録することで、独自の変換を実現できます。
ここでは例として、CSVでデータを受け取ったり返却したりする仕組みを作ってみましょう。User
は上記の例と同じです。
まず、Renderer
を定義します。
public static final class UserRenderer extends RendererSupport<User> {
@Override
public void render( Context ctx, User user ) throws Exception {
ctx.getResponse().contentType( "text/plain" );
ctx.render( user.getId() + "," + user.getName() );
}
}
Render
インターフェースのスケルトンであるRendererSupport
を継承します。ハンドラー内のContext.render()
がUser
を引数として呼ばれると、User
クラスを出力できる、このレンダラーが呼ばれます。引数はそのリクエストのContext
と、render()
に渡されたUser
です。大体想像通りかと思います。レスポンスの出力方法は、ハンドラーでやっていた時と同様です。
public static final class UserParser extends NoOptParserSupport {
@Override
protected <T> T parse( Context context, TypedData requestBody, TypeToken<T> type ) throws Exception {
if ( type.getRawType().equals( User.class ) ) {
String[] csv = requestBody.getText().split( "," );
User user = new User( Integer.parseInt( csv[0] ), csv[1] );
return Types.cast( user );
} else {
return null;
}
}
}
レンダラーと同様、パーサーもParser
インターフェースを実装するNoOptParserSupport
スケルトンを継承します。NoOptParserSupport
は、特にparse(Class)
が呼ばれたときのための、最も簡単なパーサーを提供するために使います。TypeToken
、すなわち要求されている方がUser
でなかった場合、このパーサーでは処理できないことを示すためnull
を返します。
public static final class CustomHandler implements Handler {
@Override
public void handle( Context ctx ) throws Exception {
ctx.byMethod( m -> {
m.get( iCtx -> {
iCtx.render( new User( 123, "John" ) );
} );
m.post( iCtx -> {
iCtx.parse( User.class ).then( user -> {
iCtx.render( "Received: " + user );
} );
} );
} );
}
}
レジストリに登録する場合の動きを示すため、ここではハンドラーも独立したクラスにしてみました。
public static void main( String[] args ) throws Exception {
RatpackServer.start( server -> server
.serverConfig( ServerConfig.builder().development( true ) )
.registryOf( spec -> {
spec.add( new UserRenderer() )
.add( new UserParser() )
.add( new CustomHandler() );
} )
.handler( CustomHandler.class ) );
}
Registry
に、作成した独自クラスを登録してあげる必要があります。handler()
メソッドに渡しているのがインスタンスではなくClass
なのに注目してください。
実行結果
$ curl localhost:5050 && echo
123,John
$ curl -X POST localhost:5050 --data "456,Jack" && echo
Received: Sample5_2_Registry.User(id=456, name=Jack)
CSVが独自定義のレンダラー、パーサーを通して処理されているのが確認できました。