diff options
author | Paul Hawke <paul.hawke@gmail.com> | 2013-03-08 21:43:35 -0600 |
---|---|---|
committer | Paul Hawke <paul.hawke@gmail.com> | 2013-03-08 21:43:35 -0600 |
commit | 47b3e36685d8af2b8be644fd06da4297de4aa204 (patch) | |
tree | fd6e4f58c2f184ac82ca48b14f25bc83e3809004 /samples | |
parent | 48f1d0ff6e4b0114f39901048ae1f64a679a01e8 (diff) | |
download | nanohttpd-47b3e36685d8af2b8be644fd06da4297de4aa204.tar.gz |
Project cleanup - broke out "samples" and "core" maven modules to reduce the production JAR file footprint and remove clutter.
Diffstat (limited to 'samples')
-rw-r--r-- | samples/pom.xml | 96 | ||||
-rw-r--r-- | samples/src/main/java/fi/iki/elonen/FileUploadTesting.java | 58 | ||||
-rw-r--r-- | samples/src/main/java/fi/iki/elonen/HelloServer.java | 50 | ||||
-rw-r--r-- | samples/src/main/java/fi/iki/elonen/SimpleWebServer.java | 337 | ||||
-rw-r--r-- | samples/src/test/resources/file-upload-test.htm | 16 |
5 files changed, 557 insertions, 0 deletions
diff --git a/samples/pom.xml b/samples/pom.xml new file mode 100644 index 0000000..cd955bd --- /dev/null +++ b/samples/pom.xml @@ -0,0 +1,96 @@ +<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-samples</artifactId> + <version>1.0.0</version> + <packaging>jar</packaging> + + <name>NanoHTTPd-samples</name> + <url>http://maven.apache.org</url> + + <properties> + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + </properties> + + <dependencies> + <dependency> + <groupId>fi.iki.elonen</groupId> + <artifactId>nanohttpd</artifactId> + <version>1.0.0</version> + </dependency> + </dependencies> + + <distributionManagement> + <repository> + <id>fictional-io</id> + <name>fictional.io</name> + <url>ftp://fictional.io/domains/fictional.io/public_html/maven/maven2</url> + <uniqueVersion>false</uniqueVersion> + </repository> + <snapshotRepository> + <id>fictional-io</id> + <name>fictional.io</name> + <url>ftp://fictional.io/domains/fictional.io/public_html/maven/maven2</url> + <uniqueVersion>false</uniqueVersion> + </snapshotRepository> + </distributionManagement> + + <scm> + <connection>scm:git:git://github.com/psh/nanohttpd.git</connection> + <url>scm:git:git://github.com/psh/nanohttpd.git</url> + <developerConnection>scm:git:git://github.com/psh/nanohttpd.git</developerConnection> + <tag>nanohttpd-1.0.0</tag> + </scm> + + <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> + </plugins> + </build> +</project> diff --git a/samples/src/main/java/fi/iki/elonen/FileUploadTesting.java b/samples/src/main/java/fi/iki/elonen/FileUploadTesting.java new file mode 100644 index 0000000..f8326e1 --- /dev/null +++ b/samples/src/main/java/fi/iki/elonen/FileUploadTesting.java @@ -0,0 +1,58 @@ +package fi.iki.elonen; + +import java.io.IOException; +import java.util.Map; + +/** + * @author Paul S. Hawke (paul.hawke@gmail.com) + * On: 2/21/13 at 7:05 AM + */ +public class FileUploadTesting extends NanoHTTPD { + public FileUploadTesting() { + super(8080); + } + + public static void main(String[] args) { + FileUploadTesting server = new FileUploadTesting(); + + 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"); + } + + @Override + public Response serve(String uri, Method method, Map<String, String> header, Map<String, String> parms, Map<String, String> files) { + StringBuilder sb = new StringBuilder(); + sb.append("<h3>Request:</h3>"); + sb.append("<blockquote>"); + sb.append("<b>URI:</b>").append(uri).append("<br>"); + sb.append("<b>Method:</b>").append(method).append("<br>"); + sb.append("</blockquote>"); + sb.append("<h3>Headers:</h3>"); + sb.append("<blockquote>"); + sb.append(String.valueOf(header)); + sb.append("</blockquote>"); + sb.append("<h3>Parameters:</h3>"); + sb.append("<blockquote>"); + sb.append(String.valueOf(parms)); + sb.append("</blockquote>"); + sb.append("<h3>Files:</h3>"); + sb.append("<blockquote>"); + sb.append(String.valueOf(files)); + sb.append("</blockquote>"); + return new Response("<html><body>" + sb.toString() + "</body></html>"); + } +} diff --git a/samples/src/main/java/fi/iki/elonen/HelloServer.java b/samples/src/main/java/fi/iki/elonen/HelloServer.java new file mode 100644 index 0000000..370b88b --- /dev/null +++ b/samples/src/main/java/fi/iki/elonen/HelloServer.java @@ -0,0 +1,50 @@ +package fi.iki.elonen; + +import java.io.*; +import java.util.*; + +/** + * An example of subclassing NanoHTTPD to make a custom HTTP server. + */ +public class HelloServer extends NanoHTTPD { + private HelloServer() { + super(8080); + } + + @Override + public Response serve(String uri, Method method, Map<String, String> header, Map<String, String> parms, Map<String, String> files) { + System.out.println(method + " '" + uri + "' "); + + String msg = "<html><body><h1>Hello server</h1>\n"; + if (parms.get("username") == null) + msg += + "<form action='?' method='get'>\n" + + " <p>Your name: <input type='text' name='username'></p>\n" + + "</form>\n"; + else + msg += "<p>Hello, " + parms.get("username") + "!</p>"; + + msg += "</body></html>\n"; + + return new NanoHTTPD.Response(msg); + } + + public static void main(String[] args) { + HelloServer helloServer = new HelloServer(); + + try { + helloServer.start(); + } catch (IOException ioe) { + System.err.println("Couldn't start server:\n" + ioe); + System.exit(-1); + } + + System.out.println("Listening on port 8080. Hit Enter to stop.\n"); + try { + System.in.read(); + } catch (Throwable ignored) { + } + + helloServer.stop(); + } +} diff --git a/samples/src/main/java/fi/iki/elonen/SimpleWebServer.java b/samples/src/main/java/fi/iki/elonen/SimpleWebServer.java new file mode 100644 index 0000000..8f94f80 --- /dev/null +++ b/samples/src/main/java/fi/iki/elonen/SimpleWebServer.java @@ -0,0 +1,337 @@ +package fi.iki.elonen; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.StringTokenizer; + +public class SimpleWebServer extends NanoHTTPD { + /** + * Hashtable mapping (String)FILENAME_EXTENSION -> (String)MIME_TYPE + */ + private static final Map<String, String> MIME_TYPES; + static { + Map<String, String> mime = new HashMap<String, String>(); + mime.put("css", "text/css"); + mime.put("htm", "text/html"); + mime.put("html", "text/html"); + mime.put("xml", "text/xml"); + mime.put("txt", "text/plain"); + mime.put("asc", "text/plain"); + mime.put("gif", "image/gif"); + mime.put("jpg", "image/jpeg"); + mime.put("jpeg", "image/jpeg"); + mime.put("png", "image/png"); + mime.put("mp3", "audio/mpeg"); + mime.put("m3u", "audio/mpeg-url"); + mime.put("mp4", "video/mp4"); + mime.put("ogv", "video/ogg"); + mime.put("flv", "video/x-flv"); + mime.put("mov", "video/quicktime"); + mime.put("swf", "application/x-shockwave-flash"); + mime.put("js", "application/javascript"); + mime.put("pdf", "application/pdf"); + mime.put("doc", "application/msword"); + mime.put("ogg", "application/x-ogg"); + mime.put("zip", "application/octet-stream"); + mime.put("exe", "application/octet-stream"); + mime.put("class", "application/octet-stream"); + MIME_TYPES = mime; + } + + /** + * The distribution licence + */ + private static final String LICENCE = "Copyright (C) 2001,2005-2011 by Jarno Elonen <elonen@iki.fi>\n" + + "and Copyright (C) 2010 by Konstantinos Togias <info@ktogias.gr>\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 File rootDir; + + public SimpleWebServer(int port, File wwwroot) { + super(port); + this.rootDir = wwwroot; + } + + public File getRootDir() { + return rootDir; + } + + /** + * 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("/")) + newUri += "/"; + else if (tok.equals(" ")) + newUri += "%20"; + else { + try { + newUri += URLEncoder.encode(tok, "UTF-8"); + } catch (UnsupportedEncodingException ignored) { + } + } + } + return newUri; + } + + /** + * Serves file from homeDir and its' subdirectories (only). Uses only URI, ignores all headers and HTTP parameters. + */ + public Response serveFile(String uri, Map<String, String> header, File homeDir) { + Response res = null; + + // Make sure we won't die of an exception later + if (!homeDir.isDirectory()) + res = new Response(Response.Status.INTERNAL_ERROR, NanoHTTPD.MIME_PLAINTEXT, "INTERNAL ERRROR: serveFile(): given homeDir is not a directory."); + + if (res == null) { + // Remove URL arguments + uri = uri.trim().replace(File.separatorChar, '/'); + if (uri.indexOf('?') >= 0) + uri = uri.substring(0, uri.indexOf('?')); + + // Prohibit getting out of current directory + if (uri.startsWith("src/main") || uri.endsWith("src/main") || uri.contains("../")) + res = new Response(Response.Status.FORBIDDEN, NanoHTTPD.MIME_PLAINTEXT, "FORBIDDEN: Won't serve ../ for security reasons."); + } + + File f = new File(homeDir, uri); + if (res == null && !f.exists()) + res = new Response(Response.Status.NOT_FOUND, NanoHTTPD.MIME_PLAINTEXT, "Error 404, file not found."); + + // List the directory, if necessary + if (res == null && f.isDirectory()) { + // Browsers get confused without '/' after the + // directory, send a redirect. + if (!uri.endsWith("/")) { + uri += "/"; + res = new Response(Response.Status.REDIRECT, NanoHTTPD.MIME_HTML, "<html><body>Redirected: <a href=\"" + uri + "\">" + uri + + "</a></body></html>"); + res.addHeader("Location", uri); + } + + if (res == null) { + // First try index.html and index.htm + if (new File(f, "index.html").exists()) + f = new File(homeDir, uri + "/index.html"); + else if (new File(f, "index.htm").exists()) + f = new File(homeDir, uri + "/index.htm"); + // No index file, list the directory if it is readable + else if (f.canRead()) { + String[] files = f.list(); + String msg = "<html><body><h1>Directory " + uri + "</h1><br/>"; + + if (uri.length() > 1) { + String u = uri.substring(0, uri.length() - 1); + int slash = u.lastIndexOf('/'); + if (slash >= 0 && slash < u.length()) + msg += "<b><a href=\"" + uri.substring(0, slash + 1) + "\">..</a></b><br/>"; + } + + if (files != null) { + for (int i = 0; i < files.length; ++i) { + File curFile = new File(f, files[i]); + boolean dir = curFile.isDirectory(); + if (dir) { + msg += "<b>"; + files[i] += "/"; + } + + msg += "<a href=\"" + encodeUri(uri + files[i]) + "\">" + files[i] + "</a>"; + + // Show file size + if (curFile.isFile()) { + long len = curFile.length(); + msg += " <font size=2>("; + if (len < 1024) + msg += len + " bytes"; + else if (len < 1024 * 1024) + msg += len / 1024 + "." + (len % 1024 / 10 % 100) + " KB"; + else + msg += len / (1024 * 1024) + "." + len % (1024 * 1024) / 10 % 100 + " MB"; + + msg += ")</font>"; + } + msg += "<br/>"; + if (dir) + msg += "</b>"; + } + } + msg += "</body></html>"; + res = new Response(msg); + } else { + res = new Response(Response.Status.FORBIDDEN, NanoHTTPD.MIME_PLAINTEXT, "FORBIDDEN: No directory listing."); + } + } + } + + try { + if (res == null) { + // Get MIME type from file name extension, if possible + String mime = null; + int dot = f.getCanonicalPath().lastIndexOf('.'); + if (dot >= 0) + mime = MIME_TYPES.get(f.getCanonicalPath().substring(dot + 1).toLowerCase()); + if (mime == null) + mime = NanoHTTPD.MIME_DEFAULT_BINARY; + + // Calculate etag + String etag = Integer.toHexString((f.getAbsolutePath() + f.lastModified() + "" + f.length()).hashCode()); + + // Support (simple) skipping: + long startFrom = 0; + long endAt = -1; + String range = header.get("range"); + if (range != null) { + if (range.startsWith("bytes=")) { + range = range.substring("bytes=".length()); + int minus = range.indexOf('-'); + try { + if (minus > 0) { + startFrom = Long.parseLong(range.substring(0, minus)); + endAt = Long.parseLong(range.substring(minus + 1)); + } + } catch (NumberFormatException ignored) { + } + } + } + + // Change return code and add Content-Range header when skipping is requested + long fileLen = f.length(); + if (range != null && startFrom >= 0) { + if (startFrom >= fileLen) { + res = new Response(Response.Status.RANGE_NOT_SATISFIABLE, NanoHTTPD.MIME_PLAINTEXT, ""); + res.addHeader("Content-Range", "bytes 0-0/" + fileLen); + res.addHeader("ETag", etag); + } else { + if (endAt < 0) + endAt = fileLen - 1; + long newLen = endAt - startFrom + 1; + if (newLen < 0) + newLen = 0; + + final long dataLen = newLen; + FileInputStream fis = new FileInputStream(f) { + @Override + public int available() throws IOException { + return (int) dataLen; + } + }; + fis.skip(startFrom); + + res = new Response(Response.Status.PARTIAL_CONTENT, mime, fis); + res.addHeader("Content-Length", "" + dataLen); + res.addHeader("Content-Range", "bytes " + startFrom + "-" + endAt + "/" + fileLen); + res.addHeader("ETag", etag); + } + } else { + if (etag.equals(header.get("if-none-match"))) + res = new Response(Response.Status.NOT_MODIFIED, mime, ""); + else { + res = new Response(Response.Status.OK, mime, new FileInputStream(f)); + res.addHeader("Content-Length", "" + fileLen); + res.addHeader("ETag", etag); + } + } + } + } catch (IOException ioe) { + res = new Response(Response.Status.FORBIDDEN, NanoHTTPD.MIME_PLAINTEXT, "FORBIDDEN: Reading file failed."); + } + + res.addHeader("Accept-Ranges", "bytes"); // Announce that the file server accepts partial content requestes + return res; + } + + @Override + public Response serve(String uri, Method method, Map<String, String> header, Map<String, String> parms, Map<String, String> files) { + System.out.println(method + " '" + uri + "' "); + + 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) + "'"); + } + e = files.keySet().iterator(); + while (e.hasNext()) { + String value = e.next(); + System.out.println(" UPLOADED: '" + value + "' = '" + files.get(value) + "'"); + } + + return serveFile(uri, header, getRootDir()); + } + + /** + * Starts as a standalone file server and waits for Enter. + */ + public static void main(String[] args) { + System.out.println("NanoHTTPD 1.25 (C) 2001,2005-2011 Jarno Elonen and (C) 2010 Konstantinos Togias\n" + + "(Command line options: [-p port] [-d root-dir] [--licence])\n"); + + // Defaults + int port = 8080; + File wwwroot = new File(".").getAbsoluteFile(); + + // Show licence if requested + for (int i = 0; i < args.length; ++i) + if (args[i].equalsIgnoreCase("-p")) + port = Integer.parseInt(args[i + 1]); + else if (args[i].equalsIgnoreCase("-d")) + wwwroot = new File(args[i + 1]).getAbsoluteFile(); + else if (args[i].toLowerCase().endsWith("licence")) { + System.out.println(LICENCE + "\n"); + break; + } + + SimpleWebServer server = new SimpleWebServer(port, wwwroot); + + try { + server.start(); + } catch (IOException ioe) { + System.err.println("Couldn't start server:\n" + ioe); + System.exit(-1); + } + + System.out.println("Now serving files in port " + port + " from \"" + wwwroot + "\""); + System.out.println("Hit Enter to stop.\n"); + + try { + System.in.read(); + } catch (Throwable ignored) { + } + + server.stop(); + } +} diff --git a/samples/src/test/resources/file-upload-test.htm b/samples/src/test/resources/file-upload-test.htm new file mode 100644 index 0000000..7d553bf --- /dev/null +++ b/samples/src/test/resources/file-upload-test.htm @@ -0,0 +1,16 @@ +<html> + <body> + <p>This is a file upload test for NanoHTTPD.</p> + <form action="http://localhost:8080/" enctype="multipart/form-data" method="post"> + <label for="textline">Text:</label> + <input type="text" id="textline" name="textline" size="30"><br> + <label for="datafile1">First File:</label> + <input type="file" id="datafile1" name="datafile1" size="40"><br> + <label for="datafile2">Second File:</label> + <input type="file" id="datafile2" name="datafile2" size="40"><br> + <label for="datafile2">Third File:</label> + <input type="file" id="datafile3" name="datafile3" size="40"><br> + <input type="submit"> + </form> + </body> +</html> |