diff options
author | ritchie <ritchie@gmx.at> | 2015-09-26 09:35:51 +0200 |
---|---|---|
committer | ritchie <ritchie@gmx.at> | 2015-09-26 09:35:51 +0200 |
commit | 6a95983865c086f44853880e0454eff813bb8b45 (patch) | |
tree | ffcfaeaa4264dc11b517ae1e292c2f7af8c575b5 | |
parent | 34ea556fbb94657ff8be493b937f5e5e75ed0f28 (diff) | |
download | nanohttpd-6a95983865c086f44853880e0454eff813bb8b45.tar.gz |
integrates the nice features of #218 in #214
-rw-r--r-- | nanolets/.settings/org.eclipse.core.resources.prefs | 1 | ||||
-rw-r--r-- | nanolets/src/main/java/fi/iki/elonen/router/RouterNanoHTTPD.java | 358 | ||||
-rw-r--r-- | nanolets/src/test/java/fi/iki/elonen/router/AppNanolets.java | 2 | ||||
-rw-r--r-- | nanolets/src/test/java/fi/iki/elonen/router/TestNanolets.java | 91 | ||||
-rw-r--r-- | nanolets/src/test/resources/blabla.html | 1 | ||||
-rw-r--r-- | nanolets/src/test/resources/dir/blabla.html | 1 | ||||
-rw-r--r-- | nanolets/src/test/resources/dir/index.htm | 1 | ||||
-rw-r--r-- | nanolets/src/test/resources/dir/nanohttpd_logo.png | bin | 0 -> 19931 bytes |
8 files changed, 302 insertions, 153 deletions
diff --git a/nanolets/.settings/org.eclipse.core.resources.prefs b/nanolets/.settings/org.eclipse.core.resources.prefs index 839d647..29abf99 100644 --- a/nanolets/.settings/org.eclipse.core.resources.prefs +++ b/nanolets/.settings/org.eclipse.core.resources.prefs @@ -2,4 +2,5 @@ eclipse.preferences.version=1 encoding//src/main/java=UTF-8 encoding//src/main/resources=UTF-8 encoding//src/test/java=UTF-8 +encoding//src/test/resources=UTF-8 encoding/<project>=UTF-8 diff --git a/nanolets/src/main/java/fi/iki/elonen/router/RouterNanoHTTPD.java b/nanolets/src/main/java/fi/iki/elonen/router/RouterNanoHTTPD.java index 82dca8b..06c3778 100644 --- a/nanolets/src/main/java/fi/iki/elonen/router/RouterNanoHTTPD.java +++ b/nanolets/src/main/java/fi/iki/elonen/router/RouterNanoHTTPD.java @@ -33,14 +33,22 @@ package fi.iki.elonen.router; * #L% */ +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import fi.iki.elonen.NanoHTTPD; import fi.iki.elonen.NanoHTTPD.Response.IStatus; @@ -167,6 +175,120 @@ public class RouterNanoHTTPD extends NanoHTTPD { } /** + * General nanolet to print debug info's as a html page. + */ + public static class StaticPageHandler extends DefaultHandler { + + /** + * Hashtable mapping (String)FILENAME_EXTENSION -> (String)MIME_TYPE + */ + @SuppressWarnings("serial") + 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("svg", "image/svg+xml"); + 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"); + put("m3u8", "application/vnd.apple.mpegurl"); + put("ts", " video/mp2t"); + } + }; + + // 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 ? "application/octet-stream" : mime; + } + + private static String[] getPathArray(String uri) { + String array[] = uri.split("/"); + ArrayList<String> pathArray = new ArrayList<String>(); + + for (String s : array) { + if (s.length() > 0) + pathArray.add(s); + } + + return pathArray.toArray(new String[]{}); + + } + + @Override + public String getText() { + throw new IllegalStateException("this method should not be called"); + } + + @Override + public String getMimeType() { + throw new IllegalStateException("this method should not be called"); + } + + @Override + public IStatus getStatus() { + return Status.OK; + } + + public Response get(UriResource uriResource, Map<String, String> urlParams, IHTTPSession session) { + String baseUri = uriResource.getUri(); + String realUri = normalizeUri(session.getUri()); + for (int index = 0; index < Math.min(baseUri.length(), realUri.length()); index++) { + if (baseUri.charAt(index) != realUri.charAt(index)) { + realUri = normalizeUri(realUri.substring(index)); + break; + } + } + File fileOrdirectory = uriResource.initParameter(File.class); + for (String pathPart : getPathArray(realUri)) { + fileOrdirectory = new File(fileOrdirectory, pathPart); + } + if (fileOrdirectory.isDirectory()) { + fileOrdirectory = new File(fileOrdirectory, "index.html"); + if (!fileOrdirectory.exists()) { + fileOrdirectory = new File(fileOrdirectory.getParentFile(), "index.htm"); + } + } + if (!fileOrdirectory.exists() || !fileOrdirectory.isFile()) { + return new Error404UriHandler().get(uriResource, urlParams, session); + } else { + try { + FileInputStream ins = new FileInputStream(fileOrdirectory); + return NanoHTTPD.newChunkedResponse(NanoHTTPD.Response.Status.OK, getMimeTypeForFile(fileOrdirectory.getName()), new BufferedInputStream(ins)); + } catch (IOException ioe) { + return NanoHTTPD.newFixedLengthResponse(NanoHTTPD.Response.Status.REQUEST_TIMEOUT, getMimeType(), null); + } + } + } + } + + /** * Handling error 404 - unrecognized urls */ public static class Error404UriHandler extends DefaultHandler { @@ -238,72 +360,56 @@ public class RouterNanoHTTPD extends NanoHTTPD { } - public static class UriPart { - - private String name; - - private boolean isParam; - - public UriPart(String name, boolean isParam) { - this.name = name; - this.isParam = isParam; - } - - @Override - public String toString() { - return new StringBuilder("UriPart{name='").append(name)// - .append("\', isParam=").append(isParam)// - .append('}').toString(); - } + public static class UriResource { - public boolean isParam() { - return isParam; - } + private static final Pattern PARAM_PATTERN = Pattern.compile("(?<=(^|/)):[a-zA-Z0-9_-]+(?=(/|$))"); - public String getName() { - return name; - } + private static final String PARAM_MATCHER = "([A-Za-z0-9\\-\\._~:/?#\\[\\]@!\\$&'\\(\\)\\*\\+,;=]+)"; - } + private static final Map<String, String> EMPTY = Collections.unmodifiableMap(new HashMap<String, String>()); - public static class UriResource { + private final String uri; - private boolean hasParameters; + private final Pattern uriPattern; - private int uriParamsCount; + private final int priority; - private String uri; + private final Class<?> handler; - private List<UriPart> uriParts; + private final Object[] initParameter; - private Class<?> handler; + private List<String> uriParams = new ArrayList<String>(); - public UriResource(String uri, Class<?> handler) { - this.hasParameters = false; + public UriResource(String uri, int priority, Class<?> handler, Object... initParameter) { this.handler = handler; - uriParamsCount = 0; + this.initParameter = initParameter; if (uri != null) { this.uri = normalizeUri(uri); parse(); + this.uriPattern = createUriPattern(); + } else { + this.uriPattern = null; + this.uri = null; } + this.priority = priority + uriParams.size() * 1000; } private void parse() { - String[] parts = uri.split("/"); - uriParts = new ArrayList<UriPart>(); - for (String part : parts) { - boolean isParam = part.startsWith(":"); - UriPart uriPart = null; - if (isParam) { - hasParameters = true; - uriParamsCount++; - uriPart = new UriPart(part.substring(1), true); - } else { - uriPart = new UriPart(part, false); - } - uriParts.add(uriPart); - } + } + private Pattern createUriPattern() { + String patternUri = uri; + Matcher matcher = PARAM_PATTERN.matcher(patternUri); + int start = 0; + while (matcher.find(start)) { + uriParams.add(patternUri.substring(matcher.start() + 1, matcher.end())); + patternUri = new StringBuilder(patternUri.substring(0, matcher.start()))// + .append(PARAM_MATCHER)// + .append(patternUri.substring(matcher.end())).toString(); + start = matcher.start() + PARAM_MATCHER.length(); + matcher = PARAM_PATTERN.matcher(patternUri); + } + return Pattern.compile(patternUri); } public Response process(Map<String, String> urlParams, IHTTPSession session) { @@ -343,28 +449,42 @@ public class RouterNanoHTTPD extends NanoHTTPD { @Override public String toString() { - return new StringBuilder("UrlResource{hasParameters=").append(hasParameters)// - .append(", uriParamsCount=").append(uriParamsCount)// - .append(", uri='").append((uri == null ? "/" : uri))// - .append("', urlParts=").append(uriParts)// + return new StringBuilder("UrlResource{uri='").append((uri == null ? "/" : uri))// + .append("', urlParts=").append(uriParams)// .append('}')// .toString(); } - public boolean hasParameters() { - return hasParameters; - } - public String getUri() { return uri; } - public List<UriPart> getUriParts() { - return uriParts; + public <T> T initParameter(Class<T> paramClazz) { + return initParameter(0, paramClazz); + } + + public <T> T initParameter(int parameterIndex, Class<T> paramClazz) { + if (initParameter.length > parameterIndex) { + return paramClazz.cast(initParameter[parameterIndex]); + } + LOG.severe("init parameter index not available " + parameterIndex); + return null; } - public int getUriParamsCount() { - return uriParamsCount; + public Map<String, String> match(String url) { + Matcher matcher = uriPattern.matcher(url); + if (matcher.matches()) { + if (uriParams.size() > 0) { + Map<String, String> result = new HashMap<String, String>(); + for (int i = 1; i <= matcher.groupCount(); i++) { + result.put(uriParams.get(i - 1), matcher.group(i)); + } + return result; + } else { + return EMPTY; + } + } + return null; } } @@ -391,78 +511,42 @@ public class RouterNanoHTTPD extends NanoHTTPD { * @param url * @return */ - public UriResource matchUrl(String url) { - String work = normalizeUri(url); - String[] parts = work.split("/"); - List<UriResource> resultList = new ArrayList<UriResource>(); + public Response process(IHTTPSession session) { + String work = normalizeUri(session.getUri()); + Map<String, String> params = null; + UriResource uriResource = error404Url; for (UriResource u : mappings) { - // Check if count of parts is the same - if (parts.length != u.getUriParts().size()) { - continue; // different - } - List<UriPart> uriParts = u.getUriParts(); - boolean match = true; - for (int i = 0; i < parts.length; i++) { - String currentPart = parts[i]; - UriPart uriPart = uriParts.get(i); - boolean goOn = false; - if (currentPart.equals(uriPart.getName())) { - // exact match - goOn = true; - } else { - // not match - if (uriPart.isParam()) { - goOn = true; - } else { - match = false; - goOn = false; - } - } - if (!goOn) { - match = false; - break; - } - } - if (match) { - // current match - resultList.add(u); - } - } - if (!resultList.isEmpty()) { - // some results - UriResource result = null; - if (resultList.size() > 1) { - // return the rule with less parameters - int params = 1024; - for (UriResource u : resultList) { - if (!u.hasParameters()) { - result = u; - break; - } else { - if (u.getUriParamsCount() <= params) { - result = u; - } - } - } - return result; - } else { - return resultList.get(0); + params = u.match(work); + if (params != null) { + uriResource = u; + break; } } - return error404Url; + return uriResource.process(params, session); } - public void addRoute(String url, Class<?> handler) { + private void addRoute(String url, int priority, Class<?> handler, Object... initParameter) { if (url != null) { if (handler != null) { - mappings.add(new UriResource(url, handler)); + mappings.add(new UriResource(url, priority + mappings.size(), handler, initParameter)); } else { - mappings.add(new UriResource(url, notImplemented)); + mappings.add(new UriResource(url, priority + mappings.size(), notImplemented)); } + sortMappings(); } } - public void removeRoute(String url) { + private void sortMappings() { + Collections.sort(mappings, new Comparator<UriResource>() { + + @Override + public int compare(UriResource o1, UriResource o2) { + return o1.priority - o2.priority; + } + }); + } + + private void removeRoute(String url) { String uriToDelete = normalizeUri(url); Iterator<UriResource> iter = mappings.iterator(); while (iter.hasNext()) { @@ -475,35 +559,13 @@ public class RouterNanoHTTPD extends NanoHTTPD { } public void setNotFoundHandler(Class<?> handler) { - error404Url = new UriResource(null, handler); + error404Url = new UriResource(null, 100, handler); } public void setNotImplemented(Class<?> handler) { notImplemented = handler; } - /** - * Extract parameters and their values for the given route - * - * @param route - * @param uri - * @return - */ - public Map<String, String> getUrlParams(UriResource route, String uri) { - Map<String, String> result = new HashMap<String, String>(); - if (route.getUri() == null) { - return result; - } - String workUri = normalizeUri(uri); - String[] parts = workUri.split("/"); - for (int i = 0; i < parts.length; i++) { - UriPart currentPart = route.getUriParts().get(i); - if (currentPart.isParam()) { - result.put(currentPart.getName(), parts[i]); - } - } - return result; - } } private UriRouter router; @@ -524,12 +586,12 @@ public class RouterNanoHTTPD extends NanoHTTPD { public void addMappings() { router.setNotImplemented(NotImplementedHandler.class); router.setNotFoundHandler(Error404UriHandler.class); - router.addRoute("/", IndexHandler.class); - router.addRoute("/index.html", IndexHandler.class); + router.addRoute("/", Integer.MAX_VALUE / 2, IndexHandler.class); + router.addRoute("/index.html", Integer.MAX_VALUE / 2, IndexHandler.class); } - public void addRoute(String url, Class<?> handler) { - router.addRoute(url, handler); + public void addRoute(String url, Class<?> handler, Object... initParameter) { + router.addRoute(url, 100, handler, initParameter); } public void removeRoute(String url) { @@ -539,8 +601,6 @@ public class RouterNanoHTTPD extends NanoHTTPD { @Override public Response serve(IHTTPSession session) { // Try to find match - UriResource uriResource = router.matchUrl(session.getUri()); - // Process the uri - return uriResource.process(router.getUrlParams(uriResource, session.getUri()), session); + return router.process(session); } } diff --git a/nanolets/src/test/java/fi/iki/elonen/router/AppNanolets.java b/nanolets/src/test/java/fi/iki/elonen/router/AppNanolets.java index b2ae4ed..ad01303 100644 --- a/nanolets/src/test/java/fi/iki/elonen/router/AppNanolets.java +++ b/nanolets/src/test/java/fi/iki/elonen/router/AppNanolets.java @@ -40,6 +40,7 @@ package fi.iki.elonen.router; */ import java.io.ByteArrayInputStream; +import java.io.File; import java.io.IOException; import java.io.InputStream; import java.util.Map; @@ -145,6 +146,7 @@ public class AppNanolets extends RouterNanoHTTPD { addRoute("/toBeDeleted", String.class); removeRoute("/toBeDeleted"); addRoute("/stream", StreamUrl.class); + addRoute("/browse/(.)+", StaticPageHandler.class, new File("src/test/resources").getAbsoluteFile()); } /** diff --git a/nanolets/src/test/java/fi/iki/elonen/router/TestNanolets.java b/nanolets/src/test/java/fi/iki/elonen/router/TestNanolets.java index bd360da..62d4f1b 100644 --- a/nanolets/src/test/java/fi/iki/elonen/router/TestNanolets.java +++ b/nanolets/src/test/java/fi/iki/elonen/router/TestNanolets.java @@ -38,12 +38,13 @@ import java.io.IOException; import java.io.InputStream; import java.io.PipedInputStream; import java.io.PipedOutputStream; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.apache.http.HttpEntity; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpDelete; import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.methods.HttpHead; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPut; import org.apache.http.client.methods.HttpTrace; @@ -77,7 +78,28 @@ public class TestNanolets { }); serverStartThread.start(); // give the server some tine to start. - Thread.sleep(100); + Thread.sleep(200); + } + + public static void main(String[] args) { + { + String uri = "def"; + Pattern.compile("([A-Za-z0-9\\-\\._~:/?#\\[\\]@!\\$&'\\(\\)\\*\\+,;=]+)"); + Pattern URI_PATTERN = Pattern.compile("([A-Za-z0-9\\-\\._~:/?#\\[\\]@!\\$&'\\(\\)\\*\\+,;=]+)"); + System.out.println(URI_PATTERN.matcher(uri).matches()); + } + + String uri = "photos/abc/def"; + Pattern URI_PATTERN = Pattern.compile("photos/([A-Za-z0-9\\-\\._~:/?#\\[\\]@!\\$&'\\(\\)\\*\\+,;=]+)/([A-Za-z0-9\\-\\._~:/?#\\[\\]@!\\$&'\\(\\)\\*\\+,;=]+)"); + Matcher matcher = URI_PATTERN.matcher(uri); + System.out.println("--------------->" + "/" + uri); + while (matcher.matches()) { + + System.out.println(matcher.group()); + } + // for (int index = 0; index < matcher.groupCount(); index++) { + // System.out.println(matcher.group()); + // } } @Test @@ -187,6 +209,28 @@ public class TestNanolets { new RouterNanoHTTPD.GeneralHandler().getText(); } + @Test(expected = IllegalStateException.class) + public void illegalMethod3() throws Exception { + new RouterNanoHTTPD.StaticPageHandler().getText(); + } + + @Test(expected = IllegalStateException.class) + public void illegalMethod4() throws Exception { + new RouterNanoHTTPD.StaticPageHandler().getMimeType(); + } + + @Test(expected = ClassCastException.class) + public void checkIniParameter1() throws Exception { + new RouterNanoHTTPD.UriResource("browse", 100, null, "init").initParameter(String.class); + new RouterNanoHTTPD.UriResource("browse", 100, null, "init").initParameter(Integer.class); + } + + @Test + public void checkIniParameter2() throws Exception { + Assert.assertEquals("init", new RouterNanoHTTPD.UriResource("browse", 100, null, "init").initParameter(String.class)); + Assert.assertNull(new RouterNanoHTTPD.UriResource("browse", 100, null).initParameter(String.class)); + } + @Test public void doGeneralParams() throws Exception { CloseableHttpClient httpclient = HttpClients.createDefault(); @@ -227,8 +271,8 @@ public class TestNanolets { @Test public void uriToString() throws Exception { Assert.assertEquals(// - "UrlResource{hasParameters=true, uriParamsCount=2, uri='photos/:customer_id/:photo_id', urlParts=[UriPart{name='photos', isParam=false}, UriPart{name='customer_id', isParam=true}, UriPart{name='photo_id', isParam=true}]}",// - new UriResource("/photos/:customer_id/:photo_id", GeneralHandler.class).toString()); + "UrlResource{uri='photos/:customer_id/:photo_id', urlParts=[customer_id, photo_id]}",// + new UriResource("/photos/:customer_id/:photo_id", 100, GeneralHandler.class).toString()); } @Test @@ -273,6 +317,45 @@ public class TestNanolets { return bytes; } + @Test + public void staticFiles() throws Exception { + CloseableHttpClient httpclient = HttpClients.createDefault(); + + HttpTrace httphead = new HttpTrace("http://localhost:9090/browse/blabla.html"); + CloseableHttpResponse response = httpclient.execute(httphead); + HttpEntity entity = response.getEntity(); + String string = new String(readContents(entity), "UTF-8"); + Assert.assertEquals("<html><body><h3>just a page</h3></body></html>", string); + response.close(); + + httphead = new HttpTrace("http://localhost:9090/browse/dir/blabla.html"); + response = httpclient.execute(httphead); + entity = response.getEntity(); + string = new String(readContents(entity), "UTF-8"); + Assert.assertEquals("<html><body><h3>just an other page</h3></body></html>", string); + response.close(); + + httphead = new HttpTrace("http://localhost:9090/browse/dir/nanohttpd_logo.png"); + response = httpclient.execute(httphead); + entity = response.getEntity(); + Assert.assertEquals("image/png", entity.getContentType().getValue()); + response.close(); + + httphead = new HttpTrace("http://localhost:9090/browse/dir/xxx.html"); + response = httpclient.execute(httphead); + entity = response.getEntity(); + string = new String(readContents(entity), "UTF-8"); + Assert.assertEquals("<html><body><h3>Error 404: the requested page doesn't exist.</h3></body></html>", string); + response.close(); + + httphead = new HttpTrace("http://localhost:9090/browse/dir/"); + response = httpclient.execute(httphead); + entity = response.getEntity(); + string = new String(readContents(entity), "UTF-8"); + Assert.assertEquals("<html><body><h3>just an index page</h3></body></html>", string); + response.close(); + } + @AfterClass public static void tearDown() throws Exception { stdIn.write("\n\n".getBytes()); diff --git a/nanolets/src/test/resources/blabla.html b/nanolets/src/test/resources/blabla.html new file mode 100644 index 0000000..f346c87 --- /dev/null +++ b/nanolets/src/test/resources/blabla.html @@ -0,0 +1 @@ +<html><body><h3>just a page</h3></body></html>
\ No newline at end of file diff --git a/nanolets/src/test/resources/dir/blabla.html b/nanolets/src/test/resources/dir/blabla.html new file mode 100644 index 0000000..2fd951b --- /dev/null +++ b/nanolets/src/test/resources/dir/blabla.html @@ -0,0 +1 @@ +<html><body><h3>just an other page</h3></body></html>
\ No newline at end of file diff --git a/nanolets/src/test/resources/dir/index.htm b/nanolets/src/test/resources/dir/index.htm new file mode 100644 index 0000000..f410e3a --- /dev/null +++ b/nanolets/src/test/resources/dir/index.htm @@ -0,0 +1 @@ +<html><body><h3>just an index page</h3></body></html>
\ No newline at end of file diff --git a/nanolets/src/test/resources/dir/nanohttpd_logo.png b/nanolets/src/test/resources/dir/nanohttpd_logo.png Binary files differnew file mode 100644 index 0000000..0eff47c --- /dev/null +++ b/nanolets/src/test/resources/dir/nanohttpd_logo.png |