Moved handlers to their own (independent) classes.

This commit is contained in:
FrozenCow 2011-02-05 20:51:20 +01:00
parent 883eba6890
commit 2a79aea7bb
7 changed files with 274 additions and 244 deletions

View File

@ -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();

View File

@ -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<String, HttpHandler> handlers = new TreeMap<String, HttpHandler>(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);
}
}

View File

@ -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<String, HttpHandler> handlers = new TreeMap<String, HttpHandler>(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<String,String> field : response.fields.entrySet()) {
for (Entry<String, String> 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<String, HttpHandler> entry : handlers.entrySet()) {
for (Entry<String, HttpHandler> 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;
}
}
}

View File

@ -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";
}
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}