aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/src/main/java/fi/iki/elonen/NanoHTTPD.java8
-rw-r--r--core/src/main/java/fi/iki/elonen/util/ServerRunner.java (renamed from webserver/src/main/java/fi/iki/elonen/ServerRunner.java)4
-rw-r--r--core/src/test/java/fi/iki/elonen/HttpDeleteRequestTest.java15
-rw-r--r--core/src/test/java/fi/iki/elonen/HttpGetRequestTest.java3
-rw-r--r--core/src/test/java/fi/iki/elonen/HttpHeadRequestTest.java3
-rw-r--r--core/src/test/java/fi/iki/elonen/integration/GZipIntegrationTest.java16
-rw-r--r--nanolets/.settings/org.eclipse.core.resources.prefs5
-rw-r--r--nanolets/.settings/org.eclipse.jdt.core.prefs5
-rw-r--r--nanolets/.settings/org.eclipse.m2e.core.prefs4
-rw-r--r--nanolets/pom.xml19
-rw-r--r--nanolets/src/main/java/fi/iki/elonen/router/RouterNanoHTTPD.java545
-rw-r--r--nanolets/src/test/java/fi/iki/elonen/router/AppNanolets.java130
-rw-r--r--pom.xml1
-rw-r--r--samples/src/main/java/fi/iki/elonen/HelloServer.java2
-rw-r--r--samples/src/main/java/fi/iki/elonen/TempFilesServer.java1
-rw-r--r--samples/src/main/java/fi/iki/elonen/debug/DebugServer.java2
-rw-r--r--webserver/src/main/java/fi/iki/elonen/SimpleWebServer.java6
17 files changed, 738 insertions, 31 deletions
diff --git a/core/src/main/java/fi/iki/elonen/NanoHTTPD.java b/core/src/main/java/fi/iki/elonen/NanoHTTPD.java
index 7992650..d75c762 100644
--- a/core/src/main/java/fi/iki/elonen/NanoHTTPD.java
+++ b/core/src/main/java/fi/iki/elonen/NanoHTTPD.java
@@ -1801,21 +1801,21 @@ public abstract class NanoHTTPD {
/**
* Create a response with unknown length (using HTTP 1.1 chunking).
*/
- public Response newChunkedResponse(IStatus status, String mimeType, InputStream data) {
+ public static Response newChunkedResponse(IStatus status, String mimeType, InputStream data) {
return new Response(status, mimeType, data, -1);
}
/**
* Create a response with known length.
*/
- public Response newFixedLengthResponse(IStatus status, String mimeType, InputStream data, long totalBytes) {
+ public static Response newFixedLengthResponse(IStatus status, String mimeType, InputStream data, long totalBytes) {
return new Response(status, mimeType, data, totalBytes);
}
/**
* Create a text response with known length.
*/
- public Response newFixedLengthResponse(IStatus status, String mimeType, String txt) {
+ public static Response newFixedLengthResponse(IStatus status, String mimeType, String txt) {
if (txt == null) {
return newFixedLengthResponse(status, mimeType, new ByteArrayInputStream(new byte[0]), 0);
} else {
@@ -1833,7 +1833,7 @@ public abstract class NanoHTTPD {
/**
* Create a text response with known length.
*/
- public Response newFixedLengthResponse(String msg) {
+ public static Response newFixedLengthResponse(String msg) {
return newFixedLengthResponse(Status.OK, NanoHTTPD.MIME_HTML, msg);
}
diff --git a/webserver/src/main/java/fi/iki/elonen/ServerRunner.java b/core/src/main/java/fi/iki/elonen/util/ServerRunner.java
index d08eb01..c8d22d4 100644
--- a/webserver/src/main/java/fi/iki/elonen/ServerRunner.java
+++ b/core/src/main/java/fi/iki/elonen/util/ServerRunner.java
@@ -1,4 +1,4 @@
-package fi.iki.elonen;
+package fi.iki.elonen.util;
/*
* #%L
@@ -37,6 +37,8 @@ import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
+import fi.iki.elonen.NanoHTTPD;
+
public class ServerRunner {
/**
diff --git a/core/src/test/java/fi/iki/elonen/HttpDeleteRequestTest.java b/core/src/test/java/fi/iki/elonen/HttpDeleteRequestTest.java
index 87b3782..8ce49e1 100644
--- a/core/src/test/java/fi/iki/elonen/HttpDeleteRequestTest.java
+++ b/core/src/test/java/fi/iki/elonen/HttpDeleteRequestTest.java
@@ -42,8 +42,7 @@ public class HttpDeleteRequestTest extends HttpServerTest {
@Test
public void testDeleteRequestThatDoesntSendBackResponseBody_EmptyString() throws Exception {
- this.testServer.response = new NanoHTTPD(0) {
- }.newFixedLengthResponse(NanoHTTPD.Response.Status.NO_CONTENT, NanoHTTPD.MIME_HTML, "");
+ this.testServer.response = NanoHTTPD.newFixedLengthResponse(NanoHTTPD.Response.Status.NO_CONTENT, NanoHTTPD.MIME_HTML, "");
ByteArrayOutputStream outputStream = invokeServer("DELETE " + HttpServerTest.URI + " HTTP/1.1");
@@ -61,8 +60,7 @@ public class HttpDeleteRequestTest extends HttpServerTest {
@Test
public void testDeleteRequestThatDoesntSendBackResponseBody_NullInputStream() throws Exception {
- this.testServer.response = new NanoHTTPD(0) {
- }.newChunkedResponse(NanoHTTPD.Response.Status.NO_CONTENT, NanoHTTPD.MIME_HTML, (InputStream) null);
+ this.testServer.response = NanoHTTPD.newChunkedResponse(NanoHTTPD.Response.Status.NO_CONTENT, NanoHTTPD.MIME_HTML, (InputStream) null);
ByteArrayOutputStream outputStream = invokeServer("DELETE " + HttpServerTest.URI + " HTTP/1.1");
@@ -80,8 +78,7 @@ public class HttpDeleteRequestTest extends HttpServerTest {
@Test
public void testDeleteRequestThatDoesntSendBackResponseBody_NullString() throws Exception {
- this.testServer.response = new NanoHTTPD(0) {
- }.newFixedLengthResponse(NanoHTTPD.Response.Status.NO_CONTENT, NanoHTTPD.MIME_HTML, (String) null);
+ this.testServer.response = NanoHTTPD.newFixedLengthResponse(NanoHTTPD.Response.Status.NO_CONTENT, NanoHTTPD.MIME_HTML, (String) null);
ByteArrayOutputStream outputStream = invokeServer("DELETE " + HttpServerTest.URI + " HTTP/1.1");
@@ -99,8 +96,7 @@ public class HttpDeleteRequestTest extends HttpServerTest {
@Test
public void testDeleteRequestThatSendsBackResponseBody_Accepted() throws Exception {
- this.testServer.response = new NanoHTTPD(0) {
- }.newFixedLengthResponse(NanoHTTPD.Response.Status.ACCEPTED, "application/xml", "<body />");
+ this.testServer.response = NanoHTTPD.newFixedLengthResponse(NanoHTTPD.Response.Status.ACCEPTED, "application/xml", "<body />");
ByteArrayOutputStream outputStream = invokeServer("DELETE " + HttpServerTest.URI + " HTTP/1.1");
@@ -119,8 +115,7 @@ public class HttpDeleteRequestTest extends HttpServerTest {
@Test
public void testDeleteRequestThatSendsBackResponseBody_Success() throws Exception {
- this.testServer.response = new NanoHTTPD(0) {
- }.newFixedLengthResponse(NanoHTTPD.Response.Status.OK, "application/xml", "<body />");
+ this.testServer.response = NanoHTTPD.newFixedLengthResponse(NanoHTTPD.Response.Status.OK, "application/xml", "<body />");
ByteArrayOutputStream outputStream = invokeServer("DELETE " + HttpServerTest.URI + " HTTP/1.1");
diff --git a/core/src/test/java/fi/iki/elonen/HttpGetRequestTest.java b/core/src/test/java/fi/iki/elonen/HttpGetRequestTest.java
index de1285f..e1a6b8b 100644
--- a/core/src/test/java/fi/iki/elonen/HttpGetRequestTest.java
+++ b/core/src/test/java/fi/iki/elonen/HttpGetRequestTest.java
@@ -168,8 +168,7 @@ public class HttpGetRequestTest extends HttpServerTest {
@Test
public void testOutputOfServeSentBackToClient() throws Exception {
String responseBody = "Success!";
- this.testServer.response = new NanoHTTPD(0) {
- }.newFixedLengthResponse(responseBody);
+ this.testServer.response = NanoHTTPD.newFixedLengthResponse(responseBody);
ByteArrayOutputStream outputStream = invokeServer("GET " + HttpServerTest.URI + " HTTP/1.1");
String[] expected = {
diff --git a/core/src/test/java/fi/iki/elonen/HttpHeadRequestTest.java b/core/src/test/java/fi/iki/elonen/HttpHeadRequestTest.java
index 8baf22d..4eb3147 100644
--- a/core/src/test/java/fi/iki/elonen/HttpHeadRequestTest.java
+++ b/core/src/test/java/fi/iki/elonen/HttpHeadRequestTest.java
@@ -48,8 +48,7 @@ public class HttpHeadRequestTest extends HttpServerTest {
public void setUp() {
super.setUp();
String responseBody = "Success!";
- this.testServer.response = new NanoHTTPD(0) {
- }.newFixedLengthResponse(responseBody);
+ this.testServer.response = NanoHTTPD.newFixedLengthResponse(responseBody);
}
@Test
diff --git a/core/src/test/java/fi/iki/elonen/integration/GZipIntegrationTest.java b/core/src/test/java/fi/iki/elonen/integration/GZipIntegrationTest.java
index 4b95481..a278406 100644
--- a/core/src/test/java/fi/iki/elonen/integration/GZipIntegrationTest.java
+++ b/core/src/test/java/fi/iki/elonen/integration/GZipIntegrationTest.java
@@ -81,7 +81,7 @@ public class GZipIntegrationTest extends IntegrationTestBase<GZipIntegrationTest
@Test
public void contentEncodingShouldBeAddedToFixedLengthResponses() throws IOException {
- testServer.response = testServer.newFixedLengthResponse("This is a test");
+ testServer.response = NanoHTTPD.newFixedLengthResponse("This is a test");
HttpGet request = new HttpGet("http://localhost:8192/");
request.addHeader("Accept-encoding", "gzip");
HttpResponse response = httpclient.execute(request);
@@ -93,7 +93,7 @@ public class GZipIntegrationTest extends IntegrationTestBase<GZipIntegrationTest
@Test
public void contentEncodingShouldBeAddedToChunkedResponses() throws IOException {
InputStream data = new ByteArrayInputStream("This is a test".getBytes("UTF-8"));
- testServer.response = testServer.newChunkedResponse(NanoHTTPD.Response.Status.OK, "text/plain", data);
+ testServer.response = NanoHTTPD.newChunkedResponse(NanoHTTPD.Response.Status.OK, "text/plain", data);
HttpGet request = new HttpGet("http://localhost:8192/");
request.addHeader("Accept-encoding", "gzip");
HttpResponse response = httpclient.execute(request);
@@ -104,7 +104,7 @@ public class GZipIntegrationTest extends IntegrationTestBase<GZipIntegrationTest
@Test
public void shouldFindCorrectAcceptEncodingAmongMany() throws IOException {
- testServer.response = testServer.newFixedLengthResponse("This is a test");
+ testServer.response = NanoHTTPD.newFixedLengthResponse("This is a test");
HttpGet request = new HttpGet("http://localhost:8192/");
request.addHeader("Accept-encoding", "deflate,gzip");
HttpResponse response = httpclient.execute(request);
@@ -115,7 +115,7 @@ public class GZipIntegrationTest extends IntegrationTestBase<GZipIntegrationTest
@Test
public void contentLengthShouldBeRemovedFromZippedResponses() throws IOException {
- testServer.response = testServer.newFixedLengthResponse("This is a test");
+ testServer.response = NanoHTTPD.newFixedLengthResponse("This is a test");
HttpGet request = new HttpGet("http://localhost:8192/");
request.addHeader("Accept-encoding", "gzip");
HttpResponse response = httpclient.execute(request);
@@ -125,7 +125,7 @@ public class GZipIntegrationTest extends IntegrationTestBase<GZipIntegrationTest
@Test
public void fixedLengthContentIsEncodedProperly() throws IOException {
- testServer.response = testServer.newFixedLengthResponse("This is a test");
+ testServer.response = NanoHTTPD.newFixedLengthResponse("This is a test");
HttpGet request = new HttpGet("http://localhost:8192/");
request.addHeader("Accept-encoding", "gzip");
HttpResponse response = new DecompressingHttpClient(httpclient).execute(request);
@@ -135,7 +135,7 @@ public class GZipIntegrationTest extends IntegrationTestBase<GZipIntegrationTest
@Test
public void chunkedContentIsEncodedProperly() throws IOException {
InputStream data = new ByteArrayInputStream("This is a test".getBytes("UTF-8"));
- testServer.response = testServer.newChunkedResponse(NanoHTTPD.Response.Status.OK, "text/plain", data);
+ testServer.response = NanoHTTPD.newChunkedResponse(NanoHTTPD.Response.Status.OK, "text/plain", data);
HttpGet request = new HttpGet("http://localhost:8192/");
request.addHeader("Accept-encoding", "gzip");
HttpResponse response = new DecompressingHttpClient(httpclient).execute(request);
@@ -144,7 +144,7 @@ public class GZipIntegrationTest extends IntegrationTestBase<GZipIntegrationTest
@Test
public void noGzipWithoutAcceptEncoding() throws IOException {
- testServer.response = testServer.newFixedLengthResponse("This is a test");
+ testServer.response = NanoHTTPD.newFixedLengthResponse("This is a test");
HttpGet request = new HttpGet("http://localhost:8192/");
HttpResponse response = httpclient.execute(request);
Header contentEncoding = response.getFirstHeader("content-encoding");
@@ -154,7 +154,7 @@ public class GZipIntegrationTest extends IntegrationTestBase<GZipIntegrationTest
@Test
public void contentShouldNotBeGzippedIfContentLengthIsAddedManually() throws IOException {
- testServer.response = testServer.newFixedLengthResponse("This is a test");
+ testServer.response = NanoHTTPD.newFixedLengthResponse("This is a test");
testServer.response.addHeader("Content-Length", "" + ("This is a test".getBytes("UTF-8").length));
HttpGet request = new HttpGet("http://localhost:8192/");
request.addHeader("Accept-encoding", "gzip");
diff --git a/nanolets/.settings/org.eclipse.core.resources.prefs b/nanolets/.settings/org.eclipse.core.resources.prefs
new file mode 100644
index 0000000..839d647
--- /dev/null
+++ b/nanolets/.settings/org.eclipse.core.resources.prefs
@@ -0,0 +1,5 @@
+eclipse.preferences.version=1
+encoding//src/main/java=UTF-8
+encoding//src/main/resources=UTF-8
+encoding//src/test/java=UTF-8
+encoding/<project>=UTF-8
diff --git a/nanolets/.settings/org.eclipse.jdt.core.prefs b/nanolets/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..60105c1
--- /dev/null
+++ b/nanolets/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,5 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
+org.eclipse.jdt.core.compiler.compliance=1.6
+org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
+org.eclipse.jdt.core.compiler.source=1.6
diff --git a/nanolets/.settings/org.eclipse.m2e.core.prefs b/nanolets/.settings/org.eclipse.m2e.core.prefs
new file mode 100644
index 0000000..f897a7f
--- /dev/null
+++ b/nanolets/.settings/org.eclipse.m2e.core.prefs
@@ -0,0 +1,4 @@
+activeProfiles=
+eclipse.preferences.version=1
+resolveWorkspaceProjects=true
+version=1
diff --git a/nanolets/pom.xml b/nanolets/pom.xml
new file mode 100644
index 0000000..6cf1c3f
--- /dev/null
+++ b/nanolets/pom.xml
@@ -0,0 +1,19 @@
+<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>
+ <parent>
+ <groupId>org.nanohttpd</groupId>
+ <artifactId>nanohttpd-project</artifactId>
+ <version>2.2.0-SNAPSHOT</version>
+ </parent>
+ <artifactId>nanohttpd-nanolets</artifactId>
+ <packaging>jar</packaging>
+ <name>NanoHttpd nano application server</name>
+ <dependencies>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>nanohttpd</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/nanolets/src/main/java/fi/iki/elonen/router/RouterNanoHTTPD.java b/nanolets/src/main/java/fi/iki/elonen/router/RouterNanoHTTPD.java
new file mode 100644
index 0000000..f347741
--- /dev/null
+++ b/nanolets/src/main/java/fi/iki/elonen/router/RouterNanoHTTPD.java
@@ -0,0 +1,545 @@
+package fi.iki.elonen.router;
+
+/*
+ * #%L
+ * NanoHttpd-Samples
+ * %%
+ * 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.InputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import fi.iki.elonen.NanoHTTPD;
+import fi.iki.elonen.NanoHTTPD.Response.IStatus;
+import fi.iki.elonen.NanoHTTPD.Response.Status;
+
+/**
+ * @author vnnv
+ * @author ritchieGitHub
+ */
+public class RouterNanoHTTPD extends NanoHTTPD {
+
+ /**
+ * logger to log to.
+ */
+ private static final Logger LOG = Logger.getLogger(RouterNanoHTTPD.class.getName());
+
+ public interface UriResponder {
+
+ public Response get(UriResource uriResource, Map<String, String> urlParams, IHTTPSession session);
+
+ public Response put(UriResource uriResource, Map<String, String> urlParams, IHTTPSession session);
+
+ public Response post(UriResource uriResource, Map<String, String> urlParams, IHTTPSession session);
+
+ public Response delete(UriResource uriResource, Map<String, String> urlParams, IHTTPSession session);
+
+ public Response other(String method, UriResource uriResource, Map<String, String> urlParams, IHTTPSession session);
+ }
+
+ /**
+ * General nanolet to inherit from if you provide stream data, only chucked
+ * responses will be generated.
+ */
+ public static abstract class DefaultStreamHandler implements UriResponder {
+
+ public abstract String getMimeType();
+
+ public abstract IStatus getStatus();
+
+ public abstract InputStream getData();
+
+ public Response get(UriResource uriResource, Map<String, String> urlParams, IHTTPSession session) {
+ return NanoHTTPD.newChunkedResponse(getStatus(), getMimeType(), getData());
+ }
+
+ public Response post(UriResource uriResource, Map<String, String> urlParams, IHTTPSession session) {
+ return get(uriResource, urlParams, session);
+ }
+
+ public Response put(UriResource uriResource, Map<String, String> urlParams, IHTTPSession session) {
+ return get(uriResource, urlParams, session);
+ }
+
+ public Response delete(UriResource uriResource, Map<String, String> urlParams, IHTTPSession session) {
+ return get(uriResource, urlParams, session);
+ }
+
+ public Response other(String method, UriResource uriResource, Map<String, String> urlParams, IHTTPSession session) {
+ return get(uriResource, urlParams, session);
+ }
+ }
+
+ /**
+ * General nanolet to inherit from if you provide text or html data, only
+ * fixed size responses will be generated.
+ */
+ public static abstract class DefaultHandler extends DefaultStreamHandler {
+
+ public abstract String getText();
+
+ public abstract IStatus getStatus();
+
+ public Response get(UriResource uriResource, Map<String, String> urlParams, IHTTPSession session) {
+ return NanoHTTPD.newFixedLengthResponse(getStatus(), getMimeType(), getText());
+ }
+
+ @Override
+ public InputStream getData() {
+ throw new IllegalStateException("this method should not be called in a text based nanolet");
+ }
+ }
+
+ /**
+ * General nanolet to print debug info's as a html page.
+ */
+ public static class GeneralHandler extends DefaultHandler {
+
+ @Override
+ public String getText() {
+ throw new IllegalStateException("this method should not be called");
+ }
+
+ @Override
+ public String getMimeType() {
+ return "text/html";
+ }
+
+ @Override
+ public IStatus getStatus() {
+ return Status.OK;
+ }
+
+ public Response get(UriResource uriResource, Map<String, String> urlParams, IHTTPSession session) {
+ StringBuilder text = new StringBuilder("<html><body>");
+ text.append("<h1>Url: ");
+ text.append(session.getUri());
+ text.append("</h1><br>");
+ Map<String, String> queryParams = session.getParms();
+ if (queryParams.size() > 0) {
+ for (Map.Entry<String, String> entry : queryParams.entrySet()) {
+ String key = entry.getKey();
+ String value = entry.getValue();
+ text.append("<p>Param '");
+ text.append(key);
+ text.append("' = ");
+ text.append(value);
+ text.append("</p>");
+ }
+ } else {
+ text.append("<p>no params in url</p><br>");
+ }
+ return NanoHTTPD.newFixedLengthResponse(getStatus(), getMimeType(), text.toString());
+ }
+ }
+
+ /**
+ * Handling error 404 - unrecognized urls
+ */
+ public static class Error404UriHandler extends DefaultHandler {
+
+ public String getText() {
+ return "<html><body><h3>Error 404: the requested page doesn't exist.</h3></body></html>";
+ }
+
+ @Override
+ public String getMimeType() {
+ return "text/html";
+ }
+
+ @Override
+ public IStatus getStatus() {
+ return Status.NOT_FOUND;
+ }
+ }
+
+ /**
+ * Handling index
+ */
+ public static class IndexHandler extends DefaultHandler {
+
+ public String getText() {
+ return "<html><body><h2>Hello world!</h3></body></html>";
+ }
+
+ @Override
+ public String getMimeType() {
+ return "text/html";
+ }
+
+ @Override
+ public IStatus getStatus() {
+ return Status.OK;
+ }
+
+ }
+
+ public static class NotImplementedHandler extends DefaultHandler {
+
+ public String getText() {
+ return "<html><body><h2>The uri is mapped in the router, but no handler is specified. <br> Status: Not implemented!</h3></body></html>";
+ }
+
+ @Override
+ public String getMimeType() {
+ return "text/html";
+ }
+
+ @Override
+ public IStatus getStatus() {
+ return Status.OK;
+ }
+ }
+
+ public static class UriUtils {
+
+ public static String normalizeUri(String value) {
+ if (value == null) {
+ return value;
+ }
+ if (value.startsWith("/")) {
+ value = value.substring(1);
+ }
+ if (value.endsWith("/")) {
+ value = value.substring(0, value.length() - 1);
+ }
+ return value;
+
+ }
+
+ }
+
+ 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 "UriPart{name='" + name + '\'' + ", isParam=" + isParam + '}';
+ }
+
+ public boolean isParam() {
+ return isParam;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ }
+
+ public static class UriResource {
+
+ private boolean hasParameters;
+
+ private int uriParamsCount;
+
+ private String uri;
+
+ private List<UriPart> uriParts;
+
+ private Class<?> handler;
+
+ public UriResource(String uri, Class<?> handler) {
+ this.hasParameters = false;
+ this.handler = handler;
+ uriParamsCount = 0;
+ if (uri != null) {
+ this.uri = UriUtils.normalizeUri(uri);
+ parse();
+ }
+ }
+
+ 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);
+ }
+
+ }
+
+ public Response process(Map<String, String> urlParams, IHTTPSession session) {
+ String error = "General error!";
+ if (handler != null) {
+ try {
+ Object object = handler.newInstance();
+ if (object instanceof UriResponder) {
+ UriResponder responder = (UriResponder) object;
+ switch (session.getMethod()) {
+ case GET:
+ return responder.get(this, urlParams, session);
+ case POST:
+ return responder.post(this, urlParams, session);
+ case PUT:
+ return responder.put(this, urlParams, session);
+ case DELETE:
+ return responder.delete(this, urlParams, session);
+ default:
+ return responder.other(session.getMethod().toString(), this, urlParams, session);
+ }
+ } else {
+ return NanoHTTPD.newFixedLengthResponse(Status.OK, "text/plain", //
+ new StringBuilder("Return: ")//
+ .append(handler.getCanonicalName())//
+ .append(".toString() -> ")//
+ .append(object)//
+ .toString());
+ }
+ } catch (InstantiationException e) {
+ error = "Error: " + e.getClass().getName() + " : " + e.getMessage();
+ LOG.log(Level.SEVERE, error, e);
+ } catch (IllegalAccessException e) {
+ error = "Error: " + e.getClass().getName() + " : " + e.getMessage();
+ LOG.log(Level.SEVERE, error, e);
+ }
+ }
+
+ return NanoHTTPD.newFixedLengthResponse(Status.INTERNAL_ERROR, "text/plain", error);
+ }
+
+ @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)//
+ .append('}')//
+ .toString();
+ }
+
+ public boolean hasParameters() {
+ return hasParameters;
+ }
+
+ public String getUri() {
+ return uri;
+ }
+
+ public List<UriPart> getUriParts() {
+ return uriParts;
+ }
+
+ public int getUriParamsCount() {
+ return uriParamsCount;
+ }
+
+ }
+
+ public static class UriRouter {
+
+ private List<UriResource> mappings;
+
+ private UriResource error404Url;
+
+ private Class<?> notImplemented;
+
+ public UriRouter() {
+ mappings = new ArrayList<UriResource>();
+ }
+
+ /**
+ * Search in the mappings if the given url matches some of the rules If
+ * there are more than one marches returns the rule with less parameters
+ * e.g. mapping 1 = /user/:id mapping 2 = /user/help if the incoming uri
+ * is www.example.com/user/help - mapping 2 is returned if the incoming
+ * uri is www.example.com/user/3232 - mapping 1 is returned
+ *
+ * @param url
+ * @return
+ */
+ public UriResource matchUrl(String url) {
+ String work = UriUtils.normalizeUri(url);
+ String[] parts = work.split("/");
+ List<UriResource> resultList = new ArrayList<UriResource>();
+ 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);
+ }
+ }
+ return error404Url;
+ }
+
+ public void addRoute(String url, Class<?> handler) {
+ if (url != null) {
+ if (handler != null) {
+ mappings.add(new UriResource(url, handler));
+ } else {
+ mappings.add(new UriResource(url, notImplemented));
+ }
+ }
+ }
+
+ public void removeRoute(String url) {
+ if (mappings.contains(url)) {
+ mappings.remove(url);
+ }
+ }
+
+ public void setNotFoundHandler(Class<?> handler) {
+ error404Url = new UriResource(null, 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 = UriUtils.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;
+
+ public RouterNanoHTTPD(int port) {
+ super(port);
+ router = new UriRouter();
+ }
+
+ /**
+ * default routings, they are over writable.
+ *
+ * <pre>
+ * router.setNotFoundHandler(GeneralHandler.class);
+ * </pre>
+ */
+
+ public void addMappings() {
+ router.setNotImplemented(NotImplementedHandler.class);
+ router.setNotFoundHandler(Error404UriHandler.class);
+ router.addRoute("/", IndexHandler.class);
+ router.addRoute("/index.html", IndexHandler.class);
+ }
+
+ public void addRoute(String url, Class<?> handler) {
+ router.addRoute(url, handler);
+ }
+
+ public void removeRoute(String url) {
+ router.removeRoute(url);
+ }
+
+ @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);
+ }
+}
diff --git a/nanolets/src/test/java/fi/iki/elonen/router/AppNanolets.java b/nanolets/src/test/java/fi/iki/elonen/router/AppNanolets.java
new file mode 100644
index 0000000..ae1e5af
--- /dev/null
+++ b/nanolets/src/test/java/fi/iki/elonen/router/AppNanolets.java
@@ -0,0 +1,130 @@
+package fi.iki.elonen.router;
+
+/*
+ * #%L
+ * NanoHttpd-Samples
+ * %%
+ * 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%
+ */
+
+/**
+ * Created by vnnv on 7/17/15.
+ * Simple httpd server based on NanoHTTPD
+ * Read the source. Everything is there.
+ */
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.util.Map;
+
+import fi.iki.elonen.NanoHTTPD;
+import fi.iki.elonen.util.ServerRunner;
+
+public class AppNanolets extends RouterNanoHTTPD {
+
+ private static final int PORT = 8080;
+
+ public static class UserHandler extends DefaultHandler {
+
+ @Override
+ public String getText() {
+ return "not implemented";
+ }
+
+ public String getText(Map<String, String> urlParams, NanoHTTPD.IHTTPSession session) {
+ String text = "<html><body>User handler. Method: " + session.getMethod().toString() + "<br>";
+ text += "<h1>Uri parameters:</h1>";
+ for (Map.Entry<String, String> entry : urlParams.entrySet()) {
+ String key = entry.getKey();
+ String value = entry.getValue();
+ text += "<div> Param: " + key + "&nbsp;Value: " + value + "</div>";
+ }
+ text += "<h1>Query parameters:</h1>";
+ for (Map.Entry<String, String> entry : session.getParms().entrySet()) {
+ String key = entry.getKey();
+ String value = entry.getValue();
+ text += "<div> Query Param: " + key + "&nbsp;Value: " + value + "</div>";
+ }
+ text += "</body></html>";
+
+ return text;
+ }
+
+ @Override
+ public String getMimeType() {
+ return "text/html";
+ }
+
+ @Override
+ public NanoHTTPD.Response.IStatus getStatus() {
+ return NanoHTTPD.Response.Status.OK;
+ }
+
+ public NanoHTTPD.Response get(UriResource uriResource, Map<String, String> urlParams, NanoHTTPD.IHTTPSession session) {
+ String text = getText(urlParams, session);
+ ByteArrayInputStream inp = new ByteArrayInputStream(text.getBytes());
+ int size = text.getBytes().length;
+ return NanoHTTPD.newFixedLengthResponse(getStatus(), getMimeType(), inp, size);
+ }
+
+ }
+
+ /**
+ * Create the server instance
+ */
+ public AppNanolets() throws IOException {
+ super(PORT);
+ addMappings();
+ System.out.println("\nRunning! Point your browers to http://localhost:" + PORT + "/ \n");
+ }
+
+ /**
+ * Add the routes Every route is an absolute path Parameters starts with ":"
+ * Handler class should implement @UriResponder interface If the handler not
+ * implement UriResponder interface - toString() is used
+ */
+ @Override
+ public void addMappings() {
+ super.addMappings();
+ addRoute("/user", UserHandler.class);
+ addRoute("/user/:id", UserHandler.class);
+ addRoute("/user/help", GeneralHandler.class);
+ addRoute("/photos/:customer_id/:photo_id", null);
+ addRoute("/test", String.class);
+ }
+
+ /**
+ * Main entry point
+ *
+ * @param args
+ */
+ public static void main(String[] args) {
+ ServerRunner.run(AppNanolets.class);
+ }
+}
diff --git a/pom.xml b/pom.xml
index 67a8dc0..d6844ef 100644
--- a/pom.xml
+++ b/pom.xml
@@ -88,6 +88,7 @@
<module>webserver</module>
<module>websocket</module>
<module>markdown-plugin</module>
+ <module>nanolets</module>
</modules>
<licenses>
<license>
diff --git a/samples/src/main/java/fi/iki/elonen/HelloServer.java b/samples/src/main/java/fi/iki/elonen/HelloServer.java
index 7226108..bc91231 100644
--- a/samples/src/main/java/fi/iki/elonen/HelloServer.java
+++ b/samples/src/main/java/fi/iki/elonen/HelloServer.java
@@ -36,6 +36,8 @@ package fi.iki.elonen;
import java.util.Map;
import java.util.logging.Logger;
+import fi.iki.elonen.util.ServerRunner;
+
/**
* An example of subclassing NanoHTTPD to make a custom HTTP server.
*/
diff --git a/samples/src/main/java/fi/iki/elonen/TempFilesServer.java b/samples/src/main/java/fi/iki/elonen/TempFilesServer.java
index dbe7d37..8429e32 100644
--- a/samples/src/main/java/fi/iki/elonen/TempFilesServer.java
+++ b/samples/src/main/java/fi/iki/elonen/TempFilesServer.java
@@ -37,6 +37,7 @@ import java.util.ArrayList;
import java.util.List;
import fi.iki.elonen.debug.DebugServer;
+import fi.iki.elonen.util.ServerRunner;
/**
* @author Paul S. Hawke (paul.hawke@gmail.com) On: 3/9/13 at 12:47 AM
diff --git a/samples/src/main/java/fi/iki/elonen/debug/DebugServer.java b/samples/src/main/java/fi/iki/elonen/debug/DebugServer.java
index 056673d..0ffc34f 100644
--- a/samples/src/main/java/fi/iki/elonen/debug/DebugServer.java
+++ b/samples/src/main/java/fi/iki/elonen/debug/DebugServer.java
@@ -38,7 +38,7 @@ import java.util.List;
import java.util.Map;
import fi.iki.elonen.NanoHTTPD;
-import fi.iki.elonen.ServerRunner;
+import fi.iki.elonen.util.ServerRunner;
public class DebugServer extends NanoHTTPD {
diff --git a/webserver/src/main/java/fi/iki/elonen/SimpleWebServer.java b/webserver/src/main/java/fi/iki/elonen/SimpleWebServer.java
index cd2e9bd..f3ad1c2 100644
--- a/webserver/src/main/java/fi/iki/elonen/SimpleWebServer.java
+++ b/webserver/src/main/java/fi/iki/elonen/SimpleWebServer.java
@@ -51,6 +51,7 @@ 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 {
@@ -386,9 +387,8 @@ public class SimpleWebServer extends NanoHTTPD {
return msg.toString();
}
- @Override
- public Response newFixedLengthResponse(IStatus status, String mimeType, String message) {
- Response response = super.newFixedLengthResponse(status, mimeType, message);
+ public static Response newFixedLengthResponse(IStatus status, String mimeType, String message) {
+ Response response = NanoHTTPD.newFixedLengthResponse(status, mimeType, message);
response.addHeader("Accept-Ranges", "bytes");
return response;
}