Jetty9 - First take at HTTP client implementation.

This commit is contained in:
Simone Bordet 2012-09-04 19:20:29 +02:00
parent 4de5b0ad63
commit b18ab0e76a
42 changed files with 2754 additions and 885 deletions

View File

@ -0,0 +1,77 @@
package org.eclipse.jetty.client;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.eclipse.jetty.client.api.Response;
public class BufferingResponseListener extends Response.Listener.Adapter
{
private final CountDownLatch latch = new CountDownLatch(1);
private final int maxCapacity;
private Response response;
private Throwable failure;
private byte[] buffer = new byte[0];
public BufferingResponseListener()
{
this(16 * 1024 * 1024);
}
public BufferingResponseListener(int maxCapacity)
{
this.maxCapacity = maxCapacity;
}
@Override
public void onContent(Response response, ByteBuffer content)
{
long newLength = buffer.length + content.remaining();
if (newLength > maxCapacity)
throw new IllegalStateException("Buffering capacity exceeded");
byte[] newBuffer = new byte[(int)newLength];
System.arraycopy(buffer, 0, newBuffer, 0, buffer.length);
content.get(newBuffer, buffer.length, content.remaining());
buffer = newBuffer;
}
@Override
public void onSuccess(Response response)
{
this.response = response;
latch.countDown();
}
@Override
public void onFailure(Response response, Throwable failure)
{
this.response = response;
this.failure = failure;
latch.countDown();
}
public Response await(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException
{
boolean expired = !latch.await(timeout, unit);
if (failure != null)
throw new ExecutionException(failure);
if (expired)
throw new TimeoutException();
return response;
}
public byte[] content()
{
return buffer;
}
public String contentAsString(String encoding)
{
return new String(content(), Charset.forName(encoding));
}
}

View File

@ -0,0 +1,32 @@
package org.eclipse.jetty.client;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Iterator;
import org.eclipse.jetty.client.api.ContentProvider;
public class ByteBufferContentProvider implements ContentProvider
{
private final ByteBuffer[] buffers;
public ByteBufferContentProvider(ByteBuffer... buffers)
{
this.buffers = buffers;
}
@Override
public long length()
{
int length = 0;
for (ByteBuffer buffer : buffers)
length += buffer.remaining();
return length;
}
@Override
public Iterator<ByteBuffer> iterator()
{
return Arrays.asList(buffers).iterator();
}
}

View File

@ -1,294 +0,0 @@
//========================================================================
//Copyright 2012-2012 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.client;
import java.io.IOException;
import java.net.SocketAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.Arrays;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import com.sun.jndi.toolkit.url.Uri;
import org.eclipse.jetty.client.api.Address;
import org.eclipse.jetty.client.api.Connection;
import org.eclipse.jetty.client.api.Destination;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.io.AsyncConnection;
import org.eclipse.jetty.io.AsyncEndPoint;
import org.eclipse.jetty.io.SelectChannelEndPoint;
import org.eclipse.jetty.io.SelectorManager;
import org.eclipse.jetty.util.FutureCallback;
import org.eclipse.jetty.util.Jetty;
import org.eclipse.jetty.util.component.AggregateLifeCycle;
/**
* <p>{@link HTTPClient} provides an asynchronous non-blocking implementation to perform HTTP requests to a server.</p>
* <p>{@link HTTPClient} provides easy-to-use methods such as {@link #GET(String)} that allow to perform HTTP
* requests in a one-liner, but also gives the ability to fine tune the configuration of requests via
* {@link Request.Builder}.</p>
* <p>{@link HTTPClient} acts as a central configuration point for network parameters (such as idle timeouts) and
* HTTP parameters (such as whether to follow redirects).</p>
* <p>{@link HTTPClient} transparently pools connections to servers, but allows direct control of connections for
* cases where this is needed.</p>
* <p>Typical usage:</p>
* <pre>
* // One liner:
* new HTTPClient().GET("http://localhost:8080/").get().getStatus();
*
* // Using the builder with a timeout
* HTTPClient client = new HTTPClient();
* Response response = client.builder("http://localhost:8080/").build().send().get(5, TimeUnit.SECONDS);
* int status = response.getStatus();
*
* // Asynchronously
* HTTPClient client = new HTTPClient();
* client.builder("http://localhost:8080/").build().send(new Response.Listener.Adapter()
* {
* &#64;Override
* public void onComplete(Response response)
* {
* ...
* }
* });
* </pre>
*/
public class HTTPClient extends AggregateLifeCycle
{
private final ConcurrentMap<String, Destination> destinations = new ConcurrentHashMap<>();
private volatile String agent = "Jetty/" + Jetty.VERSION;
private volatile boolean followRedirects = true;
private volatile Executor executor;
private volatile int maxConnectionsPerAddress = Integer.MAX_VALUE;
private volatile int maxQueueSizePerAddress = Integer.MAX_VALUE;
private volatile SocketAddress bindAddress;
private volatile SelectorManager selectorManager;
private volatile long idleTimeout;
@Override
protected void doStart() throws Exception
{
selectorManager = newSelectorManager();
addBean(selectorManager);
super.doStart();
}
protected SelectorManager newSelectorManager()
{
ClientSelectorManager result = new ClientSelectorManager();
result.setMaxIdleTime(getIdleTimeout());
return result;
}
@Override
protected void doStop() throws Exception
{
super.doStop();
}
public long getIdleTimeout()
{
return idleTimeout;
}
public void setIdleTimeout(long idleTimeout)
{
this.idleTimeout = idleTimeout;
}
/**
* @return the address to bind socket channels to
* @see #setBindAddress(SocketAddress)
*/
public SocketAddress getBindAddress()
{
return bindAddress;
}
/**
* @param bindAddress the address to bind socket channels to
* @see #getBindAddress()
*/
public void setBindAddress(SocketAddress bindAddress)
{
this.bindAddress = bindAddress;
}
public Future<Response> GET(String uri)
{
return GET(URI.create(uri));
}
public Future<Response> GET(URI uri)
{
return builder(uri)
.method("GET")
// Add decoder, cookies, agent, default headers, etc.
.agent(getUserAgent())
.followRedirects(isFollowRedirects())
.build()
.send();
}
public Request.Builder builder(String uri)
{
return builder(URI.create(uri));
}
public Request.Builder builder(URI uri)
{
return new StandardRequest(this, uri);
}
public Request.Builder builder(Request prototype)
{
return null;
}
public Destination getDestination(String address)
{
Destination destination = destinations.get(address);
if (destination == null)
{
destination = new StandardDestination(this, address);
Destination existing = destinations.putIfAbsent(address, destination);
if (existing != null)
destination = existing;
}
return destination;
}
public String getUserAgent()
{
return agent;
}
public void setUserAgent(String agent)
{
this.agent = agent;
}
public boolean isFollowRedirects()
{
return followRedirects;
}
public void setFollowRedirects(boolean follow)
{
this.followRedirects = follow;
}
public void join()
{
}
public void join(long timeout, TimeUnit unit)
{
}
public Future<Response> send(Request request, Response.Listener listener)
{
URI uri = request.uri();
String scheme = uri.getScheme();
if (!Arrays.asList("http", "https").contains(scheme.toLowerCase()))
throw new IllegalArgumentException("Invalid protocol " + scheme);
String key = scheme.toLowerCase() + "://" + uri.getHost().toLowerCase();
int port = uri.getPort();
if (port < 0)
key += "https".equalsIgnoreCase(scheme) ? ":443" : ":80";
return getDestination(key).send(request, listener);
}
public Executor getExecutor()
{
return executor;
}
public int getMaxConnectionsPerAddress()
{
return maxConnectionsPerAddress;
}
public void setMaxConnectionsPerAddress(int maxConnectionsPerAddress)
{
this.maxConnectionsPerAddress = maxConnectionsPerAddress;
}
public int getMaxQueueSizePerAddress()
{
return maxQueueSizePerAddress;
}
public void setMaxQueueSizePerAddress(int maxQueueSizePerAddress)
{
this.maxQueueSizePerAddress = maxQueueSizePerAddress;
}
protected Future<Connection> newConnection(Destination destination) throws IOException
{
SocketChannel channel = SocketChannel.open();
SocketAddress bindAddress = getBindAddress();
if (bindAddress != null)
channel.bind(bindAddress);
channel.socket().setTcpNoDelay(true);
channel.connect(destination.address().toSocketAddress());
FutureCallback<Connection> result = new FutureCallback<>();
selectorManager.connect(channel, result);
return result;
}
protected class ClientSelectorManager extends SelectorManager
{
public ClientSelectorManager()
{
this(1);
}
public ClientSelectorManager(int selectors)
{
super(selectors);
}
@Override
protected Selectable newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey sKey) throws IOException
{
return new SelectChannelEndPoint(channel, selectSet, sKey, getMaxIdleTime());
}
@Override
public AsyncConnection newConnection(SocketChannel channel, AsyncEndPoint endpoint, Object attachment)
{
// TODO: SSL
return new StandardConnection(channel, endpoint, )
}
@Override
protected void execute(Runnable task)
{
getExecutor().execute(task);
}
}
}

View File

@ -0,0 +1,430 @@
//========================================================================
//Copyright 2012-2012 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.client;
import java.io.IOException;
import java.net.ConnectException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.URI;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.Arrays;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import javax.net.ssl.SSLEngine;
import org.eclipse.jetty.client.api.Connection;
import org.eclipse.jetty.client.api.Destination;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.MappedByteBufferPool;
import org.eclipse.jetty.io.SelectChannelEndPoint;
import org.eclipse.jetty.io.SelectorManager;
import org.eclipse.jetty.io.ssl.SslConnection;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.FutureCallback;
import org.eclipse.jetty.util.Jetty;
import org.eclipse.jetty.util.component.AggregateLifeCycle;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
/**
* <p>{@link HttpClient} provides an efficient, asynchronous, non-blocking implementation
* to perform HTTP requests to a server through a simple API that offers also blocking semantic.</p>
* <p>{@link HttpClient} provides easy-to-use methods such as {@link #GET(String)} that allow to perform HTTP
* requests in a one-liner, but also gives the ability to fine tune the configuration of requests via
* {@link HttpClient#newRequest(URI)}.</p>
* <p>{@link HttpClient} acts as a central configuration point for network parameters (such as idle timeouts)
* and HTTP parameters (such as whether to follow redirects).</p>
* <p>{@link HttpClient} transparently pools connections to servers, but allows direct control of connections
* for cases where this is needed.</p>
* <p>Typical usage:</p>
* <pre>
* // One liner:
* new HTTPClient().GET("http://localhost:8080/").get().status();
*
* // Building a request with a timeout
* HTTPClient client = new HTTPClient();
* Response response = client.newRequest("localhost:8080").send().get(5, TimeUnit.SECONDS);
* int status = response.status();
*
* // Asynchronously
* HTTPClient client = new HTTPClient();
* client.newRequest("localhost:8080").send(new Response.Listener.Adapter()
* {
* &#64;Override
* public void onSuccess(Response response)
* {
* ...
* }
* });
* </pre>
*/
public class HttpClient extends AggregateLifeCycle
{
private static final Logger LOG = Log.getLogger(HttpClient.class);
private final ConcurrentMap<String, Destination> destinations = new ConcurrentHashMap<>();
private final ConcurrentMap<Long, HttpConversation> conversations = new ConcurrentHashMap<>();
private volatile Executor executor;
private volatile ByteBufferPool byteBufferPool;
private volatile ScheduledExecutorService scheduler;
private volatile SelectorManager selectorManager;
private volatile SslContextFactory sslContextFactory;
private volatile String agent = "Jetty/" + Jetty.VERSION;
private volatile boolean followRedirects = true;
private volatile int maxConnectionsPerAddress = 8;
private volatile int maxQueueSizePerAddress = 1024;
private volatile int requestBufferSize = 4096;
private volatile int responseBufferSize = 4096;
private volatile SocketAddress bindAddress;
private volatile long idleTimeout;
public ByteBufferPool getByteBufferPool()
{
return byteBufferPool;
}
public SslContextFactory getSslContextFactory()
{
return sslContextFactory;
}
@Override
protected void doStart() throws Exception
{
if (executor == null)
executor = new QueuedThreadPool();
addBean(executor);
if (byteBufferPool == null)
byteBufferPool = new MappedByteBufferPool();
addBean(byteBufferPool);
if (scheduler == null)
scheduler = Executors.newSingleThreadScheduledExecutor();
addBean(scheduler);
selectorManager = newSelectorManager();
addBean(selectorManager);
super.doStart();
}
protected SelectorManager newSelectorManager()
{
return new ClientSelectorManager();
}
@Override
protected void doStop() throws Exception
{
super.doStop();
}
public long getIdleTimeout()
{
return idleTimeout;
}
public void setIdleTimeout(long idleTimeout)
{
this.idleTimeout = idleTimeout;
}
/**
* @return the address to bind socket channels to
* @see #setBindAddress(SocketAddress)
*/
public SocketAddress getBindAddress()
{
return bindAddress;
}
/**
* @param bindAddress the address to bind socket channels to
* @see #getBindAddress()
*/
public void setBindAddress(SocketAddress bindAddress)
{
this.bindAddress = bindAddress;
}
public Future<Response> GET(String uri)
{
return GET(URI.create(uri));
}
public Future<Response> GET(URI uri)
{
// TODO: Add decoder, cookies, agent, default headers, etc.
return newRequest(uri)
.method(HttpMethod.GET)
.version(HttpVersion.HTTP_1_1)
.agent(getUserAgent())
.idleTimeout(getIdleTimeout())
.followRedirects(isFollowRedirects())
.send();
}
public Request newRequest(String host, int port)
{
return newRequest(URI.create(address("http", host, port)));
}
public Request newRequest(URI uri)
{
return new HttpRequest(this, uri);
}
protected Request newRequest(long id, URI uri)
{
return new HttpRequest(this, id, uri);
}
private String address(String scheme, String host, int port)
{
return scheme + "://" + host + ":" + port;
}
public Destination getDestination(String scheme, String host, int port)
{
String address = address(scheme, host, port);
Destination destination = destinations.get(address);
if (destination == null)
{
destination = new HttpDestination(this, scheme, host, port);
Destination existing = destinations.putIfAbsent(address, destination);
if (existing != null)
destination = existing;
}
return destination;
}
public String getUserAgent()
{
return agent;
}
public void setUserAgent(String agent)
{
this.agent = agent;
}
public boolean isFollowRedirects()
{
return followRedirects;
}
public void setFollowRedirects(boolean follow)
{
this.followRedirects = follow;
}
public void send(Request request, Response.Listener listener)
{
String scheme = request.scheme().toLowerCase();
if (!Arrays.asList("http", "https").contains(scheme))
throw new IllegalArgumentException("Invalid protocol " + scheme);
String host = request.host().toLowerCase();
int port = request.port();
if (port < 0)
port = "https".equals(scheme) ? 443 : 80;
getDestination(scheme, host, port).send(request, listener);
}
public Executor getExecutor()
{
return executor;
}
public int getMaxConnectionsPerAddress()
{
return maxConnectionsPerAddress;
}
public void setMaxConnectionsPerAddress(int maxConnectionsPerAddress)
{
this.maxConnectionsPerAddress = maxConnectionsPerAddress;
}
public int getMaxQueueSizePerAddress()
{
return maxQueueSizePerAddress;
}
public void setMaxQueueSizePerAddress(int maxQueueSizePerAddress)
{
this.maxQueueSizePerAddress = maxQueueSizePerAddress;
}
public int getRequestBufferSize()
{
return requestBufferSize;
}
public void setRequestBufferSize(int requestBufferSize)
{
this.requestBufferSize = requestBufferSize;
}
public int getResponseBufferSize()
{
return responseBufferSize;
}
public void setResponseBufferSize(int responseBufferSize)
{
this.responseBufferSize = responseBufferSize;
}
protected void newConnection(Destination destination, Callback<Connection> callback)
{
SocketChannel channel = null;
try
{
channel = SocketChannel.open();
SocketAddress bindAddress = getBindAddress();
if (bindAddress != null)
channel.bind(bindAddress);
channel.socket().setTcpNoDelay(true);
channel.configureBlocking(false);
channel.connect(new InetSocketAddress(destination.host(), destination.port()));
Future<Connection> result = new ConnectionCallback(destination, callback);
selectorManager.connect(channel, result);
}
catch (IOException x)
{
if (channel != null)
close(channel);
callback.failed(null, x);
}
}
private void close(SocketChannel channel)
{
try
{
channel.close();
}
catch (IOException x)
{
LOG.ignore(x);
}
}
public HttpConversation conversationFor(Request request)
{
long id = request.id();
HttpConversation conversation = conversations.get(id);
if (conversation == null)
{
conversation = new HttpConversation();
HttpConversation existing = conversations.putIfAbsent(id, conversation);
if (existing != null)
conversation = existing;
}
return conversation;
}
protected class ClientSelectorManager extends SelectorManager
{
public ClientSelectorManager()
{
this(1);
}
public ClientSelectorManager(int selectors)
{
super(selectors);
}
@Override
protected EndPoint newEndPoint(SocketChannel channel, ManagedSelector selector, SelectionKey key)
{
return new SelectChannelEndPoint(channel, selector, key, scheduler, getIdleTimeout());
}
@Override
public org.eclipse.jetty.io.Connection newConnection(SocketChannel channel, EndPoint endPoint, Object attachment) throws IOException
{
ConnectionCallback callback = (ConnectionCallback)attachment;
Destination destination = callback.destination;
SslContextFactory sslContextFactory = getSslContextFactory();
if ("https".equals(destination.scheme()))
{
if (sslContextFactory == null)
{
IOException failure = new ConnectException("Missing " + SslContextFactory.class.getSimpleName() + " for " + destination.scheme() + " requests");
callback.failed(null, failure);
throw failure;
}
else
{
SSLEngine engine = sslContextFactory.newSSLEngine(endPoint.getRemoteAddress());
engine.setUseClientMode(false);
SslConnection sslConnection = new SslConnection(getByteBufferPool(), getExecutor(), endPoint, engine);
EndPoint appEndPoint = sslConnection.getDecryptedEndPoint();
HttpConnection connection = new HttpConnection(HttpClient.this, appEndPoint);
appEndPoint.setConnection(connection);
callback.callback.completed(connection);
connection.onOpen();
return sslConnection;
}
}
else
{
HttpConnection connection = new HttpConnection(HttpClient.this, endPoint);
callback.callback.completed(connection);
return connection;
}
}
@Override
protected void execute(Runnable task)
{
getExecutor().execute(task);
}
}
private class ConnectionCallback extends FutureCallback<Connection>
{
private final Destination destination;
private final Callback<Connection> callback;
private ConnectionCallback(Destination destination, Callback<Connection> callback)
{
this.destination = destination;
this.callback = callback;
}
}
}

View File

@ -0,0 +1,106 @@
package org.eclipse.jetty.client;
import org.eclipse.jetty.client.api.Connection;
import org.eclipse.jetty.client.api.ContentProvider;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.io.AbstractConnection;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
public class HttpConnection extends AbstractConnection implements Connection
{
private static final Logger LOG = Log.getLogger(HttpConnection.class);
private final HttpClient client;
private volatile HttpConversation conversation;
public HttpConnection(HttpClient client, EndPoint endPoint)
{
super(endPoint, client.getExecutor());
this.client = client;
}
public HttpClient getHttpClient()
{
return client;
}
@Override
public void onOpen()
{
super.onOpen();
fillInterested();
}
@Override
protected boolean onReadTimeout()
{
HttpConversation conversation = this.conversation;
if (conversation != null)
conversation.idleTimeout();
return true;
}
@Override
public void send(Request request, Response.Listener listener)
{
normalizeRequest(request);
HttpConversation conversation = client.conversationFor(request);
this.conversation = conversation;
conversation.prepare(this, request, listener);
conversation.send();
}
private void normalizeRequest(Request request)
{
HttpVersion version = request.version();
HttpFields headers = request.headers();
ContentProvider content = request.content();
// Make sure the path is there
String path = request.path();
if (path.matches("\\s*"))
request.path("/");
// Add content headers
if (content != null)
{
long contentLength = content.length();
if (contentLength >= 0)
{
if (!headers.containsKey(HttpHeader.CONTENT_LENGTH.asString()))
headers.put(HttpHeader.CONTENT_LENGTH, String.valueOf(contentLength));
}
else
{
if (!headers.containsKey(HttpHeader.TRANSFER_ENCODING.asString()))
headers.put(HttpHeader.TRANSFER_ENCODING, "chunked");
}
}
// TODO: decoder headers
// If we are HTTP 1.1, add the Host header
if (version.getVersion() > 10)
{
if (!headers.containsKey(HttpHeader.HOST.asString()))
headers.put(HttpHeader.HOST, request.host() + ":" + request.port());
}
}
@Override
public void onFillable()
{
HttpConversation conversation = this.conversation;
if (conversation != null)
conversation.receive();
else
// TODO test sending white space... we want to consume it but throw if it's not whitespace
LOG.warn("Ready to read response, but no receiver");
}
}

View File

@ -0,0 +1,77 @@
package org.eclipse.jetty.client;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response;
public class HttpConversation
{
private final HttpSender sender;
private final HttpReceiver receiver;
private HttpConnection connection;
private Request request;
private Response.Listener listener;
private HttpResponse response;
public HttpConversation()
{
sender = new HttpSender();
receiver = new HttpReceiver();
}
public void prepare(HttpConnection connection, Request request, Response.Listener listener)
{
if (this.connection != null)
throw new IllegalStateException();
this.connection = connection;
this.request = request;
this.listener = listener;
this.response = new HttpResponse(request, listener);
}
public void done()
{
reset();
}
private void reset()
{
connection = null;
request = null;
listener = null;
}
public HttpConnection connection()
{
return connection;
}
public Request request()
{
return request;
}
public Response.Listener listener()
{
return listener;
}
public HttpResponse response()
{
return response;
}
public void send()
{
sender.send(this);
}
public void idleTimeout()
{
receiver.idleTimeout();
}
public void receive()
{
receiver.receive(this);
}
}

View File

@ -0,0 +1,200 @@
//========================================================================
//Copyright 2012-2012 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.client;
import java.util.Queue;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Future;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.atomic.AtomicInteger;
import org.eclipse.jetty.client.api.Connection;
import org.eclipse.jetty.client.api.Destination;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.FutureCallback;
public class HttpDestination implements Destination
{
private final AtomicInteger connectionCount = new AtomicInteger();
private final HttpClient client;
private final String scheme;
private final String host;
private final int port;
private final Queue<Response> requests;
private final Queue<Connection> idleConnections;
private final Queue<Connection> activeConnections;
public HttpDestination(HttpClient client, String scheme, String host, int port)
{
this.client = client;
this.scheme = scheme;
this.host = host;
this.port = port;
this.requests = new ArrayBlockingQueue<>(client.getMaxQueueSizePerAddress());
this.idleConnections = new ArrayBlockingQueue<>(client.getMaxConnectionsPerAddress());
this.activeConnections = new ArrayBlockingQueue<>(client.getMaxConnectionsPerAddress());
}
@Override
public String scheme()
{
return scheme;
}
@Override
public String host()
{
return host;
}
@Override
public int port()
{
return port;
}
@Override
public void send(Request request, Response.Listener listener)
{
if (!scheme.equals(request.scheme()))
throw new IllegalArgumentException("Invalid request scheme " + request.scheme() + " for destination " + this);
if (!host.equals(request.host()))
throw new IllegalArgumentException("Invalid request host " + request.host() + " for destination " + this);
if (port != request.port())
throw new IllegalArgumentException("Invalid request port " + request.port() + " for destination " + this);
HttpResponse response = new HttpResponse(request, listener);
if (client.isRunning())
{
if (requests.offer(response))
{
if (!client.isRunning() && requests.remove(response))
{
throw new RejectedExecutionException(HttpClient.class.getSimpleName() + " is shutting down");
}
else
{
Request.Listener requestListener = request.listener();
notifyRequestQueued(requestListener, request);
ensureConnection();
}
}
else
{
throw new RejectedExecutionException("Max requests per address " + client.getMaxQueueSizePerAddress() + " exceeded");
}
}
}
private void notifyRequestQueued(Request.Listener listener, Request request)
{
try
{
if (listener != null)
listener.onQueued(request);
}
catch (Exception x)
{
// TODO: log or abort request send ?
}
}
private void ensureConnection()
{
int maxConnections = client.getMaxConnectionsPerAddress();
while (true)
{
int count = connectionCount.get();
if (count >= maxConnections)
break;
if (connectionCount.compareAndSet(count, count + 1))
{
newConnection(new Callback<Connection>()
{
@Override
public void completed(Connection connection)
{
dispatch(connection);
}
@Override
public void failed(Connection connection, Throwable x)
{
// TODO: what here ?
}
});
break;
}
}
}
public Future<Connection> newConnection()
{
FutureCallback<Connection> result = new FutureCallback<>();
newConnection(result);
return result;
}
private void newConnection(Callback<Connection> callback)
{
client.newConnection(this, callback);
}
/**
* Responsibility of this method is to dequeue a request, associate it to the given {@code connection}
* and dispatch a thread to execute the request.
*
* This can be done in several ways: one could be to
* @param connection
*/
protected void dispatch(final Connection connection)
{
final Response response = requests.poll();
if (response == null)
{
idleConnections.offer(connection);
}
else
{
activeConnections.offer(connection);
client.getExecutor().execute(new Runnable()
{
@Override
public void run()
{
connection.send(response.request(), response.listener());
}
});
}
}
// TODO: 1. We must do queuing of requests in any case, because we cannot do blocking connect
// TODO: 2. We must be non-blocking connect, therefore we need to queue
// Connections should compete for the queue of requests in separate threads
// that poses a problem of thread pool size: if < maxConnections we're starving
//
// conn1 is executed, takes on the queue => I need at least one thread per destination
// we need to queue the request, pick an idle connection, then execute { conn.send(request, listener) }
// if I create manually the connection, then I call send(request, listener)
// Other ways ?
}

View File

@ -0,0 +1,208 @@
package org.eclipse.jetty.client;
import java.io.EOFException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.concurrent.TimeoutException;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpParser;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
class HttpReceiver implements HttpParser.ResponseHandler<ByteBuffer>
{
private static final Logger LOG = Log.getLogger(HttpReceiver.class);
private final HttpParser parser = new HttpParser(this);
private HttpConversation conversation;
public void receive(HttpConversation conversation)
{
if (this.conversation != null)
throw new IllegalStateException();
this.conversation = conversation;
HttpConnection connection = conversation.connection();
HttpClient client = connection.getHttpClient();
ByteBufferPool bufferPool = client.getByteBufferPool();
ByteBuffer buffer = bufferPool.acquire(client.getResponseBufferSize(), true);
EndPoint endPoint = connection.getEndPoint();
try
{
while (true)
{
int read = endPoint.fill(buffer);
if (read > 0)
{
parser.parseNext(buffer);
// TODO: response done, reset ?
}
else if (read == 0)
{
connection.fillInterested();
break;
}
else
{
parser.shutdownInput();
break;
}
}
}
catch (IOException x)
{
LOG.debug(x);
bufferPool.release(buffer);
fail(x);
}
}
@Override
public boolean startResponse(HttpVersion version, int status, String reason)
{
// Probe the protocol listeners
// HttpClient client = connection.getHttpClient();
// listener = client.find(status); // TODO
// listener = new RedirectionListener(connection);
// if (listener == null)
// listener = applicationListener;
HttpResponse response = conversation.response();
response.version(version).status(status).reason(reason);
notifyBegin(conversation.listener(), response);
return false;
}
@Override
public boolean parsedHeader(HttpHeader header, String name, String value)
{
conversation.response().headers().put(name, value);
return false;
}
@Override
public boolean headerComplete()
{
notifyHeaders(conversation.listener(), conversation.response());
return false;
}
@Override
public boolean content(ByteBuffer buffer)
{
notifyContent(conversation.listener(), conversation.response(), buffer);
return false;
}
@Override
public boolean messageComplete(long contentLength)
{
success();
return false;
}
protected void success()
{
Response.Listener listener = conversation.listener();
Response response = conversation.response();
conversation.done();
notifySuccess(listener, response);
}
protected void fail(Throwable failure)
{
Response.Listener listener = conversation.listener();
Response response = conversation.response();
conversation.done();
notifyFailure(listener, response, failure);
}
@Override
public boolean earlyEOF()
{
fail(new EOFException());
return false;
}
@Override
public void badMessage(int status, String reason)
{
conversation.response().status(status).reason(reason);
fail(new HttpResponseException());
}
private void notifyBegin(Response.Listener listener, HttpResponse response)
{
try
{
if (listener != null)
listener.onBegin(response);
}
catch (Exception x)
{
LOG.info("Exception while notifying listener " + listener, x);
}
}
private void notifyHeaders(Response.Listener listener, HttpResponse response)
{
try
{
if (listener != null)
listener.onHeaders(response);
}
catch (Exception x)
{
LOG.info("Exception while notifying listener " + listener, x);
}
}
private void notifyContent(Response.Listener listener, HttpResponse response, ByteBuffer buffer)
{
try
{
if (listener != null)
listener.onContent(response, buffer);
}
catch (Exception x)
{
LOG.info("Exception while notifying listener " + listener, x);
}
}
private void notifySuccess(Response.Listener listener, Response response)
{
try
{
if (listener != null)
listener.onSuccess(response);
}
catch (Exception x)
{
LOG.info("Exception while notifying listener " + listener, x);
}
}
private void notifyFailure(Response.Listener listener, Response response, Throwable failure)
{
try
{
if (listener != null)
listener.onFailure(response, failure);
}
catch (Exception x)
{
LOG.info("Exception while notifying listener " + listener, x);
}
}
public void idleTimeout()
{
fail(new TimeoutException());
}
}

View File

@ -0,0 +1,259 @@
//========================================================================
//Copyright 2012-2012 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.client;
import java.net.URI;
import java.util.Map;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicLong;
import org.eclipse.jetty.client.api.ContentDecoder;
import org.eclipse.jetty.client.api.ContentProvider;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.util.FutureCallback;
public class HttpRequest implements Request
{
private static final AtomicLong ids = new AtomicLong();
private final HttpClient client;
private final long id;
private String scheme;
private final String host;
private final int port;
private String path;
private HttpMethod method;
private HttpVersion version;
private String agent;
private long idleTimeout;
private Response response;
private Listener listener;
private ContentProvider content;
private final HttpFields headers = new HttpFields();
public HttpRequest(HttpClient client, URI uri)
{
this(client, ids.incrementAndGet(), uri);
}
protected HttpRequest(HttpClient client, long id, URI uri)
{
this.client = client;
this.id = id;
scheme(uri.getScheme());
host = uri.getHost();
port = uri.getPort();
path(uri.getPath());
// TODO: query params
}
@Override
public long id()
{
return id;
}
@Override
public String scheme()
{
return scheme;
}
@Override
public Request scheme(String scheme)
{
this.scheme = scheme;
return this;
}
@Override
public String host()
{
return host;
}
@Override
public int port()
{
return port;
}
@Override
public HttpMethod method()
{
return method;
}
@Override
public Request method(HttpMethod method)
{
this.method = method;
return this;
}
@Override
public String path()
{
return path;
}
@Override
public Request path(String path)
{
this.path = path;
return this;
}
@Override
public HttpVersion version()
{
return version;
}
@Override
public Request version(HttpVersion version)
{
this.version = version;
return this;
}
@Override
public Request param(String name, String value)
{
return this;
}
@Override
public Map<String, String> params()
{
return null;
}
@Override
public String agent()
{
return agent;
}
@Override
public Request agent(String userAgent)
{
this.agent = userAgent;
return this;
}
@Override
public Request header(String name, String value)
{
headers.add(name, value);
return this;
}
@Override
public HttpFields headers()
{
return headers;
}
@Override
public Listener listener()
{
return listener;
}
@Override
public Request listener(Request.Listener listener)
{
this.listener = listener;
return this;
}
@Override
public ContentProvider content()
{
return content;
}
@Override
public Request content(ContentProvider content)
{
this.content = content;
return this;
}
@Override
public Request decoder(ContentDecoder decoder)
{
return this;
}
@Override
public Request cookie(String key, String value)
{
return this;
}
@Override
public Request followRedirects(boolean follow)
{
return this;
}
@Override
public long idleTimeout()
{
return idleTimeout;
}
@Override
public Request idleTimeout(long timeout)
{
this.idleTimeout = timeout;
return this;
}
@Override
public Future<Response> send()
{
final FutureCallback<Response> result = new FutureCallback<>();
BufferingResponseListener listener = new BufferingResponseListener()
{
@Override
public void onSuccess(Response response)
{
super.onSuccess(response);
result.completed(response);
}
@Override
public void onFailure(Response response, Throwable failure)
{
super.onFailure(response, failure);
result.failed(response, failure);
}
};
send(listener);
return result;
}
@Override
public void send(final Response.Listener listener)
{
client.send(this, listener);
}
}

View File

@ -13,58 +13,82 @@
package org.eclipse.jetty.client;
import java.io.InputStream;
import org.eclipse.jetty.client.api.ContentProvider;
import org.eclipse.jetty.client.api.Headers;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.util.FutureCallback;
public class StandardResponse extends FutureCallback<Response> implements Response
public class HttpResponse extends FutureCallback<Response> implements Response
{
private final HttpFields headers = new HttpFields();
private final Request request;
private final Listener listener;
private HttpVersion version;
private int status;
private String reason;
public StandardResponse(Request request, Response.Listener listener)
public HttpResponse(Request request, Response.Listener listener)
{
this.request = request;
this.listener = listener;
}
@Override
public int getStatus()
public HttpVersion version()
{
return 0;
return version;
}
public HttpResponse version(HttpVersion version)
{
this.version = version;
return this;
}
@Override
public Headers getHeaders()
public int status()
{
return null;
return status;
}
public HttpResponse status(int status)
{
this.status = status;
return this;
}
public String reason()
{
return reason;
}
public HttpResponse reason(String reason)
{
this.reason = reason;
return this;
}
@Override
public Request getRequest()
public HttpFields headers()
{
return headers;
}
@Override
public Request request()
{
return request;
}
@Override
public ContentProvider content()
public Listener listener()
{
return null;
}
@Override
public InputStream contentAsStream()
{
return null;
return listener;
}
@Override
public void abort()
{
request.abort();
// request.abort();
}
}

View File

@ -0,0 +1,28 @@
package org.eclipse.jetty.client;
public class HttpResponseException extends RuntimeException
{
public HttpResponseException()
{
}
public HttpResponseException(String message)
{
super(message);
}
public HttpResponseException(String message, Throwable cause)
{
super(message, cause);
}
public HttpResponseException(Throwable cause)
{
super(cause);
}
public HttpResponseException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace)
{
super(message, cause, enableSuppression, writableStackTrace);
}
}

View File

@ -0,0 +1,319 @@
package org.eclipse.jetty.client;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Collections;
import java.util.Iterator;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jetty.client.api.ContentProvider;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.http.HttpGenerator;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
class HttpSender
{
private static final Logger LOG = Log.getLogger(HttpSender.class);
private final HttpGenerator generator = new HttpGenerator();
private HttpConversation conversation;
private long contentLength;
private Iterator<ByteBuffer> contentChunks;
private ByteBuffer header;
private ByteBuffer chunk;
private boolean requestHeadersComplete;
public void send(HttpConversation conversation)
{
this.conversation = conversation;
ContentProvider content = conversation.request().content();
this.contentLength = content == null ? -1 : content.length();
this.contentChunks = content == null ? Collections.<ByteBuffer>emptyIterator() : content.iterator();
send();
}
private void send()
{
try
{
HttpConnection connection = conversation.connection();
EndPoint endPoint = connection.getEndPoint();
HttpClient client = connection.getHttpClient();
ByteBufferPool byteBufferPool = client.getByteBufferPool();
HttpGenerator.RequestInfo info = null;
ByteBuffer content = contentChunks.hasNext() ? contentChunks.next() : BufferUtil.EMPTY_BUFFER;
boolean lastContent = !contentChunks.hasNext();
while (true)
{
HttpGenerator.Result result = generator.generateRequest(info, header, chunk, content, lastContent);
switch (result)
{
case NEED_INFO:
{
Request request = conversation.request();
info = new HttpGenerator.RequestInfo(request.version(), request.headers(), contentLength, request.method().asString(), request.path());
break;
}
case NEED_HEADER:
{
header = byteBufferPool.acquire(client.getRequestBufferSize(), false);
break;
}
case NEED_CHUNK:
{
chunk = byteBufferPool.acquire(HttpGenerator.CHUNK_SIZE, false);
break;
}
case FLUSH:
{
StatefulExecutorCallback callback = new StatefulExecutorCallback(client.getExecutor())
{
@Override
protected void pendingCompleted()
{
notifyRequestHeadersComplete();
send();
}
@Override
protected void failed(Throwable x)
{
fail(x);
}
};
if (header == null)
header = BufferUtil.EMPTY_BUFFER;
if (chunk == null)
chunk = BufferUtil.EMPTY_BUFFER;
if (content == null)
content = BufferUtil.EMPTY_BUFFER;
endPoint.write(null, callback, header, chunk, content);
if (callback.pending())
return;
if (callback.completed())
{
if (!requestHeadersComplete)
{
requestHeadersComplete = true;
notifyRequestHeadersComplete();
}
releaseBuffers();
content = contentChunks.hasNext() ? contentChunks.next() : BufferUtil.EMPTY_BUFFER;
lastContent = !contentChunks.hasNext();
}
break;
}
case SHUTDOWN_OUT:
{
endPoint.shutdownOutput();
break;
}
case CONTINUE:
{
break;
}
case DONE:
{
if (generator.isEnd())
success();
return;
}
default:
{
throw new IllegalStateException("Unknown result " + result);
}
}
}
}
catch (IOException x)
{
LOG.debug(x);
fail(x);
}
finally
{
releaseBuffers();
}
}
protected void success()
{
notifyRequestSuccess();
}
protected void fail(Throwable x)
{
BufferUtil.clear(header);
BufferUtil.clear(chunk);
releaseBuffers();
notifyRequestFailure(x);
notifyResponseFailure(x);
conversation.connection().getEndPoint().shutdownOutput();
generator.abort();
}
private void releaseBuffers()
{
ByteBufferPool byteBufferPool = conversation.connection().getHttpClient().getByteBufferPool();
if (!BufferUtil.hasContent(header))
{
byteBufferPool.release(header);
header = null;
}
if (!BufferUtil.hasContent(chunk))
{
byteBufferPool.release(chunk);
chunk = null;
}
}
private void notifyRequestHeadersComplete()
{
Request request = conversation.request();
Request.Listener listener = request.listener();
try
{
if (listener != null)
listener.onHeaders(request);
}
catch (Exception x)
{
LOG.info("Exception while notifying listener " + listener, x);
}
}
private void notifyRequestSuccess()
{
Request request = conversation.request();
Request.Listener listener = request.listener();
try
{
if (listener != null)
listener.onSuccess(request);
}
catch (Exception x)
{
LOG.info("Exception while notifying listener " + listener, x);
}
}
private void notifyRequestFailure(Throwable x)
{
Request request = conversation.request();
Request.Listener listener = request.listener();
try
{
if (listener != null)
listener.onFailure(request, x);
}
catch (Exception xx)
{
LOG.info("Exception while notifying listener " + listener, xx);
}
}
private void notifyResponseFailure(Throwable x)
{
Response.Listener listener = conversation.listener();
try
{
if (listener != null)
listener.onFailure(null, x);
}
catch (Exception xx)
{
LOG.info("Exception while notifying listener " + listener, xx);
}
}
private static abstract class StatefulExecutorCallback implements Callback<Void>, Runnable
{
private final AtomicReference<State> state = new AtomicReference<>(State.INCOMPLETE);
private final Executor executor;
private StatefulExecutorCallback(Executor executor)
{
this.executor = executor;
}
@Override
public final void completed(final Void context)
{
State previous = state.get();
while (true)
{
if (state.compareAndSet(previous, State.COMPLETE))
break;
previous = state.get();
}
if (previous == State.PENDING)
executor.execute(this);
}
@Override
public final void run()
{
pendingCompleted();
}
protected abstract void pendingCompleted();
@Override
public final void failed(Void context, final Throwable x)
{
State previous = state.get();
while (true)
{
if (state.compareAndSet(previous, State.FAILED))
break;
previous = state.get();
}
if (previous == State.PENDING)
{
executor.execute(new Runnable()
{
@Override
public void run()
{
failed(x);
}
});
}
else
{
failed(x);
}
}
protected abstract void failed(Throwable x);
public boolean pending()
{
return state.compareAndSet(State.INCOMPLETE, State.PENDING);
}
public boolean completed()
{
return state.get() == State.COMPLETE;
}
public boolean failed()
{
return state.get() == State.FAILED;
}
private enum State
{
INCOMPLETE, PENDING, COMPLETE, FAILED
}
}
}

View File

@ -0,0 +1,91 @@
package org.eclipse.jetty.client;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.AccessDeniedException;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Iterator;
import java.util.NoSuchElementException;
import org.eclipse.jetty.client.api.ContentProvider;
public class PathContentProvider implements ContentProvider
{
private final Path filePath;
private final long fileSize;
private final int bufferSize;
public PathContentProvider(Path filePath) throws IOException
{
this(filePath, 4096);
}
public PathContentProvider(Path filePath, int bufferSize) throws IOException
{
if (!Files.isRegularFile(filePath))
throw new NoSuchFileException(filePath.toString());
if (!Files.isReadable(filePath))
throw new AccessDeniedException(filePath.toString());
this.filePath = filePath;
this.fileSize = Files.size(filePath);
this.bufferSize = bufferSize;
}
@Override
public long length()
{
return fileSize;
}
@Override
public Iterator<ByteBuffer> iterator()
{
return new LazyIterator();
}
private class LazyIterator implements Iterator<ByteBuffer>
{
private final ByteBuffer buffer = ByteBuffer.allocateDirect(bufferSize);
private SeekableByteChannel channel;
private long position;
@Override
public boolean hasNext()
{
return position < length();
}
@Override
public ByteBuffer next()
{
try
{
if (channel == null)
channel = Files.newByteChannel(filePath, StandardOpenOption.READ);
buffer.clear();
int read = channel.read(buffer);
if (read < 0)
throw new NoSuchElementException();
position += read;
buffer.flip();
return buffer;
}
catch (IOException x)
{
throw (NoSuchElementException)new NoSuchElementException().initCause(x);
}
}
@Override
public void remove()
{
throw new UnsupportedOperationException();
}
}
}

View File

@ -0,0 +1,42 @@
package org.eclipse.jetty.client;
import java.net.URI;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response;
public class RedirectionListener extends Response.Listener.Adapter
{
private final HttpConnection connection;
public RedirectionListener(HttpConnection connection)
{
this.connection = connection;
}
@Override
public void onSuccess(Response response)
{
switch (response.status())
{
case 301: // GET or HEAD only allowed, keep the method
{
break;
}
case 302:
case 303: // use GET for next request
{
String location = response.headers().get("location");
HttpClient httpClient = connection.getHttpClient();
Request redirect = httpClient.newRequest(response.request().id(), URI.create(location));
redirect.send(this);
}
}
}
@Override
public void onFailure(Response response, Throwable failure)
{
// TODO
}
}

View File

@ -1,98 +0,0 @@
//========================================================================
//Copyright 2012-2012 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.client;
import java.util.Queue;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Future;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.client.api.Address;
import org.eclipse.jetty.client.api.Connection;
import org.eclipse.jetty.client.api.Destination;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response;
public class StandardDestination implements Destination
{
private final HTTPClient client;
private final Address address;
private final Queue<Response> requests;
private final Queue<Connection> connections;
public StandardDestination(HTTPClient client, Address address)
{
this.client = client;
this.address = address;
this.requests = new ArrayBlockingQueue<>(client.getMaxQueueSizePerAddress());
this.connections = new ArrayBlockingQueue<>(client.getMaxConnectionsPerAddress());
}
@Override
public Address address()
{
return address;
}
@Override
public Connection connect(long timeout, TimeUnit unit)
{
return null;
}
@Override
public Future<Response> send(Request request, Response.Listener listener)
{
if (!address.equals(request.address()))
throw new IllegalArgumentException("Invalid request address " + request.address() + " for destination " + this);
StandardResponse response = new StandardResponse(request, listener);
Connection connection = connections.poll();
if (connection == null)
newConnection();
if (!requests.offer(response))
throw new RejectedExecutionException("Max requests per address " + client.getMaxQueueSizePerAddress() + " exceeded");
return response;
}
protected Future<Connection> newConnection()
{
return client.newConnection(this);
}
protected Connection getConnection()
{
Connection connection = connections.poll();
if (connection == null)
{
client.
}
return connection;
}
// TODO: 1. We must do queuing of requests in any case, because we cannot do blocking connect
// TODO: 2. We must be non-blocking connect, therefore we need to queue
// Connections should compete for the queue of requests in separate threads
// that poses a problem of thread pool size: if < maxConnections we're starving
//
/**
* I need a Future<Connection> connect(), and a void connect(Callback<Connection>)
*/
}

View File

@ -1,126 +0,0 @@
//========================================================================
//Copyright 2012-2012 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.client;
import java.io.File;
import java.net.URI;
import java.util.concurrent.Future;
import org.eclipse.jetty.client.api.Address;
import org.eclipse.jetty.client.api.Authentication;
import org.eclipse.jetty.client.api.ContentDecoder;
import org.eclipse.jetty.client.api.ContentProvider;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response;
public class StandardRequest implements Request, Request.Builder
{
private final HTTPClient client;
private final URI uri;
private String method;
private String agent;
private Response response;
public StandardRequest(HTTPClient client, URI uri)
{
this.client = client;
this.uri = uri;
}
@Override
public Request.Builder method(String method)
{
this.method = method;
return this;
}
@Override
public Request.Builder agent(String userAgent)
{
this.agent = userAgent;
return this;
}
@Override
public Request.Builder header(String name, String value)
{
return this;
}
@Override
public Request.Builder listener(Request.Listener listener)
{
return this;
}
@Override
public Request.Builder file(File file)
{
return this;
}
@Override
public Request.Builder content(ContentProvider buffer)
{
return this;
}
@Override
public Request.Builder decoder(ContentDecoder decoder)
{
return this;
}
@Override
public Request.Builder param(String name, String value)
{
return this;
}
@Override
public Request.Builder cookie(String key, String value)
{
return this;
}
@Override
public Request.Builder authentication(Authentication authentication)
{
return this;
}
@Override
public Request.Builder followRedirects(boolean follow)
{
return this;
}
@Override
public Request build()
{
return this;
}
@Override
public Future<Response> send()
{
return send(null);
}
@Override
public Future<Response> send(final Response.Listener listener)
{
return client.send(this, listener);
}
}

View File

@ -19,7 +19,7 @@ import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.client.api.Response;
public class StreamResponseListener extends Response.Listener.Adapter
public class StreamingResponseListener extends Response.Listener.Adapter
{
public Response get(long timeout, TimeUnit seconds)
{

View File

@ -13,9 +13,7 @@
package org.eclipse.jetty.client.api;
import java.util.concurrent.Future;
public interface Connection extends AutoCloseable
{
Future<Response> send(Request request, Response.Listener listener);
void send(Request request, Response.Listener listener);
}

View File

@ -13,6 +13,9 @@
package org.eclipse.jetty.client.api;
public interface ContentProvider
import java.nio.ByteBuffer;
public interface ContentProvider extends Iterable<ByteBuffer>
{
long length();
}

View File

@ -14,13 +14,16 @@
package org.eclipse.jetty.client.api;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
public interface Destination
{
Connection connect(long timeout, TimeUnit unit);
String scheme();
Future<Response> send(Request request, Response.Listener listener);
String host();
Address address();
int port();
Future<Connection> newConnection();
void send(Request request, Response.Listener listener);
}

View File

@ -1,30 +0,0 @@
//========================================================================
//Copyright 2012-2012 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.client.api;
public class Headers
{
public Header get(String name)
{
return null;
}
public static class Header
{
public int valueAsInt()
{
return 0;
}
}
}

View File

@ -13,53 +13,74 @@
package org.eclipse.jetty.client.api;
import java.io.File;
import java.net.URI;
import java.util.Map;
import java.util.concurrent.Future;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpVersion;
public interface Request
{
long id();
String scheme();
Request scheme(String scheme);
String host();
int port();
HttpMethod method();
Request method(HttpMethod method);
String path();
Request path(String path);
HttpVersion version();
Request version(HttpVersion version);
Map<String, String> params();
Request param(String name, String value);
HttpFields headers();
Request header(String name, String value);
ContentProvider content();
Request content(ContentProvider buffer);
Request decoder(ContentDecoder decoder);
Request cookie(String key, String value);
String agent();
Request agent(String userAgent);
long idleTimeout();
Request idleTimeout(long timeout);
Request followRedirects(boolean follow);
Listener listener();
Request listener(Listener listener);
Future<Response> send();
Future<Response> send(Response.Listener listener);
URI uri();
void abort();
/**
* <p>A builder for requests</p>.
*/
public interface Builder
{
Builder method(String method);
Builder header(String name, String value);
Builder listener(Request.Listener listener);
Builder file(File file);
Builder content(ContentProvider buffer);
Builder decoder(ContentDecoder decoder);
Builder param(String name, String value);
Builder cookie(String key, String value);
Builder authentication(Authentication authentication);
Builder agent(String userAgent);
Builder followRedirects(boolean follow);
Request build();
}
void send(Response.Listener listener);
public interface Listener
{
public void onQueue(Request request);
public void onQueued(Request request);
public void onBegin(Request request);
@ -67,16 +88,14 @@ public interface Request
public void onFlush(Request request, int bytes);
public void onComplete(Request request);
public void onSuccess(Request request);
public void onException(Request request, Exception exception);
public void onEnd(Request request);
public void onFailure(Request request, Throwable failure);
public static class Adapter implements Listener
{
@Override
public void onQueue(Request request)
public void onQueued(Request request)
{
}
@ -96,17 +115,12 @@ public interface Request
}
@Override
public void onComplete(Request request)
public void onSuccess(Request request)
{
}
@Override
public void onException(Request request, Exception exception)
{
}
@Override
public void onEnd(Request request)
public void onFailure(Request request, Throwable failure)
{
}
}

View File

@ -13,85 +13,63 @@
package org.eclipse.jetty.client.api;
import java.io.InputStream;
import java.nio.ByteBuffer;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpVersion;
public interface Response
{
int getStatus();
Request request();
Headers getHeaders();
Listener listener();
Request getRequest();
HttpVersion version();
ContentProvider content();
int status();
InputStream contentAsStream();
String reason();
HttpFields headers();
void abort();
public interface Listener
{
public boolean onBegin(Response response, String version, int code, String message);
public void onBegin(Response response);
public boolean onHeader(Response response, String name, String value);
public void onHeaders(Response response);
public boolean onHeaders(Response response);
public void onContent(Response response, ByteBuffer content);
public boolean onContent(Response response, ByteBuffer content);
public void onSuccess(Response response);
public boolean onTrailer(Response response, String name, String value);
public void onComplete(Response response);
public void onException(Response response, Exception exception);
public void onEnd(Response response);
public void onFailure(Response response, Throwable failure);
public static class Adapter implements Listener
{
@Override
public boolean onBegin(Response response, String version, int code, String message)
{
return false;
}
@Override
public boolean onHeader(Response response, String name, String value)
{
return false;
}
@Override
public boolean onHeaders(Response response)
{
return false;
}
@Override
public boolean onContent(Response response, ByteBuffer content)
{
return false;
}
@Override
public boolean onTrailer(Response response, String name, String value)
{
return false;
}
@Override
public void onComplete(Response response)
public void onBegin(Response response)
{
}
@Override
public void onException(Response response, Exception exception)
public void onHeaders(Response response)
{
}
@Override
public void onEnd(Response response)
public void onContent(Response response, ByteBuffer content)
{
}
@Override
public void onSuccess(Response response)
{
}
@Override
public void onFailure(Response response, Throwable failure)
{
}
}

View File

@ -0,0 +1,35 @@
package org.eclipse.jetty.client;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.NetworkConnector;
import org.eclipse.jetty.server.SelectChannelConnector;
import org.eclipse.jetty.server.Server;
import org.junit.After;
public class AbstractHttpClientTest
{
protected Server server;
protected HttpClient client;
protected NetworkConnector connector;
public void start(Handler handler) throws Exception
{
server = new Server();
connector = new SelectChannelConnector(server);
server.addConnector(connector);
server.setHandler(handler);
server.start();
client = new HttpClient();
client.start();
}
@After
public void destroy() throws Exception
{
if (client != null)
client.stop();
if (server != null)
server.stop();
}
}

View File

@ -21,43 +21,12 @@ import javax.servlet.http.HttpServletResponse;
import junit.framework.Assert;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.SelectChannelConnector;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.junit.After;
import org.junit.Test;
public class HTTPClientTest
public class HttpClientTest extends AbstractHttpClientTest
{
private Server server;
private HTTPClient client;
private Connector.NetConnector connector;
public void start(Handler handler) throws Exception
{
server = new Server();
connector = new SelectChannelConnector();
server.addConnector(connector);
server.setHandler(handler);
server.start();
client = new HTTPClient();
client.start();
}
@After
public void destroy() throws Exception
{
client.stop();
client.join(5, TimeUnit.SECONDS);
server.stop();
server.join();
}
@Test
public void testGETNoResponseContent() throws Exception
{
@ -73,6 +42,6 @@ public class HTTPClientTest
Response response = client.GET("http://localhost:" + connector.getLocalPort()).get(5, TimeUnit.SECONDS);
Assert.assertNotNull(response);
Assert.assertEquals(200, response.getStatus());
Assert.assertEquals(200, response.status());
}
}

View File

@ -0,0 +1,160 @@
package org.eclipse.jetty.client;
public class HttpReceiverTest
{
// private HttpClient client;
//
// @Before
// public void init() throws Exception
// {
// client = new HttpClient();
// client.start();
// }
//
// @After
// public void destroy() throws Exception
// {
// client.stop();
// }
//
// @Test
// public void test_Receive_NoResponseContent() throws Exception
// {
// ByteArrayEndPoint endPoint = new ByteArrayEndPoint();
// HttpConnection connection = new HttpConnection(client, endPoint);
// endPoint.setInput("" +
// "HTTP/1.1 200 OK\r\n" +
// "Content-length: 0\r\n" +
// "\r\n");
// final AtomicReference<Response> responseRef = new AtomicReference<>();
// final CountDownLatch latch = new CountDownLatch(1);
// HttpReceiver receiver = new HttpReceiver(connection, null, new Response.Listener.Adapter()
// {
// @Override
// public void onSuccess(Response response)
// {
// responseRef.set(response);
// latch.countDown();
// }
// });
// receiver.receive(connection);
//
// Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
// Response response = responseRef.get();
// Assert.assertNotNull(response);
// Assert.assertEquals(200, response.status());
// Assert.assertEquals("OK", response.reason());
// Assert.assertSame(HttpVersion.HTTP_1_1, response.version());
// HttpFields headers = response.headers();
// Assert.assertNotNull(headers);
// Assert.assertEquals(1, headers.size());
// Assert.assertEquals("0", headers.get(HttpHeader.CONTENT_LENGTH));
// }
//
// @Test
// public void test_Receive_ResponseContent() throws Exception
// {
// ByteArrayEndPoint endPoint = new ByteArrayEndPoint();
// HttpConnection connection = new HttpConnection(client, endPoint);
// String content = "0123456789ABCDEF";
// endPoint.setInput("" +
// "HTTP/1.1 200 OK\r\n" +
// "Content-length: " + content.length() + "\r\n" +
// "\r\n" +
// content);
// BufferingResponseListener listener = new BufferingResponseListener();
// HttpReceiver receiver = new HttpReceiver(connection, null, listener);
// receiver.receive(connection);
//
// Response response = listener.await(5, TimeUnit.SECONDS);
// Assert.assertNotNull(response);
// Assert.assertEquals(200, response.status());
// Assert.assertEquals("OK", response.reason());
// Assert.assertSame(HttpVersion.HTTP_1_1, response.version());
// HttpFields headers = response.headers();
// Assert.assertNotNull(headers);
// Assert.assertEquals(1, headers.size());
// Assert.assertEquals(String.valueOf(content.length()), headers.get(HttpHeader.CONTENT_LENGTH));
// String received = listener.contentAsString("UTF-8");
// Assert.assertEquals(content, received);
// }
//
// @Test
// public void test_Receive_ResponseContent_EarlyEOF() throws Exception
// {
// ByteArrayEndPoint endPoint = new ByteArrayEndPoint();
// HttpConnection connection = new HttpConnection(client, endPoint);
// String content1 = "0123456789";
// String content2 = "ABCDEF";
// endPoint.setInput("" +
// "HTTP/1.1 200 OK\r\n" +
// "Content-length: " + (content1.length() + content2.length()) + "\r\n" +
// "\r\n" +
// content1);
// BufferingResponseListener listener = new BufferingResponseListener();
// HttpReceiver receiver = new HttpReceiver(connection, null, listener);
// receiver.receive(connection);
// endPoint.setInputEOF();
// receiver.receive(connection);
//
// try
// {
// listener.await(5, TimeUnit.SECONDS);
// Assert.fail();
// }
// catch (ExecutionException e)
// {
// Assert.assertTrue(e.getCause() instanceof EOFException);
// }
// }
//
// @Test
// public void test_Receive_ResponseContent_IdleTimeout() throws Exception
// {
// ByteArrayEndPoint endPoint = new ByteArrayEndPoint();
// HttpConnection connection = new HttpConnection(client, endPoint);
// endPoint.setInput("" +
// "HTTP/1.1 200 OK\r\n" +
// "Content-length: 1\r\n" +
// "\r\n");
// BufferingResponseListener listener = new BufferingResponseListener();
// HttpReceiver receiver = new HttpReceiver(connection, null, listener);
// receiver.receive(connection);
// // Simulate an idle timeout
// receiver.idleTimeout();
//
// try
// {
// listener.await(5, TimeUnit.SECONDS);
// Assert.fail();
// }
// catch (ExecutionException e)
// {
// Assert.assertTrue(e.getCause() instanceof TimeoutException);
// }
// }
//
// @Test
// public void test_Receive_BadResponse() throws Exception
// {
// ByteArrayEndPoint endPoint = new ByteArrayEndPoint();
// HttpConnection connection = new HttpConnection(client, endPoint);
// endPoint.setInput("" +
// "HTTP/1.1 200 OK\r\n" +
// "Content-length: A\r\n" +
// "\r\n");
// BufferingResponseListener listener = new BufferingResponseListener();
// HttpReceiver receiver = new HttpReceiver(connection, null, listener);
// receiver.receive(connection);
//
// try
// {
// listener.await(5, TimeUnit.SECONDS);
// Assert.fail();
// }
// catch (ExecutionException e)
// {
// Assert.assertTrue(e.getCause() instanceof HttpResponseException);
// }
// }
}

View File

@ -0,0 +1,266 @@
package org.eclipse.jetty.client;
import java.net.URI;
import java.nio.ByteBuffer;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.io.ByteArrayEndPoint;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
public class HttpSenderTest
{
private HttpClient client;
@Before
public void init() throws Exception
{
client = new HttpClient();
client.start();
}
@After
public void destroy() throws Exception
{
client.stop();
}
@Test
public void test_Send_NoRequestContent() throws Exception
{
ByteArrayEndPoint endPoint = new ByteArrayEndPoint();
HttpConnection connection = new HttpConnection(client, endPoint);
Request httpRequest = new HttpRequest(client, URI.create("http://localhost/"));
final CountDownLatch headersLatch = new CountDownLatch(1);
final CountDownLatch successLatch = new CountDownLatch(1);
httpRequest.listener(new Request.Listener.Adapter()
{
@Override
public void onHeaders(Request request)
{
headersLatch.countDown();
}
@Override
public void onSuccess(Request request)
{
successLatch.countDown();
}
});
connection.send(httpRequest, null);
String requestString = endPoint.takeOutputString();
Assert.assertTrue(requestString.startsWith("GET "));
Assert.assertTrue(requestString.endsWith("\r\n\r\n"));
Assert.assertTrue(headersLatch.await(5, TimeUnit.SECONDS));
Assert.assertTrue(successLatch.await(5, TimeUnit.SECONDS));
}
@Test
public void test_Send_NoRequestContent_IncompleteFlush() throws Exception
{
ByteArrayEndPoint endPoint = new ByteArrayEndPoint("", 16);
HttpConnection connection = new HttpConnection(client, endPoint);
Request httpRequest = new HttpRequest(client, URI.create("http://localhost/"));
final CountDownLatch headersLatch = new CountDownLatch(1);
httpRequest.listener(new Request.Listener.Adapter()
{
@Override
public void onHeaders(Request request)
{
headersLatch.countDown();
}
});
connection.send(httpRequest, null);
// This take will free space in the buffer and allow for the write to complete
StringBuilder request = new StringBuilder(endPoint.takeOutputString());
// Wait for the write to complete
Assert.assertTrue(headersLatch.await(5, TimeUnit.SECONDS));
request.append(endPoint.takeOutputString());
String requestString = request.toString();
Assert.assertTrue(requestString.startsWith("GET "));
Assert.assertTrue(requestString.endsWith("\r\n\r\n"));
}
@Test
public void test_Send_NoRequestContent_Exception() throws Exception
{
ByteArrayEndPoint endPoint = new ByteArrayEndPoint();
// Shutdown output to trigger the exception on write
endPoint.shutdownOutput();
HttpConnection connection = new HttpConnection(client, endPoint);
Request httpRequest = new HttpRequest(client, URI.create("http://localhost/"));
final CountDownLatch failureLatch = new CountDownLatch(2);
httpRequest.listener(new Request.Listener.Adapter()
{
@Override
public void onFailure(Request request, Throwable x)
{
failureLatch.countDown();
}
});
connection.send(httpRequest, new Response.Listener.Adapter()
{
@Override
public void onFailure(Response response, Throwable failure)
{
failureLatch.countDown();
}
});
Assert.assertTrue(failureLatch.await(5, TimeUnit.SECONDS));
}
@Test
public void test_Send_NoRequestContent_IncompleteFlush_Exception() throws Exception
{
ByteArrayEndPoint endPoint = new ByteArrayEndPoint("", 16);
HttpConnection connection = new HttpConnection(client, endPoint);
Request httpRequest = new HttpRequest(client, URI.create("http://localhost/"));
final CountDownLatch failureLatch = new CountDownLatch(2);
httpRequest.listener(new Request.Listener.Adapter()
{
@Override
public void onFailure(Request request, Throwable x)
{
failureLatch.countDown();
}
});
connection.send(httpRequest, new Response.Listener.Adapter()
{
@Override
public void onFailure(Response response, Throwable failure)
{
failureLatch.countDown();
}
});
// Shutdown output to trigger the exception on write
endPoint.shutdownOutput();
// This take will free space in the buffer and allow for the write to complete
// although it will fail because we shut down the output
endPoint.takeOutputString();
Assert.assertTrue(failureLatch.await(5, TimeUnit.SECONDS));
}
@Test
public void test_Send_SmallRequestContent_InOneBuffer() throws Exception
{
ByteArrayEndPoint endPoint = new ByteArrayEndPoint();
HttpConnection connection = new HttpConnection(client, endPoint);
Request httpRequest = new HttpRequest(client, URI.create("http://localhost/"));
String content = "abcdef";
httpRequest.content(new ByteBufferContentProvider(ByteBuffer.wrap(content.getBytes("UTF-8"))));
final CountDownLatch headersLatch = new CountDownLatch(1);
final CountDownLatch successLatch = new CountDownLatch(1);
httpRequest.listener(new Request.Listener.Adapter()
{
@Override
public void onHeaders(Request request)
{
headersLatch.countDown();
}
@Override
public void onSuccess(Request request)
{
successLatch.countDown();
}
});
connection.send(httpRequest, null);
String requestString = endPoint.takeOutputString();
Assert.assertTrue(requestString.startsWith("GET "));
Assert.assertTrue(requestString.endsWith("\r\n\r\n" + content));
Assert.assertTrue(headersLatch.await(5, TimeUnit.SECONDS));
Assert.assertTrue(successLatch.await(5, TimeUnit.SECONDS));
}
@Test
public void test_Send_SmallRequestContent_InTwoBuffers() throws Exception
{
ByteArrayEndPoint endPoint = new ByteArrayEndPoint();
HttpConnection connection = new HttpConnection(client, endPoint);
Request httpRequest = new HttpRequest(client, URI.create("http://localhost/"));
String content1 = "0123456789";
String content2 = "abcdef";
httpRequest.content(new ByteBufferContentProvider(ByteBuffer.wrap(content1.getBytes("UTF-8")), ByteBuffer.wrap(content2.getBytes("UTF-8"))));
final CountDownLatch headersLatch = new CountDownLatch(1);
final CountDownLatch successLatch = new CountDownLatch(1);
httpRequest.listener(new Request.Listener.Adapter()
{
@Override
public void onHeaders(Request request)
{
headersLatch.countDown();
}
@Override
public void onSuccess(Request request)
{
successLatch.countDown();
}
});
connection.send(httpRequest, null);
String requestString = endPoint.takeOutputString();
Assert.assertTrue(requestString.startsWith("GET "));
Assert.assertTrue(requestString.endsWith("\r\n\r\n" + content1 + content2));
Assert.assertTrue(headersLatch.await(5, TimeUnit.SECONDS));
Assert.assertTrue(successLatch.await(5, TimeUnit.SECONDS));
}
@Test
public void test_Send_SmallRequestContent_Chunked_InTwoChunks() throws Exception
{
ByteArrayEndPoint endPoint = new ByteArrayEndPoint();
HttpConnection connection = new HttpConnection(client, endPoint);
Request httpRequest = new HttpRequest(client, URI.create("http://localhost/"));
String content1 = "0123456789";
String content2 = "ABCDEF";
httpRequest.content(new ByteBufferContentProvider(ByteBuffer.wrap(content1.getBytes("UTF-8")), ByteBuffer.wrap(content2.getBytes("UTF-8")))
{
@Override
public long length()
{
return -1;
}
});
final CountDownLatch headersLatch = new CountDownLatch(1);
final CountDownLatch successLatch = new CountDownLatch(1);
httpRequest.listener(new Request.Listener.Adapter()
{
@Override
public void onHeaders(Request request)
{
headersLatch.countDown();
}
@Override
public void onSuccess(Request request)
{
successLatch.countDown();
}
});
connection.send(httpRequest, null);
String requestString = endPoint.takeOutputString();
Assert.assertTrue(requestString.startsWith("GET "));
String content = Integer.toHexString(content1.length()).toUpperCase() + "\r\n" + content1 + "\r\n";
content += Integer.toHexString(content2.length()).toUpperCase() + "\r\n" + content2 + "\r\n";
content += "0\r\n\r\n";
Assert.assertTrue(requestString.endsWith("\r\n\r\n" + content));
Assert.assertTrue(headersLatch.await(5, TimeUnit.SECONDS));
Assert.assertTrue(successLatch.await(5, TimeUnit.SECONDS));
}
}

View File

@ -0,0 +1,58 @@
package org.eclipse.jetty.client;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
public class RedirectionTest extends AbstractHttpClientTest
{
@Before
public void init() throws Exception
{
start(new RedirectHandler());
}
@Test
public void test303() throws Exception
{
Response response = client.newRequest("localhost", connector.getLocalPort())
.path("/303/done")
.send().get(5, TimeUnit.SECONDS);
Assert.assertNotNull(response);
Assert.assertEquals(200, response.status());
Assert.assertFalse(response.headers().containsKey(HttpHeader.LOCATION.asString()));
}
private class RedirectHandler extends AbstractHandler
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
try
{
String[] paths = target.split("/", 3);
int status = Integer.parseInt(paths[1]);
response.setStatus(status);
response.setHeader("Location", request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + "/" + paths[2]);
}
catch (NumberFormatException x)
{
response.setStatus(200);
}
finally
{
baseRequest.setHandled(true);
}
}
}
}

View File

@ -15,8 +15,10 @@ package org.eclipse.jetty.client.api;
import org.apache.http.client.HttpClient;
import org.apache.http.impl.client.DefaultHttpClient;
import org.junit.Ignore;
import org.junit.Test;
@Ignore
public class ApacheUsage
{
@Test

View File

@ -22,8 +22,10 @@ import com.ning.http.client.Cookie;
import com.ning.http.client.Realm;
import com.ning.http.client.Request;
import com.ning.http.client.Response;
import org.junit.Ignore;
import org.junit.Test;
@Ignore
public class NingUsage
{
@Test

View File

@ -13,14 +13,21 @@
package org.eclipse.jetty.client.api;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.nio.file.Paths;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jetty.client.HTTPClient;
import org.eclipse.jetty.client.StreamResponseListener;
import org.eclipse.jetty.client.BufferingResponseListener;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.PathContentProvider;
import org.eclipse.jetty.client.StreamingResponseListener;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpVersion;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
@ -28,122 +35,134 @@ import org.junit.Test;
public class Usage
{
@Test
public void testSimpleBlockingGET() throws Exception
public void testGETBlocking_ShortAPI() throws Exception
{
HTTPClient client = new HTTPClient();
HttpClient client = new HttpClient();
Future<Response> responseFuture = client.GET("http://localhost:8080/foo");
Response response = responseFuture.get();
response.getStatus(); // 200
Assert.assertEquals(200, response.status());
// Headers abstraction needed for:
// 1. case insensitivity
// 2. multi values
// 3. value conversion
// Reuse SPDY's ?
response.getHeaders().get("Content-Length").valueAsInt();
response.headers().get("Content-Length");
}
@Test
public void testBlockingGET() throws Exception
public void testGETBlocking() throws Exception
{
HTTPClient client = new HTTPClient();
HttpClient client = new HttpClient();
// Address must be provided, it's the only thing non defaultable
Request.Builder builder = client.builder("localhost:8080");
Future<Response> responseFuture = builder.method("GET").path("/").header("Origin", "localhost").build().send();
responseFuture.get();
Request request = client.newRequest("localhost", 8080)
.scheme("https")
.method(HttpMethod.GET)
.path("/uri")
.version(HttpVersion.HTTP_1_1)
.param("a", "b")
.header("X-Header", "Y-value")
.agent("Jetty HTTP Client")
.cookie("cookie1", "value1")
.decoder(null)
.content(null)
.idleTimeout(5000L);
Future<Response> responseFuture = request.send();
Response response = responseFuture.get();
Assert.assertEquals(200, response.status());
}
@Test
public void testSimpleAsyncGET() throws Exception
public void testGETAsync() throws Exception
{
HTTPClient client = new HTTPClient();
client.builder("localhost:8080").method("GET").path("/").header("Origin", "localhost").build().send(new Response.Listener.Adapter()
HttpClient client = new HttpClient();
final AtomicReference<Response> responseRef = new AtomicReference<>();
final CountDownLatch latch = new CountDownLatch(1);
client.newRequest("localhost", 8080).send(new Response.Listener.Adapter()
{
@Override
public void onEnd(Response response)
public void onSuccess(Response response)
{
responseRef.set(response);
latch.countDown();
}
});
Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
Response response = responseRef.get();
Assert.assertNotNull(response);
Assert.assertEquals(200, response.status());
}
@Test
public void testRequestListener() throws Exception
{
HTTPClient client = new HTTPClient();
Response response = client.builder("localhost:8080")
.method("GET")
.path("/")
HttpClient client = new HttpClient();
Response response = client.newRequest("localhost", 8080)
.listener(new Request.Listener.Adapter()
{
@Override
public void onEnd(Request request)
public void onSuccess(Request request)
{
}
})
.build().send(new Response.Listener.Adapter()
{
@Override
public void onEnd(Response response)
{
}
}).get();
response.getStatus();
}).send().get(5, TimeUnit.SECONDS);
Assert.assertEquals(200, response.status());
}
@Test
public void testRequestWithExplicitConnectionControl() throws Exception
{
HTTPClient client = new HTTPClient();
try (Connection connection = client.getDestination(Address.from("localhost:8080")).connect(5, TimeUnit.SECONDS))
HttpClient client = new HttpClient();
try (Connection connection = client.getDestination("http", "localhost", 8080).newConnection().get(5, TimeUnit.SECONDS))
{
Request.Builder builder = client.builder("localhost:8080");
Request request = builder.method("GET").path("/").header("Origin", "localhost").build();
Future<Response> response = connection.send(request, new Response.Listener.Adapter());
response.get().getStatus();
Request request = client.newRequest("localhost", 8080);
BufferingResponseListener listener = new BufferingResponseListener();
connection.send(request, listener);
Response response = listener.await(5, TimeUnit.SECONDS);
Assert.assertNotNull(response);
Assert.assertEquals(200, response.status());
}
}
@Test
public void testFileUpload() throws Exception
{
HTTPClient client = new HTTPClient();
Response response = client.builder("localhost:8080")
.method("GET").path("/").file(new File("")).build().send().get();
response.getStatus();
HttpClient client = new HttpClient();
Response response = client.newRequest("localhost", 8080)
.content(new PathContentProvider(Paths.get(""))).send().get();
Assert.assertEquals(200, response.status());
}
@Test
public void testCookie() throws Exception
{
HTTPClient client = new HTTPClient();
client.builder("localhost:8080").cookie("key", "value").build().send().get().getStatus(); // 200
HttpClient client = new HttpClient();
Response response = client.newRequest("localhost", 8080).cookie("key", "value").send().get();
Assert.assertEquals(200, response.status());
}
@Test
public void testAuthentication() throws Exception
{
HTTPClient client = new HTTPClient();
client.builder("localhost:8080").authentication(new Authentication.Kerberos()).build().send().get().getStatus(); // 200
}
// @Test
// public void testAuthentication() throws Exception
// {
// HTTPClient client = new HTTPClient();
// client.newRequest("localhost", 8080).authentication(new Authentication.Kerberos()).build().send().get().status(); // 200
// }
@Test
public void testFollowRedirects() throws Exception
{
HTTPClient client = new HTTPClient();
HttpClient client = new HttpClient();
client.setFollowRedirects(false);
client.builder("localhost:8080").followRedirects(true).build().send().get().getStatus(); // 200
client.newRequest("localhost", 8080).followRedirects(true).send().get().status(); // 200
}
@Test
public void testResponseStream() throws Exception
{
HTTPClient client = new HTTPClient();
StreamResponseListener listener = new StreamResponseListener();
client.builder("localhost:8080").build().send(listener);
HttpClient client = new HttpClient();
StreamingResponseListener listener = new StreamingResponseListener();
client.newRequest("localhost", 8080).send(listener);
// Call to get() blocks until the headers arrived
Response response = listener.get(5, TimeUnit.SECONDS);
if (response.getStatus() == 200)
if (response.status() == 200)
{
// Solution 1: use input stream
byte[] buffer = new byte[256];

View File

@ -0,0 +1,2 @@
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
org.eclipse.jetty.LEVEL=DEBUG

View File

@ -49,10 +49,10 @@ import org.eclipse.jetty.util.log.Logger;
/* ------------------------------------------------------------ */
/**
* HTTP Fields. A collection of HTTP header and or Trailer fields.
*
*
* <p>This class is not synchronized as it is expected that modifications will only be performed by a
* single thread.
*
*
*/
public class HttpFields implements Iterable<HttpFields.Field>
{
@ -352,7 +352,7 @@ public class HttpFields implements Iterable<HttpFields.Field>
/**
* Get a Field by index.
* @return A Field value or null if the Field value has not been set
*
*
*/
public Field getField(int i)
{
@ -418,7 +418,7 @@ public class HttpFields implements Iterable<HttpFields.Field>
/* -------------------------------------------------------------- */
/**
* Get multi headers
*
*
* @return Enumeration of the values, or null if no such header.
* @param name the case-insensitive field name
*/
@ -441,7 +441,7 @@ public class HttpFields implements Iterable<HttpFields.Field>
/* -------------------------------------------------------------- */
/**
* Get multi headers
*
*
* @return Enumeration of the values
* @param name the case-insensitive field name
*/
@ -480,7 +480,7 @@ public class HttpFields implements Iterable<HttpFields.Field>
* Get multi field values with separator. The multiple values can be represented as separate
* headers of the same name, or by a single header using the separator(s), or a combination of
* both. Separators may be quoted.
*
*
* @param name the case-insensitive field name
* @param separators String of separators.
* @return Enumeration of the values, or null if no such header.
@ -523,7 +523,7 @@ public class HttpFields implements Iterable<HttpFields.Field>
/* -------------------------------------------------------------- */
/**
* Set a field.
*
*
* @param name the name of the field
* @param value the value of the field. If null the field is cleared.
*/
@ -544,11 +544,11 @@ public class HttpFields implements Iterable<HttpFields.Field>
{
put(header,value.toString());
}
/* -------------------------------------------------------------- */
/**
* Set a field.
*
*
* @param header the header name of the field
* @param value the value of the field. If null the field is cleared.
*/
@ -567,7 +567,7 @@ public class HttpFields implements Iterable<HttpFields.Field>
/* -------------------------------------------------------------- */
/**
* Set a field.
*
*
* @param name the name of the field
* @param list the List value of the field. If null the field is cleared.
*/
@ -583,7 +583,7 @@ public class HttpFields implements Iterable<HttpFields.Field>
/**
* Add to or set a field. If the field is allowed to have multiple values, add will add multiple
* headers of the same name.
*
*
* @param name the name of the field
* @param value the value of the field.
* @exception IllegalArgumentException If the name is a single valued field and already has a
@ -591,9 +591,9 @@ public class HttpFields implements Iterable<HttpFields.Field>
*/
public void add(String name, String value) throws IllegalArgumentException
{
if (value == null)
if (value == null)
return;
Field field = _names.get(name);
Field last = null;
while (field != null)
@ -612,18 +612,18 @@ public class HttpFields implements Iterable<HttpFields.Field>
else
_names.put(name, field);
}
/* -------------------------------------------------------------- */
public void add(HttpHeader header, HttpHeaderValue value) throws IllegalArgumentException
{
add(header,value.toString());
}
/* -------------------------------------------------------------- */
/**
* Add to or set a field. If the field is allowed to have multiple values, add will add multiple
* headers of the same name.
*
*
* @param name the name of the field
* @param value the value of the field.
* @exception IllegalArgumentException If the name is a single valued field and already has a
@ -655,18 +655,18 @@ public class HttpFields implements Iterable<HttpFields.Field>
/* ------------------------------------------------------------ */
/**
* Remove a field.
*
*
* @param name
*/
public void remove(HttpHeader name)
{
remove(name.toString());
}
/* ------------------------------------------------------------ */
/**
* Remove a field.
*
*
* @param name
*/
public void remove(String name)
@ -683,7 +683,7 @@ public class HttpFields implements Iterable<HttpFields.Field>
/**
* Get a header as an long value. Returns the value of an integer field or -1 if not found. The
* case of the field name is ignored.
*
*
* @param name the case-insensitive field name
* @exception NumberFormatException If bad long found
*/
@ -697,7 +697,7 @@ public class HttpFields implements Iterable<HttpFields.Field>
/**
* Get a header as a date value. Returns the value of a date field, or -1 if not found. The case
* of the field name is ignored.
*
*
* @param name the case-insensitive field name
*/
public long getDateField(String name)
@ -720,7 +720,7 @@ public class HttpFields implements Iterable<HttpFields.Field>
/* -------------------------------------------------------------- */
/**
* Sets the value of an long field.
*
*
* @param name the field name
* @param value the field long value
*/
@ -729,11 +729,11 @@ public class HttpFields implements Iterable<HttpFields.Field>
String v = Long.toString(value);
put(name, v);
}
/* -------------------------------------------------------------- */
/**
* Sets the value of an long field.
*
*
* @param name the field name
* @param value the field long value
*/
@ -747,7 +747,7 @@ public class HttpFields implements Iterable<HttpFields.Field>
/* -------------------------------------------------------------- */
/**
* Sets the value of a date field.
*
*
* @param name the field name
* @param date the field date value
*/
@ -760,7 +760,7 @@ public class HttpFields implements Iterable<HttpFields.Field>
/* -------------------------------------------------------------- */
/**
* Sets the value of a date field.
*
*
* @param name the field name
* @param date the field date value
*/
@ -769,11 +769,11 @@ public class HttpFields implements Iterable<HttpFields.Field>
String d=formatDate(date);
put(name, d);
}
/* -------------------------------------------------------------- */
/**
* Sets the value of a date field.
*
*
* @param name the field name
* @param date the field date value
*/
@ -786,7 +786,7 @@ public class HttpFields implements Iterable<HttpFields.Field>
/* ------------------------------------------------------------ */
/**
* Format a set cookie value
*
*
* @param cookie The cookie.
*/
public void addSetCookie(HttpCookie cookie)
@ -805,7 +805,7 @@ public class HttpFields implements Iterable<HttpFields.Field>
/**
* Format a set cookie value
*
*
* @param name the name
* @param value the value
* @param domain the domain
@ -842,7 +842,7 @@ public class HttpFields implements Iterable<HttpFields.Field>
if (value != null && value.length() > 0)
quoted|=QuotedStringTokenizer.quoteIfNeeded(buf, value, delim);
if (path != null && path.length() > 0)
{
buf.append(";Path=");
@ -921,7 +921,8 @@ public class HttpFields implements Iterable<HttpFields.Field>
/* -------------------------------------------------------------- */
@Override
public String toString()
public String
toString()
{
try
{
@ -963,7 +964,7 @@ public class HttpFields implements Iterable<HttpFields.Field>
/**
* Add fields from another HttpFields instance. Single valued fields are replaced, while all
* others are added.
*
*
* @param fields
*/
public void add(HttpFields fields)
@ -984,13 +985,13 @@ public class HttpFields implements Iterable<HttpFields.Field>
/**
* Get field value parameters. Some field values can have parameters. This method separates the
* value from the parameters and optionally populates a map with the parameters. For example:
*
*
* <PRE>
*
*
* FieldName : Value ; param1=val1 ; param2=val2
*
*
* </PRE>
*
*
* @param value The Field value, possibly with parameteres.
* @param parameters A map to populate with the parameters, or null
* @return The value.
@ -1083,7 +1084,7 @@ public class HttpFields implements Iterable<HttpFields.Field>
/* ------------------------------------------------------------ */
/**
* List values in quality order.
*
*
* @param e Enumeration of values with quality parameters
* @return values in quality order.
*/
@ -1153,7 +1154,7 @@ public class HttpFields implements Iterable<HttpFields.Field>
_value = value;
_next = null;
}
/* ------------------------------------------------------------ */
private Field(String name, String value)
{
@ -1179,7 +1180,7 @@ public class HttpFields implements Iterable<HttpFields.Field>
}
return bytes;
}
/* ------------------------------------------------------------ */
private byte[] toSanitisedValue(String s)
{
@ -1236,7 +1237,7 @@ public class HttpFields implements Iterable<HttpFields.Field>
{
return _header;
}
/* ------------------------------------------------------------ */
public String getName()
{
@ -1273,20 +1274,20 @@ public class HttpFields implements Iterable<HttpFields.Field>
{
if (_value==null)
return false;
if (value.equalsIgnoreCase(_value))
return true;
String[] split = _value.split("\\s*,\\s*");
for (String s : split)
{
if (value.equalsIgnoreCase(s))
return true;
}
if (_next!=null)
return _next.contains(value);
return false;
}
}

View File

@ -1141,7 +1141,7 @@ public class HttpParser
}
/* ------------------------------------------------------------------------------- */
public void inputShutdown()
public void shutdownInput()
{
// was this unexpected?
switch(_state)

View File

@ -324,7 +324,7 @@ public class HttpParserTest
parser.reset();
init();
parser.parseNext(buffer);
parser.inputShutdown();
parser.shutdownInput();
assertEquals("PUT", _methodOrVersion);
assertEquals("/doodle", _uriOrStatus);
assertEquals("HTTP/1.0", _versionOrReason);
@ -400,7 +400,7 @@ public class HttpParserTest
init();
parser.parseNext(buffer);
parser.inputShutdown();
parser.shutdownInput();
assertEquals("HTTP/1.1", _methodOrVersion);
assertEquals("200", _uriOrStatus);
assertEquals("Correct", _versionOrReason);

View File

@ -39,14 +39,14 @@ public class ByteArrayEndPoint extends AbstractEndPoint
{
static final Logger LOG = Log.getLogger(ByteArrayEndPoint.class);
public final static InetSocketAddress NOIP=new InetSocketAddress(0);
protected ByteBuffer _in;
protected ByteBuffer _out;
protected boolean _ishut;
protected boolean _oshut;
protected boolean _closed;
protected boolean _growOutput;
/* ------------------------------------------------------------ */
/**
@ -102,14 +102,14 @@ public class ByteArrayEndPoint extends AbstractEndPoint
setIdleTimeout(idleTimeoutMs);
}
/* ------------------------------------------------------------ */
@Override
protected void onIncompleteFlush()
{
{
// Don't need to do anything here as takeOutput does the signalling.
}
@ -177,7 +177,16 @@ public class ByteArrayEndPoint extends AbstractEndPoint
*/
public String getOutputString()
{
return BufferUtil.toString(_out,StringUtil.__UTF8_CHARSET);
return getOutputString(StringUtil.__UTF8_CHARSET);
}
/* ------------------------------------------------------------ */
/**
* @return Returns the out.
*/
public String getOutputString(Charset charset)
{
return BufferUtil.toString(_out,charset);
}
/* ------------------------------------------------------------ */
@ -198,17 +207,17 @@ public class ByteArrayEndPoint extends AbstractEndPoint
*/
public String takeOutputString()
{
ByteBuffer buffer=takeOutput();
return BufferUtil.toString(buffer,StringUtil.__UTF8_CHARSET);
return takeOutputString(StringUtil.__UTF8_CHARSET);
}
/* ------------------------------------------------------------ */
/**
* @return Returns the out.
*/
public String getOutputString(Charset charset)
public String takeOutputString(Charset charset)
{
return BufferUtil.toString(_out,charset);
ByteBuffer buffer=takeOutput();
return BufferUtil.toString(buffer,charset);
}
/* ------------------------------------------------------------ */
@ -397,7 +406,7 @@ public class ByteArrayEndPoint extends AbstractEndPoint
super.setIdleTimeout(idleTimeout);
scheduleIdleTimeout(idleTimeout);
}
}

View File

@ -47,7 +47,7 @@ public class ChannelEndPoint extends AbstractEndPoint
private volatile boolean _ishut;
private volatile boolean _oshut;
public ChannelEndPoint(ScheduledExecutorService scheduler,SocketChannel channel) throws IOException
public ChannelEndPoint(ScheduledExecutorService scheduler,SocketChannel channel)
{
super(scheduler,
(InetSocketAddress)channel.socket().getLocalSocketAddress(),
@ -186,7 +186,7 @@ public class ChannelEndPoint extends AbstractEndPoint
if (flushed>0)
{
notIdle();
// clear empty buffers to prevent position creeping up the buffer
for (ByteBuffer b : buffers)
if (BufferUtil.isEmpty(b))

View File

@ -66,6 +66,9 @@ public class MappedByteBufferPool implements ByteBufferPool
public void release(ByteBuffer buffer)
{
if (buffer == null)
return;
int bucket = bucketFor(buffer.capacity());
ConcurrentMap<Integer, Queue<ByteBuffer>> buffers = buffersFor(buffer.isDirect());

View File

@ -18,7 +18,6 @@
package org.eclipse.jetty.io;
import java.io.IOException;
import java.nio.channels.CancelledKeyException;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
@ -75,7 +74,7 @@ public class SelectChannelEndPoint extends ChannelEndPoint implements SelectorMa
*/
private volatile int _interestOps;
public SelectChannelEndPoint(SocketChannel channel, ManagedSelector selector, SelectionKey key, ScheduledExecutorService scheduler, long idleTimeout) throws IOException
public SelectChannelEndPoint(SocketChannel channel, ManagedSelector selector, SelectionKey key, ScheduledExecutorService scheduler, long idleTimeout)
{
super(scheduler,channel);
_selector = selector;

View File

@ -302,7 +302,7 @@ abstract public class WriteFlusher
// Are we complete?
for (ByteBuffer b : buffers)
{
if (b.hasRemaining())
if (BufferUtil.hasContent(b))
{
PendingState<?> pending=new PendingState<>(buffers, context, callback);
if (updateState(__WRITING,pending))
@ -362,7 +362,7 @@ abstract public class WriteFlusher
// Are we complete?
for (ByteBuffer b : buffers)
{
if (b.hasRemaining())
if (BufferUtil.hasContent(b))
{
if (updateState(__COMPLETING,pending))
onIncompleteFlushed();

View File

@ -190,7 +190,7 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
}
else if (filled < 0)
{
_parser.inputShutdown();
_parser.shutdownInput();
// We were only filling if fully consumed, so if we have
// read -1 then we have nothing to parse and thus nothing that
// will generate a response. If we had a suspended request pending
@ -517,7 +517,7 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
// If no more input
if (getEndPoint().isInputShutdown())
{
_parser.inputShutdown();
_parser.shutdownInput();
return;
}
@ -536,7 +536,7 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
LOG.debug("{} block filled {}",this,filled);
if (filled<0)
{
_parser.inputShutdown();
_parser.shutdownInput();
return;
}
}

View File

@ -34,16 +34,16 @@ import java.nio.charset.Charset;
/* ------------------------------------------------------------------------------- */
/**
* Buffer utility methods.
*
*
* These utility methods facilitate the usage of NIO {@link ByteBuffer}'s in a more flexible way.
* The standard {@link ByteBuffer#flip()} assumes that once flipped to flush a buffer,
* that it will be completely emptied before being cleared ready to be filled again.
* The standard {@link ByteBuffer#flip()} assumes that once flipped to flush a buffer,
* that it will be completely emptied before being cleared ready to be filled again.
* The {@link #flipToFill(ByteBuffer)} and {@link #flipToFlush(ByteBuffer, int)} methods provided here
* do not assume that the buffer is empty and will preserve content when flipped.
* <p>
* ByteBuffers can be considered in one of two modes: Flush mode where valid content is contained between
* position and limit which is consumed by advancing the position; and Fill mode where empty space is between
* the position and limit, which is filled by advancing the position. In fill mode, there may be valid data
* ByteBuffers can be considered in one of two modes: Flush mode where valid content is contained between
* position and limit which is consumed by advancing the position; and Fill mode where empty space is between
* the position and limit, which is filled by advancing the position. In fill mode, there may be valid data
* in the buffer before the position and the start of this data is given by the return value of {@link #flipToFill(ByteBuffer)}
* <p>
* A typical pattern for using the buffers in this style is:
@ -70,7 +70,7 @@ public class BufferUtil
/* ------------------------------------------------------------ */
/** Allocate ByteBuffer in flush mode.
* The position and limit will both be zero, indicating that the buffer is
* The position and limit will both be zero, indicating that the buffer is
* empty and must be flipped before any data is put to it.
* @param capacity
* @return Buffer
@ -84,7 +84,7 @@ public class BufferUtil
/* ------------------------------------------------------------ */
/** Allocate ByteBuffer in flush mode.
* The position and limit will both be zero, indicating that the buffer is
* The position and limit will both be zero, indicating that the buffer is
* empty and must be flipped before any data is put to it.
* @param capacity
* @return Buffer
@ -95,7 +95,7 @@ public class BufferUtil
buf.limit(0);
return buf;
}
/* ------------------------------------------------------------ */
/** Clear the buffer to be empty in flush mode.
@ -113,27 +113,30 @@ public class BufferUtil
/* ------------------------------------------------------------ */
/** Clear the buffer to be empty in fill mode.
* The position is set to 0 and the limit is set to the capacity.
* The position is set to 0 and the limit is set to the capacity.
* @param buffer The buffer to clear.
*/
public static void clearToFill(ByteBuffer buffer)
{
buffer.position(0);
buffer.limit(buffer.capacity());
if (buffer!=null)
{
buffer.position(0);
buffer.limit(buffer.capacity());
}
}
/* ------------------------------------------------------------ */
/** Flip the buffer to fill mode.
* The position is set to the first unused position in the buffer
* The position is set to the first unused position in the buffer
* (the old limit) and the limit is set to the capacity.
* If the buffer is empty, then this call is effectively {@link #clearToFill(ByteBuffer)}.
* If there is no unused space to fill, a {@link ByteBuffer#compact()} is done to attempt
* to create space.
* <p>
* This method is used as a replacement to {@link ByteBuffer#compact()}.
*
*
* @param buffer The buffer to flip
* @return The position of the valid data before the flipped position. This value should be
* @return The position of the valid data before the flipped position. This value should be
* passed to a subsequent call to {@link #flipToFlush(ByteBuffer, int)}
*/
public static int flipToFill(ByteBuffer buffer)
@ -155,7 +158,7 @@ public class BufferUtil
buffer.limit(buffer.capacity());
return 0;
}
buffer.position(limit);
buffer.limit(capacity);
return position;
@ -177,12 +180,12 @@ public class BufferUtil
buffer.limit(buffer.position());
buffer.position(position);
}
/* ------------------------------------------------------------ */
/** Convert a ByteBuffer to a byte array.
* @param buffer The buffer to convert in flush mode. The buffer is not altered.
* @return An array of bytes duplicated from the buffer.
* @return An array of bytes duplicated from the buffer.
*/
public static byte[] toArray(ByteBuffer buffer)
{
@ -206,7 +209,7 @@ public class BufferUtil
{
return buf==null || buf.remaining()==0;
}
/* ------------------------------------------------------------ */
/** Check for a non null and non empty buffer.
* @param buf the buffer to check
@ -216,7 +219,7 @@ public class BufferUtil
{
return buf!=null && buf.remaining()>0;
}
/* ------------------------------------------------------------ */
/** Check for a non null and full buffer.
* @param buf the buffer to check
@ -226,7 +229,7 @@ public class BufferUtil
{
return buf!=null && buf.limit()==buf.capacity();
}
/* ------------------------------------------------------------ */
/** Get remaining from null checked buffer
* @param buffer The buffer to get the remaining from, in flush mode.
@ -248,7 +251,7 @@ public class BufferUtil
return 0;
return buffer.capacity()-buffer.limit();
}
/* ------------------------------------------------------------ */
/** Compact the buffer
* @param buffer
@ -260,7 +263,7 @@ public class BufferUtil
buffer.compact().flip();
return full && buffer.limit()<buffer.capacity();
}
/* ------------------------------------------------------------ */
/**
* Put data from one buffer into another, avoiding over/under flows
@ -274,7 +277,7 @@ public class BufferUtil
int remaining=from.remaining();
if (remaining>0)
{
if (remaining<=to.remaining())
if (remaining<=to.remaining())
{
to.put(from);
put=remaining;
@ -301,7 +304,7 @@ public class BufferUtil
return put;
}
/* ------------------------------------------------------------ */
/**
* Put data from one buffer into another, avoiding over/under flows
@ -337,7 +340,7 @@ public class BufferUtil
flipToFlush(to,pos);
}
}
/* ------------------------------------------------------------ */
/**
*/
@ -347,14 +350,14 @@ public class BufferUtil
to.limit(limit+1);
to.put(limit,b);
}
/* ------------------------------------------------------------ */
public static void readFrom(File file, ByteBuffer buffer) throws IOException
{
RandomAccessFile raf = new RandomAccessFile(file,"r");
FileChannel channel = raf.getChannel();
long needed=raf.length();
while (needed>0 && buffer.hasRemaining())
needed=needed-channel.read(buffer);
}
@ -387,7 +390,7 @@ public class BufferUtil
out.write(buffer.get(i));
}
}
/* ------------------------------------------------------------ */
/** Convert the buffer to an ISO-8859-1 String
* @param buffer The buffer to convert in flush mode. The buffer is unchanged
@ -411,7 +414,7 @@ public class BufferUtil
/* ------------------------------------------------------------ */
/** Convert the buffer to an ISO-8859-1 String
* @param buffer The buffer to convert in flush mode. The buffer is unchanged
* @param charset The {@link Charset} to use to convert the bytes
* @param charset The {@link Charset} to use to convert the bytes
* @return The buffer as a string.
*/
public static String toString(ByteBuffer buffer, Charset charset)
@ -431,7 +434,7 @@ public class BufferUtil
/* ------------------------------------------------------------ */
/** Convert a partial buffer to an ISO-8859-1 String
* @param buffer The buffer to convert in flush mode. The buffer is unchanged
* @param charset The {@link Charset} to use to convert the bytes
* @param charset The {@link Charset} to use to convert the bytes
* @return The buffer as a string.
*/
public static String toString(ByteBuffer buffer, int position, int length, Charset charset)
@ -450,11 +453,11 @@ public class BufferUtil
}
return new String(array,buffer.arrayOffset()+position,length,charset);
}
/* ------------------------------------------------------------ */
/**
* Convert buffer to an integer. Parses up to the first non-numeric character. If no number is found an IllegalArgumentException is thrown
*
*
* @param buffer
* A buffer containing an integer in flush mode. The position is not changed.
* @return an int
@ -495,7 +498,7 @@ public class BufferUtil
/**
* Convert buffer to an long. Parses up to the first non-numeric character. If no number is found an IllegalArgumentException is thrown
*
*
* @param buffer
* A buffer containing an integer in flush mode. The position is not changed.
* @return an int
@ -579,7 +582,7 @@ public class BufferUtil
}
}
}
/* ------------------------------------------------------------ */
public static void putDecInt(ByteBuffer buffer, int n)
{
@ -684,10 +687,10 @@ public class BufferUtil
{
return ByteBuffer.wrap(s.getBytes(charset));
}
/**
* Create a new ByteBuffer using provided byte array.
*
*
* @param array
* the byte array to back buffer with.
* @return ByteBuffer with provided byte array, in flush mode
@ -696,10 +699,10 @@ public class BufferUtil
{
return ByteBuffer.wrap(array);
}
/**
* Create a new ByteBuffer using the provided byte array.
*
*
* @param array
* the byte array to use.
* @param offset
@ -719,7 +722,7 @@ public class BufferUtil
MappedByteBuffer buffer=raf.getChannel().map(MapMode.READ_ONLY,0,raf.length());
return buffer;
}
public static String toSummaryString(ByteBuffer buffer)
{
if (buffer==null)
@ -749,19 +752,19 @@ public class BufferUtil
builder.append(']');
return builder.toString();
}
public static String toDetailString(ByteBuffer buffer)
{
if (buffer==null)
return "null";
StringBuilder buf = new StringBuilder();
buf.append(buffer.getClass().getSimpleName());
buf.append("@");
if (buffer.hasArray())
buf.append(Integer.toHexString(((Object)buffer.array()).hashCode()));
else
buf.append("?");
buf.append("?");
buf.append("[p=");
buf.append(buffer.position());
buf.append(",l=");
@ -771,7 +774,7 @@ public class BufferUtil
buf.append(",r=");
buf.append(buffer.remaining());
buf.append("]={");
for (int i=0;i<buffer.position();i++)
{
char c=(char)buffer.get(i);
@ -823,11 +826,11 @@ public class BufferUtil
}
buffer.limit(limit);
buf.append("}");
return buf.toString();
}
private final static int[] decDivisors =
{ 1000000000, 100000000, 10000000, 1000000, 100000, 10000, 1000, 100, 10, 1 };
@ -837,7 +840,7 @@ public class BufferUtil
private final static long[] decDivisorsL =
{ 1000000000000000000L, 100000000000000000L, 10000000000000000L, 1000000000000000L, 100000000000000L, 10000000000000L, 1000000000000L, 100000000000L,
10000000000L, 1000000000L, 100000000L, 10000000L, 1000000L, 100000L, 10000L, 1000L, 100L, 10L, 1L };
public static void putCRLF(ByteBuffer buffer)
{
buffer.put((byte)13);