Skip to content

Instantly share code, notes, and snippets.

@eeichinger
Last active October 7, 2016 08:44
Show Gist options
  • Save eeichinger/1ed7588b1c2a17459a61dbb5f08fb60e to your computer and use it in GitHub Desktop.
Save eeichinger/1ed7588b1c2a17459a61dbb5f08fb60e to your computer and use it in GitHub Desktop.
experiment testing Apache HttpAsyncClient HttpCache behaviour
package de.porsche.pcc.instrumentation;
import com.github.tomakehurst.wiremock.client.WireMock;
import com.github.tomakehurst.wiremock.junit.WireMockRule;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpException;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpResponse;
import org.apache.http.HttpResponseInterceptor;
import org.apache.http.client.cache.CacheResponseStatus;
import org.apache.http.client.cache.HttpCacheContext;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.utils.DateUtils;
import org.apache.http.impl.client.cache.BasicHttpCacheStorage;
import org.apache.http.impl.client.cache.CacheConfig;
import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;
import org.apache.http.impl.nio.client.HttpAsyncClientBuilder;
import org.apache.http.protocol.HttpContext;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import java.io.IOException;
import java.net.URI;
import java.util.Date;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;
/**
* @author Erich Eichinger
* @since 06/10/16
*/
@Slf4j
public class CachingCloseableHttpAsyncClientTest {
@Rule
public WireMockRule wireMockRule = new WireMockRule(0);
CloseableHttpAsyncClient client;
@Before
public void before() {
client = HttpAsyncClientBuilder
.create()
.setMaxConnPerRoute(10)
.setMaxConnTotal(10)
// ensure we set a date on the response, otherwise for 304 responses, Apache HttpCache miscalculates the age
// of a CacheEntry from the original 200 response's date instead of the 304 response's.
.addInterceptorFirst(new HttpResponseInterceptor() {
@Override
public void process(HttpResponse response, HttpContext context) throws HttpException, IOException {
if (!response.containsHeader("Date")) {
response.addHeader(HttpHeaders.DATE, DateUtils.formatDate(new Date(System.currentTimeMillis())));
}
}
})
.build();
final BasicHttpCacheStorage httpCacheStorage = new BasicHttpCacheStorage(CacheConfig.custom().setMaxCacheEntries(CacheConfig.DEFAULT_MAX_CACHE_ENTRIES).build());
final CacheConfig cacheConfig = CacheConfig
.custom()
.setSharedCache(true)
.setAsynchronousWorkersCore(10)
.setAsynchronousWorkersMax(100)
.setAsynchronousWorkerIdleLifetimeSecs(10)
.build();
client = new CachingCloseableHttpAsyncClient(
client
, httpCacheStorage
, cacheConfig
);
client.start();
}
@After
public void after() throws Exception {
client.close();
}
@Test
public void handles_cacheable_responses() throws Exception {
HttpGet httpGet = new HttpGet(new URI("http://localhost:" + wireMockRule.port() + "/foo"));
WireMock.stubFor(WireMock
.get(WireMock.urlMatching("/foo"))
.willReturn(WireMock
.aResponse()
.withStatus(200)
.withHeader(HttpHeaders.CACHE_CONTROL, "public, max-age=1")
.withHeader("Content-Type", "text/plain")
.withHeader(HttpHeaders.ETAG, "xyz")
.withBody("data")
)
);
WireMock.stubFor(WireMock
.get(WireMock.urlMatching("/foo"))
.withHeader(HttpHeaders.IF_NONE_MATCH, WireMock.equalTo("xyz"))
.willReturn(WireMock
.aResponse()
.withStatus(304)
)
);
HttpCacheContext context;
// 1. request CACHE_MISS
// 2. request CACHE_HIT
// 3. request triggers revalidation with server -> VALIDATED
// 4. request is a CACHE_HIT - item has been revalidated with server and got a 304 Response
// 5. request is a CACHE_HIT
// 6. request triggers revalidation with server again -> VALIDATED
// 7. request is a CACHE_HIT - item has been revalidated with server and got a 304 Response
context = syncExecute(httpGet); // 1
assertThat(context.getCacheResponseStatus(), equalTo(CacheResponseStatus.CACHE_MISS));
Thread.sleep(500);
context = syncExecute(httpGet); // 2
assertThat(context.getCacheResponseStatus(), equalTo(CacheResponseStatus.CACHE_HIT));
Thread.sleep(600);
context = syncExecute(httpGet); // 3
assertThat(context.getCacheResponseStatus(), equalTo(CacheResponseStatus.VALIDATED));
Thread.sleep(100);
context = syncExecute(httpGet); // 4
assertThat(context.getCacheResponseStatus(), equalTo(CacheResponseStatus.CACHE_HIT));
Thread.sleep(100);
context = syncExecute(httpGet); // 5
assertThat(context.getCacheResponseStatus(), equalTo(CacheResponseStatus.CACHE_HIT));
Thread.sleep(800);
context = syncExecute(httpGet); // 6
assertThat(context.getCacheResponseStatus(), equalTo(CacheResponseStatus.VALIDATED));
Thread.sleep(100);
context = syncExecute(httpGet); // 7
assertThat(context.getCacheResponseStatus(), equalTo(CacheResponseStatus.CACHE_HIT));
WireMock.verify(3, WireMock.getRequestedFor(WireMock.urlEqualTo("/foo")));
}
@SneakyThrows
private HttpCacheContext syncExecute(HttpGet httpGet) throws InterruptedException, java.util.concurrent.ExecutionException {
final HttpCacheContext context = HttpCacheContext.create();
CloseableHttpResponse response = (CloseableHttpResponse) client.execute(httpGet, context, null).get();
response.close();
return context;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment