/**
* Copyright (c) 2009 - 2011 AppWork UG(haftungsbeschränkt) <e-mail@appwork.org>
*
* This file is part of org.appwork.utils.net.httpserver
*
* This software is licensed under the Artistic License 2.0,
* see the LICENSE file or http://www.opensource.org/licenses/artistic-license-2.0.php
* for details
*/
package org.appwork.utils.net.httpserver;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.URLDecoder;
import java.nio.ByteBuffer;
import java.util.LinkedList;
import org.appwork.net.protocol.http.HTTPConstants;
import org.appwork.net.protocol.http.HTTPConstants.ResponseCode;
import org.appwork.utils.Exceptions;
import org.appwork.utils.Regex;
import org.appwork.utils.logging.Log;
import org.appwork.utils.net.HTTPHeader;
import org.appwork.utils.net.HeaderCollection;
import org.appwork.utils.net.httpconnection.HTTPConnectionUtils;
import org.appwork.utils.net.httpserver.requests.GetRequest;
import org.appwork.utils.net.httpserver.requests.HttpRequest;
import org.appwork.utils.net.httpserver.requests.PostRequest;
import org.appwork.utils.net.httpserver.responses.HttpResponse;
/**
* @author daniel
*
*/
public class HttpConnection implements Runnable {
public static LinkedList<String[]> parseParameterList(final String requestedParameters) throws IOException {
final LinkedList<String[]> requestedURLParameters = new LinkedList<String[]>();
if (requestedParameters != null) {
/* build requestedParamters */
final String[] parameters = requestedParameters.split("&(?!#)");
for (final String parameter : parameters) {
/* we only want the first = be parsed */
final String params[] = parameter.split("=", 2);
if (params.length == 1) {
/* no value */
requestedURLParameters.add(new String[] { URLDecoder.decode(params[0], "UTF-8"), null });
} else {
/* key = value */
requestedURLParameters.add(new String[] { URLDecoder.decode(params[0], "UTF-8"), URLDecoder.decode(params[1], "UTF-8") });
}
}
}
return requestedURLParameters;
}
private final HttpServer server;
private final Socket clientSocket;
private final Thread thread;
private boolean responseHeadersSent = false;
private HttpResponse response = null;
public HttpConnection(final HttpServer server, final Socket clientSocket) {
this.server = server;
this.clientSocket = clientSocket;
this.thread = new Thread(server.getThreadGroup(), this) {
@Override
public void interrupt() {
try {
HttpConnection.this.finishThis();
} finally {
super.interrupt();
}
}
};
this.thread.setName("HttpConnectionThread: " + this);
this.thread.start();
}
/**
* parses the request and creates a GET/POST-Request Object and fills it
* with all received data
*
* @return
* @throws IOException
*/
private HttpRequest buildRequest() throws IOException {
HttpRequest request = null;
/* read request Method and Path */
ByteBuffer header = HTTPConnectionUtils.readheader(this.clientSocket.getInputStream(), true);
byte[] bytesRequestLine = new byte[header.limit()];
header.get(bytesRequestLine);
String requestLine = new String(bytesRequestLine, "ISO-8859-1").trim();
String method = new Regex(requestLine, "(GET|POST)").getMatch(0);
final String requestedURL = new Regex(requestLine, " (/.*?) ").getMatch(0);
final String requestedPath = new Regex(requestedURL, "(/.*?)($|\\?)").getMatch(0);
final String requestedParameters = new Regex(requestedURL, "\\?(.+)").getMatch(0);
final LinkedList<String[]> requestedURLParameters = HttpConnection.parseParameterList(requestedParameters);
/* read request Headers */
ByteBuffer headers = HTTPConnectionUtils.readheader(this.clientSocket.getInputStream(), false);
byte[] bytesHeaders = new byte[headers.limit()];
headers.get(bytesHeaders);
headers = null;
String[] headerStrings = new String(bytesHeaders, "ISO-8859-1").split("(\r\n)|(\n)");
/* build requestHeaders HashMap */
final HeaderCollection requestHeaders = new HeaderCollection();
for (final String line : headerStrings) {
String key = null;
String value = null;
int index = 0;
if ((index = line.indexOf(": ")) > 0) {
key = line.substring(0, index);
value = line.substring(index + 2);
} else if ((index = line.indexOf(":")) > 0) {
/* buggy clients that don't have :space ARG */
key = line.substring(0, index);
value = line.substring(index + 1);
} else {
key = null;
value = line;
}
requestHeaders.add(new HTTPHeader(key, value));
}
header = null;
bytesRequestLine = null;
bytesHeaders = null;
headerStrings = null;
/* create Request and fill it */
if ("GET".equalsIgnoreCase(method)) {
request = new GetRequest();
} else if ("POST".equalsIgnoreCase(method)) {
request = new PostRequest(this);
} else {
throw new IOException("Unsupported " + requestLine);
}
method = null;
requestLine = null;
request.setRequestedURLParameters(requestedURLParameters);
request.setRequestedPath(requestedPath);
request.setRequestedURL(requestedURL);
request.setRequestHeaders(requestHeaders);
return request;
}
/**
* closes the client socket and removes this connection from server
* connection pool
*/
private void finishThis() {
try {
this.clientSocket.getOutputStream().flush();
} catch (final Throwable nothing) {
}
try {
this.clientSocket.close();
} catch (final Throwable nothing) {
}
}
/**
* @return
* @throws IOException
*/
public InputStream getInputStream() throws IOException {
return this.clientSocket.getInputStream();
}
/**
* return the outputStream for this connection. send response headers if
* they have not been sent yet send yet
*
* @return
* @throws IOException
*/
public synchronized OutputStream getOutputStream() throws IOException {
this.sendResponseHeaders();
return this.clientSocket.getOutputStream();
}
@Override
public void run() {
try {
final HttpRequest request = this.buildRequest();
this.response = new HttpResponse(this);
boolean handled = false;
LinkedList<HttpRequestHandler> handlers = null;
synchronized (this.server.getHandler()) {
handlers = this.server.getHandler();
}
for (final HttpRequestHandler handler : handlers) {
if (request instanceof GetRequest) {
if (handler.onGetRequest((GetRequest) request, this.response)) {
handled = true;
break;
}
} else if (request instanceof PostRequest) {
if (handler.onPostRequest((PostRequest) request, this.response)) {
handled = true;
break;
}
}
}
if (!handled) {
/* generate error handler */
if (request instanceof GetRequest) {
this.response.setResponseCode(ResponseCode.SERVERERROR_NOT_IMPLEMENTED);
} else if (request instanceof PostRequest) {
this.response.setResponseCode(ResponseCode.SERVERERROR_NOT_IMPLEMENTED);
}
}
/* send response headers if they have not been sent yet send yet */
this.response.getOutputStream();
} catch (final Throwable e) {
Log.exception(e);
try {
this.response = new HttpResponse(this);
this.response.setResponseCode(ResponseCode.SERVERERROR_INTERNAL);
final byte[] bytes = Exceptions.getStackTrace(e).getBytes("UTF-8");
this.response.getResponseHeaders().add(new HTTPHeader(HTTPConstants.HEADER_REQUEST_CONTENT_TYPE, "text; charset=UTF-8"));
this.response.getResponseHeaders().add(new HTTPHeader(HTTPConstants.HEADER_REQUEST_CONTENT_LENGTH, bytes.length + ""));
this.response.getOutputStream().write(bytes);
this.response.getOutputStream().flush();
} catch (final Throwable nothing) {
Log.exception(nothing);
}
} finally {
this.finishThis();
}
}
/**
* this function sends the response headers
*
* @throws IOException
*/
private synchronized void sendResponseHeaders() throws IOException {
try {
if (this.responseHeadersSent == true) { throw new IOException("Headers already send!"); }
if (this.response != null) {
final OutputStream out = this.clientSocket.getOutputStream();
out.write(HttpResponse.HTTP11);
out.write(this.response.getResponseCode().getBytes());
out.write(HttpResponse.NEWLINE);
for (final HTTPHeader h : this.response.getResponseHeaders()) {
out.write(h.getKey().getBytes("ISO-8859-1"));
out.write(HTTPHeader.DELIMINATOR);
out.write(h.getValue().getBytes("ISO-8859-1"));
out.write(HttpResponse.NEWLINE);
}
out.write(HttpResponse.NEWLINE);
out.flush();
}
} finally {
this.responseHeadersSent = true;
}
}
}