Improved HTTP performance and POST/PUT compatbility.

This commit is contained in:
FrozenCow 2011-02-15 21:00:04 +01:00
parent e900aca2e0
commit 2d693b1ebf
4 changed files with 159 additions and 36 deletions

View File

@ -0,0 +1,61 @@
package org.dynmap.web;
import java.io.IOException;
import java.io.InputStream;
import java.util.logging.Logger;
public class BoundInputStream extends InputStream {
protected static final Logger log = Logger.getLogger("Minecraft");
private InputStream base;
private long bound;
public BoundInputStream(InputStream base, long bound) {
this.base = base;
this.bound = bound;
}
@Override
public int read() throws IOException {
if (bound <= 0) return -1;
int r = base.read();
if (r >= 0)
bound--;
return r;
}
@Override
public int available() throws IOException {
return (int)Math.min(base.available(), bound);
}
@Override
public boolean markSupported() {
return false;
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
if (bound <= 0) return -1;
len = (int)Math.min(bound, len);
int r = base.read(b, off, len);
bound -= r;
return r;
}
@Override
public int read(byte[] b) throws IOException {
return read(b, 0, b.length);
}
@Override
public long skip(long n) throws IOException {
long r = base.skip(Math.min(bound, n));
bound -= r;
return r;
}
@Override
public void close() throws IOException {
base.close();
}
}

View File

@ -0,0 +1,6 @@
package org.dynmap.web;
public class HttpField {
public static final String contentLength = "Content-Length";
public static final String contentType = "Content-Type";
}

View File

@ -6,6 +6,7 @@ import java.util.HashMap;
import java.util.Map; import java.util.Map;
public class HttpResponse { public class HttpResponse {
private HttpServerConnection connection;
public String version = "1.0"; public String version = "1.0";
public int statusCode = 200; public int statusCode = 200;
public String statusMessage = "OK"; public String statusMessage = "OK";
@ -14,7 +15,7 @@ public class HttpResponse {
private OutputStream body; private OutputStream body;
public OutputStream getBody() throws IOException { public OutputStream getBody() throws IOException {
if (body != null) { if (body != null) {
HttpServerConnection.writeResponseHeader(body, this); connection.writeResponseHeader(this);
OutputStream b = body; OutputStream b = body;
body = null; body = null;
return b; return b;
@ -22,7 +23,8 @@ public class HttpResponse {
return null; return null;
} }
public HttpResponse(OutputStream body) { public HttpResponse(HttpServerConnection connection, OutputStream body) {
this.connection = connection;
this.body = body; this.body = body;
} }
} }

View File

@ -1,11 +1,11 @@
package org.dynmap.web; package org.dynmap.web;
import java.io.BufferedOutputStream; import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.PrintStream;
import java.io.StringWriter;
import java.net.Socket; import java.net.Socket;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.logging.Level; import java.util.logging.Level;
@ -18,23 +18,53 @@ import org.dynmap.debug.Debug;
public class HttpServerConnection extends Thread { public class HttpServerConnection extends Thread {
protected static final Logger log = Logger.getLogger("Minecraft"); protected static final Logger log = Logger.getLogger("Minecraft");
private static Pattern requestHeaderLine = Pattern.compile("^(\\S+)\\s+(\\S+)\\s+HTTP/(.+)$");
private static Pattern requestHeaderField = Pattern.compile("^([^:]+):\\s*(.+)$");
private Socket socket; private Socket socket;
private HttpServer server; private HttpServer server;
private PrintStream printOut;
private StringWriter sw = new StringWriter();
private Matcher requestHeaderLineMatcher;
private Matcher requestHeaderFieldMatcher;
public HttpServerConnection(Socket socket, HttpServer server) { public HttpServerConnection(Socket socket, HttpServer server) {
this.socket = socket; this.socket = socket;
this.server = server; this.server = server;
} }
private static Pattern requestHeaderLine = Pattern.compile("^(\\S+)\\s+(\\S+)\\s+HTTP/(.+)$"); private final static void readLine(InputStream in, StringWriter sw) throws IOException {
private static Pattern requestHeaderField = Pattern.compile("^([^:]+):\\s*(.+)$"); int readc;
while((readc = in.read()) > 0) {
char c = (char)readc;
if (c == '\n')
break;
else if (c != '\r')
sw.append(c);
}
}
private final String readLine(InputStream in) throws IOException {
readLine(in, sw);
String r = sw.toString();
sw.getBuffer().setLength(0);
return r;
}
private final boolean readRequestHeader(InputStream in, HttpRequest request) throws IOException {
String statusLine = readLine(in);
private static boolean readRequestHeader(InputStream in, HttpRequest request) throws IOException {
BufferedReader r = new BufferedReader(new InputStreamReader(in));
String statusLine = r.readLine();
if (statusLine == null) if (statusLine == null)
return false; return false;
Matcher m = requestHeaderLine.matcher(statusLine);
if (requestHeaderLineMatcher == null) {
requestHeaderLineMatcher = requestHeaderLine.matcher(statusLine);
} else {
requestHeaderLineMatcher.reset(statusLine);
}
Matcher m = requestHeaderLineMatcher;
if (!m.matches()) if (!m.matches())
return false; return false;
request.method = m.group(1); request.method = m.group(1);
@ -42,10 +72,14 @@ public class HttpServerConnection extends Thread {
request.version = m.group(3); request.version = m.group(3);
String line; String line;
while ((line = r.readLine()) != null) { while (!(line = readLine(in)).equals("")) {
if (line.equals("")) if (requestHeaderFieldMatcher == null) {
break; requestHeaderFieldMatcher = requestHeaderField.matcher(line);
m = requestHeaderField.matcher(line); } else {
requestHeaderFieldMatcher.reset(line);
}
m = requestHeaderFieldMatcher;
// Warning: unknown lines are ignored. // Warning: unknown lines are ignored.
if (m.matches()) { if (m.matches()) {
String fieldName = m.group(1); String fieldName = m.group(1);
@ -57,40 +91,61 @@ public class HttpServerConnection extends Thread {
return true; return true;
} }
public static void writeResponseHeader(OutputStream out, HttpResponse response) throws IOException { public static final void writeResponseHeader(PrintStream out, HttpResponse response) throws IOException {
BufferedOutputStream o = new BufferedOutputStream(out); out.append("HTTP/");
StringBuilder sb = new StringBuilder(); out.append(response.version);
sb.append("HTTP/"); out.append(" ");
sb.append(response.version); out.append(String.valueOf(response.statusCode));
sb.append(" "); out.append(" ");
sb.append(response.statusCode); out.append(response.statusMessage);
sb.append(" "); out.append("\r\n");
sb.append(response.statusMessage);
sb.append("\r\n");
for (Entry<String, String> field : response.fields.entrySet()) { for (Entry<String, String> field : response.fields.entrySet()) {
sb.append(field.getKey()); out.append(field.getKey());
sb.append(": "); out.append(": ");
sb.append(field.getValue()); out.append(field.getValue());
sb.append("\r\n"); out.append("\r\n");
} }
sb.append("\r\n"); out.append("\r\n");
o.write(sb.toString().getBytes()); out.flush();
o.flush(); }
public final void writeResponseHeader(HttpResponse response) throws IOException {
writeResponseHeader(printOut, response);
} }
public void run() { public void run() {
try { try {
socket.setSoTimeout(5000); socket.setSoTimeout(5000);
InputStream in = socket.getInputStream();
OutputStream out = socket.getOutputStream();
printOut = new PrintStream(out);
while (true) { while (true) {
HttpRequest request = new HttpRequest(); HttpRequest request = new HttpRequest();
InputStream in = socket.getInputStream();
if (!readRequestHeader(in, request)) { if (!readRequestHeader(in, request)) {
socket.close(); socket.close();
return; return;
} }
// TODO: Optimize HttpHandler-finding by using a real path-aware long bound = -1;
// tree. BoundInputStream boundBody = null;
{
String contentLengthStr = request.fields.get(HttpField.contentLength);
if (contentLengthStr != null) {
try {
bound = Long.parseLong(contentLengthStr);
} catch (NumberFormatException e) {
}
if (bound >= 0) {
request.body = boundBody = new BoundInputStream(in, bound);
} else {
request.body = in;
}
}
}
// TODO: Optimize HttpHandler-finding by using a real path-aware tree.
HttpHandler handler = null; HttpHandler handler = null;
String relativePath = null; String relativePath = null;
for (Entry<String, HttpHandler> entry : server.handlers.entrySet()) { for (Entry<String, HttpHandler> entry : server.handlers.entrySet()) {
@ -108,8 +163,7 @@ public class HttpServerConnection extends Thread {
return; return;
} }
OutputStream out = socket.getOutputStream(); HttpResponse response = new HttpResponse(this, out);
HttpResponse response = new HttpResponse(out);
try { try {
handler.handle(relativePath, request, response); handler.handle(relativePath, request, response);