|
@@ -1,83 +1,208 @@
|
|
|
package at.acdp.urweb.web;
|
|
|
-import org.nanohttpd.protocols.http.IHTTPSession;
|
|
|
-import org.nanohttpd.protocols.http.NanoHTTPD;
|
|
|
-import org.nanohttpd.protocols.http.response.Response;
|
|
|
-import org.nanohttpd.util.ServerRunner;
|
|
|
-
|
|
|
|
|
|
-import java.util.HashMap;
|
|
|
-import java.util.List;
|
|
|
+import java.io.BufferedInputStream;
|
|
|
+import java.io.File;
|
|
|
+import java.io.IOException;
|
|
|
+import java.security.MessageDigest;
|
|
|
+import java.security.NoSuchAlgorithmException;
|
|
|
+import java.util.Base64;
|
|
|
import java.util.Map;
|
|
|
+import java.util.Timer;
|
|
|
+import java.util.concurrent.atomic.AtomicLong;
|
|
|
+import java.util.logging.Logger;
|
|
|
|
|
|
-import static org.nanohttpd.protocols.http.response.Response.newFixedLengthResponse;
|
|
|
-
|
|
|
-public class WebServer extends NanoHTTPD {
|
|
|
- public WebServer() {
|
|
|
- super(8080);
|
|
|
+import com.eclipsesource.json.Json;
|
|
|
+import com.eclipsesource.json.JsonArray;
|
|
|
+import org.nanohttpd.protocols.http.IHTTPSession;
|
|
|
+import org.nanohttpd.protocols.http.response.IStatus;
|
|
|
+import org.nanohttpd.protocols.http.response.Response;
|
|
|
+import org.nanohttpd.protocols.http.response.Status;
|
|
|
+import org.nanohttpd.protocols.websockets.CloseCode;
|
|
|
+import org.nanohttpd.protocols.websockets.WebSocket;
|
|
|
+import org.nanohttpd.protocols.websockets.WebSocketFrame;
|
|
|
+import org.nanohttpd.router.RouterNanoHTTPD;
|
|
|
+import org.nanohttpd.util.IHandler;
|
|
|
+import org.slf4j.LoggerFactory;
|
|
|
+
|
|
|
+/**
|
|
|
+ * @author Paul S. Hawke (paul.hawke@gmail.com) On: 4/23/14 at 10:31 PM
|
|
|
+ */
|
|
|
+public class WebServer extends RouterNanoHTTPD{
|
|
|
+ private final static org.slf4j.Logger logger = LoggerFactory.getLogger(WebServer.class);
|
|
|
+
|
|
|
+ /**
|
|
|
+ * logger to log to.
|
|
|
+ */
|
|
|
+ private static final Logger LOG = Logger.getLogger(WebServer.class.getName());
|
|
|
+ private java.util.Timer t=new Timer();
|
|
|
+ private final boolean debug;
|
|
|
+
|
|
|
+ public WebServer(int port, boolean debug) {
|
|
|
+ super(port);
|
|
|
+ this.debug = debug;
|
|
|
+ this.addHTTPInterceptor(new WebServer.Interceptor());
|
|
|
+ addMappings();
|
|
|
}
|
|
|
|
|
|
- public static void main(String[] args) {
|
|
|
- ServerRunner.run(WebServer.class);
|
|
|
+ @Override
|
|
|
+ public void addMappings() {
|
|
|
+ super.addMappings();
|
|
|
+ addRoute("/user/:id", BlockHandler.class);
|
|
|
+ addRoute("/interface", UriResponder.class); // this will cause an error
|
|
|
+ addRoute("/toBeDeleted", String.class);
|
|
|
+ removeRoute("/toBeDeleted");
|
|
|
+
|
|
|
+ addRoute("/static(.)+", MyHandler.class, new File("webroot/").getAbsoluteFile());
|
|
|
+ //addRoute("/", StaticPageTestHandler.class, new File("webroot/index.html").getAbsoluteFile());
|
|
|
+ }
|
|
|
+ protected WebSocket openWebSocket(IHTTPSession handshake) {
|
|
|
+ return new MyWebSocket(this, handshake);
|
|
|
}
|
|
|
|
|
|
- @Override public Response serve(IHTTPSession session) {
|
|
|
- Map<String, List<String>> decodedQueryParameters =
|
|
|
- decodeParameters(session.getQueryParameterString());
|
|
|
+ private static class MyWebSocket extends WebSocket {
|
|
|
+ private final WebServer server;
|
|
|
|
|
|
- StringBuilder sb = new StringBuilder();
|
|
|
- sb.append("<html>");
|
|
|
- sb.append("<head><title>Debug Server</title></head>");
|
|
|
- sb.append("<body>");
|
|
|
- sb.append("<h1>Debug Server</h1>");
|
|
|
+ public MyWebSocket(WebServer server, IHTTPSession handshakeRequest) {
|
|
|
+ super(handshakeRequest);
|
|
|
+ this.server = server;
|
|
|
+ }
|
|
|
|
|
|
- sb.append("<p><blockquote><b>URI</b> = ").append(
|
|
|
- String.valueOf(session.getUri())).append("<br />");
|
|
|
+ @Override
|
|
|
+ protected void onOpen() {
|
|
|
+ logger.info(String.format("client connected: %s",getHandshakeRequest().getRemoteIpAddress().toString()));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ protected void onClose(CloseCode code, String reason, boolean initiatedByRemote) {
|
|
|
+ if (server.debug) {
|
|
|
+ System.out.println("C [" + (initiatedByRemote ? "Remote" : "Self") + "] " + (code != null ? code : "UnknownCloseCode[" + code + "]")
|
|
|
+ + (reason != null && !reason.isEmpty() ? ": " + reason : ""));
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- sb.append("<b>Method</b> = ").append(
|
|
|
- String.valueOf(session.getMethod())).append("</blockquote></p>");
|
|
|
+ @Override
|
|
|
+ protected void onMessage(WebSocketFrame message) {
|
|
|
+ try {
|
|
|
+ message.setUnmasked();
|
|
|
+ sendFrame(message);
|
|
|
+ } catch (IOException e) {
|
|
|
+ throw new RuntimeException(e);
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- sb.append("<h3>Headers</h3><p><blockquote>").
|
|
|
- append(toString(session.getHeaders())).append("</blockquote></p>");
|
|
|
+ @Override
|
|
|
+ protected void onPong(WebSocketFrame pong) {
|
|
|
+ if (server.debug) {
|
|
|
+ System.out.println("P " + pong);
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- sb.append("<h3>Parms</h3><p><blockquote>").
|
|
|
- append(toString(session.getParms())).append("</blockquote></p>");
|
|
|
+ @Override
|
|
|
+ protected void onException(IOException exception) {
|
|
|
+ logger.warn("exception occured", exception);
|
|
|
+ }
|
|
|
|
|
|
- sb.append("<h3>Parms (multi values?)</h3><p><blockquote>").
|
|
|
- append(toString(decodedQueryParameters)).append("</blockquote></p>");
|
|
|
+ @Override
|
|
|
+ protected void debugFrameReceived(WebSocketFrame frame) {
|
|
|
+ if (server.debug) {
|
|
|
+ System.out.println("R " + frame);
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- try {
|
|
|
- Map<String, String> files = new HashMap<String, String>();
|
|
|
- session.parseBody(files);
|
|
|
- sb.append("<h3>Files</h3><p><blockquote>").
|
|
|
- append(toString(files)).append("</blockquote></p>");
|
|
|
- } catch (Exception e) {
|
|
|
- e.printStackTrace();
|
|
|
+ @Override
|
|
|
+ protected void debugFrameSent(WebSocketFrame frame) {
|
|
|
+ if (server.debug) {
|
|
|
+ System.out.println("S " + frame);
|
|
|
+ }
|
|
|
}
|
|
|
+ }
|
|
|
|
|
|
- sb.append("</body>");
|
|
|
- sb.append("</html>");
|
|
|
- return newFixedLengthResponse(sb.toString());
|
|
|
+ public static String makeAcceptKey(String key) throws NoSuchAlgorithmException {
|
|
|
+ MessageDigest md = MessageDigest.getInstance("SHA-1");
|
|
|
+ String text = key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
|
|
+ md.update(text.getBytes(), 0, text.length());
|
|
|
+ byte[] sha1hash = md.digest();
|
|
|
+ Base64.Encoder b64encoder = Base64.getEncoder();
|
|
|
+ return b64encoder.encodeToString(sha1hash);
|
|
|
}
|
|
|
|
|
|
- private String toString(Map<String, ? extends Object> map) {
|
|
|
- if (map.size() == 0) {
|
|
|
- return "";
|
|
|
+ private boolean isWebSocketConnectionHeader(Map<String, String> headers) {
|
|
|
+ String connection = (String)headers.get("connection");
|
|
|
+ return connection != null && connection.toLowerCase().contains("Upgrade".toLowerCase());
|
|
|
+ }
|
|
|
+
|
|
|
+ protected boolean isWebsocketRequested(IHTTPSession session) {
|
|
|
+ Map<String, String> headers = session.getHeaders();
|
|
|
+ String upgrade = (String)headers.get("upgrade");
|
|
|
+ boolean isCorrectConnection = this.isWebSocketConnectionHeader(headers);
|
|
|
+ boolean isUpgrade = "websocket".equalsIgnoreCase(upgrade);
|
|
|
+ return isUpgrade && isCorrectConnection;
|
|
|
+ }
|
|
|
+
|
|
|
+ public Response handleWebSocket(IHTTPSession session) {
|
|
|
+ Map<String, String> headers = session.getHeaders();
|
|
|
+ if (this.isWebsocketRequested(session)) {
|
|
|
+ if (!"13".equalsIgnoreCase((String)headers.get("sec-websocket-version"))) {
|
|
|
+ return Response.newFixedLengthResponse(Status.BAD_REQUEST, "text/plain", "Invalid Websocket-Version " + (String)headers.get("sec-websocket-version"));
|
|
|
+ } else if (!headers.containsKey("sec-websocket-key")) {
|
|
|
+ return Response.newFixedLengthResponse(Status.BAD_REQUEST, "text/plain", "Missing Websocket-Key");
|
|
|
+ } else {
|
|
|
+ WebSocket webSocket = this.openWebSocket(session);
|
|
|
+ Response handshakeResponse = webSocket.getHandshakeResponse();
|
|
|
+
|
|
|
+ try {
|
|
|
+ handshakeResponse.addHeader("sec-websocket-accept", makeAcceptKey((String)headers.get("sec-websocket-key")));
|
|
|
+ } catch (NoSuchAlgorithmException var6) {
|
|
|
+ return Response.newFixedLengthResponse(Status.INTERNAL_ERROR, "text/plain", "The SHA-1 Algorithm required for websockets is not available on the server.");
|
|
|
+ }
|
|
|
+
|
|
|
+ if (headers.containsKey("sec-websocket-protocol")) {
|
|
|
+ handshakeResponse.addHeader("sec-websocket-protocol", ((String)headers.get("sec-websocket-protocol")).split(",")[0]);
|
|
|
+ }
|
|
|
+
|
|
|
+ return handshakeResponse;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ protected final class Interceptor implements IHandler<IHTTPSession, Response> {
|
|
|
+ public Interceptor() {
|
|
|
+ }
|
|
|
+ public Response handle(IHTTPSession input) {
|
|
|
+ return WebServer.this.handleWebSocket(input);
|
|
|
}
|
|
|
- return unsortedList(map);
|
|
|
}
|
|
|
|
|
|
- private String unsortedList(Map<String, ? extends Object> map) {
|
|
|
- StringBuilder sb = new StringBuilder();
|
|
|
- sb.append("<ul>");
|
|
|
- for (Map.Entry entry : map.entrySet()) {
|
|
|
- listItem(sb, entry);
|
|
|
+ public static class BlockHandler extends DefaultHandler {
|
|
|
+ @Override
|
|
|
+ public String getMimeType() {
|
|
|
+ return MIME_PLAINTEXT;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public String getText() {
|
|
|
+ return "not implemented";
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public IStatus getStatus() {
|
|
|
+ return Status.OK;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Response get(UriResource uriResource, Map<String, String> urlParams, IHTTPSession session) {
|
|
|
+ JsonArray blocks = Json.array();
|
|
|
+ return Response.newFixedLengthResponse(blocks.toString());
|
|
|
}
|
|
|
- sb.append("</ul>");
|
|
|
- return sb.toString();
|
|
|
}
|
|
|
|
|
|
- private void listItem(StringBuilder sb, Map.Entry entry) {
|
|
|
- sb.append("<li><code><b>").append(entry.getKey()).
|
|
|
- append("</b> = ").append(entry.getValue()).append("</code></li>");
|
|
|
+ public static class MyHandler extends StaticPageHandler{
|
|
|
+ @Override
|
|
|
+ protected BufferedInputStream fileToInputStream(File fileOrdirectory) throws IOException {
|
|
|
+
|
|
|
+ return new BufferedInputStream(this.getClass().getResourceAsStream(fileOrdirectory.toString()));
|
|
|
+ }
|
|
|
}
|
|
|
}
|