Spring 5 WebClient only making Http call when using .block() after .exchange() - spring

This call works as expected and makes the POST successfully:
public class MyService implements IMyService {
private final WebClient webClient;
private final String url;
MyService(#Qualifier("web-client") WebClient webClient,
String url) {
this.webClient = webClient;
this.url = url;
}
#SneakyThrows
#Override
public void execute(Long jobId) {
MultiValueMap<String, String> requestParms = new LinkedMultiValueMap<>();
requestParms.add("arguments", "--batchJobId=" + jobId.toString());
HttpEntity<MultiValueMap<String, String>> requestEntity =
new HttpEntity<>(requestParms, null);
final WebClient.ResponseSpec responseSpec = webClient.post()
.uri(new URI(url + "/tasks/executions"))
.body(BodyInserters.fromMultipartData(requestParms))
.exchange()
.block();
}
}
Inside the configuration class:
#Bean
#Qualifier("web-client")
public WebClient getWebClient() {
return WebClient.builder()
.filter(basicAuthentication("user", "pass"))
.filter(printLnFilter())
.build();
}
private ExchangeFilterFunction printLnFilter() {
return (request, next) -> {
System.out.println("\n\n" + request.method().toString().toUpperCase() + ":\n\nURL:"
+ request.url().toString() + ":\n\nHeaders:" + request.headers().toString() + "\n\nAttributes:"
+ request.attributes() + "\n\n");
return next.exchange(request);
};
}
In the example above, we see the URL, Attributes, and Headers logged and the Http call success fully made. However, just removing the block() call results in no call ever being made, no logs:
// No call made
final WebClient.ResponseSpec responseSpec = webClient.post()
.uri(new URI(url + "/tasks/executions"))
.body(BodyInserters.fromMultipartData(requestParms))
.exchange();

That's because it's non blocking...
From Spring Docs:
Simply put, WebClient is an interface representing the main entry
point for performing web requests.
It has been created as a part of the Spring Web Reactive module and
will be replacing the classic RestTemplate in these scenarios. The new
client is a reactive, non-blocking solution that works over the
HTTP/1.1 protocol.
It's an implementation using the Reactive Streams concept through the Project Reactor implementation

Related

Getting null, when i make a external post endpoint using Spring webclient

Getting a null when i make call to post endpoint using spring webclient
I tried using webclient post end point. Got null instead og Object as return type
final int size = 16 * 1024 * 1024;
final ExchangeStrategies strategies = ExchangeStrategies.builder()
.codecs(codecs -> codecs.defaultCodecs().maxInMemorySize(size))
.build();
#Bean
public WebClient webClient() {
return WebClient
.builder()
.exchangeStrategies(strategies)
.build();
}
Object = countryz = webClient.post()
.uri(new URI("https://countriesnow.space/api/v0.1/countries/population"))
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromObject(country))
.retrieve().bodyToMono(Object.class).block();
Create Weblcient Bean
#Bean
public WebClient webClient() {
final int size = 16 * 1024 * 1024;
final ExchangeStrategies strategies = ExchangeStrategies.builder()
.codecs(codecs -> codecs.defaultCodecs().maxInMemorySize(size))
.build();
return WebClient.builder()
.exchangeStrategies(strategies)
.build();
}
In your service class
#Autowired
private WebClient webClient;
Object countryz = webClient.post()
.uri("https://countriesnow.space/api/v0.1/countries/population")
.header("cache-control", "no-cache")
.header("content-type", "application/json")
.body(BodyInserters.fromObject(Collections.singletonMap("country", "nigeria")))
.retrieve().bodyToMono(Object.class).block();

Why isn't my Spring WebFilter being applied to my webclient API requests?

I am trying to create a WebFilter for my Spring App's web client requests so that a token will be added within the headers of the request. It seems that my WebFilter isn't ever called since the println I've added in the filter is never printed out.
This is my WebFilter
#Component
public class Auth0RequestFilter implements WebFilter {
#Value("${auth0.audiences}")
private Set<String> auth0Audiences;
#Autowired
Auth0Client auth0Client;
#Override
public Mono<Void> filter(ServerWebExchange serverWebExchange,
WebFilterChain webFilterChain) {
String audience = serverWebExchange.getRequest().getURI().getHost();
System.out.println("We've reached this piece of code");
if(auth0Audiences.contains(audience)) {
String accessToken = auth0Client.getAccessToken(audience);
serverWebExchange.getRequest().getHeaders().add("authorization", "Bearer ".concat(accessToken));
}
return webFilterChain.filter(serverWebExchange);
}
}
And this is my API request:
#Component
public class TestAPICall {
final Auth0RequestFilter auth0RequestFilter;
public TestAPICall(Auth0RequestFilter auth0RequestFilter) {
this.auth0RequestFilter = auth0RequestFilter;
}
WebClient client = WebClient.create();
#Scheduled(fixedRate = 10000)
public void scheduleFixedRateTask() {
client.get()
.uri("https://google.com")
.retrieve()
.bodyToMono(String.class)
.block();
}
}
WebFilter is a contract for interception-style, chained processing of server Web requests, not client.
To intercept client requests you need to use ExchangeFilterFunction
ExchangeFilterFunction filterFunction = (clientRequest, nextFilter) -> {
…
return nextFilter.exchange(clientRequest);
};
and then add it to the WebClient instance
WebClient webClient = WebClient.builder()
.filter(filterFunction)
.build();

Spring Reactive WebClient Request text/csv Response content

Currently I want to call a service using WebClient which returns csv content with response header as text/csv. I want to something like below and convert the CSV response to POJO.
#Data
public class Address {
String name;
String street;
String id;
String city;
}
public class SOQLBulkJobResultResponse<T> {
List<T> records;
}
//Read SalesForceBulkAPIReponse
public SOQLBulkJobResultResponse<T> getJobResult(UriComponents uriComponents, final Class<T> clazz) {
URI uri = UriComponentsBuilder.fromUriString(baseUrl).uriComponents(uriComponents).build().toUri();
ParameterizedTypeReference<SOQLBulkJobResultResponse<T>> typeReference =
ParameterizedTypeReference.forType(ResolvableType.forClassWithGenerics(SOQLBulkJobResultResponse.class, clazz).getType());
log.info("Calling out: " + uriComponents);
return Optional.ofNullable(this.webClient.get()
.uri(uri)
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.retrieve()
.bodyToMono(typeReference)
.retry(1)
.share()
.block())
.orElseThrow(() -> new IllegalStateException("No response from /queryResponse endpoint for URI: " + uri));
}
// Get CSV Data from API
#Bean
public WebClientCustomizer webClientCustomizer() {
HttpClient httpClient = HttpClient.create().responseTimeout(Duration.ofSeconds(responseTimeoutSec))
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connectionTimeoutSec * 1000);
return webClientBuilder -> webClientBuilder
.codecs(
configurer -> {
ObjectMapper csvDecoderObjectMapper = new ObjectMapper();
csvDecoderObjectMapper.configure(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES, false);
configurer.customCodecs().registerWithDefaultConfig(new Jackson2JsonDecoder(csvDecoderObjectMapper,new MediaType("text", "csv")));
ObjectMapper encoderObjectMapper = new ObjectMapper();
encoderObjectMapper.setPropertyNamingStrategy(PropertyNamingStrategies.UPPER_CAMEL_CASE);
ObjectMapper decoderObjectMapper = new ObjectMapper();
decoderObjectMapper.setPropertyNamingStrategy(PropertyNamingStrategies.UPPER_CAMEL_CASE);
configurer.defaultCodecs().jackson2JsonEncoder(new Jackson2JsonEncoder(encoderObjectMapper, MediaType.APPLICATION_JSON));
configurer.defaultCodecs().jackson2JsonDecoder(new Jackson2JsonDecoder(decoderObjectMapper, MediaType.APPLICATION_JSON));
configurer.defaultCodecs().maxInMemorySize(10 * 1024 * 1024);
})
.filter(WebClientFilter.handleErrors())
.clientConnector(new ReactorClientHttpConnector(httpClient))
.build();
}
As I understand Jackson2JsonDecoder is not right decoder for CSV content. Need solutions/suggestion here.

403 response with Request Interceptors Feing client

I have a request interceptor config for my feign client that i will like to verify if it is configured properly. It is suppose to make request to the auth url and get a authorization taken.
This seems to work fine. But i think its not putting it to every request sent to to the resource server. Hence i keep getting 403. but when i try this on postman with the auth token generated in my code it works fine.
Bellow is the code
#Component
public class FeignC2aSystemOAuthInterceptor implements RequestInterceptor {
#Value("${c2a.oauth2.clientId}")
private String clientId;
#Value("${c2a_system.authUrl}")
private String authUrl;
#Value("${c2a.oauth2.clientSecret}")
private String clientSecret;
private String jwt;
private LocalDateTime expirationDate = LocalDateTime.now();
private final RestTemplate restTemplate;
public FeignC2aSystemOAuthInterceptor(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
#Override
public void apply(RequestTemplate requestTemplate) {
if (LocalDateTime.now().isAfter(expirationDate)) {
requestToken();
System.out.println("JUST AFTER REQUEST" + this.jwt);
}
/* use the token */
System.out.println("USE THE TOKEN" + this.jwt);
requestTemplate.header("Authorization: Bearer " + this.jwt);
}
private void requestToken() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
MultiValueMap<String, String> map = new LinkedMultiValueMap<String, String>();
map.add("client_id", clientId);
map.add("client_secret", clientSecret);
map.add("grant_type", "client_credentials");
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<MultiValueMap<String, String>>(map, headers);
ResponseEntity<C2AAuthResponse> response = restTemplate.postForEntity(authUrl, request, C2AAuthResponse.class);
this.jwt = Objects.requireNonNull(response.getBody()).getAccessToken();
LocalDateTime localDateTime = LocalDateTime.now();
this.expirationDate = localDateTime.plusSeconds(response.getBody().getExpiresIn());
}
config
#Configuration
public class FeignC2aSystemConfig {
#Bean
RestTemplate getRestTemplate() {
return new RestTemplate();
};
#Bean
FeignC2aSystemOAuthInterceptor fen () {
return new FeignC2aSystemOAuthInterceptor(getRestTemplate());
}
#Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}
and client
#FeignClient(name = "c2aSystem", url = "${c2a_system.base_url}", configuration = FeignC2aSystemConfig.class)
public interface C2AApiClient {
#PostMapping(value = C2ASystemIntegrationUrls.SEND, produces = "application/json", consumes = "application/json")
HttpServletResponse sendSms(#RequestBody C2aMessage c2aMessage);
#GetMapping(value = C2ASystemIntegrationUrls.GETLIST, produces = "application/json", consumes = "application/json")
List<MessageData> getMessages();
}
during logging i have noticed that it i call the interceptor and i can see the auth token logged using sout.
Please i would like to know if i have made a mess somewhere along the way that might cause it not to apply the authorization token to the request, thanks
You're using the RequestTemplate API wrong in this line:
requestTemplate.header("Authorization: Bearer " + this.jwt);
the header method accepts 2 parameters. First a key and then the corresponding value, and there's an overload with a String vararg. Your code will complile because of the varag parameter but won't work because it'll be handled as an empty array argument.
The implementation in the RequestTemplate is clear. If the array is empty, it'll consider that header for removal.
The fix is easy, just put the JWT token into the second argument instead of concatenating it with the header key, like this:
requestTemplate.header("Authorization: Bearer ", this.jwt);

How to Mock rest service

I'm wondering how do I mock the rest controller for the code below,
public void sendData(ID id, String xmlString, Records record) throws ValidationException{
ClientHttpRequestFactory requestFactory = new
HttpComponentsClientHttpRequestFactory(HttpClients.createDefault());
RestTemplate restTemplate = new RestTemplate(requestFactory);
List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();
messageConverters.add(new StringHttpMessageConverter(Charset.forName("UTF-8")));
restTemplate.setMessageConverters(messageConverters);
MultiValueMap<String,String> header = new LinkedMultiValueMap<>();
header.add("x-api-key",api_key);
header.add("Content-Type",content_type);
header.add("Cache-Control",cache_control);
HttpEntity<String> request = new HttpEntity<>(xmlString, header);
try {
restTemplate.postForEntity(getUri(id,record), request, String.class);
}catch (RestClientResponseException e){
throw new ValidationException("Error occurred while sending a file to some server "+e.getResponseBodyAsString());
}
}
Any suggestion would be helpful.
I tried to do something like this,
#RunWith(MockitoJUnitRunner.class)
public class Safe2RestControllerTest {
private MockRestServiceServer server;
private RestTemplate restTemplate;
private restControllerClass serviceToTest;
#Before
public void init(){
//some code for initialization of the parameters used in controller class
this.server = MockRestServiceServer.bindTo(this.restTemplate).ignoreExpectOrder(true).build();
}
#Test
public void testSendDataToSafe2() throws ValidationException, URISyntaxException {
//some code here when().then()
String responseBody = "{\n" +
" \"responseMessage\": \"Validation succeeded, message
accepted.\",\n" +
" \"responseCode\": \"SUCCESS\",\n" +
" 2\"responseID\": \"627ccf4dcc1a413588e5e2bae7f47e9c::0d86869e-663a-41f0-9f4c-4c7e0b278905\"\n" +
"}";
this.server.expect(MockRestRequestMatchers.requestTo(uri))
.andRespond(MockRestResponseCreators.withSuccess(responseBody,
MediaType.APPLICATION_JSON));
serviceToTest.sendDataToSafe2(id, xmlString, record);
this.server.verify();
}
}
This is the test case what I'm trying to do but it still calling actual rest api
As pointed out by #JBNizet, you should take a look at MockRestServiceServer. It allows you to test Spring components which are using RestTemplate to make HTTP calls.
See MockRestServiceServer and #RestClientTest.

Resources