Moved HttpClientTransport SPDY implementation in its own module under the SPDY project.

This commit is contained in:
Simone Bordet 2013-07-23 09:40:34 +02:00
parent af06b25538
commit beee37e96a
13 changed files with 773 additions and 76 deletions

View File

@ -0,0 +1,101 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.eclipse.jetty.spdy</groupId>
<artifactId>spdy-parent</artifactId>
<version>9.1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>spdy-http-client-transport</artifactId>
<name>Jetty :: SPDY :: HTTP Client Transport</name>
<properties>
<bundle-symbolic-name>${project.groupId}.client.http</bundle-symbolic-name>
</properties>
<build>
<plugins>
<plugin>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy</id>
<phase>generate-resources</phase>
<goals>
<goal>copy</goal>
</goals>
<configuration>
<artifactItems>
<artifactItem>
<groupId>org.mortbay.jetty.npn</groupId>
<artifactId>npn-boot</artifactId>
<version>${npn.version}</version>
<type>jar</type>
<overWrite>false</overWrite>
<outputDirectory>${project.build.directory}/npn</outputDirectory>
</artifactItem>
</artifactItems>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<argLine>-Xbootclasspath/p:${project.build.directory}/npn/npn-boot-${npn.version}.jar</argLine>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<extensions>true</extensions>
<executions>
<execution>
<goals>
<goal>manifest</goal>
</goals>
<configuration>
<instructions>
<Export-Package>org.eclipse.jetty.spdy.client.http;version="9.1"</Export-Package>
<Import-Package>!org.eclipse.jetty.npn,org.eclipse.jetty.*;version="[9.0,10.0)",*</Import-Package>
</instructions>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-client</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.spdy</groupId>
<artifactId>spdy-client</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.spdy</groupId>
<artifactId>spdy-http-common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.spdy</groupId>
<artifactId>spdy-http-server</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -16,7 +16,7 @@
// ========================================================================
//
package org.eclipse.jetty.client.spdy;
package org.eclipse.jetty.spdy.client.http;
import org.eclipse.jetty.client.HttpChannel;
import org.eclipse.jetty.client.HttpDestination;

View File

@ -16,7 +16,7 @@
// ========================================================================
//
package org.eclipse.jetty.client.spdy;
package org.eclipse.jetty.spdy.client.http;
import java.net.SocketAddress;

View File

@ -16,7 +16,7 @@
// ========================================================================
//
package org.eclipse.jetty.client.spdy;
package org.eclipse.jetty.spdy.client.http;
import org.eclipse.jetty.client.HttpChannel;
import org.eclipse.jetty.client.HttpClient;

View File

@ -16,7 +16,7 @@
// ========================================================================
//
package org.eclipse.jetty.client.spdy;
package org.eclipse.jetty.spdy.client.http;
import java.util.concurrent.atomic.AtomicReference;
@ -30,7 +30,7 @@ import org.eclipse.jetty.util.Promise;
public class HttpDestinationOverSPDY extends HttpDestination implements Promise<Connection>
{
private final AtomicReference<ConnectState> connect = new AtomicReference<>(ConnectState.DISCONNECTED);
private volatile HttpConnectionOverSPDY connection;
private HttpConnectionOverSPDY connection;
public HttpDestinationOverSPDY(HttpClient client, String scheme, String host, int port)
{
@ -48,7 +48,7 @@ public class HttpDestinationOverSPDY extends HttpDestination implements Promise<
case DISCONNECTED:
{
if (!connect.compareAndSet(current, ConnectState.CONNECTING))
continue;
break;
newConnection(this);
return;
}
@ -59,10 +59,9 @@ public class HttpDestinationOverSPDY extends HttpDestination implements Promise<
}
case CONNECTED:
{
HttpConnectionOverSPDY connection = this.connection;
if (connection != null)
process(connection, false);
break;
if (process(connection, false))
break;
return;
}
default:
{
@ -75,14 +74,14 @@ public class HttpDestinationOverSPDY extends HttpDestination implements Promise<
@Override
public void succeeded(Connection result)
{
HttpConnectionOverSPDY connection = this.connection = (HttpConnectionOverSPDY)result;
if (connect.compareAndSet(ConnectState.CONNECTING, ConnectState.CONNECTED))
{
HttpConnectionOverSPDY connection = this.connection = (HttpConnectionOverSPDY)result;
process(connection, true);
}
else
{
result.close();
connection.close();
failed(new IllegalStateException());
}
}
@ -93,39 +92,40 @@ public class HttpDestinationOverSPDY extends HttpDestination implements Promise<
connect.set(ConnectState.DISCONNECTED);
}
private void process(final HttpConnectionOverSPDY connection, boolean dispatch)
private boolean process(final HttpConnectionOverSPDY connection, boolean dispatch)
{
HttpClient client = getHttpClient();
final HttpExchange exchange = getHttpExchanges().poll();
LOG.debug("Processing exchange {} on connection {}", exchange, connection);
if (exchange != null)
if (exchange == null)
return false;
final Request request = exchange.getRequest();
Throwable cause = request.getAbortCause();
if (cause != null)
{
final Request request = exchange.getRequest();
Throwable cause = request.getAbortCause();
if (cause != null)
LOG.debug("Abort before processing {}: {}", exchange, cause);
abort(exchange, cause);
}
else
{
if (dispatch)
{
abort(exchange, cause);
LOG.debug("Aborted before processing {}: {}", exchange, cause);
client.getExecutor().execute(new Runnable()
{
@Override
public void run()
{
connection.send(exchange);
}
});
}
else
{
if (dispatch)
{
client.getExecutor().execute(new Runnable()
{
@Override
public void run()
{
connection.send(exchange);
}
});
}
else
{
connection.send(exchange);
}
connection.send(exchange);
}
}
return true;
}
private enum ConnectState

View File

@ -16,7 +16,7 @@
// ========================================================================
//
package org.eclipse.jetty.client.spdy;
package org.eclipse.jetty.spdy.client.http;
import org.eclipse.jetty.client.HttpExchange;
import org.eclipse.jetty.client.HttpReceiver;
@ -32,6 +32,7 @@ import org.eclipse.jetty.spdy.api.RstInfo;
import org.eclipse.jetty.spdy.api.Stream;
import org.eclipse.jetty.spdy.api.StreamFrameListener;
import org.eclipse.jetty.spdy.api.StreamStatus;
import org.eclipse.jetty.spdy.http.HTTPSPDYHeader;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.Fields;
@ -55,37 +56,52 @@ public class HttpReceiverOverSPDY extends HttpReceiver implements StreamFrameLis
if (exchange == null)
return;
HttpResponse response = exchange.getResponse();
Fields fields = replyInfo.getHeaders();
// TODO: use HTTPSPDYHeader enum
HttpVersion version = HttpVersion.fromString(fields.get(":version").value());
response.version(version);
Integer status = fields.get(":status").valueAsInt();
response.status(status);
response.reason(HttpStatus.getMessage(status));
onResponseBegin(exchange);
for (Fields.Field field : fields)
try
{
// TODO: handle multiple values properly
// TODO: skip special headers
HttpField httpField = new HttpField(field.name(), field.value());
onResponseHeader(exchange, httpField);
HttpResponse response = exchange.getResponse();
Fields fields = replyInfo.getHeaders();
short spdy = stream.getSession().getVersion();
HttpVersion version = HttpVersion.fromString(fields.get(HTTPSPDYHeader.VERSION.name(spdy)).value());
response.version(version);
String[] status = fields.get(HTTPSPDYHeader.STATUS.name(spdy)).value().split(" ", 2);
Integer code = Integer.parseInt(status[0]);
response.status(code);
String reason = status.length < 2 ? HttpStatus.getMessage(code) : status[1];
response.reason(reason);
if (responseBegin(exchange))
{
for (Fields.Field field : fields)
{
String name = field.name();
if (HTTPSPDYHeader.from(spdy, name) != null)
continue;
// TODO: handle multiple values properly
HttpField httpField = new HttpField(name, field.value());
responseHeader(exchange, httpField);
}
if (responseHeaders(exchange))
{
if (replyInfo.isClose())
{
responseSuccess(exchange);
}
}
}
}
onResponseHeaders(exchange);
if (replyInfo.isClose())
catch (Exception x)
{
onResponseSuccess(exchange);
responseFailure(x);
}
}
@Override
public StreamFrameListener onPush(Stream stream, PushInfo pushInfo)
{
// SPDY push not supported
getHttpChannel().getSession().rst(new RstInfo(stream.getId(), StreamStatus.REFUSED_STREAM), new Callback.Adapter());
return null;
}
@ -103,14 +119,24 @@ public class HttpReceiverOverSPDY extends HttpReceiver implements StreamFrameLis
if (exchange == null)
return;
int length = dataInfo.length();
// TODO: avoid data copy here
onResponseContent(exchange, dataInfo.asByteBuffer(false));
dataInfo.consume(length);
if (dataInfo.isClose())
try
{
onResponseSuccess(exchange);
int length = dataInfo.length();
// TODO: avoid data copy here
boolean process = responseContent(exchange, dataInfo.asByteBuffer(false));
dataInfo.consume(length);
if (process)
{
if (dataInfo.isClose())
{
responseSuccess(exchange);
}
}
}
catch (Exception x)
{
responseFailure(x);
}
}
}

View File

@ -16,17 +16,18 @@
// ========================================================================
//
package org.eclipse.jetty.client.spdy;
package org.eclipse.jetty.spdy.client.http;
import org.eclipse.jetty.client.HttpContent;
import org.eclipse.jetty.client.HttpExchange;
import org.eclipse.jetty.client.HttpSender;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.spdy.api.ByteBufferDataInfo;
import org.eclipse.jetty.spdy.api.Stream;
import org.eclipse.jetty.spdy.api.SynInfo;
import org.eclipse.jetty.spdy.http.HTTPSPDYHeader;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.Fields;
import org.eclipse.jetty.util.Promise;
@ -46,14 +47,33 @@ public class HttpSenderOverSPDY extends HttpSender
}
@Override
protected void sendHeaders(HttpExchange exchange, final HttpContent content)
protected void sendHeaders(HttpExchange exchange, final HttpContent content, final Callback callback)
{
final Request request = exchange.getRequest();
short spdyVersion = getHttpChannel().getSession().getVersion();
Fields fields = new Fields();
HttpFields headers = request.getHeaders();
for (HttpField header : headers)
fields.add(header.getName(), header.getValue());
HttpField hostHeader = null;
for (HttpField header : request.getHeaders())
{
String name = header.getName();
// The host header needs a special treatment
if (HTTPSPDYHeader.from(spdyVersion, name) != HTTPSPDYHeader.HOST)
fields.add(name, header.getValue());
else
hostHeader = header;
}
// Add special SPDY headers
fields.put(HTTPSPDYHeader.METHOD.name(spdyVersion), request.getMethod().asString());
String path = request.getPath();
String query = request.getQuery();
if (query != null)
path += "?" + query;
fields.put(HTTPSPDYHeader.URI.name(spdyVersion), path);
fields.put(HTTPSPDYHeader.VERSION.name(spdyVersion), request.getVersion().asString());
if (hostHeader != null)
fields.put(HTTPSPDYHeader.HOST.name(spdyVersion), hostHeader.getValue());
SynInfo synInfo = new SynInfo(fields, !content.hasContent());
getHttpChannel().getSession().syn(synInfo, getHttpChannel().getHttpReceiver(), new Promise<Stream>()
@ -63,23 +83,29 @@ public class HttpSenderOverSPDY extends HttpSender
{
if (content.hasContent())
HttpSenderOverSPDY.this.stream = stream;
content.succeeded();
callback.succeeded();
}
@Override
public void failed(Throwable failure)
{
content.failed(failure);
callback.failed(failure);
}
});
}
@Override
protected void sendContent(HttpExchange exchange, HttpContent content)
protected void sendContent(HttpExchange exchange, HttpContent content, Callback callback)
{
assert stream != null;
ByteBufferDataInfo dataInfo = new ByteBufferDataInfo(content.getByteBuffer(), content.isLast());
stream.data(dataInfo, content);
if (content.isConsumed())
{
callback.succeeded();
}
else
{
ByteBufferDataInfo dataInfo = new ByteBufferDataInfo(content.getByteBuffer(), content.isLast());
stream.data(dataInfo, callback);
}
}
@Override

View File

@ -0,0 +1,82 @@
//
// ========================================================================
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.spdy.client.http;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.NetworkConnector;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.spdy.api.SPDY;
import org.eclipse.jetty.spdy.client.SPDYClient;
import org.eclipse.jetty.spdy.server.http.HTTPSPDYServerConnectionFactory;
import org.eclipse.jetty.spdy.server.http.PushStrategy;
import org.eclipse.jetty.toolchain.test.TestTracker;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.junit.After;
import org.junit.Rule;
public abstract class AbstractHttpClientServerTest
{
@Rule
public final TestTracker tracker = new TestTracker();
protected Server server;
protected NetworkConnector connector;
protected SPDYClient.Factory factory;
protected HttpClient client;
protected String scheme = HttpScheme.HTTP.asString();
public void start(Handler handler) throws Exception
{
server = new Server();
short version = SPDY.V3;
HTTPSPDYServerConnectionFactory spdyConnectionFactory = new HTTPSPDYServerConnectionFactory(version, new HttpConfiguration(), new PushStrategy.None());
connector = new ServerConnector(server, spdyConnectionFactory);
server.addConnector(connector);
server.setHandler(handler);
server.start();
QueuedThreadPool executor = new QueuedThreadPool();
executor.setName(executor.getName() + "-client");
factory = new SPDYClient.Factory(executor);
factory.start();
client = new HttpClient(new HttpClientTransportOverSPDY(factory.newSPDYClient(version)), null);
client.setExecutor(executor);
client.start();
}
@After
public void dispose() throws Exception
{
if (client != null)
client.stop();
if (factory != null)
factory.stop();
if (server != null)
server.stop();
}
}

View File

@ -0,0 +1,36 @@
//
// ========================================================================
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.spdy.client.http;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.handler.AbstractHandler;
public class EmptyServerHandler extends AbstractHandler
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
}
}

View File

@ -0,0 +1,422 @@
//
// ========================================================================
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.spdy.client.http;
import java.io.IOException;
import java.net.URI;
import java.net.URLEncoder;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.zip.GZIPOutputStream;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.client.util.BytesContentProvider;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.toolchain.test.annotation.Slow;
import org.junit.Assert;
import org.junit.Test;
public class HttpClientTest extends AbstractHttpClientServerTest
{
@Test
public void test_GET_ResponseWithoutContent() throws Exception
{
start(new EmptyServerHandler());
Response response = client.GET(scheme + "://localhost:" + connector.getLocalPort());
Assert.assertNotNull(response);
Assert.assertEquals(200, response.getStatus());
}
@Test
public void test_GET_ResponseWithContent() throws Exception
{
final byte[] data = new byte[]{0, 1, 2, 3, 4, 5, 6, 7};
start(new AbstractHandler()
{
@Override
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
response.getOutputStream().write(data);
baseRequest.setHandled(true);
}
});
ContentResponse response = client.GET(scheme + "://localhost:" + connector.getLocalPort());
Assert.assertNotNull(response);
Assert.assertEquals(200, response.getStatus());
byte[] content = response.getContent();
Assert.assertArrayEquals(data, content);
}
@Test
public void test_GET_WithParameters_ResponseWithContent() throws Exception
{
final String paramName1 = "a";
final String paramName2 = "b";
start(new AbstractHandler()
{
@Override
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
response.setCharacterEncoding("UTF-8");
ServletOutputStream output = response.getOutputStream();
String paramValue1 = request.getParameter(paramName1);
output.write(paramValue1.getBytes("UTF-8"));
String paramValue2 = request.getParameter(paramName2);
Assert.assertEquals("", paramValue2);
output.write("empty".getBytes("UTF-8"));
baseRequest.setHandled(true);
}
});
String value1 = "\u20AC";
String paramValue1 = URLEncoder.encode(value1, "UTF-8");
String query = paramName1 + "=" + paramValue1 + "&" + paramName2;
ContentResponse response = client.GET(scheme + "://localhost:" + connector.getLocalPort() + "/?" + query);
Assert.assertNotNull(response);
Assert.assertEquals(200, response.getStatus());
String content = new String(response.getContent(), "UTF-8");
Assert.assertEquals(value1 + "empty", content);
}
@Test
public void test_GET_WithParametersMultiValued_ResponseWithContent() throws Exception
{
final String paramName1 = "a";
final String paramName2 = "b";
start(new AbstractHandler()
{
@Override
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
response.setCharacterEncoding("UTF-8");
ServletOutputStream output = response.getOutputStream();
String[] paramValues1 = request.getParameterValues(paramName1);
for (String paramValue : paramValues1)
output.write(paramValue.getBytes("UTF-8"));
String paramValue2 = request.getParameter(paramName2);
output.write(paramValue2.getBytes("UTF-8"));
baseRequest.setHandled(true);
}
});
String value11 = "\u20AC";
String value12 = "\u20AA";
String value2 = "&";
String paramValue11 = URLEncoder.encode(value11, "UTF-8");
String paramValue12 = URLEncoder.encode(value12, "UTF-8");
String paramValue2 = URLEncoder.encode(value2, "UTF-8");
String query = paramName1 + "=" + paramValue11 + "&" + paramName1 + "=" + paramValue12 + "&" + paramName2 + "=" + paramValue2;
ContentResponse response = client.GET(scheme + "://localhost:" + connector.getLocalPort() + "/?" + query);
Assert.assertNotNull(response);
Assert.assertEquals(200, response.getStatus());
String content = new String(response.getContent(), "UTF-8");
Assert.assertEquals(value11 + value12 + value2, content);
}
@Test
public void test_POST_WithParameters() throws Exception
{
final String paramName = "a";
final String paramValue = "\u20AC";
start(new AbstractHandler()
{
@Override
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
String value = request.getParameter(paramName);
if (paramValue.equals(value))
{
response.setCharacterEncoding("UTF-8");
response.setContentType("text/plain");
response.getOutputStream().print(value);
}
}
});
ContentResponse response = client.POST(scheme + "://localhost:" + connector.getLocalPort())
.param(paramName, paramValue)
.timeout(5, TimeUnit.SECONDS)
.send();
Assert.assertNotNull(response);
Assert.assertEquals(200, response.getStatus());
Assert.assertEquals(paramValue, new String(response.getContent(), "UTF-8"));
}
@Test
public void test_PUT_WithParameters() throws Exception
{
final String paramName = "a";
final String paramValue = "\u20AC";
start(new AbstractHandler()
{
@Override
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
String value = request.getParameter(paramName);
if (paramValue.equals(value))
{
response.setCharacterEncoding("UTF-8");
response.setContentType("text/plain");
response.getOutputStream().print(value);
}
}
});
URI uri = URI.create(scheme + "://localhost:" + connector.getLocalPort() + "/path?" + paramName + "=" + paramValue);
ContentResponse response = client.newRequest(uri)
.method(HttpMethod.PUT)
.timeout(5, TimeUnit.SECONDS)
.send();
Assert.assertNotNull(response);
Assert.assertEquals(200, response.getStatus());
Assert.assertEquals(paramValue, new String(response.getContent(), "UTF-8"));
}
@Test
public void test_POST_WithParameters_WithContent() throws Exception
{
final byte[] content = {0, 1, 2, 3};
final String paramName = "a";
final String paramValue = "\u20AC";
start(new AbstractHandler()
{
@Override
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
String value = request.getParameter(paramName);
if (paramValue.equals(value))
{
response.setCharacterEncoding("UTF-8");
response.setContentType("application/octet-stream");
response.getOutputStream().write(content);
}
}
});
ContentResponse response = client.POST(scheme + "://localhost:" + connector.getLocalPort() + "/?b=1")
.param(paramName, paramValue)
.content(new BytesContentProvider(content))
.timeout(555, TimeUnit.SECONDS)
.send();
Assert.assertNotNull(response);
Assert.assertEquals(200, response.getStatus());
Assert.assertArrayEquals(content, response.getContent());
}
@Test
public void test_POST_WithContent_NotifiesRequestContentListener() throws Exception
{
final byte[] content = {0, 1, 2, 3};
start(new EmptyServerHandler());
ContentResponse response = client.POST(scheme + "://localhost:" + connector.getLocalPort())
.onRequestContent(new Request.ContentListener()
{
@Override
public void onContent(Request request, ByteBuffer buffer)
{
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
if (!Arrays.equals(content, bytes))
request.abort(new Exception());
}
})
.content(new BytesContentProvider(content))
.timeout(5, TimeUnit.SECONDS)
.send();
Assert.assertNotNull(response);
Assert.assertEquals(200, response.getStatus());
}
@Test
public void test_POST_WithContent_TracksProgress() throws Exception
{
start(new EmptyServerHandler());
final AtomicInteger progress = new AtomicInteger();
ContentResponse response = client.POST(scheme + "://localhost:" + connector.getLocalPort())
.onRequestContent(new Request.ContentListener()
{
@Override
public void onContent(Request request, ByteBuffer buffer)
{
byte[] bytes = new byte[buffer.remaining()];
Assert.assertEquals(1, bytes.length);
buffer.get(bytes);
Assert.assertEquals(bytes[0], progress.getAndIncrement());
}
})
.content(new BytesContentProvider(new byte[]{0}, new byte[]{1}, new byte[]{2}, new byte[]{3}, new byte[]{4}))
.timeout(5, TimeUnit.SECONDS)
.send();
Assert.assertNotNull(response);
Assert.assertEquals(200, response.getStatus());
Assert.assertEquals(5, progress.get());
}
@Test
public void test_GZIP_ContentEncoding() throws Exception
{
final byte[] data = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
start(new AbstractHandler()
{
@Override
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
response.setHeader("Content-Encoding", "gzip");
GZIPOutputStream gzipOutput = new GZIPOutputStream(response.getOutputStream());
gzipOutput.write(data);
gzipOutput.finish();
}
});
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
.timeout(5, TimeUnit.SECONDS)
.send();
Assert.assertEquals(200, response.getStatus());
Assert.assertArrayEquals(data, response.getContent());
}
@Slow
@Test
public void test_Request_IdleTimeout() throws Exception
{
final long idleTimeout = 1000;
start(new AbstractHandler()
{
@Override
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
try
{
baseRequest.setHandled(true);
TimeUnit.MILLISECONDS.sleep(2 * idleTimeout);
}
catch (InterruptedException x)
{
throw new ServletException(x);
}
}
});
final String host = "localhost";
final int port = connector.getLocalPort();
try
{
client.newRequest(host, port)
.scheme(scheme)
.idleTimeout(idleTimeout, TimeUnit.MILLISECONDS)
.timeout(3 * idleTimeout, TimeUnit.MILLISECONDS)
.send();
Assert.fail();
}
catch (ExecutionException expected)
{
Assert.assertTrue(expected.getCause() instanceof TimeoutException);
}
// Make another request without specifying the idle timeout, should not fail
ContentResponse response = client.newRequest(host, port)
.scheme(scheme)
.timeout(3 * idleTimeout, TimeUnit.MILLISECONDS)
.send();
Assert.assertNotNull(response);
Assert.assertEquals(200, response.getStatus());
}
@Test
public void testSendToIPv6Address() throws Exception
{
start(new EmptyServerHandler());
ContentResponse response = client.newRequest("[::1]", connector.getLocalPort())
.scheme(scheme)
.timeout(5, TimeUnit.SECONDS)
.send();
Assert.assertNotNull(response);
Assert.assertEquals(200, response.getStatus());
}
@Test
public void test_HEAD_With_ResponseContentLength() throws Exception
{
final int length = 1024;
start(new AbstractHandler()
{
@Override
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
response.getOutputStream().write(new byte[length]);
}
});
// HEAD requests receive a Content-Length header, but do not
// receive the content so they must handle this case properly
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
.method(HttpMethod.HEAD)
.timeout(5, TimeUnit.SECONDS)
.send();
Assert.assertNotNull(response);
Assert.assertEquals(200, response.getStatus());
Assert.assertEquals(0, response.getContent().length);
// Perform a normal GET request to be sure the content is now read
response = client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
.timeout(5, TimeUnit.SECONDS)
.send();
Assert.assertNotNull(response);
Assert.assertEquals(200, response.getStatus());
Assert.assertEquals(length, response.getContent().length);
}
}

View File

@ -0,0 +1,4 @@
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
#org.eclipse.jetty.LEVEL=DEBUG
#org.eclipse.jetty.client.LEVEL=DEBUG
#org.eclipse.jetty.spdy.LEVEL=DEBUG