aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJarno Elonen <elonen@iki.fi>2015-09-13 20:49:59 +0300
committerJarno Elonen <elonen@iki.fi>2015-09-13 20:49:59 +0300
commit60b7e9249246749a16bf8bc29aa117e4201eae8b (patch)
tree35c559493a5ec0e92846c51e048f683fdd5c4057
parent9f38ca21e99986290bf65a8fec17d2592d98d744 (diff)
parentf3b51b6d9e8b24f2f2e021465b0f5972ad12892d (diff)
downloadnanohttpd-60b7e9249246749a16bf8bc29aa117e4201eae8b.tar.gz
Merge branch 'master' into static-new-xx
-rw-r--r--README.md25
-rw-r--r--core/pom.xml3
-rw-r--r--core/src/main/java/fi/iki/elonen/NanoHTTPD.java44
-rw-r--r--core/src/main/java/fi/iki/elonen/util/ServerRunner.java (renamed from webserver/src/main/java/fi/iki/elonen/ServerRunner.java)6
-rw-r--r--core/src/test/java/fi/iki/elonen/HttpHeadRequestTest.java2
-rw-r--r--core/src/test/java/fi/iki/elonen/HttpSSLServerTest.java87
-rw-r--r--core/src/test/java/fi/iki/elonen/HttpServerTest.java8
-rw-r--r--core/src/test/java/fi/iki/elonen/HttpSessionHeadersTest.java5
-rw-r--r--core/src/test/resources/keystore.jksbin0 -> 2276 bytes
-rw-r--r--fileupload/.gitignore2
-rw-r--r--fileupload/pom.xml46
-rw-r--r--fileupload/src/main/java/fi/iki/elonen/NanoFileUpload.java118
-rw-r--r--fileupload/src/test/java/fi/iki/elonen/TestNanoFileUpLoad.java251
-rw-r--r--markdown-plugin/pom.xml3
-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/LICENSE.txt26
-rw-r--r--nanolets/pom.xml28
-rw-r--r--nanolets/src/main/java/fi/iki/elonen/router/RouterNanoHTTPD.java546
-rw-r--r--nanolets/src/test/java/fi/iki/elonen/router/AppNanolets.java158
-rw-r--r--nanolets/src/test/java/fi/iki/elonen/router/TestNanolets.java283
-rw-r--r--pom.xml29
-rw-r--r--samples/pom.xml3
-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/pom.xml5
-rw-r--r--webserver/src/main/java/fi/iki/elonen/SimpleWebServer.java74
-rw-r--r--webserver/src/test/java/fi/iki/elonen/AbstractTestHttpServer.java67
-rw-r--r--webserver/src/test/java/fi/iki/elonen/TestCorsHttpServer.java154
-rw-r--r--webserver/src/test/java/fi/iki/elonen/TestCorsHttpServerWithSingleOrigin.java121
-rw-r--r--webserver/src/test/java/fi/iki/elonen/TestHttpServer.java25
-rw-r--r--websocket/pom.xml3
-rw-r--r--websocket/src/main/java/fi/iki/elonen/NanoWebSocketServer.java12
-rw-r--r--websocket/src/test/java/fi/iki/elonen/samples/echo/EchoWebSocketsTest.java12
36 files changed, 2088 insertions, 77 deletions
diff --git a/README.md b/README.md
index 80ef93c..e7835a0 100644
--- a/README.md
+++ b/README.md
@@ -121,6 +121,19 @@ NanoHTTPD project currently consist of four parts:
* File server serves also very long files without memory overhead.
* Contains a built-in list of most common MIME types.
* Runtime extension support (extensions that serve particular MIME types) - example extension that serves Markdown formatted files. Simply including an extension JAR in the webserver classpath is enough for the extension to be loaded.
+* Simple [CORS](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing) support via `--cors` paramater
+ * by default serves `Access-Control-Allow-Headers: origin,accept,content-type`
+ * possibility to set `Access-Control-Allow-Headers` by setting System property: `AccessControlAllowHeader`
+ * _example: _ `-DAccessControlAllowHeader=origin,accept,content-type,Authorization`
+ * possible values:
+ * `--cors`: activates CORS support, `Access-Control-Allow-Origin` will be set to `*`
+ * `--cors=some_value`: `Access-Control-Allow-Origin` will be set to `some_value`.
+
+**_CORS argument examples_**
+
+
+* `--cors=http://appOne.company.com`
+* `--cors="http://appOne.company.com, http://appTwo.company.com"`: note the double quotes so that the 2 URLs are considered part of a single argument.
## Maven dependencies
@@ -198,7 +211,19 @@ The latest Github master version can be fetched through sonatype.org:
</repository>
</repositories>
+### generating an self signed ssl certificate
+
+Just a hint how to generate a certificate for localhost.
+ keytool -genkey -keyalg RSA -alias selfsigned -keystore keystore.jks -storepass password -validity 360 -keysize 2048 -ext SAN=DNS:localhost,IP:127.0.0.1 -validity 9999
+
+This will generate a keystore file named 'keystore.jks' with a self signed certificate for a host named localhost with the ip adress 127.0.0.1 . Now
+you can use:
+
+ server.makeSecure(NanoHTTPD.makeSSLSocketFactory("/keystore.jks", "password".toCharArray()));
+
+Before you start the server to make Nanohttpd serve https connections, when you make sure 'keystore.jks' is in your classpath .
+
-----
*Thank you to everyone who has reported bugs and suggested fixes.*
diff --git a/core/pom.xml b/core/pom.xml
index 1ddc30d..2b65dca 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -22,4 +22,7 @@
<scope>test</scope>
</dependency>
</dependencies>
+ <properties>
+ <minimal.coverage>0.81</minimal.coverage>
+ </properties>
</project>
diff --git a/core/src/main/java/fi/iki/elonen/NanoHTTPD.java b/core/src/main/java/fi/iki/elonen/NanoHTTPD.java
index 689ef7c..da87bbb 100644
--- a/core/src/main/java/fi/iki/elonen/NanoHTTPD.java
+++ b/core/src/main/java/fi/iki/elonen/NanoHTTPD.java
@@ -33,7 +33,25 @@ package fi.iki.elonen;
* #L%
*/
-import java.io.*;
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.Closeable;
+import java.io.DataOutput;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.PushbackInputStream;
+import java.io.RandomAccessFile;
+import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
@@ -457,6 +475,10 @@ public abstract class NanoHTTPD {
protected class HTTPSession implements IHTTPSession {
+ private static final int REQUEST_BUFFER_LEN = 512;
+
+ private static final int MEMORY_STORE_LIMIT = 1024;
+
public static final int BUFSIZE = 8192;
private final TempFileManager tempFileManager;
@@ -902,8 +924,6 @@ public abstract class NanoHTTPD {
@Override
public void parseBody(Map<String, String> files) throws IOException, ResponseException {
- final int REQUEST_BUFFER_LEN = 512;
- final int MEMORY_STORE_LIMIT = 1024;
RandomAccessFile randomAccessFile = null;
try {
long size = getBodySize();
@@ -1576,17 +1596,11 @@ public abstract class NanoHTTPD {
* by the caller.
*/
public static SSLServerSocketFactory makeSSLSocketFactory(KeyStore loadedKeyStore, KeyManagerFactory loadedKeyFactory) throws IOException {
- SSLServerSocketFactory res = null;
try {
- TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
- trustManagerFactory.init(loadedKeyStore);
- SSLContext ctx = SSLContext.getInstance("TLS");
- ctx.init(loadedKeyFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null);
- res = ctx.getServerSocketFactory();
+ return makeSSLSocketFactory(loadedKeyStore, loadedKeyFactory.getKeyManagers());
} catch (Exception e) {
throw new IOException(e.getMessage());
}
- return res;
}
/**
@@ -1594,22 +1608,16 @@ public abstract class NanoHTTPD {
* certificate and passphrase
*/
public static SSLServerSocketFactory makeSSLSocketFactory(String keyAndTrustStoreClasspathPath, char[] passphrase) throws IOException {
- SSLServerSocketFactory res = null;
try {
KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
InputStream keystoreStream = NanoHTTPD.class.getResourceAsStream(keyAndTrustStoreClasspathPath);
keystore.load(keystoreStream, passphrase);
- TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
- trustManagerFactory.init(keystore);
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keystore, passphrase);
- SSLContext ctx = SSLContext.getInstance("TLS");
- ctx.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null);
- res = ctx.getServerSocketFactory();
+ return makeSSLSocketFactory(keystore, keyManagerFactory);
} catch (Exception e) {
throw new IOException(e.getMessage());
}
- return res;
}
private static final void safeClose(Object closeable) {
@@ -1634,7 +1642,7 @@ public abstract class NanoHTTPD {
private final int myPort;
- private ServerSocket myServerSocket;
+ private volatile ServerSocket myServerSocket;
private SSLServerSocketFactory sslServerSocketFactory;
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..e0aa3db 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 {
/**
@@ -46,7 +48,7 @@ public class ServerRunner {
public static void executeInstance(NanoHTTPD server) {
try {
- server.start();
+ server.start(NanoHTTPD.SOCKET_READ_TIMEOUT, false);
} catch (IOException ioe) {
System.err.println("Couldn't start server:\n" + ioe);
System.exit(-1);
diff --git a/core/src/test/java/fi/iki/elonen/HttpHeadRequestTest.java b/core/src/test/java/fi/iki/elonen/HttpHeadRequestTest.java
index 4eb3147..9b5983e 100644
--- a/core/src/test/java/fi/iki/elonen/HttpHeadRequestTest.java
+++ b/core/src/test/java/fi/iki/elonen/HttpHeadRequestTest.java
@@ -45,7 +45,7 @@ import org.junit.Test;
public class HttpHeadRequestTest extends HttpServerTest {
@Override
- public void setUp() {
+ public void setUp() throws Exception {
super.setUp();
String responseBody = "Success!";
this.testServer.response = NanoHTTPD.newFixedLengthResponse(responseBody);
diff --git a/core/src/test/java/fi/iki/elonen/HttpSSLServerTest.java b/core/src/test/java/fi/iki/elonen/HttpSSLServerTest.java
new file mode 100644
index 0000000..4923cca
--- /dev/null
+++ b/core/src/test/java/fi/iki/elonen/HttpSSLServerTest.java
@@ -0,0 +1,87 @@
+package fi.iki.elonen;
+
+/*
+ * #%L
+ * NanoHttpd-Core
+ * %%
+ * 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.File;
+import java.io.IOException;
+
+import javax.net.ssl.SSLServerSocketFactory;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.ClientProtocolException;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpTrace;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class HttpSSLServerTest extends HttpServerTest {
+
+ @Test
+ public void testSSLConnection() throws ClientProtocolException, IOException {
+ DefaultHttpClient httpclient = new DefaultHttpClient();
+ HttpTrace httphead = new HttpTrace("https://localhost:9043/index.html");
+ HttpResponse response = httpclient.execute(httphead);
+ HttpEntity entity = response.getEntity();
+ Assert.assertEquals(200, response.getStatusLine().getStatusCode());
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ System.setProperty("javax.net.ssl.trustStore", new File("src/test/resources/keystore.jks").getAbsolutePath());
+ this.testServer = new TestServer(9043);
+ this.testServer.makeSecure(NanoHTTPD.makeSSLSocketFactory("/keystore.jks", "password".toCharArray()));
+ this.tempFileManager = new TestTempFileManager();
+ this.testServer.start();
+ try {
+ long start = System.currentTimeMillis();
+ Thread.sleep(100L);
+ while (!this.testServer.wasStarted()) {
+ Thread.sleep(100L);
+ if (System.currentTimeMillis() - start > 2000) {
+ Assert.fail("could not start server");
+ }
+ }
+ } catch (InterruptedException e) {
+ }
+ }
+
+ @After
+ public void tearDown() {
+ this.testServer.stop();
+ }
+}
diff --git a/core/src/test/java/fi/iki/elonen/HttpServerTest.java b/core/src/test/java/fi/iki/elonen/HttpServerTest.java
index 429f5ab..e8be61e 100644
--- a/core/src/test/java/fi/iki/elonen/HttpServerTest.java
+++ b/core/src/test/java/fi/iki/elonen/HttpServerTest.java
@@ -83,6 +83,10 @@ public class HttpServerTest {
super(8192);
}
+ public TestServer(int port) {
+ super(port);
+ }
+
public HTTPSession createSession(TempFileManager tempFileManager, InputStream inputStream, OutputStream outputStream) {
return new HTTPSession(tempFileManager, inputStream, outputStream);
}
@@ -126,7 +130,7 @@ public class HttpServerTest {
protected TestServer testServer;
- private TestTempFileManager tempFileManager;
+ protected TestTempFileManager tempFileManager;
protected void assertLinesOfText(String[] expected, List<String> lines) {
// assertEquals(expected.length, lines.size());
@@ -172,7 +176,7 @@ public class HttpServerTest {
}
@Before
- public void setUp() {
+ public void setUp() throws Exception {
this.testServer = new TestServer();
this.tempFileManager = new TestTempFileManager();
}
diff --git a/core/src/test/java/fi/iki/elonen/HttpSessionHeadersTest.java b/core/src/test/java/fi/iki/elonen/HttpSessionHeadersTest.java
index 65947b9..1494e28 100644
--- a/core/src/test/java/fi/iki/elonen/HttpSessionHeadersTest.java
+++ b/core/src/test/java/fi/iki/elonen/HttpSessionHeadersTest.java
@@ -48,11 +48,6 @@ public class HttpSessionHeadersTest extends HttpServerTest {
private static final TestTempFileManager TEST_TEMP_FILE_MANAGER = new TestTempFileManager();
- @Override
- public void setUp() {
- super.setUp();
- }
-
@Test
@Ignore
public void testHeadersRemoteIp() throws Exception {
diff --git a/core/src/test/resources/keystore.jks b/core/src/test/resources/keystore.jks
new file mode 100644
index 0000000..354b5d3
--- /dev/null
+++ b/core/src/test/resources/keystore.jks
Binary files differ
diff --git a/fileupload/.gitignore b/fileupload/.gitignore
new file mode 100644
index 0000000..868a6b2
--- /dev/null
+++ b/fileupload/.gitignore
@@ -0,0 +1,2 @@
+/.settings/
+/LICENSE.txt
diff --git a/fileupload/pom.xml b/fileupload/pom.xml
new file mode 100644
index 0000000..0be88a9
--- /dev/null
+++ b/fileupload/pom.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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">
+ <parent>
+ <artifactId>nanohttpd-project</artifactId>
+ <groupId>org.nanohttpd</groupId>
+ <version>2.2.0-SNAPSHOT</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+ <artifactId>nanohttpd-apache-fileupload</artifactId>
+ <name>NanoHttpd-apache file upload integration</name>
+ <dependencies>
+ <dependency>
+ <groupId>org.nanohttpd</groupId>
+ <artifactId>nanohttpd</artifactId>
+ <version>2.2.0-SNAPSHOT</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>commons-fileupload</groupId>
+ <artifactId>commons-fileupload</artifactId>
+ <version>1.3.1</version>
+ </dependency>
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>servlet-api</artifactId>
+ <version>2.5</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpclient</artifactId>
+ <version>4.4.1</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpmime</artifactId>
+ <version>4.4.1</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ <properties>
+ <minimal.coverage>0.99</minimal.coverage>
+ </properties>
+</project> \ No newline at end of file
diff --git a/fileupload/src/main/java/fi/iki/elonen/NanoFileUpload.java b/fileupload/src/main/java/fi/iki/elonen/NanoFileUpload.java
new file mode 100644
index 0000000..ec02d4a
--- /dev/null
+++ b/fileupload/src/main/java/fi/iki/elonen/NanoFileUpload.java
@@ -0,0 +1,118 @@
+package fi.iki.elonen;
+
+/*
+ * #%L
+ * apache-fileupload-integration
+ * %%
+ * 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 static fi.iki.elonen.NanoHTTPD.Method.POST;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.fileupload.FileItem;
+import org.apache.commons.fileupload.FileItemFactory;
+import org.apache.commons.fileupload.FileItemIterator;
+import org.apache.commons.fileupload.FileUpload;
+import org.apache.commons.fileupload.FileUploadBase;
+import org.apache.commons.fileupload.FileUploadException;
+import org.apache.commons.fileupload.UploadContext;
+
+/**
+ * @author victor & ritchieGitHub
+ */
+public class NanoFileUpload extends FileUpload {
+
+ public static class NanoHttpdContext implements UploadContext {
+
+ private NanoHTTPD.IHTTPSession session;
+
+ public NanoHttpdContext(NanoHTTPD.IHTTPSession session) {
+ this.session = session;
+ }
+
+ @Override
+ public long contentLength() {
+ long size;
+ try {
+ String cl1 = session.getHeaders().get("content-length");
+ size = Long.parseLong(cl1);
+ } catch (NumberFormatException var4) {
+ size = -1L;
+ }
+
+ return size;
+ }
+
+ @Override
+ public String getCharacterEncoding() {
+ return "UTF-8";
+ }
+
+ @Override
+ public String getContentType() {
+ return this.session.getHeaders().get("content-type");
+ }
+
+ @Override
+ public int getContentLength() {
+ return (int) contentLength();
+ }
+
+ @Override
+ public InputStream getInputStream() throws IOException {
+ return session.getInputStream();
+ }
+ }
+
+ public static final boolean isMultipartContent(NanoHTTPD.IHTTPSession session) {
+ return session.getMethod() == POST && FileUploadBase.isMultipartContent(new NanoHttpdContext(session));
+ }
+
+ public NanoFileUpload(FileItemFactory fileItemFactory) {
+ super(fileItemFactory);
+ }
+
+ public List<FileItem> parseRequest(NanoHTTPD.IHTTPSession session) throws FileUploadException {
+ return this.parseRequest(new NanoHttpdContext(session));
+ }
+
+ public Map<String, List<FileItem>> parseParameterMap(NanoHTTPD.IHTTPSession session) throws FileUploadException {
+ return this.parseParameterMap(new NanoHttpdContext(session));
+ }
+
+ public FileItemIterator getItemIterator(NanoHTTPD.IHTTPSession session) throws FileUploadException, IOException {
+ return super.getItemIterator(new NanoHttpdContext(session));
+ }
+
+}
diff --git a/fileupload/src/test/java/fi/iki/elonen/TestNanoFileUpLoad.java b/fileupload/src/test/java/fi/iki/elonen/TestNanoFileUpLoad.java
new file mode 100644
index 0000000..f04375d
--- /dev/null
+++ b/fileupload/src/test/java/fi/iki/elonen/TestNanoFileUpLoad.java
@@ -0,0 +1,251 @@
+package fi.iki.elonen;
+
+/*
+ * #%L
+ * NanoHttpd-apache file upload integration
+ * %%
+ * 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.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.fileupload.FileItem;
+import org.apache.commons.fileupload.FileItemIterator;
+import org.apache.commons.fileupload.FileItemStream;
+import org.apache.commons.fileupload.FileUploadException;
+import org.apache.commons.fileupload.disk.DiskFileItemFactory;
+import org.apache.commons.fileupload.util.Streams;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.ClientProtocolException;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.methods.HttpTrace;
+import org.apache.http.entity.ContentType;
+import org.apache.http.entity.mime.HttpMultipartMode;
+import org.apache.http.entity.mime.MultipartEntityBuilder;
+import org.apache.http.entity.mime.content.FileBody;
+import org.apache.http.entity.mime.content.StringBody;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.internal.runners.statements.Fail;
+
+import fi.iki.elonen.NanoHTTPD.Response.Status;
+
+/**
+ * very strange but if the file upload is the first request the test fails.
+ *
+ * @author ritchieGitHub
+ */
+@FixMethodOrder
+public class TestNanoFileUpLoad {
+
+ protected TestServer testServer;
+
+ public static class TestServer extends NanoHTTPD {
+
+ public Response response = newFixedLengthResponse("");
+
+ public String uri;
+
+ public Method method;
+
+ public Map<String, String> header;
+
+ public Map<String, String> parms;
+
+ public Map<String, List<FileItem>> files;
+
+ public Map<String, List<String>> decodedParamters;
+
+ public Map<String, List<String>> decodedParamtersFromParameter;
+
+ public String queryParameterString;
+
+ public TestServer() {
+ super(8192);
+ uploader = new NanoFileUpload(new DiskFileItemFactory());
+ }
+
+ public HTTPSession createSession(TempFileManager tempFileManager, InputStream inputStream, OutputStream outputStream) {
+ return new HTTPSession(tempFileManager, inputStream, outputStream);
+ }
+
+ public HTTPSession createSession(TempFileManager tempFileManager, InputStream inputStream, OutputStream outputStream, InetAddress inetAddress) {
+ return new HTTPSession(tempFileManager, inputStream, outputStream, inetAddress);
+ }
+
+ @Override
+ public String decodePercent(String str) {
+ return super.decodePercent(str);
+ }
+
+ NanoFileUpload uploader;
+
+ @Override
+ public Response serve(IHTTPSession session) {
+
+ this.uri = session.getUri();
+ this.method = session.getMethod();
+ this.header = session.getHeaders();
+ this.parms = session.getParms();
+ if (NanoFileUpload.isMultipartContent(session)) {
+ try {
+ if ("/uploadFile1".equals(this.uri)) {
+ session.getHeaders().put("content-length", "AA");
+ files = uploader.parseParameterMap(session);
+ }
+ if ("/uploadFile2".equals(this.uri)) {
+ files = new HashMap<String, List<FileItem>>();
+ List<FileItem> parseRequest = uploader.parseRequest(session);
+ files.put(parseRequest.get(0).getFieldName(), parseRequest);
+ }
+ if ("/uploadFile3".equals(this.uri)) {
+ files = new HashMap<String, List<FileItem>>();
+ FileItemIterator iter = uploader.getItemIterator(session);
+ while (iter.hasNext()) {
+ FileItemStream item = iter.next();
+ final String fileName = item.getName();
+ FileItem fileItem = uploader.getFileItemFactory().createItem(item.getFieldName(), item.getContentType(), item.isFormField(), fileName);
+ files.put(fileItem.getFieldName(), Arrays.asList(new FileItem[]{
+ fileItem
+ }));
+ try {
+ Streams.copy(item.openStream(), fileItem.getOutputStream(), true);
+ } catch (Exception e) {
+ }
+ fileItem.setHeaders(item.getHeaders());
+ }
+ }
+ } catch (Exception e) {
+ this.response.setStatus(Status.INTERNAL_ERROR);
+ e.printStackTrace();
+ }
+ }
+ this.queryParameterString = session.getQueryParameterString();
+ this.decodedParamtersFromParameter = decodeParameters(this.queryParameterString);
+ this.decodedParamters = decodeParameters(session.getQueryParameterString());
+ return this.response;
+ }
+
+ }
+
+ @Test
+ public void testNormalRequest() throws Exception {
+ CloseableHttpClient httpclient = HttpClients.createDefault();
+ HttpTrace httphead = new HttpTrace("http://localhost:8192/index.html");
+ CloseableHttpResponse response = httpclient.execute(httphead);
+ Assert.assertEquals(200, response.getStatusLine().getStatusCode());
+ response.close();
+ }
+
+ @Test
+ public void testPostWithMultipartFormUpload1() throws Exception {
+ CloseableHttpClient httpclient = HttpClients.createDefault();
+ String textFileName = "src/test/java/fi/iki/elonen/TestNanoFileUpLoad.java";
+ HttpPost post = new HttpPost("http://localhost:8192/uploadFile1");
+
+ executeUpload(httpclient, textFileName, post);
+ FileItem file = this.testServer.files.get("upfile").get(0);
+ Assert.assertEquals(file.getSize(), new File(textFileName).length());
+ }
+
+ @Test
+ public void testPostWithMultipartFormUpload2() throws Exception {
+ CloseableHttpClient httpclient = HttpClients.createDefault();
+ String textFileName = "src/test/java/fi/iki/elonen/TestNanoFileUpLoad.java";
+ HttpPost post = new HttpPost("http://localhost:8192/uploadFile2");
+
+ executeUpload(httpclient, textFileName, post);
+ FileItem file = this.testServer.files.get("upfile").get(0);
+ Assert.assertEquals(file.getSize(), new File(textFileName).length());
+ }
+
+ @Test
+ public void testPostWithMultipartFormUpload3() throws Exception {
+ CloseableHttpClient httpclient = HttpClients.createDefault();
+ String textFileName = "src/test/java/fi/iki/elonen/TestNanoFileUpLoad.java";
+ HttpPost post = new HttpPost("http://localhost:8192/uploadFile3");
+
+ executeUpload(httpclient, textFileName, post);
+ FileItem file = this.testServer.files.get("upfile").get(0);
+ Assert.assertEquals(file.getSize(), new File(textFileName).length());
+ }
+
+ private void executeUpload(CloseableHttpClient httpclient, String textFileName, HttpPost post) throws IOException, ClientProtocolException {
+ FileBody fileBody = new FileBody(new File(textFileName), ContentType.DEFAULT_BINARY);
+ StringBody stringBody1 = new StringBody("Message 1", ContentType.MULTIPART_FORM_DATA);
+
+ MultipartEntityBuilder builder = MultipartEntityBuilder.create();
+ builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
+ builder.addPart("upfile", fileBody);
+ builder.addPart("text1", stringBody1);
+ HttpEntity entity = builder.build();
+ //
+ post.setEntity(entity);
+ HttpResponse response = httpclient.execute(post);
+ Assert.assertEquals(200, response.getStatusLine().getStatusCode());
+ }
+
+ @Before
+ public void setUp() throws IOException {
+ this.testServer = new TestServer();
+ this.testServer.start();
+ try {
+ long start = System.currentTimeMillis();
+ Thread.sleep(100L);
+ while (!this.testServer.wasStarted()) {
+ Thread.sleep(100L);
+ if (System.currentTimeMillis() - start > 2000) {
+ Assert.fail("could not start server");
+ }
+ }
+ } catch (InterruptedException e) {
+ }
+ }
+
+ @After
+ public void tearDown() {
+ this.testServer.stop();
+ }
+
+}
diff --git a/markdown-plugin/pom.xml b/markdown-plugin/pom.xml
index 590bb44..f7034c7 100644
--- a/markdown-plugin/pom.xml
+++ b/markdown-plugin/pom.xml
@@ -56,4 +56,7 @@
</plugin>
</plugins>
</build>
+ <properties>
+ <minimal.coverage>0.0</minimal.coverage>
+ </properties>
</project>
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/LICENSE.txt b/nanolets/LICENSE.txt
new file mode 100644
index 0000000..fcebfe1
--- /dev/null
+++ b/nanolets/LICENSE.txt
@@ -0,0 +1,26 @@
+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.
diff --git a/nanolets/pom.xml b/nanolets/pom.xml
new file mode 100644
index 0000000..a190abb
--- /dev/null
+++ b/nanolets/pom.xml
@@ -0,0 +1,28 @@
+<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>
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpclient</artifactId>
+ <version>4.4.1</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ <properties>
+ <minimal.coverage>0.98</minimal.coverage>
+ </properties>
+</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..82dca8b
--- /dev/null
+++ b/nanolets/src/main/java/fi/iki/elonen/router/RouterNanoHTTPD.java
@@ -0,0 +1,546 @@
+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.Iterator;
+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 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 new StringBuilder("UriPart{name='").append(name)//
+ .append("\', isParam=").append(isParam)//
+ .append('}').toString();
+ }
+
+ 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 = 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 (Exception 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 = 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) {
+ String uriToDelete = normalizeUri(url);
+ Iterator<UriResource> iter = mappings.iterator();
+ while (iter.hasNext()) {
+ UriResource uriResource = iter.next();
+ if (uriToDelete.equals(uriResource.getUri())) {
+ iter.remove();
+ break;
+ }
+ }
+ }
+
+ 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 = 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..b2ae4ed
--- /dev/null
+++ b/nanolets/src/test/java/fi/iki/elonen/router/AppNanolets.java
@@ -0,0 +1,158 @@
+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.io.InputStream;
+import java.util.Map;
+
+import fi.iki.elonen.NanoHTTPD;
+import fi.iki.elonen.NanoHTTPD.Response.IStatus;
+import fi.iki.elonen.NanoHTTPD.Response.Status;
+import fi.iki.elonen.util.ServerRunner;
+
+public class AppNanolets extends RouterNanoHTTPD {
+
+ private static final int PORT = 9090;
+
+ 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);
+ }
+
+ }
+
+ static public class StreamUrl extends DefaultStreamHandler {
+
+ @Override
+ public String getMimeType() {
+ return "text/plain";
+ }
+
+ @Override
+ public IStatus getStatus() {
+ return Status.OK;
+ }
+
+ @Override
+ public InputStream getData() {
+ return new ByteArrayInputStream("a stream of data ;-)".getBytes());
+ }
+
+ }
+
+ /**
+ * 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("/general/:param1/:param2", GeneralHandler.class);
+ addRoute("/photos/:customer_id/:photo_id", null);
+ addRoute("/test", String.class);
+ addRoute("/interface", UriResponder.class); // this will cause an error
+ // when called
+ addRoute("/toBeDeleted", String.class);
+ removeRoute("/toBeDeleted");
+ addRoute("/stream", StreamUrl.class);
+ }
+
+ /**
+ * Main entry point
+ *
+ * @param args
+ */
+ public static void main(String[] args) {
+ ServerRunner.run(AppNanolets.class);
+ }
+}
diff --git a/nanolets/src/test/java/fi/iki/elonen/router/TestNanolets.java b/nanolets/src/test/java/fi/iki/elonen/router/TestNanolets.java
new file mode 100644
index 0000000..bd360da
--- /dev/null
+++ b/nanolets/src/test/java/fi/iki/elonen/router/TestNanolets.java
@@ -0,0 +1,283 @@
+package fi.iki.elonen.router;
+
+/*
+ * #%L
+ * NanoHttpd nano application server
+ * %%
+ * 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.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+
+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;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import fi.iki.elonen.router.RouterNanoHTTPD.GeneralHandler;
+import fi.iki.elonen.router.RouterNanoHTTPD.UriResource;
+
+public class TestNanolets {
+
+ private static PipedOutputStream stdIn;
+
+ private static Thread serverStartThread;
+
+ @BeforeClass
+ public static void setUp() throws Exception {
+ stdIn = new PipedOutputStream();
+ System.setIn(new PipedInputStream(stdIn));
+ serverStartThread = new Thread(new Runnable() {
+
+ @Override
+ public void run() {
+ String[] args = {};
+ AppNanolets.main(args);
+ }
+ });
+ serverStartThread.start();
+ // give the server some tine to start.
+ Thread.sleep(100);
+ }
+
+ @Test
+ public void doSomeBasicMethodTest() throws Exception {
+ CloseableHttpClient httpclient = HttpClients.createDefault();
+
+ HttpGet httpget = new HttpGet("http://localhost:9090/user/blabla");
+ CloseableHttpResponse response = httpclient.execute(httpget);
+ HttpEntity entity = response.getEntity();
+ String string = new String(readContents(entity), "UTF-8");
+ Assert.assertEquals(
+ "<html><body>User handler. Method: GET<br><h1>Uri parameters:</h1><div> Param: id&nbsp;Value: blabla</div><h1>Query parameters:</h1></body></html>", string);
+ response.close();
+
+ HttpPost httppost = new HttpPost("http://localhost:9090/user/blabla");
+ response = httpclient.execute(httppost);
+ entity = response.getEntity();
+ string = new String(readContents(entity), "UTF-8");
+ Assert.assertEquals(
+ "<html><body>User handler. Method: POST<br><h1>Uri parameters:</h1><div> Param: id&nbsp;Value: blabla</div><h1>Query parameters:</h1></body></html>", string);
+ response.close();
+
+ HttpPut httpgput = new HttpPut("http://localhost:9090/user/blabla");
+ response = httpclient.execute(httpgput);
+ entity = response.getEntity();
+ string = new String(readContents(entity), "UTF-8");
+ Assert.assertEquals(
+ "<html><body>User handler. Method: PUT<br><h1>Uri parameters:</h1><div> Param: id&nbsp;Value: blabla</div><h1>Query parameters:</h1></body></html>", string);
+ response.close();
+
+ HttpDelete httpdelete = new HttpDelete("http://localhost:9090/user/blabla");
+ response = httpclient.execute(httpdelete);
+ entity = response.getEntity();
+ string = new String(readContents(entity), "UTF-8");
+ Assert.assertEquals(
+ "<html><body>User handler. Method: DELETE<br><h1>Uri parameters:</h1><div> Param: id&nbsp;Value: blabla</div><h1>Query parameters:</h1></body></html>", string);
+ response.close();
+ }
+
+ @Test
+ public void doNonRouterRequest() throws Exception {
+ CloseableHttpClient httpclient = HttpClients.createDefault();
+
+ HttpGet httpget = new HttpGet("http://localhost:9090/test");
+ CloseableHttpResponse response = httpclient.execute(httpget);
+ HttpEntity entity = response.getEntity();
+ String string = new String(readContents(entity), "UTF-8");
+ Assert.assertEquals("Return: java.lang.String.toString() -> ", string);
+ response.close();
+ }
+
+ @Test
+ public void doExceptionRequest() throws Exception {
+ CloseableHttpClient httpclient = HttpClients.createDefault();
+
+ HttpGet httpget = new HttpGet("http://localhost:9090/interface");
+ CloseableHttpResponse response = httpclient.execute(httpget);
+ HttpEntity entity = response.getEntity();
+ String string = new String(readContents(entity), "UTF-8");
+ Assert.assertEquals("Error: java.lang.InstantiationException : fi.iki.elonen.router.RouterNanoHTTPD$UriResponder", string);
+ response.close();
+ }
+
+ @Test
+ public void doDeletedRoute() throws Exception {
+ CloseableHttpClient httpclient = HttpClients.createDefault();
+
+ HttpGet httpget = new HttpGet("http://localhost:9090/toBeDeleted");
+ CloseableHttpResponse response = httpclient.execute(httpget);
+ HttpEntity entity = response.getEntity();
+ String 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();
+ }
+
+ @Test
+ public void doUriSelection1() throws Exception {
+ CloseableHttpClient httpclient = HttpClients.createDefault();
+
+ HttpGet httpget = new HttpGet("http://localhost:9090/user/help");
+ CloseableHttpResponse response = httpclient.execute(httpget);
+ HttpEntity entity = response.getEntity();
+ String string = new String(readContents(entity), "UTF-8");
+ Assert.assertEquals("<html><body><h1>Url: /user/help</h1><br><p>no params in url</p><br>", string);
+ response.close();
+ }
+
+ @Test
+ public void doStreamOfData() throws Exception {
+ CloseableHttpClient httpclient = HttpClients.createDefault();
+
+ HttpGet httpget = new HttpGet("http://localhost:9090/stream");
+ CloseableHttpResponse response = httpclient.execute(httpget);
+ HttpEntity entity = response.getEntity();
+ String string = new String(readContents(entity), "UTF-8");
+ Assert.assertEquals("a stream of data ;-)", string);
+ response.close();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void illegalMethod1() throws Exception {
+ new AppNanolets.UserHandler().getData();
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void illegalMethod2() throws Exception {
+ new RouterNanoHTTPD.GeneralHandler().getText();
+ }
+
+ @Test
+ public void doGeneralParams() throws Exception {
+ CloseableHttpClient httpclient = HttpClients.createDefault();
+
+ HttpGet httpget = new HttpGet("http://localhost:9090/general/value1/value2?param3=value3&param4=value4");
+
+ CloseableHttpResponse response = httpclient.execute(httpget);
+ HttpEntity entity = response.getEntity();
+ String string = new String(readContents(entity), "UTF-8");
+ Assert.assertEquals("<html><body><h1>Url: /general/value1/value2</h1><br><p>Param 'param3' = value3</p><p>Param 'param4' = value4</p>", string);
+ response.close();
+ }
+
+ @Test
+ public void doIndexHandler() throws Exception {
+ CloseableHttpClient httpclient = HttpClients.createDefault();
+
+ HttpGet httpget = new HttpGet("http://localhost:9090/index.html");
+ CloseableHttpResponse response = httpclient.execute(httpget);
+ HttpEntity entity = response.getEntity();
+ String string = new String(readContents(entity), "UTF-8");
+ Assert.assertEquals("<html><body><h2>Hello world!</h3></body></html>", string);
+ response.close();
+ }
+
+ @Test
+ public void doMissingHandler() throws Exception {
+ CloseableHttpClient httpclient = HttpClients.createDefault();
+
+ HttpGet httpget = new HttpGet("http://localhost:9090/photos/abc/def");
+ CloseableHttpResponse response = httpclient.execute(httpget);
+ HttpEntity entity = response.getEntity();
+ String string = new String(readContents(entity), "UTF-8");
+ Assert.assertEquals("<html><body><h2>The uri is mapped in the router, but no handler is specified. <br> Status: Not implemented!</h3></body></html>", string);
+ response.close();
+ }
+
+ @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());
+ }
+
+ @Test
+ public void doOtherMethod() throws Exception {
+ CloseableHttpClient httpclient = HttpClients.createDefault();
+
+ HttpTrace httphead = new HttpTrace("http://localhost:9090/index.html");
+ CloseableHttpResponse response = httpclient.execute(httphead);
+ HttpEntity entity = response.getEntity();
+ String string = new String(readContents(entity), "UTF-8");
+ Assert.assertEquals("<html><body><h2>Hello world!</h3></body></html>", string);
+ response.close();
+ }
+
+ @Test
+ public void normalize() throws Exception {
+ Assert.assertNull(RouterNanoHTTPD.normalizeUri(null));
+ Assert.assertEquals("", RouterNanoHTTPD.normalizeUri("/"));
+ Assert.assertEquals("xxx/yyy", RouterNanoHTTPD.normalizeUri("/xxx/yyy"));
+ Assert.assertEquals("xxx/yyy", RouterNanoHTTPD.normalizeUri("/xxx/yyy/"));
+ }
+
+ private byte[] readContents(HttpEntity entity) throws IOException {
+ InputStream instream = entity.getContent();
+ return readContents(instream);
+ }
+
+ private byte[] readContents(InputStream instream) throws IOException {
+ byte[] bytes;
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+ try {
+ byte[] buffer = new byte[1024];
+ int count;
+ while ((count = instream.read(buffer)) >= 0) {
+ out.write(buffer, 0, count);
+ }
+ bytes = out.toByteArray();
+ } finally {
+ instream.close();
+ }
+ return bytes;
+ }
+
+ @AfterClass
+ public static void tearDown() throws Exception {
+ stdIn.write("\n\n".getBytes());
+ serverStartThread.join(2000);
+ Assert.assertFalse(serverStartThread.isAlive());
+ }
+
+}
diff --git a/pom.xml b/pom.xml
index e2ea817..9334315 100644
--- a/pom.xml
+++ b/pom.xml
@@ -8,7 +8,7 @@
</parent>
<groupId>org.nanohttpd</groupId>
<artifactId>nanohttpd-project</artifactId>
- <version>2.2.1-SNAPSHOT</version>
+ <version>2.2.0-SNAPSHOT</version>
<packaging>pom</packaging>
<name>NanoHttpd-Project</name>
<description>NanoHttpd is a light-weight HTTP server designed for embedding in other applications.</description>
@@ -88,6 +88,8 @@
<module>webserver</module>
<module>websocket</module>
<module>markdown-plugin</module>
+ <module>nanolets</module>
+ <module>fileupload</module>
</modules>
<licenses>
<license>
@@ -231,6 +233,26 @@
<goal>report</goal>
</goals>
</execution>
+ <execution>
+ <id>default-check</id>
+ <goals>
+ <goal>check</goal>
+ </goals>
+ <configuration>
+ <rules>
+ <rule>
+ <element>BUNDLE</element>
+ <limits>
+ <limit>
+ <counter>LINE</counter>
+ <value>COVEREDRATIO</value>
+ <minimum>${minimal.coverage}</minimum>
+ </limit>
+ </limits>
+ </rule>
+ </rules>
+ </configuration>
+ </execution>
</executions>
</plugin>
</plugins>
@@ -318,7 +340,7 @@
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
- <version>4.8.2</version>
+ <version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
@@ -473,4 +495,7 @@
</build>
</profile>
</profiles>
+ <properties>
+ <minimal.coverage>0.77</minimal.coverage>
+ </properties>
</project>
diff --git a/samples/pom.xml b/samples/pom.xml
index 5ff6764..e57150a 100644
--- a/samples/pom.xml
+++ b/samples/pom.xml
@@ -20,4 +20,7 @@
<version>${project.version}</version>
</dependency>
</dependencies>
+ <properties>
+ <minimal.coverage>0.0</minimal.coverage>
+ </properties>
</project>
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/pom.xml b/webserver/pom.xml
index 32b1f54..3ecfd72 100644
--- a/webserver/pom.xml
+++ b/webserver/pom.xml
@@ -4,7 +4,7 @@
<parent>
<groupId>org.nanohttpd</groupId>
<artifactId>nanohttpd-project</artifactId>
- <version>2.2.1-SNAPSHOT</version>
+ <version>2.2.0-SNAPSHOT</version>
</parent>
<artifactId>nanohttpd-webserver</artifactId>
<packaging>jar</packaging>
@@ -49,4 +49,7 @@
</plugin>
</plugins>
</build>
+ <properties>
+ <minimal.coverage>0.74</minimal.coverage>
+ </properties>
</project>
diff --git a/webserver/src/main/java/fi/iki/elonen/SimpleWebServer.java b/webserver/src/main/java/fi/iki/elonen/SimpleWebServer.java
index 0ddc21c..f5415d6 100644
--- a/webserver/src/main/java/fi/iki/elonen/SimpleWebServer.java
+++ b/webserver/src/main/java/fi/iki/elonen/SimpleWebServer.java
@@ -32,7 +32,6 @@ package fi.iki.elonen;
* OF THE POSSIBILITY OF SUCH DAMAGE.
* #L%
*/
-
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
@@ -51,6 +50,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 {
@@ -137,6 +137,7 @@ public class SimpleWebServer extends NanoHTTPD {
String host = null; // bind to all interfaces by default
List<File> rootDirs = new ArrayList<File>();
boolean quiet = false;
+ String cors = null;
Map<String, String> options = new HashMap<String, String>();
// Parse command-line, with short and long versions of the options.
@@ -149,6 +150,12 @@ public class SimpleWebServer extends NanoHTTPD {
quiet = true;
} else if (args[i].equalsIgnoreCase("-d") || args[i].equalsIgnoreCase("--dir")) {
rootDirs.add(new File(args[i + 1]).getAbsoluteFile());
+ } else if (args[i].startsWith("--cors")) {
+ cors = "*";
+ int equalIdx = args[i].indexOf('=');
+ if (equalIdx > 0) {
+ cors = args[i].substring(equalIdx + 1);
+ }
} else if (args[i].equalsIgnoreCase("--licence")) {
System.out.println(SimpleWebServer.LICENCE + "\n");
} else if (args[i].startsWith("-X:")) {
@@ -164,7 +171,6 @@ public class SimpleWebServer extends NanoHTTPD {
if (rootDirs.isEmpty()) {
rootDirs.add(new File(".").getAbsoluteFile());
}
-
options.put("host", host);
options.put("port", "" + port);
options.put("quiet", String.valueOf(quiet));
@@ -179,7 +185,6 @@ public class SimpleWebServer extends NanoHTTPD {
}
}
options.put("home", sb.toString());
-
ServiceLoader<WebServerPluginInfo> serviceLoader = ServiceLoader.load(WebServerPluginInfo.class);
for (WebServerPluginInfo info : serviceLoader) {
String[] mimeTypes = info.getMimeTypes();
@@ -198,8 +203,7 @@ public class SimpleWebServer extends NanoHTTPD {
registerPluginForMimeType(indexFiles, mime, info.getWebServerPlugin(mime), options);
}
}
-
- ServerRunner.executeInstance(new SimpleWebServer(host, port, rootDirs, quiet));
+ ServerRunner.executeInstance(new SimpleWebServer(host, port, rootDirs, quiet, cors));
}
protected static void registerPluginForMimeType(String[] indexFiles, String mimeType, WebServerPlugin plugin, Map<String, String> commandLineOptions) {
@@ -223,20 +227,26 @@ public class SimpleWebServer extends NanoHTTPD {
private final boolean quiet;
+ private final String cors;
+
protected List<File> rootDirs;
- public SimpleWebServer(String host, int port, File wwwroot, boolean quiet) {
- super(host, port);
- this.quiet = quiet;
- this.rootDirs = new ArrayList<File>();
- this.rootDirs.add(wwwroot);
+ public SimpleWebServer(String host, int port, File wwwroot, boolean quiet, String cors) {
+ this(host, port, Collections.singletonList(wwwroot), quiet, cors);
+ }
- init();
+ public SimpleWebServer(String host, int port, File wwwroot, boolean quiet) {
+ this(host, port, Collections.singletonList(wwwroot), quiet, null);
}
public SimpleWebServer(String host, int port, List<File> wwwroots, boolean quiet) {
+ this(host, port, wwwroots, quiet, null);
+ }
+
+ public SimpleWebServer(String host, int port, List<File> wwwroots, boolean quiet, String cors) {
super(host, port);
this.quiet = quiet;
+ this.cors = cors;
this.rootDirs = new ArrayList<File>(wwwroots);
init();
@@ -393,6 +403,21 @@ public class SimpleWebServer extends NanoHTTPD {
}
private Response respond(Map<String, String> headers, IHTTPSession session, String uri) {
+ // First let's handle CORS OPTION query
+ Response r;
+ if (cors != null && Method.OPTIONS.equals(session.getMethod())) {
+ r = new NanoHTTPD.Response(Response.Status.OK, MIME_PLAINTEXT, null, 0);
+ } else {
+ r = defaultRespond(headers, session, uri);
+ }
+
+ if (cors != null) {
+ r = addCORSHeaders(headers, r, cors);
+ }
+ return r;
+ }
+
+ private Response defaultRespond(Map<String, String> headers, IHTTPSession session, String uri) {
// Remove URL arguments
uri = uri.trim().replace(File.separatorChar, '/');
if (uri.indexOf('?') >= 0) {
@@ -595,4 +620,31 @@ public class SimpleWebServer extends NanoHTTPD {
res.addHeader("Accept-Ranges", "bytes");
return res;
}
+
+ protected Response addCORSHeaders(Map<String, String> queryHeaders, Response resp, String cors) {
+ resp.addHeader("Access-Control-Allow-Origin", cors);
+ resp.addHeader("Access-Control-Allow-Headers", calculateAllowHeaders(queryHeaders));
+ resp.addHeader("Access-Control-Allow-Credentials", "true");
+ resp.addHeader("Access-Control-Allow-Methods", ALLOWED_METHODS);
+ resp.addHeader("Access-Control-Max-Age", "" + MAX_AGE);
+
+ return resp;
+ }
+
+ private String calculateAllowHeaders(Map<String, String> queryHeaders) {
+ // here we should use the given asked headers
+ // but NanoHttpd uses a Map whereas it is possible for requester to send
+ // several time the same header
+ // let's just use default values for this version
+ return System.getProperty(ACCESS_CONTROL_ALLOW_HEADER_PROPERTY_NAME, DEFAULT_ALLOWED_HEADERS);
+ }
+
+ private final static String ALLOWED_METHODS = "GET, POST, PUT, DELETE, OPTIONS, HEAD";
+
+ private final static int MAX_AGE = 42 * 60 * 60;
+
+ // explicitly relax visibility to package for tests purposes
+ final static String DEFAULT_ALLOWED_HEADERS = "origin,accept,content-type";
+
+ public final static String ACCESS_CONTROL_ALLOW_HEADER_PROPERTY_NAME = "AccessControlAllowHeader";
}
diff --git a/webserver/src/test/java/fi/iki/elonen/AbstractTestHttpServer.java b/webserver/src/test/java/fi/iki/elonen/AbstractTestHttpServer.java
new file mode 100644
index 0000000..b56c2b0
--- /dev/null
+++ b/webserver/src/test/java/fi/iki/elonen/AbstractTestHttpServer.java
@@ -0,0 +1,67 @@
+package fi.iki.elonen;
+
+/*
+ * #%L
+ * NanoHttpd-Webserver
+ * %%
+ * 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.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import org.apache.http.HttpEntity;
+
+/**
+ * @author Matthieu Brouillard [matthieu@brouillard.fr]
+ */
+public class AbstractTestHttpServer {
+
+ protected byte[] readContents(HttpEntity entity) throws IOException {
+ InputStream instream = entity.getContent();
+ return readContents(instream);
+ }
+
+ protected byte[] readContents(InputStream instream) throws IOException {
+ byte[] bytes;
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ try {
+ byte[] buffer = new byte[1024];
+ int count;
+ while ((count = instream.read(buffer)) >= 0) {
+ out.write(buffer, 0, count);
+ }
+ bytes = out.toByteArray();
+ } finally {
+ instream.close();
+ }
+ return bytes;
+ }
+
+}
diff --git a/webserver/src/test/java/fi/iki/elonen/TestCorsHttpServer.java b/webserver/src/test/java/fi/iki/elonen/TestCorsHttpServer.java
new file mode 100644
index 0000000..93f4699
--- /dev/null
+++ b/webserver/src/test/java/fi/iki/elonen/TestCorsHttpServer.java
@@ -0,0 +1,154 @@
+package fi.iki.elonen;
+
+/*
+ * #%L
+ * NanoHttpd-Webserver
+ * %%
+ * 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.ByteArrayOutputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpOptions;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * @author Matthieu Brouillard [matthieu@brouillard.fr]
+ */
+public class TestCorsHttpServer extends AbstractTestHttpServer {
+
+ private static PipedOutputStream stdIn;
+
+ private static Thread serverStartThread;
+
+ @BeforeClass
+ public static void setUp() throws Exception {
+ stdIn = new PipedOutputStream();
+ System.setIn(new PipedInputStream(stdIn));
+ serverStartThread = new Thread(new Runnable() {
+
+ @Override
+ public void run() {
+ String[] args = {
+ "--host",
+ "localhost",
+ "--port",
+ "9090",
+ "--dir",
+ "src/test/resources",
+ "--cors"
+ };
+ SimpleWebServer.main(args);
+ }
+ });
+ serverStartThread.start();
+ // give the server some tine to start.
+ Thread.sleep(100);
+ }
+
+ @AfterClass
+ public static void tearDown() throws Exception {
+ stdIn.write("\n\n".getBytes());
+ serverStartThread.join(2000);
+ Assert.assertFalse(serverStartThread.isAlive());
+ }
+
+ @Test
+ public void doTestOption() throws Exception {
+ CloseableHttpClient httpclient = HttpClients.createDefault();
+ HttpOptions httpOption = new HttpOptions("http://localhost:9090/xxx/yyy.html");
+ CloseableHttpResponse response = httpclient.execute(httpOption);
+ Assert.assertEquals(200, response.getStatusLine().getStatusCode());
+ Assert.assertNotNull("Cors should have added a header: Access-Control-Allow-Origin", response.getLastHeader("Access-Control-Allow-Origin"));
+ Assert.assertEquals("Cors should have added a header: Access-Control-Allow-Origin: *", "*", response.getLastHeader("Access-Control-Allow-Origin").getValue());
+ response.close();
+ }
+
+ @Test
+ public void doSomeBasicTest() throws Exception {
+ CloseableHttpClient httpclient = HttpClients.createDefault();
+ HttpGet httpget = new HttpGet("http://localhost:9090/testdir/test.html");
+ CloseableHttpResponse response = httpclient.execute(httpget);
+ HttpEntity entity = response.getEntity();
+ String string = new String(readContents(entity), "UTF-8");
+
+ Assert.assertNotNull("Cors should have added a header: Access-Control-Allow-Origin", response.getLastHeader("Access-Control-Allow-Origin"));
+ Assert.assertEquals("Cors should have added a header: Access-Control-Allow-Origin: *", "*", response.getLastHeader("Access-Control-Allow-Origin").getValue());
+ Assert.assertEquals("<html>\n<head>\n<title>dummy</title>\n</head>\n<body>\n\t<h1>it works</h1>\n</body>\n</html>", string);
+ response.close();
+ }
+
+ @Test
+ public void testAccessControlAllowHeaderUsesDefaultsWithoutSystemProperty() throws Exception {
+ Assert.assertNull("no System " + SimpleWebServer.ACCESS_CONTROL_ALLOW_HEADER_PROPERTY_NAME + " shoudl be set",
+ System.getProperty(SimpleWebServer.ACCESS_CONTROL_ALLOW_HEADER_PROPERTY_NAME));
+
+ CloseableHttpClient httpclient = HttpClients.createDefault();
+ HttpOptions httpOption = new HttpOptions("http://localhost:9090/xxx/yyy.html");
+ CloseableHttpResponse response = httpclient.execute(httpOption);
+ Assert.assertEquals(200, response.getStatusLine().getStatusCode());
+ Assert.assertEquals("Cors should have added a header: Access-Control-Allow-Headers: " + SimpleWebServer.DEFAULT_ALLOWED_HEADERS,
+ SimpleWebServer.DEFAULT_ALLOWED_HEADERS, response.getLastHeader("Access-Control-Allow-Headers").getValue());
+ response.close();
+ }
+
+ @Test
+ public void testAccessControlAllowHeaderUsesSystemPropertyWhenSet() throws Exception {
+ Assert.assertNull("no System " + SimpleWebServer.ACCESS_CONTROL_ALLOW_HEADER_PROPERTY_NAME + " shoudl be set",
+ System.getProperty(SimpleWebServer.ACCESS_CONTROL_ALLOW_HEADER_PROPERTY_NAME));
+
+ final String expectedValue = "origin";
+ System.setProperty(SimpleWebServer.ACCESS_CONTROL_ALLOW_HEADER_PROPERTY_NAME, expectedValue);
+
+ try {
+ CloseableHttpClient httpclient = HttpClients.createDefault();
+ HttpOptions httpOption = new HttpOptions("http://localhost:9090/xxx/yyy.html");
+ CloseableHttpResponse response = httpclient.execute(httpOption);
+ Assert.assertEquals(200, response.getStatusLine().getStatusCode());
+ Assert.assertEquals("Cors should have added a header: Access-Control-Allow-Headers: " + expectedValue, expectedValue,
+ response.getLastHeader("Access-Control-Allow-Headers").getValue());
+ response.close();
+ } finally {
+ System.clearProperty(SimpleWebServer.ACCESS_CONTROL_ALLOW_HEADER_PROPERTY_NAME);
+ }
+ }
+}
diff --git a/webserver/src/test/java/fi/iki/elonen/TestCorsHttpServerWithSingleOrigin.java b/webserver/src/test/java/fi/iki/elonen/TestCorsHttpServerWithSingleOrigin.java
new file mode 100644
index 0000000..dbd2c4e
--- /dev/null
+++ b/webserver/src/test/java/fi/iki/elonen/TestCorsHttpServerWithSingleOrigin.java
@@ -0,0 +1,121 @@
+package fi.iki.elonen;
+
+/*
+ * #%L
+ * NanoHttpd-Webserver
+ * %%
+ * 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.ByteArrayOutputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpOptions;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * @author Matthieu Brouillard [matthieu@brouillard.fr]
+ */
+public class TestCorsHttpServerWithSingleOrigin extends AbstractTestHttpServer {
+
+ private static PipedOutputStream stdIn;
+
+ private static Thread serverStartThread;
+
+ @BeforeClass
+ public static void setUp() throws Exception {
+ stdIn = new PipedOutputStream();
+ System.setIn(new PipedInputStream(stdIn));
+ serverStartThread = new Thread(new Runnable() {
+
+ @Override
+ public void run() {
+ String[] args = {
+ "--host",
+ "localhost",
+ "--port",
+ "9090",
+ "--dir",
+ "src/test/resources",
+ "--cors=http://localhost:9090"
+ };
+ SimpleWebServer.main(args);
+ }
+ });
+ serverStartThread.start();
+ // give the server some tine to start.
+ Thread.sleep(100);
+ }
+
+ @AfterClass
+ public static void tearDown() throws Exception {
+ stdIn.write("\n\n".getBytes());
+ serverStartThread.join(2000);
+ Assert.assertFalse(serverStartThread.isAlive());
+ }
+
+ @Test
+ public void doTestOption() throws Exception {
+ CloseableHttpClient httpclient = HttpClients.createDefault();
+ HttpOptions httpOption = new HttpOptions("http://localhost:9090/xxx/yyy.html");
+ CloseableHttpResponse response = httpclient.execute(httpOption);
+ Assert.assertEquals(200, response.getStatusLine().getStatusCode());
+ Assert.assertNotNull("Cors should have added a header: Access-Control-Allow-Origin", response.getLastHeader("Access-Control-Allow-Origin"));
+ Assert.assertEquals("Cors should have added a header: Access-Control-Allow-Origin: http://localhost:9090", "http://localhost:9090",
+ response.getLastHeader("Access-Control-Allow-Origin").getValue());
+ response.close();
+ }
+
+ @Test
+ public void doSomeBasicTest() throws Exception {
+ CloseableHttpClient httpclient = HttpClients.createDefault();
+ HttpGet httpget = new HttpGet("http://localhost:9090/testdir/test.html");
+ CloseableHttpResponse response = httpclient.execute(httpget);
+ HttpEntity entity = response.getEntity();
+ String string = new String(readContents(entity), "UTF-8");
+
+ Assert.assertNotNull("Cors should have added a header: Access-Control-Allow-Origin", response.getLastHeader("Access-Control-Allow-Origin"));
+ Assert.assertEquals("Cors should have added a header: Access-Control-Allow-Origin: http://localhost:9090", "http://localhost:9090",
+ response.getLastHeader("Access-Control-Allow-Origin").getValue());
+ Assert.assertEquals("<html>\n<head>\n<title>dummy</title>\n</head>\n<body>\n\t<h1>it works</h1>\n</body>\n</html>", string);
+ response.close();
+ }
+}
diff --git a/webserver/src/test/java/fi/iki/elonen/TestHttpServer.java b/webserver/src/test/java/fi/iki/elonen/TestHttpServer.java
index aa08f49..bb16741 100644
--- a/webserver/src/test/java/fi/iki/elonen/TestHttpServer.java
+++ b/webserver/src/test/java/fi/iki/elonen/TestHttpServer.java
@@ -50,7 +50,7 @@ import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
-public class TestHttpServer {
+public class TestHttpServer extends AbstractTestHttpServer {
private static PipedOutputStream stdIn;
@@ -146,27 +146,4 @@ public class TestHttpServer {
response.close();
}
-
- private byte[] readContents(HttpEntity entity) throws IOException {
- InputStream instream = entity.getContent();
- return readContents(instream);
- }
-
- private byte[] readContents(InputStream instream) throws IOException {
- byte[] bytes;
- ByteArrayOutputStream out = new ByteArrayOutputStream();
-
- try {
- byte[] buffer = new byte[1024];
- int count;
- while ((count = instream.read(buffer)) >= 0) {
- out.write(buffer, 0, count);
- }
- bytes = out.toByteArray();
- } finally {
- instream.close();
- }
- return bytes;
- }
-
}
diff --git a/websocket/pom.xml b/websocket/pom.xml
index 55d2fde..4cb99a1 100644
--- a/websocket/pom.xml
+++ b/websocket/pom.xml
@@ -62,4 +62,7 @@
<scope>test</scope>
</dependency>
</dependencies>
+ <properties>
+ <minimal.coverage>0.67</minimal.coverage>
+ </properties>
</project>
diff --git a/websocket/src/main/java/fi/iki/elonen/NanoWebSocketServer.java b/websocket/src/main/java/fi/iki/elonen/NanoWebSocketServer.java
index e23abe2..73e5f67 100644
--- a/websocket/src/main/java/fi/iki/elonen/NanoWebSocketServer.java
+++ b/websocket/src/main/java/fi/iki/elonen/NanoWebSocketServer.java
@@ -96,13 +96,13 @@ public abstract class NanoWebSocketServer extends NanoHTTPD {
this.handshakeResponse.addHeader(NanoWebSocketServer.HEADER_CONNECTION, NanoWebSocketServer.HEADER_CONNECTION_VALUE);
}
- public void close(CloseCode code, String reason) throws IOException {
+ public void close(CloseCode code, String reason, boolean initiatedByRemote) throws IOException {
State oldState = this.state;
this.state = State.CLOSING;
if (oldState == State.OPEN) {
sendFrame(new CloseFrame(code, reason));
} else {
- doClose(code, reason, false);
+ doClose(code, reason, initiatedByRemote);
}
}
@@ -149,13 +149,7 @@ public abstract class NanoWebSocketServer extends NanoHTTPD {
// Answer for my requested close
doClose(code, reason, false);
} else {
- // Answer close request from other endpoint and close self
- State oldState = this.state;
- this.state = State.CLOSING;
- if (oldState == State.OPEN) {
- sendFrame(new CloseFrame(code, reason));
- }
- doClose(code, reason, true);
+ close(code, reason, true);
}
}
diff --git a/websocket/src/test/java/fi/iki/elonen/samples/echo/EchoWebSocketsTest.java b/websocket/src/test/java/fi/iki/elonen/samples/echo/EchoWebSocketsTest.java
index 6061023..e90ae71 100644
--- a/websocket/src/test/java/fi/iki/elonen/samples/echo/EchoWebSocketsTest.java
+++ b/websocket/src/test/java/fi/iki/elonen/samples/echo/EchoWebSocketsTest.java
@@ -68,6 +68,8 @@ public class EchoWebSocketsTest {
SimpleEchoSocket socket = new SimpleEchoSocket();
socket.getToSendMessages().add("Hello");
socket.getToSendMessages().add("Thanks for the conversation.");
+ socket.getToSendMessages().add(createString(31000));
+ socket.getToSendMessages().add(createString(65400));
try {
client.start();
URI echoUri = new URI(destUri);
@@ -84,9 +86,17 @@ public class EchoWebSocketsTest {
e.printStackTrace();
}
}
- Assert.assertEquals(2, socket.getReceivedMessages().size());
+ Assert.assertEquals(4, socket.getReceivedMessages().size());
Assert.assertEquals("Hello", socket.getReceivedMessages().get(0));
Assert.assertEquals("Thanks for the conversation.", socket.getReceivedMessages().get(1));
}
+
+ private String createString(int i) {
+ StringBuilder builder = new StringBuilder();
+ while (builder.length() < i) {
+ builder.append("A very long text.");
+ }
+ return builder.toString();
+ }
}