From 5512c2d8a143914cebc820df2cdeb4995a04a102 Mon Sep 17 00:00:00 2001 From: kazuhira Date: Sat, 9 Sep 2017 23:20:04 +0900 Subject: [PATCH] add, RESTEasy ClientHttpEngine customize example --- resteasy-customize-http-engine/build.sbt | 43 ++++++ .../project/build.properties | 1 + .../project/plugins.sbt | 0 .../javaee7/resteasy/OkHttpEngine.scala | 75 +++++++++ .../services/javax.ws.rs.ext.Providers | 1 + .../littlewings/javaee7/resteasy/Book.scala | 3 + .../resteasy/RestEasyHttpEngineSpec.scala | 146 ++++++++++++++++++ .../resteasy/ScalaObjectMapperProvider.scala | 19 +++ .../javaee7/resteasy/TestRestServer.scala | 51 ++++++ 9 files changed, 339 insertions(+) create mode 100644 resteasy-customize-http-engine/build.sbt create mode 100644 resteasy-customize-http-engine/project/build.properties create mode 100644 resteasy-customize-http-engine/project/plugins.sbt create mode 100644 resteasy-customize-http-engine/src/main/scala/org/littlewings/javaee7/resteasy/OkHttpEngine.scala create mode 100644 resteasy-customize-http-engine/src/test/resources/META-INF/services/javax.ws.rs.ext.Providers create mode 100644 resteasy-customize-http-engine/src/test/scala/org/littlewings/javaee7/resteasy/Book.scala create mode 100644 resteasy-customize-http-engine/src/test/scala/org/littlewings/javaee7/resteasy/RestEasyHttpEngineSpec.scala create mode 100644 resteasy-customize-http-engine/src/test/scala/org/littlewings/javaee7/resteasy/ScalaObjectMapperProvider.scala create mode 100644 resteasy-customize-http-engine/src/test/scala/org/littlewings/javaee7/resteasy/TestRestServer.scala diff --git a/resteasy-customize-http-engine/build.sbt b/resteasy-customize-http-engine/build.sbt new file mode 100644 index 0000000..257671d --- /dev/null +++ b/resteasy-customize-http-engine/build.sbt @@ -0,0 +1,43 @@ +name := "resteasy-customize-http-engine" + +organization := "org.littlewings" + +version := "0.0.1-SNAPSHOT" + +scalaVersion := "2.12.3" + +updateOptions := updateOptions.value.withCachedResolution(true) + +scalacOptions ++= Seq("-Xlint", "-deprecation", "-unchecked", "-feature") + +libraryDependencies ++= Seq( + // for RESTEasy Client + "org.jboss.resteasy" % "resteasy-client" % "3.1.4.Final" % Compile exclude("junit", "junit"), + "org.jboss.resteasy" % "resteasy-jackson2-provider" % "3.1.4.Final" % Compile, + "com.fasterxml.jackson.module" %% "jackson-module-scala" % "2.8.9" % Compile, + + // dependency retrieve + "org.jboss.resteasy" % "resteasy-jaxrs" % "3.1.4.Final" % Compile, + "org.jboss.logging" % "jboss-logging" % "3.3.0.Final" % Compile, + "org.jboss.logging" % "jboss-logging-annotations" % "2.0.1.Final" % Compile, + "org.jboss.logging" % "jboss-logging-processor" % "2.0.1.Final" % Compile, + "org.apache.httpcomponents" % "httpclient" % "4.5.2" % Compile, + "com.fasterxml.jackson.core" % "jackson-core" % "2.8.9" % Compile, + "com.fasterxml.jackson.core" % "jackson-annotations" % "2.8.9" % Compile, + "com.fasterxml.jackson.core" % "jackson-databind" % "2.8.9" % Compile, + "com.fasterxml.jackson.jaxrs" % "jackson-jaxrs-json-provider" % "2.8.9" % Compile, + "javax.activation" % "activation" % "1.1.1" % Compile, + "org.jboss.spec.javax.servlet" % "jboss-servlet-api_3.1_spec" % "1.0.0.Final" % Compile, + "org.jboss.spec.javax.annotation" % "jboss-annotations-api_1.2_spec" % "1.0.0.Final" % Compile, + "org.jboss.spec.javax.ws.rs" % "jboss-jaxrs-api_2.0_spec" % "1.0.1.Beta1" % Compile, + "commons-io" % "commons-io" % "2.5" % Compile, + "net.jcip" % "jcip-annotations" % "1.0" % Compile, + + // for Customize HttpEngine + "com.squareup.okhttp3" % "okhttp" % "3.9.0" % Compile, + + // for test + "org.jboss.resteasy" % "resteasy-netty4" % "3.1.4.Final" % Test, + "io.netty" % "netty-all" % "4.1.15.Final" % Test, + "org.scalatest" %% "scalatest" % "3.0.4" % Test +) diff --git a/resteasy-customize-http-engine/project/build.properties b/resteasy-customize-http-engine/project/build.properties new file mode 100644 index 0000000..4dbb2dc --- /dev/null +++ b/resteasy-customize-http-engine/project/build.properties @@ -0,0 +1 @@ +sbt.version = 1.0.1 \ No newline at end of file diff --git a/resteasy-customize-http-engine/project/plugins.sbt b/resteasy-customize-http-engine/project/plugins.sbt new file mode 100644 index 0000000..e69de29 diff --git a/resteasy-customize-http-engine/src/main/scala/org/littlewings/javaee7/resteasy/OkHttpEngine.scala b/resteasy-customize-http-engine/src/main/scala/org/littlewings/javaee7/resteasy/OkHttpEngine.scala new file mode 100644 index 0000000..7d21021 --- /dev/null +++ b/resteasy-customize-http-engine/src/main/scala/org/littlewings/javaee7/resteasy/OkHttpEngine.scala @@ -0,0 +1,75 @@ +package org.littlewings.javaee7.resteasy + +import java.io.{ByteArrayOutputStream, InputStream} +import javax.net.ssl.{HostnameVerifier, SSLContext} +import javax.ws.rs.core.MultivaluedMap + +import okhttp3.{MediaType, OkHttpClient, Request, RequestBody} +import org.jboss.resteasy.client.jaxrs.ClientHttpEngine +import org.jboss.resteasy.client.jaxrs.internal.{ClientInvocation, ClientResponse} +import org.jboss.resteasy.util.CaseInsensitiveMap + +import scala.beans.BeanProperty + +class OkHttpEngine extends ClientHttpEngine { + val okHttpClient: OkHttpClient = new OkHttpClient + + @BeanProperty + var sslContext: SSLContext = _ + + @BeanProperty + var hostnameVerifier: HostnameVerifier = _ + + override def invoke(request: ClientInvocation): ClientResponse = { + val okHttpRequestBuilder = + new Request.Builder() + .url(request.getUri.toURL) + + val body = Option(request.getEntity).map { _ => + val baos = new ByteArrayOutputStream + request.getDelegatingOutputStream.setDelegate(baos) + request.writeRequestBody(baos) + baos.close() + baos.toByteArray + } + + val headers = request.getHeaders.asMap + headers.entrySet.forEach(header => header.getValue.forEach(okHttpRequestBuilder.addHeader(header.getKey, _))) + + okHttpRequestBuilder.method( + request.getMethod, + body.map(RequestBody.create(MediaType.parse(request.getHeaders.getMediaType.toString), _)).getOrElse(null) + ) + + val okHttpRequest = okHttpRequestBuilder.build + + val okHttpResponse = okHttpClient.newCall(okHttpRequest).execute() + + val response = new ClientResponse(request.getClientConfiguration) { + var inputStream: InputStream = _ + + override def releaseConnection(): Unit = okHttpResponse.close() + + override def setInputStream(is: InputStream): Unit = inputStream = is + + override def getInputStream = { + if (inputStream == null) { + inputStream = okHttpResponse.body().byteStream() + } + + inputStream + } + } + + response.setStatus(okHttpResponse.code()) + + val responseHeaders: MultivaluedMap[String, String] = new CaseInsensitiveMap + val headerNames = okHttpResponse.headers.names + headerNames.forEach(name => okHttpResponse.headers(name).forEach(value => responseHeaders.add(name, value))) + + response.setHeaders(responseHeaders) + response + } + + override def close(): Unit = () +} diff --git a/resteasy-customize-http-engine/src/test/resources/META-INF/services/javax.ws.rs.ext.Providers b/resteasy-customize-http-engine/src/test/resources/META-INF/services/javax.ws.rs.ext.Providers new file mode 100644 index 0000000..60fc47b --- /dev/null +++ b/resteasy-customize-http-engine/src/test/resources/META-INF/services/javax.ws.rs.ext.Providers @@ -0,0 +1 @@ +org.littlewings.javaee7.resteasy.ScalaObjectMapperProvider diff --git a/resteasy-customize-http-engine/src/test/scala/org/littlewings/javaee7/resteasy/Book.scala b/resteasy-customize-http-engine/src/test/scala/org/littlewings/javaee7/resteasy/Book.scala new file mode 100644 index 0000000..dae0611 --- /dev/null +++ b/resteasy-customize-http-engine/src/test/scala/org/littlewings/javaee7/resteasy/Book.scala @@ -0,0 +1,3 @@ +package org.littlewings.javaee7.resteasy + +case class Book(isbn: String, title: String, price: Int) diff --git a/resteasy-customize-http-engine/src/test/scala/org/littlewings/javaee7/resteasy/RestEasyHttpEngineSpec.scala b/resteasy-customize-http-engine/src/test/scala/org/littlewings/javaee7/resteasy/RestEasyHttpEngineSpec.scala new file mode 100644 index 0000000..658644c --- /dev/null +++ b/resteasy-customize-http-engine/src/test/scala/org/littlewings/javaee7/resteasy/RestEasyHttpEngineSpec.scala @@ -0,0 +1,146 @@ +package org.littlewings.javaee7.resteasy + +import javax.ws.rs.client.{ClientBuilder, Entity} +import javax.ws.rs.core.Response + +import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder +import org.jboss.resteasy.client.jaxrs.engines.URLConnectionEngine +import org.scalatest.{FunSuite, Matchers} + +class RestEasyHttpEngineSpec extends FunSuite with Matchers { + test("use standard engine") { + TestRestServer.withServer { + val client = ClientBuilder.newBuilder.build + + try { + val response = + client + .target("http://localhost:8080/test/get") + .request + .get() + + response.getStatus should be(Response.Status.OK.getStatusCode) + response.readEntity(classOf[Book]) should be( + Book("978-4798140926", "Java EE 7徹底入門 標準Javaフレームワークによる高信頼性Webシステムの構築", 4104) + ) + + response.close() + } finally { + client.close() + } + } + } + + test("use java.net.HttpConnection engine") { + TestRestServer.withServer { + val client = new ResteasyClientBuilder().httpEngine(new URLConnectionEngine).build() + + try { + val response = + client + .target("http://localhost:8080/test/get") + .request + .get() + + response.getStatus should be(Response.Status.OK.getStatusCode) + response.readEntity(classOf[Book]) should be( + Book("978-4798140926", "Java EE 7徹底入門 標準Javaフレームワークによる高信頼性Webシステムの構築", 4104) + ) + + response.close() + } finally { + client.close() + } + } + } + + test("use OkHttp engine") { + TestRestServer.withServer { + val client = new ResteasyClientBuilder().httpEngine(new OkHttpEngine).build() + + try { + val response = + client + .target("http://localhost:8080/test/get") + .request + .get() + + response.getStatus should be(Response.Status.OK.getStatusCode) + response.readEntity(classOf[Book]) should be( + Book("978-4798140926", "Java EE 7徹底入門 標準Javaフレームワークによる高信頼性Webシステムの構築", 4104) + ) + + response.close() + } finally { + client.close() + } + } + } + + test("use OkHttp engine, post") { + TestRestServer.withServer { + val client = new ResteasyClientBuilder().httpEngine(new OkHttpEngine).build() + + try { + val response = + client + .target("http://localhost:8080/test/post") + .request + .post(Entity.json(Book("978-4774183169", "パーフェクト Java EE", 3456))) + + response.getStatus should be(Response.Status.OK.getStatusCode) + response.readEntity(classOf[Book]) should be( + Book("978-4774183169", "パーフェクト Java EE", 6912) + ) + + response.close() + } finally { + client.close() + } + } + } + + test("use OkHttp engine, put") { + TestRestServer.withServer { + val client = new ResteasyClientBuilder().httpEngine(new OkHttpEngine).build() + + try { + val response = + client + .target("http://localhost:8080/test/put") + .request + .put(Entity.json(Book("978-4774183169", "パーフェクト Java EE", 3456))) + + response.getStatus should be(Response.Status.OK.getStatusCode) + response.readEntity(classOf[Book]) should be( + Book("978-4774183169", "パーフェクト Java EE", 6912) + ) + + response.close() + } finally { + client.close() + } + } + } + + test("use OkHttp engine, delete") { + TestRestServer.withServer { + val client = new ResteasyClientBuilder().httpEngine(new OkHttpEngine).build() + + try { + val response = + client + .target("http://localhost:8080/test/delete") + .request + .method("DELETE", Entity.json(Book("978-4774183169", "パーフェクト Java EE", 3456))) + + response.getStatus should be(Response.Status.NO_CONTENT.getStatusCode) + response.getEntity should be(null) + + response.close() + } finally { + client.close() + } + } + } +} diff --git a/resteasy-customize-http-engine/src/test/scala/org/littlewings/javaee7/resteasy/ScalaObjectMapperProvider.scala b/resteasy-customize-http-engine/src/test/scala/org/littlewings/javaee7/resteasy/ScalaObjectMapperProvider.scala new file mode 100644 index 0000000..c270e38 --- /dev/null +++ b/resteasy-customize-http-engine/src/test/scala/org/littlewings/javaee7/resteasy/ScalaObjectMapperProvider.scala @@ -0,0 +1,19 @@ +package org.littlewings.javaee7.resteasy + +import javax.ws.rs.{Consumes, Produces} +import javax.ws.rs.core.MediaType +import javax.ws.rs.ext.{ContextResolver, Provider} + +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.module.scala.DefaultScalaModule + +@Provider +@Consumes(Array(MediaType.APPLICATION_JSON)) +@Produces(Array(MediaType.APPLICATION_JSON)) +class ScalaObjectMapperProvider extends ContextResolver[ObjectMapper] { + override def getContext(typ: Class[_]): ObjectMapper = { + val objectMapper = new ObjectMapper + objectMapper.registerModule(DefaultScalaModule) + objectMapper + } +} diff --git a/resteasy-customize-http-engine/src/test/scala/org/littlewings/javaee7/resteasy/TestRestServer.scala b/resteasy-customize-http-engine/src/test/scala/org/littlewings/javaee7/resteasy/TestRestServer.scala new file mode 100644 index 0000000..7c2364d --- /dev/null +++ b/resteasy-customize-http-engine/src/test/scala/org/littlewings/javaee7/resteasy/TestRestServer.scala @@ -0,0 +1,51 @@ +package org.littlewings.javaee7.resteasy + +import javax.ws.rs.core.MediaType +import javax.ws.rs._ + +import org.jboss.resteasy.plugins.server.netty.NettyJaxrsServer + +object TestRestServer { + def withServer(fun: => Unit): Unit = { + val netty = new NettyJaxrsServer + val deployment = netty.getDeployment + deployment.setResourceClasses(java.util.Arrays.asList(classOf[TestResource].getName)) + netty.setRootResourcePath("") + netty.setPort(8080) + netty.setDeployment(deployment) + netty.start() + + try { + fun + } finally { + netty.stop() + } + } +} + +@Path("test") +class TestResource { + @GET + @Path("get") + @Produces(Array(MediaType.APPLICATION_JSON)) + def get: Book = + Book("978-4798140926", "Java EE 7徹底入門 標準Javaフレームワークによる高信頼性Webシステムの構築", 4104) + + @POST + @Path("post") + @Consumes(Array(MediaType.APPLICATION_JSON)) + @Produces(Array(MediaType.APPLICATION_JSON)) + def post(book: Book): Book = book.copy(price = book.price * 2) + + @PUT + @Path("put") + @Consumes(Array(MediaType.APPLICATION_JSON)) + @Produces(Array(MediaType.APPLICATION_JSON)) + def put(book: Book): Book = book.copy(price = book.price * 2) + + @DELETE + @Path("delete") + @Consumes(Array(MediaType.APPLICATION_JSON)) + @Produces(Array(MediaType.APPLICATION_JSON)) + def delete(book: Book): Unit = () +}