I'm trying to get a basic "httpclient" "httprequest" "httpresponse" working with Resilience4j Retry.
The verbatim code from : https://resilience4j.readme.io/docs/retry
RetryConfig config = RetryConfig.custom()
.maxAttempts(5)
.waitDuration(Duration.ofMillis(1000))
.retryOnResult(response -> response.getStatus() == 500)
.retryOnException(e -> e instanceof WebServiceException)
.retryExceptions(IOException.class, TimeoutException.class)
.ignoreExceptions(BusinessException.class, OtherBusinessException.class)
.build();
// Create a RetryRegistry with a custom global configuration
RetryRegistry registry = RetryRegistry.of(config);
// Get or create a Retry from the registry -
// Retry will be backed by the default config
Retry retryWithDefaultConfig = registry.retry("name1");
Note, their code above misses defining the generic "T", like this:
RetryConfig config = RetryConfig.<MyConcrete>custom()
and the verbatim code from : https://resilience4j.readme.io/docs/examples
Supplier<String> supplierWithResultAndExceptionHandler = SupplierUtils
.andThen(supplier, (result, exception) -> "Hello Recovery");
Supplier<HttpResponse> supplier = () -> httpClient.doRemoteCall();
Supplier<HttpResponse> supplierWithResultHandling = SupplierUtils.andThen(supplier, result -> {
if (result.getStatusCode() == 400) {
throw new ClientException();
} else if (result.getStatusCode() == 500) {
throw new ServerException();
}
return result;
});
HttpResponse httpResponse = circuitBreaker
.executeSupplier(supplierWithResultHandling);
======
So using those 2 "partials" , I've come up with this.
Note, I am using some "real" java.net.http.HttpClient and java.net.http.HttpResponse (from JDK11)
import io.github.resilience4j.core.SupplierUtils;
import io.github.resilience4j.retry.Retry;
import io.github.resilience4j.retry.RetryConfig;
import io.github.resilience4j.retry.RetryRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import javax.inject.Inject;
import java.io.IOException;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.concurrent.TimeoutException;
import java.util.function.Supplier;
public final class ResilientHttpClient /* implements IResilientHttpClient */ {
private static Logger logger;
private final HttpClient httpClient;
#Inject
public ResilientHttpClient(final HttpClient httpClient) {
this(LoggerFactory
.getLogger(ResilientHttpClient.class), httpClient);
}
/**
* Constructor, which pre-populates the provider with one resource instance.
*/
public ResilientHttpClient(final Logger lgr,
final HttpClient httpClient) {
if (null == lgr) {
throw new IllegalArgumentException("Logger is null");
}
this.logger = lgr;
if (null == httpClient) {
throw new IllegalArgumentException("HttpClient is null");
}
this.httpClient = httpClient;
}
public String executeHttpRequest(String circuitbreakerInstanceName, HttpRequest httpRequest) {
try {
/* circuitbreakerInstanceName is future place holder for .yml configuration see : https://resilience4j.readme.io/docs/getting-started-3 */
RetryConfig config = RetryConfig.<HttpResponse>custom()
.waitDuration(Duration.ofMillis(1000))
.retryOnResult(response -> response.statusCode() == 500)
.retryOnException(e -> e instanceof ArithmeticException)
.retryExceptions(IOException.class, TimeoutException.class)
//.ignoreExceptions(BusinessException.class, OtherBusinessException.class)
.build();
// Create a RetryRegistry with a custom global configuration
RetryRegistry registry = RetryRegistry.of(config);
// Get or create a Retry from the registry -
// Retry will be backed by the default config
Retry retryWithDefaultConfig = registry.retry(circuitbreakerInstanceName);
Supplier<HttpResponse> supplier = () -> this.httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
Supplier<String> supplierWithResultAndExceptionHandler = SupplierUtils
.andThen(supplier, (result, exception) -> "Hello Recovery");
Supplier<HttpResponse> supplierWithResultHandling = SupplierUtils.andThen(supplier, result -> {
if (result.statusCode() == HttpStatus.BAD_REQUEST.value()) {
throw new RuntimeException("400");
} else if (result.statusCode() == HttpStatus.INTERNAL_SERVER_ERROR.value()) {
throw new RuntimeException("500");
}
return result;
});
HttpResponse<String> response = retryWithDefaultConfig.executeSupplier(supplierWithResultHandling);
String responseBody = response.body();
return responseBody;
} catch (Exception ex) {
throw new RuntimeException((ex));
}
}
}
The issue I am having is:
The line:
Supplier<HttpResponse> supplier = () - > this.httpClient.send(httpRequest, HttpResponse.BodyHandlers.ofString());
is giving an error (in intelliJ) of "unhandled exceptions" "IOException, InterruptedException"
So modifying the method to be:
public String executeHttpRequest(String circuitbreakerInstanceName, HttpRequest httpRequest) throws IOException, InterruptedException {
"feels wrong". But even when I try it...it doesn't resolve anything. :(
It is probably some lamda checked-exception voodoo.
But more to the point:
So I don't know if the way I've brought together the 2 partials is even correct. The samples are a little lacking in the fully-working area.
Thank for any help. Getting a basic httpclient "retry" a few times shouldn't be too hard. But I'm hitting my head against the wall.
My gradle dependencies.
dependencies {
implementation group: 'javax.inject', name: 'javax.inject', version: javaxInjectVersion
implementation group: 'org.slf4j', name: 'slf4j-api', version: slf4jVersion
implementation group: 'org.springframework', name: 'spring-web', version: springWebVersion
implementation "io.github.resilience4j:resilience4j-circuitbreaker:${resilience4jVersion}"
implementation "io.github.resilience4j:resilience4j-ratelimiter:${resilience4jVersion}"
implementation "io.github.resilience4j:resilience4j-retry:${resilience4jVersion}"
implementation "io.github.resilience4j:resilience4j-bulkhead:${resilience4jVersion}"
implementation "io.github.resilience4j:resilience4j-cache:${resilience4jVersion}"
implementation "io.github.resilience4j:resilience4j-timelimiter:${resilience4jVersion}"
testCompile group: 'junit', name: 'junit', version: junitVersion
}
and
resilience4jVersion = '1.5.0'
slf4jVersion = "1.7.30"
javaxInjectVersion = "1"
springWebVersion = '5.2.8.RELEASE'
junitVersion = "4.12"
just out of interest:
Which Java version are you using? Java 11?
Why can't you use Spring Boot? The Resilience4j Spring Boot starter simplifies the configuration a lot.
If you configure retryOnResult(response -> response.getStatus() == 500), you don't have to use SupplierUtils anymore to map a HttpResponse with a certain status code to a runtime exception.
RetryConfig config = RetryConfig.<HttpResponse<String>>custom()
.waitDuration(Duration.ofMillis(1000))
.retryOnResult(response -> response.statusCode() == 500)
.retryExceptions(IOException.class, TimeoutException.class)
.build();
Please don't create Registries and Configs inside of executeHttpRequest, but inject them into your Constructor.
You can create a static method like this:
public static <T> HttpResponse<T> executeHttpRequest(Callable<HttpResponse<T>> callable, Retry retry, CircuitBreaker circuitBreaker) throws Exception {
return Decorators.ofCallable(callable)
.withRetry(retry)
.withCircuitBreaker(circuitBreaker)
.call();
}
and invoke the method as follows:
HttpResponse<String> response = executeHttpRequest(
() -> httpClient.send(request, HttpResponse.BodyHandlers.ofString()),
retry,
circuitBreaker);
Related
java.net has a simple getServerCertificates in its API (example follows). I was looking for a similar operation in reactor-netty, and if not there, in any other reactive API for spring-boot/webflux/HttpClient.
This operation (client reads certificate) does not seem possible in reactor-netty. Is it? If it isn't is there an alternative method in another spring-boot component to do this?
package com.example.readCertificate.service;
import java.net.URL;
import java.securiiity.cert.Certificate;
import javax.net.ssl.HttpsURLConnection;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class ShowCert {
private Logger logger = LogManager.getLogger();
public void showCert(String url) {
try {
URL destinationURL = new URL(url);
HttpsURLConnection connection = (HttpsURLConnection) destinationURL.openConnection();
connection.connect();
Certificate[] certificates = connection.getServerCertificates();
for (Certificate certificate : certificates) {
logger.info("certificate is:" + certificate);
}
} catch (Exception e) {
logger.error(e);
}
}
}
In WebClient from Spring WebFlux we usually use netty as backend. We provide a bean ReactorClientHttpConnector in which we create netty http-client.
For handling SSL netty uses handler within the channel pipeline.
Here I'm putting a callback to event doOnConnected() and accesing the SSL handler and SSLSession.
SSLSession provides methods getPeerCertificates(), getLocalCertificates()
, so we can get access to certificates here.
#Bean
public ReactorClientHttpConnector reactorClientHttpConnector() {
return new ReactorClientHttpConnector(
HttpClient.create()
.doOnConnected(connection -> {
ChannelPipeline pipeline = connection.channel().pipeline();
Optional.ofNullable(pipeline)
.map(p -> p.get(SslHandler.class))
.map(SslHandler::engine)
.map(SSLEngine::getSession)
.ifPresent(sslSession -> {
try {
Certificate[] peerCertificates = sslSession.getPeerCertificates();
if (Objects.nonNull(peerCertificates)) {
Stream.of(peerCertificates)
.forEach(System.out::println);
}
} catch (Exception e) {
e.printStackTrace();
}
});
})
);
}
And create your WebClient:
#Bean
public WebClient httpsClient() {
return WebClient.builder()
.clientConnector(reactorClientHttpConnector())
.baseUrl("https://secured-resource.com)
.build();
}
Then while making http-call with this httpsClient bean you should see the results in your console
I am trying to connect to the google cloud platform pub/sub from behind a proxy.
Using Spring lib "org.springframework.cloud:spring-cloud-gcp-starter-pubsub" which uses the google pub sub client, which in order to make the pull call for the subscription uses gRPC calls.
In order to set the proxy I can use GRPC_PROXY_EXP environment variable but I also need credentials to go through this proxy.
I've tries several approaches, including configuring the org.springframework.cloud.gcp.pubsub.support.SubscriberFactory similar to here https://medium.com/google-cloud/accessing-google-cloud-apis-though-a-proxy-fe46658b5f2a
#Bean
fun inboundQuotationsChannelAdapter(
#Qualifier("inboundQuotationsMessageChannel") quotationsChannel: MessageChannel,
mpProperties: ConfigurationProperties,
defaultSubscriberFactory: SubscriberFactory
): PubSubInboundChannelAdapter {
Authenticator.setDefault(ProxyAuthenticator("ala","bala"))
val proxySubscriberFactory: DefaultSubscriberFactory = defaultSubscriberFactory as DefaultSubscriberFactory
proxySubscriberFactory.setCredentialsProvider(ProxyCredentialsProvider(getCredentials()))
val headers = mutableMapOf(Pair("Proxy-Authorization", getBasicAuth()))
proxySubscriberFactory.setChannelProvider(SubscriberStubSettings.defaultGrpcTransportProviderBuilder()
.setHeaderProvider(FixedHeaderProvider.create(headers)).build())
val proxySubscriberTemplate = PubSubSubscriberTemplate(proxySubscriberFactory)
val adapter = PubSubInboundChannelAdapter(proxySubscriberTemplate, mpProperties.gcp.quotationSubscription)
adapter.outputChannel = quotationsChannel
adapter.ackMode = AckMode.MANUAL
adapter.payloadType = ActivityStateChanged::class.java
return adapter
}
#Throws(IOException::class)
fun getCredentials(): GoogleCredentials {
val httpTransportFactory = getHttpTransportFactory(
"127.0.0.1", 3128, "ala", "bala"
)
return GoogleCredentials.getApplicationDefault(httpTransportFactory)
}
fun getHttpTransportFactory(
proxyHost: String?,
proxyPort: Int,
proxyUsername: String?,
proxyPassword: String?
): HttpTransportFactory? {
val proxyHostDetails = HttpHost(proxyHost, proxyPort)
val httpRoutePlanner: HttpRoutePlanner = DefaultProxyRoutePlanner(proxyHostDetails)
val credentialsProvider: CredentialsProvider = BasicCredentialsProvider()
credentialsProvider.setCredentials(
AuthScope(proxyHostDetails.hostName, proxyHostDetails.port),
UsernamePasswordCredentials(proxyUsername, proxyPassword)
)
val httpClient: HttpClient = ApacheHttpTransport.newDefaultHttpClientBuilder()
.setRoutePlanner(httpRoutePlanner)
.setProxyAuthenticationStrategy(ProxyAuthenticationStrategy.INSTANCE)
.setDefaultCredentialsProvider(credentialsProvider)
.setDefaultRequestConfig(
RequestConfig.custom()
.setAuthenticationEnabled(true)
.setProxy(proxyHostDetails)
.build())
.addInterceptorLast(HttpRequestInterceptor { request, context ->
request.addHeader(
BasicHeader(
"Proxy-Authorization",
getBasicAuth()
)
)
})
.build()
val httpTransport: HttpTransport = ApacheHttpTransport(httpClient)
return HttpTransportFactory { httpTransport }
}
Also tried using #GRpcGlobalInterceptor from LogNet
https://github.com/LogNet/grpc-spring-boot-starter
#Bean
#GRpcGlobalInterceptor
fun globalServerInterceptor(): ServerInterceptor {
return GrpcServerInterceptor(configurationProperties)
}
#Bean
#GRpcGlobalInterceptor
fun globalClientInterceptor(): ClientInterceptor {
return GrpcClientInterceptor(configurationProperties)
}
with
class GrpcClientInterceptor(private val configurationProperties: ConfigurationProperties) :
ClientInterceptor {
private val proxyUsername = configurationProperties.http.proxy.username
private val proxyPassword = configurationProperties.http.proxy.password
private val proxyHeaderKey = Metadata.Key.of("Proxy-Authorization", Metadata.ASCII_STRING_MARSHALLER)
private fun getBasicAuth(): String {
val usernameAndPassword = "$proxyUsername:$proxyPassword"
val encoded = Base64.getEncoder().encodeToString(usernameAndPassword.toByteArray())
return "Basic $encoded"
}
override fun <ReqT, RespT> interceptCall(
method: MethodDescriptor<ReqT, RespT>?,
callOptions: CallOptions?, next: Channel
): ClientCall<ReqT, RespT>? {
return object : SimpleForwardingClientCall<ReqT, RespT>(next.newCall(method, callOptions)) {
override fun start(responseListener: Listener<RespT>?, headers: Metadata) {
headers.put(proxyHeaderKey, getBasicAuth())
super.start(object : SimpleForwardingClientCallListener<RespT>(responseListener) {
override fun onHeaders(headers: Metadata) {
super.onHeaders(headers)
}
}, headers)
}
}
}
}
class GrpcServerInterceptor(private val configurationProperties: ConfigurationProperties) :
ServerInterceptor {
private val proxyUsername = configurationProperties.http.proxy.username
private val proxyPassword = configurationProperties.http.proxy.password
override fun <ReqT : Any?, RespT : Any?> interceptCall(
call: ServerCall<ReqT, RespT>?,
headers: io.grpc.Metadata?,
next: ServerCallHandler<ReqT, RespT>?
): ServerCall.Listener<ReqT> {
val proxyHeaderKey = Metadata.Key.of("Proxy-Authorization", Metadata.ASCII_STRING_MARSHALLER)
if (!headers!!.containsKey(proxyHeaderKey))
headers!!.put(proxyHeaderKey, getBasicAuth())
return next!!.startCall(call, headers)
}
private fun getBasicAuth(): String {
val usernameAndPassword = "$proxyUsername:$proxyPassword"
val encoded = Base64.getEncoder().encodeToString(usernameAndPassword.toByteArray())
return "Basic $encoded"
}
}
(also tried the annotation directly on class level - ofc it did not work)
Also tried using #GrpcGlobalServerInterceptor and #GrpcGlobalClientInterceptor from https://github.com/yidongnan/grpc-spring-boot-starter/tree/v2.12.0.RELEASE but this dependency crashed the app entirely
Here you can find an example on how to set the proxy credentials from the Java API documentation to configuring-a-proxy ;
public CloudTasksClient getService() throws IOException {
TransportChannelProvider transportChannelProvider =
CloudTasksStubSettings.defaultGrpcTransportProviderBuilder()
.setChannelConfigurator(
new ApiFunction<ManagedChannelBuilder, ManagedChannelBuilder>() {
#Override
public ManagedChannelBuilder apply(ManagedChannelBuilder managedChannelBuilder) {
return managedChannelBuilder.proxyDetector(
new ProxyDetector() {
#Nullable
#Override
public ProxiedSocketAddress proxyFor(SocketAddress socketAddress)
throws IOException {
return HttpConnectProxiedSocketAddress.newBuilder()
.setUsername(PROXY_USERNAME)
.setPassword(PROXY_PASSWORD)
.setProxyAddress(new InetSocketAddress(PROXY_HOST, PROXY_PORT))
.setTargetAddress((InetSocketAddress) socketAddress)
.build();
}
});
}
})
.build();
CloudTasksSettings cloudTasksSettings =
CloudTasksSettings.newBuilder()
.setTransportChannelProvider(transportChannelProvider)
.build();
return CloudTasksClient.create(cloudTasksSettings);
}
Take into consideration the note where it says that gRPC proxy is currently experimental.
There are two clients that communicate with google. One using "http" and one using "gRPC". (https instead of http is also possible)
The Solution Dan posted is just the solution for gRPC.
Here my Solution for http by using an Apache-Http-Client.
try (InputStream jsonCredentialsFile = json-file as InputStream) {
GoogleCredentials credentials = GoogleCredentials.fromStream(jsonCredentialsFile, new HttpTransportFactory() {
public HttpTransport create() {
DefaultHttpClient httpClient = new DefaultHttpClient();
httpClient.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, new HttpHost("myProxyHost", myProxyPort, "http"));
// DefaultHttpClient is deprecated but this recommended solution does not work in this context.
// org.apache.http.impl.client.InternalHttpClient.getParams() always throws UnsupportedOperationException
// HttpClientBuilder builder = HttpClientBuilder.create();
// if (StringUtils.isBlank(proxyServer) || proxyport < 0) {
// builder.setProxy(new HttpHost(proxyServer, proxyport, "http"));
// }
// CloseableHttpClient httpClient = builder.build();
return new ApacheHttpTransport(httpClient);
}
})
.createScoped(Lists.newArrayList("https://www.googleapis.com/auth/cloud-platform"));
DocumentProcessorServiceSettings.Builder dpssBuilder = DocumentProcessorServiceSettings.newBuilder()
.setEndpoint(endpoint)
.setCredentialsProvider(FixedCredentialsProvider.create(credentials));
dpssBuilder.setTransportChannelProvider(transportProvider);
DocumentProcessorServiceClient client = DocumentProcessorServiceClient.create(dpssBuilder.build());
// use the client
}
Expected result: connecting to the RSocket websocket based endpoint from front end that includes authentication information as metadata will trigger PayloadSocketAcceptorInterceptor's jwt authentication system.
Actual result: This only happens when sending responseRequest from JS frontend, fails when doing the same with streamRequest. No errors. Not one of the authentication related methods get called in the classes below. I've logged all of them.
Code for RSocketConfig:
#Configuration
#EnableRSocketSecurity
#EnableReactiveMethodSecurity
class RSocketConfig {
#Autowired
lateinit var rSocketAuthenticationManager: RSocketAuthenticationManager
#Bean
fun rSocketMessageHandler(strategies: RSocketStrategies?): RSocketMessageHandler? {
val handler = RSocketMessageHandler()
handler.argumentResolverConfigurer.addCustomResolver(AuthenticationPrincipalArgumentResolver())
handler.rSocketStrategies = strategies!!
return handler
}
#Bean
fun authorization(rsocket: RSocketSecurity): PayloadSocketAcceptorInterceptor {
rsocket.authorizePayload { authorize: AuthorizePayloadsSpec ->
authorize
.route("flux-stream").authenticated()
.anyRequest().authenticated()
.anyExchange().permitAll()
}
.jwt { jwtSpec: RSocketSecurity.JwtSpec ->
try {
jwtSpec.authenticationManager(rSocketAuthenticationManager)
} catch (e: Exception) {
throw RuntimeException(e)
}
}
return rsocket.build()
}
#Bean
fun rSocketRequester(strategies: RSocketStrategies, props: RSocketProperties): Mono<RSocketRequester> =
RSocketRequester.builder()
.rsocketStrategies(strategies)
.connectWebSocket(getUri(props))
fun getUri(props: RSocketProperties): URI =
URI.create(String.format("ws://localhost:${props.server.port}${props.server.mappingPath}"))
}
Code for RSocketAuthenticationManager:
#Component
class RSocketAuthenticationManager(): ReactiveAuthenticationManager {
#Autowired
lateinit var cognitoConfig: CognitoConfig
#Override
override fun authenticate(authentication: Authentication): Mono<Authentication> {
val authToken: String = authentication.credentials.toString()
try {
return if(isTokenValid(authToken)) {
val decoded = JWT.decode(authToken)
decoded.claims.entries.forEach { (key, value) -> println("$key = ${value.asString()}") }
val authorities: MutableList<GrantedAuthority> = ArrayList()
println("authentication successful!")
Mono.just(UsernamePasswordAuthenticationToken(decoded.subject, null, authorities))
} else {
println("invalid authentication token")
Mono.empty<Authentication>();
}
} catch (e: Exception) {
println("authentication errored")
e.printStackTrace()
return Mono.empty<Authentication>()
}
}
#Throws(Exception::class)
fun isTokenValid(token: String): Boolean {
// code borrowed from
// https://github.com/awslabs/cognito-proxy-rest-service/blob/2f9a9ffcc742c8ab8a694b7cf39dc5d8b3247898/src/main/kotlin/com/budilov/cognito/services/CognitoService.kt#L41
// Decode the key and set the kid
val decodedJwtToken = JWT.decode(token)
val kid = decodedJwtToken.keyId
val http = UrlJwkProvider(URL(cognitoConfig.jwksUrl))
// Let's cache the result from Cognito for the default of 10 hours
val provider = GuavaCachedJwkProvider(http)
val jwk = provider.get(kid)
val algorithm = Algorithm.RSA256(jwk.publicKey as RSAKey)
val verifier = JWT.require(algorithm)
.withIssuer(cognitoConfig.jwtTokenIssuer)
.build() //Reusable verifier instance
val jwt = try {
verifier.verify(token)
} catch (e: Exception) {
false
}
return (jwt != null)
}
}
Dependencies related to the issue:
implementation("org.springframework.boot:spring-boot-starter-webflux:2.3.0.RELEASE")
implementation("org.springframework.boot:spring-boot-starter-websocket:2.3.0.RELEASE")
implementation("org.springframework.boot:spring-boot-configuration-processor:2.3.0.RELEASE")
implementation("org.springframework.boot:spring-boot-starter-rsocket:2.3.0.RELEASE")
implementation("org.springframework.boot:spring-boot-starter-integration:2.3.0.RELEASE")
implementation("org.springframework.boot:spring-boot-starter-security:2.3.0.RELEASE")
implementation("org.springframework.security:spring-security-rsocket:5.4.2")
implementation("org.springframework.security:spring-security-messaging:5.4.2")
implementation("org.springframework.boot:spring-boot-starter-oauth2-resource-server:2.3.0.RELEASE")
implementation("com.auth0:java-jwt:3.3.0")
implementation("com.auth0:jwks-rsa:0.1.0")
I'm not too familiar with Spring Security, so maybe I'm missing something obvious.
You should have a service method annotated to receive the authentication principal.
#MessageMapping("runCommand")
suspend fun runCommand(request: CommandRequest, rSocketRequester: RSocketRequester, #AuthenticationPrincipal jwt: String): Flow<CommandResponse> {
Can you extract a simpler project that you can share on github to work through why it's not working?
A full example is here https://spring.io/blog/2020/06/17/getting-started-with-rsocket-spring-security
The issue has been solved, for anyone in need check the repos history with fixes: https://github.com/Braffolk/spring-rsocket-stream-jwt-authentication/commits/master
I wrote code in our Spring Boot 2 application to make a third-party API call with HTTPUrlConnection.
public String loginApi(LoginDTO loginDto)
{
String responseData = null;
HttpURLConnection conn = null;
try {
link = authBaseUrl + loginUrl;
url = new URL(link);
conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setRequestProperty(CONTENT_TYPE, MEDIA_TYPE);
String body = getAuth0LoginDto(loginDto);
// =====================
// For POST only - START
conn.setDoOutput(true);
OutputStream os = conn.getOutputStream();
os.write(body.getBytes(StandardCharsets.UTF_8));
os.flush();
os.close();
// For POST only - END
// ====================
try (BufferedReader br = (conn.getResponseCode() >= 400
? new BufferedReader(new InputStreamReader(conn.getErrorStream()))
: new BufferedReader(new InputStreamReader(conn.getInputStream())))) {
StringBuilder everything = new StringBuilder();
String output = null;
while ((output = br.readLine()) != null) {
everything.append(output);
}
responseData = everything.toString();
}
} catch (JsonProcessingException e) {
throw new Auth0Exception("Could not create Auth0 Login Body", e);
} catch (IOException e) {
throw new Auth0Exception("Error with Login API", e);
} finally {
if (conn != null) {
conn.disconnect();
}
}
return responseData;
}
Now, I am very much used to doing real integration testing, where I make a real call to the web-service and check the results.
I am now being asked to use strictly Mockito, not PowerMockito, not EasyMock, to create mocking tests, and I have never done that before. My knowledge of Mockito is weak also since I haven't used it in a very long time.
So, I know it has been asked before, and I have really searched on the internet, and I really haven't found a full piece of code as an example. I see code snippets which leaves me with pieces missing, and I am not knowledgeable enough to add those parts myself.
I know this code actual implementation works fine, and the integration test works fine also. But, what I have seen before is that some users are being told they need to change their client code in order to make the mockito tests work.
If I don't get the mocking tests working for HTTPUrlConnection, then I'll be forced to switch over to RestTemplate and Mocking since my co-worker insists we use RestTemplate anyway.
Thanks!
Since you have asked for a small example which does not make sense but should show the idea:
import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.HttpURLConnection;
import java.net.URL;
public class App {
public int status(URL url) {
HttpURLConnection urlConnection = null;
try {
urlConnection = create(url);
return urlConnection.getResponseCode();
} catch (IOException e) {
throw new UncheckedIOException(e);
} finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
}
}
HttpURLConnection create(URL url) throws IOException {
return (HttpURLConnection) url.openConnection();
}
}
I would implement this with a spy and as I recommended a mocked HttpURLConnection:
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.junit.jupiter.MockitoExtension;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
#ExtendWith(MockitoExtension.class)
class AppTest {
#Spy
App app;
#Mock
HttpURLConnection connection;
#Test
void status() throws IOException {
int expected = 200;
doReturn(connection).when(app).create(any());
doReturn(expected).when(connection).getResponseCode();
URL url = new URL("http://www.google.ats");
int status = app.status(url);
Assertions.assertEquals(expected, status);
}
}
Maybe somebody can help me find out how to solve this.
I am using jersey-apache-client 1.17
I tried to use Jersey client to build a standalone application (no Servlet container or whatever, just the Java classes) which communicates with a RESTFUL API, and everything worked fine until I tried to handle the mediatype "text/csv; charset=utf-8" which is a CSV stream sent by the server.
The thing is that I can read this stream with the following code:
InputStreamReader reader = new InputStreamReader(itemExportBuilder
.get(ClientResponse.class).getEntityInputStream());
Csv csv = new Csv();
Input input = csv.createInput(reader);
try {
String[] readLine;
while ((readLine = input.readLine()) != null) {
LOG.debug("Reading CSV: {}", readLine);
}
} catch (IOException e) {
e.printStackTrace();
}
try {
input.close();
} catch (IOException e) {
e.printStackTrace();
}
But I'd like to encapsulate it and put it into a MessageBodyReader. But after writing this code, I just can't make the client use the following class:
package client.response;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.Provider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
#Provider
public class ItemExportMessageBodyReader implements MessageBodyReader<ItemExportResponse> {
private static final Logger LOG = LoggerFactory.getLogger(ItemExportMessageBodyReader.class);
private static final Integer SKU = 0;
private static final Integer BASE_SKU = 1;
public boolean isReadable(Class<?> paramClass, Type type, Annotation[] annotations,
MediaType mediaType) {
LOG.info("Cheking if content is readable or not");
return paramClass == ItemExportResponse.class && !mediaType.isWildcardType()
&& !mediaType.isWildcardSubtype()
&& mediaType.isCompatible(MediaType.valueOf("text/csv; charset=utf-8"));
}
public ItemExportResponse readFrom(Class<ItemExportResponse> paramClass, Type paramType,
Annotation[] paramArrayOfAnnotation, MediaType paramMediaType,
MultivaluedMap<String, String> paramMultivaluedMap, InputStream entityStream)
throws IOException, WebApplicationException {
InputStreamReader reader = new InputStreamReader(entityStream);
Csv csv = new Csv();
Input input = csv.createInput(reader);
List<Item> items = new ArrayList<Item>();
try {
String[] readLine;
while ((readLine = input.readLine()) != null) {
LOG.trace("Reading CSV: {}", readLine);
Item item = new Item();
item.setBaseSku(readLine[BASE_SKU]);
items.add(item);
}
} catch (IOException e) {
LOG.warn("Item export HTTP response handling failed", e);
} finally {
try {
input.close();
} catch (IOException e) {
LOG.warn("Could not close the HTTP response stream", e);
}
}
ItemExportResponse response = new ItemExportResponse();
response.setItems(items);
return response;
}
}
The following documentation says that the preferred way of making this work in a JAX-RS client to register the message body reader with the code below:
Using Entity Providers with JAX-RS Client API
Client client = ClientBuilder.newBuilder().register(MyBeanMessageBodyReader.class).build();
Response response = client.target("http://example/comm/resource").request(MediaType.APPLICATION_XML).get();
System.out.println(response.getStatus());
MyBean myBean = response.readEntity(MyBean.class);
System.out.println(myBean);
Now the thing is that I can't use the ClientBuilder. I have to extend from a specific class which constructs the client another way, and I have no access to change the construction.
So when I receive the response from the server, the client fails with the following Exception:
com.sun.jersey.api.client.ClientHandlerException: A message body reader for Java class client.response.ItemExportResponse, and Java type class client.response.ItemExportResponse, and MIME media type text/csv; charset=utf-8 was not found
Any other way to register my MessageBodyReader?
OK. If anybody would bump into my question I solved this mystery by upgrading from Jersey 1.17 to version 2.9. The documentation I linked above also covers this version not the old one, this is where the confusion stems from.
Jersey introduced backward INCOMPATIBLE changes starting from version 2, so I have no clue how to configure it in version 1.17.
In version 2 the proposed solution worked fine.