Wiremock simulate a proxy server running - java

I want to add a test for the following e2e scenario:
My app is making a web request to an external service through an internal proxy server, the proxy server manipulates the request body, forwards the request to the desination host and returns the response returned.
Say for example
I do a post request to external.service/an/endpoint (through my-proxy-server) the body
{
"card_number": "<proxy server pls fill the cc details>"
}
The proxy server modifies the request to fill the cc details and forwards it to external.service/an/endpoint with body
{
"card_number": "372735466563005"
}
The external.service returns status OK. proxy server returns the response without modifying.
How do I test this workflow with wiremock? I can do WireMock.stubFor() for external.service, But I don't know how to make wiremock proxy work with the proxy setting of my Webclient.
See the test, actually, Rest Template test, restTemplateWithWireMockAsProxyServer works as expected, routing my requests through the proxy, but the webClientWithWireMockAsProxyServer errors out with my RCA:
20:06:59.165 [qtp105751207-24] DEBUG wiremock.org.eclipse.jetty.server.HttpChannel - REQUEST for //localhost:58978localhost:58978 on HttpChannelOverHttp#4a71ab50{r=1,c=false,c=false/false,a=IDLE,uri=//localhost:58978localhost:58978,age=0}
CONNECT //localhost:58978localhost:58978 HTTP/1.1
Host: localhost:58978
These Calls over wiremock proxy are not possible as mentioned here. But all my urls are like http://localhost:<port>, meaning I am not making any https call.
package com.spotnana.obt.supplier.services;
import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.client.WireMock;
import com.github.tomakehurst.wiremock.core.WireMockConfiguration;
import com.github.tomakehurst.wiremock.http.RequestMethod;
import com.github.tomakehurst.wiremock.matching.RequestPatternBuilder;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.nio.charset.StandardCharsets;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpHeaders;
import org.apache.http.conn.HttpHostConnectException;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.http.client.reactive.ReactorResourceFactory;
import org.springframework.util.SocketUtils;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.netty.http.client.HttpClient;
import reactor.netty.tcp.ProxyProvider;
#Slf4j
public class SimpleWiremockProxyServerTest {
private final String HOST = "localhost";
private final String MOCK_ENDPOINT = "/my/endpoint";
private WireMockServer targetServer;
private WireMockServer proxyServer;
private WireMock targetWireMock;
private WireMock proxyWireMock;
private String targetBaseUrl;
#Before
public void setup() {
final int targetPort = SocketUtils.findAvailableTcpPort();
this.targetServer = new WireMockServer(WireMockConfiguration.wireMockConfig().port(targetPort));
this.targetServer.start();
this.targetWireMock = new WireMock(targetPort);
this.targetWireMock.resetMappings();
this.targetBaseUrl = "http://" + HOST + ":" + targetPort;
final int proxyPort = SocketUtils.findAvailableTcpPort();
this.proxyServer =
new WireMockServer(
WireMockConfiguration.wireMockConfig().port(proxyPort).enableBrowserProxying(true));
this.proxyServer.start();
this.proxyWireMock = new WireMock(proxyPort);
this.proxyWireMock.resetMappings();
}
#After
public void tearDown() throws HttpHostConnectException {
this.targetWireMock.shutdown();
this.targetServer.stop();
try {
this.proxyWireMock.shutdown();
this.proxyServer.stop();
} catch (final Exception ex) {
log.warn("Proxy server is shutdown already");
}
}
#Test
public void restTemplateWithWireMockAsProxyServer() {
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(HOST, this.proxyServer.port()));
requestFactory.setProxy(proxy);
final var reqPatternBuilder =
RequestPatternBuilder.newRequestPattern(
RequestMethod.GET, WireMock.urlEqualTo(MOCK_ENDPOINT));
final var mappingBuilder =
WireMock.get(WireMock.urlEqualTo(reqPatternBuilder.build().getUrl()));
reqPatternBuilder
.withHeader(HttpHeaders.ACCEPT, WireMock.containing(MediaType.APPLICATION_JSON_VALUE))
.withHeader(
HttpHeaders.ACCEPT_CHARSET,
WireMock.containing(StandardCharsets.UTF_8.name().toUpperCase()));
mappingBuilder.willReturn(
WireMock.aResponse()
.withStatus(HttpStatus.OK.value())
.withBody("{ \"success\": true }")
.withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE));
this.targetWireMock.register(mappingBuilder);
ResponseEntity<String> responseEntity =
new RestTemplate(requestFactory)
.getForEntity(this.targetBaseUrl + MOCK_ENDPOINT, String.class);
Assert.assertEquals(responseEntity.getStatusCode(), HttpStatus.OK);
System.out.println("responseEntity: " + responseEntity.getBody());
}
#Test
public void webClientWithWireMockAsProxyServer() {
var client = HttpClient.create()
.tcpConfiguration(
tcpClient ->
tcpClient.proxy(
proxy -> {
proxy
.type(ProxyProvider.Proxy.HTTP)
.host(HOST)
.port(this.proxyServer.port());
}));
var webClient = WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(client))
.build();
final var reqPatternBuilder =
RequestPatternBuilder.newRequestPattern(
RequestMethod.GET, WireMock.urlEqualTo(MOCK_ENDPOINT));
final var mappingBuilder =
WireMock.get(WireMock.urlEqualTo(reqPatternBuilder.build().getUrl()));
reqPatternBuilder
.withHeader(HttpHeaders.ACCEPT, WireMock.containing(MediaType.APPLICATION_JSON_VALUE))
.withHeader(
HttpHeaders.ACCEPT_CHARSET,
WireMock.containing(StandardCharsets.UTF_8.name().toUpperCase()));
mappingBuilder.willReturn(
WireMock.aResponse()
.withStatus(HttpStatus.OK.value())
.withBody("{ \"success\": true }")
.withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE));
this.targetWireMock.register(mappingBuilder);
var response =
webClient.get().uri(this.targetBaseUrl + MOCK_ENDPOINT).exchange().block().bodyToMono(String.class);
response.subscribe(x -> System.out.println("x:" + x));
}
}
I complains with error java.net.UnknownHostException: <proxy server>: nodename nor servname provided, or not known. Is there a way to mock the wiremock proxy server, rather than running an actual server for this. I also want to put validations in the proxy server for request-responses.

Wiremock doesn't support HTTP CONNECT method. You can try Hoverfly as an alternative to Wiremock. There is a github issue if you're interested in details.

Related

Unable to run JUnit5 PACT test. No method annotated with #Pact was found on test class ConsumerContractTest for provider ''

I'm trying to get a PACT test running on JUnit5. We use JUnit4 for others, but this one will be JUnit5. The error occurs when running the JUnit5 test using the pact annotation on the RequestResponsePact method.
Error : No method annotated with #Pact was found on test class ConsumerContractTest for provider ''.
I've seen Basic Pact/Junit5 Test Setup fails. No method annotated with #Pact was found for provider error, but this is issue was due to the #PactTestFor(pactMethod = "examplePact") not matching the #Pact method name. But on my code it does match.
I can't seem to figure out why I get the error and especially why the error has an empty provider(provider '') despite defining one("some-provider").
Example code :
import au.com.dius.pact.consumer.MockServer
import au.com.dius.pact.consumer.Pact
import au.com.dius.pact.consumer.dsl.PactDslJsonArray
import au.com.dius.pact.consumer.dsl.PactDslWithProvider
import au.com.dius.pact.consumer.junit5.PactConsumerTestExt
import au.com.dius.pact.consumer.junit5.PactTestFor
import au.com.dius.pact.model.RequestResponsePact
import groovyx.net.http.RESTClient
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.http.HttpStatus
#ExtendWith(PactConsumerTestExt.class)
class ConsumerContractTest {
#Pact(consumer = "some-consumer", provider = "some-provider")
RequestResponsePact examplePact(PactDslWithProvider builder) {
builder
.given("provider state")
.uponReceiving("Contract description")
.method("GET")
.matchPath("/endpoint")
.willRespondWith()
.status(200)
.headers(["Content-Type": "application/vnd.pnf.v1+json"])
.body(new PactDslJsonArray())
.toPact()
}
#Test
#PactTestFor(pactMethod = "examplePact")
void exampleTest(MockServer mockServer) {
def client = new RESTClient(mockServer.getUrl())
}
}
Not sure if that's just the gist you've posted here but I see the return word missing and also the #PactTestFor annotation missing the provider and version. Here is an example I have that works for my project.
import au.com.dius.pact.consumer.dsl.DslPart;
import au.com.dius.pact.consumer.dsl.PactDslJsonBody;
import au.com.dius.pact.consumer.dsl.PactDslWithProvider;
import au.com.dius.pact.consumer.junit5.PactConsumerTestExt;
import au.com.dius.pact.consumer.junit5.PactTestFor;
import au.com.dius.pact.core.model.PactSpecVersion;
import au.com.dius.pact.core.model.RequestResponsePact;
import au.com.dius.pact.core.model.annotations.Pact;
import io.restassured.response.Response;
import io.restassured.specification.RequestSpecification;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import java.util.HashMap;
import java.util.Map;
import static com.example.mbbackend.config.Constants.*;
import static com.example.mbbackend.util.Utils.getRequestSpecification;
import static org.junit.jupiter.api.Assertions.assertEquals;
#ExtendWith(PactConsumerTestExt.class)
class GetActorIT {
Map<String, String> headers = new HashMap<>();
String path = "/api/mb/actor/";
#Pact(provider = PACT_PROVIDER, consumer = PACT_CONSUMER)
public RequestResponsePact createPact(PactDslWithProvider builder) {
headers.put("Content-Type", "application/json");
DslPart bodyReturned = new PactDslJsonBody()
.uuid("id", "1bfff94a-b70e-4b39-bd2a-be1c0f898589")
.stringType("name", "A name")
.stringType("family", "A family")
.stringType("imageUrl", "http://anyimage.com")
.close();
return builder
.given("A request to retrieve an actor")
.uponReceiving("A request to retrieve an actor")
.pathFromProviderState(path + "${actorId}", path + "1bfff94a-b70e-4b39-bd2a-be1c0f898589")
.method("GET")
.headers(headers)
.willRespondWith()
.status(200)
.body(bodyReturned)
.toPact();
}
#Test
#PactTestFor(providerName = PACT_PROVIDER, port = PACT_PORT, pactVersion = PactSpecVersion.V3)
void runTest() {
//Mock url
RequestSpecification rq = getRequestSpecification().baseUri(MOCK_PACT_URL).headers(headers);
Response response = rq.get(path + "1bfff94a-b70e-4b39-bd2a-be1c0f898589");
assertEquals(200, response.getStatusCode());
}
}

Connecting to ES with Spring Data Elasticsearch (reactive) gives error host not reachable

I'm running on an aws-elasticsearch (with OpenSearch 1.1.x) service and im trying to connect with it from a spring application using spring-data-elasticsearch, according to the doc i configured the bean as it says.
on my local i used a ssh tunnel from my aws account.
i used this command:
ssh -4 -i my-creds.pem ec2-user#xxxx.xxxx.xxxx.xxxx -N -L 9200:vpc-my-custom-domain-etc.us-east-1.es.amazonaws.com:443
so i can connect with OpenSearch dashboard over localhost in my browser through port 9200.
Using the OpenSearch RestHighLevelClient from OpenSearch and disabling the ssl i can connect and it works just fine here the config with OS RHLC:
import org.apache.http.HttpHost;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.Map;
public class OSSCLientWorks{
private static final Logger log = LoggerFactory.getLogger(ClientAutoWrapper.class);
public void request(String indexName, Map<String, Object> doc) throws IOException {
//Create a client.
RestClientBuilder builder = RestClient.builder(new HttpHost("localhost", 9200, "https"))
.setHttpClientConfigCallback(httpClientBuilder -> httpClientBuilder
//.addInterceptorFirst(interceptor) //-> for AwsRequestInterceptor due to some struggles i had, not necessary to work with localhost
.setSSLHostnameVerifier((hostname, session) -> true));
try (RestHighLevelClient hlClient = new RestHighLevelClient(builder)) {
CreateIndexRequest createIndexRequest = new CreateIndexRequest(indexName);
var createIndexResp = hlClient.indices().create(createIndexRequest, RequestOptions.DEFAULT);
log.info("Create index resp {}", createIndexResp);
IndexRequest indexRequest = new IndexRequest(createIndexResp.index())
.id(String.valueOf(doc.get("id")))
.source(doc);
var response = hlClient.index(indexRequest, RequestOptions.DEFAULT);
var resp = response.toString();
log.info("response is {}", json);
}
}
}
, but when i try with spring and its reactive client i get this error:
reactor.core.Exceptions$ErrorCallbackNotImplemented: org.springframework.data.elasticsearch.client.NoReachableHostException: Host 'localhost:9200' not reachable. Cluster state is offline.
Caused by: org.springframework.data.elasticsearch.client.NoReachableHostException: Host 'localhost:9200' not reachable. Cluster state is offline.
here is the config i used to work with spring-data-elasticsearch:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.elasticsearch.client.ClientConfiguration;
import org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient;
import org.springframework.data.elasticsearch.client.reactive.ReactiveRestClients;
import org.springframework.data.elasticsearch.config.AbstractReactiveElasticsearchConfiguration;
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations;
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchTemplate;
import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories;
import org.springframework.data.elasticsearch.repository.config.EnableReactiveElasticsearchRepositories;
#Configuration
#EnableReactiveElasticsearchRepositories(basePackages = {"com.elastic.repo"})
public class ElasticRestHighLevelClientConfig extends AbstractReactiveElasticsearchConfiguration {
#Override
#Bean
public ReactiveElasticsearchClient reactiveElasticsearchClient() {
final ClientConfiguration clientConfiguration = ClientConfiguration.builder()
.connectedTo("localhost:9200")
.build();
return ReactiveRestClients.create(clientConfiguration);
}
#Bean
public ReactiveElasticsearchOperations elasticsearchOperations(ReactiveElasticsearchClient reactiveElasticsearchClient) {
return new ReactiveElasticsearchTemplate(reactiveElasticsearchClient);
}
}
i also tried some solutions other people posted here on SO and Github, but the problem persists, does anybody have a workaround for this? what am i doing wrong?
here i did a demo for the trouble
Thank you very much in advance!
EDIT: clarity
You have to configure to use SSL for the reactive client with one of the usingSsl()methods:
#Override
#Bean
public ReactiveElasticsearchClient reactiveElasticsearchClient() {
final ClientConfiguration clientConfiguration = ClientConfiguration.builder()
.connectedTo("localhost:9200")
.usingSsl() // <--
.build();
return ReactiveRestClients.create(clientConfiguration);
}
NoReachableHostException is just a generic error they throw when lookupActiveHost(HostProvider interface) fails.
You should debug what happens before - for you it's probably here:
#Override
public Mono clusterInfo() {
return createWebClient(endpoint) //
.head().uri("/") //
.exchangeToMono(it -> {
if (it.statusCode().isError()) {
state = ElasticsearchHost.offline(endpoint);
} else {
state = ElasticsearchHost.online(endpoint);
}
return Mono.just(state);
}).onErrorResume(throwable -> {
state = ElasticsearchHost.offline(endpoint);
clientProvider.getErrorListener().accept(throwable);
return Mono.just(state);
}).map(elasticsearchHost -> new ClusterInformation(Collections.singleton(elasticsearchHost)));
}
see what is the real exception on error resume.
I bet you will get SSL Handshake Exception, you can fix it in the clientConfiguration with .usingSsl({SSL CONTEXT HERE})
You can create insecure context like this(convert to java if needed):
SSLContext.getInstance("TLS")
.apply { init(null, InsecureTrustManagerFactory.INSTANCE.trustManagers, SecureRandom()) }

vertx get to external address

Hi I'm trying to make a request to external service with the use of httpClient vert.x but I keep getting error: Search domain query failed. Original hostname: 'google.com' failed to resolve 'google.com'
What am i missing in the code below? I'm not sure about those AddressResolverOptions, I have read about them but still I'm not sure what they are responsible for. I'd like to make the request the simplest possible way but the more I investigate the more difficult it seems
package io.vertx.starter;
import io.vertx.core.Vertx;
import io.vertx.core.VertxOptions;
import io.vertx.core.dns.AddressResolverOptions;
import io.vertx.core.http.HttpClientOptions;
import io.vertx.ext.unit.Async;
import io.vertx.ext.unit.TestContext;
import io.vertx.ext.unit.junit.VertxUnitRunner;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.net.URL;
import java.util.Collections;
#RunWith(VertxUnitRunner.class)
public class MainVerticleTest {
private Vertx vertx;
#Before
public void setUp(TestContext tc) {
vertx = Vertx.vertx(new VertxOptions().setAddressResolverOptions(
new AddressResolverOptions().addSearchDomain("google.com").addSearchDomain("bar.com"))
);
// vertx = Vertx.vertx();
vertx.deployVerticle(MainVerticle.class.getName(), tc.asyncAssertSuccess());
}
#After
public void tearDown(TestContext tc) {
vertx.close(tc.asyncAssertSuccess());
}
#Test
public void testThatTheServerIsStarted(TestContext tc) {
final HttpClientOptions httpClientOptions = new HttpClientOptions();
httpClientOptions.setConnectTimeout(300);
httpClientOptions.setIdleTimeout(5);
// httpClientOptions.` `
httpClientOptions.setSsl(true).setTrustAll(true);
// final URL url = new URL("currentUrl");
Async async = tc.async();
vertx.createHttpClient(httpClientOptions).getNow(
443,"google.com", "/", response -> {
System.out.println(response.statusCode());
async.complete();
});
//
// Async async = tc.async();
// vertx.createHttpClient().getNow(8080, "localhost", "/", response -> {
// tc.assertEquals(response.statusCode(), 200);
// response.bodyHandler(body -> {
// tc.assertTrue(body.length() > 0);
// async.complete();
// });
// });
}
}

Vertx 3.6.3: Unable to launch HTTPS server with PFX option

I am using Vertx 3.6.3. I am trying to run an HTTPS server verticle, but unfortunately, verticle is not getting deployed. Could you please let me know where I am doing it wrong?
Here is my verticle:
HTTPSVerticle:
import io.vertx.core.AbstractVerticle;
import io.vertx.core.Future;
import io.vertx.core.http.HttpServerOptions;
import io.vertx.core.json.JsonObject;
import io.vertx.core.net.PfxOptions;
public class HTTPSVerticle extends AbstractVerticle {
#Override
public void start(Future<Void> httpsServerStarted) throws Exception {
int port = config().getJsonObject("http", new JsonObject()).getInteger("port", 8000);
boolean useSsl = config().getJsonObject("http", new JsonObject()).getBoolean("useSsl", false);
String sslCertPath = config().getJsonObject("http", new JsonObject()).getString("sslCertPath", "");
String sslCertPassword = config().getJsonObject("http", new JsonObject()).getString("sslCertPassword", "");
HttpServerOptions httpServerOptions = new HttpServerOptions();
System.out.println(useSsl);
if (useSsl)
httpServerOptions
.setSsl(true)
//.setClientAuth(ClientAuth.REQUIRED)
.setPfxTrustOptions(
new PfxOptions().setPath(sslCertPath).setPassword(sslCertPassword)
);
vertx.createHttpServer(httpServerOptions).requestHandler(httpReq -> {
httpReq.response().end("Hello encrypted world");
}).listen(port, fut -> {
if (fut.succeeded()) {
System.out.println("Verticle now listening on port: " + port);
httpsServerStarted.complete();
}
else {
httpsServerStarted.fail(fut.cause());
System.out.println("Error while starting HTTP server");
}
});
}
}
Here is my test case:
TestHTTPSVerticle:
import io.vertx.core.DeploymentOptions;
import io.vertx.core.Vertx;
import io.vertx.core.http.HttpClientOptions;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.unit.Async;
import io.vertx.ext.unit.TestContext;
import io.vertx.ext.unit.junit.VertxUnitRunner;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
#RunWith(VertxUnitRunner.class)
public class TestHTTPSVerticle {
private static Vertx vertx;
#BeforeClass
public static void setUp(TestContext context) {
DeploymentOptions opts = new DeploymentOptions()
.setConfig(new JsonObject().put("http", new JsonObject()
.put("useSsl", true)
.put("sslCertPath", "test.pfx")
.put("sslCertPassword", "abcd")));
vertx = Vertx.vertx();
vertx.deployVerticle(HTTPSVerticle.class.getName(), opts, context.asyncAssertSuccess());
}
#AfterClass
public static void tearDown(TestContext context) {
vertx.close(context.asyncAssertSuccess());
}
#Test
public void testHttpsServerMessage(TestContext context) {
Async async = context.async();
System.out.println("Connecting to server...");
vertx.createHttpClient().get(8000, "localhost", "/loremipsum", respHandler -> respHandler.bodyHandler(respBody -> {
System.out.println(respBody);
context.assertTrue(respBody.toString().equals("Hello encrypted world"));
async.complete();
})).end();
}
}
Its not letting me submit it without elaborating, so redundant elaboration follows:
I am using vertx config mechanism to fetch port, useSsl, sslCertPath and sslCertPassword
I am using HttpServerOptions for configuring SSL settings for http server
When server is started successfully, it should print Verticle now listening on port: 8000
In case, server fails to start, it should print Error while starting HTTP server
But, It never invokes listen's handler with AsyncResult.

Spring Boot 5 WebClient Validate HTTPStatus first before checking HTTP Response Header

I'm trying to confirm the value of an HTTP response header with Spring 5 WebClient, but only if the web call responds with an HTTP 200 status code. In this use case if authentication is not successful, the API call returns with an HTTP 401 without the response header present. I have the following code below which functionally works, but it is making the web call twice (because I'm blocking twice). Short of just blocking on the HTTP response header only, and putting a try/catch for an NPE when the header isn't present, is there any "cleaner" way to do this?
import java.net.URI;
import java.time.Duration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.ClientRequest;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.ExchangeFunction;
import org.springframework.web.reactive.function.client.ExchangeFunctions;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
#SpringBootApplication
public class ContentCheckerApplication {
private static final Logger LOGGER = LoggerFactory.getLogger(ContentCheckerApplication.class);
private ExchangeFunction exchange = ExchangeFunctions.create(new ReactorClientHttpConnector());
public static void main(String[] args) {
SpringApplication app = new SpringApplication(ContentCheckerApplication.class);
// prevent SpringBoot from starting a web server
app.setWebApplicationType(WebApplicationType.NONE);
app.run(args);
}
#Bean
public CommandLineRunner myCommandLineRunner() {
return args -> {
// Our reactive code will be declared here
LinkedMultiValueMap<String, String> formData = new LinkedMultiValueMap<String, String>();
formData.add("username", args[2]);
formData.add("password", args[3]);
ClientRequest request = ClientRequest.method(HttpMethod.POST, new URI(args[0]+"/api/token"))
.body(BodyInserters.fromFormData(formData)).build();
Mono<ClientResponse> mresponse = exchange.exchange(request);
Mono<String> mnewToken = mresponse.map(response -> response.headers().asHttpHeaders().getFirst("WSToken"));
LOGGER.info("Blocking for status code...");
HttpStatus statusCode = mresponse.block(Duration.ofMillis(1500)).statusCode();
LOGGER.info("Got status code!");
if (statusCode.value() == 200) {
String newToken = mnewToken.block(Duration.ofMillis(1500));
LOGGER.info("Auth token is: " + newToken);
} else {
LOGGER.info("Unable to authenticate successfully! Status code: "+statusCode.value());
}
};
}
}
Thanks to comments from #M. Deinum to guide me, I have the following code which is workable now.
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.ExchangeFunction;
import org.springframework.web.reactive.function.client.ExchangeFunctions;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
#SpringBootApplication
public class ContentCheckerApplication {
private static final Logger LOGGER = LoggerFactory.getLogger(ContentCheckerApplication.class);
private ExchangeFunction exchange = ExchangeFunctions.create(new ReactorClientHttpConnector());
public static void main(String[] args) {
SpringApplication app = new SpringApplication(ContentCheckerApplication.class);
// prevent SpringBoot from starting a web server
app.setWebApplicationType(WebApplicationType.NONE);
app.run(args);
}
#Bean
public CommandLineRunner myCommandLineRunner() {
return args -> {
// Change some Netty defaults
ReactorClientHttpConnector connector = new ReactorClientHttpConnector(
options -> options.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 2000)
.compression(true)
.afterNettyContextInit(ctx -> {
ctx.addHandlerLast(new ReadTimeoutHandler(1500, TimeUnit.MILLISECONDS));
}));
LinkedMultiValueMap<String, String> formData = new LinkedMultiValueMap<String, String>();
formData.add("username", args[2]);
formData.add("password", args[3]);
WebClient webClient = WebClient.builder().clientConnector(connector).build();
Mono<String> tokenResult = webClient.post()
.uri( args[0] + "/api/token" )
.body( BodyInserters.fromFormData(formData))
.exchange()
.onErrorMap(ContentCheckerApplication::handleAuthTokenError)
.map(response -> {
if (HttpStatus.OK.equals(response.statusCode())) {
return response.headers().asHttpHeaders().getFirst("WSToken");
} else {
return "";
}
});
LOGGER.info("Subscribing for the result and then going to sleep");
tokenResult.subscribe(ContentCheckerApplication::handleAuthTokenResponse);
Thread.sleep(3600000);
};
}
private static Throwable handleAuthTokenError(Throwable e) {
LOGGER.error("Exception caught trying to process authentication token. ",e);
ContentCheckerApplication.handleAuthTokenResponse("");
return null;
}
private static void handleAuthTokenResponse(String newToken) {
LOGGER.info("Got status code!");
if (!newToken.isEmpty()) {
LOGGER.info("Auth token is: " + newToken);
} else {
LOGGER.info("Unable to authenticate successfully!");
}
System.exit(0);
}
}

Categories