diff --git a/src/main/java/org/dynmap/DynmapPlugin.java b/src/main/java/org/dynmap/DynmapPlugin.java index 89c02a93..8e360a95 100644 --- a/src/main/java/org/dynmap/DynmapPlugin.java +++ b/src/main/java/org/dynmap/DynmapPlugin.java @@ -1,18 +1,26 @@ package org.dynmap; -import java.util.logging.Logger; -import java.io.IOException; - import java.io.File; -import org.bukkit.*; -import org.bukkit.event.*; +import java.io.IOException; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.Map; +import java.util.logging.Logger; + +import org.bukkit.Server; +import org.bukkit.World; +import org.bukkit.event.Event; import org.bukkit.event.Event.Priority; import org.bukkit.event.block.BlockListener; -import org.bukkit.plugin.*; -import org.bukkit.plugin.java.*; +import org.bukkit.plugin.PluginDescriptionFile; +import org.bukkit.plugin.PluginLoader; +import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.util.config.Configuration; import org.dynmap.debug.BukkitPlayerDebugger; import org.dynmap.web.WebServer; +import org.dynmap.web.handlers.ClientConfigurationHandler; +import org.dynmap.web.handlers.ClientUpdateHandler; +import org.dynmap.web.handlers.FilesystemHandler; public class DynmapPlugin extends JavaPlugin { @@ -54,10 +62,29 @@ public class DynmapPlugin extends JavaPlugin { mapManager = new MapManager(getWorld(), debugger, configuration); mapManager.startManager(); + InetAddress bindAddress; + { + String address = configuration.getString("webserver-bindaddress", "0.0.0.0"); + try { + bindAddress = address.equals("0.0.0.0") + ? null + : InetAddress.getByName(address); + } catch (UnknownHostException e) { + bindAddress = null; + } + } + int port = configuration.getInt("webserver-port", 8123); + + webServer = new WebServer(bindAddress, port); + webServer.handlers.put("/", new FilesystemHandler(mapManager.webDirectory)); + webServer.handlers.put("/tiles/", new FilesystemHandler(mapManager.tileDirectory)); + webServer.handlers.put("/up/", new ClientUpdateHandler(mapManager, playerList, getWorld())); + webServer.handlers.put("/up/configuration", new ClientConfigurationHandler((Map) configuration.getProperty("web"))); + try { - webServer = new WebServer(mapManager, getWorld(), playerList, debugger, configuration); + webServer.startServer(); } catch (IOException e) { - log.info("position failed to start WebServer (IOException)"); + log.severe("Failed to start WebServer on " + bindAddress + ":" + port + "!"); } registerEvents(); diff --git a/src/main/java/org/dynmap/web/WebServer.java b/src/main/java/org/dynmap/web/WebServer.java index 4c094998..89914e18 100644 --- a/src/main/java/org/dynmap/web/WebServer.java +++ b/src/main/java/org/dynmap/web/WebServer.java @@ -4,42 +4,30 @@ import java.io.IOException; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; +import java.util.Collections; +import java.util.SortedMap; +import java.util.TreeMap; +import java.util.logging.Level; import java.util.logging.Logger; -import org.bukkit.World; -import org.bukkit.util.config.ConfigurationNode; -import org.dynmap.MapManager; -import org.dynmap.PlayerList; -import org.dynmap.debug.Debugger; - public class WebServer extends Thread { - - public static final String VERSION = "Huncraft"; protected static final Logger log = Logger.getLogger("Minecraft"); - private Debugger debugger; - private ServerSocket sock = null; private boolean running = false; - private MapManager mgr; - private World world; - private PlayerList playerList; - private ConfigurationNode configuration; + private InetAddress bindAddress; + private int port; - public WebServer(MapManager mgr, World world, PlayerList playerList, Debugger debugger, ConfigurationNode configuration) throws IOException { - this.mgr = mgr; - this.world = world; - this.playerList = playerList; - this.configuration = configuration; - this.debugger = debugger; + public SortedMap handlers = new TreeMap(Collections.reverseOrder()); - String bindAddress = configuration.getString("webserver-bindaddress", "0.0.0.0"); - int port = configuration.getInt("webserver-port", 8123); + public WebServer(InetAddress bindAddress, int port) { + this.bindAddress = bindAddress; + this.port = port; + } - sock = new ServerSocket(port, 5, bindAddress.equals("0.0.0.0") - ? null - : InetAddress.getByName(bindAddress)); + public void startServer() throws IOException { + sock = new ServerSocket(port, 5, bindAddress); running = true; start(); log.info("Dynmap WebServer started on " + bindAddress + ":" + port); @@ -50,7 +38,7 @@ public class WebServer extends Thread { while (running) { try { Socket socket = sock.accept(); - WebServerRequest requestThread = new WebServerRequest(socket, mgr, world, playerList, configuration, debugger); + WebServerRequest requestThread = new WebServerRequest(socket, this); requestThread.start(); } catch (IOException e) { log.info("map WebServer.run() stops with IOException"); @@ -59,7 +47,7 @@ public class WebServer extends Thread { } log.info("map WebServer run() exiting"); } catch (Exception ex) { - debugger.error("Exception on WebServer-thread: " + ex.toString()); + log.log(Level.SEVERE, "Exception on WebServer-thread", ex); } } diff --git a/src/main/java/org/dynmap/web/WebServerRequest.java b/src/main/java/org/dynmap/web/WebServerRequest.java index 86e3e31b..92c4817f 100644 --- a/src/main/java/org/dynmap/web/WebServerRequest.java +++ b/src/main/java/org/dynmap/web/WebServerRequest.java @@ -1,78 +1,32 @@ package org.dynmap.web; -import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.BufferedReader; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; -import java.io.Reader; import java.net.Socket; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.Date; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.Map; import java.util.Map.Entry; -import java.util.SortedMap; -import java.util.TreeMap; +import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; -import org.bukkit.World; -import org.bukkit.entity.Player; -import org.bukkit.util.config.ConfigurationNode; -import org.dynmap.ChatQueue; -import org.dynmap.MapManager; -import org.dynmap.PlayerList; -import org.dynmap.TileUpdate; -import org.dynmap.debug.Debugger; - public class WebServerRequest extends Thread { protected static final Logger log = Logger.getLogger("Minecraft"); - private Debugger debugger; private Socket socket; - private MapManager mgr; - private World world; - private PlayerList playerList; - private ConfigurationNode configuration; - - public SortedMap handlers = new TreeMap(Collections.reverseOrder()); + private WebServer server; - public WebServerRequest(Socket socket, MapManager mgr, World world, PlayerList playerList, ConfigurationNode configuration, Debugger debugger) { - this.debugger = debugger; + public WebServerRequest(Socket socket, WebServer server) { this.socket = socket; - this.mgr = mgr; - this.world = world; - this.playerList = playerList; - this.configuration = configuration; - - handlers.put("/", new FilesystemHandler(mgr.webDirectory)); - handlers.put("/tiles/", new FilesystemHandler(mgr.tileDirectory)); - handlers.put("/up/", new ClientUpdateHandler()); - handlers.put("/up/configuration", new ClientConfigurationHandler()); - handlers.put("/test/", new HttpHandler() { - @Override - public void handle(String path, HttpRequest request, HttpResponse response) throws IOException { - response.fields.put("Content-Type", "text/plain"); - BufferedOutputStream s = new BufferedOutputStream(response.getBody()); - s.write("test".getBytes()); - s.flush(); - s.close(); - } - }); + this.server = server; } private static Pattern requestHeaderLine = Pattern.compile("^(\\S+)\\s+(\\S+)\\s+HTTP/(.+)$"); private static Pattern requestHeaderField = Pattern.compile("^([^:]+):\\s*(.+)$"); + private static boolean readRequestHeader(InputStream in, HttpRequest request) throws IOException { BufferedReader r = new BufferedReader(new InputStreamReader(in)); Matcher m = requestHeaderLine.matcher(r.readLine()); @@ -81,9 +35,9 @@ public class WebServerRequest extends Thread { request.method = m.group(1); request.path = m.group(2); request.version = m.group(3); - + String line; - while((line = r.readLine()) != null) { + while ((line = r.readLine()) != null) { if (line.equals("")) break; m = requestHeaderField.matcher(line); @@ -97,7 +51,7 @@ public class WebServerRequest extends Thread { } return true; } - + public static void writeResponseHeader(OutputStream out, HttpResponse response) throws IOException { BufferedOutputStream o = new BufferedOutputStream(out); StringBuilder sb = new StringBuilder(); @@ -108,7 +62,7 @@ public class WebServerRequest extends Thread { sb.append(" "); sb.append(response.statusMessage); sb.append("\n"); - for(Entry field : response.fields.entrySet()) { + for (Entry field : response.fields.entrySet()) { sb.append(field.getKey()); sb.append(": "); sb.append(field.getValue()); @@ -117,7 +71,7 @@ public class WebServerRequest extends Thread { sb.append("\n"); o.write(sb.toString().getBytes()); } - + public void run() { try { socket.setSoTimeout(30000); @@ -128,21 +82,21 @@ public class WebServerRequest extends Thread { return; } - // TODO: Optimize HttpHandler-finding by using a real path-aware tree. + // TODO: Optimize HttpHandler-finding by using a real path-aware + // tree. HttpResponse response = null; - for(Entry entry : handlers.entrySet()) { + for (Entry entry : server.handlers.entrySet()) { String key = entry.getKey(); boolean directoryHandler = key.endsWith("/"); - if (directoryHandler && request.path.startsWith(entry.getKey()) || - !directoryHandler && request.path.equals(entry.getKey())) { + if (directoryHandler && request.path.startsWith(entry.getKey()) || !directoryHandler && request.path.equals(entry.getKey())) { String path = request.path.substring(entry.getKey().length()); - + response = new HttpResponse(socket.getOutputStream()); entry.getValue().handle(path, request, response); break; } } - + if (response != null) { if (response.fields.get("Content-Length") == null) { response.fields.put("Content-Length", "0"); @@ -151,7 +105,7 @@ public class WebServerRequest extends Thread { out.close(); } } - + String connection = response.fields.get("Connection"); if (connection == null || connection.equals("close")) { socket.close(); @@ -162,153 +116,21 @@ public class WebServerRequest extends Thread { return; } } catch (IOException e) { - try { socket.close(); } catch(IOException ex) { } + if (socket != null) { + try { + socket.close(); + } catch (IOException ex) { + } + } } catch (Exception e) { - try { socket.close(); } catch(IOException ex) { } - debugger.error("Exception on WebRequest-thread: " + e.toString()); + if (socket != null) { + try { + socket.close(); + } catch (IOException ex) { + } + } + log.log(Level.SEVERE, "Exception while handling request: ", e); e.printStackTrace(); } } - - public String stringifyJson(Object o) { - if (o == null) { - return "null"; - } else if (o instanceof Boolean) { - return ((Boolean) o) ? "true" : "false"; - } else if (o instanceof String) { - return "\"" + o + "\""; - } else if (o instanceof Integer || o instanceof Long || o instanceof Float || o instanceof Double) { - return o.toString(); - } else if (o instanceof LinkedHashMap) { - LinkedHashMap m = (LinkedHashMap) o; - StringBuilder sb = new StringBuilder(); - sb.append("{"); - boolean first = true; - for (Object key : m.keySet()) { - if (first) - first = false; - else - sb.append(","); - - sb.append(stringifyJson(key)); - sb.append(": "); - sb.append(stringifyJson(m.get(key))); - } - sb.append("}"); - return sb.toString(); - } else if (o instanceof ArrayList) { - ArrayList l = (ArrayList) o; - StringBuilder sb = new StringBuilder(); - int count = 0; - for (int i = 0; i < l.size(); i++) { - sb.append(count++ == 0 ? "[" : ","); - sb.append(stringifyJson(l.get(i))); - } - sb.append("]"); - return sb.toString(); - } else { - return "undefined"; - } - } - - - public class ClientConfigurationHandler implements HttpHandler { - @Override - public void handle(String path, HttpRequest request, HttpResponse response) throws IOException { - String s = stringifyJson(configuration.getProperty("web")); - - byte[] bytes = s.getBytes(); - String dateStr = new Date().toString(); - - response.fields.put("Date", dateStr); - response.fields.put("Content-Type", "text/plain"); - response.fields.put("Expires", "Thu, 01 Dec 1994 16:00:00 GMT"); - response.fields.put("Last-modified", dateStr); - response.fields.put("Content-Length", Integer.toString(bytes.length)); - BufferedOutputStream out = new BufferedOutputStream(response.getBody()); - out.write(s.getBytes()); - out.flush(); - } - } - - public class ClientUpdateHandler implements HttpHandler { - @Override - public void handle(String path, HttpRequest request, HttpResponse response) throws IOException { - int current = (int) (System.currentTimeMillis() / 1000); - long cutoff = 0; - - if (path.length() > 0) { - try { - cutoff = ((long) Integer.parseInt(path)) * 1000; - } catch (NumberFormatException e) { - } - } - - StringBuilder sb = new StringBuilder(); - long relativeTime = world.getTime() % 24000; - sb.append(current + " " + relativeTime + "\n"); - - Player[] players = playerList.getVisiblePlayers(); - for (Player player : players) { - sb.append("player " + player.getName() + " " + player.getLocation().getX() + " " + player.getLocation().getY() + " " + player.getLocation().getZ() + "\n"); - } - - TileUpdate[] tileUpdates = mgr.staleQueue.getTileUpdates(cutoff); - for (TileUpdate tu : tileUpdates) { - sb.append("tile " + tu.tile.getName() + "\n"); - } - - ChatQueue.ChatMessage[] messages = mgr.chatQueue.getChatMessages(cutoff); - for (ChatQueue.ChatMessage cu : messages) { - sb.append("chat " + cu.playerName + " " + cu.message + "\n"); - } - - debugger.debug("Sending " + players.length + " players, " + tileUpdates.length + " tile-updates, and " + messages.length + " chats. " + path + ";" + cutoff); - - byte[] bytes = sb.toString().getBytes(); - - String dateStr = new Date().toString(); - response.fields.put("Date", dateStr); - response.fields.put("Content-Type", "text/plain"); - response.fields.put("Expires", "Thu, 01 Dec 1994 16:00:00 GMT"); - response.fields.put("Last-modified", dateStr); - response.fields.put("Content-Length", Integer.toString(bytes.length)); - BufferedOutputStream out = new BufferedOutputStream(response.getBody()); - out.write(bytes); - out.flush(); - } - } - - public class JarFileHandler extends FileHandler { - private String root; - public JarFileHandler(String root) { - if (root.endsWith("/")) root = root.substring(0, root.length()-1); - this.root = root; - } - @Override - protected InputStream getFileInput(String path) { - return this.getClass().getResourceAsStream(root + "/" + path); - } - } - - public class FilesystemHandler extends FileHandler { - private File root; - public FilesystemHandler(File root) { - if (!root.isDirectory()) - throw new IllegalArgumentException(); - this.root = root; - } - @Override - protected InputStream getFileInput(String path) { - File file = new File(root, path); - if (file.getAbsolutePath().startsWith(root.getAbsolutePath()) && file.isFile()) { - try { - return new FileInputStream(file); - } catch (FileNotFoundException e) { - return null; - } - } - return null; - } - } } diff --git a/src/main/java/org/dynmap/web/handlers/ClientConfigurationHandler.java b/src/main/java/org/dynmap/web/handlers/ClientConfigurationHandler.java new file mode 100644 index 00000000..9c514585 --- /dev/null +++ b/src/main/java/org/dynmap/web/handlers/ClientConfigurationHandler.java @@ -0,0 +1,76 @@ +package org.dynmap.web.handlers; + +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Date; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.dynmap.web.HttpHandler; +import org.dynmap.web.HttpRequest; +import org.dynmap.web.HttpResponse; + +public class ClientConfigurationHandler implements HttpHandler { + private Map configuration; + public ClientConfigurationHandler(Map configuration) { + this.configuration = configuration; + } + @Override + public void handle(String path, HttpRequest request, HttpResponse response) throws IOException { + String s = stringifyJson(configuration); + + byte[] bytes = s.getBytes(); + String dateStr = new Date().toString(); + + response.fields.put("Date", dateStr); + response.fields.put("Content-Type", "text/plain"); + response.fields.put("Expires", "Thu, 01 Dec 1994 16:00:00 GMT"); + response.fields.put("Last-modified", dateStr); + response.fields.put("Content-Length", Integer.toString(bytes.length)); + BufferedOutputStream out = new BufferedOutputStream(response.getBody()); + out.write(s.getBytes()); + out.flush(); + } + + public String stringifyJson(Object o) { + if (o == null) { + return "null"; + } else if (o instanceof Boolean) { + return ((Boolean) o) ? "true" : "false"; + } else if (o instanceof String) { + return "\"" + o + "\""; + } else if (o instanceof Integer || o instanceof Long || o instanceof Float || o instanceof Double) { + return o.toString(); + } else if (o instanceof LinkedHashMap) { + LinkedHashMap m = (LinkedHashMap) o; + StringBuilder sb = new StringBuilder(); + sb.append("{"); + boolean first = true; + for (Object key : m.keySet()) { + if (first) + first = false; + else + sb.append(","); + + sb.append(stringifyJson(key)); + sb.append(": "); + sb.append(stringifyJson(m.get(key))); + } + sb.append("}"); + return sb.toString(); + } else if (o instanceof ArrayList) { + ArrayList l = (ArrayList) o; + StringBuilder sb = new StringBuilder(); + int count = 0; + for (int i = 0; i < l.size(); i++) { + sb.append(count++ == 0 ? "[" : ","); + sb.append(stringifyJson(l.get(i))); + } + sb.append("]"); + return sb.toString(); + } else { + return "undefined"; + } + } +} diff --git a/src/main/java/org/dynmap/web/handlers/ClientUpdateHandler.java b/src/main/java/org/dynmap/web/handlers/ClientUpdateHandler.java new file mode 100644 index 00000000..ab983d06 --- /dev/null +++ b/src/main/java/org/dynmap/web/handlers/ClientUpdateHandler.java @@ -0,0 +1,71 @@ +package org.dynmap.web.handlers; + +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.util.Date; + +import org.bukkit.World; +import org.bukkit.entity.Player; +import org.dynmap.ChatQueue; +import org.dynmap.MapManager; +import org.dynmap.PlayerList; +import org.dynmap.TileUpdate; +import org.dynmap.web.HttpHandler; +import org.dynmap.web.HttpRequest; +import org.dynmap.web.HttpResponse; + +public class ClientUpdateHandler implements HttpHandler { + private MapManager mapManager; + private PlayerList playerList; + private World world; + public ClientUpdateHandler(MapManager mapManager, PlayerList playerList, World world) { + this.mapManager = mapManager; + this.playerList = playerList; + this.world = world; + } + @Override + public void handle(String path, HttpRequest request, HttpResponse response) throws IOException { + int current = (int) (System.currentTimeMillis() / 1000); + long cutoff = 0; + + if (path.length() > 0) { + try { + cutoff = ((long) Integer.parseInt(path)) * 1000; + } catch (NumberFormatException e) { + } + } + + StringBuilder sb = new StringBuilder(); + long relativeTime = world.getTime() % 24000; + sb.append(current + " " + relativeTime + "\n"); + + Player[] players = playerList.getVisiblePlayers(); + for (Player player : players) { + sb.append("player " + player.getName() + " " + player.getLocation().getX() + " " + player.getLocation().getY() + " " + player.getLocation().getZ() + "\n"); + } + + TileUpdate[] tileUpdates = mapManager.staleQueue.getTileUpdates(cutoff); + for (TileUpdate tu : tileUpdates) { + sb.append("tile " + tu.tile.getName() + "\n"); + } + + ChatQueue.ChatMessage[] messages = mapManager.chatQueue.getChatMessages(cutoff); + for (ChatQueue.ChatMessage cu : messages) { + sb.append("chat " + cu.playerName + " " + cu.message + "\n"); + } + + //debugger.debug("Sending " + players.length + " players, " + tileUpdates.length + " tile-updates, and " + messages.length + " chats. " + path + ";" + cutoff); + + byte[] bytes = sb.toString().getBytes(); + + String dateStr = new Date().toString(); + response.fields.put("Date", dateStr); + response.fields.put("Content-Type", "text/plain"); + response.fields.put("Expires", "Thu, 01 Dec 1994 16:00:00 GMT"); + response.fields.put("Last-modified", dateStr); + response.fields.put("Content-Length", Integer.toString(bytes.length)); + BufferedOutputStream out = new BufferedOutputStream(response.getBody()); + out.write(bytes); + out.flush(); + } +} \ No newline at end of file diff --git a/src/main/java/org/dynmap/web/handlers/FilesystemHandler.java b/src/main/java/org/dynmap/web/handlers/FilesystemHandler.java new file mode 100644 index 00000000..9b5a0ae6 --- /dev/null +++ b/src/main/java/org/dynmap/web/handlers/FilesystemHandler.java @@ -0,0 +1,29 @@ +package org.dynmap.web.handlers; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; + +import org.dynmap.web.FileHandler; + +public class FilesystemHandler extends FileHandler { + private File root; + public FilesystemHandler(File root) { + if (!root.isDirectory()) + throw new IllegalArgumentException(); + this.root = root; + } + @Override + protected InputStream getFileInput(String path) { + File file = new File(root, path); + if (file.getAbsolutePath().startsWith(root.getAbsolutePath()) && file.isFile()) { + try { + return new FileInputStream(file); + } catch (FileNotFoundException e) { + return null; + } + } + return null; + } +} \ No newline at end of file diff --git a/src/main/java/org/dynmap/web/handlers/JarFileHandler.java b/src/main/java/org/dynmap/web/handlers/JarFileHandler.java new file mode 100644 index 00000000..aef3a7e7 --- /dev/null +++ b/src/main/java/org/dynmap/web/handlers/JarFileHandler.java @@ -0,0 +1,17 @@ +package org.dynmap.web.handlers; + +import java.io.InputStream; + +import org.dynmap.web.FileHandler; + +public class JarFileHandler extends FileHandler { + private String root; + public JarFileHandler(String root) { + if (root.endsWith("/")) root = root.substring(0, root.length()-1); + this.root = root; + } + @Override + protected InputStream getFileInput(String path) { + return this.getClass().getResourceAsStream(root + "/" + path); + } +} \ No newline at end of file