aboutsummaryrefslogtreecommitdiff
path: root/webserver
diff options
context:
space:
mode:
authorPaul Hawke <paul.hawke@gmail.com>2013-09-14 22:39:46 -0500
committerPaul Hawke <paul.hawke@gmail.com>2013-09-14 22:39:46 -0500
commit51deb639d4fd75bbbaaebc46d3f5f7ceaaddd8e8 (patch)
treed8aad2c640d9648be75f17d7a1a4d70b241c9be8 /webserver
parent399229d96d32865f9979217bdc06ea8fa56e962d (diff)
downloadnanohttpd-51deb639d4fd75bbbaaebc46d3f5f7ceaaddd8e8.tar.gz
Serving files by mime type, with plugin support so external plugins can register to handle different file types. Example plugin writted to support markdown (.md) files. @psh
Diffstat (limited to 'webserver')
-rw-r--r--webserver/markdown-plugin/pom.xml105
-rw-r--r--webserver/markdown-plugin/src/main/java/fi/iki/elonen/MarkdownWebServerPlugin.java64
-rw-r--r--webserver/markdown-plugin/src/main/java/fi/iki/elonen/MarkdownWebServerPluginInfo.java19
-rw-r--r--webserver/markdown-plugin/src/main/resources/META-INF/services/fi.iki.elonen.WebServerPluginInfo1
-rw-r--r--webserver/src/main/java/fi/iki/elonen/SimpleWebServer.java178
-rw-r--r--webserver/src/main/java/fi/iki/elonen/WebServerPlugin.java14
-rw-r--r--webserver/src/main/java/fi/iki/elonen/WebServerPluginInfo.java13
7 files changed, 352 insertions, 42 deletions
diff --git a/webserver/markdown-plugin/pom.xml b/webserver/markdown-plugin/pom.xml
new file mode 100644
index 0000000..9734392
--- /dev/null
+++ b/webserver/markdown-plugin/pom.xml
@@ -0,0 +1,105 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <groupId>fi.iki.elonen</groupId>
+ <artifactId>nanohttpd-webserver-markdown-plugin</artifactId>
+ <version>2.0.3</version>
+ <packaging>jar</packaging>
+
+ <name>NanoHttpd-Webserver-Markdown-Plugin</name>
+ <url>https://github.com/NanoHttpd/nanohttpd</url>
+
+ <dependencies>
+ <dependency>
+ <groupId>fi.iki.elonen</groupId>
+ <artifactId>nanohttpd</artifactId>
+ <version>2.0.3</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>fi.iki.elonen</groupId>
+ <artifactId>nanohttpd-webserver</artifactId>
+ <version>2.0.3</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.pegdown</groupId>
+ <artifactId>pegdown</artifactId>
+ <version>1.4.1</version>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <extensions>
+ <extension>
+ <groupId>org.jvnet.wagon-svn</groupId>
+ <artifactId>wagon-svn</artifactId>
+ <version>1.8</version>
+ </extension>
+ <extension>
+ <groupId>org.apache.maven.wagon</groupId>
+ <artifactId>wagon-ftp</artifactId>
+ <version>1.0-alpha-6</version>
+ </extension>
+ </extensions>
+
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-source-plugin</artifactId>
+ <version>2.2.1</version>
+ <executions>
+ <execution>
+ <id>attach-sources</id>
+ <goals>
+ <goal>jar</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-release-plugin</artifactId>
+ <version>2.4</version>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ <version>2.9</version>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>2.3.1</version>
+ <configuration>
+ <source>1.6</source>
+ <target>1.6</target>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ <version>2.2-beta-5</version>
+ <configuration>
+ <descriptorRefs>
+ <descriptorRef>jar-with-dependencies</descriptorRef>
+ </descriptorRefs>
+ <archive>
+ <manifest>
+ <mainClass>fi.iki.elonen.SimpleWebServer</mainClass>
+ </manifest>
+ </archive>
+ </configuration>
+ <executions>
+ <execution>
+ <phase>package</phase>
+ <goals>
+ <goal>single</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/webserver/markdown-plugin/src/main/java/fi/iki/elonen/MarkdownWebServerPlugin.java b/webserver/markdown-plugin/src/main/java/fi/iki/elonen/MarkdownWebServerPlugin.java
new file mode 100644
index 0000000..6e82c2e
--- /dev/null
+++ b/webserver/markdown-plugin/src/main/java/fi/iki/elonen/MarkdownWebServerPlugin.java
@@ -0,0 +1,64 @@
+package fi.iki.elonen;
+
+import org.pegdown.PegDownProcessor;
+
+import java.io.*;
+import java.util.Map;
+
+import static fi.iki.elonen.NanoHTTPD.MIME_HTML;
+import static fi.iki.elonen.NanoHTTPD.Response.Status.OK;
+
+/**
+ * @author Paul S. Hawke (paul.hawke@gmail.com)
+ * On: 9/13/13 at 4:03 AM
+ */
+public class MarkdownWebServerPlugin implements WebServerPlugin {
+
+ private final PegDownProcessor processor;
+
+ public MarkdownWebServerPlugin() {
+ processor = new PegDownProcessor();
+ }
+
+ @Override public boolean canServeUri(String uri, File rootDir) {
+ File f = new File(rootDir, uri);
+ return f.exists();
+ }
+
+ @Override public NanoHTTPD.Response serveFile(String uri, Map<String, String> headers, File file, String mimeType) {
+ String markdownSource = readSource(file);
+ return markdownSource == null ? null :
+ new NanoHTTPD.Response(OK, MIME_HTML, processor.markdownToHtml(markdownSource));
+ }
+
+ private String readSource(File file) {
+ FileReader fileReader = null;
+ BufferedReader reader = null;
+ try {
+ fileReader = new FileReader(file);
+ reader = new BufferedReader(fileReader);
+ String line = null;
+ StringBuilder sb = new StringBuilder();
+ do {
+ line = reader.readLine();
+ if (line != null) {
+ sb.append(line).append("\n");
+ }
+ } while (line != null);
+ reader.close();
+ return sb.toString();
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ } finally {
+ try {
+ if (fileReader != null) {
+ fileReader.close();
+ }
+ if (reader != null) {
+ reader.close();
+ }
+ } catch (IOException ignored) {}
+ }
+ }
+}
diff --git a/webserver/markdown-plugin/src/main/java/fi/iki/elonen/MarkdownWebServerPluginInfo.java b/webserver/markdown-plugin/src/main/java/fi/iki/elonen/MarkdownWebServerPluginInfo.java
new file mode 100644
index 0000000..ff0e34e
--- /dev/null
+++ b/webserver/markdown-plugin/src/main/java/fi/iki/elonen/MarkdownWebServerPluginInfo.java
@@ -0,0 +1,19 @@
+package fi.iki.elonen;
+
+/**
+ * @author Paul S. Hawke (paul.hawke@gmail.com)
+ * On: 9/13/13 at 4:01 AM
+ */
+public class MarkdownWebServerPluginInfo implements WebServerPluginInfo {
+ @Override public String[] getMimeTypes() {
+ return new String[]{"text/markdown"};
+ }
+
+ @Override public String[] getIndexFilesForMimeType(String mime) {
+ return new String[]{"index.md"};
+ }
+
+ @Override public WebServerPlugin getWebServerPlugin(String mimeType) {
+ return new MarkdownWebServerPlugin();
+ }
+}
diff --git a/webserver/markdown-plugin/src/main/resources/META-INF/services/fi.iki.elonen.WebServerPluginInfo b/webserver/markdown-plugin/src/main/resources/META-INF/services/fi.iki.elonen.WebServerPluginInfo
new file mode 100644
index 0000000..5fd02ab
--- /dev/null
+++ b/webserver/markdown-plugin/src/main/resources/META-INF/services/fi.iki.elonen.WebServerPluginInfo
@@ -0,0 +1 @@
+fi.iki.elonen.MarkdownWebServerPluginInfo
diff --git a/webserver/src/main/java/fi/iki/elonen/SimpleWebServer.java b/webserver/src/main/java/fi/iki/elonen/SimpleWebServer.java
index bff1421..90bb2e7 100644
--- a/webserver/src/main/java/fi/iki/elonen/SimpleWebServer.java
+++ b/webserver/src/main/java/fi/iki/elonen/SimpleWebServer.java
@@ -1,8 +1,12 @@
package fi.iki.elonen;
import java.io.*;
+import java.net.JarURLConnection;
+import java.net.URL;
import java.net.URLEncoder;
import java.util.*;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
public class SimpleWebServer extends NanoHTTPD {
/**
@@ -12,7 +16,10 @@ public class SimpleWebServer extends NanoHTTPD {
/**
* Default Index file names.
*/
- public static final String[] INDEX_FILE_NAMES = {"index.html", "index.htm"};
+ 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
*/
@@ -22,6 +29,7 @@ public class SimpleWebServer extends NanoHTTPD {
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");
@@ -71,13 +79,21 @@ public class SimpleWebServer extends NanoHTTPD {
+ "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 final File rootDir;
+ 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.rootDir = wwwroot;
this.quiet = quiet;
+ this.rootDirs = new ArrayList<File>();
+ this.rootDirs.add(wwwroot);
+ }
+
+ public SimpleWebServer(String host, int port, List<File> wwwroots, boolean quiet) {
+ super(host, port);
+ this.quiet = quiet;
+ this.rootDirs = new ArrayList<File>(wwwroots);
}
/**
@@ -88,7 +104,7 @@ public class SimpleWebServer extends NanoHTTPD {
int port = 8080;
String host = "127.0.0.1";
- File wwwroot = new File(".").getAbsoluteFile();
+ List<File> rootDirs = new ArrayList<File>();
boolean quiet = false;
// Parse command-line, with short and long versions of the options.
@@ -100,18 +116,67 @@ public class SimpleWebServer extends NanoHTTPD {
} else if (args[i].equalsIgnoreCase("-q") || args[i].equalsIgnoreCase("--quiet")) {
quiet = true;
} else if (args[i].equalsIgnoreCase("-d") || args[i].equalsIgnoreCase("--dir")) {
- wwwroot = new File(args[i + 1]).getAbsoluteFile();
+ rootDirs.add(new File(args[i + 1]).getAbsoluteFile());
} else if (args[i].equalsIgnoreCase("--licence")) {
System.out.println(LICENCE + "\n");
break;
}
}
- ServerRunner.executeInstance(new SimpleWebServer(host, port, wwwroot, quiet));
+ if (rootDirs.isEmpty()) {
+ rootDirs.add(new File(".").getAbsoluteFile());
+ }
+
+ ServiceLoader<WebServerPluginInfo> serviceLoader = ServiceLoader.load(WebServerPluginInfo.class);
+ for (WebServerPluginInfo info : serviceLoader) {
+ String[] mimeTypes = info.getMimeTypes();
+ for (String mime : mimeTypes) {
+ String[] indexFiles = info.getIndexFilesForMimeType(mime);
+ if (!quiet) {
+ System.out.print("# Found plugin for Mime type: \"" + mime + "\"");
+ if (indexFiles != null) {
+ System.out.print(" (serving index files: ");
+ for (String indexFile : indexFiles) {
+ System.out.print(indexFile + " ");
+ }
+ }
+ System.out.println(").");
+ }
+ registerPluginForMimeType(indexFiles, mime, info.getWebServerPlugin(mime));
+ }
+ }
+
+ ServerRunner.executeInstance(new SimpleWebServer(host, port, rootDirs, quiet));
+ }
+
+ private static void registerPluginForMimeType(String[] indexFiles, String mimeType, WebServerPlugin plugin) {
+ if (mimeType == null || plugin == null) {
+ return;
+ }
+
+ if (indexFiles != null) {
+ for (String filename : indexFiles) {
+ int dot = filename.lastIndexOf('.');
+ if (dot >= 0) {
+ String extension = filename.substring(dot + 1).toLowerCase();
+ MIME_TYPES.put(extension, mimeType);
+ }
+ }
+ INDEX_FILE_NAMES.addAll(Arrays.asList(indexFiles));
+ }
+ mimeTypeHandlers.put(mimeType, plugin);
}
private File getRootDir() {
- return rootDir;
+ return rootDirs.get(0);
+ }
+
+ private List<File> getRootDirs() {
+ return rootDirs;
+ }
+
+ private void addWwwRootDir(File wwwroot) {
+ rootDirs.add(wwwroot);
}
/**
@@ -156,11 +221,14 @@ public class SimpleWebServer extends NanoHTTPD {
}
}
- File homeDir = getRootDir();
+ List<File> homeDirs = getRootDirs();
- // Make sure we won't die of an exception later
- if (!homeDir.isDirectory()) {
- return createResponse(Response.Status.INTERNAL_ERROR, NanoHTTPD.MIME_PLAINTEXT, "INTERNAL ERRROR: serveFile(): given homeDir is not a directory.");
+ for (File homeDir : homeDirs) {
+ // Make sure we won't die of an exception later
+ if (!homeDir.isDirectory()) {
+ return createResponse(Response.Status.INTERNAL_ERROR, NanoHTTPD.MIME_PLAINTEXT,
+ "INTERNAL ERRROR: given path is not a directory (" + homeDir + ").");
+ }
}
// Remove URL arguments
@@ -174,49 +242,75 @@ public class SimpleWebServer extends NanoHTTPD {
return createResponse(Response.Status.FORBIDDEN, NanoHTTPD.MIME_PLAINTEXT, "FORBIDDEN: Won't serve ../ for security reasons.");
}
- File f = new File(homeDir, uri);
- if (!f.exists()) {
+ boolean canServeUri = false;
+ File homeDir = null;
+ for (int i = 0; !canServeUri && i < homeDirs.size(); i++) {
+ homeDir = homeDirs.get(i);
+ canServeUri = canServeUri(uri, homeDir);
+ }
+ if (!canServeUri) {
return createResponse(Response.Status.NOT_FOUND, NanoHTTPD.MIME_PLAINTEXT, "Error 404, file not found.");
}
- return serveFile(uri, header, f);
- }
-
- /**
- * 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) {
- // List the directory, if necessary
- if (file.isDirectory()) {
- // Browsers get confused without '/' after the
- // directory, send a redirect.
- if (!uri.endsWith("/")) {
- uri += "/";
- Response res = createResponse(Response.Status.REDIRECT, NanoHTTPD.MIME_HTML, "<html><body>Redirected: <a href=\"" + uri + "\">" +
- uri + "</a></body></html>");
- res.addHeader("Location", uri);
- return res;
- }
+ // 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>");
+ 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.
- File indexFile = findIndexFileInDirectory(file);
+ String indexFile = findIndexFileInDirectory(f);
if (indexFile == null) {
- if (file.canRead()) {
+ if (f.canRead()) {
// No index file, list the directory if it is readable
- return createResponse(Response.Status.OK, NanoHTTPD.MIME_HTML, listDirectory(uri, file));
+ return createResponse(Response.Status.OK, NanoHTTPD.MIME_HTML, listDirectory(uri, f));
} else {
return createResponse(Response.Status.FORBIDDEN, NanoHTTPD.MIME_PLAINTEXT, "FORBIDDEN: No directory listing.");
}
} else {
// Index file was found, so serve it.
- file = indexFile;
+ uri += indexFile;
+ f = new File(f, indexFile);
}
}
+ String mimeTypeForFile = getMimeTypeForFile(uri);
+ WebServerPlugin plugin = mimeTypeHandlers.get(mimeTypeForFile);
+ Response response = null;
+ if (plugin != null) {
+ response = plugin.serveFile(uri, header, f, mimeTypeForFile);
+ } else {
+ response = serveFile(uri, header, f, mimeTypeForFile);
+ }
+ return response != null ? response :
+ createResponse(Response.Status.NOT_FOUND, NanoHTTPD.MIME_PLAINTEXT, "Error 404, file not found.");
+ }
+
+ 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);
+ }
+ }
+ return canServeUri;
+ }
+
+ /**
+ * 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;
try {
- String mime = getMimeTypeForFile(file);
-
// Calculate etag
String etag = Integer.toHexString((file.getAbsolutePath() + file.lastModified() + "" + file.length()).hashCode());
@@ -285,11 +379,11 @@ public class SimpleWebServer extends NanoHTTPD {
}
// Get MIME type from file name extension, if possible
- private String getMimeTypeForFile(File f) throws IOException {
+ private String getMimeTypeForFile(String uri) {
+ int dot = uri.lastIndexOf('.');
String mime = null;
- int dot = f.getCanonicalPath().lastIndexOf('.');
if (dot >= 0) {
- mime = MIME_TYPES.get(f.getCanonicalPath().substring(dot + 1).toLowerCase());
+ mime = MIME_TYPES.get(uri.substring(dot + 1).toLowerCase());
}
return mime == null ? MIME_DEFAULT_BINARY : mime;
}
@@ -308,11 +402,11 @@ public class SimpleWebServer extends NanoHTTPD {
return res;
}
- private File findIndexFileInDirectory(File directory) {
+ private String findIndexFileInDirectory(File directory) {
for (String fileName : INDEX_FILE_NAMES) {
File indexFile = new File(directory, fileName);
if (indexFile.exists()) {
- return indexFile;
+ return fileName;
}
}
return null;
diff --git a/webserver/src/main/java/fi/iki/elonen/WebServerPlugin.java b/webserver/src/main/java/fi/iki/elonen/WebServerPlugin.java
new file mode 100644
index 0000000..d1292df
--- /dev/null
+++ b/webserver/src/main/java/fi/iki/elonen/WebServerPlugin.java
@@ -0,0 +1,14 @@
+package fi.iki.elonen;
+
+import java.io.File;
+import java.util.Map;
+
+/**
+* @author Paul S. Hawke (paul.hawke@gmail.com)
+* On: 9/14/13 at 8:09 AM
+*/
+public interface WebServerPlugin {
+ boolean canServeUri(String uri, File rootDir);
+
+ NanoHTTPD.Response serveFile(String uri, Map<String, String> headers, 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
new file mode 100644
index 0000000..1e3deb7
--- /dev/null
+++ b/webserver/src/main/java/fi/iki/elonen/WebServerPluginInfo.java
@@ -0,0 +1,13 @@
+package fi.iki.elonen;
+
+/**
+* @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);
+
+ WebServerPlugin getWebServerPlugin(String mimeType);
+}