diff options
author | Jon Bratseth <bratseth@yahoo-inc.com> | 2016-06-15 23:09:44 +0200 |
---|---|---|
committer | Jon Bratseth <bratseth@yahoo-inc.com> | 2016-06-15 23:09:44 +0200 |
commit | 72231250ed81e10d66bfe70701e64fa5fe50f712 (patch) | |
tree | 2728bba1131a6f6e5bdf95afec7d7ff9358dac50 /jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerTest.java |
Publish
Diffstat (limited to 'jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerTest.java')
-rw-r--r-- | jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerTest.java | 710 |
1 files changed, 710 insertions, 0 deletions
diff --git a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerTest.java b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerTest.java new file mode 100644 index 00000000000..5ef0f7db742 --- /dev/null +++ b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerTest.java @@ -0,0 +1,710 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.jdisc.http.server.jetty; + +import com.google.inject.AbstractModule; +import com.google.inject.Module; +import com.yahoo.jdisc.HeaderFields; +import com.yahoo.jdisc.Request; +import com.yahoo.jdisc.Response; +import com.yahoo.jdisc.application.BindingSetSelector; +import com.yahoo.jdisc.handler.AbstractRequestHandler; +import com.yahoo.jdisc.handler.CompletionHandler; +import com.yahoo.jdisc.handler.ContentChannel; +import com.yahoo.jdisc.handler.RequestHandler; +import com.yahoo.jdisc.handler.ResponseDispatch; +import com.yahoo.jdisc.handler.ResponseHandler; +import com.yahoo.jdisc.http.ConnectorConfig; +import com.yahoo.jdisc.http.Cookie; +import com.yahoo.jdisc.http.HttpRequest; +import com.yahoo.jdisc.http.HttpResponse; +import com.yahoo.jdisc.http.ServerConfig; +import com.yahoo.jdisc.service.BindingSetNotFoundException; +import com.yahoo.jdisc.References; +import org.apache.http.entity.mime.FormBodyPart; +import org.apache.http.entity.mime.content.StringBody; +import org.testng.annotations.Test; + +import java.net.BindException; +import java.net.URI; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.concurrent.TimeUnit; +import java.util.regex.Pattern; + +import static com.yahoo.jdisc.Response.Status.INTERNAL_SERVER_ERROR; +import static com.yahoo.jdisc.Response.Status.NOT_FOUND; +import static com.yahoo.jdisc.Response.Status.OK; +import static com.yahoo.jdisc.Response.Status.REQUEST_TIMEOUT; +import static com.yahoo.jdisc.Response.Status.REQUEST_URI_TOO_LONG; +import static com.yahoo.jdisc.Response.Status.UNSUPPORTED_MEDIA_TYPE; +import static com.yahoo.jdisc.http.HttpHeaders.Names.CONNECTION; +import static com.yahoo.jdisc.http.HttpHeaders.Names.CONTENT_TYPE; +import static com.yahoo.jdisc.http.HttpHeaders.Names.COOKIE; +import static com.yahoo.jdisc.http.HttpHeaders.Names.X_DISABLE_CHUNKING; +import static com.yahoo.jdisc.http.HttpHeaders.Names.X_TRACE_ID; +import static com.yahoo.jdisc.http.HttpHeaders.Values.APPLICATION_X_WWW_FORM_URLENCODED; +import static com.yahoo.jdisc.http.HttpHeaders.Values.CLOSE; +import static com.yahoo.jdisc.http.server.jetty.SimpleHttpClient.ResponseValidator; +import static org.cthul.matchers.CthulMatchers.containsPattern; +import static org.cthul.matchers.CthulMatchers.matchesPattern; +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.CoreMatchers.startsWith; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * @author <a href="mailto:bakksjo@yahoo-inc.com">Oyvind Bakksjo</a> + * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen Hult</a> + */ +public class HttpServerTest { + + @Test + public void requireThatServerCanListenToRandomPort() throws Exception { + final TestDriver driver = TestDrivers.newInstance(mockRequestHandler()); + assertThat(driver.server().getListenPort(), is(not(0))); + assertThat(driver.close(), is(true)); + } + + @Test + public void requireThatServerCanNotListenToBoundPort() throws Exception { + final TestDriver driver = TestDrivers.newInstance(mockRequestHandler()); + try { + TestDrivers.newConfiguredInstance( + mockRequestHandler(), + new ServerConfig.Builder(), + new ConnectorConfig.Builder() + .listenPort(driver.server().getListenPort()) + ); + } catch (final Throwable t) { + assertThat(t.getCause(), instanceOf(BindException.class)); + } + assertThat(driver.close(), is(true)); + } + + @Test + public void requireThatBindingSetNotFoundReturns404() throws Exception { + final TestDriver driver = TestDrivers.newConfiguredInstance( + mockRequestHandler(), + new ServerConfig.Builder() + .developerMode(true), + new ConnectorConfig.Builder(), + newBindingSetSelector("unknown")); + driver.client().get("/status.html") + .expectStatusCode(is(NOT_FOUND)) + .expectContent(containsPattern(Pattern.compile( + Pattern.quote(BindingSetNotFoundException.class.getName()) + + ": No binding set named 'unknown'\\.\n\tat .+", + Pattern.DOTALL | Pattern.MULTILINE))); + assertThat(driver.close(), is(true)); + } + + @Test + public void requireThatTooLongInitLineReturns414() throws Exception { + final TestDriver driver = TestDrivers.newConfiguredInstance( + mockRequestHandler(), + new ServerConfig.Builder(), + new ConnectorConfig.Builder() + .requestHeaderSize(1)); + driver.client().get("/status.html") + .expectStatusCode(is(REQUEST_URI_TOO_LONG)); + assertThat(driver.close(), is(true)); + } + + @Test + public void requireThatServerCanEcho() throws Exception { + final TestDriver driver = TestDrivers.newInstance(new EchoRequestHandler()); + driver.client().get("/status.html") + .expectStatusCode(is(OK)); + assertThat(driver.close(), is(true)); + } + + @Test + public void requireThatServerCanEchoCompressed() throws Exception { + final TestDriver driver = TestDrivers.newInstance(new EchoRequestHandler()); + SimpleHttpClient client = driver.newClient(true); + client.get("/status.html") + .expectStatusCode(is(OK)); + assertThat(driver.close(), is(true)); + } + + @Test + public void requireThatServerCanHandleMultipleRequests() throws Exception { + final TestDriver driver = TestDrivers.newInstance(new EchoRequestHandler()); + driver.client().get("/status.html") + .expectStatusCode(is(OK)); + driver.client().get("/status.html") + .expectStatusCode(is(OK)); + assertThat(driver.close(), is(true)); + } + + @Test + public void requireThatFormPostWorks() throws Exception { + final TestDriver driver = TestDrivers.newInstance(new ParameterPrinterRequestHandler()); + final String requestContent = generateContent('a', 30); + final ResponseValidator response = + driver.client().newPost("/status.html") + .addHeader(CONTENT_TYPE, APPLICATION_X_WWW_FORM_URLENCODED) + .setContent(requestContent) + .execute(); + response.expectStatusCode(is(OK)) + .expectContent(startsWith('{' + requestContent + "=[]}")); + assertThat(driver.close(), is(true)); + } + + @Test + public void requireThatFormPostDoesNotRemoveContentByDefault() throws Exception { + final TestDriver driver = TestDrivers.newInstance(new ParameterPrinterRequestHandler()); + final ResponseValidator response = + driver.client().newPost("/status.html") + .addHeader(CONTENT_TYPE, APPLICATION_X_WWW_FORM_URLENCODED) + .setContent("foo=bar") + .execute(); + response.expectStatusCode(is(OK)) + .expectContent(is("{foo=[bar]}foo=bar")); + assertThat(driver.close(), is(true)); + } + + @Test + public void requireThatFormPostKeepsContentWhenConfiguredTo() throws Exception { + final TestDriver driver = newDriverWithFormPostContentRemoved(new ParameterPrinterRequestHandler(), false); + final ResponseValidator response = + driver.client().newPost("/status.html") + .addHeader(CONTENT_TYPE, APPLICATION_X_WWW_FORM_URLENCODED) + .setContent("foo=bar") + .execute(); + response.expectStatusCode(is(OK)) + .expectContent(is("{foo=[bar]}foo=bar")); + assertThat(driver.close(), is(true)); + } + + @Test + public void requireThatFormPostRemovesContentWhenConfiguredTo() throws Exception { + final TestDriver driver = newDriverWithFormPostContentRemoved(new ParameterPrinterRequestHandler(), true); + final ResponseValidator response = + driver.client().newPost("/status.html") + .addHeader(CONTENT_TYPE, APPLICATION_X_WWW_FORM_URLENCODED) + .setContent("foo=bar") + .execute(); + response.expectStatusCode(is(OK)) + .expectContent(is("{foo=[bar]}")); + assertThat(driver.close(), is(true)); + } + + @Test + public void requireThatFormPostWithCharsetSpecifiedWorks() throws Exception { + final TestDriver driver = TestDrivers.newInstance(new ParameterPrinterRequestHandler()); + final String requestContent = generateContent('a', 30); + final ResponseValidator response = + driver.client().newPost("/status.html") + .addHeader(X_DISABLE_CHUNKING, "true") + .addHeader(CONTENT_TYPE, APPLICATION_X_WWW_FORM_URLENCODED + ";charset=UTF-8") + .setContent(requestContent) + .execute(); + response.expectStatusCode(is(OK)) + .expectContent(startsWith('{' + requestContent + "=[]}")); + assertThat(driver.close(), is(true)); + } + + @Test + public void requireThatEmptyFormPostWorks() throws Exception { + final TestDriver driver = TestDrivers.newInstance(new ParameterPrinterRequestHandler()); + final ResponseValidator response = + driver.client().newPost("/status.html") + .addHeader(CONTENT_TYPE, APPLICATION_X_WWW_FORM_URLENCODED) + .execute(); + response.expectStatusCode(is(OK)) + .expectContent(is("{}")); + assertThat(driver.close(), is(true)); + } + + @Test + public void requireThatFormParametersAreParsed() throws Exception { + final TestDriver driver = TestDrivers.newInstance(new ParameterPrinterRequestHandler()); + final ResponseValidator response = + driver.client().newPost("/status.html") + .addHeader(CONTENT_TYPE, APPLICATION_X_WWW_FORM_URLENCODED) + .setContent("a=b&c=d") + .execute(); + response.expectStatusCode(is(OK)) + .expectContent(startsWith("{a=[b], c=[d]}")); + assertThat(driver.close(), is(true)); + } + + @Test + public void requireThatUriParametersAreParsed() throws Exception { + final TestDriver driver = TestDrivers.newInstance(new ParameterPrinterRequestHandler()); + final ResponseValidator response = + driver.client().newPost("/status.html?a=b&c=d") + .addHeader(CONTENT_TYPE, APPLICATION_X_WWW_FORM_URLENCODED) + .execute(); + response.expectStatusCode(is(OK)) + .expectContent(is("{a=[b], c=[d]}")); + assertThat(driver.close(), is(true)); + } + + @Test + public void requireThatFormAndUriParametersAreMerged() throws Exception { + final TestDriver driver = TestDrivers.newInstance(new ParameterPrinterRequestHandler()); + final ResponseValidator response = + driver.client().newPost("/status.html?a=b&c=d1") + .addHeader(CONTENT_TYPE, APPLICATION_X_WWW_FORM_URLENCODED) + .setContent("c=d2&e=f") + .execute(); + response.expectStatusCode(is(OK)) + .expectContent(startsWith("{a=[b], c=[d1, d2], e=[f]}")); + assertThat(driver.close(), is(true)); + } + + @Test + public void requireThatFormCharsetIsHonored() throws Exception { + final TestDriver driver = newDriverWithFormPostContentRemoved(new ParameterPrinterRequestHandler(), true); + final ResponseValidator response = + driver.client().newPost("/status.html") + .addHeader(CONTENT_TYPE, APPLICATION_X_WWW_FORM_URLENCODED + ";charset=ISO-8859-1") + .setBinaryContent(new byte[]{66, (byte) 230, 114, 61, 98, 108, (byte) 229}) + .execute(); + response.expectStatusCode(is(OK)) + .expectContent(is("{B\u00e6r=[bl\u00e5]}")); + assertThat(driver.close(), is(true)); + } + + @Test + public void requireThatUnknownFormCharsetIsTreatedAsBadRequest() throws Exception { + final TestDriver driver = TestDrivers.newInstance(new ParameterPrinterRequestHandler()); + final ResponseValidator response = + driver.client().newPost("/status.html") + .addHeader(CONTENT_TYPE, APPLICATION_X_WWW_FORM_URLENCODED + ";charset=FLARBA-GARBA-7") + .setContent("a=b") + .execute(); + response.expectStatusCode(is(UNSUPPORTED_MEDIA_TYPE)); + assertThat(driver.close(), is(true)); + } + + @Test + public void requireThatFormPostWithPercentEncodedContentIsDecoded() throws Exception { + final TestDriver driver = TestDrivers.newInstance(new ParameterPrinterRequestHandler()); + final ResponseValidator response = + driver.client().newPost("/status.html") + .addHeader(CONTENT_TYPE, APPLICATION_X_WWW_FORM_URLENCODED) + .setContent("%20%3D%C3%98=%22%25+") + .execute(); + response.expectStatusCode(is(OK)) + .expectContent(startsWith("{ =\u00d8=[\"% ]}")); + assertThat(driver.close(), is(true)); + } + + @Test + public void requireThatFormPostWithThrowingHandlerIsExceptionSafe() throws Exception { + final TestDriver driver = TestDrivers.newInstance(new ThrowingHandler()); + final ResponseValidator response = + driver.client().newPost("/status.html") + .addHeader(CONTENT_TYPE, APPLICATION_X_WWW_FORM_URLENCODED) + .setContent("a=b") + .execute(); + response.expectStatusCode(is(INTERNAL_SERVER_ERROR)); + assertThat(driver.close(), is(true)); + } + + @Test + public void requireThatMultiPostWorks() throws Exception { + // This is taken from tcpdump of bug 5433352 and reassembled here to see that httpserver passes things on. + final String startTxtContent = "this is a test for POST."; + final String updaterConfContent + = "identifier = updater\n" + + "server_type = gds\n"; + final TestDriver driver = TestDrivers.newInstance(new EchoRequestHandler()); + final ResponseValidator response = + driver.client().newPost("/status.html") + .setMultipartContent( + newFileBody("", "start.txt", startTxtContent), + newFileBody("", "updater.conf", updaterConfContent)) + .execute(); + response.expectStatusCode(is(OK)) + .expectContent(containsString(startTxtContent)) + .expectContent(containsString(updaterConfContent)); + } + + @Test + public void requireThatRequestCookiesAreReceived() throws Exception { + final TestDriver driver = TestDrivers.newInstance(new CookiePrinterRequestHandler()); + final ResponseValidator response = + driver.client().newPost("/status.html") + .addHeader(COOKIE, "foo=bar") + .execute(); + response.expectStatusCode(is(OK)) + .expectContent(containsString("[foo=bar]")); + assertThat(driver.close(), is(true)); + } + + @Test + public void requireThatSetCookieHeaderIsCorrect() throws Exception { + final TestDriver driver = TestDrivers.newInstance(new CookieSetterRequestHandler( + new Cookie("foo", "bar").setComment("comment yeah") + .setCommentURL("http://comment.yes/") + .setDiscard(true) + .setDomain(".localhost") + .setHttpOnly(true) + .setMaxAge(5000, TimeUnit.SECONDS) + .setPath("/foopath") + .setSecure(true) + .setVersion(2))); + driver.client().get("/status.html") + .expectStatusCode(is(OK)) + .expectHeader("Set-Cookie", is("foo=bar; " + + "Max-Age=5000; " + + "Path=\"/foopath\"; " + + "Domain=.localhost; " + + "Secure; HTTPOnly; " + + "Comment=\"comment yeah\"; " + + "Version=1; " + + "CommentURL=\"http://comment.yes/\"; " + + "Discard")); + assertThat(driver.close(), is(true)); + } + + @Test(enabled = false) + public void requireThatGeneratedTraceIdIsSet() throws Exception { + final TestDriver driver = TestDrivers.newInstance(new EchoRequestHandler()); + final SimpleHttpClient client1 = driver.client(); + final SimpleHttpClient client2 = driver.newClient(); + + client1.newGet("/status.html").addHeader(X_TRACE_ID, "true").execute() + .expectHeader("X-JDisc-TraceId", matchesPattern("\\w+00000000")); + client1.newGet("/status.html").addHeader(X_TRACE_ID, "true").execute() + .expectHeader("X-JDisc-TraceId", matchesPattern("\\w+00000001")); + client2.newGet("/status.html").addHeader(X_TRACE_ID, "true").execute() + .expectHeader("X-JDisc-TraceId", matchesPattern("\\w+00000000")); + client1.newGet("/status.html").addHeader(X_TRACE_ID, "true").execute() + .expectHeader("X-JDisc-TraceId", matchesPattern("\\w+00000002")); + client2.newGet("/status.html").addHeader(X_TRACE_ID, "true").execute() + .expectHeader("X-JDisc-TraceId", matchesPattern("\\w+00000001")); + client2.newGet("/status.html").addHeader(X_TRACE_ID, "true").execute() + .expectHeader("X-JDisc-TraceId", matchesPattern("\\w+00000002")); + + assertThat(driver.close(), is(true)); + } + + @Test(enabled = false) + public void requireThatClientTraceIdIsSet() throws Exception { + final TestDriver driver = TestDrivers.newInstance(new EchoRequestHandler()); + driver.client().newGet("/status.html").addHeader(X_TRACE_ID, "foo").execute() + .expectHeader(X_TRACE_ID, is("foo")); + assertThat(driver.close(), is(true)); + } + + @Test + public void requireThatTimeoutWorks() throws Exception { + final UnresponsiveHandler requestHandler = new UnresponsiveHandler(); + final TestDriver driver = TestDrivers.newInstance(requestHandler); + driver.client().get("/status.html") + .expectStatusCode(is(REQUEST_TIMEOUT)); + ResponseDispatch.newInstance(OK).dispatch(requestHandler.responseHandler); + assertThat(driver.close(), is(true)); + } + + // Header with no value is disallowed by https://tools.ietf.org/html/rfc7230#section-3.2 + @Test + public void requireThatHeaderWithNullValueIsOmitted() throws Exception { + final TestDriver driver = TestDrivers.newInstance(new EchoWithHeaderRequestHandler("X-Foo", null)); + driver.client().get("/status.html") + .expectStatusCode(is(OK)) + .expectNoHeader("X-Foo"); + assertThat(driver.close(), is(true)); + } + + // Header with no value is disallowed by https://tools.ietf.org/html/rfc7230#section-3.2 + @Test + public void requireThatHeaderWithEmptyValueIsOmitted() throws Exception { + final TestDriver driver = TestDrivers.newInstance(new EchoWithHeaderRequestHandler("X-Foo", "")); + driver.client().get("/status.html") + .expectStatusCode(is(OK)) + .expectNoHeader("X-Foo"); + assertThat(driver.close(), is(true)); + } + + @Test + public void requireThatNoConnectionHeaderMeansKeepAliveInHttp11KeepAliveDisabled() throws Exception { + final TestDriver driver = TestDrivers.newInstance(new EchoWithHeaderRequestHandler(CONNECTION, CLOSE)); + driver.client().get("/status.html") + .expectHeader(CONNECTION, is(CLOSE)); + assertThat(driver.close(), is(true)); + } + + @Test(enabled = false) + public void requireThatRequestTrailersAreSupported() throws Exception { + final TestDriver driver = TestDrivers.newInstance(new RequestHandlerThatEchoesTrailers()); + assertThat(driver.client().raw("GET /status.html HTTP/1.1\r\n" + + "Host: localhost\r\n" + + "Transfer-Encoding: chunked\r\n\r\n" + + "0\r\n" + + "X-Foo: foo\r\n" + + "X-Bar: bar\r\n" + + "\r\n"), + containsPattern(Pattern.quote("{X-Bar=[bar], X-Foo=[foo]}"))); + assertThat(driver.close(), is(true)); + } + + @Test(enabled = false) + public void requireThatResponseTrailersAreSupported() throws Exception { + final HeaderFields trailers = new HeaderFields(); + trailers.add("X-Foo", "foo"); + trailers.add("X-Bar", "bar"); + final TestDriver driver = TestDrivers.newInstance(new RequestHandlerThatSetsResponseTrailers(trailers)); + driver.client().get("/status.html") + .expectTrailer("X-Foo", is("foo")) + .expectTrailer("X-Bar", is("bar")); + assertThat(driver.close(), is(true)); + } + + @Test + public void requireThatServerCanRespondToSslRequest() throws Exception { + final TestDriver driver = TestDrivers.newInstanceWithSsl(new EchoRequestHandler()); + driver.client().get("/status.html") + .expectStatusCode(is(OK)); + assertThat(driver.close(), is(true)); + } + + @Test + public void requireThatConnectedAtReturnsNonZero() throws Exception { + final TestDriver driver = TestDrivers.newInstance(new ConnectedAtRequestHandler()); + driver.client().get("/status.html") + .expectStatusCode(is(OK)) + .expectContent(matchesPattern("\\d{13,}")); + assertThat(driver.close(), is(true)); + } + + private static RequestHandler mockRequestHandler() { + final RequestHandler mockRequestHandler = mock(RequestHandler.class); + when(mockRequestHandler.refer()).thenReturn(References.NOOP_REFERENCE); + return mockRequestHandler; + } + + private static String generateContent(final char c, final int len) { + final StringBuilder ret = new StringBuilder(len); + for (int i = 0; i < len; ++i) { + ret.append(c); + } + return ret.toString(); + } + + private static TestDriver newDriverWithFormPostContentRemoved( + final RequestHandler requestHandler, final boolean removeFormPostBody) throws Exception { + return TestDrivers.newConfiguredInstance( + requestHandler, + new ServerConfig.Builder() + .removeRawPostBodyForWwwUrlEncodedPost(removeFormPostBody), + new ConnectorConfig.Builder()); + } + + private static FormBodyPart newFileBody(final String parameterName, final String fileName, final String fileContent) + throws Exception { + return new FormBodyPart( + parameterName, + new StringBody(fileContent) { + @Override + public String getFilename() { + return fileName; + } + + @Override + public String getTransferEncoding() { + return "binary"; + } + + @Override + public String getMimeType() { + return ""; + } + + @Override + public String getCharset() { + return null; + } + }); + } + + private static class ConnectedAtRequestHandler extends AbstractRequestHandler { + + @Override + public ContentChannel handleRequest(final Request request, final ResponseHandler handler) { + final HttpRequest httpRequest = (HttpRequest)request; + final String connectedAt = String.valueOf(httpRequest.getConnectedAt(TimeUnit.MILLISECONDS)); + final ContentChannel ch = handler.handleResponse(new Response(OK)); + ch.write(ByteBuffer.wrap(connectedAt.getBytes(StandardCharsets.UTF_8)), null); + ch.close(null); + return null; + } + } + + private static class CookieSetterRequestHandler extends AbstractRequestHandler { + + final Cookie cookie; + + CookieSetterRequestHandler(final Cookie cookie) { + this.cookie = cookie; + } + + @Override + public ContentChannel handleRequest(final Request request, final ResponseHandler handler) { + final HttpResponse response = HttpResponse.newInstance(OK); + response.encodeSetCookieHeader(Collections.singletonList(cookie)); + ResponseDispatch.newInstance(response).dispatch(handler); + return null; + } + } + + private static class CookiePrinterRequestHandler extends AbstractRequestHandler { + + @Override + public ContentChannel handleRequest(final Request request, final ResponseHandler handler) { + final List<Cookie> cookies = new ArrayList<>(((HttpRequest)request).decodeCookieHeader()); + Collections.sort(cookies, new CookieComparator()); + final ContentChannel out = ResponseDispatch.newInstance(Response.Status.OK).connect(handler); + out.write(StandardCharsets.UTF_8.encode(cookies.toString()), null); + out.close(null); + return null; + } + } + + private static class ParameterPrinterRequestHandler extends AbstractRequestHandler { + private static final CompletionHandler NULL_COMPLETION_HANDLER = null; + + @Override + public ContentChannel handleRequest(final Request request, final ResponseHandler handler) { + final Map<String, List<String>> parameters = + new TreeMap<>(((HttpRequest)request).parameters()); + final ContentChannel responseContentChannel + = ResponseDispatch.newInstance(Response.Status.OK).connect(handler); + responseContentChannel.write( + ByteBuffer.wrap(parameters.toString().getBytes(StandardCharsets.UTF_8)), + NULL_COMPLETION_HANDLER); + + // Have the request content written back to the response. + return responseContentChannel; + } + } + + private static class RequestHandlerThatEchoesTrailers extends AbstractRequestHandler { + + @Override + public ContentChannel handleRequest(final Request request, final ResponseHandler handler) { + final HttpRequest httpRequest = (HttpRequest)request; + final ContentChannel out = ResponseDispatch.newInstance(Response.Status.OK).connect(handler); + return new ContentChannel() { + + @Override + public void write(final ByteBuffer buf, final CompletionHandler handler) { + handler.completed(); + } + + @Override + public void close(final CompletionHandler handler) { + synchronized (httpRequest.trailers()) { + out.write(StandardCharsets.UTF_8.encode(httpRequest.trailers().toString()), null); + } + out.close(null); + handler.completed(); + } + }; + } + } + + private static class RequestHandlerThatSetsResponseTrailers extends AbstractRequestHandler { + + final HeaderFields trailers; + + RequestHandlerThatSetsResponseTrailers(final HeaderFields trailers) { + this.trailers = trailers; + } + + @Override + public ContentChannel handleRequest(final Request request, final ResponseHandler handler) { + final HttpResponse response = HttpResponse.newInstance(OK); + final ContentChannel content = handler.handleResponse(response); + synchronized (response.trailers()) { + response.trailers().putAll(this.trailers); + } + content.close(null); + return null; + } + } + + private static class ThrowingHandler extends AbstractRequestHandler { + @Override + public ContentChannel handleRequest(final Request request, final ResponseHandler handler) { + throw new RuntimeException("Deliberately thrown exception"); + } + } + + private static class UnresponsiveHandler extends AbstractRequestHandler { + + ResponseHandler responseHandler; + + @Override + public ContentChannel handleRequest(final Request request, final ResponseHandler handler) { + request.setTimeout(100, TimeUnit.MILLISECONDS); + responseHandler = handler; + return null; + } + } + + private static class EchoRequestHandler extends AbstractRequestHandler { + + @Override + public ContentChannel handleRequest(final Request request, final ResponseHandler handler) { + return handler.handleResponse(new Response(OK)); + } + } + + private static class EchoWithHeaderRequestHandler extends AbstractRequestHandler { + + final String headerName; + final String headerValue; + + EchoWithHeaderRequestHandler(final String headerName, final String headerValue) { + this.headerName = headerName; + this.headerValue = headerValue; + } + + @Override + public ContentChannel handleRequest(final Request request, final ResponseHandler handler) { + final Response response = new Response(OK); + response.headers().add(headerName, headerValue); + return handler.handleResponse(response); + } + } + + private static Module newBindingSetSelector(final String setName) { + return new AbstractModule() { + + @Override + protected void configure() { + bind(BindingSetSelector.class).toInstance(new BindingSetSelector() { + + @Override + public String select(final URI uri) { + return setName; + } + }); + } + }; + } + + private static class CookieComparator implements Comparator<Cookie> { + + @Override + public int compare(final Cookie lhs, final Cookie rhs) { + return lhs.getName().compareTo(rhs.getName()); + } + } +} |