aboutsummaryrefslogtreecommitdiff
path: root/webserver/src
diff options
context:
space:
mode:
Diffstat (limited to 'webserver/src')
-rw-r--r--webserver/src/main/java/fi/iki/elonen/InternalRewrite.java51
-rw-r--r--webserver/src/main/java/fi/iki/elonen/ServerRunner.java32
-rw-r--r--webserver/src/main/java/fi/iki/elonen/SimpleWebServer.java610
-rw-r--r--webserver/src/main/java/fi/iki/elonen/WebServerPlugin.java42
-rw-r--r--webserver/src/main/java/fi/iki/elonen/WebServerPluginInfo.java41
-rw-r--r--webserver/src/site/site.xml41
-rw-r--r--webserver/src/test/java/fi/iki/elonen/AbstractTestHttpServer.java67
-rw-r--r--webserver/src/test/java/fi/iki/elonen/DummyPlugin.java63
-rw-r--r--webserver/src/test/java/fi/iki/elonen/DummyPluginInfo.java58
-rw-r--r--webserver/src/test/java/fi/iki/elonen/TestCorsHttpServer.java154
-rw-r--r--webserver/src/test/java/fi/iki/elonen/TestCorsHttpServerWithSingleOrigin.java121
-rw-r--r--webserver/src/test/java/fi/iki/elonen/TestHttpServer.java149
-rw-r--r--webserver/src/test/resources/META-INF/services/fi.iki.elonen.WebServerPluginInfo1
-rw-r--r--webserver/src/test/resources/testdir/test.html8
-rw-r--r--webserver/src/test/resources/testdir/testdir/different.xml3
-rw-r--r--webserver/src/test/resources/testdir/testpdf.pdfbin0 -> 6065 bytes
16 files changed, 1118 insertions, 323 deletions
diff --git a/webserver/src/main/java/fi/iki/elonen/InternalRewrite.java b/webserver/src/main/java/fi/iki/elonen/InternalRewrite.java
index b84d88e..960f2e7 100644
--- a/webserver/src/main/java/fi/iki/elonen/InternalRewrite.java
+++ b/webserver/src/main/java/fi/iki/elonen/InternalRewrite.java
@@ -1,28 +1,63 @@
package fi.iki.elonen;
+/*
+ * #%L
+ * NanoHttpd-Webserver
+ * %%
+ * 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.ByteArrayInputStream;
import java.util.Map;
-import static fi.iki.elonen.NanoHTTPD.Response;
+import fi.iki.elonen.NanoHTTPD.Response;
/**
- * @author Paul S. Hawke (paul.hawke@gmail.com)
- * On: 9/15/13 at 2:52 PM
+ * @author Paul S. Hawke (paul.hawke@gmail.com) On: 9/15/13 at 2:52 PM
*/
public class InternalRewrite extends Response {
+
private final String uri;
+
private final Map<String, String> headers;
public InternalRewrite(Map<String, String> headers, String uri) {
- super(null);
+ super(Status.OK, NanoHTTPD.MIME_HTML, new ByteArrayInputStream(new byte[0]), 0);
this.headers = headers;
this.uri = uri;
}
- public String getUri() {
- return uri;
+ public Map<String, String> getHeaders() {
+ return this.headers;
}
- public Map<String, String> getHeaders() {
- return headers;
+ public String getUri() {
+ return this.uri;
}
}
diff --git a/webserver/src/main/java/fi/iki/elonen/ServerRunner.java b/webserver/src/main/java/fi/iki/elonen/ServerRunner.java
deleted file mode 100644
index 313097a..0000000
--- a/webserver/src/main/java/fi/iki/elonen/ServerRunner.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package fi.iki.elonen;
-
-import java.io.IOException;
-
-public class ServerRunner {
- public static void run(Class serverClass) {
- try {
- executeInstance((NanoHTTPD) serverClass.newInstance());
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
-
- public static void executeInstance(NanoHTTPD server) {
- try {
- server.start();
- } catch (IOException ioe) {
- System.err.println("Couldn't start server:\n" + ioe);
- System.exit(-1);
- }
-
- System.out.println("Server started, Hit Enter to stop.\n");
-
- try {
- System.in.read();
- } catch (Throwable ignored) {
- }
-
- server.stop();
- System.out.println("Server stopped.\n");
- }
-}
diff --git a/webserver/src/main/java/fi/iki/elonen/SimpleWebServer.java b/webserver/src/main/java/fi/iki/elonen/SimpleWebServer.java
index ed32dd7..3f176fe 100644
--- a/webserver/src/main/java/fi/iki/elonen/SimpleWebServer.java
+++ b/webserver/src/main/java/fi/iki/elonen/SimpleWebServer.java
@@ -1,7 +1,41 @@
package fi.iki.elonen;
+/*
+ * #%L
+ * NanoHttpd-Webserver
+ * %%
+ * 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.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
+import java.io.FileNotFoundException;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
@@ -17,103 +51,46 @@ import java.util.Map;
import java.util.ServiceLoader;
import java.util.StringTokenizer;
+import fi.iki.elonen.NanoHTTPD.Response.IStatus;
+import fi.iki.elonen.util.ServerRunner;
+
public class SimpleWebServer extends NanoHTTPD {
- /**
- * Common mime type for dynamic content: binary
- */
- public static final String MIME_DEFAULT_BINARY = "application/octet-stream";
+
/**
* Default Index file names.
*/
- public static final List<String> INDEX_FILE_NAMES = new ArrayList<String>() {{
- add("index.html");
- add("index.htm");
- }};
- /**
- * Hashtable mapping (String)FILENAME_EXTENSION -> (String)MIME_TYPE
- */
- private static final Map<String, String> MIME_TYPES = new HashMap<String, String>() {{
- put("css", "text/css");
- put("htm", "text/html");
- put("html", "text/html");
- put("xml", "text/xml");
- put("java", "text/x-java-source, text/java");
- put("md", "text/plain");
- put("txt", "text/plain");
- put("asc", "text/plain");
- put("gif", "image/gif");
- put("jpg", "image/jpeg");
- put("jpeg", "image/jpeg");
- put("png", "image/png");
- put("mp3", "audio/mpeg");
- put("m3u", "audio/mpeg-url");
- put("mp4", "video/mp4");
- put("ogv", "video/ogg");
- put("flv", "video/x-flv");
- put("mov", "video/quicktime");
- put("swf", "application/x-shockwave-flash");
- put("js", "application/javascript");
- put("pdf", "application/pdf");
- put("doc", "application/msword");
- put("ogg", "application/x-ogg");
- put("zip", "application/octet-stream");
- put("exe", "application/octet-stream");
- put("class", "application/octet-stream");
- }};
+ @SuppressWarnings("serial")
+ public static final List<String> INDEX_FILE_NAMES = new ArrayList<String>() {
+
+ {
+ add("index.html");
+ add("index.htm");
+ }
+ };
+
/**
* The distribution licence
*/
- private static final String LICENCE =
- "Copyright (c) 2012-2013 by Paul S. Hawke, 2001,2005-2013 by Jarno Elonen, 2010 by Konstantinos Togias\n"
- + "\n"
- + "Redistribution and use in source and binary forms, with or without\n"
- + "modification, are permitted provided that the following conditions\n"
- + "are met:\n"
- + "\n"
- + "Redistributions of source code must retain the above copyright notice,\n"
- + "this list of conditions and the following disclaimer. Redistributions in\n"
- + "binary form must reproduce the above copyright notice, this list of\n"
- + "conditions and the following disclaimer in the documentation and/or other\n"
- + "materials provided with the distribution. The name of the author may not\n"
- + "be used to endorse or promote products derived from this software without\n"
- + "specific prior written permission. \n"
- + " \n"
- + "THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR\n"
- + "IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES\n"
- + "OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.\n"
- + "IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,\n"
- + "INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT\n"
- + "NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n"
- + "DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n"
- + "THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n"
- + "(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n"
- + "OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.";
- private static Map<String, WebServerPlugin> mimeTypeHandlers = new HashMap<String, WebServerPlugin>();
- private final List<File> rootDirs;
- private final boolean quiet;
-
- public SimpleWebServer(String host, int port, File wwwroot, boolean quiet) {
- super(host, port);
- this.quiet = quiet;
- this.rootDirs = new ArrayList<File>();
- this.rootDirs.add(wwwroot);
-
- this.init();
- }
-
- public SimpleWebServer(String host, int port, List<File> wwwroots, boolean quiet) {
- super(host, port);
- this.quiet = quiet;
- this.rootDirs = new ArrayList<File>(wwwroots);
-
- this.init();
+ private static final String LICENCE;
+ static {
+ mimeTypes();
+ InputStream stream = SimpleWebServer.class.getResourceAsStream("/LICENSE.txt");
+ ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+ byte[] buffer = new byte[1024];
+ int count;
+ String text;
+ try {
+ while ((count = stream.read(buffer)) >= 0) {
+ bytes.write(buffer, 0, count);
+ }
+ text = bytes.toString("UTF-8");
+ } catch (IOException e) {
+ text = "unknown";
+ }
+ LICENCE = text;
}
- /**
- * Used to initialize and customize the server.
- */
- public void init() {
- }
+ private static Map<String, WebServerPlugin> mimeTypeHandlers = new HashMap<String, WebServerPlugin>();
/**
* Starts as a standalone file server and waits for Enter.
@@ -122,9 +99,10 @@ public class SimpleWebServer extends NanoHTTPD {
// Defaults
int port = 8080;
- String host = "127.0.0.1";
+ String host = null; // bind to all interfaces by default
List<File> rootDirs = new ArrayList<File>();
boolean quiet = false;
+ String cors = null;
Map<String, String> options = new HashMap<String, String>();
// Parse command-line, with short and long versions of the options.
@@ -137,8 +115,14 @@ public class SimpleWebServer extends NanoHTTPD {
quiet = true;
} else if (args[i].equalsIgnoreCase("-d") || args[i].equalsIgnoreCase("--dir")) {
rootDirs.add(new File(args[i + 1]).getAbsoluteFile());
+ } else if (args[i].startsWith("--cors")) {
+ cors = "*";
+ int equalIdx = args[i].indexOf('=');
+ if (equalIdx > 0) {
+ cors = args[i].substring(equalIdx + 1);
+ }
} else if (args[i].equalsIgnoreCase("--licence")) {
- System.out.println(LICENCE + "\n");
+ System.out.println(SimpleWebServer.LICENCE + "\n");
} else if (args[i].startsWith("-X:")) {
int dot = args[i].indexOf('=');
if (dot > 0) {
@@ -152,9 +136,8 @@ public class SimpleWebServer extends NanoHTTPD {
if (rootDirs.isEmpty()) {
rootDirs.add(new File(".").getAbsoluteFile());
}
-
options.put("host", host);
- options.put("port", ""+port);
+ options.put("port", "" + port);
options.put("quiet", String.valueOf(quiet));
StringBuilder sb = new StringBuilder();
for (File dir : rootDirs) {
@@ -163,10 +146,10 @@ public class SimpleWebServer extends NanoHTTPD {
}
try {
sb.append(dir.getCanonicalPath());
- } catch (IOException ignored) {}
+ } catch (IOException ignored) {
+ }
}
options.put("home", sb.toString());
-
ServiceLoader<WebServerPluginInfo> serviceLoader = ServiceLoader.load(WebServerPluginInfo.class);
for (WebServerPluginInfo info : serviceLoader) {
String[] mimeTypes = info.getMimeTypes();
@@ -185,8 +168,7 @@ public class SimpleWebServer extends NanoHTTPD {
registerPluginForMimeType(indexFiles, mime, info.getWebServerPlugin(mime), options);
}
}
-
- ServerRunner.executeInstance(new SimpleWebServer(host, port, rootDirs, quiet));
+ ServerRunner.executeInstance(new SimpleWebServer(host, port, rootDirs, quiet, cors));
}
protected static void registerPluginForMimeType(String[] indexFiles, String mimeType, WebServerPlugin plugin, Map<String, String> commandLineOptions) {
@@ -199,40 +181,69 @@ public class SimpleWebServer extends NanoHTTPD {
int dot = filename.lastIndexOf('.');
if (dot >= 0) {
String extension = filename.substring(dot + 1).toLowerCase();
- MIME_TYPES.put(extension, mimeType);
+ mimeTypes().put(extension, mimeType);
}
}
- INDEX_FILE_NAMES.addAll(Arrays.asList(indexFiles));
+ SimpleWebServer.INDEX_FILE_NAMES.addAll(Arrays.asList(indexFiles));
}
- mimeTypeHandlers.put(mimeType, plugin);
+ SimpleWebServer.mimeTypeHandlers.put(mimeType, plugin);
plugin.initialize(commandLineOptions);
}
- private File getRootDir() {
- return rootDirs.get(0);
+ private final boolean quiet;
+
+ private final String cors;
+
+ protected List<File> rootDirs;
+
+ public SimpleWebServer(String host, int port, File wwwroot, boolean quiet, String cors) {
+ this(host, port, Collections.singletonList(wwwroot), quiet, cors);
+ }
+
+ public SimpleWebServer(String host, int port, File wwwroot, boolean quiet) {
+ this(host, port, Collections.singletonList(wwwroot), quiet, null);
}
- private List<File> getRootDirs() {
- return rootDirs;
+ public SimpleWebServer(String host, int port, List<File> wwwroots, boolean quiet) {
+ this(host, port, wwwroots, quiet, null);
}
- private void addWwwRootDir(File wwwroot) {
- rootDirs.add(wwwroot);
+ public SimpleWebServer(String host, int port, List<File> wwwroots, boolean quiet, String cors) {
+ super(host, port);
+ this.quiet = quiet;
+ this.cors = cors;
+ this.rootDirs = new ArrayList<File>(wwwroots);
+
+ init();
+ }
+
+ private boolean canServeUri(String uri, File homeDir) {
+ boolean canServeUri;
+ File f = new File(homeDir, uri);
+ canServeUri = f.exists();
+ if (!canServeUri) {
+ WebServerPlugin plugin = SimpleWebServer.mimeTypeHandlers.get(getMimeTypeForFile(uri));
+ if (plugin != null) {
+ canServeUri = plugin.canServeUri(uri, homeDir);
+ }
+ }
+ return canServeUri;
}
/**
- * URL-encodes everything between "/"-characters. Encodes spaces as '%20' instead of '+'.
+ * URL-encodes everything between "/"-characters. Encodes spaces as '%20'
+ * instead of '+'.
*/
private String encodeUri(String uri) {
String newUri = "";
StringTokenizer st = new StringTokenizer(uri, "/ ", true);
while (st.hasMoreTokens()) {
String tok = st.nextToken();
- if (tok.equals("/"))
+ if (tok.equals("/")) {
newUri += "/";
- else if (tok.equals(" "))
+ } else if (tok.equals(" ")) {
newUri += "%20";
- else {
+ } else {
try {
newUri += URLEncoder.encode(tok, "UTF-8");
} catch (UnsupportedEncodingException ignored) {
@@ -242,36 +253,125 @@ public class SimpleWebServer extends NanoHTTPD {
return newUri;
}
- public Response serve(IHTTPSession session) {
- Map<String, String> header = session.getHeaders();
- Map<String, String> parms = session.getParms();
- String uri = session.getUri();
+ private String findIndexFileInDirectory(File directory) {
+ for (String fileName : SimpleWebServer.INDEX_FILE_NAMES) {
+ File indexFile = new File(directory, fileName);
+ if (indexFile.isFile()) {
+ return fileName;
+ }
+ }
+ return null;
+ }
- if (!quiet) {
- System.out.println(session.getMethod() + " '" + uri + "' ");
+ protected Response getForbiddenResponse(String s) {
+ return newFixedLengthResponse(Response.Status.FORBIDDEN, NanoHTTPD.MIME_PLAINTEXT, "FORBIDDEN: " + s);
+ }
- Iterator<String> e = header.keySet().iterator();
- while (e.hasNext()) {
- String value = e.next();
- System.out.println(" HDR: '" + value + "' = '" + header.get(value) + "'");
- }
- e = parms.keySet().iterator();
- while (e.hasNext()) {
- String value = e.next();
- System.out.println(" PRM: '" + value + "' = '" + parms.get(value) + "'");
+ protected Response getInternalErrorResponse(String s) {
+ return newFixedLengthResponse(Response.Status.INTERNAL_ERROR, NanoHTTPD.MIME_PLAINTEXT, "INTERNAL ERROR: " + s);
+ }
+
+ protected Response getNotFoundResponse() {
+ return newFixedLengthResponse(Response.Status.NOT_FOUND, NanoHTTPD.MIME_PLAINTEXT, "Error 404, file not found.");
+ }
+
+ /**
+ * Used to initialize and customize the server.
+ */
+ public void init() {
+ }
+
+ protected String listDirectory(String uri, File f) {
+ String heading = "Directory " + uri;
+ StringBuilder msg =
+ new StringBuilder("<html><head><title>" + heading + "</title><style><!--\n" + "span.dirname { font-weight: bold; }\n" + "span.filesize { font-size: 75%; }\n"
+ + "// -->\n" + "</style>" + "</head><body><h1>" + heading + "</h1>");
+
+ String up = null;
+ if (uri.length() > 1) {
+ String u = uri.substring(0, uri.length() - 1);
+ int slash = u.lastIndexOf('/');
+ if (slash >= 0 && slash < u.length()) {
+ up = uri.substring(0, slash + 1);
}
}
- for (File homeDir : getRootDirs()) {
- // Make sure we won't die of an exception later
- if (!homeDir.isDirectory()) {
- return getInternalErrorResponse("given path is not a directory (" + homeDir + ").");
+ List<String> files = Arrays.asList(f.list(new FilenameFilter() {
+
+ @Override
+ public boolean accept(File dir, String name) {
+ return new File(dir, name).isFile();
+ }
+ }));
+ Collections.sort(files);
+ List<String> directories = Arrays.asList(f.list(new FilenameFilter() {
+
+ @Override
+ public boolean accept(File dir, String name) {
+ return new File(dir, name).isDirectory();
+ }
+ }));
+ Collections.sort(directories);
+ if (up != null || directories.size() + files.size() > 0) {
+ msg.append("<ul>");
+ if (up != null || directories.size() > 0) {
+ msg.append("<section class=\"directories\">");
+ if (up != null) {
+ msg.append("<li><a rel=\"directory\" href=\"").append(up).append("\"><span class=\"dirname\">..</span></a></b></li>");
+ }
+ for (String directory : directories) {
+ String dir = directory + "/";
+ msg.append("<li><a rel=\"directory\" href=\"").append(encodeUri(uri + dir)).append("\"><span class=\"dirname\">").append(dir)
+ .append("</span></a></b></li>");
+ }
+ msg.append("</section>");
+ }
+ if (files.size() > 0) {
+ msg.append("<section class=\"files\">");
+ for (String file : files) {
+ msg.append("<li><a href=\"").append(encodeUri(uri + file)).append("\"><span class=\"filename\">").append(file).append("</span></a>");
+ File curFile = new File(f, file);
+ long len = curFile.length();
+ msg.append("&nbsp;<span class=\"filesize\">(");
+ if (len < 1024) {
+ msg.append(len).append(" bytes");
+ } else if (len < 1024 * 1024) {
+ msg.append(len / 1024).append(".").append(len % 1024 / 10 % 100).append(" KB");
+ } else {
+ msg.append(len / (1024 * 1024)).append(".").append(len % (1024 * 1024) / 10000 % 100).append(" MB");
+ }
+ msg.append(")</span></li>");
+ }
+ msg.append("</section>");
}
+ msg.append("</ul>");
}
- return respond(Collections.unmodifiableMap(header), session, uri);
+ msg.append("</body></html>");
+ return msg.toString();
+ }
+
+ public static Response newFixedLengthResponse(IStatus status, String mimeType, String message) {
+ Response response = NanoHTTPD.newFixedLengthResponse(status, mimeType, message);
+ response.addHeader("Accept-Ranges", "bytes");
+ return response;
}
private Response respond(Map<String, String> headers, IHTTPSession session, String uri) {
+ // First let's handle CORS OPTION query
+ Response r;
+ if (cors != null && Method.OPTIONS.equals(session.getMethod())) {
+ r = new NanoHTTPD.Response(Response.Status.OK, MIME_PLAINTEXT, null, 0);
+ } else {
+ r = defaultRespond(headers, session, uri);
+ }
+
+ if (cors != null) {
+ r = addCORSHeaders(headers, r, cors);
+ }
+ return r;
+ }
+
+ private Response defaultRespond(Map<String, String> headers, IHTTPSession session, String uri) {
// Remove URL arguments
uri = uri.trim().replace(File.separatorChar, '/');
if (uri.indexOf('?') >= 0) {
@@ -279,38 +379,39 @@ public class SimpleWebServer extends NanoHTTPD {
}
// Prohibit getting out of current directory
- if (uri.startsWith("src/main") || uri.endsWith("src/main") || uri.contains("../")) {
+ if (uri.contains("../")) {
return getForbiddenResponse("Won't serve ../ for security reasons.");
}
boolean canServeUri = false;
File homeDir = null;
- List<File> roots = getRootDirs();
- for (int i = 0; !canServeUri && i < roots.size(); i++) {
- homeDir = roots.get(i);
+ for (int i = 0; !canServeUri && i < this.rootDirs.size(); i++) {
+ homeDir = this.rootDirs.get(i);
canServeUri = canServeUri(uri, homeDir);
}
if (!canServeUri) {
return getNotFoundResponse();
}
- // Browsers get confused without '/' after the directory, send a redirect.
+ // Browsers get confused without '/' after the directory, send a
+ // redirect.
File f = new File(homeDir, uri);
if (f.isDirectory() && !uri.endsWith("/")) {
uri += "/";
- Response res = createResponse(Response.Status.REDIRECT, NanoHTTPD.MIME_HTML, "<html><body>Redirected: <a href=\"" +
- uri + "\">" + uri + "</a></body></html>");
+ Response res =
+ newFixedLengthResponse(Response.Status.REDIRECT, NanoHTTPD.MIME_HTML, "<html><body>Redirected: <a href=\"" + uri + "\">" + uri + "</a></body></html>");
res.addHeader("Location", uri);
return res;
}
if (f.isDirectory()) {
- // First look for index files (index.html, index.htm, etc) and if none found, list the directory if readable.
+ // First look for index files (index.html, index.htm, etc) and if
+ // none found, list the directory if readable.
String indexFile = findIndexFileInDirectory(f);
if (indexFile == null) {
if (f.canRead()) {
// No index file, list the directory if it is readable
- return createResponse(Response.Status.OK, NanoHTTPD.MIME_HTML, listDirectory(uri, f));
+ return newFixedLengthResponse(Response.Status.OK, NanoHTTPD.MIME_HTML, listDirectory(uri, f));
} else {
return getForbiddenResponse("No directory listing.");
}
@@ -318,11 +419,10 @@ public class SimpleWebServer extends NanoHTTPD {
return respond(headers, session, uri + indexFile);
}
}
-
String mimeTypeForFile = getMimeTypeForFile(uri);
- WebServerPlugin plugin = mimeTypeHandlers.get(mimeTypeForFile);
+ WebServerPlugin plugin = SimpleWebServer.mimeTypeHandlers.get(mimeTypeForFile);
Response response = null;
- if (plugin != null) {
+ if (plugin != null && plugin.canServeUri(uri, homeDir)) {
response = plugin.serveFile(uri, headers, session, f, mimeTypeForFile);
if (response != null && response instanceof InternalRewrite) {
InternalRewrite rewrite = (InternalRewrite) response;
@@ -334,37 +434,39 @@ public class SimpleWebServer extends NanoHTTPD {
return response != null ? response : getNotFoundResponse();
}
- protected Response getNotFoundResponse() {
- return createResponse(Response.Status.NOT_FOUND, NanoHTTPD.MIME_PLAINTEXT,
- "Error 404, file not found.");
- }
+ @Override
+ public Response serve(IHTTPSession session) {
+ Map<String, String> header = session.getHeaders();
+ Map<String, String> parms = session.getParms();
+ String uri = session.getUri();
- protected Response getForbiddenResponse(String s) {
- return createResponse(Response.Status.FORBIDDEN, NanoHTTPD.MIME_PLAINTEXT, "FORBIDDEN: "
- + s);
- }
+ if (!this.quiet) {
+ System.out.println(session.getMethod() + " '" + uri + "' ");
- protected Response getInternalErrorResponse(String s) {
- return createResponse(Response.Status.INTERNAL_ERROR, NanoHTTPD.MIME_PLAINTEXT,
- "INTERNAL ERRROR: " + s);
- }
+ Iterator<String> e = header.keySet().iterator();
+ while (e.hasNext()) {
+ String value = e.next();
+ System.out.println(" HDR: '" + value + "' = '" + header.get(value) + "'");
+ }
+ e = parms.keySet().iterator();
+ while (e.hasNext()) {
+ String value = e.next();
+ System.out.println(" PRM: '" + value + "' = '" + parms.get(value) + "'");
+ }
+ }
- private boolean canServeUri(String uri, File homeDir) {
- boolean canServeUri;
- File f = new File(homeDir, uri);
- canServeUri = f.exists();
- if (!canServeUri) {
- String mimeTypeForFile = getMimeTypeForFile(uri);
- WebServerPlugin plugin = mimeTypeHandlers.get(mimeTypeForFile);
- if (plugin != null) {
- canServeUri = plugin.canServeUri(uri, homeDir);
+ for (File homeDir : this.rootDirs) {
+ // Make sure we won't die of an exception later
+ if (!homeDir.isDirectory()) {
+ return getInternalErrorResponse("given path is not a directory (" + homeDir + ").");
}
}
- return canServeUri;
+ return respond(Collections.unmodifiableMap(header), session, uri);
}
/**
- * Serves file from homeDir and its' subdirectories (only). Uses only URI, ignores all headers and HTTP parameters.
+ * Serves file from homeDir and its' subdirectories (only). Uses only URI,
+ * ignores all headers and HTTP parameters.
*/
Response serveFile(String uri, Map<String, String> header, File file, String mime) {
Response res;
@@ -390,12 +492,27 @@ public class SimpleWebServer extends NanoHTTPD {
}
}
- // Change return code and add Content-Range header when skipping is requested
+ // get if-range header. If present, it must match etag or else we
+ // should ignore the range request
+ String ifRange = header.get("if-range");
+ boolean headerIfRangeMissingOrMatching = (ifRange == null || etag.equals(ifRange));
+
+ String ifNoneMatch = header.get("if-none-match");
+ boolean headerIfNoneMatchPresentAndMatching = ifNoneMatch != null && (ifNoneMatch.equals("*") || ifNoneMatch.equals(etag));
+
+ // Change return code and add Content-Range header when skipping is
+ // requested
long fileLen = file.length();
- if (range != null && startFrom >= 0) {
- if (startFrom >= fileLen) {
- res = createResponse(Response.Status.RANGE_NOT_SATISFIABLE, NanoHTTPD.MIME_PLAINTEXT, "");
- res.addHeader("Content-Range", "bytes 0-0/" + fileLen);
+
+ if (headerIfRangeMissingOrMatching && range != null && startFrom >= 0 && startFrom < fileLen) {
+ // range request that matches current etag
+ // and the startFrom of the range is satisfiable
+ if (headerIfNoneMatchPresentAndMatching) {
+ // range request that matches current etag
+ // and the startFrom of the range is satisfiable
+ // would return range from file
+ // respond with not-modified
+ res = newFixedLengthResponse(Response.Status.NOT_MODIFIED, mime, "");
res.addHeader("ETag", etag);
} else {
if (endAt < 0) {
@@ -406,25 +523,39 @@ public class SimpleWebServer extends NanoHTTPD {
newLen = 0;
}
- final long dataLen = newLen;
- FileInputStream fis = new FileInputStream(file) {
- @Override
- public int available() throws IOException {
- return (int) dataLen;
- }
- };
+ FileInputStream fis = new FileInputStream(file);
fis.skip(startFrom);
- res = createResponse(Response.Status.PARTIAL_CONTENT, mime, fis);
- res.addHeader("Content-Length", "" + dataLen);
+ res = newFixedLengthResponse(Response.Status.PARTIAL_CONTENT, mime, fis, newLen);
+ res.addHeader("Accept-Ranges", "bytes");
+ res.addHeader("Content-Length", "" + newLen);
res.addHeader("Content-Range", "bytes " + startFrom + "-" + endAt + "/" + fileLen);
res.addHeader("ETag", etag);
}
} else {
- if (etag.equals(header.get("if-none-match")))
- res = createResponse(Response.Status.NOT_MODIFIED, mime, "");
- else {
- res = createResponse(Response.Status.OK, mime, new FileInputStream(file));
+
+ if (headerIfRangeMissingOrMatching && range != null && startFrom >= fileLen) {
+ // return the size of the file
+ // 4xx responses are not trumped by if-none-match
+ res = newFixedLengthResponse(Response.Status.RANGE_NOT_SATISFIABLE, NanoHTTPD.MIME_PLAINTEXT, "");
+ res.addHeader("Content-Range", "bytes */" + fileLen);
+ res.addHeader("ETag", etag);
+ } else if (range == null && headerIfNoneMatchPresentAndMatching) {
+ // full-file-fetch request
+ // would return entire file
+ // respond with not-modified
+ res = newFixedLengthResponse(Response.Status.NOT_MODIFIED, mime, "");
+ res.addHeader("ETag", etag);
+ } else if (!headerIfRangeMissingOrMatching && headerIfNoneMatchPresentAndMatching) {
+ // range request that doesn't match current etag
+ // would return entire (different) file
+ // respond with not-modified
+
+ res = newFixedLengthResponse(Response.Status.NOT_MODIFIED, mime, "");
+ res.addHeader("ETag", etag);
+ } else {
+ // supply the file
+ res = newFixedFileResponse(file, mime);
res.addHeader("Content-Length", "" + fileLen);
res.addHeader("ETag", etag);
}
@@ -436,106 +567,37 @@ public class SimpleWebServer extends NanoHTTPD {
return res;
}
- // Get MIME type from file name extension, if possible
- private String getMimeTypeForFile(String uri) {
- int dot = uri.lastIndexOf('.');
- String mime = null;
- if (dot >= 0) {
- mime = MIME_TYPES.get(uri.substring(dot + 1).toLowerCase());
- }
- return mime == null ? MIME_DEFAULT_BINARY : mime;
- }
-
- // Announce that the file server accepts partial content requests
- private Response createResponse(Response.Status status, String mimeType, InputStream message) {
- Response res = new Response(status, mimeType, message);
+ private Response newFixedFileResponse(File file, String mime) throws FileNotFoundException {
+ Response res;
+ res = newFixedLengthResponse(Response.Status.OK, mime, new FileInputStream(file), (int) file.length());
res.addHeader("Accept-Ranges", "bytes");
return res;
}
- // Announce that the file server accepts partial content requests
- private Response createResponse(Response.Status status, String mimeType, String message) {
- Response res = new Response(status, mimeType, message);
- res.addHeader("Accept-Ranges", "bytes");
- return res;
+ protected Response addCORSHeaders(Map<String, String> queryHeaders, Response resp, String cors) {
+ resp.addHeader("Access-Control-Allow-Origin", cors);
+ resp.addHeader("Access-Control-Allow-Headers", calculateAllowHeaders(queryHeaders));
+ resp.addHeader("Access-Control-Allow-Credentials", "true");
+ resp.addHeader("Access-Control-Allow-Methods", ALLOWED_METHODS);
+ resp.addHeader("Access-Control-Max-Age", "" + MAX_AGE);
+
+ return resp;
}
- private String findIndexFileInDirectory(File directory) {
- for (String fileName : INDEX_FILE_NAMES) {
- File indexFile = new File(directory, fileName);
- if (indexFile.exists()) {
- return fileName;
- }
- }
- return null;
+ private String calculateAllowHeaders(Map<String, String> queryHeaders) {
+ // here we should use the given asked headers
+ // but NanoHttpd uses a Map whereas it is possible for requester to send
+ // several time the same header
+ // let's just use default values for this version
+ return System.getProperty(ACCESS_CONTROL_ALLOW_HEADER_PROPERTY_NAME, DEFAULT_ALLOWED_HEADERS);
}
- protected String listDirectory(String uri, File f) {
- String heading = "Directory " + uri;
- StringBuilder msg = new StringBuilder("<html><head><title>" + heading + "</title><style><!--\n" +
- "span.dirname { font-weight: bold; }\n" +
- "span.filesize { font-size: 75%; }\n" +
- "// -->\n" +
- "</style>" +
- "</head><body><h1>" + heading + "</h1>");
+ private final static String ALLOWED_METHODS = "GET, POST, PUT, DELETE, OPTIONS, HEAD";
- String up = null;
- if (uri.length() > 1) {
- String u = uri.substring(0, uri.length() - 1);
- int slash = u.lastIndexOf('/');
- if (slash >= 0 && slash < u.length()) {
- up = uri.substring(0, slash + 1);
- }
- }
+ private final static int MAX_AGE = 42 * 60 * 60;
- List<String> files = Arrays.asList(f.list(new FilenameFilter() {
- @Override
- public boolean accept(File dir, String name) {
- return new File(dir, name).isFile();
- }
- }));
- Collections.sort(files);
- List<String> directories = Arrays.asList(f.list(new FilenameFilter() {
- @Override
- public boolean accept(File dir, String name) {
- return new File(dir, name).isDirectory();
- }
- }));
- Collections.sort(directories);
- if (up != null || directories.size() + files.size() > 0) {
- msg.append("<ul>");
- if (up != null || directories.size() > 0) {
- msg.append("<section class=\"directories\">");
- if (up != null) {
- msg.append("<li><a rel=\"directory\" href=\"").append(up).append("\"><span class=\"dirname\">..</span></a></b></li>");
- }
- for (String directory : directories) {
- String dir = directory + "/";
- msg.append("<li><a rel=\"directory\" href=\"").append(encodeUri(uri + dir)).append("\"><span class=\"dirname\">").append(dir).append("</span></a></b></li>");
- }
- msg.append("</section>");
- }
- if (files.size() > 0) {
- msg.append("<section class=\"files\">");
- for (String file : files) {
- msg.append("<li><a href=\"").append(encodeUri(uri + file)).append("\"><span class=\"filename\">").append(file).append("</span></a>");
- File curFile = new File(f, file);
- long len = curFile.length();
- msg.append("&nbsp;<span class=\"filesize\">(");
- if (len < 1024) {
- msg.append(len).append(" bytes");
- } else if (len < 1024 * 1024) {
- msg.append(len / 1024).append(".").append(len % 1024 / 10 % 100).append(" KB");
- } else {
- msg.append(len / (1024 * 1024)).append(".").append(len % (1024 * 1024) / 10 % 100).append(" MB");
- }
- msg.append(")</span></li>");
- }
- msg.append("</section>");
- }
- msg.append("</ul>");
- }
- msg.append("</body></html>");
- return msg.toString();
- }
+ // explicitly relax visibility to package for tests purposes
+ final static String DEFAULT_ALLOWED_HEADERS = "origin,accept,content-type";
+
+ public final static String ACCESS_CONTROL_ALLOW_HEADER_PROPERTY_NAME = "AccessControlAllowHeader";
}
diff --git a/webserver/src/main/java/fi/iki/elonen/WebServerPlugin.java b/webserver/src/main/java/fi/iki/elonen/WebServerPlugin.java
index 08a9f22..8b490d3 100644
--- a/webserver/src/main/java/fi/iki/elonen/WebServerPlugin.java
+++ b/webserver/src/main/java/fi/iki/elonen/WebServerPlugin.java
@@ -1,19 +1,51 @@
package fi.iki.elonen;
+/*
+ * #%L
+ * NanoHttpd-Webserver
+ * %%
+ * 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.File;
import java.util.Map;
import fi.iki.elonen.NanoHTTPD.IHTTPSession;
/**
-* @author Paul S. Hawke (paul.hawke@gmail.com)
-* On: 9/14/13 at 8:09 AM
-*/
+ * @author Paul S. Hawke (paul.hawke@gmail.com) On: 9/14/13 at 8:09 AM
+ */
public interface WebServerPlugin {
- void initialize(Map<String, String> commandLineOptions);
-
boolean canServeUri(String uri, File rootDir);
+ void initialize(Map<String, String> commandLineOptions);
+
NanoHTTPD.Response serveFile(String uri, Map<String, String> headers, IHTTPSession session, File file, String mimeType);
}
diff --git a/webserver/src/main/java/fi/iki/elonen/WebServerPluginInfo.java b/webserver/src/main/java/fi/iki/elonen/WebServerPluginInfo.java
index 1e3deb7..0fe5f4e 100644
--- a/webserver/src/main/java/fi/iki/elonen/WebServerPluginInfo.java
+++ b/webserver/src/main/java/fi/iki/elonen/WebServerPluginInfo.java
@@ -1,13 +1,46 @@
package fi.iki.elonen;
+/*
+ * #%L
+ * NanoHttpd-Webserver
+ * %%
+ * 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%
+ */
+
/**
-* @author Paul S. Hawke (paul.hawke@gmail.com)
-* On: 9/14/13 at 8:09 AM
-*/
+ * @author Paul S. Hawke (paul.hawke@gmail.com) On: 9/14/13 at 8:09 AM
+ */
public interface WebServerPluginInfo {
- String[] getMimeTypes();
String[] getIndexFilesForMimeType(String mime);
+ String[] getMimeTypes();
+
WebServerPlugin getWebServerPlugin(String mimeType);
}
diff --git a/webserver/src/site/site.xml b/webserver/src/site/site.xml
new file mode 100644
index 0000000..4270945
--- /dev/null
+++ b/webserver/src/site/site.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<project name="${project.name}">
+ <skin>
+ <groupId>org.apache.maven.skins</groupId>
+ <artifactId>maven-fluido-skin</artifactId>
+ <version>1.3.0</version>
+ </skin>
+ <bannerLeft>
+ <src>../images/nanohttpd_logo.png</src>
+ </bannerLeft>
+ <bannerRight>
+ <src>../images/nanohttpd_logo_text.png</src>
+ </bannerRight>
+ <publishDate position="left" format="yyyy-MM-dd" />
+ <version position="right" />
+ <poweredBy>
+ <logo name="Maven" href="http://maven.apache.org/"
+ img="http://maven.apache.org/images/logos/maven-feather.png" />
+ </poweredBy>
+ <custom>
+ <fluidoSkin>
+ <topBarEnabled>false</topBarEnabled>
+ <sideBarEnabled>true</sideBarEnabled>
+ <gitHub>
+ <projectId>Nanohttpd/nanohttpd</projectId>
+ <ribbonOrientation>right</ribbonOrientation>
+ <ribbonColor>black</ribbonColor>
+ </gitHub>
+ </fluidoSkin>
+ </custom>
+ <body>
+ <breadcrumbs>
+ <item name="${project.name}" href="index.html" />
+ </breadcrumbs>
+ <menu name="Documentation">
+ <item name="About" href="index.html" />
+ </menu>
+ <menu ref="modules" />
+ <menu ref="reports" />
+ </body>
+</project> \ No newline at end of file
diff --git a/webserver/src/test/java/fi/iki/elonen/AbstractTestHttpServer.java b/webserver/src/test/java/fi/iki/elonen/AbstractTestHttpServer.java
new file mode 100644
index 0000000..b56c2b0
--- /dev/null
+++ b/webserver/src/test/java/fi/iki/elonen/AbstractTestHttpServer.java
@@ -0,0 +1,67 @@
+package fi.iki.elonen;
+
+/*
+ * #%L
+ * NanoHttpd-Webserver
+ * %%
+ * 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.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import org.apache.http.HttpEntity;
+
+/**
+ * @author Matthieu Brouillard [matthieu@brouillard.fr]
+ */
+public class AbstractTestHttpServer {
+
+ protected byte[] readContents(HttpEntity entity) throws IOException {
+ InputStream instream = entity.getContent();
+ return readContents(instream);
+ }
+
+ protected byte[] readContents(InputStream instream) throws IOException {
+ byte[] bytes;
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ try {
+ byte[] buffer = new byte[1024];
+ int count;
+ while ((count = instream.read(buffer)) >= 0) {
+ out.write(buffer, 0, count);
+ }
+ bytes = out.toByteArray();
+ } finally {
+ instream.close();
+ }
+ return bytes;
+ }
+
+}
diff --git a/webserver/src/test/java/fi/iki/elonen/DummyPlugin.java b/webserver/src/test/java/fi/iki/elonen/DummyPlugin.java
new file mode 100644
index 0000000..09d5cc4
--- /dev/null
+++ b/webserver/src/test/java/fi/iki/elonen/DummyPlugin.java
@@ -0,0 +1,63 @@
+package fi.iki.elonen;
+
+/*
+ * #%L
+ * NanoHttpd-Webserver
+ * %%
+ * 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.ByteArrayInputStream;
+import java.io.File;
+import java.io.InputStream;
+import java.util.Map;
+
+import fi.iki.elonen.NanoHTTPD.IHTTPSession;
+import fi.iki.elonen.NanoHTTPD.Response;
+import fi.iki.elonen.NanoHTTPD.Response.Status;
+
+public class DummyPlugin implements WebServerPlugin {
+
+ @Override
+ public boolean canServeUri(String uri, File rootDir) {
+ return true;
+ }
+
+ @Override
+ public void initialize(Map<String, String> commandLineOptions) {
+ }
+
+ @Override
+ public Response serveFile(String uri, Map<String, String> headers, IHTTPSession session, File file, String mimeType) {
+ byte[] bytes = "<xml/>".getBytes();
+ InputStream data = new ByteArrayInputStream(bytes);
+ return new Response(Status.OK, "text/xml", data, bytes.length);
+ }
+
+}
diff --git a/webserver/src/test/java/fi/iki/elonen/DummyPluginInfo.java b/webserver/src/test/java/fi/iki/elonen/DummyPluginInfo.java
new file mode 100644
index 0000000..7187573
--- /dev/null
+++ b/webserver/src/test/java/fi/iki/elonen/DummyPluginInfo.java
@@ -0,0 +1,58 @@
+package fi.iki.elonen;
+
+/*
+ * #%L
+ * NanoHttpd-Webserver
+ * %%
+ * 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%
+ */
+
+public class DummyPluginInfo implements WebServerPluginInfo {
+
+ @Override
+ public String[] getIndexFilesForMimeType(String mime) {
+
+ return new String[]{
+ "index.xml"
+ };
+ }
+
+ @Override
+ public String[] getMimeTypes() {
+ return new String[]{
+ "text/xml"
+ };
+ }
+
+ @Override
+ public WebServerPlugin getWebServerPlugin(String mimeType) {
+ return new DummyPlugin();
+ }
+
+}
diff --git a/webserver/src/test/java/fi/iki/elonen/TestCorsHttpServer.java b/webserver/src/test/java/fi/iki/elonen/TestCorsHttpServer.java
new file mode 100644
index 0000000..93f4699
--- /dev/null
+++ b/webserver/src/test/java/fi/iki/elonen/TestCorsHttpServer.java
@@ -0,0 +1,154 @@
+package fi.iki.elonen;
+
+/*
+ * #%L
+ * NanoHttpd-Webserver
+ * %%
+ * 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.ByteArrayOutputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpOptions;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * @author Matthieu Brouillard [matthieu@brouillard.fr]
+ */
+public class TestCorsHttpServer extends AbstractTestHttpServer {
+
+ private static PipedOutputStream stdIn;
+
+ private static Thread serverStartThread;
+
+ @BeforeClass
+ public static void setUp() throws Exception {
+ stdIn = new PipedOutputStream();
+ System.setIn(new PipedInputStream(stdIn));
+ serverStartThread = new Thread(new Runnable() {
+
+ @Override
+ public void run() {
+ String[] args = {
+ "--host",
+ "localhost",
+ "--port",
+ "9090",
+ "--dir",
+ "src/test/resources",
+ "--cors"
+ };
+ SimpleWebServer.main(args);
+ }
+ });
+ serverStartThread.start();
+ // give the server some tine to start.
+ Thread.sleep(100);
+ }
+
+ @AfterClass
+ public static void tearDown() throws Exception {
+ stdIn.write("\n\n".getBytes());
+ serverStartThread.join(2000);
+ Assert.assertFalse(serverStartThread.isAlive());
+ }
+
+ @Test
+ public void doTestOption() throws Exception {
+ CloseableHttpClient httpclient = HttpClients.createDefault();
+ HttpOptions httpOption = new HttpOptions("http://localhost:9090/xxx/yyy.html");
+ CloseableHttpResponse response = httpclient.execute(httpOption);
+ Assert.assertEquals(200, response.getStatusLine().getStatusCode());
+ Assert.assertNotNull("Cors should have added a header: Access-Control-Allow-Origin", response.getLastHeader("Access-Control-Allow-Origin"));
+ Assert.assertEquals("Cors should have added a header: Access-Control-Allow-Origin: *", "*", response.getLastHeader("Access-Control-Allow-Origin").getValue());
+ response.close();
+ }
+
+ @Test
+ public void doSomeBasicTest() throws Exception {
+ CloseableHttpClient httpclient = HttpClients.createDefault();
+ HttpGet httpget = new HttpGet("http://localhost:9090/testdir/test.html");
+ CloseableHttpResponse response = httpclient.execute(httpget);
+ HttpEntity entity = response.getEntity();
+ String string = new String(readContents(entity), "UTF-8");
+
+ Assert.assertNotNull("Cors should have added a header: Access-Control-Allow-Origin", response.getLastHeader("Access-Control-Allow-Origin"));
+ Assert.assertEquals("Cors should have added a header: Access-Control-Allow-Origin: *", "*", response.getLastHeader("Access-Control-Allow-Origin").getValue());
+ Assert.assertEquals("<html>\n<head>\n<title>dummy</title>\n</head>\n<body>\n\t<h1>it works</h1>\n</body>\n</html>", string);
+ response.close();
+ }
+
+ @Test
+ public void testAccessControlAllowHeaderUsesDefaultsWithoutSystemProperty() throws Exception {
+ Assert.assertNull("no System " + SimpleWebServer.ACCESS_CONTROL_ALLOW_HEADER_PROPERTY_NAME + " shoudl be set",
+ System.getProperty(SimpleWebServer.ACCESS_CONTROL_ALLOW_HEADER_PROPERTY_NAME));
+
+ CloseableHttpClient httpclient = HttpClients.createDefault();
+ HttpOptions httpOption = new HttpOptions("http://localhost:9090/xxx/yyy.html");
+ CloseableHttpResponse response = httpclient.execute(httpOption);
+ Assert.assertEquals(200, response.getStatusLine().getStatusCode());
+ Assert.assertEquals("Cors should have added a header: Access-Control-Allow-Headers: " + SimpleWebServer.DEFAULT_ALLOWED_HEADERS,
+ SimpleWebServer.DEFAULT_ALLOWED_HEADERS, response.getLastHeader("Access-Control-Allow-Headers").getValue());
+ response.close();
+ }
+
+ @Test
+ public void testAccessControlAllowHeaderUsesSystemPropertyWhenSet() throws Exception {
+ Assert.assertNull("no System " + SimpleWebServer.ACCESS_CONTROL_ALLOW_HEADER_PROPERTY_NAME + " shoudl be set",
+ System.getProperty(SimpleWebServer.ACCESS_CONTROL_ALLOW_HEADER_PROPERTY_NAME));
+
+ final String expectedValue = "origin";
+ System.setProperty(SimpleWebServer.ACCESS_CONTROL_ALLOW_HEADER_PROPERTY_NAME, expectedValue);
+
+ try {
+ CloseableHttpClient httpclient = HttpClients.createDefault();
+ HttpOptions httpOption = new HttpOptions("http://localhost:9090/xxx/yyy.html");
+ CloseableHttpResponse response = httpclient.execute(httpOption);
+ Assert.assertEquals(200, response.getStatusLine().getStatusCode());
+ Assert.assertEquals("Cors should have added a header: Access-Control-Allow-Headers: " + expectedValue, expectedValue,
+ response.getLastHeader("Access-Control-Allow-Headers").getValue());
+ response.close();
+ } finally {
+ System.clearProperty(SimpleWebServer.ACCESS_CONTROL_ALLOW_HEADER_PROPERTY_NAME);
+ }
+ }
+}
diff --git a/webserver/src/test/java/fi/iki/elonen/TestCorsHttpServerWithSingleOrigin.java b/webserver/src/test/java/fi/iki/elonen/TestCorsHttpServerWithSingleOrigin.java
new file mode 100644
index 0000000..dbd2c4e
--- /dev/null
+++ b/webserver/src/test/java/fi/iki/elonen/TestCorsHttpServerWithSingleOrigin.java
@@ -0,0 +1,121 @@
+package fi.iki.elonen;
+
+/*
+ * #%L
+ * NanoHttpd-Webserver
+ * %%
+ * 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.ByteArrayOutputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpOptions;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * @author Matthieu Brouillard [matthieu@brouillard.fr]
+ */
+public class TestCorsHttpServerWithSingleOrigin extends AbstractTestHttpServer {
+
+ private static PipedOutputStream stdIn;
+
+ private static Thread serverStartThread;
+
+ @BeforeClass
+ public static void setUp() throws Exception {
+ stdIn = new PipedOutputStream();
+ System.setIn(new PipedInputStream(stdIn));
+ serverStartThread = new Thread(new Runnable() {
+
+ @Override
+ public void run() {
+ String[] args = {
+ "--host",
+ "localhost",
+ "--port",
+ "9090",
+ "--dir",
+ "src/test/resources",
+ "--cors=http://localhost:9090"
+ };
+ SimpleWebServer.main(args);
+ }
+ });
+ serverStartThread.start();
+ // give the server some tine to start.
+ Thread.sleep(100);
+ }
+
+ @AfterClass
+ public static void tearDown() throws Exception {
+ stdIn.write("\n\n".getBytes());
+ serverStartThread.join(2000);
+ Assert.assertFalse(serverStartThread.isAlive());
+ }
+
+ @Test
+ public void doTestOption() throws Exception {
+ CloseableHttpClient httpclient = HttpClients.createDefault();
+ HttpOptions httpOption = new HttpOptions("http://localhost:9090/xxx/yyy.html");
+ CloseableHttpResponse response = httpclient.execute(httpOption);
+ Assert.assertEquals(200, response.getStatusLine().getStatusCode());
+ Assert.assertNotNull("Cors should have added a header: Access-Control-Allow-Origin", response.getLastHeader("Access-Control-Allow-Origin"));
+ Assert.assertEquals("Cors should have added a header: Access-Control-Allow-Origin: http://localhost:9090", "http://localhost:9090",
+ response.getLastHeader("Access-Control-Allow-Origin").getValue());
+ response.close();
+ }
+
+ @Test
+ public void doSomeBasicTest() throws Exception {
+ CloseableHttpClient httpclient = HttpClients.createDefault();
+ HttpGet httpget = new HttpGet("http://localhost:9090/testdir/test.html");
+ CloseableHttpResponse response = httpclient.execute(httpget);
+ HttpEntity entity = response.getEntity();
+ String string = new String(readContents(entity), "UTF-8");
+
+ Assert.assertNotNull("Cors should have added a header: Access-Control-Allow-Origin", response.getLastHeader("Access-Control-Allow-Origin"));
+ Assert.assertEquals("Cors should have added a header: Access-Control-Allow-Origin: http://localhost:9090", "http://localhost:9090",
+ response.getLastHeader("Access-Control-Allow-Origin").getValue());
+ Assert.assertEquals("<html>\n<head>\n<title>dummy</title>\n</head>\n<body>\n\t<h1>it works</h1>\n</body>\n</html>", string);
+ response.close();
+ }
+}
diff --git a/webserver/src/test/java/fi/iki/elonen/TestHttpServer.java b/webserver/src/test/java/fi/iki/elonen/TestHttpServer.java
new file mode 100644
index 0000000..bb16741
--- /dev/null
+++ b/webserver/src/test/java/fi/iki/elonen/TestHttpServer.java
@@ -0,0 +1,149 @@
+package fi.iki.elonen;
+
+/*
+ * #%L
+ * NanoHttpd-Webserver
+ * %%
+ * 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.ByteArrayOutputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class TestHttpServer extends AbstractTestHttpServer {
+
+ private static PipedOutputStream stdIn;
+
+ private static Thread serverStartThread;
+
+ @BeforeClass
+ public static void setUp() throws Exception {
+ stdIn = new PipedOutputStream();
+ System.setIn(new PipedInputStream(stdIn));
+ serverStartThread = new Thread(new Runnable() {
+
+ @Override
+ public void run() {
+ String[] args = {
+ "--host",
+ "localhost",
+ "--port",
+ "9090",
+ "--dir",
+ "src/test/resources"
+ };
+ SimpleWebServer.main(args);
+ }
+ });
+ serverStartThread.start();
+ // give the server some tine to start.
+ Thread.sleep(100);
+ }
+
+ @AfterClass
+ public static void tearDown() throws Exception {
+ stdIn.write("\n\n".getBytes());
+ serverStartThread.join(2000);
+ Assert.assertFalse(serverStartThread.isAlive());
+ }
+
+ @Test
+ public void doTest404() throws Exception {
+ CloseableHttpClient httpclient = HttpClients.createDefault();
+ HttpGet httpget = new HttpGet("http://localhost:9090/xxx/yyy.html");
+ CloseableHttpResponse response = httpclient.execute(httpget);
+ Assert.assertEquals(404, response.getStatusLine().getStatusCode());
+ response.close();
+ }
+
+ @Test
+ public void doPlugin() throws Exception {
+ CloseableHttpClient httpclient = HttpClients.createDefault();
+ HttpGet httpget = new HttpGet("http://localhost:9090/index.xml");
+ CloseableHttpResponse response = httpclient.execute(httpget);
+ String string = new String(readContents(response.getEntity()), "UTF-8");
+ Assert.assertEquals("<xml/>", string);
+ response.close();
+
+ httpget = new HttpGet("http://localhost:9090/testdir/testdir/different.xml");
+ response = httpclient.execute(httpget);
+ string = new String(readContents(response.getEntity()), "UTF-8");
+ Assert.assertEquals("<xml/>", string);
+ response.close();
+ }
+
+ @Test
+ public void doSomeBasicTest() throws Exception {
+ CloseableHttpClient httpclient = HttpClients.createDefault();
+ HttpGet httpget = new HttpGet("http://localhost:9090/testdir/test.html");
+ CloseableHttpResponse response = httpclient.execute(httpget);
+ HttpEntity entity = response.getEntity();
+ String string = new String(readContents(entity), "UTF-8");
+ Assert.assertEquals("<html>\n<head>\n<title>dummy</title>\n</head>\n<body>\n\t<h1>it works</h1>\n</body>\n</html>", string);
+ response.close();
+
+ httpget = new HttpGet("http://localhost:9090/");
+ response = httpclient.execute(httpget);
+ entity = response.getEntity();
+ string = new String(readContents(entity), "UTF-8");
+ Assert.assertTrue(string.indexOf("testdir") > 0);
+ response.close();
+
+ httpget = new HttpGet("http://localhost:9090/testdir");
+ response = httpclient.execute(httpget);
+ entity = response.getEntity();
+ string = new String(readContents(entity), "UTF-8");
+ Assert.assertTrue(string.indexOf("test.html") > 0);
+ response.close();
+
+ httpget = new HttpGet("http://localhost:9090/testdir/testpdf.pdf");
+ response = httpclient.execute(httpget);
+ entity = response.getEntity();
+
+ byte[] actual = readContents(entity);
+ byte[] expected = readContents(new FileInputStream("src/test/resources/testdir/testpdf.pdf"));
+ Assert.assertArrayEquals(expected, actual);
+ response.close();
+
+ }
+}
diff --git a/webserver/src/test/resources/META-INF/services/fi.iki.elonen.WebServerPluginInfo b/webserver/src/test/resources/META-INF/services/fi.iki.elonen.WebServerPluginInfo
new file mode 100644
index 0000000..8819204
--- /dev/null
+++ b/webserver/src/test/resources/META-INF/services/fi.iki.elonen.WebServerPluginInfo
@@ -0,0 +1 @@
+fi.iki.elonen.DummyPluginInfo \ No newline at end of file
diff --git a/webserver/src/test/resources/testdir/test.html b/webserver/src/test/resources/testdir/test.html
new file mode 100644
index 0000000..4cb157c
--- /dev/null
+++ b/webserver/src/test/resources/testdir/test.html
@@ -0,0 +1,8 @@
+<html>
+<head>
+<title>dummy</title>
+</head>
+<body>
+ <h1>it works</h1>
+</body>
+</html> \ No newline at end of file
diff --git a/webserver/src/test/resources/testdir/testdir/different.xml b/webserver/src/test/resources/testdir/testdir/different.xml
new file mode 100644
index 0000000..e7a01d5
--- /dev/null
+++ b/webserver/src/test/resources/testdir/testdir/different.xml
@@ -0,0 +1,3 @@
+<xml>
+ This sould not show up ;-)
+</xml> \ No newline at end of file
diff --git a/webserver/src/test/resources/testdir/testpdf.pdf b/webserver/src/test/resources/testdir/testpdf.pdf
new file mode 100644
index 0000000..da8161f
--- /dev/null
+++ b/webserver/src/test/resources/testdir/testpdf.pdf
Binary files differ