blob: e5d5b2821f8b09767057b62c5fbd5a7704357741 [file] [log] [blame]
package adaptorlib;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
abstract class AbstractHandler implements HttpHandler {
private static final Logger LOG
= Logger.getLogger(AbstractHandler.class.getName());
// Numbers for logging incoming and completed communications.
private int numberConnectionStarted = 0;
private int numberConnectionFinished = 0;
/**
* The hostname is sometimes needed to generate the correct DocId; in the case
* that it is needed and the host is an old HTTP/1.0 client, this value will
* be used.
*/
protected final String fallbackHostname;
/**
* Default encoding to encode simple response messages.
*/
protected final Charset defaultEncoding;
/**
* @param fallbackHostname Fallback hostname in case we talk to an old HTTP
* client
* @param defaultEncoding Encoding to use when sending simple text responses
*/
protected AbstractHandler(String fallbackHostname, Charset defaultEncoding) {
this.fallbackHostname = fallbackHostname;
this.defaultEncoding = defaultEncoding;
}
protected String getLoggableRequestHeaders(HttpExchange ex) {
StringBuilder sb = new StringBuilder();
for (Map.Entry<String,List<String>> me
: ex.getRequestHeaders().entrySet()) {
for (String value : me.getValue()) {
sb.append(me.getKey());
sb.append(": ");
sb.append(value);
sb.append(", ");
}
}
// Cut off trailing ", "
return sb.substring(0, sb.length() - 2);
}
protected void logRequest(HttpExchange ex) {
if (LOG.isLoggable(Level.FINER)) {
LOG.log(Level.FINER, "Received {1} request to {0}. Headers: '{'{2}'}'",
new Object[] {ex.getRequestURI(), ex.getRequestMethod(),
getLoggableRequestHeaders(ex)});
}
}
/**
* Best-effort attempt to reform the identical URI the client used to
* contact the server.
*/
protected URI getRequestUri(HttpExchange ex) {
String host = ex.getRequestHeaders().getFirst("Host");
if (host == null) {
// Client must be using HTTP/1.0
host = fallbackHostname;
}
URI base;
try {
base = new URI("http", host, "/", null, null);
} catch (URISyntaxException e) {
throw new IllegalStateException(e);
}
URI requestedUri = ex.getRequestURI();
// If uri is already absolute (e.g., a proxy is involved), then this
// does nothing, otherwise it resolves the URI for us based on who we
// think we are
requestedUri = base.resolve(requestedUri);
LOG.log(Level.FINER, "Resolved original URI to: {0}", requestedUri);
return requestedUri;
}
/**
* Sends response to GSA. Should only be used when the request method is
* HEAD.
*/
protected void respondToHead(HttpExchange ex, int code, String contentType)
throws IOException {
ex.getResponseHeaders().set("Transfer-Encoding", "chunked");
respond(ex, code, contentType, (byte[])null);
}
/**
* Sends cheaply-generated response message to GSA. This is intended for use
* with pre-build, canned messages. It automatically handles not sending the
* actual content when the request method is HEAD. If the content requires
* a moderate amount of work to produce, then you should manually call
* {@link #respond} or {@link #respondToHead} depending on the situation.
*/
protected void cannedRespond(HttpExchange ex, int code, String contentType,
String response) throws IOException {
if ("HEAD".equals(ex.getRequestMethod())) {
respondToHead(ex, code, contentType);
} else {
respond(ex, code, contentType, response.getBytes(defaultEncoding));
}
}
/**
* Sends response to GSA. Should not be used directly if the request method
* is HEAD.
*/
protected void respond(HttpExchange ex, int code, String contentType,
byte response[]) throws IOException {
// TODO(johnfelton) : For now, don't set the content type.
// ex.getResponseHeaders().set("Content-Type", contentType);
if (response == null) {
// No body. Required for HEAD requests
ex.sendResponseHeaders(code, -1);
} else {
// Chuncked encoding
ex.sendResponseHeaders(code, 0);
OutputStream responseBody = ex.getResponseBody();
LOG.finest("before writing response");
responseBody.write(response);
responseBody.flush();
responseBody.close();
LOG.finest("after writing response");
}
ex.close();
LOG.finest("after closing exchange");
}
protected abstract void meteredHandle(HttpExchange ex) throws IOException;
/**
* Performs entry counting, calls {@link #meteredHandle}, and performs exit
* counting. Also logs.
*/
public void handle(HttpExchange ex) throws IOException {
try {
synchronized(this) {
numberConnectionStarted++;
LOG.log(Level.FINE, "begining in={0},out={1}", new Object[] {
numberConnectionStarted, numberConnectionFinished});
}
logRequest(ex);
LOG.log(Level.FINE, "Processing request with {0}",
this.getClass().getName());
meteredHandle(ex);
} catch (Exception e) {
LOG.log(Level.WARNING,
"Unexpected exception propagated to top-level request handling",
e);
} finally {
synchronized(this) {
numberConnectionFinished++;
LOG.log(Level.FINE, "ending in={0},out={1}", new Object[] {
numberConnectionStarted, numberConnectionFinished});
}
}
}
}