基本的な説明
JSONICには、JSONを使ったWebサービスが簡単に構築できるサーブレットが二種類用意されています。
サーブレット | 説明 |
---|---|
RPCServlet | JSON-RPC 1.0/2.0 仕様に対応したRPC(Remote Procedure Call)サービスを構築できます。 |
RESTServlet | GET/POST/PUT/DELETEなどHTTP Methodをベースに操作を行うRESTfullなWebサービスを構築できます。 |
RPCサーブレット
RPCサーブレットは、JSON-RPC 1.0 および JSON-RPC 2.0 の両方をサポートしたWEBサービス構築用サーブレットです。
■ RPCサーブレットの概要
RPCサーブレットを使うと、指定したパスに対しJSONをPOSTすることで、対象クラスのメソッドを呼び出すことができます(GET/PUT/DELETEは無効です)。paramsに指定された配列の値はメソッドの引数に指定された型に従い自動的に変換されます。なお、クラス名はUpperCamel、メソッド名はLowerCamelに自動的に変換されます。そして、実行後、戻り値がJSONに変換されクライアントに返されます。
POST /rpc.json HTTP/1.0 ... Content-Type: application/json { "method": "class.method", "params": [ arg1, arg2, ... ], "id": request_id }
class, methodにはそれぞれ変数の値、argNにはメソッドの引数を設定してください。requesst_idには送受信の同期確認用のキーとしてnull以外の任意の値を設定してください(HTTPでは、送信と受信は同期処理ですのでほとんど意味はありませんが、省略すると通知(Notification)モードとなりレスポンスのメッセージボディが返されませんので必ず値を指定してください)。
例えば、mappingsに "/{package}/{class}.{ext}": "boo.${package}.${class}Service" という指定があった場合、 /foo/woo/rpc.jsonというパスに次のJSONがPOSTすると、boo.foo.woo.CalcServiceクラスのplusメソッドが呼び出されます。
POST /foo/woo/rpc.json HTTP/1.0 ... Content-Type: application/json { "method": "calc.plus", "params": [1,2], "id": 1 }
boo.foo.woo.CalcServiceは次のように実装されていたとします。
package boo.foo.woo; public class CalcService { public int plus(int a, int b) { return a + b; } }
この時、レスポンスのメッセージボディとしては次のような結果が返されます。
HTTP/1.0 200 OK ... Content-Type: application/json { "result": 3, "error": null, "id": 1 }
各パラメータの意味は次の通りです。
方向 | パラメータ | 説明 |
---|---|---|
リクエスト | jsonrpc | JSON-RPC 2.0で接続する時のみ"2.0"を指定します。1.0で接続する時は指定しません。 |
method | メソッド名を指定します。パス変数でclass が指定されている場合はメソッド名を、指定していない場合はクラス名とメソッド名を「class.method 」の形式で指定する必要があります。なお、クラス名はパッケージを含んだ完全名ではなく、後述で設定するマッピングに対応した名前を指定してください。 | |
params | パラメータを指定します。JSON arrayを指定した場合はメソッドの引数に指定された型に変換され引渡されます。JSON-RPC 2.0ではJSON objectを指定できますが、その場合は、第一引数に指定された型に変換され引渡されます。 | |
id | 設定するとレスポンスのid値として同じ値が戻されます。なお、JSON-RPC 1.0ではidがnullの時、2.0ではidを省略すると通知(Notification)モードとなりレスポンスのメッセージボディが返されませんので必ず値を指定してください。 | |
レスポンス | jsonrpc | JSON-RPC 2.0で接続した時のみ"2.0"が返されます。1.0では設定されません。 |
result | 成功した時には、結果が設定されます。JSON-RPC 2.0では成功した時のみ設定されます。 | |
error | エラーの時にはエラー情報が設定されます。JSON-RPC 2.0ではエラーの時のみ設定されます。 | |
id | リクエストで設定されたidの値がそのまま戻されます。 |
■ エラーオブジェクト
RPCサーブレットでエラーが発生した場合にはレスポンスのメッセージボディでクライアントに通知されます。ステータスコードは、エラーの有無に関わらず200 OKが返されます。
HTTP/1.0 200 OK ... Content-Type: application/json { "result": null, "error": { "code": -32600, "message": "Invalid Request.", "data": {} }, "id": 1 }
errorの値には code, message, dataの三つのキーを持つJSON objectが設定されます。codeとmessageについては次表を参照してください。dataには投げられた例外のプロパティがセットされます(ただし、Throwableクラスのプロパティは除外されます)。
エラー内容 | HTTP Status Code | Message Body |
---|---|---|
JSONリクエストがJSON-RPCのリクエストとして不正 | 200 OK | { "code": -32600, "message": "Invalid Request." } |
methodで指定したクラス/メソッドが見つからない(※1) | 200 OK | { "code": -32601, "message": "Method not found." } |
paramsが不適切(※2) | 200 OK | { "code": -32602, "message": "Invalid params." } |
JSONの解析に失敗した | 200 OK | { "code": -32700, "message": "Parse error.", "data": { "columnNumber": エラーが発生した列番号, "errorOffset": エラーが発生した位置, "lineNumber": エラーが発生した行番号 } } |
errorsに設定された例外が発生した | 200 OK | { "code": errorsで設定した値, "message": 例外オブジェクトの単純クラス名 + ": " + getMessage()の値, "data": 例外オブジェクト(ただし、Throwableクラスに定義されているプロパティは除く) } |
その他の例外が発生した | 200 OK | { "code": -32603, "message": "Internal error." } |
■ 設定方法
RPC サーブレットは、web.xmlにRPCServletを指定し、パスとClassのマッピングなどの設定を行うだけです。
<servlet> <servlet-name>rpcServlet</servlet-name> <servlet-class>net.arnx.jsonic.web.RPCServlet</servlet-class> <init-param> <param-name>config</param-name> <param-value> { "debug": true, "mappings": { "/{package}/{class}.json": "sample.web.${package}.service.${class}Service", "/rpc.json": "sample.${class}Service" } } </param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>rpcServlet</servlet-name> <url-pattern>*.json</url-pattern> </servlet-mapping>
configで設定できる値は次の通りです(errorsを除き、RESTServletと同じです)。
キー | 値型 | 説明 |
---|---|---|
container | net.arnx.jsonic.web.Container | クラスのインスタンスを取得するためのコンテナを設定します。デフォルトは、net.arnx.jsonic.web.Container です。 |
encoding | java.lang.String | Request/Responseの文字エンコーディングを設定します。デフォルトはUTF-8です。 |
expire | java.lang.Boolean | クライアントキャッシュを抑制するHTTPヘッダを出力します(Cache-Control: no-cache, Pragma: no-cache, Expires: Tue, 29 Feb 2000 12:00:00 GMT )。デフォルトはtrueです。 |
debug | java.lang.Boolean | デバッグモードの有効/無効を切り替えます。デフォルトはfalseです。 |
mappings | java.util.Map<String, String> | URLパスとクラスのマッピングを行います。
パス中の{name} で囲まれた部分はパス変数として、クラス名の${name} に置換されたりメソッドの引数に設定されます(※3)。また、{name:regex} と記載することで、変数の定義を設定することができます(definitionsより優先します)。 |
definitions | java.util.Map<String, Pattern> | mappings中の変数の定義を正規表現で設定します。設定されない場合は[^/().]+ が設定されたものと扱われます。 |
init | java.lang.String | 処理の実行前に呼び出されるメソッド名を設定します。デフォルトは"init" です。 |
destroy | java.lang.String | 処理の実行後に呼び出されるメソッド名を設定します。デフォルトは"destroy" です。 |
processor | net.arnx.jsonic.JSON | 処理に使用するJSONクラスを設定します。デフォルトではThrowableのメソッドのみ無視するJSONクラスが設定されます。 |
namingConversion | boolean | 呼び出し時のクラス名、メソッド名の変換を行うか否か設定します。デフォルトはtrueです。 |
errors | java.util.Map<Class< extends Exception>, Integer> | Exceptionクラスとエラーコードのマッピングを行います(継承したクラスも対象になります)。 |
JSONIC 1.1 RPCモードからの変更点
- JSON-RPC 2.0に対応しました。
- [不具合] パス変数に「.」を指定できるため、意図せぬクラスの呼び出しが可能になる場合があったため、デフォルトのパターンを
[^/().]+
に変更しました。 - [非互換] メッセージボディにJSONを設定しリクエストする時には、
Content-Type: application/json
の指定が必須になりました。 - [非互換] 各種DI Container用Containerクラスのパッケージを
net.arnx.jsonic.web
からnet.arnx.jsonic.web.extension
に変更しました。 - Containerにリクエストの最初と最後に呼ばれるstartメソッドとendメソッドを追加しました(WebServiceServletでも利用できます)。
- WebServiceServletにあったpreinvokeやpostinvokeがContainerに移動し引数も変更されました。
- errors を設定することで例外とエラーコードのマッピングができるようになりました。
- [非互換] errorsに設定された例外が発生した場合のみdataにオブジェクトを設定するように変更しました。
RESTサーブレット
RESTサーブレットは、GET/POST/PUT/DELETEなどHTTP Methodをベースに操作を行うRESTfullなWebサービス構築用サーブレットです。
■ RESTサーブレットの概要
RESTサーブレットを使うと、GET/POST/PUT/DELETEなどのHTTP Methodに従って、対象となったクラスのメソッドが呼び出されます。その後、戻り値がJSONに変換されクライアントに返されます。
HTTP MethodとJava メソッド名のデフォルトのマッピングは次の通りです(※4)。
HTTP Method | Java メソッド名 | 引数 |
---|---|---|
GET | find | リクエストパラメータを. あるいは[] で区切られた階層構造とみなし引数の型に従い変換し設定されます。 |
POST | create | Content-Typeが「application/json 」の時は、メッセージボディのJSON文字列を引数の型に従い変換し設定されます。Content-Typeが「 application/x-www-form-urlencoded 」の時はリクエストパラメータを. あるいは[] で区切られた階層構造とみなし引数の型に従い変換し設定されます。 |
PUT | update | |
DELETE | delete |
例えば、mappingsに "/{package}/{class}.{ext}": "boo.${package}.${class}Service"
という指定があった場合、 /foo/woo/resource.json
というパスをGETすると、boo.foo.woo.ResourceService
クラスのfind
が呼び出されます。
GET /foo/woo/resource.json HTTP/1.0 ...
boo.foo.woo.ResourceServiceは次のように実装されていたとします。
package boo.foo.woo; public class ResourceService { public Object find(Map params) { List<Map> list = Database.select("select * from resource", params); return list; } }
この時、レスポンスのメッセージボディとしては次のような結果が返されるかもしれません(※5)。
HTTP/1.0 200 OK ... Content-Type: application/json [ { "id": 1, "name": "boo", "age": 10 }, { "id": 2, "name": "foo", "age": 12 }, { "id": 3, "name": "woo", "age": 14 } ]
引数には送信されたデータが指定された型に従い変換され設定されます。引数への設定は、データの送信方法によって次のような違いがあります。
Content Type | Request Type | 説明 |
---|---|---|
application/json | JSON object | パス変数、リクエストパラメータの順に追加されたJSON objectが設定されます(同じキーが複数出現した場合は配列化されます)。 |
JSON array | 送信されたJSON arrayを引数リストとして扱います。なお、第一引数がJSON objectである場合には、上記と同様にパス変数、リクエストパラメータ、第一引数の順でデータが追加されます。 その他の型や第二引数以降はそのまま設定されます。 | |
URLパラメータ application/x-www-form-urlencoded | リクエストパラメータを. あるいは[] で区切られた階層構造とみなし変換したオブジェクトが設定されます。 |
さきほどの例でidが3のデータを指定する場合は次のようにします。
GET /foo/woo/resource.json?id=3 HTTP/1.0 ...
この時、レスポンスのメッセージボディとしては次のような結果が返されるかもしれません。
HTTP/1.0 200 OK ... Content-Type: application/json [ { "id": 3, "name": "woo", "age": 14 } ]
よりREST的にしたいのであれば、mappingsのパスに /{package}/{class}/{id}.{ext}
を定義するなどしてパラメータをURLに含めることなどもできます。
■ エラーオブジェクト
エラーの発生はHTTP Status Codeによりクライアントに通知されます。
エラー内容 | HTTP Status Code | Message Body |
---|---|---|
クラス/メソッドが見つからない(※6) | 404 Not found | |
送信されたJSONの解析/変換に失敗した | 400 Bad request | |
errorsに設定された例外が発生した | errorsで設定したステータスコード | { "name": 例外オブジェクトの単純クラス名 "message": 例外オブジェクトのgetMessage()の値, "data": 例外オブジェクト(ただし、Throwableクラスに定義されているプロパティは除く) } |
その他の例外が発生した | 500 Internal Server Error |
■ 設定方法
REST サーブレットは、web.xmlにRESTServletを指定し、パスとClassのマッピングなどの設定を行うだけです。
<servlet> <servlet-name>restServlet</servlet-name> <servlet-class>net.arnx.jsonic.web.RESTServlet</servlet-class> <init-param> <param-name>config</param-name> <param-value> { "debug": true, "mappings": { "/{package}/{class}/{id:[0-9]+}.json": "sample.web.${package}.service.${class}Service", "/{package}/{class}.json": "sample.web.${package}.service.${class}Service", "/{class}.json": "sample.${class}Service" } } </param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>restServlet</servlet-name> <url-pattern>*.json</url-pattern> </servlet-mapping>
configで設定できる値は次の通りです(method, verbを除き、RPCServletと同じです)。
キー | 値型 | 説明 |
---|---|---|
container | net.arnx.jsonic.web.Container | クラスのインスタンスを取得するためのコンテナを設定します。デフォルトは、net.arnx.jsonic.web.Container です。 |
encoding | java.lang.String | Request/Responseの文字エンコーディングを設定します。デフォルトはUTF-8です。 |
expire | java.lang.Boolean | クライアントキャッシュを抑制するHTTPヘッダを出力します(Cache-Control: no-cache, Pragma: no-cache, Expires: Tue, 29 Feb 2000 12:00:00 GMT )。デフォルトはtrueです。 |
debug | java.lang.Boolean | デバッグモードの有効/無効を切り替えます。デフォルトはfalseです。 |
mappings | java.util.Map<String, String> | URLパスとクラスのマッピングを行います。
パス中の{name} で囲まれた部分はパス変数として、クラス名の${name} に置換されたりメソッドの引数に設定されます(※8)。また、{name:regex} と記載することで、変数の定義を設定することができます(definitionsより優先します)。 |
definitions | java.util.Map<String, Pattern> | mappings中の変数の定義を正規表現で設定します。設定されない場合は[^/().]+ が設定されたものと扱われます。 |
init | java.lang.String | 処理の実行前に呼び出されるメソッド名を設定します。デフォルトは"init" です。 |
destroy | java.lang.String | 処理の実行後に呼び出されるメソッド名を設定します。デフォルトは"destroy" です。 |
processor | net.arnx.jsonic.JSON | 処理に使用するJSONクラスを設定します。デフォルトではThrowableのメソッドのみ無視するJSONクラスが設定されます。 |
namingConversion | boolean | 呼び出し時のクラス名、メソッド名の変換を行うか否か設定します。デフォルトはtrueです。 |
errors | java.util.Map<Class< extends Exception>, Integer> | ExceptionクラスとHTTP Status Codeのマッピングを行います(継承したクラスも対象になります)。 |
method | java.util.Map<String, String> | HTTP Methodに対応するメソッド名を設定します。デフォルトは、{ "GET": "find", "POST": "create", "PUT": "update", "DELETE": "delete" } です。なお、パス変数にmethodが設定されている場合は無視されます。 |
verb | java.util.Set<String> | 使用できるHTTP Methodを制限します。デフォルトは、["HEAD", "GET", "POST", "PUT", "DELETE", "OPTIONS"] です。HEADとOPTIONSを使う場合は、methodも対応付ける必要があります。 |
なお、method, verb
に関しては、mappings
の各データ毎にも設定できます。その場合は次のようにマッピング先をJSON objectにします(マッピング先のプロパティ名はtarget
にしてください)。
"mappings": { "/{package}/{class}.json": { "target": "sample.web.${package}.service.${class}Service", "method": { "GET": "print" }, "verb": [ "GET" ] }, ... }
■ パス変数によるメソッドの指定
本来RESTでは、HTTP Methodで処理が決定されるためRPC的な任意のメソッド呼び出しは推奨されませんが、それでは不便が多いためパス変数にmethod
を指定することで、任意のメソッド呼び出しを可能にしました。
例えば、次のように設定を行うと /foo/calc.sum.json
を呼び出すと sample.web.foo.service.CalcService
の sum
メソッドが呼びだされます。
"mappings": { "/{package}/{class}.{method}.json": "sample.web.${package}.service.${class}Service" }
なお、このような使い方をする際は、verbと組み合わせてHTTP Methodを制限して使うことが推奨されます(GETで更新処理などを行うと、検索エンジンのクロールでデータが削除されるなどの問題が発生する可能性があります)。
■ JSONP - JSON with padding
HTTP MethodがGETの場合、リクエストパラメータとしてcallback=Function名を指定することでJSONPによる返答を返すことができるようになります。
<script type="text/javascript"> function call(value) { alert(value); } </script> ... <script type="text/javascript" src="http://host/hoge.json?callback=call"></script>
■ JSON以外のレスポンス
CSVやXMLなどJSON以外のレスポンスを返したい場合やリダイレクトしたい場合は、処理の最後でHttpServletResponse#flushBuffer()を実行して出力をコミットしてください。ステータスコードやコンテントタイプの設定、JSONの出力などJSONIC側の後続処理を抑制することができます。
public class HogeService { // 自動インジェクション public HttpServletResponse response; // CSVを出力する public void print() throws IOException { response.setCharcterEncoding("MS932"); PrintWriter writer = response.getWriter(); writer.write("a,b,c\r\n"); // バッファをフラッシュして、出力を確定させる response.flushBuffer(); } }
JSONIC 1.1 RESTモードからの変更点
- [不具合] パス変数に「.」を指定できるため、意図せぬクラスの呼び出しが可能になる場合があったため、デフォルトのパターンを
[^/().]+
に変更しました。 - [非互換] メッセージボディにJSONを設定しリクエストする時には、
Content-Type: application/json
の指定が必須になりました。 - [非互換] 各種DI Container用Containerクラスのパッケージを
net.arnx.jsonic.web
からnet.arnx.jsonic.web.extension
に変更しました。 - [非互換] 1.1では、送信したパラメータより引数の多いメソッドのみを検索対象としていましたが、1.2では、送信したパラメータより引数の少ないメソッドのみ検索対象とするよう変更しました。
- Containerにリクエストの最初と最後に呼ばれるstartメソッドとendメソッドを追加しました(WebServiceServletでも利用できます)。
- WebServiceServletにあったpreinvokeやpostinvokeがContainerに移動し引数も変更されました。
- 呼び出し対象のメソッドを変更できるように設定項目にverbを追加しました。
- 任意のメソッドを呼び出すための機能を追加しました。
- [非互換] エラーオブジェクトの形式を変更しました。
- [非互換] errorsに設定された例外が発生した場合のみdataにオブジェクトを設定するように変更しました。
DIコンテナ対応
RPCサーブレット、RESTサーブレットは、内部のコンテナを切り替えることで呼び出し対象のインスタンスを任意のDIコンテナにて管理することが可能です。
JSONICでは、Seasar2、Spring Framework、Google Guiceに対応したContainerを標準添付しています(※1.2よりパッケージ名が変更されています。ご注意ください)。このContainerを利用すると、DI Container上で管理されているコンポーネントをWebServiceとして利用することができるようになります。
<servlet> <servlet-name>rpcServlet or restServlet</servlet-name> <servlet-class>net.arnx.jsonic.web.RPCServlet or RESTServlet</servlet-class> <init-param> <param-name>config</param-name> <param-value> // "container": (net.arnx.jsonic.web.Containerを実装したクラス) // Seasar2対応Container "container": "net.arnx.jsonic.web.extension.S2Container" // Spring Framework対応Container "container": "net.arnx.jsonic.web.extension.SpringContainer" // Google Guice対応Container "container": "net.arnx.jsonic.web.extension.GuiceContainer" ... </param-value> </init-param> </servlet>
デフォルトでは最低限の機能のみ持つnet.arnx.jsonic.web.Containerが使われます。このコンテナが持つ機能は次の通りです。
- オブジェクトはClass#newInstance()により毎回生成されます。
- ログは、ServletContext#log()を使って書き出されます。
- 呼び出し対象となるクラスにinitあるいはdestroyという名前のメソッドがある場合、処理の前後に呼び出します(この機能は全コンテナ共通です)。(※9)
- JSPの暗黙オブジェクトライクなパブリックフィールドベースの簡易DIを提供します。
暗黙オブジェクトは以下のように設定してください。クラスだけでなくフィールド名も合わせる必要があります(デフォルトコンテナのみの機能です)。
public class HogeService { public ServletConfig config; public ServletContext application; public HttpServletRequest request; public HttpServletResponse response; public HttpSession session; }
コンテナ自身にHttpServletRequestとHttpServletResponseのDI機能がないSpringContainerに関しては、setterによるインジェクション機能を提供しています(下記例を参照)。
public class SpringDrivenService {
// setterを用意すると自動で挿入します。プロパティ名を一致させる必要はありません。
public void setRequest(HttpServletRequest request) {
...
}
public void setResponse(HttpServletResponse response) {
...
}
}
処理エラーの取り扱い
処理でエラーが発生した場合、専用のログを取ったり、、トランザクションをロールバックしたいなどあるかもしれません。その場合は、コンテナを継承してexecuteメソッドをオーバーライドします。
public class TransactionalContainer extends Container {
...
// 処理実行時によばれます。
public Object execute(JSON json, Object component, Method method, List<?> params) throws Exception {
Object ret = null;
try {
ret = super.execute(json, component, method, params);
tx.commit();
} catch (Exception e) {
tx.rollback();
throw e;
}
}
}
Gatewayフィルタ
JSONICでは、おまけ機能としてJSONを使ってServletで良く使う各種の機能を実装したFilterを提供しています。JSONICの書式を使えるため手軽に設定が可能です。
最初にマッチしたパスの設定が使われますが、そこで設定が行われなわれていない場合、ルートの設定が初期値として利用されます。パスには正規表現が利用できます。
<filter> <filter-name>Gateway Filter</filter-name> <filter-class>net.arnx.jsonic.web.GatewayFilter</filter-class> <init-param> <param-name>config</param-name> <param-value> // 共通設定 encoding: 'UTF-8' // 文字コード設定 locale: 'en' // Responseのロケールを設定 compression: true // GZip圧縮 // 拡張子がjsonのパスを対象 '.+\.json': { expire: true // クライアントキャッシュを無効化 } // 例:日本向け設定 '/ja/([^.]+)': { forward: '/$1.json' // JSON Web Serviceに転送 encoding: 'SHIFT_JIS' expire: true locale: 'ja-JP' access: ['jpuser'] // アクセス可能なロール } </param-value> </init-param> </filter> <filter-mapping> <filter-name>Gateway Filter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
configで設定できる値は次の通りです。なお、これらの設定はフィルタの設定に関わらず一度だけしか適用されません。
キー | 値型 | 説明 |
---|---|---|
encoding | java.lang.String | Request/Responseの文字エンコーディングを設定します。デフォルトはnullです。 |
compression | java.lang.Boolean | クライアントからAccept-Encoding: gzip or x-gzip が送られる場合、ResponseをGZip圧縮します。 |
expire | java.lang.Boolean | クライアントキャッシュを抑制するHTTPヘッダを出力します(Cache-Control: no-cache, Pragma: no-cache, Expires: Tue, 29 Feb 2000 12:00:00 GMT )。デフォルトはfalse です。 |
forward | java.lang.String | 指定されたパスに転送します(パスはコンテキストパス以下を指定します。正規表現の置換変数が利用できます)。 |
access | java.util.Set<String> | アクセス可能なアプリケーションロールを配列で指定します(認証そのものはコンテナの機能などを使う必要があります)。 |
locale | java.util.Locale | Responseのロケールを設定します。 |
なお、encodingとexpireに関してはRPCサーブレットやRESTサーブレット側にも同様の設定が用意されており、そちら側の設定が優先されます。