package com.redblackit.web.client;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import javax.servlet.GenericServlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.http.impl.client.DefaultHttpClient;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.util.FileCopyUtils;
import com.redblackit.web.KeyAndTrustStoreInfo;
import com.redblackit.web.server.DefaultEmbeddedJettyServer;
import com.redblackit.web.server.EchoServlet;
import com.redblackit.web.server.EmbeddedJettyServer;
import com.redblackit.web.server.HostNetUtils;
@RunWith(Parameterized.class)
public abstract class AbstractClientHttpRequestFactoryTestBase {
private static EmbeddedJettyServer jettyServer;
protected static final String hostname = HostNetUtils.getLocalHostname();
private static int httpPort = HostNetUtils.getFreePort(8080);
private static int httpsPort = HostNetUtils.getFreePort(8443);
/**
* Parameter method
*/
@Parameters
public static List<Object[]> getParameters() {
X509HttpClientFactoryBean x509HttpClientFactoryBean = new X509HttpClientFactoryBean();
x509HttpClientFactoryBean.setHttpsPort(httpsPort);
x509HttpClientFactoryBean.setKeyStore(KeyAndTrustStoreInfo.getClient0Ks());
x509HttpClientFactoryBean
.setKeyStorePassword(KeyAndTrustStoreInfo.CLIENT0_KS_PWD);
x509HttpClientFactoryBean
.setTrustStore(KeyAndTrustStoreInfo.getClient0Ts());
x509HttpClientFactoryBean
.setTrustStorePassword(KeyAndTrustStoreInfo.CLIENT0_TS_PWD);
try {
x509HttpClientFactoryBean.afterPropertiesSet();
} catch (Exception e) {
throw new RuntimeException(e);
}
Object[][] parameters = {
{ new DefaultHttpClient(), "http", httpPort },
{ x509HttpClientFactoryBean.getObject(), "https", httpsPort } };
return Arrays.asList(parameters);
}
private ClientHttpRequestFactory factory;
private String baseUrl;
/**
* Start Jetty and deploy test servlets.
*
* @throws Exception
*/
@BeforeClass
public static void startJettyServer() throws Exception {
jettyServer = new DefaultEmbeddedJettyServer(httpPort, httpsPort,
KeyAndTrustStoreInfo.getServer1Ks(),
KeyAndTrustStoreInfo.SERVER1_KS_PWD);
ServletContextHandler jettyContext = new ServletContextHandler(
ServletContextHandler.SESSIONS);
jettyContext.setContextPath("/");
jettyServer.getServer().setHandler(jettyContext);
jettyContext.addServlet(new ServletHolder(new EchoServlet()), "/echo");
jettyContext.addServlet(new ServletHolder(new StatusServlet(200)),
"/status/ok");
jettyContext.addServlet(new ServletHolder(new StatusServlet(404)),
"/status/notfound");
jettyContext.addServlet(new ServletHolder(new MethodServlet("DELETE")),
"/methods/delete");
jettyContext.addServlet(new ServletHolder(new MethodServlet("GET")),
"/methods/get");
jettyContext.addServlet(new ServletHolder(new MethodServlet("HEAD")),
"/methods/head");
jettyContext.addServlet(
new ServletHolder(new MethodServlet("OPTIONS")),
"/methods/options");
jettyContext.addServlet(new ServletHolder(new MethodServlet("POST")),
"/methods/post");
jettyContext.addServlet(new ServletHolder(new MethodServlet("PUT")),
"/methods/put");
jettyContext.addServlet(new ServletHolder(new RedirectServlet(
"/status/ok")), "/redirect");
jettyServer.startWait();
}
/**
* Stop Jetty, and wait for it to end
*
* @throws Exception
*/
@AfterClass
public static void stopJettyServer() throws Exception {
if (jettyServer != null) {
jettyServer.stopWait();
}
}
/**
* Create a factory with the correct client
*/
@Before
public final void createFactory() {
factory = createSpecificFactory();
}
/**
* @param baseUrl
*/
public AbstractClientHttpRequestFactoryTestBase(String baseUrl) {
super();
this.baseUrl = baseUrl;
}
/**
* Template method to create the specific factory instance
*
* @return specific factory
*/
protected abstract ClientHttpRequestFactory createSpecificFactory();
/**
* Servlet that sets a given status code, and the URL in a special header
* */
private static class StatusServlet extends GenericServlet {
/**
* For serialization
*/
private static final long serialVersionUID = 1L;
private final int sc;
private StatusServlet(int sc) {
this.sc = sc;
}
@Override
public void service(ServletRequest req, ServletResponse resp)
throws ServletException, IOException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
response.setStatus(sc);
String location = request.getRequestURL().toString();
response.setHeader("X-Status-URL", location);
}
}
/**
* Check creation of request for method and URI, and processing of simple
* HTTP status when page not found
*
* @throws Exception
*/
@Test
public void status() throws Exception {
URI uri = new URI(baseUrl + "/status/notfound");
ClientHttpRequest request = factory.createRequest(uri, HttpMethod.GET);
Assert.assertEquals("Invalid HTTP method", HttpMethod.GET,
request.getMethod());
Assert.assertEquals("Invalid HTTP URI", uri, request.getURI());
ClientHttpResponse response = request.execute();
try {
assertStatusEquals(response, HttpStatus.NOT_FOUND);
} finally {
response.close();
}
}
/**
* Test GET sending and receiving headers. Also ensure it works when we set
* the content length.
*
* @throws Exception
*/
@Test
public void echoGet() throws Exception {
echoMethod(HttpMethod.GET, false, false);
}
/**
* Test PUT sending and receiving body and headers. Also ensure it works
* when we set the content length.
*
* @throws Exception
*/
@Test
public void echoPut() throws Exception {
echoMethod(HttpMethod.PUT, true, true);
}
/**
* Test POST sending and receiving body and headers. Also ensure it works
* when we set the content length.
*
* @throws Exception
*/
@Test
public void echoPost() throws Exception {
echoMethod(HttpMethod.POST, true, true);
}
/**
* Test DELETE sending and receiving headers. Also ensure it works when we
* set the content length.
*
* @throws Exception
*/
@Test
public void echoDelete() throws Exception {
echoMethod(HttpMethod.DELETE, false, false);
}
/**
* Test HEAD sending and receiving headers. Also ensure it works when we set
* the content length.
*
* @throws Exception
*/
@Test
public void echoHead() throws Exception {
echoMethod(HttpMethod.HEAD, true, false);
}
/**
* @throws IOException
* @throws URISyntaxException
* @throws UnsupportedEncodingException
*/
private void echoMethod(HttpMethod method, boolean sendBody,
boolean recvBody) throws IOException, URISyntaxException,
UnsupportedEncodingException {
ClientHttpRequest request = factory.createRequest(new URI(baseUrl
+ "/echo"), method);
Assert.assertEquals("Invalid HTTP method", method, request.getMethod());
String headerName = "MyHeader";
String headerValue1 = "value1";
request.getHeaders().add(headerName, headerValue1);
String headerValue2 = "value2";
request.getHeaders().add(headerName, headerValue2);
byte[] body = null;
if (sendBody) {
body = "Hello World".getBytes("UTF-8");
FileCopyUtils.copy(body, request.getBody());
request.getHeaders().setContentLength(body.length);
} else {
request.getHeaders().setContentLength(0);
}
ClientHttpResponse response = request.execute();
try {
assertStatusEquals(response, HttpStatus.OK);
Assert.assertTrue("Header not found", response.getHeaders()
.containsKey(headerName));
Assert.assertEquals("Header value not found", Arrays.asList(
headerValue1, headerValue2),
response.getHeaders().get(headerName));
InputStream bodyis = response.getBody();
byte[] responseBody = null;
if (bodyis != null) {
responseBody = FileCopyUtils.copyToByteArray(bodyis);
}
if (recvBody) {
Assert.assertNotNull("Response body", bodyis);
Assert.assertTrue("Invalid body",
Arrays.equals(body, responseBody));
} else {
Assert.assertTrue(
"Response body should be null or empty:responseBody="
+ Arrays.toString(responseBody),
responseBody == null || responseBody.length == 0);
}
} finally {
response.close();
}
}
/**
* Check we cannot set body request that has been sent already, and get an
* appropriate exception
*
* @throws Exception
*/
@Test(expected = IllegalStateException.class)
public void multipleWrites() throws Exception {
ClientHttpRequest request = factory.createRequest(new URI(baseUrl
+ "/echo"), HttpMethod.POST);
byte[] body = "Hello World".getBytes("UTF-8");
FileCopyUtils.copy(body, request.getBody());
ClientHttpResponse response = request.execute();
try {
FileCopyUtils.copy(body, request.getBody());
} finally {
response.close();
}
}
/**
* Check we cannot add headers that has been sent already, and get an
* appropriate exception
*
* @throws Exception
*/
@Test(expected = UnsupportedOperationException.class)
public void headersAfterExecute() throws Exception {
ClientHttpRequest request = factory.createRequest(new URI(baseUrl
+ "/echo"), HttpMethod.POST);
request.getHeaders().add("MyHeader", "value");
byte[] body = "Hello World".getBytes("UTF-8");
FileCopyUtils.copy(body, request.getBody());
ClientHttpResponse response = request.execute();
try {
request.getHeaders().add("MyHeader", "value");
} finally {
response.close();
}
}
/**
* Check all methods
*
* @throws Exception
*/
@Test
public void httpMethods() throws Exception {
assertHttpMethod("get", HttpMethod.GET);
assertHttpMethod("head", HttpMethod.HEAD);
assertHttpMethod("post", HttpMethod.POST);
assertHttpMethod("put", HttpMethod.PUT);
assertHttpMethod("options", HttpMethod.OPTIONS);
assertHttpMethod("delete", HttpMethod.DELETE);
}
/**
* See above. Use method-matching servlet for supplied method
*
* @param path
* @param method
* @throws Exception
*/
private void assertHttpMethod(String path, HttpMethod method)
throws Exception {
ClientHttpResponse response = null;
try {
ClientHttpRequest request = factory.createRequest(new URI(baseUrl
+ "/methods/" + path), method);
response = request.execute();
Assert.assertEquals("Invalid method", path
.toUpperCase(Locale.ENGLISH), request.getMethod().name());
} finally {
if (response != null) {
response.close();
}
}
}
/**
* Check re-direct will happen transparently, for different methods.
*
* @throws Exception
*/
@Test
public void redirect() throws Exception {
redirectWithMethod(HttpMethod.POST);
redirectWithMethod(HttpMethod.PUT);
redirectWithMethod(HttpMethod.GET);
}
/**
* Do redirect test for specific method
*
* @throws IOException
* @throws URISyntaxException
*/
private void redirectWithMethod(HttpMethod method) throws IOException,
URISyntaxException {
ClientHttpResponse response = null;
try {
ClientHttpRequest request = factory.createRequest(new URI(baseUrl
+ "/redirect"), method);
response = request.execute();
assertStatusEquals(response, HttpStatus.OK);
Assert.assertNull("Location header not expected", response
.getHeaders().getLocation());
Assert.assertEquals("Path is as originally re-directed", baseUrl
+ "/status/ok", response.getHeaders().get("X-Status-URL")
.get(0));
} finally {
if (response != null) {
response.close();
response = null;
}
}
}
/**
* Assert on status value, giving status text on failure
*
* @param response
* @param statusCode
* @throws IOException
*/
private void assertStatusEquals(ClientHttpResponse response,
HttpStatus statusCode) throws IOException {
Assert.assertEquals("Invalid status code:" + response.getStatusText(),
statusCode, response.getStatusCode());
}
/**
* Servlet matching method against URL
*/
protected static class MethodServlet extends GenericServlet {
/**
* For serialization
*/
private static final long serialVersionUID = 1L;
private final String method;
private MethodServlet(String method) {
this.method = method;
}
@Override
public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException {
HttpServletRequest httpReq = (HttpServletRequest) req;
Assert.assertEquals("Invalid HTTP method", method,
httpReq.getMethod());
}
}
/**
* Servlet to re-direct to configured URL
*/
private static class RedirectServlet extends GenericServlet {
/**
* For serialization
*/
private static final long serialVersionUID = 1L;
private final String location;
private RedirectServlet(String location) {
this.location = location;
}
@Override
public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
response.setStatus(HttpServletResponse.SC_SEE_OTHER);
StringBuilder builder = new StringBuilder();
builder.append(request.getScheme()).append("://");
builder.append(request.getServerName()).append(':')
.append(request.getServerPort());
builder.append(location);
response.setHeader("Location", builder.toString());
}
}
}