How to set HTTP Protocol version when using Spring restTemplate.exchange? - java

I'm using Spring Framework restTemplate.exchange method to perform HTTPS GET call and map the response object to the entity class like below.
The target server gives success response only when I send the request with HTTP protocol version 1.1 (HTTP1.1). I verified it using the curl command. RestTemplate uses HTTP1.2 by default. I'm using Apache HTTP Client v4.3 here in which httpClient.getParams is deprecated. So I'm not able to set the HTTP protocol version using getParams method. Is there any other way to set the http protocol version as HTTP1.1 when using restTemplate.exchange method?
#Autowired
CloseableHttpClient httpClient;
#Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate(clientHttpRequestFactory());
return restTemplate;
}
#Bean
public HttpComponentsClientHttpRequestFactory clientHttpRequestFactory() {
HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new
HttpComponentsClientHttpRequestFactory();
clientHttpRequestFactory.setHttpClient(httpClient);
return clientHttpRequestFactory;
}
public ResponseEntity<Foo> getResponse(){
RestTemplate restTemplate = restTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity entity = new HttpEntity(headers);
ResponseEntity<Foo> response = restTemplate
.exchange(fooResourceUrl, HttpMethod.GET, entity, Foo.class);
return response;
}

Related

Post request for an api works with restTemplate but throws proxy error with webClient

I'm trying to call an api with WebClient and it throws 404 with the below error message
{"fault":{"faultstring":"Unable to identify proxy for host: localhost:9943 and url: \/url\/post\/msg","detail":{"errorcode":"messaging.adaptors.http.flow.ApplicationNotFound"}}}
Below is the snippet where I'm trying to call the api
HttpClient httpClient = HttpClient.create()
.tcpConfiguration(client -> client
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000)
.doOnConnected(conn -> conn.addHandlerLast(new ReadTimeoutHandler(10000))
.addHandlerLast(new WriteTimeoutHandler(10000))));
ClientHttpConnector connector = new ReactorClientHttpConnector(httpClient);
WebClient webClient = WebClient.builder().baseUrl("https://example.com").clientConnector(connector)
.exchangeStrategies(ExchangeStrategies.builder()
.codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(16 * 1024 * 1024)).build())
.build();
return webClient.post().uri(uriBuilder -> {
URI url = uriBuilder.path(/postMsg).build();
return url;
}).headers(t -> {
t.addAll(headers);
}).body(Mono.just(request), Request.class)
.exchangeToMono(clientResponse -> handleResponse(clientResponse));
Tried with and without proxy and it doesn't work.
But the same api works fine from postman and with restTemplate. Below is the snippet which works fine with restTemplate
HttpHeaders header = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
HttpEntity<Request> entity = new HttpEntity<Request>(request, headers);
String response = restTemplate
.exchange("https://example.com/postMsg", HttpMethod.POST, entity, String.class)
.getBody();
request and headers are the same for both
webClient and restTemplate implementation.
Any pointers on what I'm doing wrong is appreciated

Why is my Session gone when using RestTemplate to access Spring MVC?

I am trying to access a Spring MVC app. That uses a CSRF Token. I do an initial GET to receive the Token. Then add it to my POST with my JSESSIONID. However, during debug the Server app doesn't find my JSESSIONID. And therefore, doesn't authenticate my token, and gives me 403.
I can't tell but it looks like my GET JSESSIONID doesn't get saved in the server HTTP Session repository.
Is there a way, to validate:
The session is in the server context?
Am I sending the correct header data?
Here's my code:
public String testLogin() {
ResponseEntity<String> response =
restTemplate.getForEntity(LOGIN_RESOURCE_URL, String.class);
List<String> cookies = new ArrayList<String>();
cookies = response.getHeaders().get("Set-Cookie");
String[] firstString = cookies.get(0).split("=|;");
String jsessionPart = firstString[1];
String[] secondString = cookies.get(1).split("=|;");
String tokenPart = secondString[1];
BasicCookieStore cookieStore = new BasicCookieStore();
BasicClientCookie cookie = new BasicClientCookie("JSESSIONID",
jsessionPart);
cookie.setDomain(".mydomain.com");
cookie.setPath("/");
cookieStore.addCookie(cookie);
BasicClientCookie cookie2 = new BasicClientCookie("X-XSRF-TOKEN",
tokenPart);
cookie2.setDomain(".mydomain.com");
cookie2.setPath("/");
cookieStore.addCookie(cookie2);
HttpClient client = HttpClientBuilder
.create()
.setDefaultCookieStore(cookieStore)
.disableRedirectHandling()
.build();
HttpComponentsClientHttpRequestFactory factory = new
HttpComponentsClientHttpRequestFactory();
factory.setHttpClient(client);
RestTemplate postTemplate = new RestTemplate(factory);
HttpEntity<?> requestEntity = new HttpEntity<Object>(body, headers);
ResponseEntity<String> response = postTemplate.exchange(loginUserUrl,
HttpMethod.POST, requestEntity,String.class);
To your code sample I added user name and password plus changed the content type. The 403 still happens whether i sent content type or not:
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
// if you need to pass form parameters in request with headers.
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
try {
map.add( URLEncoder.encode("username", "UTF-8"),
URLEncoder.encode("userdev", "UTF-8") );
map.add(URLEncoder.encode("password", "UTF-8"),
URLEncoder.encode("devpwd","UTF-8") );
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>
(map, headers);
ResponseEntity<String> response =
this.restTemplate(builder).exchange(RESOURCE_URL, HttpMethod.POST,
requestEntity, String.class);
Instead of messing around with cookies yourself let the framework, Apache HttpClient, handle this for you. Configure the RestTemplate to work with a properly configured HttpClient.
Something like this should do the trick
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder.requestFactory(this::requestFactory)
.build();
}
#Bean
public HttpComponentsClientHttpRequestFactory requestFactory() {
RequestConfig defaultRequestConfig = RequestConfig.custom()
.setCookieSpec(CookieSpecs.DEFAULT)
.setExpectContinueEnabled(true)
.build();
CloseableHttpClient httpClient = HttpClientBuilder.create()
.setDefaultCookieStore(new BasicCookieStore())
.setDefaultRequestConfig(defaultRequestConfig).build();
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
return requestFactory;
}
This will configure the RestTemplate to use a HttpClient that stores cookies in a CookieStore in between requests. Reuse the configured RestTemplate and don't create a new one because you might need it.

RestTemplate 404 error upon adding HttpComponentsClientHttpRequestFactory

I am using RestTemplate exchange method to call a Microservice.
Its working fine with the below code.
RestTemplate rs = new RestTemplate();
rs.exchange(........);
But if I add HttpComponentsClientHttpRequestFactory to the resttemplate, its giving 404 Not found exception with the below code
RestTemplate restTemplate = new RestTemplate();
HttpClient httpClient = HttpClientBuilder.create().setMaxConnTotal(200).setMaxConnPerRoute(100).build();
restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory(httpClient));
restTemplate.exchange(......);

Add Custom ClientHttpRequestInterceptor in RestTemplateBuilder with HttpComponentsClientHttpRequestFactory

I need to add a Custom Header in all my RestTemplate Client requests. So I implemented ClientHttpRequestInterceptor. And I add the interceptor in my RestTemplateBuilder config like shown below. The problem is that when the RestTemplate makes the HTTP call it throws following exception:
java.lang.ClassCastException: org.springframework.http.client.InterceptingClientHttpRequestFactory cannot be cast to org.springframework.http.client.HttpComponentsClientHttpRequestFactory
RestTemplate Bean Creation :
#Bean
public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) {
PoolingHttpClientConnectionManager poolingConnectionManager = new PoolingHttpClientConnectionManager();
poolingConnectionManager.setMaxTotal(restTemplateProps.getMaxConnectionsPerPool());
poolingConnectionManager.setDefaultMaxPerRoute(restTemplateProps.getMaxDefaultConnectionPerRoute());
CloseableHttpClient client = HttpClientBuilder.create().setConnectionManager(poolingConnectionManager).build();
ClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory(client);
restTemplateBuilder = restTemplateBuilder.additionalInterceptors(new MyClientHttpRequestInterceptor());
return restTemplateBuilder.requestFactory(clientHttpRequestFactory).build();
}
Also, I am updating the timeouts later in below code:
protected void setRestTemplateTimeouts() {
HttpComponentsClientHttpRequestFactory rf =
(HttpComponentsClientHttpRequestFactory) restTemplate.getRequestFactory();
rf.setConnectTimeout(restTemplateProps.getConnectionTimeout());
rf.setReadTimeout(restTemplateProps.getSocketTimeout());
}
Can anyone help me fix this?
The problem was, I was trying to set the connect and read timeouts after setting the ClientHttpRequestInterceptor.
In my setRestTemplateTimeouts() method when I try to fetch & typecast requestFactory to HttpComponentsClientHttpRequestFactory, I get the ClassCastException exception because restTemplate.getRequestFactory() returns InterceptingClientHttpRequestFactory instead of HttpComponentsClientHttpRequestFactory. This is because I added an interceptor in my restTemplate object.
Solution is to set the timeouts before setting interceptor because you can't set timeouts after setting an interceptor. Refer the code below:
#Bean
public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) {
PoolingHttpClientConnectionManager poolingConnectionManager = new PoolingHttpClientConnectionManager();
poolingConnectionManager.setMaxTotal(restTemplateProps.getMaxConnectionsPerPool());
poolingConnectionManager.setDefaultMaxPerRoute(restTemplateProps.getMaxDefaultConnectionPerRoute());
CloseableHttpClient client = HttpClientBuilder.create().setConnectionManager(poolingConnectionManager).build();
HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory(client);
clientHttpRequestFactory.setConnectTimeout(restTemplateProps.getConnectionTimeout());
clientHttpRequestFactory.setReadTimeout(restTemplateProps.getSocketTimeout());
restTemplateBuilder = restTemplateBuilder.additionalInterceptors(new MyClientHttpRequestInterceptor());
return restTemplateBuilder.requestFactory(clientHttpRequestFactory).build();
}
This is how i manage to get the interceptor to log both request and response without throwing exception - Attempted read from closed stream.
#Bean
public RestTemplate getRestTemplateConfig()
throws KeyStoreException, IOException, UnrecoverableKeyException, CertificateException, NoSuchAlgorithmException, KeyManagementException {
SSLContext context = SSLContextBuilder
.create()
.loadKeyMaterial(ResourceUtils.getFile("/opt/cert/keystore.jks"),
"password".toCharArray(),
"password".toCharArray())
.build();
HttpClient client = HttpClients
.custom()
.setSSLContext(context)
.build();
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(client);
RestTemplate restTemplate = new RestTemplate(requestFactory);
//Provide a buffer for the outgoing/incoming stream, allowing the response body to be read multiple times
// (if not configured, the interceptor reads the Response stream, and then returns body=null when responding to the data)
restTemplate.setRequestFactory(new BufferingClientHttpRequestFactory(requestFactory));
restTemplate.setErrorHandler(new RestTemplateResponseErrorHandler());
restTemplate.setInterceptors(Collections.<ClientHttpRequestInterceptor>singletonList(
new RestTemplateInterceptor()));
restTemplate.getMessageConverters().add(jacksonSupportsMoreTypes());
return restTemplate;
}
private HttpMessageConverter jacksonSupportsMoreTypes() {
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setSupportedMediaTypes(Arrays.asList( MediaType.APPLICATION_OCTET_STREAM));
return converter;
}

Setting Security cookie using RestTemplate

I am trying to call a Restful JSON service using RestTemplate and Jackson json convertor. Now in order to call the service I need to pass in a Security cookie. I can achieve this by using URLConnection (See the code below)
URL url= new URL("https://XXXXXXXX");
URLConnection yc = url.openConnection();
yc.setRequestProperty("SecurityCookie", ssocookie.getValue());</code>
Whats the parallel for this in RestTemplate? Here is a code snippet which I have been using to call a Restful Service using RestTemplate:
RestTemplate rest = new RestTemplate();
InputBean input = new InputBean();
input.setResource("SampleResource");
HttpEntity<InputBean > entity = new HttpEntity<InputBean>(input);
ResponseEntity<OutputBean> response1 = rest.postForEntity(
"https://XXXXXXXXX",
entity, OutputBean.class);</code>
I can not figure out how to pass the security cookie while using RestTemplate to call the service. Any help on this would be great.
I wrote a blog post that explains how to do this using request headers:
http://springinpractice.com/2012/04/08/sending-cookies-with-resttemplate/
Here's the code:
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.add("Cookie", "JSESSIONID=" + session.getValue());
HttpEntity requestEntity = new HttpEntity(null, requestHeaders);
ResponseEntity rssResponse = restTemplate.exchange(
"https://jira.example.com/sr/jira.issueviews:searchrequest-xml/18107/SearchRequest-18107.xml?tempMax=1000",
HttpMethod.GET,
requestEntity,
Rss.class);
Rss rss = rssResponse.getBody();
You can access the underlying HttpURLConnection used by RestTemplate by wiring your RestTemplate up with a custom ClientHttpRequestFactory, which lets you access the underlying connection to set headers, properties, etc. The ClientHttpRequestFactory is used by RestTemplate when creating new connections.
In particular, you can extend the SimpleClientHttpRequestFactory implementation and override the prepareConnection() method:
public class YourClientHttpRequestFactory extends SimpleClientHttpRequestFactory {
#Override
protected void prepareConnection(HttpURLConnection connection, String httpMethod) {
connection.setRequestProperty("SecurityCookie", ssocookie.getValue());
}
}
This is how it has worked for us
requestHeaders.add("Cookie", "JSESSIONID=" + session.getValue());

Categories