From 6a7559f3ad2724f6b74fc9794f18d41df154d0a1 Mon Sep 17 00:00:00 2001 From: FrozenCow Date: Fri, 7 Jan 2011 04:50:43 +0100 Subject: [PATCH] Expanded internal webserver to also handle web and tiles, where web-files can be retrieved from filesystem or jar. Also some debugging changes. --- build.xml | 3 +- src/main/java/org/dynmap/DynmapPlugin.java | 14 +- src/main/java/org/dynmap/MapManager.java | 20 +- src/main/java/org/dynmap/MapTile.java | 2 +- src/main/java/org/dynmap/WebServer.java | 10 +- .../java/org/dynmap/WebServerRequest.java | 221 +++++++++++++----- .../dynmap/debug/BukkitPlayerDebugger.java | 26 ++- src/main/java/org/dynmap/debug/Debugger.java | 2 + .../org/dynmap/render/CaveTileRenderer.java | 5 +- .../org/dynmap/render/DayTileRenderer.java | 26 ++- src/main/resources/plugin.yml | 2 +- 11 files changed, 247 insertions(+), 84 deletions(-) diff --git a/build.xml b/build.xml index 83f7871f..f2efef73 100644 --- a/build.xml +++ b/build.xml @@ -11,7 +11,7 @@ - + @@ -19,6 +19,7 @@ + diff --git a/src/main/java/org/dynmap/DynmapPlugin.java b/src/main/java/org/dynmap/DynmapPlugin.java index d126fb04..45c525d9 100644 --- a/src/main/java/org/dynmap/DynmapPlugin.java +++ b/src/main/java/org/dynmap/DynmapPlugin.java @@ -17,7 +17,6 @@ public class DynmapPlugin extends JavaPlugin { private WebServer server = null; private MapManager mgr = null; - private DynmapBlockListener listener = null; private BukkitPlayerDebugger debugger = new BukkitPlayerDebugger(this); @@ -31,36 +30,33 @@ public class DynmapPlugin extends JavaPlugin { @Override public void onEnable() { - log.info("Map INIT"); - + debugger.enable(); mgr = new MapManager(getWorld(), debugger); mgr.startManager(); try { - server = new WebServer(mgr.serverport, mgr); + server = new WebServer(mgr.serverport, mgr, getServer(), debugger); } catch(IOException e) { log.info("position failed to start WebServer (IOException)"); } - - listener = new DynmapBlockListener(mgr); registerEvents(); } @Override public void onDisable() { - log.info("Map UNINIT"); - mgr.stopManager(); if(server != null) { server.shutdown(); server = null; } + debugger.disable(); } public void registerEvents() { - getServer().getPluginManager().registerEvent(Event.Type.BLOCK_PLACED, listener, Priority.Normal, this); + getServer().getPluginManager().registerEvent(Event.Type.BLOCK_PLACED, new DynmapBlockListener(mgr), Priority.Normal, this); + getServer().getPluginManager().registerEvent(Event.Type.PLAYER_COMMAND, new DynmapPlayerListener(mgr), Priority.Normal, this); //getServer().getPluginManager().registerEvent(Event.Type.BLOCK_DESTROYED, listener, Priority.Normal, this); /* etc.getLoader().addListener(PluginLoader.Hook.COMMAND, listener, this, PluginListener.Priority.MEDIUM); etc.getLoader().addListener(PluginLoader.Hook.BLOCK_CREATED, listener, this, PluginListener.Priority.MEDIUM); diff --git a/src/main/java/org/dynmap/MapManager.java b/src/main/java/org/dynmap/MapManager.java index d8826de9..e69e26ea 100644 --- a/src/main/java/org/dynmap/MapManager.java +++ b/src/main/java/org/dynmap/MapManager.java @@ -87,12 +87,12 @@ public class MapManager extends Thread { /* path to signs file */ public String signspath = "signs.txt"; - /* port to run web server on */ - public int serverport = 8123; - /* bind web server to ip-address */ public String bindaddress = "0.0.0.0"; + /* port to run web server on */ + public int serverport = 8123; + /* time to pause between rendering tiles (ms) */ public int renderWait = 500; @@ -114,6 +114,9 @@ public class MapManager extends Thread { /* zoomed-out tile cache */ public Cache zoomCache; + /* web files location */ + public String webPath; + public void debug(String msg) { debugger.debug(msg); @@ -129,6 +132,8 @@ public class MapManager extends Thread { signspath = "signs.txt"; serverport = 8123; bindaddress = "0.0.0.0"; + //webPath = "/srv/http/dynmap/"; + webPath = "[JAR]"; tileStore = new HashMap(); staleTiles = new LinkedList(); @@ -138,11 +143,11 @@ public class MapManager extends Thread { colors = loadColorSet(colorsetpath); renderer = new CombinedTileRenderer(new MapTileRenderer[] { - new DayTileRenderer(colors, tilepath + "t_{X}_{Y}.png"), - new CaveTileRenderer(colors, tilepath + "ct_{X}_{Y}.png") + new DayTileRenderer(debugger, colors, tilepath + "t_{X}_{Y}.png"), + new CaveTileRenderer(debugger, colors, tilepath + "ct_{X}_{Y}.png") }); } - + /* tile X for position x */ static int tilex(int x) { @@ -236,6 +241,7 @@ public class MapManager extends Thread { MapTile t = this.popStaleTile(); if(t != null) { + debugger.debug("rendering tile " + t + "..."); renderer.render(t); updateUpdates(t, tileUpdates); @@ -262,6 +268,8 @@ public class MapManager extends Thread { /* "touch" a block - its map tile will be regenerated */ public boolean touch(int x, int y, int z) { + debugger.debug("touched: " + x + "," + y + "," + z); + int dx = x - anchorx; int dy = y - anchory; int dz = z - anchorz; diff --git a/src/main/java/org/dynmap/MapTile.java b/src/main/java/org/dynmap/MapTile.java index 6e1084e1..fdcf13e0 100644 --- a/src/main/java/org/dynmap/MapTile.java +++ b/src/main/java/org/dynmap/MapTile.java @@ -81,7 +81,7 @@ public class MapTile { /* check if all relevant chunks are loaded */ public boolean isMapLoaded() { - int x1 = mx - 64; + int x1 = mx - MapManager.tileHeight / 2; int x2 = mx + MapManager.tileWidth / 2 + MapManager.tileHeight / 2; int z1 = mz - MapManager.tileHeight / 2; diff --git a/src/main/java/org/dynmap/WebServer.java b/src/main/java/org/dynmap/WebServer.java index 32ad19e1..280148ff 100644 --- a/src/main/java/org/dynmap/WebServer.java +++ b/src/main/java/org/dynmap/WebServer.java @@ -4,6 +4,7 @@ import java.io.*; import java.net.*; import java.util.*; import org.bukkit.*; +import org.dynmap.debug.Debugger; import java.util.logging.Logger; @@ -12,14 +13,19 @@ 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 Server server; - public WebServer(int port, MapManager mgr) throws IOException + public WebServer(int port, MapManager mgr, Server server, Debugger debugger) throws IOException { this.mgr = mgr; + this.server = server; + this.debugger = debugger; sock = new ServerSocket(port, 5, mgr.bindaddress.equals("0.0.0.0") ? null : InetAddress.getByName(mgr.bindaddress)); running = true; start(); @@ -31,7 +37,7 @@ public class WebServer extends Thread { while (running) { try { Socket socket = sock.accept(); - WebServerRequest requestThread = new WebServerRequest(socket, mgr); + WebServerRequest requestThread = new WebServerRequest(socket, mgr, server, debugger); requestThread.start(); } catch (IOException e) { diff --git a/src/main/java/org/dynmap/WebServerRequest.java b/src/main/java/org/dynmap/WebServerRequest.java index c926f592..764f9ffa 100644 --- a/src/main/java/org/dynmap/WebServerRequest.java +++ b/src/main/java/org/dynmap/WebServerRequest.java @@ -2,97 +2,85 @@ package org.dynmap; import java.io.BufferedOutputStream; import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.Socket; import java.util.Date; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.logging.Logger; import org.bukkit.*; +import org.dynmap.debug.Debugger; public class WebServerRequest extends Thread { protected static final Logger log = Logger.getLogger("Minecraft"); - private Socket sock; + private Debugger debugger; + private Socket socket; private MapManager mgr; - private DynmapPlugin etc; + private Server server; - public WebServerRequest(Socket socket, MapManager mgr) + public WebServerRequest(Socket socket, MapManager mgr, Server server, Debugger debugger) { - sock = socket; + this.debugger = debugger; + this.socket = socket; this.mgr = mgr; + this.server = server; } - - private static void sendHeader(BufferedOutputStream out, int code, String contentType, long contentLength, long lastModified) throws IOException - { - out.write(("HTTP/1.0 " + code + " OK\r\n" + - "Date: " + new Date().toString() + "\r\n" + - "Server: JibbleWebServer/1.0\r\n" + - "Content-Type: " + contentType + "\r\n" + - "Expires: Thu, 01 Dec 1994 16:00:00 GMT\r\n" + - ((contentLength != -1) ? "Content-Length: " + contentLength + "\r\n" : "") + - "Last-modified: " + new Date(lastModified).toString() + "\r\n" + - "\r\n").getBytes()); + + private static void writeHttpHeader(BufferedOutputStream out, int statusCode, String statusText) throws IOException { + out.write("HTTP/1.0 ".getBytes()); + out.write(Integer.toString(statusCode).getBytes()); + out.write((" " + statusText + "\r\n").getBytes()); } - - private static void sendError(BufferedOutputStream out, int code, String message) throws IOException - { - message = message + "
" + WebServer.VERSION; - sendHeader(out, code, "text/html", message.length(), System.currentTimeMillis()); - out.write(message.getBytes()); - out.flush(); - out.close(); + + private static void writeHeaderField(BufferedOutputStream out, String name, String value) throws IOException { + out.write(name.getBytes()); + out.write((int)':'); + out.write((int)' '); + out.write(value.getBytes()); + out.write(13); + out.write(10); + } + + private static void writeEndOfHeaders(BufferedOutputStream out) throws IOException { + out.write(13); + out.write(10); } public void run() { InputStream reader = null; try { - sock.setSoTimeout(30000); - BufferedReader in = new BufferedReader(new InputStreamReader(sock.getInputStream())); - BufferedOutputStream out = new BufferedOutputStream(sock.getOutputStream()); + socket.setSoTimeout(30000); + BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream())); + BufferedOutputStream out = new BufferedOutputStream(socket.getOutputStream()); String request = in.readLine(); if (request == null || !request.startsWith("GET ") || !(request.endsWith(" HTTP/1.0") || request.endsWith("HTTP/1.1"))) { // Invalid request type (no "GET") - sendError(out, 500, "Invalid Method."); + writeHttpHeader(out, 500, "Invalid Method."); + writeEndOfHeaders(out); return; } String path = request.substring(4, request.length() - 9); - - int current = (int) (System.currentTimeMillis() / 1000); - long cutoff = 0; - - if(path.charAt(0) == '/') { - try { - cutoff = ((long) Integer.parseInt(path.substring(1))) * 1000; - } catch(NumberFormatException e) { + debugger.debug("request: " + path); + if (path.startsWith("/up/")) { + handleUp(out, path.substring(3)); + } else if (path.startsWith("/tiles/")) { + handleMapToDirectory(out, path.substring(6), "/srv/http/dynmap/tiles/"); + } else if (path.startsWith("/")) { + if(mgr.webPath.equals("[JAR]")) { + handleMapToJar(out, path); + } else if(mgr.webPath.length() > 0) { + handleMapToDirectory(out, path, mgr.webPath); } } - - sendHeader(out, 200, "text/plain", -1, System.currentTimeMillis()); - - StringBuilder sb = new StringBuilder(); - - long relativeTime = etc.getServer().getTime() % 24000; - sb.append(current + " " + relativeTime + "\n"); - - for(Player player : etc.getServer().getOnlinePlayers()) { - sb.append(player.getName() + " player " + player.getLocation().getX() + " " + player.getLocation().getY() + " " + player.getLocation().getZ() + "\n"); - } - - synchronized(mgr.lock) { - for(TileUpdate tu : mgr.tileUpdates) { - if(tu.at >= cutoff) { - sb.append(tu.tile.px + "_" + tu.tile.py + " " + tu.tile.zpx + "_" + tu.tile.zpy + " t\n"); - } - } - } - - out.write(sb.toString().getBytes()); - out.flush(); out.close(); } @@ -107,4 +95,123 @@ public class WebServerRequest extends Thread { } } } + + public void handleUp(BufferedOutputStream out, String path) throws IOException { + int current = (int) (System.currentTimeMillis() / 1000); + long cutoff = 0; + + if(path.charAt(0) == '/') { + try { + cutoff = ((long) Integer.parseInt(path.substring(1))) * 1000; + } catch(NumberFormatException e) { + } + } + + StringBuilder sb = new StringBuilder(); + + long relativeTime = server.getTime() % 24000; + sb.append(current + " " + relativeTime + "\n"); + + for(Player player : server.getOnlinePlayers()) { + sb.append(player.getName() + " player " + player.getLocation().getX() + " " + player.getLocation().getY() + " " + player.getLocation().getZ() + "\n"); + } + + synchronized(mgr.lock) { + for(TileUpdate tu : mgr.tileUpdates) { + if(tu.at >= cutoff) { + sb.append(tu.tile.px + "_" + tu.tile.py + " " + tu.tile.zpx + "_" + tu.tile.zpy + " t\n"); + } + } + } + + byte[] bytes = sb.toString().getBytes(); + + String dateStr = new Date().toString(); + writeHttpHeader(out, 200, "OK"); + writeHeaderField(out, "Date", dateStr); + writeHeaderField(out, "Content-Type", "text/plain"); + writeHeaderField(out, "Expires", "Thu, 01 Dec 1994 16:00:00 GMT"); + writeHeaderField(out, "Last-modified", dateStr); + writeHeaderField(out, "Content-Length", Integer.toString(bytes.length)); + writeEndOfHeaders(out); + out.write(bytes); + } + + private byte[] readBuffer = new byte[40960]; + + public void writeFile(BufferedOutputStream out, String path, InputStream fileInput) throws IOException { + int dotindex = path.lastIndexOf('.'); + String extension = null; + if (dotindex > 0) extension = path.substring(dotindex); + + writeHttpHeader(out, 200, "OK"); + writeHeaderField(out, "Content-Type", getMimeTypeFromExtension(extension)); + writeHeaderField(out, "Connection", "close"); + writeEndOfHeaders(out); + try { + int readBytes; + while((readBytes = fileInput.read(readBuffer)) > 0) { + out.write(readBuffer, 0, readBytes); + } + } catch(IOException e) { + fileInput.close(); + throw e; + } + fileInput.close(); + } + + public String getFilePath(String path) { + int qmark = path.indexOf('?'); + if (qmark >= 0) path = path.substring(0, qmark); + path = path.substring(1); + + if (path.startsWith("/") || path.startsWith(".")) + return null; + if (path.length() == 0) path = "index.html"; + return path; + } + + public void handleMapToJar(BufferedOutputStream out, String path) throws IOException { + path = getFilePath(path); + if (path != null) { + InputStream s = this.getClass().getResourceAsStream("/web/" + path); + if (s != null) { + writeFile(out, path, s); + return; + } + } + writeHttpHeader(out, 404, "Not found"); + writeEndOfHeaders(out); + } + + public void handleMapToDirectory(BufferedOutputStream out, String path, String directoryPath) throws IOException { + path = getFilePath(path); + if (path != null) { + File tilesDirectory = new File(directoryPath); + File tileFile = new File(tilesDirectory, path); + + if (tileFile.getAbsolutePath().startsWith(tilesDirectory.getAbsolutePath()) && tileFile.isFile()) { + FileInputStream s = new FileInputStream(tileFile); + writeFile(out, path, s); + return; + } + } + writeHttpHeader(out, 404, "Not found"); + writeEndOfHeaders(out); + } + + private static Map mimes = new HashMap(); + static { + mimes.put(".html", "text/html"); + mimes.put(".htm", "text/html"); + mimes.put(".js", "text/javascript"); + mimes.put(".png", "image/png"); + mimes.put(".css", "text/css"); + mimes.put(".txt", "text/plain"); + } + public static String getMimeTypeFromExtension(String extension) { + String m = mimes.get(extension); + if (m != null) return m; + return "application/octet-steam"; + } } diff --git a/src/main/java/org/dynmap/debug/BukkitPlayerDebugger.java b/src/main/java/org/dynmap/debug/BukkitPlayerDebugger.java index 4d71a85d..3b772865 100644 --- a/src/main/java/org/dynmap/debug/BukkitPlayerDebugger.java +++ b/src/main/java/org/dynmap/debug/BukkitPlayerDebugger.java @@ -1,7 +1,10 @@ package org.dynmap.debug; import java.util.HashSet; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.bukkit.Color; import org.bukkit.Player; import org.bukkit.event.Event; import org.bukkit.event.Event.Priority; @@ -12,6 +15,10 @@ import org.bukkit.plugin.PluginDescriptionFile; import org.bukkit.plugin.java.JavaPlugin; public class BukkitPlayerDebugger implements Debugger { + protected static final Logger log = Logger.getLogger("Minecraft"); + + private boolean isLogging = true; + private JavaPlugin plugin; private HashSet debugees = new HashSet(); private String debugCommand; @@ -30,6 +37,7 @@ public class BukkitPlayerDebugger implements Debugger { public void enable() { plugin.getServer().getPluginManager().registerEvent(Event.Type.PLAYER_COMMAND, new CommandListener(), Priority.Normal, plugin); plugin.getServer().getPluginManager().registerEvent(Event.Type.PLAYER_QUIT, new CommandListener(), Priority.Normal, plugin); + log.info("Debugger enabled, use: " + debugCommand); } public void disable() { @@ -48,12 +56,28 @@ public class BukkitPlayerDebugger implements Debugger { debugees.clear(); } - public void debug(String message) { + public void sendToDebuggees(String message) { for (Player p : debugees) { p.sendMessage(prepend + message); } } + public void debug(String message) { + sendToDebuggees(message); + if (isLogging) log.info(prepend + message); + } + + public void error(String message) { + sendToDebuggees(prepend + Color.RED + message); + if (isLogging) log.log(Level.SEVERE, prepend + message); + } + + public void error(String message, Throwable thrown) { + sendToDebuggees(prepend + Color.RED + message); + sendToDebuggees(thrown.toString()); + if (isLogging) log.log(Level.SEVERE, prepend + message); + } + protected class CommandListener extends PlayerListener { @Override public void onPlayerCommand(PlayerChatEvent event) { diff --git a/src/main/java/org/dynmap/debug/Debugger.java b/src/main/java/org/dynmap/debug/Debugger.java index 43f5cbe3..45583367 100644 --- a/src/main/java/org/dynmap/debug/Debugger.java +++ b/src/main/java/org/dynmap/debug/Debugger.java @@ -2,4 +2,6 @@ package org.dynmap.debug; public interface Debugger { void debug(String message); + void error(String message); + void error(String message, Throwable thrown); } diff --git a/src/main/java/org/dynmap/render/CaveTileRenderer.java b/src/main/java/org/dynmap/render/CaveTileRenderer.java index 9f233307..593e9c3e 100644 --- a/src/main/java/org/dynmap/render/CaveTileRenderer.java +++ b/src/main/java/org/dynmap/render/CaveTileRenderer.java @@ -5,11 +5,12 @@ import java.util.Map; import org.bukkit.World; import org.dynmap.MapManager; +import org.dynmap.debug.Debugger; public class CaveTileRenderer extends DayTileRenderer { - public CaveTileRenderer(Map colors, String outputPath) { - super(colors, outputPath); + public CaveTileRenderer(Debugger debugger, Map colors, String outputPath) { + super(debugger, colors, outputPath); } @Override diff --git a/src/main/java/org/dynmap/render/DayTileRenderer.java b/src/main/java/org/dynmap/render/DayTileRenderer.java index 26218acf..0dbf1dd2 100644 --- a/src/main/java/org/dynmap/render/DayTileRenderer.java +++ b/src/main/java/org/dynmap/render/DayTileRenderer.java @@ -16,16 +16,31 @@ import javax.imageio.ImageIO; import org.bukkit.World; import org.dynmap.MapManager; import org.dynmap.MapTile; +import org.dynmap.debug.Debugger; public class DayTileRenderer implements MapTileRenderer { - protected static final Logger log = Logger.getLogger("Minecraft"); + protected Debugger debugger; protected String outputPath; + protected String outputZoomPath; private Map colors; - public DayTileRenderer(Map colors, String outputPath) { + public DayTileRenderer(Debugger debugger, Map colors, String outputPath) { + this(debugger, colors, outputPath, convertToZoomPath(outputPath)); + } + + public DayTileRenderer(Debugger debugger, Map colors, String outputPath, String outputZoomPath) { + this.debugger = debugger; this.colors = colors; this.outputPath = outputPath; + this.outputZoomPath = outputZoomPath; } + + private static String convertToZoomPath(String outputPath) { + File outputFile = new File(outputPath); + String zoomFilename = "z" + outputFile.getName(); + return new File(outputFile.getParentFile(), zoomFilename).getPath(); + } + public void render(MapTile tile) { World world = tile.getWorld(); BufferedImage im = new BufferedImage(MapManager.tileWidth, MapManager.tileHeight, BufferedImage.TYPE_INT_RGB); @@ -142,14 +157,17 @@ public class DayTileRenderer implements MapTileRenderer { String tilePath = outputPath .replace("{X}", Integer.toString(tile.px)) .replace("{Y}", Integer.toString(tile.py)); + + debugger.debug("saving tile " + tilePath); + /* save image */ try { File file = new File(tilePath); ImageIO.write(im, "png", file); } catch(IOException e) { - log.log(Level.SEVERE, "Failed to save tile: " + tilePath, e); + debugger.error("Failed to save tile: " + tilePath, e); } catch(java.lang.NullPointerException e) { - log.log(Level.SEVERE, "Failed to save tile (NullPointerException): " + tilePath, e); + debugger.error("Failed to save tile (NullPointerException): " + tilePath, e); } /* now update zoom-out tile */ diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 245488a0..228eb52a 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,3 +1,3 @@ -name: Dynamic Map +name: dynmap main: org.dynmap.DynmapPlugin version: 0.1