Merge branch 'jetty-9' of ssh://git.eclipse.org/gitroot/jetty/org.eclipse.jetty.project into jetty-9

This commit is contained in:
Jesse McConnell 2012-10-11 13:27:18 -05:00
commit b6fcdf0f8f
128 changed files with 5141 additions and 1432 deletions

View File

@ -0,0 +1,186 @@
//
// ========================================================================
// Copyright (c) 1995-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.embedded;
import java.lang.management.ManagementFactory;
import org.eclipse.jetty.deploy.DeploymentManager;
import org.eclipse.jetty.deploy.providers.ContextProvider;
import org.eclipse.jetty.deploy.providers.WebAppProvider;
import org.eclipse.jetty.io.ArrayByteBufferPool;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.FilterConnection;
import org.eclipse.jetty.jmx.MBeanContainer;
import org.eclipse.jetty.security.HashLoginService;
import org.eclipse.jetty.server.FilterConnectionFactory;
import org.eclipse.jetty.server.ForwardedRequestCustomizer;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpChannelConfig;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.NCSARequestLog;
import org.eclipse.jetty.server.SecureRequestCustomizer;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.SslConnectionFactory;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.server.handler.RequestLogHandler;
import org.eclipse.jetty.server.handler.StatisticsHandler;
import org.eclipse.jetty.spdy.server.NPNServerConnectionFactory;
import org.eclipse.jetty.spdy.server.http.HTTPSPDYServerConnectionFactory;
import org.eclipse.jetty.spdy.server.http.PushStrategy;
import org.eclipse.jetty.spdy.server.http.ReferrerPushStrategy;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.util.thread.TimerScheduler;
public class SpdyServer
{
public static void main(String[] args) throws Exception
{
String jetty_home = System.getProperty("jetty.home","../jetty-distribution/target/distribution");
System.setProperty("jetty.home",jetty_home);
// Setup Threadpool
QueuedThreadPool threadPool = new QueuedThreadPool();
threadPool.setMaxThreads(500);
Server server = new Server(threadPool);
server.manage(threadPool);
server.setDumpAfterStart(false);
server.setDumpBeforeStop(false);
// Setup JMX
MBeanContainer mbContainer=new MBeanContainer(ManagementFactory.getPlatformMBeanServer());
server.addBean(mbContainer);
// Common HTTP configuration
HttpChannelConfig config = new HttpChannelConfig();
config.setSecurePort(8443);
config.addCustomizer(new ForwardedRequestCustomizer());
config.addCustomizer(new SecureRequestCustomizer());
// Http Connector
HttpConnectionFactory http = new HttpConnectionFactory(config);
FilterConnectionFactory filter = new FilterConnectionFactory(http.getProtocol());
filter.addFilter(new FilterConnection.DumpToFileFilter("http-"));
ServerConnector httpConnector = new ServerConnector(server,filter,http);
httpConnector.setPort(8080);
httpConnector.setIdleTimeout(30000);
server.addConnector(httpConnector);
// SSL configurations
SslContextFactory sslContextFactory = new SslContextFactory();
sslContextFactory.setKeyStorePath(jetty_home + "/etc/keystore");
sslContextFactory.setKeyStorePassword("OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4");
sslContextFactory.setKeyManagerPassword("OBF:1u2u1wml1z7s1z7a1wnl1u2g");
sslContextFactory.setTrustStorePath(jetty_home + "/etc/keystore");
sslContextFactory.setTrustStorePassword("OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4");
sslContextFactory.setExcludeCipherSuites(
"SSL_RSA_WITH_DES_CBC_SHA",
"SSL_DHE_RSA_WITH_DES_CBC_SHA",
"SSL_DHE_DSS_WITH_DES_CBC_SHA",
"SSL_RSA_EXPORT_WITH_RC4_40_MD5",
"SSL_RSA_EXPORT_WITH_DES40_CBC_SHA",
"SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA",
"SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA");
// Spdy Connector
PushStrategy push = new ReferrerPushStrategy();
HTTPSPDYServerConnectionFactory spdy2 = new HTTPSPDYServerConnectionFactory(2,config,push);
spdy2.setInputBufferSize(8192);
spdy2.setInitialWindowSize(32768);
HTTPSPDYServerConnectionFactory spdy3 = new HTTPSPDYServerConnectionFactory(3,config,push);
spdy2.setInputBufferSize(8192);
NPNServerConnectionFactory npn = new NPNServerConnectionFactory(spdy3.getProtocol(),spdy2.getProtocol(),http.getProtocol());
npn.setDefaultProtocol(http.getProtocol());
npn.setInputBufferSize(1024);
FilterConnectionFactory npn_filter = new FilterConnectionFactory(npn.getProtocol());
npn_filter.addFilter(new FilterConnection.DumpToFileFilter("npn-"));
SslConnectionFactory ssl = new SslConnectionFactory(sslContextFactory,npn_filter.getProtocol());
FilterConnectionFactory ssl_filter = new FilterConnectionFactory(ssl.getProtocol());
ssl_filter.addFilter(new FilterConnection.DumpToFileFilter("ssl-"));
ServerConnector spdyConnector = new ServerConnector(server,ssl_filter,ssl,npn_filter,npn,spdy3,spdy2,http);
spdyConnector.setPort(8443);
server.addConnector(spdyConnector);
// Setup handlers
HandlerCollection handlers = new HandlerCollection();
ContextHandlerCollection contexts = new ContextHandlerCollection();
RequestLogHandler requestLogHandler = new RequestLogHandler();
handlers.setHandlers(new Handler[] { contexts, new DefaultHandler(), requestLogHandler });
StatisticsHandler stats = new StatisticsHandler();
stats.setHandler(handlers);
server.setHandler(stats);
// Setup deployers
DeploymentManager deployer = new DeploymentManager();
deployer.setContexts(contexts);
server.addBean(deployer);
ContextProvider context_provider = new ContextProvider();
context_provider.setMonitoredDirName(jetty_home + "/contexts");
context_provider.setScanInterval(2);
deployer.addAppProvider(context_provider);
WebAppProvider webapp_provider = new WebAppProvider();
webapp_provider.setMonitoredDirName(jetty_home + "/webapps");
webapp_provider.setParentLoaderPriority(false);
webapp_provider.setExtractWars(true);
webapp_provider.setScanInterval(2);
webapp_provider.setDefaultsDescriptor(jetty_home + "/etc/webdefault.xml");
webapp_provider.setContextXmlDir(jetty_home + "/contexts");
deployer.addAppProvider(webapp_provider);
HashLoginService login = new HashLoginService();
login.setName("Test Realm");
login.setConfig(jetty_home + "/etc/realm.properties");
server.addBean(login);
NCSARequestLog requestLog = new NCSARequestLog(jetty_home + "/logs/jetty-yyyy_mm_dd.log");
requestLog.setExtended(false);
requestLogHandler.setRequestLog(requestLog);
server.setStopAtShutdown(true);
server.setSendServerVersion(true);
server.start();
server.dumpStdErr();
server.join();
}
}

View File

@ -18,7 +18,6 @@
package org.eclipse.jetty.client;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@ -40,9 +39,9 @@ public class AuthenticationProtocolHandler implements ProtocolHandler
public static final Logger LOG = Log.getLogger(AuthenticationProtocolHandler.class);
private static final Pattern WWW_AUTHENTICATE_PATTERN = Pattern.compile("([^\\s]+)\\s+realm=\"([^\"]+)\".*", Pattern.CASE_INSENSITIVE);
private final ResponseNotifier notifier = new ResponseNotifier();
private final HttpClient client;
private final int maxContentLength;
private final ResponseNotifier notifier;
public AuthenticationProtocolHandler(HttpClient client)
{
@ -53,6 +52,7 @@ public class AuthenticationProtocolHandler implements ProtocolHandler
{
this.client = client;
this.maxContentLength = maxContentLength;
this.notifier = new ResponseNotifier(client);
}
@Override
@ -64,6 +64,7 @@ public class AuthenticationProtocolHandler implements ProtocolHandler
@Override
public Response.Listener getResponseListener()
{
// Return new instances every time to keep track of the response content
return new AuthenticationListener();
}
@ -78,12 +79,14 @@ public class AuthenticationProtocolHandler implements ProtocolHandler
public void onComplete(Result result)
{
Request request = result.getRequest();
HttpConversation conversation = client.getConversation(request.conversation());
Response.Listener listener = conversation.exchanges().peekFirst().listener();
ContentResponse response = new HttpContentResponse(result.getResponse(), getContent(), getEncoding());
if (result.isFailed())
{
Throwable failure = result.getFailure();
LOG.debug("Authentication challenge failed {}", failure);
forwardFailure(request, response, failure);
notifier.forwardFailureComplete(listener, request, result.getRequestFailure(), response, result.getResponseFailure());
return;
}
@ -91,7 +94,7 @@ public class AuthenticationProtocolHandler implements ProtocolHandler
if (wwwAuthenticates.isEmpty())
{
LOG.debug("Authentication challenge without WWW-Authenticate header");
forwardFailure(request, response, new HttpResponseException("HTTP protocol violation: 401 without WWW-Authenticate header", response));
notifier.forwardFailureComplete(listener, request, null, response, new HttpResponseException("HTTP protocol violation: 401 without WWW-Authenticate header", response));
return;
}
@ -110,16 +113,15 @@ public class AuthenticationProtocolHandler implements ProtocolHandler
if (authentication == null)
{
LOG.debug("No authentication available for {}", request);
forwardSuccess(request, response);
notifier.forwardSuccessComplete(listener, request, response);
return;
}
HttpConversation conversation = client.getConversation(request);
final Authentication.Result authnResult = authentication.authenticate(request, response, wwwAuthenticate.value, conversation);
LOG.debug("Authentication result {}", authnResult);
if (authnResult == null)
{
forwardSuccess(request, response);
notifier.forwardSuccessComplete(listener, request, response);
return;
}
@ -134,32 +136,6 @@ public class AuthenticationProtocolHandler implements ProtocolHandler
});
}
private void forwardFailure(Request request, Response response, Throwable failure)
{
HttpConversation conversation = client.getConversation(request);
Response.Listener listener = conversation.exchanges().peekFirst().listener();
notifier.notifyBegin(listener, response);
notifier.notifyHeaders(listener, response);
if (response instanceof ContentResponse)
notifier.notifyContent(listener, response, ByteBuffer.wrap(((ContentResponse)response).content()));
notifier.notifyFailure(listener, response, failure);
conversation.complete();
notifier.notifyComplete(listener, new Result(request, response, failure));
}
private void forwardSuccess(Request request, Response response)
{
HttpConversation conversation = client.getConversation(request);
Response.Listener listener = conversation.exchanges().peekFirst().listener();
notifier.notifyBegin(listener, response);
notifier.notifyHeaders(listener, response);
if (response instanceof ContentResponse)
notifier.notifyContent(listener, response, ByteBuffer.wrap(((ContentResponse)response).content()));
notifier.notifySuccess(listener, response);
conversation.complete();
notifier.notifyComplete(listener, new Result(request, response));
}
private List<WWWAuthenticate> parseWWWAuthenticate(Response response)
{
// TODO: these should be ordered by strength

View File

@ -0,0 +1,106 @@
//
// ========================================================================
// Copyright (c) 1995-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 org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.client.util.BufferingResponseListener;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpHeaderValue;
public class ContinueProtocolHandler implements ProtocolHandler
{
private static final String ATTRIBUTE = ContinueProtocolHandler.class.getName() + ".100continue";
private final HttpClient client;
private final ResponseNotifier notifier;
public ContinueProtocolHandler(HttpClient client)
{
this.client = client;
this.notifier = new ResponseNotifier(client);
}
@Override
public boolean accept(Request request, Response response)
{
boolean expect100 = request.headers().contains(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString());
boolean handled100 = client.getConversation(request.conversation()).getAttribute(ATTRIBUTE) != null;
return expect100 && !handled100;
}
@Override
public Response.Listener getResponseListener()
{
// Return new instances every time to keep track of the response content
return new ContinueListener();
}
private class ContinueListener extends BufferingResponseListener
{
@Override
public void onSuccess(Response response)
{
// Handling of success must be done here and not from onComplete(),
// since the onComplete() is not invoked because the request is not completed yet.
HttpConversation conversation = client.getConversation(response.conversation());
// Mark the 100 Continue response as handled
conversation.setAttribute(ATTRIBUTE, Boolean.TRUE);
HttpExchange exchange = conversation.exchanges().peekLast();
assert exchange.response() == response;
Response.Listener listener = exchange.listener();
switch (response.status())
{
case 100:
{
// All good, continue
exchange.resetResponse(true);
conversation.listener(listener);
exchange.proceed(true);
break;
}
default:
{
// Server either does not support 100 Continue, or it does and wants to refuse the request content
HttpContentResponse contentResponse = new HttpContentResponse(response, getContent(), getEncoding());
notifier.forwardSuccess(listener, contentResponse);
conversation.listener(listener);
exchange.proceed(false);
break;
}
}
}
@Override
public void onFailure(Response response, Throwable failure)
{
HttpConversation conversation = client.getConversation(response.conversation());
// Mark the 100 Continue response as handled
conversation.setAttribute(ATTRIBUTE, Boolean.TRUE);
HttpExchange exchange = conversation.exchanges().peekLast();
assert exchange.response() == response;
Response.Listener listener = exchange.listener();
HttpContentResponse contentResponse = new HttpContentResponse(response, getContent(), getEncoding());
notifier.forwardFailureComplete(listener, exchange.request(), exchange.requestFailure(), contentResponse, failure);
}
}
}

View File

@ -22,6 +22,7 @@ import java.io.IOException;
import java.net.ConnectException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.SocketException;
import java.net.URI;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
@ -120,6 +121,8 @@ public class HttpClient extends ContainerLifeCycle
private volatile int maxRedirects = 8;
private volatile SocketAddress bindAddress;
private volatile long idleTimeout;
private volatile boolean tcpNoDelay = true;
private volatile boolean dispatchIO = true;
public HttpClient()
{
@ -140,7 +143,11 @@ public class HttpClient extends ContainerLifeCycle
protected void doStart() throws Exception
{
if (sslContextFactory != null)
{
addBean(sslContextFactory);
// Avoid to double dispatch when using SSL
setDispatchIO(false);
}
if (executor == null)
executor = new QueuedThreadPool();
@ -151,12 +158,13 @@ public class HttpClient extends ContainerLifeCycle
addBean(byteBufferPool);
if (scheduler == null)
scheduler = new TimerScheduler();
scheduler = new TimerScheduler(HttpClient.class.getSimpleName() + "@" + hashCode() + "-Scheduler");
addBean(scheduler);
selectorManager = newSelectorManager();
addBean(selectorManager);
handlers.add(new ContinueProtocolHandler(this));
handlers.add(new RedirectProtocolHandler(this));
handlers.add(new AuthenticationProtocolHandler(this));
@ -169,7 +177,7 @@ public class HttpClient extends ContainerLifeCycle
protected SelectorManager newSelectorManager()
{
return new ClientSelectorManager();
return new ClientSelectorManager(getExecutor(), getScheduler());
}
@Override
@ -314,7 +322,7 @@ public class HttpClient extends ContainerLifeCycle
SocketAddress bindAddress = getBindAddress();
if (bindAddress != null)
channel.bind(bindAddress);
channel.socket().setTcpNoDelay(true);
configure(channel);
channel.configureBlocking(false);
channel.connect(new InetSocketAddress(destination.host(), destination.port()));
@ -329,6 +337,11 @@ public class HttpClient extends ContainerLifeCycle
}
}
protected void configure(SocketChannel channel) throws SocketException
{
channel.socket().setTcpNoDelay(isTCPNoDelay());
}
private void close(SocketChannel channel)
{
try
@ -341,9 +354,8 @@ public class HttpClient extends ContainerLifeCycle
}
}
protected HttpConversation getConversation(Request request)
protected HttpConversation getConversation(long id)
{
long id = request.id();
HttpConversation conversation = conversations.get(id);
if (conversation == null)
{
@ -363,13 +375,17 @@ public class HttpClient extends ContainerLifeCycle
LOG.debug("{} removed", conversation);
}
// TODO: find a better method name
protected Response.Listener lookup(Request request, Response response)
protected List<ProtocolHandler> getProtocolHandlers()
{
for (ProtocolHandler handler : handlers)
return handlers;
}
protected ProtocolHandler findProtocolHandler(Request request, Response response)
{
for (ProtocolHandler handler : getProtocolHandlers())
{
if (handler.accept(request, response))
return handler.getResponseListener();
return handler;
}
return null;
}
@ -507,6 +523,42 @@ public class HttpClient extends ContainerLifeCycle
this.maxRedirects = maxRedirects;
}
public boolean isTCPNoDelay()
{
return tcpNoDelay;
}
public void setTCPNoDelay(boolean tcpNoDelay)
{
this.tcpNoDelay = tcpNoDelay;
}
/**
* @return true to dispatch I/O operations in a different thread, false to execute them in the selector thread
* @see #setDispatchIO(boolean)
*/
public boolean isDispatchIO()
{
return dispatchIO;
}
/**
* Whether to dispatch I/O operations from the selector thread to a different thread.
* <p />
* This implementation never blocks on I/O operation, but invokes application callbacks that may
* take time to execute or block on other I/O.
* If application callbacks are known to take time or block on I/O, then parameter {@code dispatchIO}
* must be set to true.
* If application callbacks are known to be quick and never block on I/O, then parameter {@code dispatchIO}
* may be set to false.
*
* @param dispatchIO true to dispatch I/O operations in a different thread, false to execute them in the selector thread
*/
public void setDispatchIO(boolean dispatchIO)
{
this.dispatchIO = dispatchIO;
}
@Override
public void dump(Appendable out, String indent) throws IOException
{
@ -516,20 +568,20 @@ public class HttpClient extends ContainerLifeCycle
protected class ClientSelectorManager extends SelectorManager
{
public ClientSelectorManager()
public ClientSelectorManager(Executor executor, Scheduler scheduler)
{
this(1);
this(executor, scheduler, 1);
}
public ClientSelectorManager(int selectors)
public ClientSelectorManager(Executor executor, Scheduler scheduler, int selectors)
{
super(selectors);
super(executor, scheduler, selectors);
}
@Override
protected EndPoint newEndPoint(SocketChannel channel, ManagedSelector selector, SelectionKey key)
{
return new SelectChannelEndPoint(channel, selector, key, scheduler, getIdleTimeout());
return new SelectChannelEndPoint(channel, selector, key, getScheduler(), getIdleTimeout());
}
@Override
@ -574,19 +626,12 @@ public class HttpClient extends ContainerLifeCycle
}
}
@Override
protected void connectionFailed(SocketChannel channel, Throwable ex, Object attachment)
{
ConnectionCallback callback = (ConnectionCallback)attachment;
callback.callback.failed(null, ex);
}
@Override
protected void execute(Runnable task)
{
getExecutor().execute(task);
}
}
private class ConnectionCallback extends FutureCallback<Connection>

View File

@ -54,10 +54,11 @@ public class HttpConnection extends AbstractConnection implements Connection
private final HttpDestination destination;
private final HttpSender sender;
private final HttpReceiver receiver;
private long idleTimeout;
public HttpConnection(HttpClient client, EndPoint endPoint, HttpDestination destination)
{
super(endPoint, client.getExecutor());
super(endPoint, client.getExecutor(), client.isDispatchIO());
this.client = client;
this.destination = destination;
this.sender = new HttpSender(this);
@ -104,11 +105,18 @@ public class HttpConnection extends AbstractConnection implements Connection
public void send(Request request, Response.Listener listener)
{
normalizeRequest(request);
HttpConversation conversation = client.getConversation(request);
// Save the old idle timeout to restore it
EndPoint endPoint = getEndPoint();
idleTimeout = endPoint.getIdleTimeout();
endPoint.setIdleTimeout(request.idleTimeout());
HttpConversation conversation = client.getConversation(request.conversation());
HttpExchange exchange = new HttpExchange(conversation, this, request, listener);
setExchange(exchange);
conversation.exchanges().offer(exchange);
conversation.listener(listener);
sender.send(exchange);
}
@ -292,6 +300,9 @@ public class HttpConnection extends AbstractConnection implements Connection
HttpExchange existing = this.exchange.getAndSet(null);
if (existing == exchange)
{
// Restore idle timeout
getEndPoint().setIdleTimeout(idleTimeout);
LOG.debug("{} disassociated from {}", exchange, this);
if (success)
{
@ -337,6 +348,11 @@ public class HttpConnection extends AbstractConnection implements Connection
receiver.fail(new HttpResponseException("Response aborted", response));
}
public void proceed(boolean proceed)
{
sender.proceed(proceed);
}
@Override
public void close()
{

View File

@ -39,6 +39,12 @@ public class HttpContentResponse implements ContentResponse
this.encoding = encoding;
}
@Override
public long conversation()
{
return response.conversation();
}
@Override
public Listener listener()
{
@ -94,4 +100,15 @@ public class HttpContentResponse implements ContentResponse
throw new UnsupportedCharsetException(encoding);
}
}
@Override
public String toString()
{
return String.format("%s[%s %d %s - %d bytes]",
HttpContentResponse.class.getSimpleName(),
version(),
status(),
reason(),
content().length);
}
}

View File

@ -63,16 +63,25 @@ public class HttpConversation implements Attributes
this.listener = listener;
}
/**
* @return the exchange that has been identified as the last of this conversation
* @see #last(HttpExchange)
*/
public HttpExchange last()
{
return last;
}
/**
* Remembers the given {@code exchange} as the last of this conversation.
*
* @param exchange the exchange that is the last of this conversation
* @see #last()
*/
public void last(HttpExchange exchange)
{
if (last == null)
last = exchange;
last = exchange;
}
public void complete()

View File

@ -46,7 +46,6 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable
private static final Logger LOG = Log.getLogger(HttpDestination.class);
private final AtomicInteger connectionCount = new AtomicInteger();
private final ResponseNotifier responseNotifier = new ResponseNotifier();
private final HttpClient client;
private final String scheme;
private final String host;
@ -55,6 +54,7 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable
private final BlockingQueue<Connection> idleConnections;
private final BlockingQueue<Connection> activeConnections;
private final RequestNotifier requestNotifier;
private final ResponseNotifier responseNotifier;
public HttpDestination(HttpClient client, String scheme, String host, int port)
{
@ -66,6 +66,7 @@ public class HttpDestination implements Destination, AutoCloseable, Dumpable
this.idleConnections = new ArrayBlockingQueue<>(client.getMaxConnectionsPerAddress());
this.activeConnections = new ArrayBlockingQueue<>(client.getMaxConnectionsPerAddress());
this.requestNotifier = new RequestNotifier(client);
this.responseNotifier = new ResponseNotifier(client);
}
protected BlockingQueue<Connection> getIdleConnections()

View File

@ -58,6 +58,11 @@ public class HttpExchange
return request;
}
public Throwable requestFailure()
{
return requestFailure;
}
public Response.Listener listener()
{
return listener;
@ -68,6 +73,11 @@ public class HttpExchange
return response;
}
public Throwable responseFailure()
{
return responseFailure;
}
public void receive()
{
connection.receive();
@ -84,9 +94,17 @@ public class HttpExchange
public Result responseComplete(Throwable failure)
{
this.responseFailure = failure;
int responseSuccess = 0b1100;
int responseFailure = 0b0100;
return complete(failure == null ? responseSuccess : responseFailure);
if (failure == null)
{
int responseSuccess = 0b1100;
return complete(responseSuccess);
}
else
{
proceed(false);
int responseFailure = 0b0100;
return complete(responseFailure);
}
}
/**
@ -117,7 +135,7 @@ public class HttpExchange
if (this == conversation.last())
conversation.complete();
connection.complete(this, success);
return new Result(request, requestFailure, response, responseFailure);
return new Result(request(), requestFailure(), response(), responseFailure());
}
return null;
}
@ -128,6 +146,19 @@ public class HttpExchange
connection.abort(response);
}
public void resetResponse(boolean success)
{
int responseSuccess = 0b1100;
int responseFailure = 0b0100;
int code = success ? responseSuccess : responseFailure;
complete.addAndGet(-code);
}
public void proceed(boolean proceed)
{
connection.proceed(proceed);
}
@Override
public String toString()
{

View File

@ -42,14 +42,15 @@ public class HttpReceiver implements HttpParser.ResponseHandler<ByteBuffer>
private static final Logger LOG = Log.getLogger(HttpReceiver.class);
private final HttpParser parser = new HttpParser(this);
private final ResponseNotifier notifier = new ResponseNotifier();
private final HttpConnection connection;
private volatile boolean failed;
private volatile ContentDecoder decoder;
private final ResponseNotifier notifier;
private ContentDecoder decoder;
private State state = State.IDLE;
public HttpReceiver(HttpConnection connection)
{
this.connection = connection;
this.notifier = new ResponseNotifier(connection.getHttpClient());
}
public void receive()
@ -76,8 +77,9 @@ public class HttpReceiver implements HttpParser.ResponseHandler<ByteBuffer>
}
else
{
// Shutting down the parser may invoke messageComplete()
if (!parser.shutdownInput())
// Shutting down the parser may invoke messageComplete() or fail()
parser.shutdownInput();
if (state == State.IDLE || state == State.RECEIVE)
fail(new EOFException());
break;
}
@ -102,6 +104,8 @@ public class HttpReceiver implements HttpParser.ResponseHandler<ByteBuffer>
@Override
public boolean startResponse(HttpVersion version, int status, String reason)
{
state = State.RECEIVE;
HttpExchange exchange = connection.getExchange();
HttpConversation conversation = exchange.conversation();
HttpResponse response = exchange.response();
@ -112,7 +116,8 @@ public class HttpReceiver implements HttpParser.ResponseHandler<ByteBuffer>
Response.Listener currentListener = exchange.listener();
Response.Listener initialListener = conversation.exchanges().peekFirst().listener();
HttpClient client = connection.getHttpClient();
Response.Listener handlerListener = client.lookup(exchange.request(), response);
ProtocolHandler protocolHandler = client.findProtocolHandler(exchange.request(), response);
Response.Listener handlerListener = protocolHandler == null ? null : protocolHandler.getResponseListener();
if (handlerListener == null)
{
conversation.last(exchange);
@ -123,6 +128,7 @@ public class HttpReceiver implements HttpParser.ResponseHandler<ByteBuffer>
}
else
{
LOG.debug("Found protocol handler {}", protocolHandler);
if (currentListener == initialListener)
conversation.listener(handlerListener);
else
@ -214,7 +220,7 @@ public class HttpReceiver implements HttpParser.ResponseHandler<ByteBuffer>
{
HttpExchange exchange = connection.getExchange();
// The exchange may be null if it was failed before
if (exchange != null && !failed)
if (exchange != null && state == State.RECEIVE)
success();
return true;
}
@ -222,7 +228,7 @@ public class HttpReceiver implements HttpParser.ResponseHandler<ByteBuffer>
protected void success()
{
parser.reset();
decoder = null;
state = State.SUCCESS;
HttpExchange exchange = connection.getExchange();
HttpResponse response = exchange.response();
@ -233,7 +239,10 @@ public class HttpReceiver implements HttpParser.ResponseHandler<ByteBuffer>
HttpConversation conversation = exchange.conversation();
notifier.notifySuccess(conversation.listener(), response);
if (result != null)
{
notifier.notifyComplete(conversation.listener(), result);
reset();
}
}
protected void fail(Throwable failure)
@ -247,7 +256,7 @@ public class HttpReceiver implements HttpParser.ResponseHandler<ByteBuffer>
return;
parser.close();
failed = true;
state = State.FAILURE;
HttpResponse response = exchange.response();
LOG.debug("Failed {} {}", response, failure);
@ -257,7 +266,10 @@ public class HttpReceiver implements HttpParser.ResponseHandler<ByteBuffer>
HttpConversation conversation = exchange.conversation();
notifier.notifyFailure(conversation.listener(), response, failure);
if (result != null)
{
notifier.notifyComplete(conversation.listener(), result);
reset();
}
}
@Override
@ -281,9 +293,14 @@ public class HttpReceiver implements HttpParser.ResponseHandler<ByteBuffer>
fail(new TimeoutException());
}
private void reset()
{
decoder = null;
state = State.IDLE;
}
private class DoubleResponseListener implements Response.Listener
{
private final ResponseNotifier notifier = new ResponseNotifier();
private final Response.Listener listener1;
private final Response.Listener listener2;
@ -335,4 +352,9 @@ public class HttpReceiver implements HttpParser.ResponseHandler<ByteBuffer>
notifier.notifyComplete(listener2, result);
}
}
private enum State
{
IDLE, RECEIVE, SUCCESS, FAILURE
}
}

View File

@ -101,7 +101,7 @@ public class HttpRequest implements Request
}
@Override
public long id()
public long conversation()
{
return id;
}

View File

@ -77,6 +77,12 @@ public class HttpResponse implements Response
return headers;
}
@Override
public long conversation()
{
return exchange.request().conversation();
}
@Override
public Listener listener()
{

View File

@ -21,6 +21,7 @@ package org.eclipse.jetty.client;
import java.nio.ByteBuffer;
import java.util.Collections;
import java.util.Iterator;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicReference;
@ -28,6 +29,8 @@ import org.eclipse.jetty.client.api.ContentProvider;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Result;
import org.eclipse.jetty.http.HttpGenerator;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpHeaderValue;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.util.BufferUtil;
@ -38,22 +41,22 @@ import org.eclipse.jetty.util.log.Logger;
public class HttpSender
{
private static final Logger LOG = Log.getLogger(HttpSender.class);
private static final String EXPECT_100_ATTRIBUTE = HttpSender.class.getName() + ".expect100";
private final HttpGenerator generator = new HttpGenerator();
private final ResponseNotifier responseNotifier = new ResponseNotifier();
private final HttpConnection connection;
private final RequestNotifier requestNotifier;
private long contentLength;
private Iterator<ByteBuffer> contentChunks;
private ByteBuffer header;
private ByteBuffer chunk;
private volatile boolean committed;
private volatile boolean failed;
private final ResponseNotifier responseNotifier;
private Iterator<ByteBuffer> contentIterator;
private ContentInfo expectedContent;
private boolean committed;
private boolean failed;
public HttpSender(HttpConnection connection)
{
this.connection = connection;
this.requestNotifier = new RequestNotifier(connection.getHttpClient());
this.responseNotifier = new ResponseNotifier(connection.getHttpClient());
}
public void send(HttpExchange exchange)
@ -68,42 +71,70 @@ public class HttpSender
LOG.debug("Sending {}", request);
requestNotifier.notifyBegin(request);
ContentProvider content = request.content();
this.contentLength = content == null ? -1 : content.length();
this.contentChunks = content == null ? Collections.<ByteBuffer>emptyIterator() : content.iterator();
this.contentIterator = content == null ? Collections.<ByteBuffer>emptyIterator() : content.iterator();
send();
}
}
public void proceed(boolean proceed)
{
ContentInfo contentInfo = expectedContent;
if (contentInfo != null)
{
contentInfo.await();
if (proceed)
send();
else
fail(new HttpRequestException("Expectation failed", connection.getExchange().request()));
}
}
private void send()
{
HttpClient client = connection.getHttpClient();
ByteBufferPool bufferPool = client.getByteBufferPool();
ByteBuffer header = null;
ByteBuffer chunk = null;
try
{
HttpClient client = connection.getHttpClient();
EndPoint endPoint = connection.getEndPoint();
HttpExchange exchange = connection.getExchange();
ByteBufferPool byteBufferPool = client.getByteBufferPool();
final Request request = exchange.request();
HttpGenerator.RequestInfo info = null;
ByteBuffer content = contentChunks.hasNext() ? contentChunks.next() : BufferUtil.EMPTY_BUFFER;
boolean lastContent = !contentChunks.hasNext();
HttpConversation conversation = client.getConversation(request.conversation());
HttpGenerator.RequestInfo requestInfo = null;
boolean expect100 = request.headers().contains(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString());
expect100 &= conversation.getAttribute(EXPECT_100_ATTRIBUTE) == null;
if (expect100)
conversation.setAttribute(EXPECT_100_ATTRIBUTE, Boolean.TRUE);
ContentInfo contentInfo = this.expectedContent;
if (contentInfo == null)
contentInfo = new ContentInfo(contentIterator);
else
expect100 = false;
this.expectedContent = null;
while (true)
{
HttpGenerator.Result result = generator.generateRequest(info, header, chunk, content, lastContent);
HttpGenerator.Result result = generator.generateRequest(requestInfo, header, chunk, contentInfo.content, contentInfo.lastContent);
switch (result)
{
case NEED_INFO:
{
info = new HttpGenerator.RequestInfo(request.version(), request.headers(), contentLength, request.method().asString(), request.path());
ContentProvider content = request.content();
long contentLength = content == null ? -1 : content.length();
requestInfo = new HttpGenerator.RequestInfo(request.version(), request.headers(), contentLength, request.method().asString(), request.path());
break;
}
case NEED_HEADER:
{
header = byteBufferPool.acquire(client.getRequestBufferSize(), false);
header = bufferPool.acquire(client.getRequestBufferSize(), false);
break;
}
case NEED_CHUNK:
{
chunk = byteBufferPool.acquire(HttpGenerator.CHUNK_SIZE, false);
chunk = bufferPool.acquire(HttpGenerator.CHUNK_SIZE, false);
break;
}
case FLUSH:
@ -119,9 +150,20 @@ public class HttpSender
@Override
protected void pendingCompleted()
{
LOG.debug("Write completed for {}", request);
if (!committed)
committed(request);
send();
if (expectedContent == null)
{
send();
}
else
{
LOG.debug("Expecting 100 Continue for {}", request);
expectedContent.ready();
}
}
@Override
@ -130,22 +172,37 @@ public class HttpSender
fail(x);
}
};
if (header == null)
header = BufferUtil.EMPTY_BUFFER;
if (chunk == null)
chunk = BufferUtil.EMPTY_BUFFER;
endPoint.write(null, callback, header, chunk, content);
if (expect100)
{
// Save the expected content waiting for the 100 Continue response
expectedContent = contentInfo;
}
write(callback, header, chunk, expect100 ? null : contentInfo.content);
if (callback.pending())
{
LOG.debug("Write pending for {}", request);
return;
}
if (callback.completed())
{
if (!committed)
committed(request);
releaseBuffers();
content = contentChunks.hasNext() ? contentChunks.next() : BufferUtil.EMPTY_BUFFER;
lastContent = !contentChunks.hasNext();
if (expect100)
{
LOG.debug("Expecting 100 Continue for {}", request);
expectedContent.ready();
return;
}
else
{
// Send further content
contentInfo = new ContentInfo(contentIterator);
}
}
}
break;
@ -179,7 +236,49 @@ public class HttpSender
}
finally
{
releaseBuffers();
releaseBuffers(bufferPool, header, chunk);
}
}
private void write(Callback<Void> callback, ByteBuffer header, ByteBuffer chunk, ByteBuffer content)
{
int mask = 0;
if (header != null)
mask += 1;
if (chunk != null)
mask += 2;
if (content != null)
mask += 4;
EndPoint endPoint = connection.getEndPoint();
switch (mask)
{
case 0:
endPoint.write(null, callback, BufferUtil.EMPTY_BUFFER);
break;
case 1:
endPoint.write(null, callback, header);
break;
case 2:
endPoint.write(null, callback, chunk);
break;
case 3:
endPoint.write(null, callback, header, chunk);
break;
case 4:
endPoint.write(null, callback, content);
break;
case 5:
endPoint.write(null, callback, header, content);
break;
case 6:
endPoint.write(null, callback, chunk, content);
break;
case 7:
endPoint.write(null, callback, header, chunk, content);
break;
default:
throw new IllegalStateException();
}
}
@ -216,9 +315,6 @@ public class HttpSender
protected void fail(Throwable failure)
{
// Cleanup first
BufferUtil.clear(header);
BufferUtil.clear(chunk);
releaseBuffers();
generator.abort();
failed = true;
@ -245,19 +341,12 @@ public class HttpSender
}
}
private void releaseBuffers()
private void releaseBuffers(ByteBufferPool bufferPool, ByteBuffer header, ByteBuffer chunk)
{
ByteBufferPool bufferPool = connection.getHttpClient().getByteBufferPool();
if (!BufferUtil.hasContent(header))
{
bufferPool.release(header);
header = null;
}
if (!BufferUtil.hasContent(chunk))
{
bufferPool.release(chunk);
chunk = null;
}
}
private static abstract class StatefulExecutorCallback implements Callback<Void>, Runnable
@ -341,4 +430,34 @@ public class HttpSender
INCOMPLETE, PENDING, COMPLETE, FAILED
}
}
private class ContentInfo
{
private final CountDownLatch latch = new CountDownLatch(1);
public final boolean lastContent;
public final ByteBuffer content;
public ContentInfo(Iterator<ByteBuffer> contentIterator)
{
lastContent = !contentIterator.hasNext();
content = lastContent ? BufferUtil.EMPTY_BUFFER : contentIterator.next();
}
public void ready()
{
latch.countDown();
}
public void await()
{
try
{
latch.await();
}
catch (InterruptedException x)
{
throw new IllegalStateException(x);
}
}
}
}

View File

@ -28,12 +28,13 @@ public class RedirectProtocolHandler extends Response.Listener.Empty implements
{
private static final String ATTRIBUTE = RedirectProtocolHandler.class.getName() + ".redirect";
private final ResponseNotifier notifier = new ResponseNotifier();
private final HttpClient client;
private final ResponseNotifier notifier;
public RedirectProtocolHandler(HttpClient client)
{
this.client = client;
this.notifier = new ResponseNotifier(client);
}
@Override
@ -104,7 +105,7 @@ public class RedirectProtocolHandler extends Response.Listener.Empty implements
private void redirect(Result result, HttpMethod method, String location)
{
Request request = result.getRequest();
HttpConversation conversation = client.getConversation(request);
HttpConversation conversation = client.getConversation(request.conversation());
Integer redirects = (Integer)conversation.getAttribute(ATTRIBUTE);
if (redirects == null)
redirects = 0;
@ -114,7 +115,7 @@ public class RedirectProtocolHandler extends Response.Listener.Empty implements
++redirects;
conversation.setAttribute(ATTRIBUTE, redirects);
Request redirect = client.newRequest(request.id(), location);
Request redirect = client.newRequest(request.conversation(), location);
// Use given method
redirect.method(method);
@ -140,7 +141,7 @@ public class RedirectProtocolHandler extends Response.Listener.Empty implements
{
Request request = result.getRequest();
Response response = result.getResponse();
HttpConversation conversation = client.getConversation(request);
HttpConversation conversation = client.getConversation(request.conversation());
Response.Listener listener = conversation.exchanges().peekFirst().listener();
// TODO: should we reply all event, or just the failure ?
notifier.notifyFailure(listener, response, failure);

View File

@ -20,6 +20,8 @@ package org.eclipse.jetty.client;
import java.nio.ByteBuffer;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.client.api.Result;
import org.eclipse.jetty.util.log.Log;
@ -28,6 +30,12 @@ import org.eclipse.jetty.util.log.Logger;
public class ResponseNotifier
{
private static final Logger LOG = Log.getLogger(ResponseNotifier.class);
private final HttpClient client;
public ResponseNotifier(HttpClient client)
{
this.client = client;
}
public void notifyBegin(Response.Listener listener, Response response)
{
@ -106,4 +114,38 @@ public class ResponseNotifier
LOG.info("Exception while notifying listener " + listener, x);
}
}
public void forwardSuccess(Response.Listener listener, Response response)
{
notifyBegin(listener, response);
notifyHeaders(listener, response);
if (response instanceof ContentResponse)
notifyContent(listener, response, ByteBuffer.wrap(((ContentResponse)response).content()));
notifySuccess(listener, response);
}
public void forwardSuccessComplete(Response.Listener listener, Request request, Response response)
{
HttpConversation conversation = client.getConversation(request.conversation());
forwardSuccess(listener, response);
conversation.complete();
notifyComplete(listener, new Result(request, response));
}
public void forwardFailure(Response.Listener listener, Response response, Throwable failure)
{
notifyBegin(listener, response);
notifyHeaders(listener, response);
if (response instanceof ContentResponse)
notifyContent(listener, response, ByteBuffer.wrap(((ContentResponse)response).content()));
notifyFailure(listener, response, failure);
}
public void forwardFailureComplete(Response.Listener listener, Request request, Throwable requestFailure, Response response, Throwable responseFailure)
{
HttpConversation conversation = client.getConversation(request.conversation());
forwardFailure(listener, response, responseFailure);
conversation.complete();
notifyComplete(listener, new Result(request, requestFailure, response, responseFailure));
}
}

View File

@ -44,7 +44,7 @@ public interface Request
/**
* @return the conversation id
*/
long id();
long conversation();
/**
* @return the scheme of this request, such as "http" or "https"

View File

@ -36,6 +36,11 @@ import org.eclipse.jetty.http.HttpVersion;
*/
public interface Response
{
/**
* @return the conversation id
*/
long conversation();
/**
* @return the response listener passed to {@link Request#send(Listener)}
*/

View File

@ -23,16 +23,17 @@ import java.util.Collection;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.NetworkConnector;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.toolchain.test.TestTracker;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.junit.After;
import org.junit.Rule;
import org.junit.rules.TestWatchman;
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.model.FrameworkMethod;
@RunWith(Parameterized.class)
public abstract class AbstractHttpClientServerTest
@ -44,17 +45,7 @@ public abstract class AbstractHttpClientServerTest
}
@Rule
public final TestWatchman testName = new TestWatchman()
{
@Override
public void starting(FrameworkMethod method)
{
super.starting(method);
System.err.printf("Running %s.%s()%n",
method.getMethod().getDeclaringClass().getName(),
method.getName());
}
};
public final TestTracker tracker = new TestTracker();
protected SslContextFactory sslContextFactory;
protected String scheme;

View File

@ -0,0 +1,440 @@
//
// ========================================================================
// Copyright (c) 1995-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.ByteArrayOutputStream;
import java.io.IOException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.client.api.Result;
import org.eclipse.jetty.client.util.BufferingResponseListener;
import org.eclipse.jetty.client.util.BytesContentProvider;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpHeaderValue;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.toolchain.test.annotation.Slow;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.junit.Assert;
import org.junit.Test;
public class HttpClientContinueTest extends AbstractHttpClientServerTest
{
public HttpClientContinueTest(SslContextFactory sslContextFactory)
{
super(sslContextFactory);
}
@Test
public void test_Expect100Continue_WithOneContent_Respond100Continue() throws Exception
{
test_Expect100Continue_Respond100Continue("data1".getBytes("UTF-8"));
}
@Test
public void test_Expect100Continue_WithMultipleContents_Respond100Continue() throws Exception
{
test_Expect100Continue_Respond100Continue("data1".getBytes("UTF-8"), "data2".getBytes("UTF-8"), "data3".getBytes("UTF-8"));
}
private void test_Expect100Continue_Respond100Continue(byte[]... contents) throws Exception
{
start(new AbstractHandler()
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
// Send 100-Continue and copy the content back
IO.copy(request.getInputStream(), response.getOutputStream());
}
});
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
.header(HttpHeader.EXPECT.asString(), HttpHeaderValue.CONTINUE.asString())
.content(new BytesContentProvider(contents))
.send()
.get(5, TimeUnit.SECONDS);
Assert.assertNotNull(response);
Assert.assertEquals(200, response.status());
int index = 0;
byte[] responseContent = response.content();
for (byte[] content : contents)
{
for (byte b : content)
{
Assert.assertEquals(b, responseContent[index++]);
}
}
}
@Test
public void test_Expect100Continue_WithChunkedContent_Respond100Continue() throws Exception
{
start(new AbstractHandler()
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
// Send 100-Continue and copy the content back
ServletInputStream input = request.getInputStream();
// Make sure we chunk the response too
response.flushBuffer();
IO.copy(input, response.getOutputStream());
}
});
byte[] content1 = new byte[10240];
byte[] content2 = new byte[16384];
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
.header(HttpHeader.EXPECT.asString(), HttpHeaderValue.CONTINUE.asString())
.content(new BytesContentProvider(content1, content2)
{
@Override
public long length()
{
return -1;
}
})
.send()
.get(5, TimeUnit.SECONDS);
Assert.assertNotNull(response);
Assert.assertEquals(200, response.status());
int index = 0;
byte[] responseContent = response.content();
for (byte b : content1)
Assert.assertEquals(b, responseContent[index++]);
for (byte b : content2)
Assert.assertEquals(b, responseContent[index++]);
}
@Test
public void test_Expect100Continue_WithContent_Respond417ExpectationFailed() throws Exception
{
test_Expect100Continue_WithContent_RespondError(417);
}
@Test
public void test_Expect100Continue_WithContent_Respond413RequestEntityTooLarge() throws Exception
{
test_Expect100Continue_WithContent_RespondError(413);
}
private void test_Expect100Continue_WithContent_RespondError(final int error) throws Exception
{
start(new AbstractHandler()
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
response.sendError(error);
}
});
byte[] content1 = new byte[10240];
byte[] content2 = new byte[16384];
final CountDownLatch latch = new CountDownLatch(1);
client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
.header(HttpHeader.EXPECT.asString(), HttpHeaderValue.CONTINUE.asString())
.content(new BytesContentProvider(content1, content2))
.send(new BufferingResponseListener()
{
@Override
public void onComplete(Result result)
{
Assert.assertTrue(result.isFailed());
Assert.assertNotNull(result.getRequestFailure());
Assert.assertNull(result.getResponseFailure());
byte[] content = getContent();
Assert.assertNotNull(content);
Assert.assertTrue(content.length > 0);
Assert.assertEquals(error, result.getResponse().status());
latch.countDown();
}
});
Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
}
@Test
public void test_Expect100Continue_WithContent_WithRedirect() throws Exception
{
final String data = "success";
start(new AbstractHandler()
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
if (request.getRequestURI().endsWith("/done"))
{
response.getOutputStream().print(data);
}
else
{
// Send 100-Continue and consume the content
IO.copy(request.getInputStream(), new ByteArrayOutputStream());
// Send a redirect
response.sendRedirect("/done");
}
}
});
byte[] content = new byte[10240];
final CountDownLatch latch = new CountDownLatch(1);
client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
.method(HttpMethod.POST)
.path("/continue")
.header(HttpHeader.EXPECT.asString(), HttpHeaderValue.CONTINUE.asString())
.content(new BytesContentProvider(content))
.send(new BufferingResponseListener()
{
@Override
public void onComplete(Result result)
{
Assert.assertFalse(result.isFailed());
Assert.assertEquals(200, result.getResponse().status());
Assert.assertEquals(data, getContentAsString());
latch.countDown();
}
});
Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
}
@Test
public void test_Redirect_WithExpect100Continue_WithContent() throws Exception
{
// A request with Expect: 100-Continue cannot receive non-final responses like 3xx
final String data = "success";
start(new AbstractHandler()
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
if (request.getRequestURI().endsWith("/done"))
{
// Send 100-Continue and consume the content
IO.copy(request.getInputStream(), new ByteArrayOutputStream());
response.getOutputStream().print(data);
}
else
{
// Send a redirect
response.sendRedirect("/done");
}
}
});
byte[] content = new byte[10240];
final CountDownLatch latch = new CountDownLatch(1);
client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
.method(HttpMethod.POST)
.path("/redirect")
.header(HttpHeader.EXPECT.asString(), HttpHeaderValue.CONTINUE.asString())
.content(new BytesContentProvider(content))
.send(new BufferingResponseListener()
{
@Override
public void onComplete(Result result)
{
Assert.assertTrue(result.isFailed());
Assert.assertNotNull(result.getRequestFailure());
Assert.assertNull(result.getResponseFailure());
Assert.assertEquals(302, result.getResponse().status());
latch.countDown();
}
});
Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
}
@Slow
@Test
public void test_Expect100Continue_WithContent_WithResponseFailure_Before100Continue() throws Exception
{
final long idleTimeout = 1000;
start(new AbstractHandler()
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
try
{
TimeUnit.MILLISECONDS.sleep(2 * idleTimeout);
}
catch (InterruptedException x)
{
throw new ServletException(x);
}
}
});
client.setIdleTimeout(idleTimeout);
byte[] content = new byte[1024];
final CountDownLatch latch = new CountDownLatch(1);
client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
.header(HttpHeader.EXPECT.asString(), HttpHeaderValue.CONTINUE.asString())
.content(new BytesContentProvider(content))
.send(new BufferingResponseListener()
{
@Override
public void onComplete(Result result)
{
Assert.assertTrue(result.isFailed());
Assert.assertNotNull(result.getRequestFailure());
Assert.assertNotNull(result.getResponseFailure());
latch.countDown();
}
});
Assert.assertTrue(latch.await(3 * idleTimeout, TimeUnit.MILLISECONDS));
}
@Slow
@Test
public void test_Expect100Continue_WithContent_WithResponseFailure_After100Continue() throws Exception
{
final long idleTimeout = 1000;
start(new AbstractHandler()
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
// Send 100-Continue and consume the content
IO.copy(request.getInputStream(), new ByteArrayOutputStream());
try
{
TimeUnit.MILLISECONDS.sleep(2 * idleTimeout);
}
catch (InterruptedException x)
{
throw new ServletException(x);
}
}
});
client.setIdleTimeout(idleTimeout);
byte[] content = new byte[1024];
final CountDownLatch latch = new CountDownLatch(1);
client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
.header(HttpHeader.EXPECT.asString(), HttpHeaderValue.CONTINUE.asString())
.content(new BytesContentProvider(content))
.send(new BufferingResponseListener()
{
@Override
public void onComplete(Result result)
{
Assert.assertTrue(result.isFailed());
Assert.assertNull(result.getRequestFailure());
Assert.assertNotNull(result.getResponseFailure());
latch.countDown();
}
});
Assert.assertTrue(latch.await(3 * idleTimeout, TimeUnit.MILLISECONDS));
}
@Test
public void test_Expect100Continue_WithContent_WithResponseFailure_During100Continue() throws Exception
{
start(new AbstractHandler()
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
// Send 100-Continue and consume the content
IO.copy(request.getInputStream(), new ByteArrayOutputStream());
}
});
client.getProtocolHandlers().clear();
client.getProtocolHandlers().add(new ContinueProtocolHandler(client)
{
@Override
public Response.Listener getResponseListener()
{
final Response.Listener listener = super.getResponseListener();
return new Response.Listener.Empty()
{
@Override
public void onBegin(Response response)
{
response.abort();
}
@Override
public void onFailure(Response response, Throwable failure)
{
listener.onFailure(response, failure);
}
};
}
});
byte[] content = new byte[1024];
final CountDownLatch latch = new CountDownLatch(1);
client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
.header(HttpHeader.EXPECT.asString(), HttpHeaderValue.CONTINUE.asString())
.content(new BytesContentProvider(content))
.send(new BufferingResponseListener()
{
@Override
public void onComplete(Result result)
{
Assert.assertTrue(result.isFailed());
Assert.assertNotNull(result.getRequestFailure());
Assert.assertNotNull(result.getResponseFailure());
latch.countDown();
}
});
Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
}
}

View File

@ -65,6 +65,7 @@ public class HttpClientLoadTest extends AbstractHttpClientServerTest
client.setMaxConnectionsPerAddress(32768);
client.setMaxQueueSizePerAddress(1024 * 1024);
client.setDispatchIO(false);
Random random = new Random();
int iterations = 200;

View File

@ -29,7 +29,9 @@ import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicLong;
import java.util.zip.GZIPOutputStream;
import javax.servlet.ServletException;
@ -573,4 +575,50 @@ public class HttpClientTest extends AbstractHttpClientServerTest
Assert.assertEquals(200, response.status());
Assert.assertArrayEquals(data, response.content());
}
@Slow
@Test
public void test_Request_IdleTimeout() throws Exception
{
final long idleTimeout = 1000;
start(new AbstractHandler()
{
@Override
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
try
{
baseRequest.setHandled(true);
TimeUnit.MILLISECONDS.sleep(2 * idleTimeout);
}
catch (InterruptedException x)
{
throw new ServletException(x);
}
}
});
final String host = "localhost";
final int port = connector.getLocalPort();
try
{
client.newRequest(host, port)
.scheme(scheme)
.idleTimeout(idleTimeout)
.send().get(3 * idleTimeout, TimeUnit.MILLISECONDS);
Assert.fail();
}
catch (ExecutionException expected)
{
Assert.assertTrue(expected.getCause() instanceof TimeoutException);
}
// Make another request without specifying the idle timeout, should not fail
ContentResponse response = client.newRequest(host, port)
.scheme(scheme)
.send().get(3 * idleTimeout, TimeUnit.MILLISECONDS);
Assert.assertNotNull(response);
Assert.assertEquals(200, response.status());
}
}

View File

@ -34,6 +34,7 @@ import org.eclipse.jetty.client.api.Result;
import org.eclipse.jetty.client.util.ByteBufferContentProvider;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.toolchain.test.annotation.Slow;
import org.eclipse.jetty.util.log.StdErrLog;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.junit.Assert;
@ -212,6 +213,79 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest
Assert.assertEquals(0, activeConnections.size());
}
@Slow
@Test
public void test_BadRequest_WithSlowRequest_RemovesConnection() throws Exception
{
start(new EmptyServerHandler());
String host = "localhost";
int port = connector.getLocalPort();
HttpDestination destination = (HttpDestination)client.getDestination(scheme, host, port);
final BlockingQueue<Connection> idleConnections = destination.getIdleConnections();
Assert.assertEquals(0, idleConnections.size());
final BlockingQueue<Connection> activeConnections = destination.getActiveConnections();
Assert.assertEquals(0, activeConnections.size());
final long delay = 1000;
final CountDownLatch successLatch = new CountDownLatch(3);
client.newRequest(host, port)
.scheme(scheme)
.listener(new Request.Listener.Empty()
{
@Override
public void onBegin(Request request)
{
// Remove the host header, this will make the request invalid
request.header(HttpHeader.HOST.asString(), null);
}
@Override
public void onHeaders(Request request)
{
try
{
TimeUnit.MILLISECONDS.sleep(delay);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
@Override
public void onSuccess(Request request)
{
successLatch.countDown();
}
})
.send(new Response.Listener.Empty()
{
@Override
public void onSuccess(Response response)
{
Assert.assertEquals(400, response.status());
// 400 response also come with a Connection: close,
// so the connection is closed and removed
successLatch.countDown();
}
@Override
public void onComplete(Result result)
{
Assert.assertFalse(result.isFailed());
successLatch.countDown();
}
});
Assert.assertTrue(successLatch.await(delay * 5, TimeUnit.MILLISECONDS));
Assert.assertEquals(0, idleConnections.size());
Assert.assertEquals(0, activeConnections.size());
}
@Test
public void test_ConnectionFailure_RemovesConnection() throws Exception
{

View File

@ -20,6 +20,7 @@ package org.eclipse.jetty.client;
import java.io.ByteArrayOutputStream;
import java.io.EOFException;
import java.net.URI;
import java.nio.ByteBuffer;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
@ -67,7 +68,8 @@ public class HttpReceiverTest
protected HttpExchange newExchange(Response.Listener listener)
{
HttpExchange exchange = new HttpExchange(conversation, connection, null, listener);
HttpRequest request = new HttpRequest(client, URI.create("http://localhost"));
HttpExchange exchange = new HttpExchange(conversation, connection, request, listener);
conversation.exchanges().offer(exchange);
connection.setExchange(exchange);
exchange.requestComplete(null);

View File

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

View File

@ -198,15 +198,26 @@ public class HttpGenerator
else
generateHeaders(info,header,content,last);
// handle the content.
int len = BufferUtil.length(content);
if (len>0)
boolean expect100 = info.getHttpFields().contains(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString());
if (expect100)
{
_contentPrepared+=len;
if (isChunking())
prepareChunk(header,len);
_state = State.COMMITTED;
}
_state = last?State.COMPLETING:State.COMMITTED;
else
{
// handle the content.
int len = BufferUtil.length(content);
if (len>0)
{
_contentPrepared+=len;
if (isChunking())
prepareChunk(header,len);
}
_state = last?State.COMPLETING:State.COMMITTED;
}
return Result.FLUSH;
}
catch(Exception e)
{
@ -217,8 +228,6 @@ public class HttpGenerator
{
BufferUtil.flipToFlush(header,pos);
}
return Result.FLUSH;
}
case COMMITTED:

View File

@ -46,21 +46,30 @@ public abstract class AbstractConnection implements Connection
private final EndPoint _endPoint;
private final Executor _executor;
private final Callback<Void> _readCallback;
private int _inputBufferSize=8192;
private int _inputBufferSize=2048;
public AbstractConnection(EndPoint endp, Executor executor)
{
this(endp, executor, true);
this(endp,executor,true);
}
public AbstractConnection(EndPoint endp, Executor executor, final boolean dispatchCompletion)
public AbstractConnection(EndPoint endp, Executor executor, final boolean executeOnfillable)
{
if (executor == null)
throw new IllegalArgumentException("Executor must not be null!");
_endPoint = endp;
_executor = executor;
_readCallback = new ExecutorCallback<Void>(executor)
_readCallback = new ExecutorCallback<Void>(executor,0)
{
@Override
public void completed(Void context)
{
if (executeOnfillable)
super.completed(context);
else
onCompleted(context);
}
@Override
protected void onCompleted(Void context)
{
@ -107,16 +116,10 @@ public abstract class AbstractConnection implements Connection
onFillInterestedFailed(x);
}
@Override
protected boolean alwaysDispatchCompletion()
{
return dispatchCompletion;
}
@Override
public String toString()
{
return String.format("AC.ReadCB@%x", AbstractConnection.this.hashCode());
return String.format("AC.ExReadCB@%x", AbstractConnection.this.hashCode());
}
};
}
@ -137,11 +140,11 @@ public abstract class AbstractConnection implements Connection
_inputBufferSize = inputBufferSize;
}
public Executor getExecutor()
protected Executor getExecutor()
{
return _executor;
}
/**
* <p>Utility method to be called to register read interest.</p>
* <p>After a call to this method, {@link #onFillable()} or {@link #onFillInterestedFailed(Throwable)}
@ -186,7 +189,7 @@ public abstract class AbstractConnection implements Connection
* <p>Callback method invoked when the endpoint failed to be ready to be read.</p>
* @param cause the exception that caused the failure
*/
public void onFillInterestedFailed(Throwable cause)
protected void onFillInterestedFailed(Throwable cause)
{
LOG.debug("{} onFillInterestedFailed {}", this, cause);
if (_endPoint.isOpen())

View File

@ -202,11 +202,12 @@ public abstract class AbstractEndPoint implements EndPoint
{
LOG.debug("{} idle timeout expired", this);
boolean output_shutdown=isOutputShutdown();
TimeoutException timeout = new TimeoutException("Idle timeout expired: " + idleElapsed + "/" + idleTimeout + " ms");
_fillInterest.onFail(timeout);
_writeFlusher.onFail(timeout);
if (isOutputShutdown())
if (output_shutdown)
close();
notIdle();
}

View File

@ -75,11 +75,14 @@ public class ArrayByteBufferPool implements ByteBufferPool
@Override
public void release(ByteBuffer buffer)
{
Bucket bucket = bucketFor(buffer.capacity(),buffer.isDirect());
if (bucket!=null)
{
BufferUtil.clear(buffer);
bucket._queue.offer(buffer);
if (buffer!=null)
{
Bucket bucket = bucketFor(buffer.capacity(),buffer.isDirect());
if (bucket!=null)
{
BufferUtil.clear(buffer);
bucket._queue.offer(buffer);
}
}
}

View File

@ -0,0 +1,511 @@
//
// ========================================================================
// Copyright (c) 1995-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.io;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
import java.net.SocketException;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.annotation.Name;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
/**
* TODO this class is still experimental
*/
public class FilterConnection extends AbstractConnection
{
private static final Logger LOG = Log.getLogger(FilterConnection.class);
private static final boolean DEBUG = LOG.isDebugEnabled(); // Easy for the compiler to remove the code if DEBUG==false
public interface Filter
{
/**
* <p>Callback method invoked when a connection from a remote client has been accepted.</p>
* <p>The {@code socket} parameter can be used to extract socket address information of
* the remote client.</p>
*
* @param endpoint the socket associated with the remote client
*/
public void opened(EndPoint endpoint);
/**
* <p>Callback method invoked when bytes sent by a remote client arrived on the server.</p>
*
* @param endPoint the socket associated with the remote client
* @param bytes the read-only buffer containing the incoming bytes
*/
public void incoming(EndPoint endPoint, ByteBuffer bytes);
/**
* <p>Callback method invoked when bytes are sent to a remote client from the server.</p>
* <p>This method is invoked after the bytes have been actually written to the remote client.</p>
*
* @param endPoint the socket associated with the remote client
* @param bytes the read-only buffer containing the outgoing bytes
*/
public void outgoing(EndPoint endPoint, ByteBuffer bytes);
/**
* <p>Callback method invoked when a connection to a remote client has been closed.</p>
* <p>The {@code socket} parameter is already closed when this method is called, so it
* cannot be queried for socket address information of the remote client.<br />
* However, the {@code socket} parameter is the same object passed to {@link #opened(Socket)},
* so it is possible to map socket information in {@link #opened(Socket)} and retrieve it
* in this method.
*
* @param endpoint the (closed) socket associated with the remote client
*/
public void closed(EndPoint endpoint);
}
public static class DebugFilter implements Filter
{
public DebugFilter()
{
}
@Override
public void opened(EndPoint endpoint)
{
if (DEBUG)
LOG.debug("{}@{} opened%n",endpoint.getClass().getSimpleName(),Integer.toString(endpoint.hashCode(),16));
}
@Override
public void incoming(EndPoint endpoint, ByteBuffer bytes)
{
if (DEBUG)
LOG.debug("{}@{} >>> {}%n",endpoint.getClass().getSimpleName(),Integer.toString(endpoint.hashCode(),16),BufferUtil.toDetailString(bytes));
}
@Override
public void outgoing(EndPoint endpoint, ByteBuffer bytes)
{
if (DEBUG)
LOG.debug("{}@{} <<< {}%n",endpoint.getClass().getSimpleName(),Integer.toString(endpoint.hashCode(),16),BufferUtil.toDetailString(bytes));
}
@Override
public void closed(EndPoint endpoint)
{
if (DEBUG)
LOG.debug("{}@{} closed%n",endpoint.getClass().getSimpleName(),Integer.toString(endpoint.hashCode(),16));
}
}
public static class DumpToFileFilter implements Filter
{
final ConcurrentHashMap<EndPoint,OutputStream> _in = new ConcurrentHashMap<>();
final ConcurrentHashMap<EndPoint,OutputStream> _out = new ConcurrentHashMap<>();
final File _directory;
final String _prefix;
final boolean _deleteOnExit;
public DumpToFileFilter()
{
this(new File(System.getProperty("java.io.tmpdir")+File.separator+"FilterConnection"),true);
}
public DumpToFileFilter(File directory, boolean deleteOnExit)
{
this(directory,"dump-",deleteOnExit);
}
public DumpToFileFilter(String prefix)
{
this(new File(System.getProperty("java.io.tmpdir")+File.separator+"FilterConnection"),prefix,true);
}
public DumpToFileFilter(
@Name("directory") File directory,
@Name("prefix") String prefix,
@Name("deleteOnExit") boolean deleteOnExit)
{
_directory=directory;
_prefix=prefix;
_deleteOnExit=deleteOnExit;
if (!_directory.exists() && !_directory.mkdirs())
throw new IllegalArgumentException("cannot create "+directory);
if (!_directory.isDirectory())
throw new IllegalArgumentException("not directory "+directory);
if (!_directory.canWrite())
throw new IllegalArgumentException("cannot write "+directory);
}
@Override
public void opened(EndPoint endpoint)
{
try
{
File in = new File(_directory,_prefix+Integer.toHexString(endpoint.hashCode())+".in");
File out = new File(_directory,_prefix+Integer.toHexString(endpoint.hashCode())+".out");
if (_deleteOnExit)
{
in.deleteOnExit();
out.deleteOnExit();
}
_in.put(endpoint,new FileOutputStream(in));
_out.put(endpoint,new FileOutputStream(out));
}
catch (FileNotFoundException e)
{
LOG.warn(e);
}
}
@Override
public void incoming(EndPoint endpoint, ByteBuffer bytes)
{
try
{
OutputStream out=_in.get(endpoint);
if (out!=null)
out.write(BufferUtil.toArray(bytes));
}
catch(IOException e)
{
LOG.warn(e);
}
}
@Override
public void outgoing(EndPoint endpoint, ByteBuffer bytes)
{
try
{
OutputStream out=_out.get(endpoint);
if (out!=null)
out.write(BufferUtil.toArray(bytes));
}
catch(IOException e)
{
LOG.warn(e);
}
}
@Override
public void closed(EndPoint endpoint)
{
try
{
OutputStream out=_in.remove(endpoint);
if (out!=null)
out.close();
}
catch(IOException e)
{
LOG.warn(e);
}
try
{
OutputStream out=_out.remove(endpoint);
if (out!=null)
out.close();
}
catch(IOException e)
{
LOG.warn(e);
}
}
}
private final ByteBufferPool _bufferPool;
private final FilteredEndPoint _filterEndPoint;
private final int _outputBufferSize;
private final List<Filter> _filters = new CopyOnWriteArrayList<>();
public FilterConnection(ByteBufferPool byteBufferPool, Executor executor, EndPoint endPoint, int outputBufferSize)
{
super(endPoint, executor, false);
_bufferPool = byteBufferPool;
_filterEndPoint = newFilterEndPoint();
_outputBufferSize=outputBufferSize;
}
protected FilteredEndPoint newFilterEndPoint()
{
return new FilteredEndPoint();
}
public FilteredEndPoint getFilterEndPoint()
{
return _filterEndPoint;
}
public void addFilter(Filter filter)
{
_filters.add(filter);
}
public boolean removeFilter(Filter listener)
{
return _filters.remove(listener);
}
@Override
public void onOpen()
{
super.onOpen();
for (Filter filter: _filters)
filter.opened(getEndPoint());
getFilterEndPoint().getConnection().onOpen();
}
@Override
public void onClose()
{
for (Filter filter: _filters)
filter.closed(getEndPoint());
_filterEndPoint.getConnection().onClose();
super.onClose();
}
@Override
public int getMessagesIn()
{
return _filterEndPoint.getConnection().getMessagesIn();
}
@Override
public int getMessagesOut()
{
return _filterEndPoint.getConnection().getMessagesOut();
}
@Override
public void close()
{
getFilterEndPoint().getConnection().close();
}
/* ------------------------------------------------------------ */
@Override
public void onFillable()
{
if (DEBUG)
LOG.debug("onFillable enter {}", getEndPoint());
// wake up whoever is doing the fill or the flush so they can
// do all the filling, unwrapping, wrapping and flushing
_filterEndPoint.getFillInterest().fillable();
if (DEBUG)
LOG.debug("onFillable exit {}", getEndPoint());
}
/* ------------------------------------------------------------ */
@Override
public void onFillInterestedFailed(Throwable cause)
{
_filterEndPoint.getFillInterest().onFail(cause);
}
/* ------------------------------------------------------------ */
@Override
public String toString()
{
return String.format("%s@%x -> %s",
FilterConnection.class.getSimpleName(),
hashCode(),
_filterEndPoint.getConnection());
}
/* ------------------------------------------------------------ */
public class FilteredEndPoint extends AbstractEndPoint
{
private final Callback<Void> _writeCB = new Callback<Void>()
{
@Override
public void completed(Void context)
{
if (BufferUtil.isEmpty(_outBuffer))
{
_bufferPool.release(_outBuffer);
_outBuffer=null;
}
getWriteFlusher().completeWrite();
}
@Override
public void failed(Void context, Throwable x)
{
if (BufferUtil.isEmpty(_outBuffer))
{
_bufferPool.release(_outBuffer);
_outBuffer=null;
}
getWriteFlusher().onFail(x);
}
};
private ByteBuffer _outBuffer;
public FilteredEndPoint()
{
super(null,getEndPoint().getLocalAddress(), getEndPoint().getRemoteAddress());
}
@Override
protected void onIncompleteFlush()
{
if (BufferUtil.isEmpty(_outBuffer))
{
_bufferPool.release(_outBuffer);
_outBuffer=null;
getWriteFlusher().completeWrite();
}
else
getEndPoint().write(null,_writeCB,_outBuffer);
}
@Override
protected boolean needsFill() throws IOException
{
FilterConnection.this.fillInterested();
return false;
}
@Override
public synchronized int fill(ByteBuffer buffer) throws IOException
{
if (DEBUG)
LOG.debug("{} fill enter", FilterConnection.this);
int orig=buffer.remaining();
int filled = getEndPoint().fill(buffer);
if (orig>0)
buffer.position(buffer.position()+orig);
for (Filter filter: _filters)
filter.incoming(getEndPoint() ,buffer);
if (orig>0)
buffer.position(buffer.position()-orig);
if (DEBUG)
LOG.debug("{} fill {} exit", FilterConnection.this,filled);
return filled;
}
@Override
public synchronized boolean flush(ByteBuffer... buffers) throws IOException
{
if (DEBUG)
LOG.debug("{} flush enter {}", FilterConnection.this, Arrays.toString(buffers));
if (BufferUtil.hasContent(_outBuffer))
return false;
if (_outBuffer==null)
_outBuffer=_bufferPool.acquire(_outputBufferSize,true);
// Take as much data as we can
boolean all_taken=true;
for (ByteBuffer buffer : buffers)
{
if (buffer==null)
continue;
BufferUtil.flipPutFlip(buffer,_outBuffer);
if (BufferUtil.hasContent(buffer))
{
all_taken=false;
break;
}
}
for (Filter filter: _filters)
filter.outgoing(getEndPoint() ,_outBuffer);
boolean flushed = getEndPoint().flush(_outBuffer);
if (BufferUtil.isEmpty(_outBuffer))
{
_bufferPool.release(_outBuffer);
_outBuffer=null;
}
if (DEBUG)
LOG.debug("{} flush exit, consumed {}", FilterConnection.this, flushed);
return all_taken && flushed;
}
@Override
public void shutdownOutput()
{
getEndPoint().shutdownOutput();
}
@Override
public boolean isOutputShutdown()
{
return getEndPoint().isOutputShutdown();
}
@Override
public void close()
{
getEndPoint().close();
}
@Override
public boolean isOpen()
{
return getEndPoint().isOpen();
}
@Override
public Object getTransport()
{
return getEndPoint();
}
@Override
public boolean isInputShutdown()
{
return getEndPoint().isInputShutdown();
}
public EndPoint getWrappedEndPoint()
{
return getEndPoint();
}
@Override
public String toString()
{
return super.toString()+"->"+getEndPoint().toString();
}
}
}

View File

@ -58,9 +58,8 @@ public class MappedByteBufferPool implements ByteBufferPool
int capacity = bucket * factor;
result = direct ? BufferUtil.allocateDirect(capacity) : BufferUtil.allocate(capacity);
}
else
BufferUtil.clear(result);
BufferUtil.clear(result);
return result;
}

View File

@ -23,6 +23,7 @@ import java.io.IOException;
import java.net.ConnectException;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketTimeoutException;
import java.nio.channels.CancelledKeyException;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
@ -35,16 +36,18 @@ import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.eclipse.jetty.util.ForkInvoker;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.annotation.Name;
import org.eclipse.jetty.util.component.AbstractLifeCycle;
import org.eclipse.jetty.util.component.ContainerLifeCycle;
import org.eclipse.jetty.util.component.Dumpable;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.thread.Scheduler;
/**
* <p>{@link SelectorManager} manages a number of {@link ManagedSelector}s that
@ -56,25 +59,53 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa
{
protected static final Logger LOG = Log.getLogger(SelectorManager.class);
private final Executor executor;
private final Scheduler scheduler;
private final ManagedSelector[] _selectors;
private volatile long _selectorIndex;
private long _connectTimeout = 15000;
private long _selectorIndex;
protected SelectorManager()
protected SelectorManager(Executor executor, Scheduler scheduler)
{
this((Runtime.getRuntime().availableProcessors() + 1) / 2);
this(executor, scheduler, (Runtime.getRuntime().availableProcessors() + 1) / 2);
}
protected SelectorManager(@Name(value="selectors") int selectors)
protected SelectorManager(Executor executor, Scheduler scheduler, int selectors)
{
this.executor = executor;
this.scheduler = scheduler;
_selectors = new ManagedSelector[selectors];
}
public Executor getExecutor()
{
return executor;
}
public Scheduler getScheduler()
{
return scheduler;
}
public long getConnectTimeout()
{
return _connectTimeout;
}
public void setConnectTimeout(long connectTimeout)
{
_connectTimeout = connectTimeout;
}
/**
* Executes the given task in a different thread.
*
* @param task the task to execute
*/
protected abstract void execute(Runnable task);
protected void execute(Runnable task)
{
executor.execute(task);
}
/**
* @return the number of selectors in use
@ -207,6 +238,11 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa
}
}
protected boolean finishConnect(SocketChannel channel) throws IOException
{
return channel.finishConnect();
}
/**
* <p>Callback method invoked when a non-blocking connect cannot be completed.</p>
* <p>By default it just logs with level warning.</p>
@ -416,27 +452,7 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa
}
else if (key.isConnectable())
{
// Complete a connection of a registered channel
SocketChannel channel = (SocketChannel)key.channel();
try
{
boolean connected = channel.finishConnect();
if (connected)
{
key.interestOps(0);
EndPoint endpoint = createEndPoint(channel, key);
key.attach(endpoint);
}
else
{
throw new ConnectException();
}
}
catch (Exception x)
{
connectionFailed(channel, x, attachment);
closeNoExceptions(channel);
}
processConnect(key, (Connect)attachment);
}
else
{
@ -457,6 +473,32 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa
}
}
private void processConnect(SelectionKey key, Connect connect)
{
key.attach(connect.attachment);
SocketChannel channel = (SocketChannel)key.channel();
try
{
boolean connected = finishConnect(channel);
if (connected)
{
connect.timeout.cancel();
key.interestOps(0);
EndPoint endpoint = createEndPoint(channel, key);
key.attach(endpoint);
}
else
{
throw new ConnectException();
}
}
catch (Exception x)
{
connect.failed(x);
closeNoExceptions(channel);
}
}
private void closeNoExceptions(Closeable closeable)
{
try
@ -469,11 +511,6 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa
}
}
public SelectorManager getSelectorManager()
{
return SelectorManager.this;
}
public void wakeup()
{
_selector.wakeup();
@ -653,13 +690,16 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa
private class Connect implements Runnable
{
private final AtomicBoolean failed = new AtomicBoolean();
private final SocketChannel channel;
private final Object attachment;
private final Scheduler.Task timeout;
public Connect(SocketChannel channel, Object attachment)
{
this.channel = channel;
this.attachment = attachment;
this.timeout = scheduler.schedule(new ConnectTimeout(this), getConnectTimeout(), TimeUnit.MILLISECONDS);
}
@Override
@ -667,13 +707,52 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa
{
try
{
channel.register(_selector, SelectionKey.OP_CONNECT, attachment);
channel.register(_selector, SelectionKey.OP_CONNECT, this);
}
catch (ClosedChannelException x)
{
LOG.debug(x);
}
}
protected void failed(Throwable failure)
{
if (failed.compareAndSet(false, true))
connectionFailed(channel, failure, attachment);
}
}
private class ConnectTimeout implements Runnable
{
private final Connect connect;
private ConnectTimeout(Connect connect)
{
this.connect = connect;
}
@Override
public void run()
{
SocketChannel channel = connect.channel;
if (channel.isConnectionPending())
{
LOG.debug("Channel {} timed out while connecting, closing it", channel);
try
{
// This will unregister the channel from the selector
channel.close();
}
catch (IOException x)
{
LOG.ignore(x);
}
finally
{
connect.failed(new SocketTimeoutException());
}
}
}
}
private class Stop implements Runnable

View File

@ -327,18 +327,19 @@ public class SslConnection extends AbstractConnection
// TODO does this need idle timeouts
super(null,getEndPoint().getLocalAddress(), getEndPoint().getRemoteAddress());
}
public EndPoint getEncryptedEndPoint()
{
return getEndPoint();
}
@Override
protected FillInterest getFillInterest()
{
return super.getFillInterest();
}
@Override
public void setIdleTimeout(long idleTimeout)
{
super.setIdleTimeout(idleTimeout);
getEndPoint().setIdleTimeout(idleTimeout);
}
@Override
protected WriteFlusher getWriteFlusher()

View File

@ -0,0 +1,50 @@
//
// ========================================================================
// Copyright (c) 1995-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.io;
import java.nio.channels.SocketChannel;
import org.junit.Ignore;
// TODO ignore this test as it creates too many /tmp files and is not portable and does not even check the contents!
@Ignore
public class FilteredSelectChannelEndPointTest extends SelectChannelEndPointTest
{
public FilteredSelectChannelEndPointTest()
{
}
@Override
protected Connection newConnection(SocketChannel channel, EndPoint endpoint)
{
FilterConnection filter = new FilterConnection(new MappedByteBufferPool(),_threadPool,endpoint,8192);
filter.addFilter(new FilterConnection.DumpToFileFilter());
Connection connection= super.newConnection(null,filter.getFilterEndPoint());
filter.getFilterEndPoint().setConnection(connection);
return filter;
}
@Override
public void testBlockedReadIdle() throws Exception
{
super.testBlockedReadIdle();
}
}

View File

@ -60,18 +60,12 @@ public class SelectChannelEndPointInterestsTest
connector = ServerSocketChannel.open();
connector.bind(new InetSocketAddress("localhost", 0));
selectorManager = new SelectorManager()
selectorManager = new SelectorManager(threadPool, scheduler)
{
@Override
protected void execute(Runnable task)
{
threadPool.execute(task);
}
@Override
protected EndPoint newEndPoint(SocketChannel channel, ManagedSelector selector, SelectionKey selectionKey) throws IOException
{
return new SelectChannelEndPoint(channel, selector, selectionKey, scheduler, 60000)
return new SelectChannelEndPoint(channel, selector, selectionKey, getScheduler(), 60000)
{
@Override
protected void onIncompleteFlush()
@ -85,7 +79,7 @@ public class SelectChannelEndPointInterestsTest
@Override
public Connection newConnection(SocketChannel channel, final EndPoint endPoint, Object attachment)
{
return new AbstractConnection(endPoint, threadPool)
return new AbstractConnection(endPoint, getExecutor())
{
@Override
public void onOpen()

View File

@ -60,14 +60,8 @@ public class SelectChannelEndPointTest
protected ServerSocketChannel _connector;
protected QueuedThreadPool _threadPool = new QueuedThreadPool();
protected Scheduler _scheduler = new TimerScheduler();
protected SelectorManager _manager = new SelectorManager()
protected SelectorManager _manager = new SelectorManager(_threadPool, _scheduler)
{
@Override
protected void execute(Runnable task)
{
_threadPool.execute(task);
}
@Override
public Connection newConnection(SocketChannel channel, EndPoint endpoint, Object attachment)
{
@ -77,7 +71,7 @@ public class SelectChannelEndPointTest
@Override
protected SelectChannelEndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey selectionKey) throws IOException
{
SelectChannelEndPoint endp = new SelectChannelEndPoint(channel, selectSet, selectionKey, _scheduler, 60000);
SelectChannelEndPoint endp = new SelectChannelEndPoint(channel, selectSet, selectionKey, getScheduler(), 60000);
_lastEndPoint = endp;
_lastEndPointLatch.countDown();
return endp;

View File

@ -0,0 +1,133 @@
//
// ========================================================================
// Copyright (c) 1995-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.io;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.toolchain.test.annotation.Slow;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.util.thread.TimerScheduler;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
public class SelectorManagerTest
{
private QueuedThreadPool executor = new QueuedThreadPool();
private TimerScheduler scheduler = new TimerScheduler();
@Before
public void prepare() throws Exception
{
executor.start();
scheduler.start();
}
@After
public void dispose() throws Exception
{
scheduler.stop();
executor.stop();
}
@Slow
@Test
public void testConnectTimeoutBeforeSuccessfulConnect() throws Exception
{
ServerSocketChannel server = ServerSocketChannel.open();
server.bind(new InetSocketAddress("localhost", 0));
SocketAddress address = server.getLocalAddress();
SocketChannel client = SocketChannel.open();
client.configureBlocking(false);
client.connect(address);
final long connectTimeout = 1000;
SelectorManager selectorManager = new SelectorManager(executor, scheduler)
{
@Override
protected EndPoint newEndPoint(SocketChannel channel, ManagedSelector selector, SelectionKey selectionKey) throws IOException
{
return new SelectChannelEndPoint(channel, selector, selectionKey, getScheduler(), connectTimeout / 2);
}
@Override
protected boolean finishConnect(SocketChannel channel) throws IOException
{
try
{
TimeUnit.MILLISECONDS.sleep(connectTimeout * 2);
return super.finishConnect(channel);
}
catch (InterruptedException e)
{
return false;
}
}
@Override
public Connection newConnection(SocketChannel channel, EndPoint endpoint, Object attachment) throws IOException
{
return new AbstractConnection(endpoint, executor)
{
@Override
public void onFillable()
{
}
};
}
@Override
protected void connectionFailed(SocketChannel channel, Throwable ex, Object attachment)
{
((Callback<Void>)attachment).failed(null, ex);
}
};
selectorManager.setConnectTimeout(connectTimeout);
selectorManager.start();
try
{
final CountDownLatch latch = new CountDownLatch(1);
selectorManager.connect(client, new Callback.Empty<Void>()
{
@Override
public void failed(Void context, Throwable x)
{
latch.countDown();
}
});
Assert.assertTrue(latch.await(connectTimeout * 3, TimeUnit.MILLISECONDS));
}
finally
{
selectorManager.stop();
}
}
}

View File

@ -29,6 +29,7 @@ import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLSocket;
@ -57,22 +58,27 @@ public class SslConnectionTest
private volatile boolean _testFill=true;
private volatile FutureCallback<Void> _writeCallback;
protected ServerSocketChannel _connector;
protected QueuedThreadPool _threadPool = new QueuedThreadPool();
protected Scheduler _scheduler = new TimerScheduler();
protected SelectorManager _manager = new SelectorManager()
final AtomicInteger _dispatches = new AtomicInteger();
protected QueuedThreadPool _threadPool = new QueuedThreadPool()
{
@Override
protected void execute(Runnable task)
public boolean dispatch(Runnable job)
{
_threadPool.execute(task);
_dispatches.incrementAndGet();
return super.dispatch(job);
}
};
protected Scheduler _scheduler = new TimerScheduler();
protected SelectorManager _manager = new SelectorManager(_threadPool, _scheduler)
{
@Override
public Connection newConnection(SocketChannel channel, EndPoint endpoint, Object attachment)
{
SSLEngine engine = __sslCtxFactory.newSSLEngine();
engine.setUseClientMode(false);
SslConnection sslConnection = new SslConnection(__byteBufferPool, _threadPool, endpoint, engine);
SslConnection sslConnection = new SslConnection(__byteBufferPool, getExecutor(), endpoint, engine);
Connection appConnection = new TestConnection(sslConnection.getDecryptedEndPoint());
sslConnection.getDecryptedEndPoint().setConnection(appConnection);
@ -83,7 +89,7 @@ public class SslConnectionTest
@Override
protected SelectChannelEndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey selectionKey) throws IOException
{
SelectChannelEndPoint endp = new SelectChannelEndPoint(channel,selectSet, selectionKey, _scheduler, 60000);
SelectChannelEndPoint endp = new SelectChannelEndPoint(channel,selectSet, selectionKey, getScheduler(), 60000);
_lastEndp=endp;
return endp;
}
@ -113,6 +119,7 @@ public class SslConnectionTest
_threadPool.start();
_scheduler.start();
_manager.start();
}
@After
@ -132,7 +139,7 @@ public class SslConnectionTest
public TestConnection(EndPoint endp)
{
super(endp, _threadPool);
super(endp, _threadPool,false);
}
@Override
@ -228,11 +235,18 @@ public class SslConnectionTest
server.configureBlocking(false);
_manager.accept(server);
client.getOutputStream().write("HelloWorld".getBytes("UTF-8"));
client.getOutputStream().write("Hello".getBytes("UTF-8"));
byte[] buffer = new byte[1024];
int len=client.getInputStream().read(buffer);
Assert.assertEquals(10, len);
Assert.assertEquals("HelloWorld",new String(buffer,0,len,StringUtil.__UTF8_CHARSET));
Assert.assertEquals(5, len);
Assert.assertEquals("Hello",new String(buffer,0,len,StringUtil.__UTF8_CHARSET));
_dispatches.set(0);
client.getOutputStream().write("World".getBytes("UTF-8"));
len=5;
while(len>0)
len-=client.getInputStream().read(buffer);
Assert.assertEquals(1, _dispatches.get());
client.close();
}

View File

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

View File

@ -33,6 +33,10 @@ public class DefaultBundleClassLoaderHelper implements BundleClassLoaderHelper
{
private static boolean identifiedOsgiImpl = false;
private static Class BundleWiringClass = null;
private static Method BundleWiringClass_getClassLoader_method = null;
private static Method BundleClass_adapt_method = null;
private static boolean isEquinox = false;
@ -41,13 +45,34 @@ public class DefaultBundleClassLoaderHelper implements BundleClassLoaderHelper
private static void init(Bundle bundle)
{
identifiedOsgiImpl = true;
try
{
isEquinox = bundle.getClass().getClassLoader().loadClass("org.eclipse.osgi.framework.internal.core.BundleHost") != null;
BundleWiringClass = bundle.getClass().getClassLoader().loadClass("org.osgi.framework.wiring.BundleWiring");
if (BundleWiringClass != null)
{
BundleWiringClass_getClassLoader_method = BundleWiringClass.getDeclaredMethod("getClassLoader", new Class[] {});
BundleClass_adapt_method = bundle.getClass().getDeclaredMethod("adapt", new Class[] { Class.class });
BundleClass_adapt_method.setAccessible(true);
return;
}
}
catch (Throwable t)
{
isEquinox = false;
//nevermind: an older version of OSGi where BundleWiring is not availble
//t.printStackTrace();
}
if (!bundle.getClass().getName().startsWith("org.apache.felix"))
{
try
{
isEquinox = bundle.getClass().getClassLoader().loadClass("org.eclipse.osgi.framework.internal.core.BundleHost") != null;
}
catch (Throwable t)
{
isEquinox = false;
}
}
if (!isEquinox)
{
@ -70,6 +95,7 @@ public class DefaultBundleClassLoaderHelper implements BundleClassLoaderHelper
*/
public ClassLoader getBundleClassLoader(Bundle bundle)
{
//Older OSGi implementations:
String bundleActivator = (String) bundle.getHeaders().get("Bundle-Activator");
if (bundleActivator == null)
{
@ -93,6 +119,22 @@ public class DefaultBundleClassLoaderHelper implements BundleClassLoaderHelper
{
init(bundle);
}
//This works for OSGi 4.2 and more recent. Aka version 1.6
//It is using ava reflection to execute:
//(BundleClassLoader) bundle.adapt(BundleWiring.class).getClassLoader()
if (BundleClass_adapt_method != null && BundleWiringClass_getClassLoader_method != null)
{
try
{
Object bundleWiring = BundleClass_adapt_method.invoke(bundle, BundleWiringClass);
return (ClassLoader)BundleWiringClass_getClassLoader_method.invoke(bundleWiring, new Object[] {});
}
catch (Throwable t)
{
t.printStackTrace();
return null;
}
}
if (isEquinox)
{
return internalGetEquinoxBundleClassLoader(bundle);
@ -135,13 +177,16 @@ public class DefaultBundleClassLoaderHelper implements BundleClassLoaderHelper
private static Field Felix_BundleImpl_m_modules_field;
private static Field Felix_ModuleImpl_m_classLoader_field;
private static Field Felix_BundleImpl_m_revisions_field;
private static ClassLoader internalGetFelixBundleClassLoader(Bundle bundle)
{
// assume felix:
try
{
// now get the current module from the bundle.
// now get the current module from the bundle.
// and return the private field m_classLoader of ModuleImpl
if (Felix_BundleImpl_m_modules_field == null)
{

View File

@ -36,8 +36,8 @@ import org.eclipse.jetty.util.resource.FileResource;
import org.osgi.framework.Bundle;
/**
* From a bundle to its location on the filesystem. Assumes the bundle is not a
* jar.
* From a bundle to its location on the filesystem.
* Often assumes the bundle is not a jar.
*
* @author hmalphettes
*/
@ -150,7 +150,6 @@ public class DefaultFileLocatorHelper implements BundleFileLocatorHelper
{
// observed this on felix-2.0.0
String location = bundle.getLocation();
// System.err.println("location " + location);
if (location.startsWith("file:/"))
{
URI uri = new URI(URIUtil.encodePath(location));
@ -183,10 +182,16 @@ public class DefaultFileLocatorHelper implements BundleFileLocatorHelper
File file = new File(location.substring("file:".length()));
return file;
}
else
{
//Resort to introspection on felix:
return getBundleInstallLocationInFelix(bundle);
}
}
return null;
}
/**
* Locate a file inside a bundle.
*
@ -357,4 +362,85 @@ public class DefaultFileLocatorHelper implements BundleFileLocatorHelper
return url;
}
// Felix introspection
private static Method Felix_BundleImpl_getArchive_method;
private static Method Felix_BundleArchive_getCurrentRevision_method;
private static Method Felix_BundleRevision_getRevisionRootDir_method;
private static boolean felixIntroSpectionDone = false;
/**
* Introspection of the implementation classes of Felix-3.x and Felix-4.x.
* <p>
* See org.apache.felix.framework.cache
* In pseudo code:
* <code>
* File revRootDir = BundleImpl.getArchive().getCurrentRevision().getRevisionRootDir();
* return new File(revRootDir, bundle.jar) if it exists?
* else return revRootDir
* </p>
* @param bundle
* @return The File or null if we failed to find it.
*/
private static File getBundleInstallLocationInFelix(Bundle bundle)
{
if (Felix_BundleImpl_getArchive_method == null) {
if (felixIntroSpectionDone)
{
return null;
}
felixIntroSpectionDone = true;
try
{
Felix_BundleImpl_getArchive_method = bundle.getClass().getDeclaredMethod("getArchive", new Class[] {});
Felix_BundleImpl_getArchive_method.setAccessible(true);
Object archive = Felix_BundleImpl_getArchive_method.invoke(bundle);
Class bundleArchiveClass = archive.getClass();
Felix_BundleArchive_getCurrentRevision_method = bundleArchiveClass.getDeclaredMethod("getCurrentRevision", new Class[] {});
Felix_BundleArchive_getCurrentRevision_method.setAccessible(true);
Object revision = Felix_BundleArchive_getCurrentRevision_method.invoke(archive);
Class bundleRevisionClass = revision.getClass();
Felix_BundleRevision_getRevisionRootDir_method = bundleRevisionClass.getMethod("getRevisionRootDir", new Class[] {});
Felix_BundleRevision_getRevisionRootDir_method.setAccessible(true);
}
catch (Throwable t)
{
//nevermind?
//t.printStackTrace();
Felix_BundleImpl_getArchive_method = null;
return null;
}
}
if (Felix_BundleImpl_getArchive_method != null)
{
try
{
Object archive = Felix_BundleImpl_getArchive_method.invoke(bundle);
Object revision = Felix_BundleArchive_getCurrentRevision_method.invoke(archive);
File revRootDir = (File)Felix_BundleRevision_getRevisionRootDir_method.invoke(revision);
//System.err.println("Got the archive revision root dir " + revRootDir.getAbsolutePath());
File bundleJar = new File(revRootDir, "bundle.jar");
if (bundleJar.exists())
{
//bundle.jar is hardcoded in org.apache.felix.framework.cache.JarRevision
//when it is not a bundle.jar, then the bundle location starts with 'file:' and we have already
//taken care if that scheme earlier.
return bundleJar;
}
else //sanity check?: if (new File(revRootDir, "META-INF/MANIFEST.MF").exists())
{
//this is a DirectoryRevision
return revRootDir;
}
}
catch (Throwable t)
{
//best effort: nevermind
//t.printStackTrace();
}
}
return null;
}
// -- end Felix introspection
}

View File

@ -23,7 +23,7 @@
<Call name="addServlet">
<Arg>org.eclipse.equinox.http.servlet.HttpServiceServlet</Arg>
<Arg>/*</Arg>
<Set name="InitOrder">0</Set>
<Set name="InitOrder">1</Set>
</Call>
<!-- custom pluggable error handler -->
<Set name="ErrorHandler">

View File

@ -15,8 +15,8 @@
<osgi-services-version>3.2.100.v20100503</osgi-services-version>
<equinox-http-servlet-version>1.0.0-v20070606</equinox-http-servlet-version>
<!--equinox-servletbridge-version>1.0.0-v20070523</equinox-servletbridge-version-->
<logback-version>0.9.18</logback-version>
<slf4j-version>1.5.11</slf4j-version>
<logback-version>0.9.29</logback-version>
<slf4j-version>1.6.1</slf4j-version>
</properties>
<modules>
<module>jetty-osgi-boot</module>

View File

@ -8,28 +8,157 @@
<modelVersion>4.0.0</modelVersion>
<artifactId>test-jetty-osgi</artifactId>
<name>Jetty :: OSGi :: Test</name>
<description>Jetty OSGi Integration test</description>
<description>Jetty OSGi Integration tests</description>
<properties>
<bundle-symbolic-name>${project.groupId}.boot.test</bundle-symbolic-name>
<bundle-symbolic-name>${project.groupId}.boot.test.spdy</bundle-symbolic-name>
<jetty-orbit-url>http://download.eclipse.org/jetty/orbit/</jetty-orbit-url>
<assembly-directory>target/distribution</assembly-directory>
<paxexam-version-old>1.2.0</paxexam-version-old>
<paxexam-version>1.2.4</paxexam-version>
<exam.version>2.6.0</exam.version>
<url.version>1.4.0</url.version>
<paxswissbox.version>1.5.1</paxswissbox.version>
<felixversion>4.0.3</felixversion>
<injection.bundle.version>1.0</injection.bundle.version>
<runner.version>1.7.6</runner.version>
<npn-version>1.1.0.v20120525</npn-version>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit-dep</artifactId>
<version>4.10</version>
</dependency>
<!-- Pax Exam Dependencies -->
<!-- OPS4J Swissbox Dependencies -->
<dependency>
<groupId>org.ops4j.pax.swissbox</groupId>
<artifactId>pax-swissbox-core</artifactId>
<version>${paxswissbox.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.ops4j.pax.swissbox</groupId>
<artifactId>pax-swissbox-extender</artifactId>
<version>${paxswissbox.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.ops4j.pax.swissbox</groupId>
<artifactId>pax-swissbox-lifecycle</artifactId>
<version>${paxswissbox.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.ops4j.pax.swissbox</groupId>
<artifactId>pax-swissbox-framework</artifactId>
<version>${paxswissbox.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.ops4j.pax.exam</groupId>
<artifactId>pax-exam</artifactId>
<version>${exam.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.geronimo.specs</groupId>
<artifactId>geronimo-atinject_1.0_spec</artifactId>
<version>${injection.bundle.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.ops4j.pax.exam</groupId>
<artifactId>pax-exam-inject</artifactId>
<version>${exam.version}</version>
<scope>test</scope>
</dependency>
<!-- Don't use the native container for now. Observed limitations:
- single test with a single configuration
- does not read the versions of the dependencies from the pom.xml
and hence hardcode the bundles versions in the source code instead
- no support for most configuration options for the OSGi container. -->
<!--dependency>
<groupId>org.ops4j.pax.exam</groupId>
<artifactId>pax-exam-container-native</artifactId>
<version>${exam.version}</version>
<scope>test</scope>
</dependency-->
<!-- container is not bad but not enough config parameters yet
can't pass the VMOption for npn-boot
<dependency>
<groupId>org.ops4j.pax.exam</groupId>
<artifactId>pax-exam-container-forked</artifactId>
<version>${exam.version}</version>
<scope>test</scope>
</dependency>
-->
<dependency>
<groupId>org.ops4j.pax.exam</groupId>
<artifactId>pax-exam-container-paxrunner</artifactId>
<version>${exam.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.ops4j.pax.runner</groupId>
<artifactId>pax-runner-no-jcl</artifactId>
<version>${runner.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.ops4j.pax.exam</groupId>
<artifactId>pax-exam-junit4</artifactId>
<version>${exam.version}</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.ops4j.pax.exam</groupId>
<artifactId>pax-exam-link-mvn</artifactId>
<version>${exam.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.ops4j.pax.url</groupId>
<artifactId>pax-url-aether</artifactId>
<version>${url.version}</version>
<scope>test</scope>
</dependency>
<!-- OSGi R4 frameworks -->
<dependency>
<groupId>org.apache.felix</groupId>
<artifactId>org.apache.felix.framework</artifactId>
<version>${felixversion}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.ops4j.pax.exam</groupId>
<artifactId>pax-exam-testforge</artifactId>
<version>${exam.version}</version>
<scope>test</scope>
</dependency>
<!-- For sane logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.6.1</version>
<scope>test</scope>
</dependency>
<!-- Orbit Servlet Deps -->
<dependency>
<groupId>org.eclipse.jetty.orbit</groupId>
<artifactId>javax.servlet</artifactId>
<scope>test</scope>
</dependency>
<!-- Orbit JSP Deps -->
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-jsp</artifactId>
<version>${project.version}</version>
</dependency>
<!-- OSGi Deps -->
<dependency>
<groupId>org.eclipse.jetty.osgi</groupId>
@ -49,7 +178,6 @@
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<!-- Jetty Deps -->
<dependency>
<groupId>org.eclipse.jetty</groupId>
@ -123,13 +251,44 @@
<version>${project.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.spdy</groupId>
<artifactId>spdy-core</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.spdy</groupId>
<artifactId>spdy-server</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.spdy</groupId>
<artifactId>spdy-http-server</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.spdy</groupId>
<artifactId>spdy-client</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mortbay.jetty.npn</groupId>
<artifactId>npn-boot</artifactId>
<version>${npn-version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-plus</artifactId>
<version>${project.version}</version>
<scope>runtime</scope>
</dependency>
<!-- Eclipse OSGi Deps -->
<dependency>
<groupId>org.eclipse.osgi</groupId>
@ -153,7 +312,6 @@
<artifactId>servlet</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>test-jetty-webapp</artifactId>
@ -161,139 +319,40 @@
<classifier>webbundle</classifier>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.ops4j.pax.exam</groupId>
<artifactId>pax-exam</artifactId>
<version>${paxexam-version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.ops4j.pax.exam</groupId>
<artifactId>pax-exam-junit</artifactId>
<version>${paxexam-version}</version>
<scope>test</scope>
<exclusions>
<exclusion>
<!-- we use junit-dep -->
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.ops4j.pax.exam</groupId>
<artifactId>pax-exam-container-default</artifactId>
<version>${paxexam-version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.toolchain</groupId>
<artifactId>jetty-test-helper</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>${slf4j-version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.ops4j.pax.exam</groupId>
<artifactId>maven-paxexam-plugin</artifactId>
<version>${paxexam-version}</version>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<!-- No point defining -Xbootclasspath as the actual OSGi VM is run as a forked process by pax-exam -->
<!--argLine>-Xbootclasspath/p:${settings.localRepository}/org/mortbay/jetty/npn/npn-boot/${npn-version}/npn-boot-${npn-version}.jar</argLine-->
<!-- But we do pass the sys property of the npn-boot jar -->
<argLine>-Dmortbay-npn-boot=${settings.localRepository}/org/mortbay/jetty/npn/npn-boot/${npn-version}/npn-boot-${npn-version}.jar</argLine>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.5.1</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.servicemix.tooling</groupId>
<artifactId>depends-maven-plugin</artifactId>
<version>1.2</version>
<executions>
<execution>
<id>generate-config</id>
<id>generate-depends-file</id>
<goals>
<goal>generate-depends-file</goal>
<goal>generate-config</goal>
</goals>
</execution>
</executions>
<configuration>
<options>
<workingDirectory>${project.build.directory}/paxexam</workingDirectory>
</options>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-orbit-servlet-api-deps</id>
<phase>generate-resources</phase>
<goals>
<goal>copy</goal>
</goals>
<configuration>
<artifactItems>
<artifactItem>
<groupId>org.eclipse.jetty.orbit</groupId>
<artifactId>javax.servlet</artifactId>
<version>${orbit-servlet-api-version}</version>
<overWrite>true</overWrite>
<outputDirectory>${assembly-directory}/lib</outputDirectory>
<destFileName>servlet-api-3.0.jar</destFileName>
</artifactItem>
</artifactItems>
</configuration>
</execution>
<execution>
<id>copy-orbit-lib-jndi-deps</id>
<phase>generate-resources</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<includeGroupIds>org.eclipse.jetty.orbit</includeGroupIds>
<includeArtifactIds>javax.mail.glassfish,javax.activation</includeArtifactIds>
<includeTypes>jar</includeTypes>
<outputDirectory>${assembly-directory}/lib/jndi</outputDirectory>
</configuration>
</execution>
<execution>
<id>copy-orbit-lib-jsp-deps</id>
<phase>generate-resources</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<includeGroupIds>org.eclipse.jetty.orbit</includeGroupIds>
<includeArtifactIds>com.sun.el,javax.el,javax.servlet.jsp,javax.servlet.jsp.jstl,org.apache.jasper.glassfish,org.apache.taglibs.standard.glassfish,org.eclipse.jdt.core</includeArtifactIds>
<includeTypes>jar</includeTypes>
<outputDirectory>${assembly-directory}/lib/jsp</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-eclipse-plugin</artifactId>
<version>2.8</version>
<configuration>
<manifest>prevent/overwriting/by/pointing/to/nonexisting/MANIFEST.MF</manifest>
<pde>false</pde>
<downloadSources>true</downloadSources>
<sourceExcludes>
<sourceExclude>**/.svn/**</sourceExclude>
</sourceExcludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
</project>

View File

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

View File

@ -0,0 +1,133 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
<Configure id="Server" class="org.eclipse.jetty.server.Server">
<!-- =========================================================== -->
<!-- HttpChannel Configuration -->
<!-- =========================================================== -->
<New id="httpConfig" class="org.eclipse.jetty.server.HttpChannelConfig">
<Set name="secureScheme">https</Set>
<Set name="securePort"><SystemProperty name="jetty.spdy.port" default="8443"/></Set>
<Set name="outputBufferSize">32768</Set>
<Set name="requestHeaderSize">8192</Set>
<Set name="responseHeaderSize">8192</Set>
<Call name="addCustomizer">
<Arg><New class="org.eclipse.jetty.server.ForwardedRequestCustomizer"/></Arg>
</Call>
</New>
<!-- =========================================================== -->
<!-- Setup a SSL Context factory -->
<!-- =========================================================== -->
<New id="sslContextFactory" class="org.eclipse.jetty.util.ssl.SslContextFactory">
<Set name="KeyStorePath"><Property name="jetty.home" default="."/>/etc/keystore
</Set>
<Set name="KeyStorePassword">OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4</Set>
<Set name="KeyManagerPassword">OBF:1u2u1wml1z7s1z7a1wnl1u2g</Set>
<Set name="TrustStorePath"><Property name="jetty.home" default="."/>/etc/keystore
</Set>
<Set name="TrustStorePassword">OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4</Set>
</New>
<!-- =========================================================== -->
<!-- Add HTTP Customizer for Secure request -->
<!-- =========================================================== -->
<Ref id="httpConfig">
<Call name="addCustomizer">
<Arg>
<New class="org.eclipse.jetty.server.SecureRequestCustomizer"/>
</Arg>
</Call>
</Ref>
<!-- =========================================================== -->
<!-- Create a push strategy -->
<!-- =========================================================== -->
<New id="pushStrategy" class="org.eclipse.jetty.spdy.server.http.ReferrerPushStrategy">
<Arg type="List">
<Array type="String">
<Item>.*\.css</Item>
<Item>.*\.js</Item>
<Item>.*\.png</Item>
<Item>.*\.jpg</Item>
<Item>.*\.gif</Item>
</Array>
</Arg>
</New>
<!-- =========================================================== -->
<!-- Set connectors -->
<!-- =========================================================== -->
<Call id="sslConnector" name="addConnector">
<Arg>
<New class="org.eclipse.jetty.server.ServerConnector">
<Arg name="server">
<Ref id="Server"/>
</Arg>
<Arg name="factories">
<Array type="org.eclipse.jetty.server.ConnectionFactory">
<Item>
<New class="org.eclipse.jetty.server.SslConnectionFactory">
<Arg name="next">npn</Arg>
<Arg name="sslContextFactory">
<Ref id="sslContextFactory"/>
</Arg>
</New>
</Item>
<Item>
<New class="org.eclipse.jetty.spdy.server.NPNServerConnectionFactory">
<Arg name="protocols">
<Array type="String">
<Item>spdy/3</Item>
<Item>spdy/2</Item>
<Item>http/1.1</Item>
</Array>
</Arg>
<Set name="defaultProtocol">http/1.1</Set>
</New>
</Item>
<Item>
<New class="org.eclipse.jetty.spdy.server.http.HTTPSPDYServerConnectionFactory">
<Arg name="version" type="int">3</Arg>
<Arg name="config">
<Ref id="httpConfig"/>
</Arg>
<!-- <Arg name="pushStrategy"><Ref id="pushStrategy"/></Arg> -->
</New>
</Item>
<Item>
<New class="org.eclipse.jetty.spdy.server.http.HTTPSPDYServerConnectionFactory">
<Arg name="version" type="int">2</Arg>
<Arg name="config">
<Ref id="httpConfig"/>
</Arg>
</New>
</Item>
<Item>
<New class="org.eclipse.jetty.server.HttpConnectionFactory">
<Arg name="config">
<Ref id="httpConfig"/>
</Arg>
</New>
</Item>
</Array>
</Arg>
<Set name="host">
<Property name="jetty.host"/>
</Set>
<Set name="port">
<SystemProperty name="jetty.spdy.port" default="8443"/>
</Set>
<Set name="idleTimeout">30000</Set>
</New>
</Arg>
</Call>
</Configure>

View File

@ -1,197 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995-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.osgi.boot;
import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
import static org.ops4j.pax.exam.CoreOptions.options;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import junit.framework.Assert;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.HttpContentResponse;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.osgi.boot.internal.serverfactory.DefaultJettyAtJettyHomeHelper;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.ops4j.pax.exam.CoreOptions;
import org.ops4j.pax.exam.Inject;
import org.ops4j.pax.exam.Option;
import org.ops4j.pax.exam.container.def.PaxRunnerOptions;
import org.ops4j.pax.exam.junit.Configuration;
import org.ops4j.pax.exam.junit.JUnit4TestRunner;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import org.osgi.service.http.HttpService;
/**
* Pax-Exam to make sure the jetty-osgi-boot can be started along with the httpservice web-bundle.
* Then make sure we can deploy an OSGi service on the top of this.
*/
@RunWith( JUnit4TestRunner.class )
public class TestJettyOSGiBootCore
{
/**
* Jetty-osgi including webapp support and also jetty-client.
* Sets the system property jetty.home.bunde=org.eclipse.jetty.osgi.boot
* to use the jetty server configuration embedded in
*
* @return list of options
*/
public static List<Option> provisionCoreJetty()
{
List<Option> res = new ArrayList<Option>();
// get the jetty home config from the osgi boot bundle.
res.add(PaxRunnerOptions.vmOptions("-Djetty.port=9876 -D" + DefaultJettyAtJettyHomeHelper.SYS_PROP_JETTY_HOME_BUNDLE + "=org.eclipse.jetty.osgi.boot"));
res.addAll(coreJettyDependencies());
return res;
}
public static List<Option> coreJettyDependencies() {
List<Option> res = new ArrayList<Option>();
res.add(mavenBundle().groupId( "org.eclipse.jetty.orbit" ).artifactId( "javax.servlet" ).versionAsInProject().noStart());
res.add(mavenBundle().groupId( "org.eclipse.osgi" ).artifactId( "org.eclipse.osgi.services" ).versionAsInProject().noStart());
res.add(mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "jetty-deploy" ).versionAsInProject().noStart());
res.add(mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "jetty-server" ).versionAsInProject().noStart());
res.add(mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "jetty-servlet" ).versionAsInProject().noStart());
res.add(mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "jetty-util" ).versionAsInProject().noStart());
res.add(mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "jetty-http" ).versionAsInProject().noStart());
res.add(mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "jetty-xml" ).versionAsInProject().noStart());
res.add(mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "jetty-webapp" ).versionAsInProject().noStart());
res.add(mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "jetty-io" ).versionAsInProject().noStart());
res.add(mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "jetty-continuation" ).versionAsInProject().noStart());
res.add(mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "jetty-security" ).versionAsInProject().noStart());
res.add(mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "jetty-servlets" ).versionAsInProject().noStart());
res.add(mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "jetty-client" ).versionAsInProject().noStart());
res.add(mavenBundle().groupId( "org.eclipse.jetty.websocket" ).artifactId( "websocket-core" ).versionAsInProject().noStart());
res.add(mavenBundle().groupId( "org.eclipse.jetty.websocket" ).artifactId( "websocket-server" ).versionAsInProject().noStart());
//optional:
//res.add(mavenBundle().groupId( "org.eclipse.jetty.websocket" ).artifactId( "websocket-client" ).versionAsInProject().noStart());
return res;
}
public static List<Option> websocketDependencies() {
List<Option> res = new ArrayList<Option>();
return res;
}
@Inject
BundleContext bundleContext = null;
@Configuration
public static Option[] configure()
{
ArrayList<Option> options = new ArrayList<Option>();
options.addAll(provisionCoreJetty());
options.addAll(Arrays.asList(options(
// install log service using pax runners profile abstraction (there are more profiles, like DS)
//logProfile(),
// this is how you set the default log level when using pax logging (logProfile)
CoreOptions.systemProperty( "org.ops4j.pax.logging.DefaultServiceLog.level" ).value( "INFO" ),
// CoreOptions.equinox(), CoreOptions.felix(),//.version("3.0.0"),
mavenBundle().groupId( "org.eclipse.jetty.osgi" ).artifactId( "jetty-osgi-boot" ).versionAsInProject().start(),
mavenBundle().groupId( "org.eclipse.jetty.osgi" ).artifactId( "jetty-httpservice" ).versionAsInProject().start(),
mavenBundle().groupId( "org.eclipse.equinox.http" ).artifactId( "servlet" ).versionAsInProject().start()
)));
return options.toArray(new Option[options.size()]);
}
/**
* You will get a list of bundles installed by default
* plus your testcase, wrapped into a bundle called pax-exam-probe
*/
@Test
public void testHttpService() throws Exception
{
// ServletContextHandler sch = null;
// sch.addServlet("className", "pathSpec").setInitOrder("0");
Map<String,Bundle> bundlesIndexedBySymbolicName = new HashMap<String, Bundle>();
for( Bundle b : bundleContext.getBundles() )
{
bundlesIndexedBySymbolicName.put(b.getSymbolicName(), b);
//System.err.println("got " + b.getSymbolicName());
}
Bundle osgiBoot = bundlesIndexedBySymbolicName.get("org.eclipse.jetty.osgi.boot");
Assert.assertNotNull("Could not find the org.eclipse.jetty.osgi.boot bundle", osgiBoot);
Assert.assertTrue(osgiBoot.getState() == Bundle.ACTIVE);
Bundle httpServiceOSGiBundle = bundlesIndexedBySymbolicName.get("org.eclipse.jetty.osgi.httpservice");
Assert.assertNotNull(httpServiceOSGiBundle);
Assert.assertTrue(httpServiceOSGiBundle.getState() == Bundle.ACTIVE);
Bundle equinoxServlet = bundlesIndexedBySymbolicName.get("org.eclipse.equinox.http.servlet");
Assert.assertNotNull(equinoxServlet);
//interestingly with equinox the bundle is not started. probably a difference in pax-exam and
//the way the bundles are activated. the rest of the test goes fine.
Assert.assertTrue(equinoxServlet.getState() == Bundle.ACTIVE);
//in the OSGi world this would be bad code and we should use a bundle tracker.
//here we purposely want to make sure that the httpService is actually ready.
ServiceReference sr = bundleContext.getServiceReference(HttpService.class.getName());
Assert.assertNotNull("The httpServiceOSGiBundle is started and should have deployed a service reference for HttpService" ,sr);
HttpService http = (HttpService)bundleContext.getService(sr);
http.registerServlet("/greetings", new HttpServlet() {
private static final long serialVersionUID = 1L;
@Override
protected void doGet(HttpServletRequest req,
HttpServletResponse resp) throws ServletException,
IOException {
resp.getWriter().write("Hello");
}
}, null, null);
//now test the servlet
HttpClient client = new HttpClient();
try
{
client.start();
Response response = client.GET("http://127.0.0.1:9876/greetings").get(5, TimeUnit.SECONDS);;
Assert.assertEquals(HttpStatus.OK_200, response.status());
String content = new String(((HttpContentResponse)response).content());
Assert.assertEquals("Hello", content);
}
finally
{
client.stop();
}
}
}

View File

@ -1,172 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995-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.osgi.boot;
import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
import static org.ops4j.pax.exam.CoreOptions.options;
import static org.ops4j.pax.exam.CoreOptions.systemProperty;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import junit.framework.Assert;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.HttpContentResponse;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.http.HttpStatus;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.ops4j.pax.exam.CoreOptions;
import org.ops4j.pax.exam.Inject;
import org.ops4j.pax.exam.Option;
import org.ops4j.pax.exam.container.def.PaxRunnerOptions;
import org.ops4j.pax.exam.junit.Configuration;
import org.ops4j.pax.exam.junit.JUnit4TestRunner;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
/**
* Pax-Exam to make sure the jetty-osgi-boot can be started along with the httpservice web-bundle.
* Then make sure we can deploy an OSGi service on the top of this.
*/
@RunWith( JUnit4TestRunner.class )
public class TestJettyOSGiBootWithJsp
{
private static final boolean LOGGING_ENABLED = true;
private static final boolean REMOTE_DEBUGGING = false;
@Inject
BundleContext bundleContext = null;
@Configuration
public static Option[] configure()
{
File etcFolder = new File("src/test/config/etc");
String etc = "file://" + etcFolder.getAbsolutePath();
ArrayList<Option> options = new ArrayList<Option>();
options.add(PaxRunnerOptions.vmOption("-Djetty.port=9876 -Djetty.home="+ etcFolder.getParentFile().getAbsolutePath() +
" -D" + OSGiServerConstants.MANAGED_JETTY_XML_CONFIG_URLS +
"="+etc+"/jetty.xml;"+etc+"/jetty-selector.xml;"+etc+"/jetty-deployer.xml;" + etc + "/jetty-testrealm.xml"));
options.add(CoreOptions.equinox());
options.add(CoreOptions.bootDelegationPackages("org.xml.sax", "org.xml.*",
"org.w3c.*", "javax.xml.*"));
options.addAll(TestJettyOSGiBootCore.coreJettyDependencies());
// Enable Logging
if(LOGGING_ENABLED) {
options.addAll(Arrays.asList(options(
// install log service using pax runners profile abstraction (there are more profiles, like DS)
// logProfile(),
// this is how you set the default log level when using pax logging (logProfile)
systemProperty( "org.ops4j.pax.logging.DefaultServiceLog.level" ).value( "INFO" )
)));
}
options.addAll(jspDependencies());
// Remote JDWP Debugging
if(REMOTE_DEBUGGING) {
options.addAll(Arrays.asList(options(
// this just adds all what you write here to java vm argumenents of the (new) osgi process.
PaxRunnerOptions.vmOption( "-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5006" )
)));
}
return options.toArray(new Option[options.size()]);
}
public static List<Option> jspDependencies() {
List<Option> res = new ArrayList<Option>();
/* orbit deps */
res.add(mavenBundle().groupId( "org.eclipse.jetty.orbit" ).artifactId( "javax.servlet.jsp" ).versionAsInProject());
res.add(mavenBundle().groupId( "org.eclipse.jetty.orbit" ).artifactId( "javax.servlet.jsp.jstl" ).versionAsInProject());
res.add(mavenBundle().groupId( "org.eclipse.jetty.orbit" ).artifactId( "javax.el" ).versionAsInProject());
res.add(mavenBundle().groupId( "org.eclipse.jetty.orbit" ).artifactId( "com.sun.el" ).versionAsInProject());
res.add(mavenBundle().groupId( "org.eclipse.jetty.orbit" ).artifactId( "org.apache.jasper.glassfish" ).versionAsInProject());
res.add(mavenBundle().groupId( "org.eclipse.jetty.orbit" ).artifactId( "org.apache.taglibs.standard.glassfish" ).versionAsInProject());
res.add(mavenBundle().groupId( "org.eclipse.jetty.orbit" ).artifactId( "org.eclipse.jdt.core" ).versionAsInProject());
/* jetty-osgi deps */
res.add(mavenBundle().groupId( "org.eclipse.jetty.osgi" ).artifactId( "jetty-osgi-boot-jsp" ).versionAsInProject().noStart());
res.add(mavenBundle().groupId( "org.eclipse.jetty.osgi" ).artifactId( "jetty-osgi-boot" ).versionAsInProject().start());
res.add(mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "test-jetty-webapp" ).classifier("webbundle").versionAsInProject());
res.add(mavenBundle().groupId( "org.eclipse.equinox.http" ).artifactId( "servlet" ).versionAsInProject().start());
return res;
}
/**
* You will get a list of bundles installed by default
* plus your testcase, wrapped into a bundle called pax-exam-probe
*/
@Test
public void listBundles() throws Exception
{
Map<String,Bundle> bundlesIndexedBySymbolicName = new HashMap<String, Bundle>();
for( Bundle b : bundleContext.getBundles() )
{
bundlesIndexedBySymbolicName.put(b.getSymbolicName(), b);
}
Bundle websocketServer = bundlesIndexedBySymbolicName.get("org.eclipse.jetty.websocket.server");
Assert.assertNotNull("Could not find the org.eclipse.jetty.websocket.server bundle", websocketServer);
Assert.assertEquals(Bundle.RESOLVED, websocketServer.getState());
Bundle osgiBoot = bundlesIndexedBySymbolicName.get("org.eclipse.jetty.osgi.boot");
Assert.assertNotNull("Could not find the org.eclipse.jetty.osgi.boot bundle", osgiBoot);
Assert.assertEquals(Bundle.ACTIVE, osgiBoot.getState());
Bundle osgiBootJsp = bundlesIndexedBySymbolicName.get("org.eclipse.jetty.osgi.boot.jsp");
Assert.assertNotNull("Could not find the org.eclipse.jetty.osgi.boot.jsp bundle", osgiBootJsp);
Assert.assertEquals("The fragment jsp is not correctly resolved.",
Bundle.RESOLVED, osgiBootJsp.getState());
Bundle testWebBundle = bundlesIndexedBySymbolicName.get("org.eclipse.jetty.test-jetty-webapp");
Assert.assertNotNull("Could not find the org.eclipse.jetty.test-jetty-webappp bundle", osgiBootJsp);
Assert.assertEquals("The test-jetty-webapp is not correctly resolved", Bundle.ACTIVE, testWebBundle.getState());
//now test the jsp/dump.jsp
HttpClient client = new HttpClient();
try
{
client.start();
Response response = client.GET("http://127.0.0.1:9876/jsp/dump.jsp").get(5, TimeUnit.SECONDS);
Assert.assertEquals(HttpStatus.OK_200, response.status());
String content = new String(((HttpContentResponse)response).content());
//System.err.println("content: " + content);
Assert.assertTrue(content.indexOf("<tr><th>ServletPath:</th><td>/jsp/dump.jsp</td></tr>") != -1);
}
finally
{
client.stop();
}
}
}

View File

@ -0,0 +1,192 @@
//
// ========================================================================
// Copyright (c) 1995-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.osgi.test;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.HttpContentResponse;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.junit.Assert;
import org.ops4j.pax.exam.CoreOptions;
import org.ops4j.pax.exam.Option;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import org.osgi.service.http.HttpService;
/**
* Helper methods for pax-exam tests
*/
public class AbstractTestOSGi
{
private Map<String,Bundle> _bundles;
/**
* Note: this will run many more tests.
* TODO: find a better way to control this and use non-deprecated methods.
* @param options
*/
protected static void addMoreOSGiContainers(List<Option> options)
{
options.add(CoreOptions.equinox().version("3.6.1"));
options.add(CoreOptions.equinox().version("3.7.1"));
options.add(CoreOptions.felix().version("3.2.2"));
options.add(CoreOptions.felix().version("4.0.2"));
}
protected Bundle getBundle(BundleContext bundleContext, String symbolicName)
{
if (_bundles == null)
{
_bundles = new HashMap<String,Bundle>();
for (Bundle b : bundleContext.getBundles())
{
Bundle prevBundle = _bundles.put(b.getSymbolicName(), b);
String err = prevBundle != null ? "2 versions of the bundle " + b.getSymbolicName() +
" " + b.getHeaders().get("Bundle-Version") +
" and " + prevBundle.getHeaders().get("Bundle-Version")
: "";
Assert.assertNull(err, prevBundle);
}
}
return _bundles.get(symbolicName);
}
protected void assertActiveBundle(BundleContext bundleContext, String symbolicName) throws Exception
{
Bundle b = getBundle(bundleContext, symbolicName);
Assert.assertNotNull(b);
Assert.assertEquals(b.getSymbolicName()+" must be active.", Bundle.ACTIVE, b.getState());
}
protected void assertActiveOrResolvedBundle(BundleContext bundleContext, String symbolicName) throws Exception
{
Bundle b = getBundle(bundleContext, symbolicName);
Assert.assertNotNull(b);
if (b.getHeaders().get("Fragment-Host") == null) diagnoseNonActiveOrNonResolvedBundle(b);
Assert.assertTrue(b.getSymbolicName()+" must be active or resolved. It was "+b.getState(),
b.getState() == Bundle.ACTIVE || b.getState() == Bundle.RESOLVED);
}
protected void assertAllBundlesActiveOrResolved(BundleContext bundleContext)
{
for (Bundle b : bundleContext.getBundles())
{
if (b.getState() == Bundle.INSTALLED)
{
diagnoseNonActiveOrNonResolvedBundle(b);
}
Assert.assertTrue(b.getState() == Bundle.ACTIVE || b.getState() == Bundle.RESOLVED);
}
}
protected boolean diagnoseNonActiveOrNonResolvedBundle(Bundle b)
{
if (b.getState() != Bundle.ACTIVE && b.getHeaders().get("Fragment-Host") == null)
{
try
{
System.err.println("Trying to start the bundle "+b.getSymbolicName()+
" that was supposed to be active or resolved.");
b.start();
System.err.println(b.getSymbolicName() + " did start");
return true;
}
catch (Throwable t)
{
System.err.println(b.getSymbolicName() + " failed to start");
t.printStackTrace(System.err);
return false;
}
}
System.err.println(b.getSymbolicName() + " was already started");
return false;
}
protected void debugBundles(BundleContext bundleContext)
{
Map<String,Bundle> bundlesIndexedBySymbolicName = new HashMap<String, Bundle>();
System.err.println("Active " + Bundle.ACTIVE);
System.err.println("RESOLVED " + Bundle.RESOLVED);
System.err.println("INSTALLED " + Bundle.INSTALLED);
for( Bundle b : bundleContext.getBundles() )
{
bundlesIndexedBySymbolicName.put(b.getSymbolicName(), b);
System.err.println(" " + b.getSymbolicName() + " " + b.getState());
}
}
protected SslContextFactory getSslContextFactory()
{
return new SslContextFactory(true);
}
protected void testHttpServiceGreetings(BundleContext bundleContext, String protocol, int port) throws Exception
{
assertActiveBundle(bundleContext, "org.eclipse.jetty.osgi.boot");
assertActiveBundle(bundleContext, "org.eclipse.jetty.osgi.httpservice");
assertActiveBundle(bundleContext, "org.eclipse.equinox.http.servlet");
//in the OSGi world this would be bad code and we should use a bundle tracker.
//here we purposely want to make sure that the httpService is actually ready.
ServiceReference sr = bundleContext.getServiceReference(HttpService.class.getName());
Assert.assertNotNull("The httpServiceOSGiBundle is started and should " +
"have deployed a service reference for HttpService" ,sr);
HttpService http = (HttpService)bundleContext.getService(sr);
http.registerServlet("/greetings", new HttpServlet() {
private static final long serialVersionUID = 1L;
@Override
protected void doGet(HttpServletRequest req,
HttpServletResponse resp) throws ServletException,
IOException {
resp.getWriter().write("Hello");
}
}, null, null);
//now test the servlet
HttpClient client = protocol.equals("https") ? new HttpClient(getSslContextFactory()) : new HttpClient();
try
{
client.start();
Response response = client.GET(protocol+"://127.0.0.1:"+port+"/greetings").get(5, TimeUnit.SECONDS);;
Assert.assertEquals(HttpStatus.OK_200, response.status());
String content = new String(((HttpContentResponse)response).content());
Assert.assertEquals("Hello", content);
}
finally
{
client.stop();
}
}
}

View File

@ -0,0 +1,121 @@
//
// ========================================================================
// Copyright (c) 1995-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.osgi.test;
import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.ops4j.pax.exam.CoreOptions;
import org.ops4j.pax.exam.Option;
import org.ops4j.pax.exam.junit.Configuration;
import org.ops4j.pax.exam.junit.JUnit4TestRunner;
import org.osgi.framework.BundleContext;
/**
* Default OSGi setup integration test
*/
@RunWith( JUnit4TestRunner.class )
public class TestJettyOSGiBootCore extends AbstractTestOSGi {
public static int DEFAULT_JETTY_HTTP_PORT = 9876;
@Inject
private BundleContext bundleContext;
@Configuration
public Option[] config()
{
ArrayList<Option> options = new ArrayList<Option>();
addMoreOSGiContainers(options);
options.addAll(provisionCoreJetty());
options.add(CoreOptions.junitBundles());
options.addAll(httpServiceJetty());
return options.toArray(new Option[options.size()]);
}
public static List<Option> provisionCoreJetty()
{
List<Option> res = new ArrayList<Option>();
// get the jetty home config from the osgi boot bundle.
res.add(CoreOptions.systemProperty("jetty.port").value(String.valueOf(DEFAULT_JETTY_HTTP_PORT)));
res.add(CoreOptions.systemProperty("jetty.home.bundle").value("org.eclipse.jetty.osgi.boot"));
res.addAll(coreJettyDependencies());
return res;
}
public static List<Option> coreJettyDependencies()
{
List<Option> res = new ArrayList<Option>();
res.add(mavenBundle().groupId( "org.eclipse.jetty.osgi" ).artifactId( "jetty-osgi-boot" ).versionAsInProject().start());
res.add(mavenBundle().groupId( "org.eclipse.jetty.orbit" ).artifactId( "javax.servlet" ).versionAsInProject().noStart());
res.add(mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "jetty-deploy" ).versionAsInProject().noStart());
res.add(mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "jetty-server" ).versionAsInProject().noStart());
res.add(mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "jetty-servlet" ).versionAsInProject().noStart());
res.add(mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "jetty-util" ).versionAsInProject().noStart());
res.add(mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "jetty-http" ).versionAsInProject().noStart());
res.add(mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "jetty-xml" ).versionAsInProject().noStart());
res.add(mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "jetty-webapp" ).versionAsInProject().noStart());
res.add(mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "jetty-io" ).versionAsInProject().noStart());
res.add(mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "jetty-continuation" ).versionAsInProject().noStart());
res.add(mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "jetty-security" ).versionAsInProject().noStart());
res.add(mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "jetty-servlets" ).versionAsInProject().noStart());
res.add(mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "jetty-client" ).versionAsInProject().noStart());
res.add(mavenBundle().groupId( "org.eclipse.jetty.websocket" ).artifactId( "websocket-core" ).versionAsInProject().noStart());
res.add(mavenBundle().groupId( "org.eclipse.jetty.websocket" ).artifactId( "websocket-server" ).versionAsInProject().noStart());
//optional:
//res.add(mavenBundle().groupId( "org.eclipse.jetty.websocket" ).artifactId( "websocket-client" ).versionAsInProject().noStart());
return res;
}
public static List<Option> httpServiceJetty()
{
List<Option> res = new ArrayList<Option>();
res.add(mavenBundle().groupId( "org.eclipse.jetty.osgi" ).artifactId( "jetty-httpservice" ).versionAsInProject().start());
res.add(mavenBundle().groupId( "org.eclipse.equinox.http" ).artifactId( "servlet" ).versionAsInProject().start());
return res;
}
@Test
public void assertAllBundlesActiveOrResolved()
{
assertAllBundlesActiveOrResolved(bundleContext);
}
/**
* You will get a list of bundles installed by default
* plus your testcase, wrapped into a bundle called pax-exam-probe
*/
@Test
public void testHttpService() throws Exception
{
testHttpServiceGreetings(bundleContext, "http", DEFAULT_JETTY_HTTP_PORT);
}
}

View File

@ -0,0 +1,122 @@
//
// ========================================================================
// Copyright (c) 1995-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.osgi.test;
import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.ops4j.pax.exam.CoreOptions;
import org.ops4j.pax.exam.Option;
import org.ops4j.pax.exam.junit.Configuration;
import org.ops4j.pax.exam.junit.JUnit4TestRunner;
import org.osgi.framework.BundleContext;
/**
* SPDY setup.
*/
@RunWith( JUnit4TestRunner.class )
public class TestJettyOSGiBootSpdy extends AbstractTestOSGi {
private static final String JETTY_SPDY_PORT = "jetty.spdy.port";
private static final int DEFAULT_JETTY_SPDY_PORT = 9877;
@Inject
private BundleContext bundleContext;
@Configuration
public Option[] config()
{
ArrayList<Option> options = new ArrayList<Option>();
addMoreOSGiContainers(options);
options.addAll(TestJettyOSGiBootCore.provisionCoreJetty());
options.addAll(TestJettyOSGiBootWithJsp.configureJettyHomeAndPort("jetty-spdy.xml"));
options.add(CoreOptions.junitBundles());
options.addAll(TestJettyOSGiBootCore.httpServiceJetty());
options.addAll(spdyJettyDependencies());
return options.toArray(new Option[options.size()]);
}
public static List<Option> spdyJettyDependencies()
{
List<Option> res = new ArrayList<Option>();
res.add(CoreOptions.systemProperty(JETTY_SPDY_PORT).value(String.valueOf(DEFAULT_JETTY_SPDY_PORT)));
//java -Xbootclasspath/p:${settings.localRepository}/org/mortbay/jetty/npn/npn-boot/${npn-version}/npn-boot-${npn-version}.jar
// res.add(CoreOptions.vmOptions("-Xbootclasspath/p:"+System.getenv("HOME")+"/.m2/repository/org/mortbay/jetty/npn/npn-boot/"+npnBootVersion+"/npn-boot-"+npnBootVersion+".jar"));
String npnBoot = System.getProperty("mortbay-npn-boot");
if (npnBoot == null)
{
throw new IllegalStateException("Please define the path to the npn boot jar as the sys property -Dmortbay-npn-boot");
//are we trying to be too nice? this kinds of work outside of maven maybe
// String npnBootUrl = mavenBundle().groupId( "org.mortbay.jetty.npn" ).artifactId( "npn-boot" ).versionAsInProject().getURL();
// String npnBootVersion = npnBootUrl.split("\\/")[2];
// if (!Character.isDigit(npnBootVersion.charAt(0)))
// {
// throw new IllegalArgumentException(npnBootUrl + " - " + npnBootVersion);
// }
// npnBoot = System.getenv("HOME")+"/.m2/repository/org/mortbay/jetty/npn/npn-boot/"+npnBootVersion+"/npn-boot-"+npnBootVersion+".jar";
}
File checkNpnBoot = new File(npnBoot);
if (!checkNpnBoot.exists())
{
throw new IllegalStateException("Unable to find the npn boot jar here: " + npnBoot);
}
res.add(CoreOptions.vmOptions("-Xbootclasspath/p:"+npnBoot));
res.add(CoreOptions.bootDelegationPackages("org.eclipse.jetty.npn"));
res.add(mavenBundle().groupId( "org.eclipse.jetty.spdy" ).artifactId( "spdy-core" ).versionAsInProject().noStart());
res.add(mavenBundle().groupId( "org.eclipse.jetty.spdy" ).artifactId( "spdy-server" ).versionAsInProject().noStart());
res.add(mavenBundle().groupId( "org.eclipse.jetty.spdy" ).artifactId( "spdy-http-server" ).versionAsInProject().noStart());
res.add(mavenBundle().groupId( "org.eclipse.jetty.spdy" ).artifactId( "spdy-client" ).versionAsInProject().noStart());
return res;
}
@Test
public void checkNpnBootOnBootstrapClasspath() throws Exception
{
Class<?> npn = Thread.currentThread().getContextClassLoader()
.loadClass("org.eclipse.jetty.npn.NextProtoNego");
Assert.assertNotNull(npn);
Assert.assertNull(npn.getClassLoader());
}
@Test
public void assertAllBundlesActiveOrResolved()
{
assertAllBundlesActiveOrResolved(bundleContext);
}
@Test
public void testSpdyOnHttpService() throws Exception
{
testHttpServiceGreetings(bundleContext, "https", DEFAULT_JETTY_SPDY_PORT);
}
}

View File

@ -0,0 +1,179 @@
//
// ========================================================================
// Copyright (c) 1995-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.osgi.test;
import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
import static org.ops4j.pax.exam.CoreOptions.options;
import static org.ops4j.pax.exam.CoreOptions.systemProperty;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import junit.framework.Assert;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.HttpContentResponse;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.osgi.boot.OSGiServerConstants;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.ops4j.pax.exam.CoreOptions;
import org.ops4j.pax.exam.Option;
import org.ops4j.pax.exam.junit.Configuration;
import org.ops4j.pax.exam.junit.JUnit4TestRunner;
import org.osgi.framework.BundleContext;
/**
* Pax-Exam to make sure the jetty-osgi-boot can be started along with the httpservice web-bundle.
* Then make sure we can deploy an OSGi service on the top of this.
*/
@RunWith( JUnit4TestRunner.class )
public class TestJettyOSGiBootWithJsp extends AbstractTestOSGi
{
private static final boolean LOGGING_ENABLED = true;
private static final boolean REMOTE_DEBUGGING = false;
@Inject
BundleContext bundleContext = null;
@Configuration
public static Option[] configure()
{
ArrayList<Option> options = new ArrayList<Option>();
addMoreOSGiContainers(options);
options.add(CoreOptions.junitBundles());
options.addAll(configureJettyHomeAndPort("jetty-selector.xml"));
options.add(CoreOptions.bootDelegationPackages("org.xml.sax", "org.xml.*",
"org.w3c.*", "javax.xml.*"));
options.addAll(TestJettyOSGiBootCore.coreJettyDependencies());
// Enable Logging
if(LOGGING_ENABLED) {
options.addAll(Arrays.asList(options(
// install log service using pax runners profile abstraction (there are more profiles, like DS)
// logProfile(),
// this is how you set the default log level when using pax logging (logProfile)
systemProperty( "org.ops4j.pax.logging.DefaultServiceLog.level" ).value( "INFO" )
)));
}
options.addAll(jspDependencies());
// Remote JDWP Debugging, this won't work with the forked container.
// if(REMOTE_DEBUGGING) {
// options.addAll(Arrays.asList(options(
// // this just adds all what you write here to java vm argumenents of the (new) osgi process.
// PaxRunnerOptions.vmOption( "-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5006" )
// )));
// }
//bug at the moment: this would make the httpservice catch all
//requests and prevent the webapp at the root context to catch any of them.
//options.addAll(TestJettyOSGiBootCore.httpServiceJetty());
return options.toArray(new Option[options.size()]);
}
public static List<Option> configureJettyHomeAndPort(String jettySelectorFileName)
{
File etcFolder = new File("src/test/config/etc");
String etc = "file://" + etcFolder.getAbsolutePath();
List<Option> options = new ArrayList<Option>();
options.add(systemProperty(OSGiServerConstants.MANAGED_JETTY_XML_CONFIG_URLS)
.value(etc + "/jetty.xml;" +
etc + "/" + jettySelectorFileName + ";" +
etc + "/jetty-deployer.xml;" +
etc + "/jetty-testrealm.xml"));
options.add(systemProperty("jetty.port").value(String.valueOf(TestJettyOSGiBootCore.DEFAULT_JETTY_HTTP_PORT)));
options.add(systemProperty("jetty.home").value(etcFolder.getParentFile().getAbsolutePath()));
return options;
}
public static List<Option> jspDependencies() {
List<Option> res = new ArrayList<Option>();
/* orbit deps */
res.add(mavenBundle().groupId( "org.eclipse.jetty.orbit" ).artifactId( "javax.servlet.jsp" ).versionAsInProject());
res.add(mavenBundle().groupId( "org.eclipse.jetty.orbit" ).artifactId( "javax.servlet.jsp.jstl" ).versionAsInProject());
res.add(mavenBundle().groupId( "org.eclipse.jetty.orbit" ).artifactId( "javax.el" ).versionAsInProject());
res.add(mavenBundle().groupId( "org.eclipse.jetty.orbit" ).artifactId( "com.sun.el" ).versionAsInProject());
res.add(mavenBundle().groupId( "org.eclipse.jetty.orbit" ).artifactId( "org.apache.jasper.glassfish" ).versionAsInProject());
res.add(mavenBundle().groupId( "org.eclipse.jetty.orbit" ).artifactId( "org.apache.taglibs.standard.glassfish" ).versionAsInProject());
res.add(mavenBundle().groupId( "org.eclipse.jetty.orbit" ).artifactId( "org.eclipse.jdt.core" ).versionAsInProject());
/* jetty-osgi deps */
res.add(mavenBundle().groupId( "org.eclipse.jetty.osgi" ).artifactId( "jetty-osgi-boot-jsp" ).versionAsInProject().noStart());
res.add(mavenBundle().groupId( "org.eclipse.jetty" ).artifactId( "test-jetty-webapp" ).classifier("webbundle").versionAsInProject());
return res;
}
@Test
public void assertAllBundlesActiveOrResolved()
{
assertAllBundlesActiveOrResolved(bundleContext);
}
//at the moment can't run httpservice with jsp at the same time.
//that is a regression in jetty-9
@Ignore
@Test
public void testHttpService() throws Exception
{
super.testHttpServiceGreetings(bundleContext, "http", TestJettyOSGiBootCore.DEFAULT_JETTY_HTTP_PORT);
}
@Test
public void testJspDump() throws Exception
{
//System.err.println("http://127.0.0.1:9876/jsp/dump.jsp sleeping....");
//Thread.currentThread().sleep(5000000);
//now test the jsp/dump.jsp
HttpClient client = new HttpClient();
try
{
client.start();
Response response = client.GET("http://127.0.0.1:"+
TestJettyOSGiBootCore.DEFAULT_JETTY_HTTP_PORT+"/jsp/dump.jsp").get(5, TimeUnit.SECONDS);
Assert.assertEquals(HttpStatus.OK_200, response.status());
String content = new String(((HttpContentResponse)response).content());
//System.err.println("content: " + content);
Assert.assertTrue(content.indexOf("<tr><th>ServletPath:</th><td>/jsp/dump.jsp</td></tr>") != -1);
}
finally
{
client.stop();
}
}
}

View File

@ -28,7 +28,9 @@
<configuration>
<instructions>
<_versionpolicy> </_versionpolicy>
<Import-Package>javax.sql.*,javax.security.*,javax.naming.*,javax.servlet.*;version="2.6.0",javax.transaction.*;version="[1.1,1.2)",*</Import-Package>
<Import-Package>javax.sql.*,javax.security.*,javax.naming.*,
javax.servlet.*;version="2.6.0",javax.transaction.*;version="[1.1,1.2)",
*</Import-Package>
</instructions>
</configuration>
</execution>

View File

@ -49,7 +49,7 @@ public class EncodingHttpWriter extends HttpWriter
public void write (char[] s,int offset, int length) throws IOException
{
if (length==0)
_out.checkAllWritten();
_out.closeIfAllContentWritten();
while (length > 0)
{

View File

@ -0,0 +1,88 @@
//
// ========================================================================
// Copyright (c) 1995-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.server;
import java.util.concurrent.CopyOnWriteArrayList;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.FilterConnection;
import org.eclipse.jetty.util.annotation.Name;
public class FilterConnectionFactory extends AbstractConnectionFactory
{
private final String _nextProtocol;
private final int _outputBufferSize;
private final CopyOnWriteArrayList<FilterConnection.Filter> _filters = new CopyOnWriteArrayList<>();
public FilterConnectionFactory()
{
this(HttpVersion.HTTP_1_1.asString());
}
public FilterConnectionFactory(String nextProtocol)
{
this(nextProtocol,16*1024);
}
public FilterConnectionFactory(@Name("nextProtocol") String nextProtocol,@Name("outputBufferSize") int outputBufferSize)
{
super("filter-"+nextProtocol);
_nextProtocol=nextProtocol;
_outputBufferSize=outputBufferSize;
}
@Override
public Connection newConnection(Connector connector, EndPoint endPoint)
{
FilterConnection filteredConnection = new FilterConnection(connector.getByteBufferPool(),connector.getExecutor(),endPoint,_outputBufferSize);
configure(filteredConnection, connector, endPoint);
addFilters(connector, filteredConnection);
ConnectionFactory next = connector.getConnectionFactory(_nextProtocol);
EndPoint filteredEndPoint = filteredConnection.getFilterEndPoint();
Connection connection = next.newConnection(connector, filteredEndPoint);
filteredEndPoint.setConnection(connection);
return filteredConnection;
}
protected void addFilters(Connector connector, FilterConnection filteredConnection)
{
for (FilterConnection.Filter filter : _filters)
filteredConnection.addFilter(filter);
}
public void addFilter(FilterConnection.Filter filter)
{
addBean(filter);
_filters.add(filter);
}
public boolean removeFilter(FilterConnection.Filter filter)
{
removeBean(filter);
return _filters.remove(filter);
}
}

View File

@ -57,6 +57,25 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
private final HttpParser _parser;
private volatile ByteBuffer _requestBuffer = null;
private volatile ByteBuffer _chunk = null;
// TODO get rid of this
private final Runnable _channelRunner = new Runnable()
{
@Override
public void run()
{
try
{
setCurrentConnection(HttpConnection.this);
_channel.run();
}
finally
{
setCurrentConnection(null);
}
}
};
public static HttpConnection getCurrentConnection()
{
@ -75,7 +94,10 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
public HttpConnection(HttpChannelConfig config, Connector connector, EndPoint endPoint)
{
super(endPoint, connector.getExecutor());
// Tell AbstractConnector executeOnFillable==false because we are guaranteeing that onfillable
// will never block nor take an excessive amount of CPU. ie it is OK for the selector thread to
// be used. In this case the thread that calls onfillable will be asked to do some IO and parsing.
super(endPoint, connector.getExecutor(),false);
_config = config;
_connector = connector;
@ -193,10 +215,10 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
while (true)
{
// Can the parser progress (even with an empty buffer)
boolean event=_parser.parseNext(_requestBuffer==null?BufferUtil.EMPTY_BUFFER:_requestBuffer);
boolean call_channel=_parser.parseNext(_requestBuffer==null?BufferUtil.EMPTY_BUFFER:_requestBuffer);
// If there is a request buffer, we are re-entering here
if (!event && BufferUtil.isEmpty(_requestBuffer))
if (!call_channel && BufferUtil.isEmpty(_requestBuffer))
{
if (_requestBuffer == null)
_requestBuffer = _bufferPool.acquire(getInputBufferSize(), false);
@ -232,11 +254,11 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
}
// Parse what we have read
event=_parser.parseNext(_requestBuffer);
call_channel=_parser.parseNext(_requestBuffer);
}
// Parse the buffer
if (event)
if (call_channel)
{
// Parse as much content as there is available before calling the channel
// this is both efficient (may queue many chunks), will correctly set available for 100 continues
@ -250,25 +272,8 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
// The parser returned true, which indicates the channel is ready to handle a request.
// Call the channel and this will either handle the request/response to completion OR,
// if the request suspends, the request/response will be incomplete so the outer loop will exit.
_channel.run();
// Return if the channel is still processing the request
if (_channel.getState().isSuspending())
{
// release buffer if no input being held.
// This is needed here to handle the case of no request input. If there
// is request input, then the release is handled by Input@onAllContentConsumed()
if (_channel.getRequest().getHttpInput().available()==0)
releaseRequestBuffer();
return;
}
// return if the connection has been changed
if (getEndPoint().getConnection()!=this)
{
releaseRequestBuffer();
return;
}
getExecutor().execute(_channelRunner);
return;
}
}
}
@ -452,54 +457,52 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
onClose();
getEndPoint().setConnection(connection);
connection.onOpen();
reset();
return;
}
}
reset();
// Is this thread dispatched from a resume ?
if (getCurrentConnection() != HttpConnection.this)
if (_parser.isStart())
{
if (_parser.isStart())
// it wants to eat more
if (_requestBuffer == null)
{
// it wants to eat more
if (_requestBuffer == null)
{
fillInterested();
}
else if (getConnector().isStarted())
{
LOG.debug("{} pipelined", this);
fillInterested();
}
else if (getConnector().isStarted())
{
LOG.debug("{} pipelined", this);
try
{
getExecutor().execute(this);
}
catch (RejectedExecutionException e)
{
if (getConnector().isStarted())
LOG.warn(e);
else
LOG.ignore(e);
getEndPoint().close();
}
}
else
try
{
getExecutor().execute(this);
}
catch (RejectedExecutionException e)
{
if (getConnector().isStarted())
LOG.warn(e);
else
LOG.ignore(e);
getEndPoint().close();
}
}
if (_parser.isClosed() && !getEndPoint().isOutputShutdown())
else
{
// TODO This is a catch all indicating some protocol handling failure
// Currently needed for requests saying they are HTTP/2.0.
// This should be removed once better error handling is in place
LOG.warn("Endpoint output not shutdown when seeking EOF");
getEndPoint().shutdownOutput();
getEndPoint().close();
}
}
if (_parser.isClosed() && !getEndPoint().isOutputShutdown())
{
// TODO This is a catch all indicating some protocol handling failure
// Currently needed for requests saying they are HTTP/2.0.
// This should be removed once better error handling is in place
LOG.warn("Endpoint output not shutdown when seeking EOF");
getEndPoint().shutdownOutput();
}
// make sure that an oshut connection is driven towards close
// TODO this is a little ugly
if (getEndPoint().isOpen() && getEndPoint().isOutputShutdown())
@ -537,7 +540,7 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
// Do we have content ready to parse?
if (BufferUtil.isEmpty(_requestBuffer))
{
{
// If no more input
if (getEndPoint().isInputShutdown())
{
@ -553,7 +556,13 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
// We will need a buffer to read into
if (_requestBuffer==null)
_requestBuffer=_bufferPool.acquire(getInputBufferSize(),false);
{
long content_length=_channel.getRequest().getContentLength();
int size=getInputBufferSize();
if (size<content_length)
size=size*4; // TODO tune this
_requestBuffer=_bufferPool.acquire(size,false);
}
// read some data
int filled=getEndPoint().fill(_requestBuffer);

View File

@ -46,13 +46,13 @@ import org.eclipse.jetty.util.resource.Resource;
*/
public class HttpOutput extends ServletOutputStream
{
private final HttpChannel _channel;
private final HttpChannel<?> _channel;
private boolean _closed;
private long _written;
private ByteBuffer _aggregate;
private int _bufferSize;
public HttpOutput(HttpChannel channel)
public HttpOutput(HttpChannel<?> channel)
{
_channel = channel;
_bufferSize = _channel.getHttpChannelConfig().getOutputBufferSize();
@ -114,9 +114,9 @@ public class HttpOutput extends ServletOutputStream
_channel.write(BufferUtil.EMPTY_BUFFER, false);
}
public boolean checkAllWritten() throws IOException
public boolean closeIfAllContentWritten() throws IOException
{
return _channel.getResponse().checkAllContentWritten(_written);
return _channel.getResponse().closeIfAllContentWritten(_written);
}
@Override
@ -169,10 +169,8 @@ public class HttpOutput extends ServletOutputStream
_written += len;
// Check if all written or full
if (!checkAllWritten() && BufferUtil.isFull(_aggregate))
{
if (!closeIfAllContentWritten() && BufferUtil.isFull(_aggregate))
_channel.write(_aggregate, false);
}
}
@Override
@ -188,7 +186,7 @@ public class HttpOutput extends ServletOutputStream
_written++;
// Check if all written or full
if (!checkAllWritten() && BufferUtil.isFull(_aggregate))
if (!closeIfAllContentWritten() && BufferUtil.isFull(_aggregate))
_channel.write(_aggregate, false);
}

View File

@ -36,7 +36,7 @@ public class Iso88591HttpWriter extends HttpWriter
{
HttpOutput out = _out;
if (length==0)
out.checkAllWritten();
out.closeIfAllContentWritten();
while (length > 0)
{

View File

@ -705,7 +705,7 @@ public class Response implements HttpServletResponse
{
try
{
checkAllContentWritten(written);
closeIfAllContentWritten(written);
}
catch(IOException e)
{
@ -714,7 +714,7 @@ public class Response implements HttpServletResponse
}
}
public boolean checkAllContentWritten(long written) throws IOException
public boolean closeIfAllContentWritten(long written) throws IOException
{
if (_contentLength >= 0 && written >= _contentLength)
{

View File

@ -133,7 +133,7 @@ public class ServerConnector extends AbstractNetworkConnector
@Name("factories") ConnectionFactory... factories)
{
super(server,executor,scheduler,pool,acceptors,factories);
_manager = new ServerConnectorManager(selectors > 0 ? selectors : Runtime.getRuntime().availableProcessors());
_manager = new ServerConnectorManager(getExecutor(), getScheduler(), selectors > 0 ? selectors : Runtime.getRuntime().availableProcessors());
addBean(_manager, true);
}
@ -346,31 +346,11 @@ public class ServerConnector extends AbstractNetworkConnector
private final class ServerConnectorManager extends SelectorManager
{
private ServerConnectorManager(int selectors)
private ServerConnectorManager(Executor executor, Scheduler scheduler, int selectors)
{
super(selectors);
super(executor, scheduler, selectors);
}
@Override
protected void execute(Runnable task)
{
getExecutor().execute(task);
}
// TODO
// @Override
// public void connectionOpened(Connection connection)
// {
// ServerConnector.this.connectionOpened(connection);
// }
// TODO
// @Override
// public void connectionClosed(Connection connection)
// {
// ServerConnector.this.connectionClosed(connection);
// }
@Override
protected SelectChannelEndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey selectionKey) throws IOException
{

View File

@ -44,7 +44,7 @@ public class Utf8HttpWriter extends HttpWriter
{
HttpOutput out = _out;
if (length==0)
out.checkAllWritten();
out.closeIfAllContentWritten();
while (length > 0)
{

View File

@ -37,7 +37,8 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.server.handler.HandlerWrapper;
import org.eclipse.jetty.toolchain.test.Stress;
import org.eclipse.jetty.toolchain.test.AdvancedRunner;
import org.eclipse.jetty.toolchain.test.annotation.Stress;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
@ -46,7 +47,9 @@ import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(AdvancedRunner.class)
public class AsyncStressTest
{
private static final Logger LOG = Log.getLogger(AsyncStressTest.class);
@ -90,17 +93,10 @@ public class AsyncStressTest
}
@Test
@Ignore
@Stress("High connection count")
public void testAsync() throws Throwable
{
if (Stress.isEnabled())
{
doConnections(1600,240);
}
else
{
doConnections(80,80);
}
doConnections(1600,240);
}
private void doConnections(int connections,final int loops) throws Throwable

View File

@ -34,16 +34,14 @@ import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.server.handler.HandlerWrapper;
import org.eclipse.jetty.toolchain.test.Stress;
import org.eclipse.jetty.util.IO;
import org.junit.After;
import org.junit.Before;
public class HttpServerTestFixture
{ // Useful constants
protected static final long PAUSE=10L;
protected static final int LOOPS=Stress.isEnabled()?250:50;
protected static final int LOOPS=50;
protected static final String HOST="localhost";
protected Server _server;

View File

@ -34,8 +34,9 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.server.handler.HandlerWrapper;
import org.eclipse.jetty.toolchain.test.AdvancedRunner;
import org.eclipse.jetty.toolchain.test.OS;
import org.eclipse.jetty.toolchain.test.Stress;
import org.eclipse.jetty.toolchain.test.annotation.Stress;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
@ -43,10 +44,10 @@ import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@Ignore
@RunWith(AdvancedRunner.class)
public class StressTest
{
private static final Logger LOG = Log.getLogger(StressTest.class);
@ -122,45 +123,42 @@ public class StressTest
@Test
@Stress("Hey, its called StressTest for a reason")
public void testMinNonPersistent() throws Throwable
{
doThreads(2,2,false);
}
@Test
@Stress("Much threading")
public void testNonPersistent() throws Throwable
{
// TODO needs to be further investigated
assumeTrue(!OS.IS_OSX || Stress.isEnabled());
assumeTrue(!OS.IS_OSX);
doThreads(10,10,false);
Thread.sleep(1000);
doThreads(20,20,false);
if (Stress.isEnabled())
{
Thread.sleep(1000);
doThreads(200,10,false);
Thread.sleep(1000);
doThreads(200,200,false);
}
Thread.sleep(1000);
doThreads(200,10,false);
Thread.sleep(1000);
doThreads(200,200,false);
}
@Test
@Stress("Much threading")
public void testPersistent() throws Throwable
{
// TODO needs to be further investigated
assumeTrue(!OS.IS_OSX || Stress.isEnabled());
assumeTrue(!OS.IS_OSX);
doThreads(10,10,true);
Thread.sleep(1000);
doThreads(40,40,true);
if (Stress.isEnabled())
{
Thread.sleep(1000);
doThreads(200,10,true);
Thread.sleep(1000);
doThreads(200,200,true);
}
Thread.sleep(1000);
doThreads(200,10,true);
Thread.sleep(1000);
doThreads(200,200,true);
}
private void doThreads(int threadCount, final int loops, final boolean persistent) throws Throwable
@ -293,33 +291,30 @@ public class StressTest
}
}
if(Stress.isEnabled())
System.out.println(" stage:\tbind\twrite\trecv\tdispatch\twrote\ttotal");
for (int q=0;q<quantums;q++)
{
System.out.println(" stage:\tbind\twrite\trecv\tdispatch\twrote\ttotal");
for (int q=0;q<quantums;q++)
{
System.out.printf("%02d00<=l<%02d00",q,(q+1));
for (int i=0;i<_latencies.length;i++)
System.out.print("\t"+count[i][q]);
System.out.println();
}
System.out.print("other ");
System.out.printf("%02d00<=l<%02d00",q,(q+1));
for (int i=0;i<_latencies.length;i++)
System.out.print("\t"+other[i]);
System.out.print("\t"+count[i][q]);
System.out.println();
System.out.print("HANDLED ");
for (int i=0;i<_latencies.length;i++)
System.out.print("\t"+_handled.get());
System.out.println();
System.out.print("TOTAL ");
for (int i=0;i<_latencies.length;i++)
System.out.print("\t"+length[i]);
System.out.println();
long ave=total/_latencies[4].size();
System.out.println("ave="+ave);
}
System.out.print("other ");
for (int i=0;i<_latencies.length;i++)
System.out.print("\t"+other[i]);
System.out.println();
System.out.print("HANDLED ");
for (int i=0;i<_latencies.length;i++)
System.out.print("\t"+_handled.get());
System.out.println();
System.out.print("TOTAL ");
for (int i=0;i<_latencies.length;i++)
System.out.print("\t"+length[i]);
System.out.println();
long ave=total/_latencies[4].size();
System.out.println("ave="+ave);
}
}

View File

@ -260,13 +260,6 @@ public class Holder<T> extends AbstractLifeCycle implements Dumpable
return _asyncSupported;
}
/* ------------------------------------------------------------ */
@Override
public String toString()
{
return _name;
}
/* ------------------------------------------------------------ */
protected void illegalStateIfContextStarted()
{
@ -282,7 +275,7 @@ public class Holder<T> extends AbstractLifeCycle implements Dumpable
@Override
public void dump(Appendable out, String indent) throws IOException
{
out.append(_name).append("==").append(_className)
out.append(toString())
.append(" - ").append(AbstractLifeCycle.getState(this)).append("\n");
ContainerLifeCycle.dump(out,indent,_initParams.entrySet());
}
@ -294,6 +287,13 @@ public class Holder<T> extends AbstractLifeCycle implements Dumpable
return ContainerLifeCycle.dump(this);
}
/* ------------------------------------------------------------ */
@Override
public String toString()
{
return String.format("%s@%x==%s",_name,hashCode(),_className);
}
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */

View File

@ -178,15 +178,10 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
*/
public void setInitOrder(int order)
{
_initOnStartup=true;
_initOnStartup=order>0;
_initOrder = order;
}
public boolean isSetInitOrder()
{
return _initOnStartup;
}
/* ------------------------------------------------------------ */
/** Comparitor by init order.
*/
@ -931,4 +926,12 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
throw se;
}
}
/* ------------------------------------------------------------ */
@Override
public String toString()
{
return String.format("%s@%x==%s,%d,%b",_name,hashCode(),_className,_initOrder,_servlet!=null);
}
}

View File

@ -119,8 +119,6 @@ public class ServletContextHandlerTest
assertEquals(2,__testServlets.get());
assertThat(holder0.getServletInstance(),nullValue());
response =_connector.getResponses("GET /test0 HTTP/1.0\r\n\r\n");
assertThat(response,containsString("200 OK"));
@ -129,6 +127,8 @@ public class ServletContextHandlerTest
_server.stop();
assertEquals(0,__testServlets.get());
holder0.setInitOrder(0);
_server.start();
assertEquals(2,__testServlets.get());
assertThat(holder0.getServletInstance(),nullValue());

View File

@ -79,6 +79,7 @@
<instructions>
<Export-Package>org.eclipse.jetty.spdy.*;version="9.0"</Export-Package>
<Import-Package>org.eclipse.jetty.*;version="[9.0,10.0)",*</Import-Package>
<_nouses>true</_nouses>
</instructions>
</configuration>
</execution>

View File

@ -46,7 +46,7 @@
<argLine>-Xbootclasspath/p:${project.build.directory}/npn/npn-boot-${npn.version}.jar</argLine>
</configuration>
</plugin>
<!-- <plugin>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<extensions>true</extensions>
@ -57,21 +57,13 @@
</goals>
<configuration>
<instructions>
<Export-Package>*</Export-Package>
<Export-Package>org.eclipse.jetty.spdy.client;version="9.0"</Export-Package>
<Import-Package>!org.eclipse.jetty.npn,org.eclipse.jetty.*;version="[9.0,10.0)",*</Import-Package>
</instructions>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
</archive>
</configuration>
</plugin>-->
</plugins>
</build>

View File

@ -36,7 +36,7 @@ import org.eclipse.jetty.util.log.Logger;
public class NextProtoNegoClientConnection extends AbstractConnection implements NextProtoNego.ClientProvider
{
private final Logger logger = Log.getLogger(getClass());
private final Logger LOG = Log.getLogger(getClass());
private final SocketChannel channel;
private final Object attachment;
private final SPDYClient client;
@ -49,7 +49,7 @@ public class NextProtoNegoClientConnection extends AbstractConnection implements
this.channel = channel;
this.attachment = attachment;
this.client = client;
this.engine=endPoint.getSslConnection().getSSLEngine();
this.engine = endPoint.getSslConnection().getSSLEngine();
NextProtoNego.put(engine, this);
}
@ -60,12 +60,12 @@ public class NextProtoNegoClientConnection extends AbstractConnection implements
try
{
getEndPoint().flush(BufferUtil.EMPTY_BUFFER);
fillInterested();
}
catch(IOException e)
{
throw new RuntimeIOException(e);
}
fillInterested();
}
@Override
@ -76,9 +76,11 @@ public class NextProtoNegoClientConnection extends AbstractConnection implements
int filled = fill();
if (filled == 0 && !completed)
fillInterested();
if (filled <= 0)
if (filled <= 0 || completed)
break;
}
if (completed)
replaceConnection();
}
private int fill()
@ -89,7 +91,7 @@ public class NextProtoNegoClientConnection extends AbstractConnection implements
}
catch (IOException x)
{
logger.debug(x);
LOG.debug(x);
getEndPoint().close();
return -1;
}
@ -105,10 +107,6 @@ public class NextProtoNegoClientConnection extends AbstractConnection implements
public void unsupported()
{
NextProtoNego.remove(engine);
// Server does not support NPN, but this is a SPDY client, so hardcode SPDY
EndPoint endPoint = getEndPoint();
Connection connection = client.getConnectionFactory().newConnection(channel, endPoint, attachment);
client.replaceConnection(endPoint, connection);
completed = true;
}
@ -116,14 +114,18 @@ public class NextProtoNegoClientConnection extends AbstractConnection implements
public String selectProtocol(List<String> protocols)
{
NextProtoNego.remove(engine);
String protocol = client.selectProtocol(protocols);
if (protocol == null)
return null;
EndPoint endPoint = getEndPoint();
Connection connection = client.getConnectionFactory().newConnection(channel, endPoint, attachment);
client.replaceConnection(endPoint, connection);
completed = true;
return protocol;
String protocol = client.selectProtocol(protocols);
return protocol == null ? null : protocol;
}
private void replaceConnection()
{
EndPoint endPoint = getEndPoint();
Connection connection = client.getConnectionFactory().newConnection(channel, endPoint, attachment);
endPoint.getConnection().onClose();
endPoint.setConnection(connection);
connection.onOpen();
completed = true;
}
}

View File

@ -153,22 +153,16 @@ public class SPDYClient
return FlowControlStrategyFactory.newFlowControlStrategy(version);
}
public void replaceConnection(EndPoint endPoint, Connection connection)
{
endPoint.getConnection().onClose();
endPoint.setConnection(connection);
connection.onOpen();
}
public static class Factory extends ContainerLifeCycle
{
private final Queue<Session> sessions = new ConcurrentLinkedQueue<>();
final ByteBufferPool bufferPool = new MappedByteBufferPool();
final Scheduler scheduler = new TimerScheduler();
final Executor executor;
private final ByteBufferPool bufferPool = new MappedByteBufferPool();
private final Scheduler scheduler;
private final Executor executor;
private final SslContextFactory sslContextFactory;
private final SelectorManager selector;
private final long idleTimeout;
private long connectTimeout = 15000;
public Factory()
{
@ -177,7 +171,7 @@ public class SPDYClient
public Factory(SslContextFactory sslContextFactory)
{
this(null, sslContextFactory);
this(null, null, sslContextFactory);
}
public Factory(Executor executor)
@ -185,29 +179,62 @@ public class SPDYClient
this(executor, null);
}
public Factory(Executor executor, SslContextFactory sslContextFactory)
public Factory(Executor executor, Scheduler scheduler)
{
this(executor, sslContextFactory, 30000);
this(executor, scheduler, null);
}
public Factory(Executor executor, SslContextFactory sslContextFactory, long idleTimeout)
public Factory(Executor executor, Scheduler scheduler, SslContextFactory sslContextFactory)
{
// TODO make this injectable
addBean(scheduler);
this(executor, scheduler, sslContextFactory, 30000);
}
public Factory(Executor executor, Scheduler scheduler, SslContextFactory sslContextFactory, long idleTimeout)
{
this.idleTimeout = idleTimeout;
if (executor == null)
executor = new QueuedThreadPool();
this.executor = executor;
addBean(executor);
if (scheduler == null)
scheduler = new TimerScheduler();
this.scheduler = scheduler;
addBean(scheduler);
this.sslContextFactory = sslContextFactory;
if (sslContextFactory != null)
addBean(sslContextFactory);
selector = new ClientSelectorManager();
selector = new ClientSelectorManager(executor, scheduler);
selector.setConnectTimeout(getConnectTimeout());
addBean(selector);
}
public ByteBufferPool getByteBufferPool()
{
return bufferPool;
}
public Scheduler getScheduler()
{
return scheduler;
}
public Executor getExecutor()
{
return executor;
}
public long getConnectTimeout()
{
return connectTimeout;
}
public void setConnectTimeout(long connectTimeout)
{
this.connectTimeout = connectTimeout;
}
public SPDYClient newSPDYClient(short version)
@ -249,6 +276,11 @@ public class SPDYClient
private class ClientSelectorManager extends SelectorManager
{
private ClientSelectorManager(Executor executor, Scheduler scheduler)
{
super(executor, scheduler);
}
@Override
protected EndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey key) throws IOException
{
@ -258,13 +290,7 @@ public class SPDYClient
if (clientIdleTimeout < 0)
clientIdleTimeout = idleTimeout;
return new SelectChannelEndPoint(channel, selectSet, key, scheduler, clientIdleTimeout);
}
@Override
protected void execute(Runnable task)
{
executor.execute(task);
return new SelectChannelEndPoint(channel, selectSet, key, getScheduler(), clientIdleTimeout);
}
@Override
@ -278,9 +304,9 @@ public class SPDYClient
if (sslContextFactory != null)
{
final SSLEngine engine = client.newSSLEngine(sslContextFactory, channel);
SslConnection sslConnection = new SslConnection(bufferPool, executor, endPoint, engine);
SslConnection sslConnection = new SslConnection(bufferPool, getExecutor(), endPoint, engine);
DecryptedEndPoint sslEndPoint = sslConnection.getDecryptedEndPoint();
NextProtoNegoClientConnection connection = new NextProtoNegoClientConnection(channel, sslEndPoint, attachment, client.factory.executor, client);
NextProtoNegoClientConnection connection = new NextProtoNegoClientConnection(channel, sslEndPoint, attachment, getExecutor(), client);
sslEndPoint.setConnection(connection);
return sslConnection;
}

View File

@ -39,16 +39,17 @@ public class SPDYClientConnectionFactory
SessionPromise sessionPromise = (SessionPromise)attachment;
SPDYClient client = sessionPromise.client;
Factory factory = client.factory;
ByteBufferPool bufferPool = factory.getByteBufferPool();
CompressionFactory compressionFactory = new StandardCompressionFactory();
Parser parser = new Parser(compressionFactory.newDecompressor());
Generator generator = new Generator(factory.bufferPool, compressionFactory.newCompressor());
Generator generator = new Generator(bufferPool, compressionFactory.newCompressor());
SPDYConnection connection = new ClientSPDYConnection(endPoint, factory.bufferPool, parser, factory);
SPDYConnection connection = new ClientSPDYConnection(endPoint, bufferPool, parser, factory);
FlowControlStrategy flowControlStrategy = client.newFlowControlStrategy();
StandardSession session = new StandardSession(client.version, factory.bufferPool, factory.executor, factory.scheduler, connection, connection, 1, sessionPromise.listener, generator, flowControlStrategy);
StandardSession session = new StandardSession(client.version, bufferPool, factory.getExecutor(), factory.getScheduler(), connection, connection, 1, sessionPromise.listener, generator, flowControlStrategy);
session.setWindowSize(client.getInitialWindowSize());
parser.addListener(session);
sessionPromise.completed(session);
@ -65,7 +66,7 @@ public class SPDYClientConnectionFactory
public ClientSPDYConnection(EndPoint endPoint, ByteBufferPool bufferPool, Parser parser, Factory factory)
{
super(endPoint, bufferPool, parser, factory.executor);
super(endPoint, bufferPool, parser, factory.getExecutor());
this.factory = factory;
}

View File

@ -52,7 +52,8 @@ public class SPDYConnection extends AbstractConnection implements Controller<Sta
public SPDYConnection(EndPoint endPoint, ByteBufferPool bufferPool, Parser parser, Executor executor,int bufferSize)
{
super(endPoint, executor);
// TODO explain why we are passing false here
super(endPoint, executor, false);
this.bufferPool = bufferPool;
this.parser = parser;
onIdle(true);

View File

@ -14,38 +14,6 @@
<bundle-symbolic-name>${project.groupId}.core</bundle-symbolic-name>
</properties>
<build>
<plugins>
<!-- <plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<extensions>true</extensions>
<executions>
<execution>
<goals>
<goal>manifest</goal>
</goals>
<configuration>
<instructions>
<Import-Package>javax.net.*,*</Import-Package>
<Export-Package>*</Export-Package>
</instructions>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
</archive>
</configuration>
</plugin>-->
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.eclipse.jetty</groupId>

View File

@ -18,85 +18,9 @@
package org.eclipse.jetty.spdy;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.eclipse.jetty.util.FutureCallback;
import org.eclipse.jetty.util.Callback;
/**
* <p>A {@link Promise} is a {@link Future} that allows a result or a failure to be set,
* so that the {@link Future} will be {@link #isDone() done}.</p>
*
* @param <T> the type of the result object
*/
public class Promise<T> implements Callback<T>, Future<T>
@Deprecated
public class Promise<T> extends FutureCallback<T>
{
private final CountDownLatch latch = new CountDownLatch(1);
private boolean cancelled;
private Throwable failure;
private T promise;
@Override
public void completed(T result)
{
this.promise = result;
latch.countDown();
}
@Override
public void failed(T context, Throwable x)
{
this.failure = x;
latch.countDown();
}
@Override
public boolean cancel(boolean mayInterruptIfRunning)
{
cancelled = true;
latch.countDown();
return true;
}
@Override
public boolean isCancelled()
{
return cancelled;
}
@Override
public boolean isDone()
{
return cancelled || latch.getCount() == 0;
}
@Override
public T get() throws InterruptedException, ExecutionException
{
latch.await();
return result();
}
@Override
public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException
{
boolean elapsed = !latch.await(timeout, unit);
if (elapsed)
throw new TimeoutException();
return result();
}
private T result() throws ExecutionException
{
if (isCancelled())
throw new CancellationException();
Throwable failure = this.failure;
if (failure != null)
throw new ExecutionException(failure);
return promise;
}
}

View File

@ -45,15 +45,21 @@
<argLine>-Xbootclasspath/p:${project.build.directory}/npn/npn-boot-${npn.version}.jar</argLine>
</configuration>
</plugin>
<plugin>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<extensions>true</extensions>
<executions>
<execution>
<goals>
<goal>manifest</goal>
</goals>
<configuration>
<instructions>
<Import-Package>org.eclipse.jetty.npn;version="0";resolution:=optional,*</Import-Package>
<Export-Package>*</Export-Package>
<Export-Package>org.eclipse.jetty.spdy.server.http;version="9.0",
org.eclipse.jetty.spdy.server.proxy;version="9.0"</Export-Package>
<Import-Package>!org.eclipse.jetty.npn,org.eclipse.jetty.*;version="[9.0,10.0)",*</Import-Package>
<_nouses>true</_nouses>
</instructions>
</configuration>
</execution>

View File

@ -34,10 +34,10 @@ import org.eclipse.jetty.spdy.client.SPDYClient;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.junit.After;
import org.junit.Rule;
import org.junit.rules.TestWatchman;
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.model.FrameworkMethod;
@RunWith(Parameterized.class)
public abstract class AbstractHTTPSPDYTest
@ -49,15 +49,16 @@ public abstract class AbstractHTTPSPDYTest
}
@Rule
public final TestWatchman testName = new TestWatchman()
public final TestWatcher testName = new TestWatcher()
{
@Override
public void starting(FrameworkMethod method)
public void starting(Description description)
{
super.starting(method);
super.starting(description);
System.err.printf("Running %s.%s()%n",
method.getMethod().getDeclaringClass().getName(),
method.getName());
description.getClassName(),
description.getMethodName());
}
};
@ -114,7 +115,7 @@ public abstract class AbstractHTTPSPDYTest
protected SPDYClient.Factory newSPDYClientFactory(Executor threadPool)
{
return new SPDYClient.Factory(threadPool, null, connector.getIdleTimeout());
return new SPDYClient.Factory(threadPool, null, null, connector.getIdleTimeout());
}
@After

View File

@ -36,21 +36,22 @@ import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestWatchman;
import org.junit.runners.model.FrameworkMethod;
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;
public class ProtocolNegotiationTest
{
@Rule
public final TestWatchman testName = new TestWatchman()
public final TestWatcher testName = new TestWatcher()
{
@Override
public void starting(FrameworkMethod method)
public void starting(Description description)
{
super.starting(method);
super.starting(description);
System.err.printf("Running %s.%s()%n",
method.getMethod().getDeclaringClass().getName(),
method.getName());
description.getClassName(),
description.getMethodName());
}
};

View File

@ -52,7 +52,7 @@ public class SSLExternalServerTest extends AbstractHTTPSPDYTest
SslContextFactory sslContextFactory = new SslContextFactory();
// Force TLSv1
sslContextFactory.setIncludeProtocols("TLSv1");
return new SPDYClient.Factory(threadPool, sslContextFactory, 30000);
return new SPDYClient.Factory(threadPool, null, sslContextFactory, 30000);
}
@Test

View File

@ -57,25 +57,26 @@ import org.junit.Before;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestWatchman;
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.model.FrameworkMethod;
@Ignore // TODO: make these tests pass
@RunWith(value = Parameterized.class)
public class ProxyHTTPSPDYTest
{
@Rule
public final TestWatchman testName = new TestWatchman()
public final TestWatcher testName = new TestWatcher()
{
@Override
public void starting(FrameworkMethod method)
public void starting(Description description)
{
super.starting(method);
super.starting(description);
System.err.printf("Running %s.%s()%n",
method.getMethod().getDeclaringClass().getName(),
method.getName());
description.getClassName(),
description.getMethodName());
}
};
private final short version;

View File

@ -1,3 +1,5 @@
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
#org.eclipse.jetty.spdy.LEVEL=DEBUG
#org.eclipse.jetty.server.LEVEL=DEBUG
#org.eclipse.jetty.io.ssl.LEVEL=DEBUG
#org.eclipse.jetty.spdy.server.LEVEL=DEBUG

View File

@ -46,7 +46,7 @@
<argLine>-Xbootclasspath/p:${project.build.directory}/npn/npn-boot-${npn.version}.jar</argLine>
</configuration>
</plugin>
<!-- <plugin>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<extensions>true</extensions>
@ -57,22 +57,14 @@
</goals>
<configuration>
<instructions>
<Import-Package>javax.net.ssl.*,*</Import-Package>
<Export-Package>*</Export-Package>
<Export-Package>org.eclipse.jetty.spdy.server;version="9.0"</Export-Package>
<Import-Package>!org.eclipse.jetty.npn,org.eclipse.jetty.*;version="[9.0,10.0)",*</Import-Package>
<_nouses>true</_nouses>
</instructions>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
</archive>
</configuration>
</plugin> -->
</plugins>
</build>

View File

@ -23,8 +23,12 @@ import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import javax.net.ssl.SSLEngine;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.FilterConnection;
import org.eclipse.jetty.io.ssl.SslConnection;
import org.eclipse.jetty.io.ssl.SslConnection.DecryptedEndPoint;
import org.eclipse.jetty.npn.NextProtoNego;
import org.eclipse.jetty.server.AbstractConnectionFactory;
@ -96,8 +100,20 @@ public class NPNServerConnectionFactory extends AbstractConnectionFactory
String dft=_defaultProtocol;
if (dft==null)
dft=_protocols.get(0);
SSLEngine engine=null;
EndPoint ep=endPoint;
while(engine==null && ep!=null)
{
if (ep instanceof SslConnection.DecryptedEndPoint)
engine=((SslConnection.DecryptedEndPoint)ep).getSslConnection().getSSLEngine();
if (ep instanceof FilterConnection.FilteredEndPoint) // TODO make more generic
ep=((FilterConnection.FilteredEndPoint)ep).getWrappedEndPoint();
else
ep=null;
}
return configure(new NextProtoNegoServerConnection((DecryptedEndPoint)endPoint, connector,protocols,_defaultProtocol),connector,endPoint);
return configure(new NextProtoNegoServerConnection(endPoint, engine, connector,protocols,_defaultProtocol),connector,endPoint);
}
@Override

View File

@ -35,23 +35,21 @@ import org.eclipse.jetty.util.log.Logger;
public class NextProtoNegoServerConnection extends AbstractConnection implements NextProtoNego.ServerProvider
{
private final Logger logger = Log.getLogger(getClass());
private final Logger LOG = Log.getLogger(getClass());
private final Connector connector;
private final SSLEngine engine;
private final List<String> protocols;
private final String defaultProtocol;
private boolean completed; // No need to be volatile: it is modified and read by the same thread
private String nextProtocol; // No need to be volatile: it is modified and read by the same thread
public NextProtoNegoServerConnection(DecryptedEndPoint endPoint, Connector connector, List<String>protocols, String defaultProtocol)
public NextProtoNegoServerConnection(EndPoint endPoint, SSLEngine engine, Connector connector, List<String>protocols, String defaultProtocol)
{
super(endPoint, connector.getExecutor());
this.connector = connector;
this.protocols = protocols;
this.defaultProtocol=defaultProtocol;
engine = endPoint.getSslConnection().getSSLEngine();
NextProtoNego.put(engine,this);
this.defaultProtocol = defaultProtocol;
this.engine = engine;
NextProtoNego.put(engine, this);
}
@Override
@ -61,23 +59,29 @@ public class NextProtoNegoServerConnection extends AbstractConnection implements
fillInterested();
}
@Override
public void onClose()
{
super.onClose();
}
@Override
public void onFillable()
{
while (true)
{
int filled = fill();
if (filled == 0 && !completed)
if (filled == 0 && nextProtocol == null)
fillInterested();
if (filled <= 0 || completed)
if (filled <= 0 || nextProtocol != null)
break;
}
if (nextProtocol != null)
{
ConnectionFactory connectionFactory = connector.getConnectionFactory(nextProtocol);
EndPoint endPoint = getEndPoint();
Connection oldConnection = endPoint.getConnection();
oldConnection.onClose();
Connection connection = connectionFactory.newConnection(connector, endPoint);
LOG.debug("{} switching from {} to {}", this, oldConnection, connection);
endPoint.setConnection(connection);
getEndPoint().getConnection().onOpen();
}
}
private int fill()
@ -88,7 +92,7 @@ public class NextProtoNegoServerConnection extends AbstractConnection implements
}
catch (IOException x)
{
logger.debug(x);
LOG.debug(x);
getEndPoint().close();
return -1;
}
@ -109,13 +113,8 @@ public class NextProtoNegoServerConnection extends AbstractConnection implements
@Override
public void protocolSelected(String protocol)
{
LOG.debug("{} protocol selected {}", this, protocol);
nextProtocol = protocol;
NextProtoNego.remove(engine);
ConnectionFactory connectionFactory = connector.getConnectionFactory(protocol);
EndPoint endPoint = getEndPoint();
endPoint.getConnection().onClose();
Connection connection = connectionFactory.newConnection(connector, endPoint);
endPoint.setConnection(connection);
connection.onOpen();
completed = true;
}
}

View File

@ -46,7 +46,7 @@ public class SSLEngineLeakTest extends AbstractTest
protected SPDYClient.Factory newSPDYClientFactory(Executor threadPool)
{
SslContextFactory sslContextFactory = newSslContextFactory();
return new SPDYClient.Factory(threadPool, sslContextFactory);
return new SPDYClient.Factory(threadPool, null, sslContextFactory);
}
@Test

View File

@ -41,7 +41,7 @@ public class SSLSynReplyTest extends SynReplyTest
protected SPDYClient.Factory newSPDYClientFactory(Executor threadPool)
{
SslContextFactory sslContextFactory = newSslContextFactory();
return new SPDYClient.Factory(threadPool, sslContextFactory);
return new SPDYClient.Factory(threadPool, null, sslContextFactory);
}
@Before

View File

@ -24,6 +24,14 @@ public abstract class ExecutorCallback<C> implements Callback<C>
{
private final ForkInvoker<C> _invoker;
private final Executor _executor;
private final Runnable _onComplete=new Runnable()
{
@Override
public void run()
{
onCompleted(null);
}
};
public ExecutorCallback(Executor executor)
{
@ -33,14 +41,32 @@ public abstract class ExecutorCallback<C> implements Callback<C>
public ExecutorCallback(Executor executor, int maxRecursion)
{
_executor = executor;
_invoker = new ExecutorCallbackInvoker(maxRecursion);
_invoker = maxRecursion>0?new ExecutorCallbackInvoker(maxRecursion):null;
if (_executor==null)
throw new IllegalArgumentException();
}
@Override
public final void completed(final C context)
public void completed(final C context)
{
// Should we execute?
if (alwaysDispatchCompletion())
if (_invoker==null)
{
if (context==null)
_executor.execute(_onComplete);
else
{
_executor.execute(new Runnable()
{
@Override
public void run()
{
onCompleted(context);
}
});
}
}
else if (alwaysDispatchCompletion())
{
_invoker.fork(context);
}
@ -53,7 +79,7 @@ public abstract class ExecutorCallback<C> implements Callback<C>
protected abstract void onCompleted(C context);
@Override
public final void failed(final C context, final Throwable x)
public void failed(final C context, final Throwable x)
{
// Always execute failure
Runnable runnable = new Runnable()

View File

@ -35,7 +35,7 @@ import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.util.thread.Scheduler;
import org.eclipse.jetty.util.thread.TimerScheduler;
import org.eclipse.jetty.websocket.client.internal.ConnectionManager;
import org.eclipse.jetty.websocket.client.internal.IWebSocketClient;
import org.eclipse.jetty.websocket.client.internal.DefaultWebSocketClient;
import org.eclipse.jetty.websocket.core.api.Extension;
import org.eclipse.jetty.websocket.core.api.ExtensionRegistry;
import org.eclipse.jetty.websocket.core.api.WebSocketPolicy;
@ -127,7 +127,7 @@ public class WebSocketClientFactory extends ContainerLifeCycle
/**
* The address to bind local physical (outgoing) TCP Sockets to.
*
*
* @return the address to bind the socket channel to
* @see #setBindAddress(SocketAddress)
*/
@ -190,7 +190,7 @@ public class WebSocketClientFactory extends ContainerLifeCycle
{
LOG.debug("Creating new WebSocket for {}",websocketPojo);
EventDriver websocket = eventDriverFactory.wrap(websocketPojo);
return new IWebSocketClient(this,websocket);
return new DefaultWebSocketClient(this,websocket);
}
public boolean sessionClosed(WebSocketSession session)
@ -204,17 +204,6 @@ public class WebSocketClientFactory extends ContainerLifeCycle
{
LOG.debug("Session Opened: {}",session);
}
// FIXME: what is going on?
// if (!isRunning())
// {
// LOG.debug("Factory.isRunning: {}",this.isRunning());
// LOG.debug("Factory.isStarted: {}",this.isStarted());
// LOG.debug("Factory.isStarting: {}",this.isStarting());
// LOG.debug("Factory.isStopped: {}",this.isStopped());
// LOG.debug("Factory.isStopping: {}",this.isStopping());
// LOG.warn("Factory is not running");
// return false;
// }
boolean ret = sessions.offer(session);
session.onConnect();
return ret;

View File

@ -19,13 +19,19 @@
package org.eclipse.jetty.websocket.client.internal;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import org.eclipse.jetty.util.B64Code;
import org.eclipse.jetty.util.QuotedStringTokenizer;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.core.api.UpgradeRequest;
import org.eclipse.jetty.websocket.core.protocol.ExtensionConfig;
@ -34,18 +40,58 @@ import org.eclipse.jetty.websocket.core.protocol.ExtensionConfig;
*/
public class ClientUpgradeRequest implements UpgradeRequest
{
public static final String COOKIE_DELIM = "\"\\\n\r\t\f\b%+ ;=";
private final static Logger LOG = Log.getLogger(ClientUpgradeRequest.class);
private static final String HEADER_VALUES_DELIM = "\"\\\n\r\t\f\b%+ ;=";
private static final Set<String> FORBIDDEN_HEADERS;
static
{
// headers not allowed to be set in ClientUpgradeRequest.headers
FORBIDDEN_HEADERS = new HashSet<>();
FORBIDDEN_HEADERS.add("cookie");
FORBIDDEN_HEADERS.add("upgrade");
FORBIDDEN_HEADERS.add("host");
FORBIDDEN_HEADERS.add("connection");
FORBIDDEN_HEADERS.add("sec-websocket-key");
FORBIDDEN_HEADERS.add("sec-websocket-extensions");
FORBIDDEN_HEADERS.add("sec-websocket-accept");
FORBIDDEN_HEADERS.add("sec-websocket-protocol");
FORBIDDEN_HEADERS.add("sec-websocket-version");
}
private final String key;
private List<String> subProtocols;
private List<ExtensionConfig> extensions;
private Map<String, String> cookies;
private Map<String, String> headers;
private String httpEndPointName;
private String host;
public ClientUpgradeRequest()
{
byte[] bytes = new byte[16];
new Random().nextBytes(bytes);
this.key = new String(B64Code.encode(bytes));
this.subProtocols = new ArrayList<>();
this.extensions = new ArrayList<>();
this.cookies = new HashMap<>();
this.headers = new HashMap<>();
}
@Override
public void addExtensions(String... extConfigs)
{
for (String extConfig : extConfigs)
{
extensions.add(ExtensionConfig.parse(extConfig));
}
}
public String generate(URI uri)
{
this.httpEndPointName = uri.toASCIIString();
this.host = uri.getHost();
StringBuilder request = new StringBuilder(512);
request.append("GET ");
if (StringUtil.isBlank(uri.getPath()))
@ -62,36 +108,88 @@ public class ClientUpgradeRequest implements UpgradeRequest
}
request.append(" HTTP/1.1\r\n");
request.append("Host: ").append(uri.getHost());
request.append("Host: ").append(this.host);
if (uri.getPort() > 0)
{
request.append(':').append(uri.getPort());
}
request.append("\r\n");
// WebSocket specifics
request.append("Upgrade: websocket\r\n");
request.append("Connection: Upgrade\r\n");
request.append("Sec-WebSocket-Key: ").append(key).append("\r\n");
if (StringUtil.isNotBlank(getOrigin()))
{
request.append("Origin: ").append(getOrigin()).append("\r\n");
}
request.append("Sec-WebSocket-Version: 13\r\n"); // RFC-6455 specified version
Map<String, String> cookies = getCookieMap();
if ((cookies != null) && (cookies.size() > 0))
// Extensions
if (!getExtensions().isEmpty())
{
for (String cookie : cookies.keySet())
request.append("Sec-WebSocket-Extensions: ");
boolean needDelim = false;
for (ExtensionConfig ext : getExtensions())
{
request.append("Cookie: ");
request.append(QuotedStringTokenizer.quoteIfNeeded(cookie,COOKIE_DELIM));
request.append("=");
request.append(QuotedStringTokenizer.quoteIfNeeded(cookies.get(cookie),COOKIE_DELIM));
request.append("\r\n");
if (needDelim)
{
request.append(", ");
}
request.append(ext.getParameterizedName());
needDelim = true;
}
request.append("\r\n");
}
// Sub Protocols
if (!getSubProtocols().isEmpty())
{
request.append("Sec-WebSocket-Protocol: ");
boolean needDelim = false;
for (String protocol : getSubProtocols())
{
if (needDelim)
{
request.append(", ");
}
request.append(protocol);
needDelim = true;
}
request.append("\r\n");
}
// Cookies
if (!getCookieMap().isEmpty())
{
request.append("Cookie: ");
boolean needDelim = false;
for (String cookie : getCookieMap().keySet())
{
if (needDelim)
{
request.append("; ");
}
request.append(QuotedStringTokenizer.quoteIfNeeded(cookie,HEADER_VALUES_DELIM));
request.append("=");
String val = cookies.get(cookie);
request.append(QuotedStringTokenizer.quoteIfNeeded(val,HEADER_VALUES_DELIM));
needDelim = true;
}
request.append("\r\n");
}
// Other headers
for (String key : headers.keySet())
{
String value = headers.get(key);
if (FORBIDDEN_HEADERS.contains(key.toLowerCase()))
{
LOG.warn("Skipping forbidden header - {}: {}",key,value);
continue; // skip
}
request.append(key).append(": ");
request.append(QuotedStringTokenizer.quoteIfNeeded(value,HEADER_VALUES_DELIM));
request.append("\r\n");
}
// request header end
request.append("\r\n");
return request.toString();
}
@ -99,36 +197,31 @@ public class ClientUpgradeRequest implements UpgradeRequest
@Override
public Map<String, String> getCookieMap()
{
// TODO Auto-generated method stub
return null;
return cookies;
}
@Override
public List<ExtensionConfig> getExtensions()
{
// TODO Auto-generated method stub
return null;
return extensions;
}
@Override
public String getHeader(String name)
{
// TODO Auto-generated method stub
return null;
return headers.get(name);
}
@Override
public String getHost()
{
// TODO Auto-generated method stub
return null;
return this.host;
}
@Override
public String getHttpEndPointName()
{
// TODO Auto-generated method stub
return null;
return httpEndPointName;
}
public String getKey()
@ -139,36 +232,45 @@ public class ClientUpgradeRequest implements UpgradeRequest
@Override
public String getOrigin()
{
// TODO Auto-generated method stub
return null;
return getHeader("Origin");
}
@Override
public List<String> getSubProtocols()
{
// TODO Auto-generated method stub
return null;
return subProtocols;
}
@Override
public boolean hasSubProtocol(String test)
{
// TODO Auto-generated method stub
for (String protocol : subProtocols)
{
if (protocol.equalsIgnoreCase(test))
{
return true;
}
}
return false;
}
@Override
public boolean isOrigin(String test)
{
// TODO Auto-generated method stub
return false;
return test.equalsIgnoreCase(getOrigin());
}
@Override
public void setSubProtocols(String string)
public void setSubProtocols(String protocols)
{
// TODO Auto-generated method stub
this.subProtocols.clear();
if (StringUtil.isBlank(protocols))
{
return;
}
for (String protocol : protocols.split("\\s*,\\s*"))
{
this.subProtocols.add(protocol);
}
}
}

View File

@ -77,7 +77,7 @@ public class ClientUpgradeResponse implements UpgradeResponse
@Override
public Iterator<String> getHeaderValues(String name)
{
List<String> values = headers.getValues(name);
List<String> values = headers.getValues(name.toLowerCase());
if (values == null)
{
return Collections.emptyIterator();

View File

@ -85,6 +85,7 @@ public class ConnectionManager extends ContainerLifeCycle
public ConnectionManager(ByteBufferPool bufferPool, Executor executor, Scheduler scheduler, SslContextFactory sslContextFactory,
WebSocketPolicy policy)
{
// TODO: configure connect timeout
selector = new WebSocketClientSelectorManager(bufferPool,executor,scheduler,policy);
selector.setSslContextFactory(sslContextFactory);
addBean(selector);
@ -106,7 +107,7 @@ public class ConnectionManager extends ContainerLifeCycle
}
}
public FutureCallback<UpgradeResponse> connectPhysical(IWebSocketClient client) throws IOException
public FutureCallback<UpgradeResponse> connectPhysical(DefaultWebSocketClient client) throws IOException
{
SocketChannel channel = SocketChannel.open();
SocketAddress bindAddress = client.getFactory().getBindAddress();

View File

@ -38,9 +38,9 @@ import org.eclipse.jetty.websocket.core.io.event.EventDriver;
/**
* WebSocketClient for working with Upgrade (request and response), and establishing connections to the websocket URI of your choice.
*/
public class IWebSocketClient extends FutureCallback<UpgradeResponse> implements WebSocketClient
public class DefaultWebSocketClient extends FutureCallback<UpgradeResponse> implements WebSocketClient
{
private static final Logger LOG = Log.getLogger(IWebSocketClient.class);
private static final Logger LOG = Log.getLogger(DefaultWebSocketClient.class);
private final WebSocketClientFactory factory;
private final WebSocketPolicy policy;
@ -57,7 +57,7 @@ public class IWebSocketClient extends FutureCallback<UpgradeResponse> implements
private ClientUpgradeResponse upgradeResponse;
private Masker masker;
public IWebSocketClient(WebSocketClientFactory factory, EventDriver websocket)
public DefaultWebSocketClient(WebSocketClientFactory factory, EventDriver websocket)
{
this.factory = factory;
LOG.debug("factory.isRunning(): {}",factory.isRunning());

View File

@ -38,7 +38,7 @@ import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.websocket.client.internal.ClientUpgradeRequest;
import org.eclipse.jetty.websocket.client.internal.ClientUpgradeResponse;
import org.eclipse.jetty.websocket.client.internal.IWebSocketClient;
import org.eclipse.jetty.websocket.client.internal.DefaultWebSocketClient;
import org.eclipse.jetty.websocket.core.api.Extension;
import org.eclipse.jetty.websocket.core.api.UpgradeException;
import org.eclipse.jetty.websocket.core.api.UpgradeResponse;
@ -81,11 +81,11 @@ public class UpgradeConnection extends AbstractConnection
private static final Logger LOG = Log.getLogger(UpgradeConnection.class);
private final ByteBufferPool bufferPool;
private final IWebSocketClient client;
private final DefaultWebSocketClient client;
private final HttpResponseHeaderParser parser;
private ClientUpgradeRequest request;
public UpgradeConnection(EndPoint endp, Executor executor, IWebSocketClient client)
public UpgradeConnection(EndPoint endp, Executor executor, DefaultWebSocketClient client)
{
super(endp,executor);
this.client = client;
@ -230,6 +230,9 @@ public class UpgradeConnection extends AbstractConnection
// Connect extensions
if (extensions != null)
{
connection.getParser().configureFromExtensions(extensions);
connection.getGenerator().configureFromExtensions(extensions);
Iterator<Extension> extIter;
// Connect outgoings
extIter = extensions.iterator();
@ -238,23 +241,6 @@ public class UpgradeConnection extends AbstractConnection
Extension ext = extIter.next();
ext.setNextOutgoingFrames(outgoing);
outgoing = ext;
// Handle RSV reservations
if (ext.useRsv1())
{
connection.getGenerator().setRsv1InUse(true);
connection.getParser().setRsv1InUse(true);
}
if (ext.useRsv2())
{
connection.getGenerator().setRsv2InUse(true);
connection.getParser().setRsv2InUse(true);
}
if (ext.useRsv3())
{
connection.getGenerator().setRsv3InUse(true);
connection.getParser().setRsv3InUse(true);
}
}
// Connect incomings

View File

@ -23,7 +23,7 @@ import java.util.concurrent.Executor;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.websocket.client.WebSocketClientFactory;
import org.eclipse.jetty.websocket.client.internal.IWebSocketClient;
import org.eclipse.jetty.websocket.client.internal.DefaultWebSocketClient;
import org.eclipse.jetty.websocket.client.masks.Masker;
import org.eclipse.jetty.websocket.core.io.AbstractWebSocketConnection;
import org.eclipse.jetty.websocket.core.protocol.WebSocketFrame;
@ -34,11 +34,11 @@ import org.eclipse.jetty.websocket.core.protocol.WebSocketFrame;
public class WebSocketClientConnection extends AbstractWebSocketConnection
{
private final WebSocketClientFactory factory;
private final IWebSocketClient client;
private final DefaultWebSocketClient client;
private final Masker masker;
private boolean connected;
public WebSocketClientConnection(EndPoint endp, Executor executor, IWebSocketClient client)
public WebSocketClientConnection(EndPoint endp, Executor executor, DefaultWebSocketClient client)
{
super(endp,executor,client.getFactory().getScheduler(),client.getPolicy(),client.getFactory().getBufferPool());
this.client = client;
@ -47,7 +47,7 @@ public class WebSocketClientConnection extends AbstractWebSocketConnection
this.masker = client.getMasker();
}
public IWebSocketClient getClient()
public DefaultWebSocketClient getClient()
{
return client;
}

View File

@ -35,33 +35,23 @@ import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.Scheduler;
import org.eclipse.jetty.websocket.client.WebSocketClientFactory;
import org.eclipse.jetty.websocket.client.internal.IWebSocketClient;
import org.eclipse.jetty.websocket.client.internal.DefaultWebSocketClient;
import org.eclipse.jetty.websocket.core.api.WebSocketPolicy;
public class WebSocketClientSelectorManager extends SelectorManager
{
private static final Logger LOG = Log.getLogger(WebSocketClientSelectorManager.class);
private final Executor executor;
private final Scheduler scheduler;
private final WebSocketPolicy policy;
private final ByteBufferPool bufferPool;
private SslContextFactory sslContextFactory;
public WebSocketClientSelectorManager(ByteBufferPool bufferPool, Executor executor, Scheduler scheduler, WebSocketPolicy policy)
{
super();
super(executor, scheduler);
this.bufferPool = bufferPool;
this.executor = executor;
this.scheduler = scheduler;
this.policy = policy;
}
@Override
protected void execute(Runnable task)
{
this.executor.execute(task);
}
public SslContextFactory getSslContextFactory()
{
return sslContextFactory;
@ -71,7 +61,7 @@ public class WebSocketClientSelectorManager extends SelectorManager
public Connection newConnection(final SocketChannel channel, EndPoint endPoint, final Object attachment) throws IOException
{
LOG.debug("newConnection({},{},{})",channel,endPoint,attachment);
IWebSocketClient client = (IWebSocketClient)attachment;
DefaultWebSocketClient client = (DefaultWebSocketClient)attachment;
try
{
@ -83,7 +73,7 @@ public class WebSocketClientSelectorManager extends SelectorManager
if (sslContextFactory != null)
{
SSLEngine engine = newSSLEngine(sslContextFactory,channel);
SslConnection sslConnection = new SslConnection(bufferPool,executor,endPoint,engine);
SslConnection sslConnection = new SslConnection(bufferPool,getExecutor(),endPoint,engine);
EndPoint sslEndPoint = sslConnection.getDecryptedEndPoint();
Connection connection = newUpgradeConnection(channel,sslEndPoint,client);
@ -116,7 +106,7 @@ public class WebSocketClientSelectorManager extends SelectorManager
protected EndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey selectionKey) throws IOException
{
LOG.debug("newEndPoint({}, {}, {})",channel,selectSet,selectionKey);
return new SelectChannelEndPoint(channel,selectSet,selectionKey,scheduler,policy.getIdleTimeout());
return new SelectChannelEndPoint(channel,selectSet,selectionKey,getScheduler(),policy.getIdleTimeout());
}
public SSLEngine newSSLEngine(SslContextFactory sslContextFactory, SocketChannel channel)
@ -128,7 +118,7 @@ public class WebSocketClientSelectorManager extends SelectorManager
return engine;
}
public UpgradeConnection newUpgradeConnection(SocketChannel channel, EndPoint endPoint, IWebSocketClient client)
public UpgradeConnection newUpgradeConnection(SocketChannel channel, EndPoint endPoint, DefaultWebSocketClient client)
{
WebSocketClientFactory factory = client.getFactory();
Executor executor = factory.getExecutor();

View File

@ -368,6 +368,8 @@ public class BlockheadServer
// Connect extensions
if (!extensions.isEmpty())
{
generator.configureFromExtensions(extensions);
Iterator<Extension> extIter;
// Connect outgoings
extIter = extensions.iterator();
@ -376,20 +378,6 @@ public class BlockheadServer
Extension ext = extIter.next();
ext.setNextOutgoingFrames(outgoing);
outgoing = ext;
// Handle RSV reservations
if (ext.useRsv1())
{
generator.setRsv1InUse(true);
}
if (ext.useRsv2())
{
generator.setRsv2InUse(true);
}
if (ext.useRsv3())
{
generator.setRsv3InUse(true);
}
}
// Connect incomings

View File

@ -0,0 +1,133 @@
//
// ========================================================================
// Copyright (c) 1995-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.websocket.client.examples;
import java.net.URI;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.util.FutureCallback;
import org.eclipse.jetty.websocket.client.WebSocketClient;
import org.eclipse.jetty.websocket.client.WebSocketClientFactory;
import org.eclipse.jetty.websocket.core.annotations.OnWebSocketClose;
import org.eclipse.jetty.websocket.core.annotations.OnWebSocketConnect;
import org.eclipse.jetty.websocket.core.annotations.OnWebSocketMessage;
import org.eclipse.jetty.websocket.core.annotations.WebSocket;
import org.eclipse.jetty.websocket.core.api.StatusCode;
import org.eclipse.jetty.websocket.core.api.WebSocketConnection;
/**
* Example of a simple Echo Client.
*/
public class SimpleEchoClient
{
@WebSocket
public static class SimpleEchoSocket
{
private final CountDownLatch closeLatch;
@SuppressWarnings("unused")
private WebSocketConnection conn;
public SimpleEchoSocket()
{
this.closeLatch = new CountDownLatch(1);
}
public boolean awaitClose(int duration, TimeUnit unit) throws InterruptedException
{
return this.closeLatch.await(duration,unit);
}
@OnWebSocketClose
public void onClose(int statusCode, String reason)
{
System.out.printf("Connection closed: %d - %s%n",statusCode,reason);
this.conn = null;
this.closeLatch.countDown(); // trigger latch
}
@OnWebSocketConnect
public void onConnect(WebSocketConnection conn)
{
System.out.printf("Got connect: %s%n",conn);
this.conn = conn;
try
{
FutureCallback<Void> callback = new FutureCallback<>();
conn.write(null,callback,"Hello");
callback.get(2,TimeUnit.SECONDS); // wait for send to complete.
callback = new FutureCallback<>();
conn.write(null,callback,"Thanks for the conversation.");
callback.get(2,TimeUnit.SECONDS); // wait for send to complete.
conn.close(StatusCode.NORMAL,"I'm done");
}
catch (Throwable t)
{
t.printStackTrace();
}
}
@OnWebSocketMessage
public void onMessage(String msg)
{
System.out.printf("Got msg: %s%n",msg);
}
}
public static void main(String[] args)
{
String destUri = "ws://echo.websocket.org";
if (args.length > 0)
{
destUri = args[0];
}
WebSocketClientFactory factory = new WebSocketClientFactory();
SimpleEchoSocket socket = new SimpleEchoSocket();
try
{
factory.start();
WebSocketClient client = factory.newWebSocketClient(socket);
URI echoUri = new URI(destUri);
System.out.printf("Connecting to : %s%n",echoUri);
client.getUpgradeRequest().addExtensions("x-webkit-deflate-frame");
client.connect(echoUri);
// wait for closed socket connection.
socket.awaitClose(5,TimeUnit.SECONDS);
}
catch (Throwable t)
{
t.printStackTrace();
}
finally
{
try
{
factory.stop();
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
}

View File

@ -1,7 +1,7 @@
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
org.eclipse.jetty.LEVEL=WARN
# org.eclipse.jetty.io.ChannelEndPoint.LEVEL=INFO
org.eclipse.jetty.websocket.LEVEL=WARN
# org.eclipse.jetty.websocket.LEVEL=WARN
# org.eclipse.jetty.websocket.LEVEL=DEBUG
# org.eclipse.jetty.websocket.client.TrackingSocket.LEVEL=DEBUG
# Hide the stacktraces

Some files were not shown because too many files have changed in this diff Show More