WebClient - adding defaultHeaders - java

I'm trying to put multiple headers into defaultHeaders(), But I don't have idea how to create Consumer object from return of createHeaders() method
this.someWebClient = WebClient.builder()
.baseUrl(someConfiguration.getApiUrl())
.clientConnector(buildTimeoutConnector())
.defaultHeaders(????) // Consumer<HttpHeaders>
.build();
I can build my header in that way:
private HttpHeaders createHeaders(String token) {
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.ACCEPT, V1_PUBLIC);
headers.add(HttpHeaders.HOST, "abc");
headers.add(HttpHeaders.AUTHORIZATION, "Bearer " + token);
return headers;
}
but how to wrap it into Consumer?

Clearly the method doc says that it needs a Consumer of some Type. So you can create an anonymous class implementing the Consumer interface or use lambda expression like this:
Using anonymous inner class:
this.someWebClient = WebClient.builder()
.baseUrl(someConfiguration.getApiUrl())
.clientConnector(buildTimeoutConnector())
.defaultHeaders(new Consumer<HttpHeaders>() {
#Override
public void accept(HttpHeaders httpHeaders) {
httpHeaders.addAll(createHeaders(token));
}
})
.build();
Using lambda:
this.someWebClient = WebClient.builder()
.baseUrl(someConfiguration.getApiUrl())
.clientConnector(buildTimeoutConnector())
.defaultHeaders(httpHeaders -> {
httpHeaders.addAll(createHeaders(token));
})
.build();

Use this.
#Bean
public WebClient webClientConfiguration() {
return WebClient
.builder()
.clientConnector(new ReactorClientHttpConnector(HttpClient.create()
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connectionTimeout)
.responseTimeout(Duration.ofMillis(responseTimeout))
))
.baseUrl(this.msConfigProperties.getApiUrl())
.defaultHeaders(httpHeaders())
.build();
}
Then Consumer function
private Consumer<HttpHeaders> httpHeaders(){
return headers -> {
headers.set(HEADER_CONTENT_TYPE, String.valueOf(MediaType.APPLICATION_JSON));
headers.setBasicAuth(this.msConfigProperties.getUserName(),this.msConfigProperties.getPassword());
headers.set(HEADER_ACCEPT_ENCODING, String.valueOf(MediaType.APPLICATION_JSON));
};
}

private void addDefaultHeaders(final HttpHeaders headers) {
headers.add(HttpHeaders.CONTENT_TYPE, "application/json");
headers.add(HttpHeaders.ACCEPT, "application/json");
}
and then
this.someWebClient = WebClient.builder()
.baseUrl(someConfiguration.getApiUrl())
.clientConnector(buildTimeoutConnector())
.defaultHeaders(this::addDefaultHeaders)
.build();

Related

Throws java.lang.IllegalStateException: block()/blockFirst()/blockLast() when retrying invalid token

I am trying to update the token when I get a response with 401 status code.
In order to do that, I used web client. I know that this mainly used to do reactive development but since resttemplate will soon be deprecated I went for this option.
The issue I am facing is that when it does call the api endpoint to get the new token, it throws a 'java.lang.IllegalStateException: block()/blockFirst()/blockLast() '. And make sense as it stated in the exception message It is not supported in thread reactor-http-nio-3.
I saw that there is a map and flatmap option, but I couldn't figure out how to use it inside the doBeforeRetry() to make it process in a different stream.
I need to have that new token before retrying.
So the question is : How can I get the token via another call and then still do the retry ?
I was able to make it work by using a try catch but I would like to find the solution how to use it inside that retry method.
I also try to block the token request by replacing the token response by a Mono and block it by using myMono.toFuture().get() as stated here block()/blockFirst()/blockLast() are blocking error when calling bodyToMono AFTER exchange()
Here is the code :
Method responsible for the call :
public String getValueFromApi(HashMap<String, Object> filter) {
String response = "";
response = webclient
.post()
.uri(endpoint)
.header("token", token.getToken())
.bodyValue(filter)
.retrieve()
.bodyToMono(String.class)
.retryWhen(Retry.max(3).doBeforeRetry(
retrySignal -> tokenService.getTokenFromApi(env)
).filter(InvalidTokenException.class::isInstance))
.block();
return response;
}
Method that retrieve the token :
public void getTokenFromApi(Environment env) {
HashMap<String, String> requestBody = new HashMap<>();
requestBody.put("name", "name");
requestBody.put("password", "password");
String response = WebClient
.builder()
.baseUrl(BASE_PATH)
.defaultHeader(HttpHeaders.CONTENT_TYPE, "application/json")
.build()
.post()
.uri(tokenUri)
.body(BodyInserters.fromValue(requestBody))
.retrieve()
.bodyToMono(String.class)
.block();
getTokenFromResponse(response);
}
private void getTokenFromResponse(String reponse) {
JsonObject tokenObject = new Gson().fromJson(reponse, JsonObject.class);
setToken(tokenObject.get("token").getAsString());
}
WebClient Builder :
#Bean
public WebClient webClientForApi(WebClient.Builder webClientBuilder) {
return webClientBuilder
.clientConnector(new ReactorClientHttpConnector(httpClient))
.filter(errorHandler())
.filter(logRequest())
.clone()
.baseUrl(BASE_PATH)
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.defaultHeader(HttpHeaders.ACCEPT, "application/json")
.build();
}
public ExchangeFilterFunction errorHandler() {
return ExchangeFilterFunction.ofResponseProcessor(clientResponse -> {
if (clientResponse.statusCode().equals(HttpStatus.UNAUTHORIZED)) {
return Mono.error(InvalidTokenException::new);
} else if (clientResponse.statusCode() == HttpStatus.INTERNAL_SERVER_ERROR) {
return Mono.error(ApiInternalServerException::new);
} else {
return Mono.just(clientResponse);
}
});
}
private ExchangeFilterFunction logRequest() {
return ExchangeFilterFunction.ofRequestProcessor(clientRequest -> {
logger.info("Request: {} {}", clientRequest.method(), clientRequest.url());
clientRequest.headers().forEach((name, values) -> values.forEach(value -> logger.info("{}={}", name, value)));
return Mono.just(clientRequest);
});
}

spring cloud gateway predicates test cases

I have implemented the predicate factory(spring cloud gateway) to validate the headers and I want to add the test cases for that
public Predicate<ServerWebExchange> apply(Config config ) {
return (ServerWebExchange t) -> {
List<String> Header = t.getRequest().getHeaders().get("abcd");
#business logic
return true;
};
}
I want to include the test cases for the predicate factory above.
I tried the test case as below
#Before
public void prepareStubs() {
stubFor(any(urlPathEqualTo("/abcd")).willReturn(aResponse().withBody("ABCD")));
}
#Test
public void testGatewayRouting() throws JsonMappingException, JsonProcessingException {
HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", "Bearer eyJraWQiOiIiLCJ0eXAiOiJKV1QiLCJhbGciOiJSUzUxMiJ9");
HttpEntity<?> entity = new HttpEntity<>(headers);
UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromHttpUrl(createURLWithPort("/abcd"));
ResponseEntity<String> response = restTemplate.exchange(uriBuilder.toUriString(), HttpMethod.GET, entity,
String.class);
assertEquals(200, response.getStatusCodeValue());
assertEquals("ABCD", response.getBody());
}

Replacing RestTemplate with WebClient

I have a controller that uses RestTemplate to get data from several rest endpoints. Since RestTemplate is blocking, my web page is taking long time to load. In order to increase the performance, I am planning to replace all my usages of RestTemplate with WebClient. One of the methods I currently have that uses RestTemplate is as below.
public List<MyObject> getMyObject(String input){
URI uri = UriComponentsBuilder.fromUriString("/someurl")
.path("123456")
.build()
.toUri();
RequestEntity<?> request = RequestEntity.get(uri).build();
ParameterizedTypeReference<List<MyObject>> responseType = new ParameterizedTypeReference<List<MyObject>>() {};
ResponseEntity<List<MyObject>> responseEntity = restTemplate.exchange(request, responseType);
MyObject obj = responseEntity.getBody();
}
Now I want to replace my above method to use WebClient but I am new to WebClient and not sure where to start. Any direction and help is appreciated.
To help you I am giving you example how we can replace restTemple with webClient. I hope you have already setup your pom.xml
Created a Configuration class.
#Slf4j
#Configuration
public class ApplicationConfig {
/**
* Web client web client.
*
* #return the web client
*/
#Bean
WebClient webClient() {
return WebClient.builder()
.filter(this.logRequest())
.filter(this.logResponse())
.build();
}
private ExchangeFilterFunction logRequest() {
return ExchangeFilterFunction.ofRequestProcessor(clientRequest -> {
log.info("WebClient request: {} {} {}", clientRequest.method(), clientRequest.url(), clientRequest.body());
clientRequest.headers().forEach((name, values) -> values.forEach(value -> log.info("{}={}", name, value)));
return Mono.just(clientRequest);
});
}
private ExchangeFilterFunction logResponse() {
return ExchangeFilterFunction.ofResponseProcessor(clientResponse -> {
log.info("WebClient response status: {}", clientResponse.statusCode());
return Mono.just(clientResponse);
});
}
}
Plus a service class calling WebClient
#Component
#RequiredArgsConstructor
public class MyObjectService {
private final WebClient webClient;
public Mono<List<Object>> getMyObject(String input) {
URI uri = UriComponentsBuilder.fromUriString("/someurl")
.path("123456")
.build()
.toUri();
ParameterizedTypeReference<List<MyObject>> responseType = new ParameterizedTypeReference<List<MyObject>>() {
};
return this.webClient
.get()
.uri(uri)
.exchange()
.flatMap(response -> response.bodyToMono(responseType));
}
}
This will give you a non blocking Mono of List<MyObject>, you can also extract body to flux by using response.bodyToFlux(responseType)
I hope this will give you a base to explore more.

Send Request Parameters in Spring Web client

I want to send link Request Parameters in Spring WebClient request link. For example:
https://www.test.com/notification?con=41280440000097&sec=1232
I tried this code:
WebClient client;
Map<String, String> map = new HashMap<>();
public Mono<Response> execute(Transaction transaction) {
map.put("some_key", "some_value");
Mono<PaymentTransaction> transactionMono = Mono.just(transaction);
return client.post().uri("/notification", token)
.accept(MediaType.APPLICATION_XML)
.contentType(MediaType.APPLICATION_XML)
.body(transactionMono, Transaction.class)
.attributes(Consumer<map>)
.retrieve()
.bodyToMono(Response.class);
}
But when I try to set the map I get Syntax error on token ">", Expression expected after this
What is the proper way to implement this without hardcoding the values into the address?
Does this work?
public Mono<PaymentResponse> execute(PaymentTransaction transaction, WebClient client) {
long conn = 1L;
int sec = 1232;
Mono<PaymentTransaction> transactionMono = Mono.just(transaction);
return client.post()
.uri(uriBuilder -> uriBuilder.scheme("https").host("www.test.com")
.path("notification")
.queryParam("con", conn)
.queryParam("sec", sec)
.build())
.accept(MediaType.APPLICATION_XML)
.contentType(MediaType.APPLICATION_XML)
.body(transactionMono, PaymentTransaction.class)
.retrieve()
.bodyToMono(PaymentResponse.class);
}
This works for me
WebClient client;
Map<String, String> map = new HashMap<>();
public Mono<Response> execute(Transaction transaction) {
Map<String,String> attributeMap = new HashMap<>();
attributeMap.put("some_key", "some_value");
Mono<PaymentTransaction> transactionMono = Mono.just(transaction);
return client.post().uri("/notification", token)
.accept(MediaType.APPLICATION_XML)
.contentType(MediaType.APPLICATION_XML)
.body(transactionMono, Transaction.class)
.attributes(map -> map.putAll(attributeMap))
.retrieve()
.bodyToMono(Response.class);
}

How to define a Header to all request using Retrofit?

I'm looking for a solution to define a unique Header to use in all requests. Today I use #Header to each request did pass like parameter but I want define only header that works in all requests without to need pass like a parameter, for example fixing this Header on my requests #GET and #POST
Today I use this. Note that each request #GET I need define Header as parameter.
//interface
#GET("/json.php")
void getUsuarioLogin(
#Header("Authorization") String token,
#QueryMap Map<String, String> params,
Callback<JsonElement> response
);
//interface
#GET("/json.php")
void addUsuario(
#Header("Authorization") String token,
#QueryMap Map<String, String> params,
Callback<JsonElement> response
);
//using
public void getUsuarioLogin(){
Map<String, String> params = new HashMap<String, String>();
params.put("email", "me#mydomain.com");
params.put("senha", ConvertStringToMD5.getMD5("mypassword"));
RestAdapter adapter = new RestAdapter.Builder()
.setLogLevel(RestAdapter.LogLevel.FULL)
.setEndpoint(WebServiceURL.getBaseWebServiceURL())
.build();
UsuarioListener listener = adapter.create(UsuarioListener.class);
listener.getUsuarioLogin(
//header
"Basic " + BasicAuthenticationRest.getBasicAuthentication(),
params,
new Callback<JsonElement>() {
#Override
public void success(JsonElement arg0, Response arg1) {
Log.i("Usuario:", arg0.toString() + "");
}
#Override
public void failure(RetrofitError arg0) {
Log.e("ERROR:", arg0.getLocalizedMessage());
}
});
}
//using
public void addUsuario(){
Map<String, String> params = new HashMap<String, String>();
params.put("name", "Fernando");
params.put("lastName", "Paiva");
RestAdapter adapter = new RestAdapter.Builder()
.setLogLevel(RestAdapter.LogLevel.FULL)
.setEndpoint(WebServiceURL.getBaseWebServiceURL())
.build();
UsuarioListener listener = adapter.create(UsuarioListener.class);
listener.addUsuario(
//header
"Basic " + BasicAuthenticationRest.getBasicAuthentication(),
params,
new Callback<JsonElement>() {
#Override
public void success(JsonElement arg0, Response arg1) {
Log.i("Usuario:", arg0.toString() + "");
}
#Override
public void failure(RetrofitError arg0) {
Log.e("ERROR:", arg0.getLocalizedMessage());
}
});
}
Official document:
Headers that need to be added to every request can be specified using a RequestInterceptor. The following code creates a RequestInterceptor that will add a User-Agent header to every request.
RequestInterceptor requestInterceptor = new RequestInterceptor() {
#Override
public void intercept(RequestFacade request) {
request.addHeader("User-Agent", "Retrofit-Sample-App");
}
};
RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint("https://api.github.com")
.setRequestInterceptor(requestInterceptor)
.build();
In Retrofit 2, you need to intercept the request on the network layer provided by OkHttp
OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
httpClient.addInterceptor(new Interceptor() {
#Override
public Response intercept(Interceptor.Chain chain) throws IOException {
Request original = chain.request();
Request request = original.newBuilder()
.header("User-Agent", "Your-App-Name")
.header("Accept", "application/vnd.yourapi.v1.full+json")
.method(original.method(), original.body())
.build();
return chain.proceed(request);
}
}
OkHttpClient client = httpClient.build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(API_BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.build();
Check this, it explains the differences very well.
Depending on your OkHttp lib:
OkHttpClient httpClient = new OkHttpClient();
httpClient.networkInterceptors().add(new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request().newBuilder().addHeader("User-Agent", System.getProperty("http.agent")).build();
return chain.proceed(request);
}
});
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(API_BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(httpClient)
.build();
As the other answers have described, you need a RequestInterceptor. Luckily, this interface has a single method, so Java 8 and above will treat it as a functional interface and let you implement it with a lambda. Simple!
For example, if you're wrapping a specific API and need a header for each endpoint, you might do this when you build your adapter:
RestAdapter whatever = new RestAdapter.Builder().setEndpoint(endpoint)
.setRequestInterceptor(r -> r.addHeader("X-Special-Vendor-Header", "2.0.0"))
.build()
Here's the solution for adding header using retrofit 2.1. We need to add interceptor
public OkHttpClient getHeader(final String authorizationValue ) {
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient okClient = new OkHttpClient.Builder()
.addInterceptor(interceptor)
.addNetworkInterceptor(
new Interceptor() {
#Override
public Response intercept(Interceptor.Chain chain) throws IOException {
Request request = null;
if (authorizationValue != null) {
Log.d("--Authorization-- ", authorizationValue);
Request original = chain.request();
// Request customization: add request headers
Request.Builder requestBuilder = original.newBuilder()
.addHeader("Authorization", authorizationValue);
request = requestBuilder.build();
}
return chain.proceed(request);
}
})
.build();
return okClient;
}
Now in your retrofit object add this header in the client
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(url)
.client(getHeader(authorizationValue))
.addConverterFactory(GsonConverterFactory.create())
.build();

Categories