summaryrefslogtreecommitdiffstats
path: root/dozentenmodulserver
diff options
context:
space:
mode:
authorSimon Rettberg2015-09-01 19:18:42 +0200
committerSimon Rettberg2015-09-01 19:18:42 +0200
commitcc9c9990466dcbc44070bd4474fef29a505866a6 (patch)
treefebc32f3a6724c396fc64f48d8cd46c8db2f603c /dozentenmodulserver
parent[*] Clean up pom.xml, add shading to client, clean up .gitignore (diff)
downloadtutor-module-cc9c9990466dcbc44070bd4474fef29a505866a6.tar.gz
tutor-module-cc9c9990466dcbc44070bd4474fef29a505866a6.tar.xz
tutor-module-cc9c9990466dcbc44070bd4474fef29a505866a6.zip
[server] Add WebServer, add XML serialization classes (vmchooser list)
Diffstat (limited to 'dozentenmodulserver')
-rw-r--r--dozentenmodulserver/pom.xml5
-rw-r--r--dozentenmodulserver/src/main/java/fi/iki/elonen/NanoHTTPD.java1098
-rw-r--r--dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/App.java30
-rw-r--r--dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/web/VmChooserEntryXml.java24
-rw-r--r--dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/web/VmChooserListXml.java14
-rw-r--r--dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/web/VmChooserParamXml.java14
-rw-r--r--dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/web/WebServer.java36
7 files changed, 1203 insertions, 18 deletions
diff --git a/dozentenmodulserver/pom.xml b/dozentenmodulserver/pom.xml
index 9c801ba1..cc37b7d1 100644
--- a/dozentenmodulserver/pom.xml
+++ b/dozentenmodulserver/pom.xml
@@ -157,6 +157,11 @@
<artifactId>commons-net</artifactId>
<version>3.3</version>
</dependency>
+ <dependency>
+ <groupId>org.anarres.mirrors.simpleframework</groupId>
+ <artifactId>simple-xml</artifactId>
+ <version>2.7.1-P1</version>
+ </dependency>
</dependencies>
</project>
diff --git a/dozentenmodulserver/src/main/java/fi/iki/elonen/NanoHTTPD.java b/dozentenmodulserver/src/main/java/fi/iki/elonen/NanoHTTPD.java
new file mode 100644
index 00000000..854f1686
--- /dev/null
+++ b/dozentenmodulserver/src/main/java/fi/iki/elonen/NanoHTTPD.java
@@ -0,0 +1,1098 @@
+package fi.iki.elonen;
+
+/*
+ * #%L
+ * NanoHttpd-Core
+ * %%
+ * Copyright (C) 2012 - 2015 nanohttpd
+ * %%
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the nanohttpd nor the names of its contributors
+ * may be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ * #L%
+ */
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.io.PushbackInputStream;
+import java.io.Reader;
+import java.io.UnsupportedEncodingException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketException;
+import java.net.SocketTimeoutException;
+import java.net.URLDecoder;
+import java.nio.charset.StandardCharsets;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.StringTokenizer;
+import java.util.TimeZone;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+import org.joda.time.format.DateTimeFormat;
+import org.joda.time.format.DateTimeFormatter;
+import org.joda.time.format.DateTimeFormatterBuilder;
+
+/**
+ * A simple, tiny, nicely embeddable HTTP server in Java
+ * <p/>
+ * <p/>
+ * NanoHTTPD
+ * <p>
+ * Copyright (c) 2012-2013 by Paul S. Hawke, 2001,2005-2013 by Jarno Elonen,
+ * 2010 by Konstantinos Togias
+ * </p>
+ * <p/>
+ * <p/>
+ * <b>Features + limitations: </b>
+ * <ul>
+ * <p/>
+ * <li>Only one Java file</li>
+ * <li>Java 5 compatible</li>
+ * <li>Released as open source, Modified BSD licence</li>
+ * <li>No fixed config files, logging, authorization etc. (Implement yourself if
+ * you need them.)</li>
+ * <li>Supports parameter parsing of GET and POST methods (+ rudimentary PUT
+ * support in 1.25)</li>
+ * <li>Supports both dynamic content and file serving</li>
+ * <li>Supports file upload (since version 1.2, 2010)</li>
+ * <li>Supports partial content (streaming)</li>
+ * <li>Supports ETags</li>
+ * <li>Never caches anything</li>
+ * <li>Doesn't limit bandwidth, request time or simultaneous connections</li>
+ * <li>Default code serves files and shows all HTTP parameters and headers</li>
+ * <li>File server supports directory listing, index.html and index.htm</li>
+ * <li>File server supports partial content (streaming)</li>
+ * <li>File server supports ETags</li>
+ * <li>File server does the 301 redirection trick for directories without '/'</li>
+ * <li>File server supports simple skipping for files (continue download)</li>
+ * <li>File server serves also very long files without memory overhead</li>
+ * <li>Contains a built-in list of most common MIME types</li>
+ * <li>All header names are converted to lower case so they don't vary between
+ * browsers/clients</li>
+ * <p/>
+ * </ul>
+ * <p/>
+ * <p/>
+ * <b>How to use: </b>
+ * <ul>
+ * <p/>
+ * <li>Subclass and implement serve() and embed to your own program</li>
+ * <p/>
+ * </ul>
+ * <p/>
+ * See the separate "LICENSE.md" file for the distribution license (Modified BSD
+ * licence)
+ */
+public abstract class NanoHTTPD implements Runnable {
+ /**
+ * Maximum time to wait on Socket.getInputStream().read() (in milliseconds)
+ * This is required as the Keep-Alive HTTP connections would otherwise
+ * block the socket reading thread forever (or as long the browser is open).
+ */
+ public static final int SOCKET_READ_TIMEOUT = 15000;
+ /**
+ * Common MIME type for dynamic content: plain text
+ */
+ public static final String MIME_PLAINTEXT = "text/plain";
+ /**
+ * Common MIME type for dynamic content: html
+ */
+ public static final String MIME_HTML = "text/html";
+ /**
+ * Pseudo-Parameter to use to store the actual query string in the
+ * parameters map for later
+ * re-processing.
+ */
+ private static final String QUERY_STRING_PARAMETER = "NanoHttpd.QUERY_STRING";
+ private final String hostname;
+ private final int myPort;
+ private ServerSocket myServerSocket;
+ private Set<Socket> openConnections = new HashSet<Socket>();
+ /**
+ * Pluggable strategy for asynchronously executing requests.
+ */
+ private AsyncRunner asyncRunner;
+
+ /**
+ * Constructs an HTTP server on given port.
+ */
+ public NanoHTTPD(int port) {
+ this(null, port);
+ }
+
+ /**
+ * Constructs an HTTP server on given hostname and port.
+ */
+ public NanoHTTPD(String hostname, int port) {
+ this.hostname = hostname;
+ this.myPort = port;
+ setAsyncRunner(new DefaultAsyncRunner());
+ }
+
+ protected static final void safeClose(Closeable closeable) {
+ if (closeable != null) {
+ try {
+ closeable.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+
+ /**
+ * Start the server.
+ *
+ * @throws IOException if the socket is in use.
+ */
+ @Override
+ public void run() {
+ try {
+ myServerSocket = new ServerSocket();
+ myServerSocket.setReuseAddress(true);
+ myServerSocket.bind((hostname != null) ? new InetSocketAddress(hostname, myPort)
+ : new InetSocketAddress(myPort));
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+
+ do {
+ try {
+ final Socket finalAccept = myServerSocket.accept();
+ registerConnection(finalAccept);
+ finalAccept.setSoTimeout(SOCKET_READ_TIMEOUT);
+ final InputStream inputStream = finalAccept.getInputStream();
+ asyncRunner.exec(new Runnable() {
+ @Override
+ public void run() {
+ OutputStream outputStream = null;
+ try {
+ outputStream = finalAccept.getOutputStream();
+ HTTPSession session = new HTTPSession(inputStream, outputStream,
+ finalAccept.getInetAddress());
+ while (!finalAccept.isClosed()) {
+ session.execute();
+ }
+ } catch (Exception e) {
+ // When the socket is closed by the client, we throw our own SocketException
+ // to break the "keep alive" loop above.
+ if (!(e instanceof SocketException && "NanoHttpd Shutdown".equals(e.getMessage()))) {
+ e.printStackTrace();
+ }
+ } finally {
+ safeClose(outputStream);
+ safeClose(inputStream);
+ safeClose(finalAccept);
+ unRegisterConnection(finalAccept);
+ }
+ }
+ });
+ } catch (IOException e) {
+ }
+ } while (!myServerSocket.isClosed());
+ }
+
+ /**
+ * Stop the server.
+ */
+ public void stop() {
+ try {
+ safeClose(myServerSocket);
+ closeAllConnections();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Registers that a new connection has been set up.
+ *
+ * @param socket the {@link Socket} for the connection.
+ */
+ public synchronized void registerConnection(Socket socket) {
+ openConnections.add(socket);
+ }
+
+ /**
+ * Registers that a connection has been closed
+ *
+ * @param socket
+ * the {@link Socket} for the connection.
+ */
+ public synchronized void unRegisterConnection(Socket socket) {
+ openConnections.remove(socket);
+ }
+
+ /**
+ * Forcibly closes all connections that are open.
+ */
+ public synchronized void closeAllConnections() {
+ for (Socket socket : openConnections) {
+ safeClose(socket);
+ }
+ }
+
+ public final int getListeningPort() {
+ return myServerSocket == null ? -1 : myServerSocket.getLocalPort();
+ }
+
+ public final boolean wasStarted() {
+ return myServerSocket != null;
+ }
+
+ public final boolean isAlive() {
+ return wasStarted() && !myServerSocket.isClosed();
+ }
+
+ /**
+ * Override this to customize the server.
+ * <p/>
+ * <p/>
+ * (By default, this returns a 404 "Not Found" plain text error response.)
+ *
+ * @param uri Percent-decoded URI without parameters, for example
+ * "/index.cgi"
+ * @param method "GET", "POST" etc.
+ * @param parms Parsed, percent decoded parameters from URI and, in case of
+ * POST, data.
+ * @param headers Header entries, percent decoded
+ * @return HTTP response, see class Response for details
+ */
+ @Deprecated
+ public Response serve(String uri, Method method, Map<String, String> headers, Map<String, String> parms,
+ Map<String, String> files) {
+ return new Response(Response.Status.NOT_FOUND, MIME_PLAINTEXT, "Not Found");
+ }
+
+ /**
+ * Override this to customize the server.
+ * <p/>
+ * <p/>
+ * (By default, this returns a 404 "Not Found" plain text error response.)
+ *
+ * @param session The HTTP session
+ * @return HTTP response, see class Response for details
+ */
+ public Response serve(IHTTPSession session) {
+ Map<String, String> files = new HashMap<String, String>();
+ Method method = session.getMethod();
+ if (Method.PUT.equals(method) || Method.POST.equals(method)) {
+ try {
+ session.parseBody(files);
+ } catch (IOException ioe) {
+ return new Response(Response.Status.INTERNAL_ERROR, MIME_PLAINTEXT,
+ "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage());
+ } catch (ResponseException re) {
+ return new Response(re.getStatus(), MIME_PLAINTEXT, re.getMessage());
+ }
+ }
+
+ Map<String, String> parms = session.getParms();
+ parms.put(QUERY_STRING_PARAMETER, session.getQueryParameterString());
+ return serve(session.getUri(), method, session.getHeaders(), parms, files);
+ }
+
+ /**
+ * Decode percent encoded <code>String</code> values.
+ *
+ * @param str the percent encoded <code>String</code>
+ * @return expanded form of the input, for example "foo%20bar" becomes
+ * "foo bar"
+ */
+ protected String decodePercent(String str) {
+ String decoded = null;
+ try {
+ decoded = URLDecoder.decode(str, "UTF8");
+ } catch (UnsupportedEncodingException ignored) {
+ }
+ return decoded;
+ }
+
+ /**
+ * Decode parameters from a URL, handing the case where a single parameter
+ * name might have been
+ * supplied several times, by return lists of values. In general these lists
+ * will contain a
+ * single
+ * element.
+ *
+ * @param parms original <b>NanoHTTPD</b> parameters values, as passed to
+ * the <code>serve()</code> method.
+ * @return a map of <code>String</code> (parameter name) to
+ * <code>List&lt;String&gt;</code> (a
+ * list of the values supplied).
+ */
+ protected Map<String, List<String>> decodeParameters(Map<String, String> parms) {
+ return this.decodeParameters(parms.get(QUERY_STRING_PARAMETER));
+ }
+
+ /**
+ * Decode parameters from a URL, handing the case where a single parameter
+ * name might have been
+ * supplied several times, by return lists of values. In general these lists
+ * will contain a
+ * single
+ * element.
+ *
+ * @param queryString a query string pulled from the URL.
+ * @return a map of <code>String</code> (parameter name) to
+ * <code>List&lt;String&gt;</code> (a
+ * list of the values supplied).
+ */
+ protected Map<String, List<String>> decodeParameters(String queryString) {
+ Map<String, List<String>> parms = new HashMap<String, List<String>>();
+ if (queryString != null) {
+ StringTokenizer st = new StringTokenizer(queryString, "&");
+ while (st.hasMoreTokens()) {
+ String e = st.nextToken();
+ int sep = e.indexOf('=');
+ String propertyName = (sep >= 0) ? decodePercent(e.substring(0, sep)).trim() : decodePercent(
+ e).trim();
+ if (!parms.containsKey(propertyName)) {
+ parms.put(propertyName, new ArrayList<String>());
+ }
+ String propertyValue = (sep >= 0) ? decodePercent(e.substring(sep + 1)) : null;
+ if (propertyValue != null) {
+ parms.get(propertyName).add(propertyValue);
+ }
+ }
+ }
+ return parms;
+ }
+
+ // ------------------------------------------------------------------------------- //
+ //
+ // Threading Strategy.
+ //
+ // ------------------------------------------------------------------------------- //
+
+ /**
+ * Pluggable strategy for asynchronously executing requests.
+ *
+ * @param asyncRunner new strategy for handling threads.
+ */
+ public void setAsyncRunner(AsyncRunner asyncRunner) {
+ this.asyncRunner = asyncRunner;
+ }
+
+ /**
+ * HTTP Request methods, with the ability to decode a <code>String</code>
+ * back to its enum value.
+ */
+ public enum Method {
+ GET,
+ PUT,
+ POST,
+ DELETE,
+ HEAD,
+ OPTIONS;
+
+ static Method lookup(String method) {
+ for (Method m : Method.values()) {
+ if (m.toString().equalsIgnoreCase(method)) {
+ return m;
+ }
+ }
+ return null;
+ }
+ }
+
+ /**
+ * Pluggable strategy for asynchronously executing requests.
+ */
+ public interface AsyncRunner {
+ void exec(Runnable code);
+ }
+
+ // ------------------------------------------------------------------------------- //
+
+ /**
+ * Default threading strategy for NanoHTTPD.
+ * <p/>
+ * <p>
+ * Uses a thread pool.
+ * </p>
+ */
+ public static class DefaultAsyncRunner implements AsyncRunner {
+ private ExecutorService pool = new ThreadPoolExecutor(2, 16, 1, TimeUnit.MINUTES,
+ new ArrayBlockingQueue<Runnable>(16));
+
+ @Override
+ public void exec(Runnable code) {
+ try {
+ pool.execute(code);
+ } catch (RejectedExecutionException e) {
+ }
+ }
+ }
+
+ /**
+ * HTTP response. Return one of these from serve().
+ */
+ public static class Response {
+ /**
+ * HTTP status code after processing, e.g. "200 OK", Status.OK
+ */
+ private IStatus status;
+ /**
+ * MIME type of content, e.g. "text/html"
+ */
+ private String mimeType;
+ /**
+ * Data of the response, may be null.
+ */
+ private InputStream data;
+ /**
+ * Headers for the HTTP response. Use addHeader() to add lines.
+ */
+ private Map<String, String> header = new HashMap<String, String>();
+ /**
+ * The request method that spawned this response.
+ */
+ private Method requestMethod;
+ /**
+ * Use chunkedTransfer
+ */
+ private boolean chunkedTransfer;
+
+ /**
+ * Default constructor: response = Status.OK, mime = MIME_HTML and your
+ * supplied message
+ */
+ public Response(String msg) {
+ this(Status.OK, MIME_HTML, msg);
+ }
+
+ /**
+ * Basic constructor.
+ */
+ public Response(IStatus status, String mimeType, InputStream data) {
+ this.status = status;
+ this.mimeType = mimeType;
+ this.data = data;
+ }
+
+ /**
+ * Convenience method that makes an InputStream out of given text.
+ */
+ public Response(IStatus status, String mimeType, String txt) {
+ this.status = status;
+ this.mimeType = mimeType;
+ try {
+ this.data = txt != null ? new ByteArrayInputStream(txt.getBytes("UTF-8")) : null;
+ } catch (java.io.UnsupportedEncodingException uee) {
+ uee.printStackTrace();
+ }
+ }
+
+ /**
+ * Adds given line to the header.
+ */
+ public void addHeader(String name, String value) {
+ header.put(name, value);
+ }
+
+ public String getHeader(String name) {
+ return header.get(name);
+ }
+
+ private static final DateTimeFormatter headerDateFormatter = DateTimeFormat.forPattern(
+ "E, d MMM yyyy HH:mm:ss 'GMT'")
+ .withLocale(Locale.US)
+ .withZoneUTC();
+
+ /**
+ * Sends given response to the socket.
+ */
+ protected void send(OutputStream outputStream) {
+ String mime = mimeType;
+
+ StringBuilder sb = new StringBuilder();
+ if (status == null) {
+ throw new Error("sendResponse(): Status can't be null.");
+ }
+ sb.append("HTTP/1.1 ");
+ sb.append(status.getDescription());
+ sb.append(" \r\n");
+
+ if (mime != null) {
+ sb.append("Content-Type: ");
+ sb.append(mime);
+ sb.append("\r\n");
+ }
+
+ if (header == null || header.get("Date") == null) {
+ sb.append("Date: ");
+ sb.append(headerDateFormatter.print(System.currentTimeMillis()));
+ sb.append("\r\n");
+ }
+
+ if (header != null) {
+ for (Entry<String, String> item : header.entrySet()) {
+ sb.append(item.getKey());
+ sb.append(": ");
+ sb.append(item.getValue());
+ sb.append("\r\n");
+ }
+ }
+
+ sendConnectionHeaderIfNotAlreadyPresent(sb, header);
+
+ try {
+ if (requestMethod != Method.HEAD && chunkedTransfer) {
+ sendAsChunked(outputStream, sb);
+ } else {
+ int pending = data != null ? data.available() : 0;
+ pending = sendContentLengthHeaderIfNotAlreadyPresent(sb, header, pending);
+ sb.append("\r\n");
+ outputStream.write(sb.toString().getBytes(StandardCharsets.UTF_8));
+ sb.setLength(0);
+ sendAsFixedLength(outputStream, pending);
+ }
+
+ if (sb.length() != 0) {
+ outputStream.write(sb.toString().getBytes(StandardCharsets.UTF_8));
+ }
+ safeClose(data);
+ } catch (IOException ioe) {
+ // Couldn't write? No can do.
+ }
+ }
+
+ protected int sendContentLengthHeaderIfNotAlreadyPresent(StringBuilder sb,
+ Map<String, String> header, int size) {
+ for (String headerName : header.keySet()) {
+ if (headerName.equalsIgnoreCase("content-length")) {
+ try {
+ return Integer.parseInt(header.get(headerName));
+ } catch (NumberFormatException ex) {
+ return size;
+ }
+ }
+ }
+
+ sb.append("Content-Length: ");
+ sb.append(size);
+ sb.append("\r\n");
+ return size;
+ }
+
+ protected void sendConnectionHeaderIfNotAlreadyPresent(StringBuilder sb, Map<String, String> header) {
+ if (!headerAlreadySent(header, "connection")) {
+ sb.append("Connection: keep-alive\r\n");
+ }
+ }
+
+ private boolean headerAlreadySent(Map<String, String> header, String name) {
+ for (String headerName : header.keySet()) {
+ if (headerName.equalsIgnoreCase(name))
+ return true;
+ }
+ return false;
+ }
+
+ private static final byte[] CRLF = "\r\n".getBytes();
+ private static final byte[] CHUNKED_END = "0\r\n\r\n".getBytes();
+ private static final int BUFFER_SIZE = 256 * 1024;
+
+ private void sendAsChunked(OutputStream outputStream, StringBuilder sb) throws IOException {
+ sb.append("Transfer-Encoding: chunked\r\n");
+ sb.append("\r\n");
+ outputStream.write(sb.toString().getBytes(StandardCharsets.UTF_8));
+ sb.setLength(0);
+ byte[] buff = new byte[BUFFER_SIZE];
+ int read;
+ while ((read = data.read(buff)) > 0) {
+ outputStream.write(String.format("%x\r\n", read).getBytes());
+ outputStream.write(buff, 0, read);
+ outputStream.write(CRLF);
+ }
+ outputStream.write(CHUNKED_END);
+ }
+
+ private void sendAsFixedLength(OutputStream outputStream, int pending) throws IOException {
+ if (requestMethod != Method.HEAD && data != null) {
+ int BUFFER_SIZE = 16 * 1024;
+ byte[] buff = new byte[BUFFER_SIZE];
+ while (pending > 0) {
+ int read = data.read(buff, 0, ((pending > BUFFER_SIZE) ? BUFFER_SIZE : pending));
+ if (read <= 0) {
+ break;
+ }
+ outputStream.write(buff, 0, read);
+ pending -= read;
+ }
+ }
+ }
+
+ public IStatus getStatus() {
+ return status;
+ }
+
+ public void setStatus(IStatus status) {
+ this.status = status;
+ }
+
+ public String getMimeType() {
+ return mimeType;
+ }
+
+ public void setMimeType(String mimeType) {
+ this.mimeType = mimeType;
+ }
+
+ public InputStream getData() {
+ return data;
+ }
+
+ public void setData(InputStream data) {
+ this.data = data;
+ }
+
+ public Method getRequestMethod() {
+ return requestMethod;
+ }
+
+ public void setRequestMethod(Method requestMethod) {
+ this.requestMethod = requestMethod;
+ }
+
+ public void setChunkedTransfer(boolean chunkedTransfer) {
+ this.chunkedTransfer = chunkedTransfer;
+ }
+
+ public interface IStatus {
+ int getRequestStatus();
+
+ String getDescription();
+ }
+
+ /**
+ * Some HTTP response status codes
+ */
+ public enum Status implements IStatus {
+ SWITCH_PROTOCOL(101, "Switching Protocols"),
+ OK(200, "OK"),
+ CREATED(201, "Created"),
+ ACCEPTED(202, "Accepted"),
+ NO_CONTENT(204, "No Content"),
+ PARTIAL_CONTENT(206, "Partial Content"),
+ REDIRECT(301, "Moved Permanently"),
+ NOT_MODIFIED(304, "Not Modified"),
+ BAD_REQUEST(400, "Bad Request"),
+ UNAUTHORIZED(401, "Unauthorized"),
+ FORBIDDEN(403, "Forbidden"),
+ NOT_FOUND(404, "Not Found"),
+ METHOD_NOT_ALLOWED(405, "Method Not Allowed"),
+ RANGE_NOT_SATISFIABLE(416, "Requested Range Not Satisfiable"),
+ INTERNAL_ERROR(500, "Internal Server Error");
+ private final int requestStatus;
+ private final String description;
+
+ Status(int requestStatus, String description) {
+ this.requestStatus = requestStatus;
+ this.description = description;
+ }
+
+ @Override
+ public int getRequestStatus() {
+ return this.requestStatus;
+ }
+
+ @Override
+ public String getDescription() {
+ return "" + this.requestStatus + " " + description;
+ }
+ }
+ }
+
+ public static final class ResponseException extends Exception {
+ private static final long serialVersionUID = 6569838532917408380L;
+ private final Response.Status status;
+
+ public ResponseException(Response.Status status, String message) {
+ super(message);
+ this.status = status;
+ }
+
+ public ResponseException(Response.Status status, String message, Exception e) {
+ super(message, e);
+ this.status = status;
+ }
+
+ public Response.Status getStatus() {
+ return status;
+ }
+ }
+
+ /**
+ * Handles one session, i.e. parses the HTTP request and returns the
+ * response.
+ */
+ public interface IHTTPSession {
+ void execute() throws IOException;
+
+ Map<String, String> getParms();
+
+ Map<String, String> getHeaders();
+
+ /**
+ * @return the path part of the URL.
+ */
+ String getUri();
+
+ String getQueryParameterString();
+
+ Method getMethod();
+
+ InputStream getInputStream();
+
+ /**
+ * Adds the files in the request body to the files map.
+ *
+ * @param files map to modify
+ */
+ void parseBody(Map<String, String> files) throws IOException, ResponseException;
+ }
+
+ protected class HTTPSession implements IHTTPSession {
+ public static final int BUFSIZE = 8192;
+ private final OutputStream outputStream;
+ private PushbackInputStream inputStream;
+ private int splitbyte;
+ private int rlen;
+ private String uri;
+ private Method method;
+ private Map<String, String> parms;
+ private Map<String, String> headers;
+ private String queryParameterString;
+ private String remoteIp;
+
+ public HTTPSession(InputStream inputStream, OutputStream outputStream) {
+ this.inputStream = new PushbackInputStream(inputStream, BUFSIZE);
+ this.outputStream = outputStream;
+ }
+
+ public HTTPSession(InputStream inputStream, OutputStream outputStream, InetAddress inetAddress) {
+ this.inputStream = new PushbackInputStream(inputStream, BUFSIZE);
+ this.outputStream = outputStream;
+ remoteIp = inetAddress.isLoopbackAddress() || inetAddress.isAnyLocalAddress() ? "127.0.0.1"
+ : inetAddress.getHostAddress().toString();
+ headers = new HashMap<String, String>();
+ }
+
+ @Override
+ public void execute() throws IOException {
+ try {
+ // Read the first 8192 bytes.
+ // The full header should fit in here.
+ // Apache's default header limit is 8KB.
+ // Do NOT assume that a single read will get the entire header at once!
+ byte[] buf = new byte[BUFSIZE];
+ splitbyte = 0;
+ rlen = 0;
+ {
+ int read = -1;
+ try {
+ read = inputStream.read(buf, 0, BUFSIZE);
+ } catch (Exception e) {
+ safeClose(inputStream);
+ safeClose(outputStream);
+ throw new SocketException("NanoHttpd Shutdown");
+ }
+ if (read == -1) {
+ // socket was been closed
+ safeClose(inputStream);
+ safeClose(outputStream);
+ throw new SocketException("NanoHttpd Shutdown");
+ }
+ while (read > 0) {
+ rlen += read;
+ splitbyte = findHeaderEnd(buf, rlen);
+ if (splitbyte > 0)
+ break;
+ read = inputStream.read(buf, rlen, BUFSIZE - rlen);
+ }
+ }
+
+ if (splitbyte < rlen) {
+ inputStream.unread(buf, splitbyte, rlen - splitbyte);
+ }
+
+ parms = new HashMap<String, String>();
+ if (null == headers) {
+ headers = new HashMap<String, String>();
+ } else {
+ headers.clear();
+ }
+
+ if (null != remoteIp) {
+ headers.put("remote-addr", remoteIp);
+ headers.put("http-client-ip", remoteIp);
+ }
+
+ // Create a BufferedReader for parsing the header.
+ BufferedReader hin = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(buf,
+ 0, rlen)));
+
+ // Decode the header into parms and header java properties
+ Map<String, String> pre = new HashMap<String, String>();
+ decodeHeader(hin, pre, parms, headers);
+
+ method = Method.lookup(pre.get("method"));
+ if (method == null) {
+ throw new ResponseException(Response.Status.BAD_REQUEST, "BAD REQUEST: Syntax error.");
+ }
+
+ uri = pre.get("uri");
+
+ // Ok, now do the serve()
+ Response r = serve(this);
+ if (r == null) {
+ throw new ResponseException(Response.Status.INTERNAL_ERROR,
+ "SERVER INTERNAL ERROR: Serve() returned a null response.");
+ } else {
+ r.setRequestMethod(method);
+ r.send(outputStream);
+ }
+ } catch (SocketException e) {
+ // throw it out to close socket object (finalAccept)
+ throw e;
+ } catch (SocketTimeoutException ste) {
+ throw ste;
+ } catch (IOException ioe) {
+ Response r = new Response(Response.Status.INTERNAL_ERROR, MIME_PLAINTEXT,
+ "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage());
+ r.send(outputStream);
+ safeClose(outputStream);
+ } catch (ResponseException re) {
+ Response r = new Response(re.getStatus(), MIME_PLAINTEXT, re.getMessage());
+ r.send(outputStream);
+ safeClose(outputStream);
+ }
+ }
+
+ @Override
+ public void parseBody(Map<String, String> files) throws IOException, ResponseException {
+ final Reader in = new InputStreamReader(inputStream);
+ long size;
+ if (headers.containsKey("content-length")) {
+ size = Integer.parseInt(headers.get("content-length"));
+ } else if (splitbyte < rlen) {
+ size = rlen - splitbyte;
+ } else {
+ size = 0;
+ }
+
+ // If the method is POST, there may be parameters
+ // in data section, too, read it:
+ if (Method.POST.equals(method)) {
+ String contentType = "";
+ String contentTypeHeader = headers.get("content-type");
+
+ StringTokenizer st = null;
+ if (contentTypeHeader != null) {
+ st = new StringTokenizer(contentTypeHeader, ",; ");
+ if (st.hasMoreTokens()) {
+ contentType = st.nextToken();
+ }
+ }
+
+ if ("multipart/form-data".equalsIgnoreCase(contentType)) {
+ throw new ResponseException(Response.Status.BAD_REQUEST,
+ "BAD REQUEST: Content type is multipart/form-data, which is not supported");
+ } else {
+ String postLine = "";
+ StringBuilder postLineBuffer = new StringBuilder();
+ char pbuf[] = new char[512];
+ while (rlen >= 0 && size > 0 && !postLine.endsWith("\r\n")) {
+ rlen = in.read(pbuf, 0, (int) Math.min(size, 512));
+ if (rlen <= 0)
+ break;
+ postLine = String.valueOf(pbuf, 0, rlen);
+ postLineBuffer.append(postLine);
+ }
+ postLine = postLineBuffer.toString().trim();
+ // Handle application/x-www-form-urlencoded
+ if ("application/x-www-form-urlencoded".equalsIgnoreCase(contentType)) {
+ decodeParms(postLine, parms);
+ } else if (postLine.length() != 0) {
+ // Special case for raw POST data => create a special files entry "postData" with raw content data
+ files.put("postData", postLine);
+ }
+ }
+ }
+ }
+
+ /**
+ * Decodes the sent headers and loads the data into Key/value pairs
+ */
+ private void decodeHeader(BufferedReader in, Map<String, String> pre, Map<String, String> parms,
+ Map<String, String> headers) throws ResponseException {
+ try {
+ // Read the request line
+ String inLine = in.readLine();
+ if (inLine == null) {
+ return;
+ }
+
+ StringTokenizer st = new StringTokenizer(inLine);
+ if (!st.hasMoreTokens()) {
+ throw new ResponseException(Response.Status.BAD_REQUEST,
+ "BAD REQUEST: Syntax error. Usage: GET /example/file.html");
+ }
+
+ pre.put("method", st.nextToken());
+
+ if (!st.hasMoreTokens()) {
+ throw new ResponseException(Response.Status.BAD_REQUEST,
+ "BAD REQUEST: Missing URI. Usage: GET /example/file.html");
+ }
+
+ String uri = st.nextToken();
+
+ // Decode parameters from the URI
+ int qmi = uri.indexOf('?');
+ if (qmi >= 0) {
+ decodeParms(uri.substring(qmi + 1), parms);
+ uri = decodePercent(uri.substring(0, qmi));
+ } else {
+ uri = decodePercent(uri);
+ }
+
+ // If there's another token, its protocol version,
+ // followed by HTTP headers. Ignore version but parse headers.
+ // NOTE: this now forces header names lower case since they are
+ // case insensitive and vary by client.
+ if (st.hasMoreTokens()) {
+ String line = in.readLine();
+ while (line != null && line.trim().length() > 0) {
+ int p = line.indexOf(':');
+ if (p >= 0)
+ headers.put(line.substring(0, p).trim().toLowerCase(Locale.US),
+ line.substring(p + 1).trim());
+ line = in.readLine();
+ }
+ }
+
+ pre.put("uri", uri);
+ } catch (IOException ioe) {
+ throw new ResponseException(Response.Status.INTERNAL_ERROR,
+ "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage(), ioe);
+ }
+ }
+
+ /**
+ * Find byte index separating header from body. It must be the last byte
+ * of the first two
+ * sequential new lines.
+ */
+ private int findHeaderEnd(final byte[] buf, int rlen) {
+ int splitbyte = 0;
+ while (splitbyte + 3 < rlen) {
+ if (buf[splitbyte] == '\r' && buf[splitbyte + 1] == '\n' && buf[splitbyte + 2] == '\r'
+ && buf[splitbyte + 3] == '\n') {
+ return splitbyte + 4;
+ }
+ splitbyte++;
+ }
+ return 0;
+ }
+
+ /**
+ * Decodes parameters in percent-encoded URI-format ( e.g.
+ * "name=Jack%20Daniels&pass=Single%20Malt" ) and
+ * adds them to given Map. NOTE: this doesn't support multiple identical
+ * keys due to the
+ * simplicity of Map.
+ */
+ private void decodeParms(String parms, Map<String, String> p) {
+ if (parms == null) {
+ queryParameterString = "";
+ return;
+ }
+
+ queryParameterString = parms;
+ StringTokenizer st = new StringTokenizer(parms, "&");
+ while (st.hasMoreTokens()) {
+ String e = st.nextToken();
+ int sep = e.indexOf('=');
+ if (sep >= 0) {
+ p.put(decodePercent(e.substring(0, sep)).trim(), decodePercent(e.substring(sep + 1)));
+ } else {
+ p.put(decodePercent(e).trim(), "");
+ }
+ }
+ }
+
+ @Override
+ public final Map<String, String> getParms() {
+ return parms;
+ }
+
+ public String getQueryParameterString() {
+ return queryParameterString;
+ }
+
+ @Override
+ public final Map<String, String> getHeaders() {
+ return headers;
+ }
+
+ @Override
+ public final String getUri() {
+ return uri;
+ }
+
+ @Override
+ public final Method getMethod() {
+ return method;
+ }
+
+ @Override
+ public final InputStream getInputStream() {
+ return inputStream;
+ }
+ }
+
+}
diff --git a/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/App.java b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/App.java
index bdbc8b7f..6a7f0e74 100644
--- a/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/App.java
+++ b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/App.java
@@ -3,7 +3,6 @@ package org.openslx.bwlp.sat;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.sql.SQLException;
-import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@@ -24,6 +23,7 @@ import org.openslx.bwlp.sat.thrift.cache.OrganizationList;
import org.openslx.bwlp.sat.util.Configuration;
import org.openslx.bwlp.sat.util.Identity;
import org.openslx.bwlp.sat.util.Json;
+import org.openslx.bwlp.sat.web.WebServer;
import org.openslx.bwlp.thrift.iface.ImageSummaryRead;
import org.openslx.bwlp.thrift.iface.NetDirection;
import org.openslx.bwlp.thrift.iface.NetRule;
@@ -37,8 +37,6 @@ public class App {
private static Logger LOGGER = Logger.getLogger(App.class);
- private static List<Thread> servers = new ArrayList<>();
-
public static boolean DEBUG = false;
public static void main(String[] args) throws TTransportException, NoSuchAlgorithmException, IOException {
@@ -105,11 +103,14 @@ public class App {
Thread t;
// Plain
t = new Thread(new BinaryListener(9090, false));
- servers.add(t);
+ t.setDaemon(true);
t.start();
// SSL
t = new Thread(new BinaryListener(9091, true));
- servers.add(t);
+ t.start();
+ // Start httpd
+ t = new Thread(new WebServer(9080));
+ t.setDaemon(true);
t.start();
// DEBUG
if (DEBUG) {
@@ -135,19 +136,12 @@ public class App {
LOGGER.info(nn);
}
- // Wait for servers
- for (Thread wait : servers) {
- boolean success = false;
- while (!success) {
- try {
- wait.join();
- success = true;
- } catch (InterruptedException e) {
- // Do nothing...
- }
+ Runtime.getRuntime().addShutdownHook(new Thread() {
+ @Override
+ public void run() {
+ QuickTimer.cancel();
+ LOGGER.info(new Date() + " - all Servers shut down, exiting...\n");
}
- }
- QuickTimer.cancel();
- LOGGER.info(new Date() + " - all Servers shut down, exiting...\n");
+ });
}
}
diff --git a/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/web/VmChooserEntryXml.java b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/web/VmChooserEntryXml.java
new file mode 100644
index 00000000..b1571e74
--- /dev/null
+++ b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/web/VmChooserEntryXml.java
@@ -0,0 +1,24 @@
+package org.openslx.bwlp.sat.web;
+
+import org.simpleframework.xml.Attribute;
+
+public class VmChooserEntryXml {
+
+ @Attribute
+ private VmChooserParamXml priority;
+ @Attribute
+ private VmChooserParamXml image_name;
+ @Attribute
+ private VmChooserParamXml creator;
+ @Attribute
+ private VmChooserParamXml short_description;
+ @Attribute
+ private VmChooserParamXml long_description;
+ @Attribute
+ private VmChooserParamXml uuid;
+ @Attribute
+ private VmChooserParamXml virtualmachine;
+ @Attribute
+ private VmChooserParamXml icon;
+
+}
diff --git a/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/web/VmChooserListXml.java b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/web/VmChooserListXml.java
new file mode 100644
index 00000000..f9cfa94d
--- /dev/null
+++ b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/web/VmChooserListXml.java
@@ -0,0 +1,14 @@
+package org.openslx.bwlp.sat.web;
+
+import java.util.List;
+
+import org.simpleframework.xml.ElementList;
+import org.simpleframework.xml.Root;
+
+@Root(name = "settings")
+public class VmChooserListXml {
+
+ @ElementList(inline = true, name = "eintrag")
+ private List<VmChooserEntryXml> entries;
+
+}
diff --git a/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/web/VmChooserParamXml.java b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/web/VmChooserParamXml.java
new file mode 100644
index 00000000..a1f0425a
--- /dev/null
+++ b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/web/VmChooserParamXml.java
@@ -0,0 +1,14 @@
+package org.openslx.bwlp.sat.web;
+
+import org.simpleframework.xml.Attribute;
+
+public class VmChooserParamXml {
+
+ @Attribute
+ private String param;
+
+ public VmChooserParamXml(String value) {
+ this.param = value;
+ }
+
+}
diff --git a/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/web/WebServer.java b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/web/WebServer.java
new file mode 100644
index 00000000..02172616
--- /dev/null
+++ b/dozentenmodulserver/src/main/java/org/openslx/bwlp/sat/web/WebServer.java
@@ -0,0 +1,36 @@
+package org.openslx.bwlp.sat.web;
+
+import fi.iki.elonen.NanoHTTPD;
+
+public class WebServer extends NanoHTTPD {
+
+ public WebServer(int port) {
+ super(port);
+ }
+
+ @Override
+ public Response serve(IHTTPSession session) {
+ String uri = session.getUri();
+
+ if (uri == null || uri.length() == 0) {
+ return internalServerError();
+ }
+
+ // Our special stuff
+ if (uri.startsWith("/vmchooser/list")) {
+ return serveVmChooserList();
+ }
+
+ return new NanoHTTPD.Response(NanoHTTPD.Response.Status.NOT_FOUND, "text/plain", "Nicht gefunden!");
+ }
+
+ private Response serveVmChooserList() {
+ return new NanoHTTPD.Response(NanoHTTPD.Response.Status.NOT_FOUND, "text/plain", "BLA");
+ }
+
+ private Response internalServerError() {
+ return new NanoHTTPD.Response(NanoHTTPD.Response.Status.INTERNAL_ERROR, "text/plain",
+ "Internal Server Error");
+ }
+
+}