aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorritchie <ritchie@gmx.at>2015-09-26 09:35:51 +0200
committerritchie <ritchie@gmx.at>2015-09-26 09:35:51 +0200
commit6a95983865c086f44853880e0454eff813bb8b45 (patch)
treeffcfaeaa4264dc11b517ae1e292c2f7af8c575b5
parent34ea556fbb94657ff8be493b937f5e5e75ed0f28 (diff)
downloadnanohttpd-6a95983865c086f44853880e0454eff813bb8b45.tar.gz
integrates the nice features of #218 in #214
-rw-r--r--nanolets/.settings/org.eclipse.core.resources.prefs1
-rw-r--r--nanolets/src/main/java/fi/iki/elonen/router/RouterNanoHTTPD.java358
-rw-r--r--nanolets/src/test/java/fi/iki/elonen/router/AppNanolets.java2
-rw-r--r--nanolets/src/test/java/fi/iki/elonen/router/TestNanolets.java91
-rw-r--r--nanolets/src/test/resources/blabla.html1
-rw-r--r--nanolets/src/test/resources/dir/blabla.html1
-rw-r--r--nanolets/src/test/resources/dir/index.htm1
-rw-r--r--nanolets/src/test/resources/dir/nanohttpd_logo.pngbin0 -> 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
new file mode 100644
index 0000000..0eff47c
--- /dev/null
+++ b/nanolets/src/test/resources/dir/nanohttpd_logo.png
Binary files differ