From 2d693b1ebf7fa2d84d687241a8ee0924ad161f67 Mon Sep 17 00:00:00 2001 From: FrozenCow Date: Tue, 15 Feb 2011 21:00:04 +0100 Subject: [PATCH] Improved HTTP performance and POST/PUT compatbility. --- .../java/org/dynmap/web/BoundInputStream.java | 61 +++++++++ src/main/java/org/dynmap/web/HttpField.java | 6 + .../java/org/dynmap/web/HttpResponse.java | 6 +- .../org/dynmap/web/HttpServerConnection.java | 122 +++++++++++++----- 4 files changed, 159 insertions(+), 36 deletions(-) create mode 100644 src/main/java/org/dynmap/web/BoundInputStream.java create mode 100644 src/main/java/org/dynmap/web/HttpField.java diff --git a/src/main/java/org/dynmap/web/BoundInputStream.java b/src/main/java/org/dynmap/web/BoundInputStream.java new file mode 100644 index 00000000..66665f16 --- /dev/null +++ b/src/main/java/org/dynmap/web/BoundInputStream.java @@ -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(); + } +} diff --git a/src/main/java/org/dynmap/web/HttpField.java b/src/main/java/org/dynmap/web/HttpField.java new file mode 100644 index 00000000..06535189 --- /dev/null +++ b/src/main/java/org/dynmap/web/HttpField.java @@ -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"; +} diff --git a/src/main/java/org/dynmap/web/HttpResponse.java b/src/main/java/org/dynmap/web/HttpResponse.java index 2e0622f8..0f481002 100644 --- a/src/main/java/org/dynmap/web/HttpResponse.java +++ b/src/main/java/org/dynmap/web/HttpResponse.java @@ -6,6 +6,7 @@ import java.util.HashMap; import java.util.Map; public class HttpResponse { + private HttpServerConnection connection; public String version = "1.0"; public int statusCode = 200; public String statusMessage = "OK"; @@ -14,7 +15,7 @@ public class HttpResponse { private OutputStream body; public OutputStream getBody() throws IOException { if (body != null) { - HttpServerConnection.writeResponseHeader(body, this); + connection.writeResponseHeader(this); OutputStream b = body; body = null; return b; @@ -22,7 +23,8 @@ public class HttpResponse { return null; } - public HttpResponse(OutputStream body) { + public HttpResponse(HttpServerConnection connection, OutputStream body) { + this.connection = connection; this.body = body; } } diff --git a/src/main/java/org/dynmap/web/HttpServerConnection.java b/src/main/java/org/dynmap/web/HttpServerConnection.java index 3c98db41..6d701d0c 100644 --- a/src/main/java/org/dynmap/web/HttpServerConnection.java +++ b/src/main/java/org/dynmap/web/HttpServerConnection.java @@ -1,11 +1,11 @@ package org.dynmap.web; import java.io.BufferedOutputStream; -import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; -import java.io.InputStreamReader; import java.io.OutputStream; +import java.io.PrintStream; +import java.io.StringWriter; import java.net.Socket; import java.util.Map.Entry; import java.util.logging.Level; @@ -18,23 +18,53 @@ import org.dynmap.debug.Debug; public class HttpServerConnection extends Thread { 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 HttpServer server; + + private PrintStream printOut; + private StringWriter sw = new StringWriter(); + private Matcher requestHeaderLineMatcher; + private Matcher requestHeaderFieldMatcher; public HttpServerConnection(Socket socket, HttpServer server) { this.socket = socket; this.server = server; } - private static Pattern requestHeaderLine = Pattern.compile("^(\\S+)\\s+(\\S+)\\s+HTTP/(.+)$"); - private static Pattern requestHeaderField = Pattern.compile("^([^:]+):\\s*(.+)$"); + private final static void readLine(InputStream in, StringWriter sw) throws IOException { + 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 static boolean readRequestHeader(InputStream in, HttpRequest request) throws IOException { - BufferedReader r = new BufferedReader(new InputStreamReader(in)); - String statusLine = r.readLine(); + private final boolean readRequestHeader(InputStream in, HttpRequest request) throws IOException { + String statusLine = readLine(in); + if (statusLine == null) return false; - Matcher m = requestHeaderLine.matcher(statusLine); + + if (requestHeaderLineMatcher == null) { + requestHeaderLineMatcher = requestHeaderLine.matcher(statusLine); + } else { + requestHeaderLineMatcher.reset(statusLine); + } + + Matcher m = requestHeaderLineMatcher; if (!m.matches()) return false; request.method = m.group(1); @@ -42,10 +72,14 @@ public class HttpServerConnection extends Thread { request.version = m.group(3); String line; - while ((line = r.readLine()) != null) { - if (line.equals("")) - break; - m = requestHeaderField.matcher(line); + while (!(line = readLine(in)).equals("")) { + if (requestHeaderFieldMatcher == null) { + requestHeaderFieldMatcher = requestHeaderField.matcher(line); + } else { + requestHeaderFieldMatcher.reset(line); + } + + m = requestHeaderFieldMatcher; // Warning: unknown lines are ignored. if (m.matches()) { String fieldName = m.group(1); @@ -57,40 +91,61 @@ public class HttpServerConnection extends Thread { return true; } - public static void writeResponseHeader(OutputStream out, HttpResponse response) throws IOException { - BufferedOutputStream o = new BufferedOutputStream(out); - StringBuilder sb = new StringBuilder(); - sb.append("HTTP/"); - sb.append(response.version); - sb.append(" "); - sb.append(response.statusCode); - sb.append(" "); - sb.append(response.statusMessage); - sb.append("\r\n"); + public static final void writeResponseHeader(PrintStream out, HttpResponse response) throws IOException { + out.append("HTTP/"); + out.append(response.version); + out.append(" "); + out.append(String.valueOf(response.statusCode)); + out.append(" "); + out.append(response.statusMessage); + out.append("\r\n"); for (Entry field : response.fields.entrySet()) { - sb.append(field.getKey()); - sb.append(": "); - sb.append(field.getValue()); - sb.append("\r\n"); + out.append(field.getKey()); + out.append(": "); + out.append(field.getValue()); + out.append("\r\n"); } - sb.append("\r\n"); - o.write(sb.toString().getBytes()); - o.flush(); + out.append("\r\n"); + out.flush(); + } + + public final void writeResponseHeader(HttpResponse response) throws IOException { + writeResponseHeader(printOut, response); } public void run() { try { socket.setSoTimeout(5000); + InputStream in = socket.getInputStream(); + OutputStream out = socket.getOutputStream(); + + printOut = new PrintStream(out); while (true) { HttpRequest request = new HttpRequest(); - InputStream in = socket.getInputStream(); + if (!readRequestHeader(in, request)) { socket.close(); return; } + + long bound = -1; + 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. + // TODO: Optimize HttpHandler-finding by using a real path-aware tree. HttpHandler handler = null; String relativePath = null; for (Entry entry : server.handlers.entrySet()) { @@ -108,8 +163,7 @@ public class HttpServerConnection extends Thread { return; } - OutputStream out = socket.getOutputStream(); - HttpResponse response = new HttpResponse(out); + HttpResponse response = new HttpResponse(this, out); try { handler.handle(relativePath, request, response);