PageRenderTime 1027ms CodeModel.GetById 0ms RepoModel.GetById 0ms app.codeStats 0ms

/src/main/java/org/dynmap/web/HttpServerConnection.java

http://github.com/webbukkit/dynmap
Java | 274 lines | 233 code | 31 blank | 10 comment | 58 complexity | 7cf99ff48a7c6ddc3542bae91d709aed MD5 | raw file
Possible License(s): Apache-2.0
  1. package org.dynmap.web;
  2. import java.io.BufferedOutputStream;
  3. import java.io.IOException;
  4. import java.io.InputStream;
  5. import java.io.OutputStream;
  6. import java.io.PrintStream;
  7. import java.io.StringWriter;
  8. import java.net.Socket;
  9. import java.net.URLDecoder;
  10. import java.util.Map.Entry;
  11. import java.util.logging.Logger;
  12. import java.util.regex.Matcher;
  13. import java.util.regex.Pattern;
  14. import org.dynmap.Log;
  15. import org.dynmap.debug.Debug;
  16. import java.net.InetSocketAddress;
  17. public class HttpServerConnection extends Thread {
  18. protected static final Logger log = Logger.getLogger("Minecraft");
  19. private static Pattern requestHeaderLine = Pattern.compile("^(\\S+)\\s+(\\S+)\\s+HTTP/(.+)$");
  20. private static Pattern requestHeaderField = Pattern.compile("^([^:]+):\\s*(.+)$");
  21. private Socket socket;
  22. private HttpServer server;
  23. private boolean do_shutdown;
  24. private boolean can_keepalive;
  25. private PrintStream printOut;
  26. private StringWriter sw = new StringWriter();
  27. private Matcher requestHeaderLineMatcher;
  28. private Matcher requestHeaderFieldMatcher;
  29. public HttpServerConnection(Socket socket, HttpServer server) {
  30. this.socket = socket;
  31. this.server = server;
  32. do_shutdown = false;
  33. can_keepalive = false;
  34. }
  35. private final static void readLine(InputStream in, StringWriter sw) throws IOException {
  36. int readc;
  37. while((readc = in.read()) > 0) {
  38. char c = (char)readc;
  39. if (c == '\n')
  40. break;
  41. else if (c != '\r')
  42. sw.append(c);
  43. }
  44. }
  45. private final String readLine(InputStream in) throws IOException {
  46. readLine(in, sw);
  47. String r = sw.toString();
  48. sw.getBuffer().setLength(0);
  49. return r;
  50. }
  51. private final boolean readRequestHeader(InputStream in, HttpRequest request) throws IOException {
  52. String statusLine = readLine(in);
  53. if (statusLine == null)
  54. return false;
  55. if (requestHeaderLineMatcher == null) {
  56. requestHeaderLineMatcher = requestHeaderLine.matcher(statusLine);
  57. } else {
  58. requestHeaderLineMatcher.reset(statusLine);
  59. }
  60. Matcher m = requestHeaderLineMatcher;
  61. if (!m.matches())
  62. return false;
  63. request.method = m.group(1);
  64. request.path = m.group(2);
  65. request.version = m.group(3);
  66. String line;
  67. while (!(line = readLine(in)).equals("")) {
  68. if (requestHeaderFieldMatcher == null) {
  69. requestHeaderFieldMatcher = requestHeaderField.matcher(line);
  70. } else {
  71. requestHeaderFieldMatcher.reset(line);
  72. }
  73. m = requestHeaderFieldMatcher;
  74. // Warning: unknown lines are ignored.
  75. if (m.matches()) {
  76. String fieldName = m.group(1);
  77. String fieldValue = m.group(2);
  78. // TODO: Does not support duplicate field-names.
  79. request.fields.put(fieldName, fieldValue);
  80. }
  81. }
  82. return true;
  83. }
  84. public static final void writeResponseHeader(PrintStream out, HttpResponse response) throws IOException {
  85. out.append("HTTP/");
  86. out.append(response.version);
  87. out.append(" ");
  88. out.append(String.valueOf(response.status.getCode()));
  89. out.append(" ");
  90. out.append(response.status.getText());
  91. out.append("\r\n");
  92. for (Entry<String, String> field : response.fields.entrySet()) {
  93. out.append(field.getKey());
  94. out.append(": ");
  95. out.append(field.getValue());
  96. out.append("\r\n");
  97. }
  98. for(Entry<String, String> custom : HttpServer.getCustomHeaders().entrySet()) {
  99. out.append(custom.getKey());
  100. out.append(": ");
  101. out.append(custom.getValue());
  102. out.append("\r\n");
  103. }
  104. out.append("\r\n");
  105. out.flush();
  106. }
  107. public final void writeResponseHeader(HttpResponse response) throws IOException {
  108. writeResponseHeader(printOut, response);
  109. }
  110. public void run() {
  111. try {
  112. if (socket == null)
  113. return;
  114. socket.setSoTimeout(5000);
  115. socket.setTcpNoDelay(true);
  116. InetSocketAddress rmtaddr = (InetSocketAddress)socket.getRemoteSocketAddress(); /* Get remote address */
  117. InputStream in = socket.getInputStream();
  118. BufferedOutputStream out = new BufferedOutputStream(socket.getOutputStream(), 40960);
  119. printOut = new PrintStream(out, false);
  120. while (true) {
  121. /* Check for start of each request - kicks out persistent connections */
  122. if(server.checkForBannedIp(rmtaddr)) {
  123. return;
  124. }
  125. HttpRequest request = new HttpRequest();
  126. request.rmtaddr = rmtaddr;
  127. if (!readRequestHeader(in, request)) {
  128. return;
  129. }
  130. long bound = -1;
  131. BoundInputStream boundBody = null;
  132. {
  133. String contentLengthStr = request.fields.get(HttpField.ContentLength);
  134. if (contentLengthStr != null) {
  135. try {
  136. bound = Long.parseLong(contentLengthStr);
  137. } catch (NumberFormatException e) {
  138. }
  139. if (bound >= 0) {
  140. request.body = boundBody = new BoundInputStream(in, bound);
  141. } else {
  142. request.body = in;
  143. }
  144. }
  145. }
  146. boolean iskeepalive = false;
  147. String keepalive = request.fields.get(HttpField.Connection);
  148. if((keepalive != null) && (keepalive.toLowerCase().indexOf("keep-alive") >= 0)) {
  149. /* See if we're clear to do keepalive */
  150. if(!iskeepalive)
  151. iskeepalive = server.canKeepAlive(this);
  152. }
  153. // TODO: Optimize HttpHandler-finding by using a real path-aware tree.
  154. HttpHandler handler = null;
  155. String relativePath = null;
  156. for (Entry<String, HttpHandler> entry : server.handlers.entrySet()) {
  157. String key = entry.getKey();
  158. boolean directoryHandler = key.endsWith("/");
  159. if (directoryHandler && request.path.startsWith(entry.getKey()) || !directoryHandler && request.path.equals(entry.getKey())) {
  160. relativePath = request.path.substring(entry.getKey().length());
  161. relativePath = URLDecoder.decode(relativePath,"utf-8");
  162. handler = entry.getValue();
  163. break;
  164. }
  165. /* Wildcard handler for non-directory matches */
  166. else if(key.endsWith("*") && request.path.startsWith(key.substring(0, key.length()-1))) { relativePath = request.path.substring(entry.getKey().length());
  167. relativePath = request.path.substring(entry.getKey().length()-1);
  168. relativePath = URLDecoder.decode(relativePath,"utf-8");
  169. handler = entry.getValue();
  170. break;
  171. }
  172. }
  173. if (handler == null) {
  174. return;
  175. }
  176. HttpResponse response = new HttpResponse(this, out);
  177. if(iskeepalive) {
  178. response.fields.put(HttpField.Connection, "keep-alive");
  179. response.fields.put("Keep-Alive", "timeout=5");
  180. }
  181. else {
  182. response.fields.put(HttpField.Connection, "close");
  183. }
  184. try {
  185. handler.handle(relativePath, request, response);
  186. } catch (IOException e) {
  187. throw e;
  188. } catch (Exception e) {
  189. Log.severe("HttpHandler '" + handler + "' has thown an exception", e);
  190. out.flush();
  191. return;
  192. }
  193. if (bound > 0 && boundBody.skip(bound) < bound) {
  194. Debug.debug("Incoming stream was only read partially by handler '" + handler + "'.");
  195. //socket.close();
  196. //return;
  197. }
  198. boolean isKeepalive = iskeepalive && !"close".equals(request.fields.get(HttpField.Connection)) && !"close".equals(response.fields.get(HttpField.Connection));
  199. String contentLength = response.fields.get("Content-Length");
  200. if (isKeepalive && contentLength == null) {
  201. // A handler has been a bad boy, but we're here to fix it.
  202. response.fields.put("Content-Length", "0");
  203. OutputStream responseBody = response.getBody();
  204. // The HttpHandler has already send the headers and written to the body without setting the Content-Length.
  205. if (responseBody == null) {
  206. Debug.debug("Response was given without Content-Length by '" + handler + "' for path '" + request.path + "'.");
  207. out.flush();
  208. return;
  209. }
  210. }
  211. out.flush();
  212. if (!isKeepalive) {
  213. return;
  214. }
  215. }
  216. } catch (IOException e) {
  217. } catch (Exception e) {
  218. if(!do_shutdown) {
  219. Log.severe("Exception while handling request: ", e);
  220. e.printStackTrace();
  221. }
  222. } finally {
  223. if (socket != null) {
  224. try {
  225. socket.close();
  226. } catch (IOException ex) {
  227. }
  228. }
  229. server.connectionEnded(this);
  230. }
  231. }
  232. public void shutdownConnection() {
  233. try {
  234. do_shutdown = true;
  235. if(socket != null) {
  236. socket.close();
  237. }
  238. join(); /* Wait for thread to die */
  239. } catch (IOException iox) {
  240. } catch (InterruptedException ix) {
  241. }
  242. }
  243. }