/*
Copyright (c) 2012 LinkedIn Corp.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/**
* $Id: $
*/
package test.r2.integ;
import com.linkedin.common.callback.Callback;
import com.linkedin.r2.message.RequestContext;
import com.linkedin.r2.message.rest.RestRequest;
import com.linkedin.r2.message.rest.RestResponse;
import com.linkedin.r2.message.rest.RestResponseBuilder;
import com.linkedin.r2.message.rest.RestStatus;
import com.linkedin.r2.message.rest.RestUtil;
import com.linkedin.r2.transport.common.RestRequestHandler;
import com.linkedin.r2.transport.http.common.HttpConstants;
import com.linkedin.r2.transport.http.server.HttpJettyServer;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import static org.testng.Assert.assertEquals;
/**
* @author Steven Ihde
* @version $Revision: $
*/
public abstract class AbstractHttpServerTest
{
private static final int PORT = 18088;
protected static final String MULTI_VALUE_HEADER_NAME = "MultiValuedHeader";
protected static final String MULTI_VALUE_HEADER_COUNT_HEADER = "MultiValuedHeaderCount";
protected final ScheduledExecutorService _scheduler = Executors.newSingleThreadScheduledExecutor();
protected abstract int getPort();
protected abstract void doSetup() throws IOException;
protected abstract void doTearDown() throws IOException;
@DataProvider
public static Object[][] configs()
{
return new Object[][] {
{true, HttpJettyServer.ServletType.RAP, PORT},
{false, HttpJettyServer.ServletType.RAP, PORT + 1},
{true, HttpJettyServer.ServletType.ASYNC_EVENT, PORT + 2},
{false, HttpJettyServer.ServletType.ASYNC_EVENT, PORT + 3}
};
}
@BeforeClass
public void setup() throws IOException
{
doSetup();
}
@AfterClass
public void tearDown() throws IOException
{
doTearDown();
_scheduler.shutdown();
}
@Test
public void testSuccess() throws Exception
{
HttpURLConnection c = (HttpURLConnection)new URL("http://localhost:" + getPort() + "/foobar").openConnection();
assertEquals(c.getResponseCode(), RestStatus.OK);
InputStream in = c.getInputStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buf = new byte[1024];
for (int r; (r = in.read(buf)) != -1; ) {
baos.write(buf, 0, r);
}
String response = new String(baos.toByteArray());
assertEquals(response, "Hello, world!");
}
@Test
public void testPost() throws Exception
{
HttpURLConnection c = (HttpURLConnection)new URL("http://localhost:" + getPort() + "/foobar").openConnection();
c.setRequestMethod("POST");
c.setDoInput(true);
c.setDoOutput(true);
OutputStream os = c.getOutputStream();
os.write(1);
os.close();
c.connect();
assertEquals(c.getResponseCode(), RestStatus.OK);
}
@Test
public void testException() throws Exception
{
HttpURLConnection c2 = (HttpURLConnection)new URL("http://localhost:" + getPort() + "/error").openConnection();
assertEquals(c2.getResponseCode(), RestStatus.INTERNAL_SERVER_ERROR);
}
@Test
public void testHeaderEcho() throws Exception
{
HttpURLConnection c = (HttpURLConnection)new URL("http://localhost:" + getPort() + "/headerEcho").openConnection();
c.setRequestProperty("Header1", "foo");
c.setRequestProperty("Header2", "bar");
assertEquals(c.getHeaderField("header1"), "foo");
assertEquals(c.getHeaderField("header2"), "bar");
}
@Test
public void testMultiValuedHeaderEcho() throws Exception
{
final List<String> values = Arrays.asList(new String[]{ "foo", "bar", "baz", "qux" });
HttpURLConnection c = (HttpURLConnection)new URL("http://localhost:" + getPort() + "/headerEcho").openConnection();
for (String v : values)
{
c.addRequestProperty(MULTI_VALUE_HEADER_NAME, v);
}
// check the number of header values received at the server side
String valueCount = c.getHeaderField(MULTI_VALUE_HEADER_COUNT_HEADER);
assertEquals(Integer.parseInt(valueCount), values.size());
// check the number of header values received at client side
// we know the headers are going to be folded into one line its way back.
List<String> echoValues = RestUtil.getHeaderValues(c.getHeaderField(MULTI_VALUE_HEADER_NAME));
assertEquals(new HashSet<String>(echoValues), new HashSet<String>(values));
}
@Test
public void testCookieEcho() throws Exception
{
String cookie = "sdsc=1%3A1SZM1shxDNbLt36wZwCgPgvN58iw%3D; Path=/; Domain=.linkedin.com; HTTPOnly";
HttpURLConnection c = (HttpURLConnection)new URL("http://localhost:" + getPort() + "/headerEcho").openConnection();
c.setRequestProperty(HttpConstants.REQUEST_COOKIE_HEADER_NAME, cookie);
assertEquals(c.getHeaderField(HttpConstants.RESPONSE_COOKIE_HEADER_NAME), cookie);
}
@Test
public void testMultipleCookiesEcho() throws Exception
{
final List<String> cookies = Arrays.asList(new String[]
{
"_lipt=deleteMe; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Path=/",
"lang=\"v=2&lang=en-us&c=\"; Version=1; Domain=linkedin.com; Path=/"
});
HttpURLConnection c = (HttpURLConnection)new URL("http://localhost:" + getPort() + "/headerEcho").openConnection();
for (String cookie : cookies)
{
c.addRequestProperty(HttpConstants.REQUEST_COOKIE_HEADER_NAME, cookie);
}
List<String> cookiesEcho = c.getHeaderFields().get(HttpConstants.RESPONSE_COOKIE_HEADER_NAME);
assertEquals(new HashSet<String>(cookiesEcho), new HashSet<String>(cookies));
}
protected static class ErrorHandler implements RestRequestHandler
{
@Override
public void handleRequest(RestRequest request, RequestContext requestContext, Callback<RestResponse> callback)
{
throw new RuntimeException("error for testing");
}
}
protected static class FoobarHandler implements RestRequestHandler
{
ScheduledExecutorService _scheduler;
FoobarHandler(ScheduledExecutorService scheduler)
{
_scheduler = scheduler;
}
@Override
public void handleRequest(RestRequest request, RequestContext requestContext, final Callback<RestResponse> callback)
{
RestResponseBuilder builder = new RestResponseBuilder();
builder.setStatus(RestStatus.OK);
builder.setEntity("Hello, world!".getBytes());
final RestResponse response = builder.build();
_scheduler.schedule(new Runnable()
{
@Override
public void run()
{
callback.onSuccess(response);
}
}, 5, TimeUnit.MILLISECONDS);
}
}
protected static class HeaderEchoHandler implements RestRequestHandler
{
@Override
public void handleRequest(RestRequest request, RequestContext requestContext, Callback<RestResponse> callback)
{
final RestResponseBuilder builder = new RestResponseBuilder()
.setStatus(RestStatus.OK)
.setEntity("Hello World".getBytes())
.setHeaders(request.getHeaders())
.setCookies(request.getCookies());
List<String> multiValuedHeaders = request.getHeaderValues(MULTI_VALUE_HEADER_NAME);
if (multiValuedHeaders != null)
{
builder.setHeader(MULTI_VALUE_HEADER_COUNT_HEADER, String.valueOf(multiValuedHeaders.size()));
}
callback.onSuccess(builder.build());
}
}
}