Apache HttpClient return cached body on conditional request - caching

I'm using CachingHttpClient implementation of HttpClient from Apache. And having the following scenario:
I made a request for a resource that returned a response with a header:
Cache-Control:max-age=5.
So CachingHttpClient caches the response.
I following I'm making a conditional request for the same resource using If-Modified-Since. And I get a response with status code 304 No modified (btw it does not even checks the server). Without a response body. Which is fine, but I would like to access the cached body, since if its not updated I want to use that.
The question is:
Is there a convenient way to access the cached response from the first call?
(Using org.apache.httpcomponents:httpclient, org.apache.httpcomponents:httpclient-cache; version 4.5.2)
server side:
#RequestMapping("/number")
public int getNumber(HttpServletResponse response, HttpServletRequest request) {
log.info("Number gen called");
response.setHeader("Cache-Control", "max-age=" + 5);
return random.nextInt();
}
client side:
HttpGet httpget = new HttpGet("http://localhost:8080/number");
httpget.setHeader("If-Modified-Since", java.time.format.DateTimeFormatter.RFC_1123_DATE_TIME.
format(ZonedDateTime.now(ZoneId.of("GMT")).minusSeconds(1)));
HttpResponse resp = httpClient.execute(httpget);
log.info("code: " + resp.getStatusLine().getStatusCode());
// here fails because no body on 2. call
String responseString = new BasicResponseHandler().handleResponse(resp);
Http Client Init
#Bean
public HttpClient httpClient() {
return CachingHttpClients.createMemoryBound();
}

Ok so if I configure the cache explicitly, I can use that cache object directly too, so this was a typical RTFM problem, my bad.
#Bean
public HttpCacheStorage httpCacheStorage() {
CacheConfig cacheConfig = CacheConfig.custom()
.setMaxCacheEntries(1000)
.setMaxObjectSize(8192)
.build();
HttpCacheStorage cacheStorage = new BasicHttpCacheStorage(cacheConfig);
return cacheStorage;
}

Related

How to get response from DHL Api to browser

i have problem with api from dhl, i create GET api from dhl, when print in console, result will print, but when using browser i got response like this :
com.squareup.okhttp.internal.http.RealResponseBody#68bd3d26
this my code :
#RequestMapping("/getData")
public String getAcc() throws IOException
{
OkHttpClient client = new OkHttpClient();
MediaType mediaType = MediaType.parse("application/json");
HttpUrl httpUrl = new HttpUrl.Builder()
.scheme("https")
.host("api-eu.dhl.com")
.addPathSegment("track")
.addPathSegment("shipments")
.addQueryParameter("trackingNumber", "cencored")
.addQueryParameter("service", "express")
.build();
Request request = new Request.Builder()
.addHeader("content-type", "application/json")
.addHeader("Connection", "close")
.addHeader("DHL-API-Key", "cencored")
.addHeader("ConsumerKey", "cencored")
.addHeader("ConsumerSecret", "cencored")
.removeHeader("Content-Encoding")
.removeHeader("Content-Length")
.url(httpUrl) // <- Finally put httpUrl in here
.build();
response = client.newCall(request).execute();
System.out.println(response.body().string());
return this.response.body().toString();
}
solved...
this is weird, but work for me.
so we can't call "response.body().string();" twice.
This is the correctly way to consume a soap webservice with spring boot: https://spring.io/guides/gs/consuming-web-service/
Follow this tutorial and it works fine.

Spring RestTemplate seems to not be thread-safe wrt headers

I have a Spring web client which posts to a Spring web server (the same URL) using two different basic-auth users. Is it a known issue that I can not use a single RestTemplate for both?
When I use a single RestTemplate, and the requests are nearly simultaneous (in different threads), though I specify different users in the header, the receiving server thinks they're from the same user! Note that the request and the headers (and the body of the post) are newly allocated for each request.
It works fine, when I use a single RestTemplate and put a synchronized() around the call to
response = RestTemplate.exchange(url, method, requestParams, MyResponse.class)
I've also tried creating two RestTemplate instances, one for each user - (each built with a RestTemplateBuilder) that works, too. I'll keep this solution, but it surprises me that it's needed.
Is this a known issue?
(I see stackOverflow answers that a RestTemplate is thread-safe after constructed, but the headers are passed in with the request, not as a setting on the already-constructed RestTemplate...)
====
Here's an example of 2 different calls, using 2 different RestTemplates because there were sometimes problems in using the same:
public OperationStatus getOpStatus(String gufi) {
HttpEntity<String> requestParams = new HttpEntity<>(Utils.createBasicHeader(cfg.getManager(), cfg.getManPass()));
ResponseEntity<OperationStatus> restResponse = null;
try {
restResponse = managerRestTemplate.exchange(
cfg.getNussOpApiPath(), HttpMethod.GET, requestParams, OperationStatus.class);
} catch (RestClientException e) {
...
}
OperationStatus opState = restResponse.getBody();
opState.setHttpStatusCode(String.valueOf(restResponse.getStatusCodeValue()));
return opState;
}
Here was a method to do a post, using the priority to switch rest templates (at the time, the target server recognized the priority by the privileges of the user)
UTMRestResponse doPost(Object objToSend, String url, String msg) throws IOException {
String user = cfg.getOpUser();
String pass = cfg.getOpPass();
RestTemplate restTemplate = opUserRestTemplate;
boolean isPriorityOp = false;
if ( objToSend instanceof OpPost) {
OpPost post = (OpPost) objToSend;
String flightNum = post.getFlightNumber();
isPriorityOp = Boolean.TRUE.equals(post.getPriorityOp()); // null is false
} else if ( objToSend instanceof PositionPost) {
PositionPost post = (PositionPost) objToSend;
isPriorityOp = Boolean.TRUE.equals(post.getPriorityOp()); // null is false
}
if (isPriorityOp) {
user = cfg.getUserEmergency();
pass = cfg.getPassEmergency();
restTemplate = emergRestTemplate;
}
String jsonToSend = CommonsObjectMapper.get().writeValueAsString(objToSend);
HttpEntity<String> requestParams = new HttpEntity<>(jsonToSend, Utils.createBasicHeader(user, pass));
UTMRestResponse restResponse = restTemplate.exchange(
url, HttpMethod.POST, requestParams, UTMRestResponse.class).getBody();
if (restResponse.getHttpStatusCode().startsWith("4")) {
String fmt = "Status:{}, url:{}, jsonSent:{}, response:{}";
logger.error(fmt, restResponse.getHttpStatusCode(), url, jsonToSend, restResponse.getMsg());
}
return restResponse;
}

Spring Integration HttpRequestExecutingMessageHandler ContentType Issue

I am facing a problem with Spring Integration. I am trying to execute a rest call via HttpRequestExecutingMessageHandler. My rest endpoint is accepting content-type 'application/json' only.
The problem is that the HttpRequestExecutingMessageHandler is posting with content-type 'text/plain;charset=UTF-8'.
#ServiceActivator(inputChannel = "transformRequestToJsonChannel",
outputChannel = "httpRequestOutChannel")
public Message<?> transformRequest(Message<DocumentConverterRequest>
message)
{
LOG.info("transforming document converter request to json: '{}'",
ObjectToJsonTransformer transformer = new ObjectToJsonTransformer();
transformer.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
Object payload = transformer.transform(message).getPayload();
LOG.info("payload: '{}'", payload.toString());
return MessageBuilder.withPayload(payload).build();
}
#Bean
#ServiceActivator(inputChannel = "httpRequestOutChannel")
public HttpRequestExecutingMessageHandler outbound() {
HttpRequestExecutingMessageHandler handler = new
HttpRequestExecutingMessageHandler(documentConverterRestUrl);
handler.setHttpMethod(HttpMethod.POST);
handler.setErrorHandler(httpResponseErrorHandler);
handler.setExpectedResponseType(String.class);
handler.setCharset(Charset.defaultCharset().name());
HeaderMapper<HttpHeaders> mapper = new DefaultHttpHeaderMapper();
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add(HttpHeaders.CONTENT_TYPE,
MediaType.APPLICATION_JSON_VALUE);
mapper.toHeaders(httpHeaders);
handler.setHeaderMapper(mapper);
handler.setOutputChannel(httpResponseChannel());
return handler;
}
How can i override the content-type?
This piece of code does nothing:
HeaderMapper<HttpHeaders> mapper = new DefaultHttpHeaderMapper();
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add(HttpHeaders.CONTENT_TYPE,
MediaType.APPLICATION_JSON_VALUE);
mapper.toHeaders(httpHeaders);
That toHeaders() is called from the HttpRequestExecutingMessageHandler when we receive response. It really useless to use it explicitly in your code, especially in the bean definition phase and when you ignore a result.
You don't need to use an explicit HeaderMapper at all: a default one should be enough for you.
The ObjectToJsonTransformer really maps that setContentType() into a headers of the message it replies:
if (headers.containsKey(MessageHeaders.CONTENT_TYPE)) {
// override, unless empty
if (this.contentTypeExplicitlySet && StringUtils.hasLength(this.contentType)) {
headers.put(MessageHeaders.CONTENT_TYPE, this.contentType);
}
}
else if (StringUtils.hasLength(this.contentType)) {
headers.put(MessageHeaders.CONTENT_TYPE, this.contentType);
}
So, there is a proper content type to map. By default HttpRequestExecutingMessageHandler uses:
/**
* Factory method for creating a basic outbound mapper instance.
* This will map all standard HTTP request headers when sending an HTTP request,
* and it will map all standard HTTP response headers when receiving an HTTP response.
* #return The default outbound mapper.
*/
public static DefaultHttpHeaderMapper outboundMapper() {
With an appropriate set of headers to map to HTTP request and from HTTP response.
The new DefaultHttpHeaderMapper() brings just an empty set of headers to map.
Please, raise an issue to improve JavaDocs and Reference Manual to note that default ctor of that class doesn't bring any headers to map.
#Bean
#ServiceActivator(inputChannel = "httpRequestOutChannel")
public HttpRequestExecutingMessageHandler outbound() {
HttpRequestExecutingMessageHandler handler = Http.outboundGateway(documentConverterRestUrl)
.httpMethod(HttpMethod.POST)
.messageConverters(new MappingJackson2HttpMessageConverter())
.mappedRequestHeaders("Content-Type")
.get();
handler.setOutputChannel(httpResponseChannel());
return handler;
}
I removed my ObjectToJsonTransformer, because the messageConverters(new MappingJackson2HttpMessageConverter()) is doing the stuff.
Also i had to add the content-type to my message header: .setHeaderIfAbsent(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)

OAuth2RestTemplate TCP connections

I'm using a Spring OAuth2RestTemplate with ClientCredentialsResourceDetails to acquire an API authorization token. The authorization server and the API endpoints are hidden behind the same load balancers (LB). We have an issues where the first connection to the API endpoint, after acquiring the token, fails with a 404 error message but subsequent calls to the same API endpoint with the same token are successful. I believe the LB is miss-configured in some way but we've been asked if we could try using separate TCP sessions for the acquisition of the token and then the REST call. Is there a way to get the Spring RestTemplate to do this?
UPDATE
Here's how I create and configure the template:
#Bean
public OAuth2RestTemplate oauth2RestTemplate(
#Value("${token.uri}") final String tokenUri,
#Value("${token.clientId:client}") final String clientId,
#Value("${token.secret:secret}") final String clientSecret,
#Value("${token.scope:platform}") final String scope,
final MappingJackson2HttpMessageConverter customJackson2HttpMessageConverter)
{
ClientCredentialsResourceDetails rd = new
ClientCredentialsResourceDetails();
rd.setAuthenticationScheme(AuthenticationScheme.header);
rd.setAccessTokenUri(tokenUri);
rd.setClientId(clientId);
rd.setClientSecret(clientSecret);
rd.setScope(Arrays.asList(scope));
OAuth2RestTemplate rt = new OAuth2RestTemplate(rd);
List<HttpMessageConverter<?>> converters = rt.getMessageConverters();
converters.add(customJackson2HttpMessageConverter);
rt.setMessageConverters(converters);
return rt;
}
and here's the call to the api:
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
headers.set("Connection", "close"); // hmm, gets replace by keep-alive on the token api request!
HttpEntity<String> entity = new HttpEntity<String>(headers);
ResponseEntity<MyObject[]> response = restTemplate.exchange(
"http://example.com/api/v1/rest/method",
HttpMethod.GET, entity, MyObject[].class);
Thanks.
Try adding the Connection request header with value as close while sending your request using resttemplate. This should force the TCP connection to be closed after each request. Not very performant though.
HttpHeaders headers = new HttpHeaders();
headers.set("Connection", "close");
This is only for the "but we've been asked if we could try using separate TCP sessions for the acquisition of the token and then the REST call." part of your question. It will not help resolve your 404 (that does seem to be an LB issue).
UPDATE: Since you're using OAuth2RestTemplate, create a ClientHttpRequestInterceptor which injects the header.
public class ConnectionCloseInterceptor implements ClientHttpRequestInterceptor {
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
HttpHeaders headers = request.getHeaders();
headers.add("Connection", "close");
return execution.execute(request, body);
}
}
Use it in your rest template (OAuth2RestTemplate extends RestTemplate so below applies to both) like so (when you create the rest template bean):
List<ClientHttpRequestInterceptor> currentInterceptors = new ArrayList<>(restTemplate.getInterceptors()); //Don't want to lose the other interceptors!
currentInterceptors.add(new ConnectionCloseInterceptor()); //Add ours
restTemplate.setInterceptors(currentInterceptors);

Get current Uri when redirected in WebClient wp7

I hope that I won't start a topic that's done already but I didn't find any proper answer here nor anywere else.
So here we go:
I use a WebClient to download HTML Code from a webpage, then I send a new request with that WebClient and the WebPage redirects me. Now I want to now where the Site has put me.
The WebClient Class itself doesn't have any suitable properties, I already tried to rewrite the class so that I could get the Response URI but somehow it doesn't work for wp7.
So any ideas how to get the URI where my WebClient got redirected? Or any idea why the application crashes when I want to use my own class:
public class MyWebClient : WebClient
{
Uri _responseUri;
public Uri ResponseUri
{
get { return _responseUri; }
}
protected override WebResponse GetWebResponse(WebRequest request, IAsyncResult result)
{
WebResponse response = base.GetWebResponse(request, result);
_responseUri = response.ResponseUri;
return response;
}
}
}
Thanks in advance!
HttpWebRequest is the solution here, since WebClient is a wrapper around it anyway. Something like this should work for your specific situation:
private HttpWebRequest request;
private bool flagIt = true;
public MainPage()
{
InitializeComponent();
request = (HttpWebRequest)WebRequest.Create("http://google.com");
request.BeginGetResponse(new AsyncCallback(GetData), request);
}
public void GetData(IAsyncResult result)
{
HttpWebRequest request = (HttpWebRequest)result.AsyncState;
HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(result);
Debug.WriteLine(response.ResponseUri.ToString());
if (flagIt)
{
request = (HttpWebRequest)WebRequest.Create("http://microsoft.com");
request.BeginGetResponse(new AsyncCallback(GetData), request);
flagIt = false;
}
}
I am initiating the request in the main page constructor and then I am handling it in the callback. Notice how I am getting the ResponseUri - your final destination.
You don't need to handle AllowAutoRedirect if you don't want blocking the redirect and simply getting the URL, like I am doing in the snippet above.

Resources