package org.limewire.http;
import java.io.IOException;
import java.net.Socket;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.HttpException;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.HttpResponse;
import org.apache.http.HttpResponseInterceptor;
import org.apache.http.impl.DefaultConnectionReuseStrategy;
import org.apache.http.impl.DefaultHttpResponseFactory;
import org.apache.http.impl.nio.DefaultServerIOEventDispatch;
import org.apache.http.nio.NHttpConnection;
import org.apache.http.nio.protocol.NHttpRequestHandler;
import org.apache.http.nio.protocol.NHttpRequestHandlerRegistry;
import org.apache.http.nio.reactor.IOEventDispatch;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
import org.apache.http.protocol.HTTP;
import org.apache.http.protocol.HttpProcessor;
import org.apache.http.protocol.HttpRequestHandler;
import org.apache.http.protocol.HttpRequestHandlerRegistry;
import org.apache.http.protocol.ResponseContent;
import org.apache.http.protocol.ResponseDate;
import org.apache.http.protocol.ResponseServer;
import org.limewire.http.auth.AuthenticationInterceptor;
import org.limewire.http.protocol.ExtendedAsyncNHttpServiceHandler;
import org.limewire.http.protocol.HttpServiceEventListener;
import org.limewire.http.protocol.LimeResponseConnControl;
import org.limewire.http.protocol.SynchronizedHttpProcessor;
import org.limewire.http.protocol.SynchronizedNHttpRequestHandlerRegistry;
import org.limewire.http.reactor.DefaultDispatchedIOReactor;
import org.limewire.http.reactor.DispatchedIOReactor;
import org.limewire.lifecycle.Service;
import org.limewire.net.ConnectionAcceptor;
import org.limewire.net.ConnectionDispatcher;
import org.limewire.nio.NIODispatcher;
import org.limewire.service.ErrorService;
/**
* Processes HTTP requests which are forwarded to {@link HttpRequestHandler}
* objects that can be registered for a URL pattern.
* <p>
* The acceptor uses HttpCore and LimeWire's HTTP component for connection
* handling. <code>BasicHttpAcceptor</code> needs to be started by invoking
* {@link #start(ConnectionDispatcher)} in order to accept connection.
*/
public abstract class BasicHttpAcceptor implements ConnectionAcceptor, Service {
private static final Log LOG = LogFactory.getLog(BasicHttpAcceptor.class);
public static final String[] DEFAULT_METHODS = new String[] { "GET",
"HEAD", "POST", };
private final AuthenticationInterceptor authenticationInterceptor;
private final String[] supportedMethods;
private final NHttpRequestHandlerRegistry registry;
private final SynchronizedHttpProcessor processor;
private final List<HttpAcceptorListener> acceptorListeners = new CopyOnWriteArrayList<HttpAcceptorListener>();
private final HttpParams params;
private DispatchedIOReactor reactor;
private ConnectionEventListener connectionListener;
private DefaultHttpResponseFactory responseFactory;
private AtomicBoolean started = new AtomicBoolean();
public BasicHttpAcceptor(HttpParams params,
AuthenticationInterceptor authenticationInterceptor,
String... supportedMethods) {
this.params = params;
this.authenticationInterceptor = authenticationInterceptor;
this.supportedMethods = supportedMethods;
this.registry = new SynchronizedNHttpRequestHandlerRegistry();
this.processor = new SynchronizedHttpProcessor();
initializeDefaultInterceptors();
}
private void initializeDefaultInterceptors() {
// order doesn't play a role
addRequestInterceptor(authenticationInterceptor);
addResponseInterceptor(new ResponseDate());
addResponseInterceptor(new ResponseServer());
addResponseInterceptor(new ResponseContent());
addResponseInterceptor(new LimeResponseConnControl());
}
public static HttpParams createDefaultParams(String userAgent, int timeout) {
BasicHttpParams params = new BasicHttpParams();
params.setIntParameter(HttpConnectionParams.SO_TIMEOUT, timeout);
params.setIntParameter(HttpConnectionParams.CONNECTION_TIMEOUT, timeout);
// size of the per connection buffers used for headers and by the
// decoder/encoder
params.setIntParameter(HttpConnectionParams.SOCKET_BUFFER_SIZE,
8 * 1024);
params.setBooleanParameter(HttpConnectionParams.TCP_NODELAY, true);
params.setParameter(HttpProtocolParams.ORIGIN_SERVER, userAgent);
params.setIntParameter(HttpConnectionParams.MAX_LINE_LENGTH, 4096);
params.setIntParameter(HttpConnectionParams.MAX_HEADER_COUNT, 50);
params.setParameter(HttpProtocolParams.HTTP_ELEMENT_CHARSET,
HTTP.ISO_8859_1);
return params;
}
/**
* Note: Needs to be called from the NIODispatcher thread.
*/
private void initializeReactor() {
assert NIODispatcher.instance().isDispatchThread();
this.connectionListener = new ConnectionEventListener();
responseFactory = new DefaultHttpResponseFactory();
ExtendedAsyncNHttpServiceHandler serviceHandler = new ExtendedAsyncNHttpServiceHandler(processor,
responseFactory, new DefaultConnectionReuseStrategy(), params);
serviceHandler.setEventListener(connectionListener);
serviceHandler.setHandlerResolver(this.registry);
this.reactor = new DefaultDispatchedIOReactor(params, NIODispatcher.instance().getScheduledExecutorService());
IOEventDispatch ioEventDispatch = new DefaultServerIOEventDispatch(
serviceHandler, params);
try {
this.reactor.execute(ioEventDispatch);
} catch (IOException e) {
// can not happen
throw new RuntimeException("Unexpected exception", e);
}
}
public void acceptConnection(String word, Socket socket) {
reactor.acceptConnection(word + " ", socket);
}
public boolean isBlocking() {
return false;
}
/**
* Returns the supported HTTP methods, e.g. "GET" or "HEAD".
*/
public String[] getHttpMethods() {
return supportedMethods;
}
/**
* Adds a listener for acceptor events.
*/
public void addAcceptorListener(HttpAcceptorListener listener) {
acceptorListeners.add(listener);
}
/**
* Adds an interceptor for incoming requests.
*
* @see HttpProcessor
*/
public void addRequestInterceptor(HttpRequestInterceptor interceptor) {
processor.addInterceptor(interceptor);
}
/**
* Adds an interceptor for outgoing responses.
*
* @see HttpProcessor
*/
public void addResponseInterceptor(HttpResponseInterceptor interceptor) {
processor.addInterceptor(interceptor);
}
/**
* Returns the reactor.
*
* <p>Note: Needs to be called from the NIODispatcher thread.
*
* @return null, if the acceptor has not been started, yet.
*/
protected DispatchedIOReactor getReactor() {
assert NIODispatcher.instance().isDispatchThread();
return reactor;
}
/**
* Removes <code>listener</code> from the list of acceptor listeners.
*
* @see #addAcceptorListener(HttpAcceptorListener)
*/
public void removeAcceptorListener(HttpAcceptorListener listener) {
acceptorListeners.remove(listener);
}
/**
* Removes an interceptor for incoming requests.
*
* @see #addRequestInterceptor(HttpRequestInterceptor)
*/
public void removeRequestInterceptor(HttpRequestInterceptor interceptor) {
processor.removeInterceptor(interceptor);
}
/**
* Adds an interceptor for outgoing responses.
*
* @see #addResponseInterceptor(HttpResponseInterceptor)
*/
public void removeResponseInterceptor(HttpResponseInterceptor interceptor) {
processor.removeInterceptor(interceptor);
}
/**
* Registers a request handler for a request pattern. See
* {@link HttpRequestHandlerRegistry} for a description of valid patterns.
* <p>
* If a request matches multiple handlers, the handler with the longer
* pattern is preferred.
* <p>
* Only a single handler may be registered per pattern.
*
* @param pattern the URI pattern to handle requests for
* @param handler the handler that processes the request
*/
public void registerHandler(final String pattern,
final NHttpRequestHandler handler) {
registry.register(pattern, authenticationInterceptor.getGuardedHandler(pattern, handler));
}
/**
* Unregisters the handlers for <code>pattern</code>.
*
* @see #registerHandler(String, HttpRequestHandler)
*/
public void unregisterHandler(final String pattern) {
authenticationInterceptor.unregisterHandler(pattern);
registry.unregister(pattern);
}
/**
* Initializes the reactor.
*
* @see #stop()
*/
public void start() {
if (started.getAndSet(true)) {
throw new IllegalStateException();
}
final AtomicBoolean inited = new AtomicBoolean(false);
try {
Future<?> result = NIODispatcher.instance().getScheduledExecutorService().submit(new Runnable() {
public void run() {
initializeReactor();
inited.set(true);
}
});
// wait for reactor to finish initialization
result.get();
} catch (InterruptedException e) {
if (inited.get())
LOG.warn("Interrupted while waiting for reactor initialization", e);
else
ErrorService.error(e); // this is a problem.
} catch (ExecutionException e) {
ErrorService.error(e);
}
}
/**
* @see #start()
*/
public void stop() {
if (!started.getAndSet(false)) {
throw new IllegalStateException();
}
}
public void initialize() {}
public String getServiceName() {
return null;
}
/**
* Forwards events from the underlying protocol layer to acceptor event
* listeners.
*/
private class ConnectionEventListener implements HttpServiceEventListener {
public void connectionOpen(NHttpConnection conn) {
assert NIODispatcher.instance().isDispatchThread();
for (HttpAcceptorListener listener : acceptorListeners) {
listener.connectionOpen(conn);
}
}
public void connectionClosed(NHttpConnection conn) {
assert NIODispatcher.instance().isDispatchThread();
for (HttpAcceptorListener listener : acceptorListeners) {
listener.connectionClosed(conn);
}
}
public void connectionTimeout(NHttpConnection conn) {
// should never happen since LimeWire will close the socket on
// timeouts which will trigger a connectionClosed() event
throw new RuntimeException();
}
public void fatalIOException(IOException e, NHttpConnection conn) {
assert NIODispatcher.instance().isDispatchThread();
LOG.debug("HTTP connection error", e);
for (HttpAcceptorListener listener : acceptorListeners) {
listener.connectionClosed(conn);
}
}
public void fatalProtocolException(HttpException e, NHttpConnection conn) {
assert NIODispatcher.instance().isDispatchThread();
LOG.debug("HTTP protocol error", e);
for (HttpAcceptorListener listener : acceptorListeners) {
listener.connectionClosed(conn);
}
}
public void responseSent(NHttpConnection conn, HttpResponse response) {
assert NIODispatcher.instance().isDispatchThread();
for (HttpAcceptorListener listener : acceptorListeners) {
listener.responseSent(conn, response);
}
}
}
}