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(......);
Related
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;
}
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.
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;
}
Im trying to get the url with basic authentication. I set the user/password as given below. The same credential is working in postman.
String RELATIVE_IDENTITY_URL = "http://my_url/api/core/v3/people/email/abc#example.com";
RestTemplate restTemplate;
Credentials credentials;
//1. Set credentials
credentials = new UsernamePasswordCredentials("admin", "admin");
CredentialsProvider credsProvider = new BasicCredentialsProvider();
credsProvider.setCredentials( AuthScope.ANY, credentials);
//2. Bind credentialsProvider to httpClient
HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
httpClientBuilder.setDefaultCredentialsProvider(credsProvider);
CloseableHttpClient httpClient = httpClientBuilder.build();
HttpComponentsClientHttpRequestFactory factory = new
HttpComponentsClientHttpRequestFactory(httpClient);
//3. create restTemplate
restTemplate = new RestTemplate();
restTemplate.setRequestFactory(factory);
//4. restTemplate execute
String url = RELATIVE_IDENTITY_URL;
String xml = restTemplate.getForObject(url,String.class);
System.out.println("Done");
I think the credentials are not set correctly. What is wrong here.?
Error:
Exception in thread "main" org.springframework.web.client.HttpClientErrorException: 401 Unauthorized
at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:91)
at org.springframework.web.client.RestTemplate.handleResponse(RestTemplate.java:667)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:620)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:580)
at org.springframework.web.client.RestTemplate.getForObject(RestTemplate.java:287)
at com.src.AuthRestService.main(AuthRestService.java:85)
You are missing the auth header and setting the credentials in your rest template execution.
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());