diff --git a/README.MD b/README.MD index f3dbdde..8ee1797 100644 --- a/README.MD +++ b/README.MD @@ -401,7 +401,7 @@ All client examples use the same base ssl configuration created within the [SSLC * [Ktor with Okhttp engine](https://github.com/ktorio/ktor) -> [Client Configuration](https://github.com/Hakky54/mutual-tls-ssl/blob/master/client/src/main/java/nl/altindag/client/service/KtorOkHttpClientService.kt) | [Example request](https://github.com/Hakky54/mutual-tls-ssl/blob/master/client/src/main/java/nl/altindag/client/service/KtorHttpClientService.kt) **Scala** -* [Twitter Finagle](https://github.com/twitter/finagle) -> [Client Configuration](https://github.com/Hakky54/mutual-tls-ssl/blob/35cba2f3a2dcd73b01fa323b99eec7777f7429bb/client/src/main/java/nl/altindag/client/ClientConfig.java#L233) | [Example request](https://github.com/Hakky54/mutual-tls-ssl/blob/master/client/src/main/java/nl/altindag/client/service/FinagleHttpClientService.java) +* [Twitter Finagle](https://github.com/twitter/finagle) -> [Client Configuration & Example request](https://github.com/Hakky54/mutual-tls-ssl/blob/master/client/src/main/java/nl/altindag/client/service/FinagleHttpClientService.java) * [Twitter Finagle Featherbed](https://github.com/finagle/featherbed) -> [Client Configuration & Example request](https://github.com/Hakky54/mutual-tls-ssl/blob/d78e4e81b8b775d3ff09c11b0a7c1532a741199e/client/src/main/java/nl/altindag/client/service/FeatherbedRequestService.scala#L19) * [Akka Http Client](https://github.com/akka/akka-http) -> [Client Configuration](https://github.com/Hakky54/mutual-tls-ssl/blob/35cba2f3a2dcd73b01fa323b99eec7777f7429bb/client/src/main/java/nl/altindag/client/ClientConfig.java#L253) | [Example request](https://github.com/Hakky54/mutual-tls-ssl/blob/master/client/src/main/java/nl/altindag/client/service/AkkaHttpClientService.java) * [Dispatch Reboot](https://github.com/dispatch/reboot) -> [Client Configuration & Example request](https://github.com/Hakky54/mutual-tls-ssl/blob/master/client/src/main/java/nl/altindag/client/service/DispatchRebootService.scala) diff --git a/client/src/main/java/nl/altindag/client/ClientConfig.java b/client/src/main/java/nl/altindag/client/ClientConfig.java index 4a79a19..ab31ab8 100644 --- a/client/src/main/java/nl/altindag/client/ClientConfig.java +++ b/client/src/main/java/nl/altindag/client/ClientConfig.java @@ -23,10 +23,6 @@ import com.google.gson.GsonBuilder; import com.sun.jersey.api.client.config.DefaultClientConfig; import com.sun.jersey.client.urlconnection.HTTPSProperties; -import com.twitter.finagle.Http; -import com.twitter.finagle.Service; -import com.twitter.finagle.http.Request; -import com.twitter.finagle.http.Response; import com.typesafe.config.ConfigFactory; import feign.Feign; import feign.googlehttpclient.GoogleHttpClient; @@ -70,8 +66,6 @@ import retrofit2.converter.gson.GsonConverterFactory; import javax.net.ssl.SSLException; -import java.net.URI; -import java.net.URISyntaxException; import java.net.http.HttpClient; @Component @@ -243,17 +237,6 @@ public Retrofit retrofit(@Qualifier("okHttpClient") OkHttpClient okHttpClient) { .build(); } - @Bean - public Service finagle(SSLFactory sslFactory) throws URISyntaxException { - var uri = new URI(Constants.getServerUrl()); - var client = Http.client().withNoHttp2(); - if (uri.getScheme().equals("https")) { - client = client.withTransport() - .tls(sslFactory.getSslContext()); - } - return client.newService(uri.getHost() + ":" + uri.getPort()); - } - @Bean public ActorSystem actorSystem() { return ActorSystem.create( diff --git a/client/src/main/java/nl/altindag/client/service/FinagleHttpClientService.java b/client/src/main/java/nl/altindag/client/service/FinagleHttpClientService.java deleted file mode 100644 index 5e1bc0a..0000000 --- a/client/src/main/java/nl/altindag/client/service/FinagleHttpClientService.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2018 Thunderberry. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package nl.altindag.client.service; - -import com.twitter.finagle.http.Request; -import com.twitter.finagle.http.RequestBuilder; -import com.twitter.finagle.http.Response; -import nl.altindag.client.ClientType; -import nl.altindag.client.model.ClientResponse; -import org.springframework.stereotype.Service; - -import java.util.concurrent.TimeUnit; - -import static nl.altindag.client.ClientType.FINAGLE; -import static nl.altindag.client.Constants.HEADER_KEY_CLIENT_TYPE; - -@Service -public class FinagleHttpClientService implements RequestService { - - private static final int TIMEOUT_AMOUNT_IN_SECONDS = 5; - - private final com.twitter.finagle.Service service; - - public FinagleHttpClientService(com.twitter.finagle.Service finagleService) { - this.service = finagleService; - } - - @Override - public ClientResponse executeRequest(String url) throws Exception { - var request = new RequestBuilder<>() - .addHeader(HEADER_KEY_CLIENT_TYPE, getClientType().getValue()) - .url(url) - .buildGet(null); - - return service.apply(request) - .map(response -> new ClientResponse(response.contentString(), response.statusCode())) - .toJavaFuture() - .get(TIMEOUT_AMOUNT_IN_SECONDS, TimeUnit.SECONDS); - } - - @Override - public ClientType getClientType() { - return FINAGLE; - } -} diff --git a/client/src/main/java/nl/altindag/client/service/FinagleHttpClientService.scala b/client/src/main/java/nl/altindag/client/service/FinagleHttpClientService.scala new file mode 100644 index 0000000..b7e8235 --- /dev/null +++ b/client/src/main/java/nl/altindag/client/service/FinagleHttpClientService.scala @@ -0,0 +1,80 @@ +/* + * Copyright 2018 Thunderberry. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package nl.altindag.client.service + +import com.twitter.finagle.http.{Request, RequestBuilder, Response} +import com.twitter.finagle.ssl.client.SslClientConfiguration +import com.twitter.finagle.ssl.{KeyCredentials, TrustCredentials} +import com.twitter.finagle.{Http, Service} +import nl.altindag.client.ClientType.FINAGLE +import nl.altindag.client.Constants.HEADER_KEY_CLIENT_TYPE +import nl.altindag.client.model.ClientResponse +import nl.altindag.client.{ClientType, Constants} +import nl.altindag.ssl.SSLFactory +import org.springframework.beans.factory.annotation.Qualifier +import org.springframework.context.annotation.Bean +import org.springframework.stereotype +import org.springframework.stereotype.Component + +import java.net.URI +import java.util.concurrent.TimeUnit +import scala.jdk.javaapi.OptionConverters + +@stereotype.Service +class FinagleHttpClientService(@Qualifier("finagleClient") service: Service[Request, Response]) extends RequestService { + + private val TIMEOUT_AMOUNT_IN_SECONDS = 5 + + override def executeRequest(url: String): ClientResponse = { + val request = RequestBuilder() + .addHeader(HEADER_KEY_CLIENT_TYPE, getClientType.getValue) + .url(url) + .buildGet() + + service.apply(request) + .map(response => new ClientResponse(response.contentString, response.statusCode)) + .toJavaFuture + .get(TIMEOUT_AMOUNT_IN_SECONDS, TimeUnit.SECONDS) + } + + override def getClientType: ClientType = FINAGLE +} + +@Component +class FinagleHttpClientConfiguration { + + @Bean(name = Array("finagleClient")) + def createFinagle(sslFactory: SSLFactory): Service[Request, Response] = { + val uri = new URI(Constants.getServerUrl) + var client = Http.client + + if ("https".equals(uri.getScheme)) { + val sslClientConfiguration = SslClientConfiguration( + keyCredentials = OptionConverters.toScala(sslFactory.getKeyManagerFactory) + .map(kmf => KeyCredentials.KeyManagerFactory(kmf)) + .getOrElse(KeyCredentials.Unspecified), + trustCredentials = OptionConverters.toScala(sslFactory.getTrustManagerFactory) + .map(tmf => TrustCredentials.TrustManagerFactory(tmf)) + .getOrElse(TrustCredentials.Unspecified) + ) + + client = client.withTransport.tls(sslClientConfiguration) + } + + client.newService(uri.getHost + ":" + uri.getPort) + } + +} diff --git a/client/src/test/java/nl/altindag/client/ClientConfigShould.java b/client/src/test/java/nl/altindag/client/ClientConfigShould.java index 1723f72..0c40e15 100644 --- a/client/src/test/java/nl/altindag/client/ClientConfigShould.java +++ b/client/src/test/java/nl/altindag/client/ClientConfigShould.java @@ -19,9 +19,6 @@ import akka.http.javadsl.Http; import com.github.mizosoft.methanol.Methanol; import com.google.api.client.http.HttpTransport; -import com.twitter.finagle.Service; -import com.twitter.finagle.http.Request; -import com.twitter.finagle.http.Response; import feign.Feign; import jakarta.ws.rs.client.Client; import kong.unirest.Unirest; @@ -40,7 +37,6 @@ import javax.net.ssl.SSLException; import java.io.IOException; import java.net.ConnectException; -import java.net.URISyntaxException; import java.net.http.HttpClient; import static nl.altindag.client.util.AssertJCustomConditions.GSON_CONVERTER_FACTORY; @@ -48,7 +44,9 @@ import static nl.altindag.client.util.SSLFactoryTestHelper.createSSLFactory; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; @ExtendWith(MockitoExtension.class) class ClientConfigShould { @@ -306,34 +304,6 @@ void createRetrofitWithProvidedOkHttpClient() { assertThat(retrofit.converterFactories()).has(GSON_CONVERTER_FACTORY); } - @Test - void createFinagleClientWithoutSecurity() throws URISyntaxException { - System.setProperty("url", TestConstants.HTTP_URL); - Service service = victim.finagle(null); - - assertThat(service.isAvailable()).isTrue(); - assertThat(service.status()).hasToString("Open"); - - service.close(); - System.clearProperty("url"); - } - - @Test - void createFinagleClientWithSecurity() throws URISyntaxException { - System.setProperty("url", TestConstants.HTTPS_URL); - SSLFactory sslFactory = createSSLFactory(false, true); - - Service service = victim.finagle(sslFactory); - - verify(sslFactory, times(1)).getSslContext(); - - assertThat(service.isAvailable()).isTrue(); - assertThat(service.status()).hasToString("Open"); - - service.close(); - System.clearProperty("url"); - } - @Test void createAkkaHttpClient() { SSLFactory sslFactory = createSSLFactory(false, true); diff --git a/client/src/test/java/nl/altindag/client/service/FinagleHttpClientServiceShould.java b/client/src/test/java/nl/altindag/client/service/FinagleHttpClientServiceShould.java deleted file mode 100644 index 95b0dce..0000000 --- a/client/src/test/java/nl/altindag/client/service/FinagleHttpClientServiceShould.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2018 Thunderberry. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package nl.altindag.client.service; - -import static nl.altindag.client.Constants.HEADER_KEY_CLIENT_TYPE; -import static nl.altindag.client.TestConstants.HTTP_URL; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import java.net.URI; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.ArgumentCaptor; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.Mockito; - -import com.twitter.finagle.Service; -import com.twitter.finagle.http.Method; -import com.twitter.finagle.http.Request; -import com.twitter.finagle.http.Response; -import com.twitter.util.Future; - -import nl.altindag.client.ClientType; -import nl.altindag.client.model.ClientResponse; -import org.mockito.junit.jupiter.MockitoExtension; -import scala.Tuple2; - -@ExtendWith(MockitoExtension.class) -class FinagleHttpClientServiceShould { - - @InjectMocks - private FinagleHttpClientService victim; - @Mock - private Service finagleService; - - @Test - void executeRequest() throws Exception { - Response response = mock(Response.class); - - when(finagleService.apply(Mockito.any(Request.class))).thenReturn(Future.value(response)); - when(response.statusCode()).thenReturn(200); - when(response.contentString()).thenReturn("Hello"); - - ArgumentCaptor requestArgumentCaptor = ArgumentCaptor.forClass(Request.class); - ClientResponse clientResponse = victim.executeRequest(HTTP_URL); - - assertThat(clientResponse.getStatusCode()).isEqualTo(200); - assertThat(clientResponse.getResponseBody()).isEqualTo("Hello"); - - verify(finagleService, times(1)).apply(requestArgumentCaptor.capture()); - assertThat(requestArgumentCaptor.getValue().method()).isEqualTo(Method.Get()); - assertThat(requestArgumentCaptor.getValue().uri()).isEqualTo(URI.create(HTTP_URL).getPath()); - - URI uri = URI.create(HTTP_URL); - assertThat(requestArgumentCaptor.getValue().headerMap().toSet().contains(Tuple2.apply("Host", uri.getHost() + ":" + uri.getPort()))).isTrue(); - assertThat(requestArgumentCaptor.getValue().headerMap().toSet().contains(Tuple2.apply(HEADER_KEY_CLIENT_TYPE, ClientType.FINAGLE.getValue()))).isTrue(); - } - -} diff --git a/client/src/test/java/nl/altindag/client/service/FinagleHttpClientServiceShould.scala b/client/src/test/java/nl/altindag/client/service/FinagleHttpClientServiceShould.scala new file mode 100644 index 0000000..0a24533 --- /dev/null +++ b/client/src/test/java/nl/altindag/client/service/FinagleHttpClientServiceShould.scala @@ -0,0 +1,79 @@ +/* + * Copyright 2018 Thunderberry. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package nl.altindag.client.service + +import com.twitter.finagle.Service +import com.twitter.finagle.http.{Request, Response} +import com.twitter.util.Future +import nl.altindag.client.TestConstants +import nl.altindag.client.TestConstants.HTTP_URL +import nl.altindag.client.util.SSLFactoryTestHelper +import org.assertj.core.api.Assertions.assertThat +import org.mockito.scalatest.MockitoSugar +import org.scalatest.funspec.AnyFunSpec + +class FinagleHttpClientServiceShould extends AnyFunSpec with MockitoSugar { + + describe("execute request") { + val finagleService = mock[Service[Request, Response]] + val response = mock[Response] + + when(finagleService.apply(any[Request])).thenReturn(Future.value(response)) + when(response.statusCode).thenReturn(200) + when(response.contentString).thenReturn("Hello") + + val victim = new FinagleHttpClientService(finagleService) + val clientResponse = victim.executeRequest(HTTP_URL) + + assertThat(clientResponse.getStatusCode).isEqualTo(200) + assertThat(clientResponse.getResponseBody).isEqualTo("Hello") + } + + describe("create finagle without ssl material when url is http and sslFactory is present") { + System.setProperty("url", TestConstants.HTTP_URL) + + val sslFactory = SSLFactoryTestHelper.createSSLFactory(true, true) + val client = new FinagleHttpClientConfiguration() + .createFinagle(sslFactory) + + assertThat(client).isNotNull + verify(sslFactory, times(0)).getKeyManagerFactory + verify(sslFactory, times(0)).getTrustManagerFactory + assertThat(client.isAvailable).isTrue + assertThat(client.status).hasToString("Open") + + client.close(); + System.clearProperty("url"); + } + + describe("create finagle http client with ssl") { + System.setProperty("url", TestConstants.HTTPS_URL) + + val sslFactory = SSLFactoryTestHelper.createSSLFactory(true, true) + val client = new FinagleHttpClientConfiguration() + .createFinagle(sslFactory) + + assertThat(client).isNotNull + verify(sslFactory, times(1)).getKeyManagerFactory + verify(sslFactory, times(1)).getTrustManagerFactory + assertThat(client.isAvailable).isTrue + assertThat(client.status).hasToString("Open") + + client.close() + System.clearProperty("url") + } + +} diff --git a/pom.xml b/pom.xml index 815d745..7c534be 100644 --- a/pom.xml +++ b/pom.xml @@ -43,7 +43,7 @@ 1.44.2 3.14.5 2.11.0 - 22.1.0 + 24.2.0 10.5.3 2.8.5 1.2.0