package com.limegroup.gnutella;
import java.io.IOException;
import java.net.Socket;
import java.util.Iterator;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.Header;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpException;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.HttpResponseInterceptor;
import org.apache.http.HttpStatus;
import org.apache.http.nio.NHttpConnection;
import org.apache.http.nio.entity.ConsumingNHttpEntity;
import org.apache.http.nio.protocol.NHttpRequestHandler;
import org.apache.http.nio.protocol.SimpleNHttpRequestHandler;
import org.apache.http.protocol.HttpContext;
import org.limewire.core.settings.SharingSettings;
import org.limewire.http.BasicHttpAcceptor;
import org.limewire.http.HttpAcceptorListener;
import org.limewire.http.auth.AuthenticationInterceptor;
import org.limewire.http.reactor.HttpIOSession;
import org.limewire.inject.EagerSingleton;
import org.limewire.nio.NIODispatcher;
import org.limewire.statistic.Statistic;
import com.google.inject.Inject;
import com.limegroup.gnutella.http.HTTPConnectionData;
import com.limegroup.gnutella.http.HttpContextParams;
import com.limegroup.gnutella.statistics.TcpBandwidthStatistics;
import com.limegroup.gnutella.statistics.TcpBandwidthStatistics.StatisticType;
import com.limegroup.gnutella.util.LimeWireUtils;
/**
* Processes HTTP requests for Gnutella uploads.
*/
@EagerSingleton
public class HTTPAcceptor extends BasicHttpAcceptor {
private static final Log LOG = LogFactory.getLog(HTTPAcceptor.class);
private static final String[] SUPPORTED_METHODS = new String[] { "GET", "HEAD", };
private final NHttpRequestHandler notFoundHandler;
@Inject
public HTTPAcceptor(TcpBandwidthStatistics tcpBandwidthStatistics,
AuthenticationInterceptor requestAuthenticator) {
super(createDefaultParams(LimeWireUtils.getHttpServer(),
Constants.TIMEOUT),
requestAuthenticator,
SUPPORTED_METHODS);
this.notFoundHandler = new SimpleNHttpRequestHandler() {
public ConsumingNHttpEntity entityRequest(HttpEntityEnclosingRequest request,
HttpContext context) throws HttpException, IOException {
return null;
}
@Override
public void handle(HttpRequest request, HttpResponse response,
HttpContext context) throws HttpException, IOException {
response.setReasonPhrase("Feature Not Active");
response.setStatusCode(HttpStatus.SC_NOT_FOUND);
}
};
addAcceptorListener(new ConnectionEventListener());
if(tcpBandwidthStatistics != null)
addResponseInterceptor(new HeaderStatisticTracker(tcpBandwidthStatistics));
else
LOG.warn("Not tracking TCP header bandwidth!");
inititalizeDefaultHandlers();
}
@Inject
void register(org.limewire.lifecycle.ServiceRegistry registry) {
registry.register(this);
}
@Override
public String getServiceName() {
return org.limewire.i18n.I18nMarker.marktr("HTTP Request Listening");
}
private void inititalizeDefaultHandlers() {
registerHandler("/browser-control", notFoundHandler);
registerHandler("/gnutella/file-view*", notFoundHandler);
registerHandler("/gnutella/res/*", notFoundHandler);
// return 400 for unmatched requests
registerHandler("*", new SimpleNHttpRequestHandler() {
public ConsumingNHttpEntity entityRequest(HttpEntityEnclosingRequest request,
HttpContext context) throws HttpException, IOException {
return null;
}
@Override
public void handle(HttpRequest request, HttpResponse response,
HttpContext context) throws HttpException, IOException {
response.setStatusCode(HttpStatus.SC_BAD_REQUEST);
}
});
}
/**
* Handles an incoming HTTP push request. This needs to be called from the
* NIO thread.
*/
public void acceptConnection(Socket socket, HTTPConnectionData data) {
assert NIODispatcher.instance().isDispatchThread();
if (getReactor() == null) {
LOG.warn("Received upload request before reactor was initialized");
return;
}
NHttpConnection conn = getReactor().acceptConnection(null, socket);
if (conn != null)
HttpContextParams.setConnectionData(conn.getContext(), data);
}
/**
* Returns a handler that responds with a HTTP 404 error.
*/
public NHttpRequestHandler getNotFoundHandler() {
return notFoundHandler;
}
/**
* Forwards events from the underlying protocol layer to acceptor event
* listeners.
*/
private static class ConnectionEventListener implements HttpAcceptorListener {
public void connectionOpen(NHttpConnection conn) {
}
public void connectionClosed(NHttpConnection conn) {
}
public void connectionTimeout(NHttpConnection conn) {
}
public void fatalIOException(IOException e, NHttpConnection conn) {
}
public void fatalProtocolException(HttpException e, NHttpConnection conn) {
}
public void responseSent(NHttpConnection conn, HttpResponse response) {
HttpIOSession session = HttpContextParams.getIOSession(conn.getContext());
session.setSocketTimeout(SharingSettings.PERSISTENT_HTTP_CONNECTION_TIMEOUT.getValue());
session.setThrottle(null);
conn.requestInput(); // make sure the new socket timeout is used.
}
}
/**
* Tracks the bandwidth used when sending a response.
*/
private static class HeaderStatisticTracker implements HttpResponseInterceptor {
private final Statistic headerUpstream;
HeaderStatisticTracker(TcpBandwidthStatistics tcpBandwidthStatistics) {
this.headerUpstream = tcpBandwidthStatistics.getStatistic(StatisticType.HTTP_HEADER_UPSTREAM);
}
/*
* XXX iterating over all headers is rather inefficient since the size
* of the headers is known in
* DefaultNHttpServerConnection.submitResponse() but can't be easily
* made accessible
*/
public void process(HttpResponse response, HttpContext context) throws HttpException,
IOException {
for (Iterator it = response.headerIterator(); it.hasNext();) {
Header header = (Header) it.next();
headerUpstream.addData(header.getName().length() + 2 + header.getValue().length());
}
}
}
}