WildFly(RESTEasy) で JAX-RS + JSONP のサンプル
@nekop さんがサンプル書いてくれました。ありがとうございます!
java-examples/jaxrs-jsonp at master · nekop/java-examples · GitHub
WildFly-8.2.0.Final で試しましたがこれで動きました。
annotations="true" で jackson-provider を指定する必要があります。
Enable JSONP · 30dc57c · nekop/java-examples · GitHub
<jboss-deployment-structure> <deployment> <dependencies> <module name="org.jboss.resteasy.resteasy-jackson-provider" annotations="true"/> </dependencies> </deployment> </jboss-deployment-structure>
jboss-deployment-structure.xml は ここらへんを参考に
Class Loading in WildFly - WildFly 8 - Project Documentation Editor
RESTEasy は 2.3.6.Final から JSONP 形式をサポートしたようです。
http://docs.jboss.org/resteasy/docs/2.3.6.Final/userguide/html/json.html#JSONP_Support
それまでは JSONP がサポートされていなかったので、Servlet Filter とかで何とかしてあげる必要がありました。
java - How enable JSONP in RESTEasy? - Stack Overflow
ドキュメントの最新 3.0.9.Final*1 では http://docs.jboss.org/resteasy/docs/3.0.9.Final/userguide/html/json.html#JSONP_Support
org.jboss.resteasy.plugins.providers.jackson.JacksonJsonpInterceptor で出来るようになってるよと書かれています。
今回の問題は、WildFly 上の RESTEasy でどうやって JacksonJsonpInterceptor を有効にするのかが焦点でした。
ここからは試した内容を書いてきます。*2
JAX-RS のリソースクラスはこれを使います。
@Path("hello") public class HelloResource { @GET @Produces({ MediaType.APPLICATION_JSON }) public RestMessage hello() { System.out.println("HelloResource#hello"); return new RestMessage("Hello"); } }
この状態で JSON で返すリクエスト(/hello) は正常に動きます。
JSONP で返すリクエスト(/hello?calback=hoge) でも JSON と同じ 「{"messaage":"Hello"}」で返ってきてしまいます。
JSONP で投げた時は、「hoge({"messaage":"Hello"})」の形で返ってきてくれるのが期待している結果です。
resteasy-jackson-provider を 明示的に有効にする (annotations 指定無し)
WildFly では、JacksonJsonpInterceptor が含まれている org.jboss.resteasy.resteasy-jackson-provider モジュールがあります。
これを明示的に有効にして試してみましたが結果は変わらず。
jboss-deployment-structure.xml
<?xml version='1.0' encoding='UTF-8'?> <jboss-deployment-structure> <deployment> <dependencies> <module name="org.jboss.resteasy.resteasy-jackson-provider" /> </dependencies> </deployment> </jboss-deployment-structure>
結果的にこれが一番近かったのですが、ここからどんどん迷走していきます。。
MediaType を application/javascript にしてみる
@Produces({ "application/javascript" }) にしてみるとそんな MessgeBodyWriter 無いよとエラーが出ました。
WARN [org.jboss.resteasy.core.ExceptionHandler] (default task-17) Failed executing GET /hello: org.jboss.resteasy.core.NoMessageBodyWriterFoundFailure: Could not find MessageBodyWriter for response object of type: jaxrsjsonp.RestMessage of media type: application/javascript
org.jboss.resteasy.reateasy-jackson2-provider にしてみる
resteasy-jackson2-provider にしてみたらいけるかと試してみましたが、結果変わらず。
<?xml version='1.0' encoding='UTF-8'?> <jboss-deployment-structure> <deployment> <exclusions> <module name="org.jboss.resteasy.resteasy-jackson-provider" /> </exclusions> <dependencies> <module name="org.jboss.resteasy.resteasy-jackson2-provider" /> </dependencies> </deployment> </jboss-deployment-structure>
war に org.jboss.resteasy.reateasy-jackson2-provider を追加する
WildFly のモジュールでは無く、アプリの war に jackson2-provider を入れてみました。
これが結構不思議な動きをします。
<?xml version='1.0' encoding='UTF-8'?> <jboss-deployment-structure> <deployment> <exclusions> <module name="org.jboss.resteasy.resteasy-jackson-provider" /> <module name="org.jboss.resteasy.resteasy-jackson2-provider" /> </exclusions> </deployment> </jboss-deployment-structure>
※war に突っ込んだせいでは無く、jackson2-provider の動きのようです。
MediaType.APPLICATION_JSON(application/json) のとき
JSON で返すリクエスト(/hello) では正常に動きます。
JSONP で返すリクエスト(/hello?calback=hoge) では、レスポンスは JSONP の形で返ってきます*3が、サーバー側でエラーが出ます。
ERROR [io.undertow.request] (default task-19) UT005023: Exception handling request to /jaxrsjsonp/api/hello: org.jboss.resteasy.spi.UnhandledException: Response is committed, can't handle exception at org.jboss.resteasy.core.SynchronousDispatcher.writeException(SynchronousDispatcher.java:148) [resteasy-jaxrs-3.0.10.Final.jar:] at org.jboss.resteasy.core.SynchronousDispatcher.writeResponse(SynchronousDispatcher.java:432) [resteasy-jaxrs-3.0.10.Final.jar:] at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:376) [resteasy-jaxrs-3.0.10.Final.jar:] at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:179) [resteasy-jaxrs-3.0.10.Final.jar:] at org.jboss.resteasy.plugins.server.servlet.ServletContainerDispatcher.service(ServletContainerDispatcher.java:220) [resteasy-jaxrs-3.0.10.Final.jar:] at org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:56) [resteasy-jaxrs-3.0.10.Final.jar:] at org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:51) [resteasy-jaxrs-3.0.10.Final.jar:] at javax.servlet.http.HttpServlet.service(HttpServlet.java:790) [jboss-servlet-api_3.1_spec-1.0.0.Final.jar:1.0.0.Final] at io.undertow.servlet.handlers.ServletHandler.handleRequest(ServletHandler.java:85) [undertow-servlet-1.1.0.Final.jar:1.1.0.Final] at io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:61) [undertow-servlet-1.1.0.Final.jar:1.1.0.Final] at io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36) [undertow-servlet-1.1.0.Final.jar:1.1.0.Final] at org.wildfly.extension.undertow.security.SecurityContextAssociationHandler.handleRequest(SecurityContextAssociationHandler.java:78) at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43) [undertow-core-1.1.0.Final.jar:1.1.0.Final] at io.undertow.servlet.handlers.security.SSLInformationAssociationHandler.handleRequest(SSLInformationAssociationHandler.java:131) [undertow-servlet-1.1.0.Final.jar:1.1.0.Final] at io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler.handleRequest(ServletAuthenticationCallHandler.java:56) [undertow-servlet-1.1.0.Final.jar:1.1.0.Final] at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43) [undertow-core-1.1.0.Final.jar:1.1.0.Final] at io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java:45) [undertow-core-1.1.0.Final.jar:1.1.0.Final] at io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java:63) [undertow-servlet-1.1.0.Final.jar:1.1.0.Final] at io.undertow.security.handlers.AuthenticationMechanismsHandler.handleRequest(AuthenticationMechanismsHandler.java:58) [undertow-core-1.1.0.Final.jar:1.1.0.Final] at io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler.handleRequest(CachedAuthenticatedSessionHandler.java:70) [undertow-servlet-1.1.0.Final.jar:1.1.0.Final] at io.undertow.security.handlers.SecurityInitialHandler.handleRequest(SecurityInitialHandler.java:76) [undertow-core-1.1.0.Final.jar:1.1.0.Final] at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43) [undertow-core-1.1.0.Final.jar:1.1.0.Final] at org.wildfly.extension.undertow.security.jacc.JACCContextIdHandler.handleRequest(JACCContextIdHandler.java:61) at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43) [undertow-core-1.1.0.Final.jar:1.1.0.Final] at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43) [undertow-core-1.1.0.Final.jar:1.1.0.Final] at io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:261) [undertow-servlet-1.1.0.Final.jar:1.1.0.Final] at io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:247) [undertow-servlet-1.1.0.Final.jar:1.1.0.Final] at io.undertow.servlet.handlers.ServletInitialHandler.access$000(ServletInitialHandler.java:76) [undertow-servlet-1.1.0.Final.jar:1.1.0.Final] at io.undertow.servlet.handlers.ServletInitialHandler$1.handleRequest(ServletInitialHandler.java:166) [undertow-servlet-1.1.0.Final.jar:1.1.0.Final] at io.undertow.server.Connectors.executeRootHandler(Connectors.java:197) [undertow-core-1.1.0.Final.jar:1.1.0.Final] at io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:759) [undertow-core-1.1.0.Final.jar:1.1.0.Final] at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [rt.jar:1.8.0_11] at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [rt.jar:1.8.0_11] at java.lang.Thread.run(Thread.java:745) [rt.jar:1.8.0_11] Caused by: java.io.IOException: UT010029: Stream is closed at io.undertow.servlet.spec.ServletOutputStreamImpl.write(ServletOutputStreamImpl.java:136) [undertow-servlet-1.1.0.Final.jar:1.1.0.Final] at org.jboss.resteasy.plugins.server.servlet.HttpServletResponseWrapper$DeferredOutputStream.write(HttpServletResponseWrapper.java:46) [resteasy-jaxrs-3.0.10.Final.jar:] at org.jboss.resteasy.util.CommitHeaderOutputStream.write(CommitHeaderOutputStream.java:71) [resteasy-jaxrs-3.0.10.Final.jar:] at sun.nio.cs.StreamEncoder.writeBytes(StreamEncoder.java:221) [rt.jar:1.8.0_11] at sun.nio.cs.StreamEncoder.implFlushBuffer(StreamEncoder.java:291) [rt.jar:1.8.0_11] at sun.nio.cs.StreamEncoder.implFlush(StreamEncoder.java:295) [rt.jar:1.8.0_11] at sun.nio.cs.StreamEncoder.flush(StreamEncoder.java:141) [rt.jar:1.8.0_11] at java.io.OutputStreamWriter.flush(OutputStreamWriter.java:229) [rt.jar:1.8.0_11] at org.jboss.resteasy.plugins.providers.jackson.Jackson2JsonpInterceptor.aroundWriteTo(Jackson2JsonpInterceptor.java:111) [resteasy-jackson2-provider-3.0.10.Final.jar:] at org.jboss.resteasy.core.interception.AbstractWriterInterceptorContext.proceed(AbstractWriterInterceptorContext.java:122) [resteasy-jaxrs-3.0.10.Final.jar:] at org.jboss.resteasy.security.doseta.DigitalSigningInterceptor.aroundWriteTo(DigitalSigningInterceptor.java:143) [resteasy-crypto-3.0.10.Final.jar:] at org.jboss.resteasy.core.interception.AbstractWriterInterceptorContext.proceed(AbstractWriterInterceptorContext.java:122) [resteasy-jaxrs-3.0.10.Final.jar:] at org.jboss.resteasy.plugins.interceptors.encoding.GZIPEncodingInterceptor.aroundWriteTo(GZIPEncodingInterceptor.java:100) [resteasy-jaxrs-3.0.10.Final.jar:] at org.jboss.resteasy.core.interception.AbstractWriterInterceptorContext.proceed(AbstractWriterInterceptorContext.java:122) [resteasy-jaxrs-3.0.10.Final.jar:] at org.jboss.resteasy.core.ServerResponseWriter.writeNomapResponse(ServerResponseWriter.java:99) [resteasy-jaxrs-3.0.10.Final.jar:] at org.jboss.resteasy.core.SynchronousDispatcher.writeResponse(SynchronousDispatcher.java:427) [resteasy-jaxrs-3.0.10.Final.jar:] ... 32 more
閉じてる Stream に対して何かしようとしてる感じ?
割に結果はちゃんと JSONP の形で受け取れています。
MediaType に application/javascript を追加したとき
@Produces({ "application/javascript", MediaType.APPLICATION_JSON })
JSON/JSONP リクエストともに正常に返ってきました。
サーバー側でもエラー出ていません。
ここで一応 JSONP 返せる事が分かり一安心。
ただ、MediaType.APPLICATION_JSON だけで動いかないのかな?ともう少し試してみました。
war に org.jboss.resteasy.reateasy-jackson-provider を追加する
さっきと同様 WildFly のモジュールを切り、war に jackson-provider を突っ込んでみました。
すると、JSON/JSONP リクエストともに正常に返ってきました。
サーバー側でもエラー出ていません。
しかし、WildFly で持ってるのに war側で入れるのは何か変だよなーとなってるところで @nekop さんがサンプルを提供してくれました。
結論
jboss-deployment-structure 大事!!