package org.xins.common.servlet.container;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.FileNameMap;
import java.net.Socket;
import java.net.URLConnection;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.http.impl.EnglishReasonPhraseCatalog;
import org.xins.common.MandatoryArgumentChecker;
import org.xins.common.Utils;
import org.xins.common.text.ParseException;
class HTTPQueryHandler extends Thread {
private static final String REQUEST_ENCODING = "ISO-8859-1";
private static final FileNameMap MIME_TYPES_MAP = URLConnection.getFileNameMap();
private static final String CRLF = "\r\n";
private static AtomicInteger _instanceNumber = new AtomicInteger();
private final Socket _client;
private final Map _servlets;
public HTTPQueryHandler(Socket client, Map servlets) throws IllegalArgumentException {
MandatoryArgumentChecker.check("client", client, "servlets", servlets);
_client = client;
_servlets = servlets;
_instanceNumber.incrementAndGet();
setName("XINS Query handler #" + _instanceNumber.get());
}
public void run() {
try {
serviceClient(_client);
} catch (Exception ex) {
Utils.logIgnoredException(ex);
} finally {
try {
_client.close();
} catch (Throwable exception) {
}
}
}
public void serviceClient(Socket client)
throws IllegalArgumentException, IOException {
MandatoryArgumentChecker.check("client", client);
InputStream inbound = client.getInputStream();
OutputStream outbound = client.getOutputStream();
try {
httpQuery(inbound, outbound);
} finally{
if (inbound != null) {
try {
inbound.close();
} catch (Throwable exception) {
Utils.logIgnoredException(exception);
}
}
if (outbound != null) {
try {
outbound.close();
} catch (Throwable exception) {
Utils.logIgnoredException(exception);
}
}
}
}
public void httpQuery(InputStream in, OutputStream out)
throws IOException {
char[] buffer = new char[16384];
BufferedReader inReader = new BufferedReader(new InputStreamReader(in, REQUEST_ENCODING));
int lengthRead = inReader.read(buffer);
if (lengthRead < 0) {
sendBadRequest(out);
return;
}
String request = new String(buffer, 0, lengthRead);
int eolIndex = request.indexOf(CRLF);
if (eolIndex < 0) {
sendBadRequest(out);
return;
}
String line = request.substring(0, eolIndex);
request = request.substring(eolIndex + 2);
if (! (line.endsWith(" HTTP/1.1") || line.endsWith(" HTTP/1.0"))) {
sendBadRequest(out);
return;
}
line = line.substring(0, line.length() - 9);
int spaceIndex = line.indexOf(' ');
if (spaceIndex < 1) {
sendBadRequest(out);
return;
}
String method = line.substring(0, spaceIndex);
String url = line.substring(spaceIndex + 1);
if ("".equals(url)) {
sendBadRequest(out);
return;
} else if ("GET".equals(method) || "HEAD".equals(method) || "OPTIONS".equals(method)) {
url = url.replace(',', '&');
}
if ("GET".equals(method) && url.endsWith("/") && getClass().getResource(url + "index.html") != null) {
url += "index.html";
}
HashMap inHeaders = new HashMap();
boolean done = false;
while (! done) {
int nextEOL = request.indexOf(CRLF);
if (nextEOL <= 0) {
done = true;
} else {
try {
parseHeader(inHeaders, request.substring(0, nextEOL));
} catch (ParseException exception) {
sendBadRequest(out);
return;
}
request = request.substring(nextEOL + 2);
}
}
String body = "".equals(request)
? ""
: request.substring(2);
String responseEncoding = REQUEST_ENCODING;
boolean getMethod = method.equals("GET") || method.equals("HEAD");
String httpResult;
if (getMethod && url.indexOf('?') == -1 && !url.endsWith("/") && !"*".equals(url)) {
httpResult = readWebPage(url);
} else {
String inContentType = getHeader(inHeaders, "Content-Type");
if ((inContentType == null || inContentType.startsWith("application/x-www-form-urlencoded")) &&
body.length() > 0) {
url += '?' + body;
body = null;
}
String virtualPath = url;
if (virtualPath.indexOf('?') != -1) {
virtualPath = virtualPath.substring(0, url.indexOf('?'));
}
if (virtualPath.endsWith("/") && virtualPath.length() > 1) {
virtualPath = virtualPath.substring(0, virtualPath.length() - 1);
}
LocalServletHandler servlet = findServlet(virtualPath);
if (servlet == null) {
sendError(out, "404 Not Found");
return;
} else {
XINSServletResponse response = servlet.query(method, url, body, inHeaders);
StringBuffer sbHttpResult = new StringBuffer();
sbHttpResult.append("HTTP/1.1 " + response.getStatus() + " " +
EnglishReasonPhraseCatalog.INSTANCE.getReason(response.getStatus(), Locale.ENGLISH) + CRLF);
Map<String, String> outHeaders = response.getHeaders();
for (Map.Entry<String, String> header : outHeaders.entrySet()) {
String nextHeader = header.getKey();
String headerValue = header.getValue();
if (headerValue != null) {
sbHttpResult.append(nextHeader + ": " + headerValue + "\r\n");
}
}
String result = response.getResult();
if (result != null) {
responseEncoding = response.getCharacterEncoding();
int length = response.getContentLength();
if (length < 0) {
length = result.getBytes(responseEncoding).length;
}
sbHttpResult.append("Content-Length: " + length + "\r\n");
sbHttpResult.append("Connection: close\r\n");
sbHttpResult.append("\r\n");
sbHttpResult.append(result);
}
httpResult = sbHttpResult.toString();
}
}
byte[] bytes = httpResult.getBytes(responseEncoding);
out.write(bytes, 0, bytes.length);
out.flush();
}
private LocalServletHandler findServlet(String path)
throws NullPointerException {
if ("*".equals(path)) {
path = "/";
}
if (path.charAt(path.length() - 1) != '/') {
path += '/';
}
LocalServletHandler servlet;
do {
servlet = (LocalServletHandler) _servlets.get(path);
if (servlet == null) {
int lastPos = path.length() - 1;
if (path.charAt(lastPos) == '/') {
path = path.substring(0, lastPos);
}
if (path.length() > 0) {
int i = path.lastIndexOf('/');
path = path.substring(0, i + 1);
}
}
} while (servlet == null && path.length() > 0);
return servlet;
}
private void sendError(OutputStream out, String status)
throws IOException {
String httpResult = "HTTP/1.1 " + status + CRLF + CRLF;
byte[] bytes = httpResult.getBytes(REQUEST_ENCODING);
out.write(bytes, 0, bytes.length);
out.flush();
}
private void sendBadRequest(OutputStream out)
throws IOException {
sendError(out, "400 Bad Request");
}
private static void parseHeader(HashMap headers, String header)
throws ParseException{
int index = header.indexOf(':');
if (index < 1) {
throw new ParseException();
}
String key = header.substring(0, index);
String value = header.substring(index + 1);
key = key.toUpperCase();
value = value.trim();
if (headers.get(key) != null) {
throw new ParseException();
}
headers.put(key, value);
}
String getHeader(HashMap headers, String key) {
return (String) headers.get(key.toUpperCase());
}
private String readWebPage(String url) throws IOException {
String httpResult;
if (getClass().getResource(url) != null) {
InputStream urlInputStream = getClass().getResourceAsStream(url);
ByteArrayOutputStream contentOutputStream = new ByteArrayOutputStream();
byte[] buf = new byte[8192];
int len;
while ((len = urlInputStream.read(buf)) > 0) {
contentOutputStream.write(buf, 0, len);
}
contentOutputStream.close();
urlInputStream.close();
String content = contentOutputStream.toString("ISO-8859-1");
httpResult = "HTTP/1.1 200 OK\r\n";
String fileName = url.substring(url.lastIndexOf('/') + 1);
httpResult += "Content-Type: " + MIME_TYPES_MAP.getContentTypeFor(fileName) + "\r\n";
int length = content.getBytes("ISO-8859-1").length;
httpResult += "Content-Length: " + length + "\r\n";
httpResult += "Connection: close\r\n";
httpResult += "\r\n";
httpResult += content;
} else {
httpResult = "HTTP/1.1 404 Not Found\r\n";
}
return httpResult;
}
}