aboutsummaryrefslogtreecommitdiff
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/.gitignore2
-rw-r--r--core/pom.xml109
-rw-r--r--core/src/main/java/fi/iki/elonen/NanoHTTPD.java2795
-rw-r--r--core/src/main/java/fi/iki/elonen/util/ServerRunner.java75
-rw-r--r--core/src/main/resources/META-INF/nanohttpd/default-mimetypes.properties30
-rw-r--r--core/src/main/resources/META-INF/nanohttpd/mimetypes.properties1
-rw-r--r--core/src/site/site.xml41
-rw-r--r--core/src/test/java/fi/iki/elonen/HttpChunkedResponseTest.java111
-rw-r--r--core/src/test/java/fi/iki/elonen/HttpDeleteRequestTest.java128
-rw-r--r--core/src/test/java/fi/iki/elonen/HttpGetRequestTest.java259
-rw-r--r--core/src/test/java/fi/iki/elonen/HttpHeadRequestTest.java236
-rw-r--r--core/src/test/java/fi/iki/elonen/HttpKeepAliveTest.java119
-rw-r--r--core/src/test/java/fi/iki/elonen/HttpParsingTest.java57
-rw-r--r--core/src/test/java/fi/iki/elonen/HttpPostRequestTest.java229
-rw-r--r--core/src/test/java/fi/iki/elonen/HttpPutRequestTest.java59
-rw-r--r--core/src/test/java/fi/iki/elonen/HttpSSLServerTest.java89
-rw-r--r--core/src/test/java/fi/iki/elonen/HttpServerTest.java225
-rw-r--r--core/src/test/java/fi/iki/elonen/HttpSessionHeadersTest.java58
-rw-r--r--core/src/test/java/fi/iki/elonen/InvalidRequestTest.java79
-rw-r--r--core/src/test/java/fi/iki/elonen/JavaIOTempDirExistTest.java87
-rw-r--r--core/src/test/java/fi/iki/elonen/MimeTest.java62
-rw-r--r--core/src/test/java/fi/iki/elonen/SSLServerSocketFactoryTest.java90
-rw-r--r--core/src/test/java/fi/iki/elonen/ServerSocketFactoryTest.java102
-rw-r--r--core/src/test/java/fi/iki/elonen/integration/CookieIntegrationTest.java130
-rw-r--r--core/src/test/java/fi/iki/elonen/integration/GZipIntegrationTest.java168
-rw-r--r--core/src/test/java/fi/iki/elonen/integration/GetAndPostIntegrationTest.java177
-rw-r--r--core/src/test/java/fi/iki/elonen/integration/IntegrationTestBase.java58
-rw-r--r--core/src/test/java/fi/iki/elonen/integration/PutStreamIntegrationTest.java85
-rw-r--r--core/src/test/java/fi/iki/elonen/integration/ShutdownTest.java64
-rw-r--r--core/src/test/resources/META-INF/nanohttpd/mimetypes.properties3
-rw-r--r--core/src/test/resources/file-upload-test.htm32
-rw-r--r--core/src/test/resources/keystore.jksbin0 -> 2276 bytes
-rw-r--r--core/src/test/resources/multipart-form-test.htm32
33 files changed, 3955 insertions, 1837 deletions
diff --git a/core/.gitignore b/core/.gitignore
new file mode 100644
index 0000000..868a6b2
--- /dev/null
+++ b/core/.gitignore
@@ -0,0 +1,2 @@
+/.settings/
+/LICENSE.txt
diff --git a/core/pom.xml b/core/pom.xml
index e82d9d0..0e4762d 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -1,83 +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>
-
- <groupId>fi.iki.elonen</groupId>
- <artifactId>nanohttpd</artifactId>
- <version>2.1.0</version>
- <packaging>jar</packaging>
-
- <name>NanoHttpd-Core</name>
- <url>https://github.com/NanoHttpd/nanohttpd</url>
-
- <dependencies>
- <dependency>
- <groupId>junit</groupId>
- <artifactId>junit</artifactId>
- <version>4.8.2</version>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>org.apache.httpcomponents</groupId>
- <artifactId>httpclient</artifactId>
- <version>4.2.5</version>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>org.apache.httpcomponents</groupId>
- <artifactId>httpmime</artifactId>
- <version>4.2.5</version>
- <scope>test</scope>
- </dependency>
- </dependencies>
-
- <build>
- <extensions>
- <extension>
- <groupId>org.jvnet.wagon-svn</groupId>
- <artifactId>wagon-svn</artifactId>
- <version>1.8</version>
- </extension>
- <extension>
- <groupId>org.apache.maven.wagon</groupId>
- <artifactId>wagon-ftp</artifactId>
- <version>1.0-alpha-6</version>
- </extension>
- </extensions>
-
- <plugins>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-source-plugin</artifactId>
- <version>2.2.1</version>
- <executions>
- <execution>
- <id>attach-sources</id>
- <goals>
- <goal>jar</goal>
- </goals>
- </execution>
- </executions>
- </plugin>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-release-plugin</artifactId>
- <version>2.4</version>
- </plugin>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-javadoc-plugin</artifactId>
- <version>2.9</version>
- </plugin>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-compiler-plugin</artifactId>
- <version>2.3.1</version>
- <configuration>
- <source>1.6</source>
- <target>1.6</target>
- </configuration>
- </plugin>
- </plugins>
- </build>
+<project 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</version>
+ </parent>
+ <artifactId>nanohttpd</artifactId>
+ <packaging>jar</packaging>
+ <name>NanoHttpd-Core</name>
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpclient</artifactId>
+ <version>4.2.5</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpmime</artifactId>
+ <version>4.2.5</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ <properties>
+ <minimal.coverage>0.82</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 73fea3c..394db9f 100644
--- a/core/src/main/java/fi/iki/elonen/NanoHTTPD.java
+++ b/core/src/main/java/fi/iki/elonen/NanoHTTPD.java
@@ -1,35 +1,108 @@
package fi.iki.elonen;
-import java.io.*;
+/*
+ * #%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.BufferedInputStream;
+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.RandomAccessFile;
+import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.net.SocketTimeoutException;
+import java.net.URL;
import java.net.URLDecoder;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
+import java.nio.charset.Charset;
+import java.security.KeyStore;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
+import java.util.Collections;
import java.util.Date;
+import java.util.Enumeration;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
-import java.util.Set;
+import java.util.Properties;
import java.util.StringTokenizer;
import java.util.TimeZone;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.zip.GZIPOutputStream;
+
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLServerSocket;
+import javax.net.ssl.SSLServerSocketFactory;
+import javax.net.ssl.TrustManagerFactory;
+
+import fi.iki.elonen.NanoHTTPD.Response.IStatus;
+import fi.iki.elonen.NanoHTTPD.Response.Status;
/**
* A simple, tiny, nicely embeddable HTTP server in Java
* <p/>
* <p/>
* NanoHTTPD
- * <p></p>Copyright (c) 2012-2013 by Paul S. Hawke, 2001,2005-2013 by Jarno Elonen, 2010 by Konstantinos Togias</p>
+ * <p>
+ * Copyright (c) 2012-2013 by Paul S. Hawke, 2001,2005-2013 by Jarno Elonen,
+ * 2010 by Konstantinos Togias
+ * </p>
* <p/>
* <p/>
* <b>Features + limitations: </b>
@@ -38,8 +111,10 @@ import java.util.TimeZone;
* <li>Only one Java file</li>
* <li>Java 5 compatible</li>
* <li>Released as open source, Modified BSD licence</li>
- * <li>No fixed config files, logging, authorization etc. (Implement yourself if you need them.)</li>
- * <li>Supports parameter parsing of GET and POST methods (+ rudimentary PUT support in 1.25)</li>
+ * <li>No fixed config files, logging, authorization etc. (Implement yourself if
+ * you need them.)</li>
+ * <li>Supports parameter parsing of GET and POST methods (+ rudimentary PUT
+ * support in 1.25)</li>
* <li>Supports both dynamic content and file serving</li>
* <li>Supports file upload (since version 1.2, 2010)</li>
* <li>Supports partial content (streaming)</li>
@@ -53,8 +128,9 @@ import java.util.TimeZone;
* <li>File server does the 301 redirection trick for directories without '/'</li>
* <li>File server supports simple skipping for files (continue download)</li>
* <li>File server serves also very long files without memory overhead</li>
- * <li>Contains a built-in list of most common mime types</li>
- * <li>All header names are converted lowercase so they don't vary between browsers/clients</li>
+ * <li>Contains a built-in list of most common MIME types</li>
+ * <li>All header names are converted to lower case so they don't vary between
+ * browsers/clients</li>
* <p/>
* </ul>
* <p/>
@@ -66,949 +142,905 @@ import java.util.TimeZone;
* <p/>
* </ul>
* <p/>
- * See the separate "LICENSE.md" file for the distribution license (Modified BSD licence)
+ * See the separate "LICENSE.md" file for the distribution license (Modified BSD
+ * licence)
*/
public abstract class NanoHTTPD {
- /**
- * Maximum time to wait on Socket.getInputStream().read() (in milliseconds)
- * This is required as the Keep-Alive HTTP connections would otherwise
- * block the socket reading thread forever (or as long the browser is open).
- */
- public static final int SOCKET_READ_TIMEOUT = 5000;
- /**
- * Common mime type for dynamic content: plain text
- */
- public static final String MIME_PLAINTEXT = "text/plain";
- /**
- * Common mime type for dynamic content: html
- */
- public static final String MIME_HTML = "text/html";
- /**
- * Pseudo-Parameter to use to store the actual query string in the parameters map for later re-processing.
- */
- private static final String QUERY_STRING_PARAMETER = "NanoHttpd.QUERY_STRING";
- private final String hostname;
- private final int myPort;
- private ServerSocket myServerSocket;
- private Set<Socket> openConnections = new HashSet<Socket>();
- private Thread myThread;
+
/**
* Pluggable strategy for asynchronously executing requests.
*/
- private AsyncRunner asyncRunner;
- /**
- * Pluggable strategy for creating and cleaning up temporary files.
- */
- private TempFileManagerFactory tempFileManagerFactory;
+ public interface AsyncRunner {
- /**
- * Constructs an HTTP server on given port.
- */
- public NanoHTTPD(int port) {
- this(null, port);
+ void closeAll();
+
+ void closed(ClientHandler clientHandler);
+
+ void exec(ClientHandler code);
}
/**
- * Constructs an HTTP server on given hostname and port.
+ * The runnable that will be used for every new client connection.
*/
- public NanoHTTPD(String hostname, int port) {
- this.hostname = hostname;
- this.myPort = port;
- setTempFileManagerFactory(new DefaultTempFileManagerFactory());
- setAsyncRunner(new DefaultAsyncRunner());
- }
+ public class ClientHandler implements Runnable {
- private static final void safeClose(Closeable closeable) {
- if (closeable != null) {
- try {
- closeable.close();
- } catch (IOException e) {
- }
+ private final InputStream inputStream;
+
+ private final Socket acceptSocket;
+
+ private ClientHandler(InputStream inputStream, Socket acceptSocket) {
+ this.inputStream = inputStream;
+ this.acceptSocket = acceptSocket;
}
- }
- private static final void safeClose(Socket closeable) {
- if (closeable != null) {
- try {
- closeable.close();
- } catch (IOException e) {
- }
+ public void close() {
+ safeClose(this.inputStream);
+ safeClose(this.acceptSocket);
}
- }
- private static final void safeClose(ServerSocket closeable) {
- if (closeable != null) {
+ @Override
+ public void run() {
+ OutputStream outputStream = null;
try {
- closeable.close();
- } catch (IOException e) {
+ outputStream = this.acceptSocket.getOutputStream();
+ TempFileManager tempFileManager = NanoHTTPD.this.tempFileManagerFactory.create();
+ HTTPSession session = new HTTPSession(tempFileManager, this.inputStream, outputStream, this.acceptSocket.getInetAddress());
+ while (!this.acceptSocket.isClosed()) {
+ session.execute();
+ }
+ } catch (Exception e) {
+ // When the socket is closed by the client,
+ // we throw our own SocketException
+ // to break the "keep alive" loop above. If
+ // the exception was anything other
+ // than the expected SocketException OR a
+ // SocketTimeoutException, print the
+ // stacktrace
+ if (!(e instanceof SocketException && "NanoHttpd Shutdown".equals(e.getMessage())) && !(e instanceof SocketTimeoutException)) {
+ NanoHTTPD.LOG.log(Level.FINE, "Communication with the client broken", e);
+ }
+ } finally {
+ safeClose(outputStream);
+ safeClose(this.inputStream);
+ safeClose(this.acceptSocket);
+ NanoHTTPD.this.asyncRunner.closed(this);
}
}
}
- /**
- * Start the server.
- *
- * @throws IOException if the socket is in use.
- */
- public void start() throws IOException {
- myServerSocket = new ServerSocket();
- myServerSocket.bind((hostname != null) ? new InetSocketAddress(hostname, myPort) : new InetSocketAddress(myPort));
-
- myThread = new Thread(new Runnable() {
- @Override
- public void run() {
- do {
- try {
- final Socket finalAccept = myServerSocket.accept();
- registerConnection(finalAccept);
- finalAccept.setSoTimeout(SOCKET_READ_TIMEOUT);
- final InputStream inputStream = finalAccept.getInputStream();
- asyncRunner.exec(new Runnable() {
- @Override
- public void run() {
- OutputStream outputStream = null;
- try {
- outputStream = finalAccept.getOutputStream();
- TempFileManager tempFileManager = tempFileManagerFactory.create();
- HTTPSession session = new HTTPSession(tempFileManager, inputStream, outputStream, finalAccept.getInetAddress());
- while (!finalAccept.isClosed()) {
- session.execute();
- }
- } catch (Exception e) {
- // When the socket is closed by the client, we throw our own SocketException
- // to break the "keep alive" loop above.
- if (!(e instanceof SocketException && "NanoHttpd Shutdown".equals(e.getMessage()))) {
- e.printStackTrace();
- }
- } finally {
- safeClose(outputStream);
- safeClose(inputStream);
- safeClose(finalAccept);
- unRegisterConnection(finalAccept);
- }
- }
- });
- } catch (IOException e) {
- }
- } while (!myServerSocket.isClosed());
- }
- });
- myThread.setDaemon(true);
- myThread.setName("NanoHttpd Main Listener");
- myThread.start();
- }
+ public static class Cookie {
- /**
- * Stop the server.
- */
- public void stop() {
- try {
- safeClose(myServerSocket);
- closeAllConnections();
- if (myThread != null) {
- myThread.join();
- }
- } catch (Exception e) {
- e.printStackTrace();
+ public static String getHTTPTime(int days) {
+ Calendar calendar = Calendar.getInstance();
+ SimpleDateFormat dateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
+ dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
+ calendar.add(Calendar.DAY_OF_MONTH, days);
+ return dateFormat.format(calendar.getTime());
}
- }
- /**
- * Registers that a new connection has been set up.
- *
- * @param socket the {@link Socket} for the connection.
- */
- public synchronized void registerConnection(Socket socket) {
- openConnections.add(socket);
- }
-
- /**
- * Registers that a connection has been closed
- *
- * @param socket
- * the {@link Socket} for the connection.
- */
- public synchronized void unRegisterConnection(Socket socket) {
- openConnections.remove(socket);
- }
+ private final String n, v, e;
- /**
- * Forcibly closes all connections that are open.
- */
- public synchronized void closeAllConnections() {
- for (Socket socket : openConnections) {
- safeClose(socket);
+ public Cookie(String name, String value) {
+ this(name, value, 30);
}
- }
- public final int getListeningPort() {
- return myServerSocket == null ? -1 : myServerSocket.getLocalPort();
- }
+ public Cookie(String name, String value, int numDays) {
+ this.n = name;
+ this.v = value;
+ this.e = getHTTPTime(numDays);
+ }
- public final boolean wasStarted() {
- return myServerSocket != null && myThread != null;
- }
+ public Cookie(String name, String value, String expires) {
+ this.n = name;
+ this.v = value;
+ this.e = expires;
+ }
- public final boolean isAlive() {
- return wasStarted() && !myServerSocket.isClosed() && myThread.isAlive();
+ public String getHTTPHeader() {
+ String fmt = "%s=%s; expires=%s";
+ return String.format(fmt, this.n, this.v, this.e);
+ }
}
/**
- * Create a response with known length.
- *
- * TODO: Remove this implementation when updating to v2.2.0.
+ * Provides rudimentary support for cookies. Doesn't support 'path',
+ * 'secure' nor 'httpOnly'. Feel free to improve it and/or add unsupported
+ * features.
+ *
+ * @author LordFokas
*/
- public static Response newFixedLengthResponse(Response.Status status, String mimeType, String txt) {
- return new Response(status, mimeType, txt);
- }
+ public class CookieHandler implements Iterable<String> {
- /**
- * Override this to customize the server.
- * <p/>
- * <p/>
- * (By default, this delegates to serveFile() and allows directory listing.)
- *
- * @param uri Percent-decoded URI without parameters, for example "/index.cgi"
- * @param method "GET", "POST" etc.
- * @param parms Parsed, percent decoded parameters from URI and, in case of POST, data.
- * @param headers Header entries, percent decoded
- * @return HTTP response, see class Response for details
- */
- @Deprecated
- public Response serve(String uri, Method method, Map<String, String> headers, Map<String, String> parms,
- Map<String, String> files) {
- return new Response(Response.Status.NOT_FOUND, MIME_PLAINTEXT, "Not Found");
- }
+ private final HashMap<String, String> cookies = new HashMap<String, String>();
- /**
- * Override this to customize the server.
- * <p/>
- * <p/>
- * (By default, this delegates to serveFile() and allows directory listing.)
- *
- * @param session The HTTP session
- * @return HTTP response, see class Response for details
- */
- public Response serve(IHTTPSession session) {
- Map<String, String> files = new HashMap<String, String>();
- Method method = session.getMethod();
- if (Method.PUT.equals(method) || Method.POST.equals(method)) {
- try {
- session.parseBody(files);
- } catch (IOException ioe) {
- return new Response(Response.Status.INTERNAL_ERROR, MIME_PLAINTEXT, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage());
- } catch (ResponseException re) {
- return new Response(re.getStatus(), MIME_PLAINTEXT, re.getMessage());
+ private final ArrayList<Cookie> queue = new ArrayList<Cookie>();
+
+ public CookieHandler(Map<String, String> httpHeaders) {
+ String raw = httpHeaders.get("cookie");
+ if (raw != null) {
+ String[] tokens = raw.split(";");
+ for (String token : tokens) {
+ String[] data = token.trim().split("=");
+ if (data.length == 2) {
+ this.cookies.put(data[0], data[1]);
+ }
+ }
}
}
- Map<String, String> parms = session.getParms();
- parms.put(QUERY_STRING_PARAMETER, session.getQueryParameterString());
- return serve(session.getUri(), method, session.getHeaders(), parms, files);
- }
+ /**
+ * Set a cookie with an expiration date from a month ago, effectively
+ * deleting it on the client side.
+ *
+ * @param name
+ * The cookie name.
+ */
+ public void delete(String name) {
+ set(name, "-delete-", -30);
+ }
- /**
- * Decode percent encoded <code>String</code> values.
- *
- * @param str the percent encoded <code>String</code>
- * @return expanded form of the input, for example "foo%20bar" becomes "foo bar"
- */
- protected String decodePercent(String str) {
- String decoded = null;
- try {
- decoded = URLDecoder.decode(str, "UTF8");
- } catch (UnsupportedEncodingException ignored) {
+ @Override
+ public Iterator<String> iterator() {
+ return this.cookies.keySet().iterator();
}
- return decoded;
- }
- /**
- * Decode parameters from a URL, handing the case where a single parameter name might have been
- * supplied several times, by return lists of values. In general these lists will contain a single
- * element.
- *
- * @param parms original <b>NanoHttpd</b> parameters values, as passed to the <code>serve()</code> method.
- * @return a map of <code>String</code> (parameter name) to <code>List&lt;String&gt;</code> (a list of the values supplied).
- */
- protected Map<String, List<String>> decodeParameters(Map<String, String> parms) {
- return this.decodeParameters(parms.get(QUERY_STRING_PARAMETER));
- }
+ /**
+ * Read a cookie from the HTTP Headers.
+ *
+ * @param name
+ * The cookie's name.
+ * @return The cookie's value if it exists, null otherwise.
+ */
+ public String read(String name) {
+ return this.cookies.get(name);
+ }
- /**
- * Decode parameters from a URL, handing the case where a single parameter name might have been
- * supplied several times, by return lists of values. In general these lists will contain a single
- * element.
- *
- * @param queryString a query string pulled from the URL.
- * @return a map of <code>String</code> (parameter name) to <code>List&lt;String&gt;</code> (a list of the values supplied).
- */
- protected Map<String, List<String>> decodeParameters(String queryString) {
- Map<String, List<String>> parms = new HashMap<String, List<String>>();
- if (queryString != null) {
- StringTokenizer st = new StringTokenizer(queryString, "&");
- while (st.hasMoreTokens()) {
- String e = st.nextToken();
- int sep = e.indexOf('=');
- String propertyName = (sep >= 0) ? decodePercent(e.substring(0, sep)).trim() : decodePercent(e).trim();
- if (!parms.containsKey(propertyName)) {
- parms.put(propertyName, new ArrayList<String>());
- }
- String propertyValue = (sep >= 0) ? decodePercent(e.substring(sep + 1)) : null;
- if (propertyValue != null) {
- parms.get(propertyName).add(propertyValue);
- }
+ public void set(Cookie cookie) {
+ this.queue.add(cookie);
+ }
+
+ /**
+ * Sets a cookie.
+ *
+ * @param name
+ * The cookie's name.
+ * @param value
+ * The cookie's value.
+ * @param expires
+ * How many days until the cookie expires.
+ */
+ public void set(String name, String value, int expires) {
+ this.queue.add(new Cookie(name, value, Cookie.getHTTPTime(expires)));
+ }
+
+ /**
+ * Internally used by the webserver to add all queued cookies into the
+ * Response's HTTP Headers.
+ *
+ * @param response
+ * The Response object to which headers the queued cookies
+ * will be added.
+ */
+ public void unloadQueue(Response response) {
+ for (Cookie cookie : this.queue) {
+ response.addHeader("Set-Cookie", cookie.getHTTPHeader());
}
}
- return parms;
}
- // ------------------------------------------------------------------------------- //
- //
- // Threading Strategy.
- //
- // ------------------------------------------------------------------------------- //
-
/**
- * Pluggable strategy for asynchronously executing requests.
- *
- * @param asyncRunner new strategy for handling threads.
+ * Default threading strategy for NanoHTTPD.
+ * <p/>
+ * <p>
+ * By default, the server spawns a new Thread for every incoming request.
+ * These are set to <i>daemon</i> status, and named according to the request
+ * number. The name is useful when profiling the application.
+ * </p>
*/
- public void setAsyncRunner(AsyncRunner asyncRunner) {
- this.asyncRunner = asyncRunner;
- }
+ public static class DefaultAsyncRunner implements AsyncRunner {
- // ------------------------------------------------------------------------------- //
- //
- // Temp file handling strategy.
- //
- // ------------------------------------------------------------------------------- //
+ private long requestCount;
- /**
- * Pluggable strategy for creating and cleaning up temporary files.
- *
- * @param tempFileManagerFactory new strategy for handling temp files.
- */
- public void setTempFileManagerFactory(TempFileManagerFactory tempFileManagerFactory) {
- this.tempFileManagerFactory = tempFileManagerFactory;
- }
+ private final List<ClientHandler> running = Collections.synchronizedList(new ArrayList<NanoHTTPD.ClientHandler>());
- /**
- * HTTP Request methods, with the ability to decode a <code>String</code> back to its enum value.
- */
- public enum Method {
- GET, PUT, POST, DELETE, HEAD, OPTIONS;
+ /**
+ * @return a list with currently running clients.
+ */
+ public List<ClientHandler> getRunning() {
+ return running;
+ }
- static Method lookup(String method) {
- for (Method m : Method.values()) {
- if (m.toString().equalsIgnoreCase(method)) {
- return m;
- }
+ @Override
+ public void closeAll() {
+ // copy of the list for concurrency
+ for (ClientHandler clientHandler : new ArrayList<ClientHandler>(this.running)) {
+ clientHandler.close();
}
- return null;
}
- }
- /**
- * Pluggable strategy for asynchronously executing requests.
- */
- public interface AsyncRunner {
- void exec(Runnable code);
- }
+ @Override
+ public void closed(ClientHandler clientHandler) {
+ this.running.remove(clientHandler);
+ }
- /**
- * Factory to create temp file managers.
- */
- public interface TempFileManagerFactory {
- TempFileManager create();
+ @Override
+ public void exec(ClientHandler clientHandler) {
+ ++this.requestCount;
+ Thread t = new Thread(clientHandler);
+ t.setDaemon(true);
+ t.setName("NanoHttpd Request Processor (#" + this.requestCount + ")");
+ this.running.add(clientHandler);
+ t.start();
+ }
}
- // ------------------------------------------------------------------------------- //
-
/**
- * Temp file manager.
+ * Default strategy for creating and cleaning up temporary files.
* <p/>
- * <p>Temp file managers are created 1-to-1 with incoming requests, to create and cleanup
- * temporary files created as a result of handling the request.</p>
+ * <p>
+ * By default, files are created by <code>File.createTempFile()</code> in
+ * the directory specified.
+ * </p>
*/
- public interface TempFileManager {
- TempFile createTempFile() throws Exception;
+ public static class DefaultTempFile implements TempFile {
- void clear();
- }
+ private final File file;
- /**
- * A temp file.
- * <p/>
- * <p>Temp files are responsible for managing the actual temporary storage and cleaning
- * themselves up when no longer needed.</p>
- */
- public interface TempFile {
- OutputStream open() throws Exception;
+ private final OutputStream fstream;
- void delete() throws Exception;
+ public DefaultTempFile(File tempdir) throws IOException {
+ this.file = File.createTempFile("NanoHTTPD-", "", tempdir);
+ this.fstream = new FileOutputStream(this.file);
+ }
- String getName();
- }
+ @Override
+ public void delete() throws Exception {
+ safeClose(this.fstream);
+ if (!this.file.delete()) {
+ throw new Exception("could not delete temporary file");
+ }
+ }
- /**
- * Default threading strategy for NanoHttpd.
- * <p/>
- * <p>By default, the server spawns a new Thread for every incoming request. These are set
- * to <i>daemon</i> status, and named according to the request number. The name is
- * useful when profiling the application.</p>
- */
- public static class DefaultAsyncRunner implements AsyncRunner {
- private long requestCount;
+ @Override
+ public String getName() {
+ return this.file.getAbsolutePath();
+ }
@Override
- public void exec(Runnable code) {
- ++requestCount;
- Thread t = new Thread(code);
- t.setDaemon(true);
- t.setName("NanoHttpd Request Processor (#" + requestCount + ")");
- t.start();
+ public OutputStream open() throws Exception {
+ return this.fstream;
}
}
/**
* Default strategy for creating and cleaning up temporary files.
* <p/>
- * <p></p>This class stores its files in the standard location (that is,
- * wherever <code>java.io.tmpdir</code> points to). Files are added
- * to an internal list, and deleted when no longer needed (that is,
- * when <code>clear()</code> is invoked at the end of processing a
- * request).</p>
+ * <p>
+ * This class stores its files in the standard location (that is, wherever
+ * <code>java.io.tmpdir</code> points to). Files are added to an internal
+ * list, and deleted when no longer needed (that is, when
+ * <code>clear()</code> is invoked at the end of processing a request).
+ * </p>
*/
public static class DefaultTempFileManager implements TempFileManager {
- private final String tmpdir;
+
+ private final File tmpdir;
+
private final List<TempFile> tempFiles;
public DefaultTempFileManager() {
- tmpdir = System.getProperty("java.io.tmpdir");
- tempFiles = new ArrayList<TempFile>();
- }
-
- @Override
- public TempFile createTempFile() throws Exception {
- DefaultTempFile tempFile = new DefaultTempFile(tmpdir);
- tempFiles.add(tempFile);
- return tempFile;
+ this.tmpdir = new File(System.getProperty("java.io.tmpdir"));
+ if (!tmpdir.exists()) {
+ tmpdir.mkdirs();
+ }
+ this.tempFiles = new ArrayList<TempFile>();
}
@Override
public void clear() {
- for (TempFile file : tempFiles) {
+ for (TempFile file : this.tempFiles) {
try {
file.delete();
} catch (Exception ignored) {
+ NanoHTTPD.LOG.log(Level.WARNING, "could not delete file ", ignored);
}
}
- tempFiles.clear();
+ this.tempFiles.clear();
+ }
+
+ @Override
+ public TempFile createTempFile(String filename_hint) throws Exception {
+ DefaultTempFile tempFile = new DefaultTempFile(this.tmpdir);
+ this.tempFiles.add(tempFile);
+ return tempFile;
}
}
/**
* Default strategy for creating and cleaning up temporary files.
- * <p/>
- * <p></p></[>By default, files are created by <code>File.createTempFile()</code> in
- * the directory specified.</p>
*/
- public static class DefaultTempFile implements TempFile {
- private File file;
- private OutputStream fstream;
-
- public DefaultTempFile(String tempdir) throws IOException {
- file = File.createTempFile("NanoHTTPD-", "", new File(tempdir));
- fstream = new FileOutputStream(file);
- }
-
- @Override
- public OutputStream open() throws Exception {
- return fstream;
- }
-
- @Override
- public void delete() throws Exception {
- safeClose(fstream);
- file.delete();
- }
+ private class DefaultTempFileManagerFactory implements TempFileManagerFactory {
@Override
- public String getName() {
- return file.getAbsolutePath();
+ public TempFileManager create() {
+ return new DefaultTempFileManager();
}
}
- /**
- * HTTP response. Return one of these from serve().
- */
- public static class Response {
- /**
- * HTTP status code after processing, e.g. "200 OK", HTTP_OK
- */
- private IStatus status;
- /**
- * MIME type of content, e.g. "text/html"
- */
- private String mimeType;
- /**
- * Data of the response, may be null.
- */
- private InputStream data;
- /**
- * Headers for the HTTP response. Use addHeader() to add lines.
- */
- private Map<String, String> header = new HashMap<String, String>();
- /**
- * The request method that spawned this response.
- */
- private Method requestMethod;
- /**
- * Use chunkedTransfer
- */
- private boolean chunkedTransfer;
+ private static final String CHARSET_REGEX = "[ |\t]*(charset)[ |\t]*=[ |\t]*['|\"]?([^\"^'^;]*)['|\"]?";
- /**
- * Default constructor: response = HTTP_OK, mime = MIME_HTML and your supplied message
- */
- public Response(String msg) {
- this(Status.OK, MIME_HTML, msg);
- }
+ private static final Pattern CHARSET_PATTERN = Pattern.compile(CHARSET_REGEX, Pattern.CASE_INSENSITIVE);
- /**
- * Basic constructor.
- */
- public Response(IStatus status, String mimeType, InputStream data) {
- this.status = status;
- this.mimeType = mimeType;
- this.data = data;
- }
+ private static final String BOUNDARY_REGEX = "[ |\t]*(boundary)[ |\t]*=[ |\t]*['|\"]?([^\"^'^;]*)['|\"]?";
- /**
- * Convenience method that makes an InputStream out of given text.
- */
- public Response(IStatus status, String mimeType, String txt) {
- this.status = status;
- this.mimeType = mimeType;
- try {
- this.data = txt != null ? new ByteArrayInputStream(txt.getBytes("UTF-8")) : null;
- } catch (java.io.UnsupportedEncodingException uee) {
- uee.printStackTrace();
- }
- }
+ private static final Pattern BOUNDARY_PATTERN = Pattern.compile(BOUNDARY_REGEX, Pattern.CASE_INSENSITIVE);
- /**
- * Adds given line to the header.
- */
- public void addHeader(String name, String value) {
- header.put(name, value);
- }
+ /**
+ * Creates a normal ServerSocket for TCP connections
+ */
+ public static class DefaultServerSocketFactory implements ServerSocketFactory {
- public String getHeader(String name) {
- return header.get(name);
+ @Override
+ public ServerSocket create() throws IOException {
+ return new ServerSocket();
}
- /**
- * Sends given response to the socket.
- */
- protected void send(OutputStream outputStream) {
- String mime = mimeType;
- SimpleDateFormat gmtFrmt = new SimpleDateFormat("E, d MMM yyyy HH:mm:ss 'GMT'", Locale.US);
- gmtFrmt.setTimeZone(TimeZone.getTimeZone("GMT"));
-
- try {
- if (status == null) {
- throw new Error("sendResponse(): Status can't be null.");
- }
- PrintWriter pw = new PrintWriter(outputStream);
- pw.print("HTTP/1.1 " + status.getDescription() + " \r\n");
-
- if (mime != null) {
- pw.print("Content-Type: " + mime + "\r\n");
- }
-
- if (header == null || header.get("Date") == null) {
- pw.print("Date: " + gmtFrmt.format(new Date()) + "\r\n");
- }
-
- if (header != null) {
- for (String key : header.keySet()) {
- String value = header.get(key);
- pw.print(key + ": " + value + "\r\n");
- }
- }
+ }
- sendConnectionHeaderIfNotAlreadyPresent(pw, header);
+ /**
+ * Creates a new SSLServerSocket
+ */
+ public static class SecureServerSocketFactory implements ServerSocketFactory {
- if (requestMethod != Method.HEAD && chunkedTransfer) {
- sendAsChunked(outputStream, pw);
- } else {
- int pending = data != null ? data.available() : 0;
- sendContentLengthHeaderIfNotAlreadyPresent(pw, header, pending);
- pw.print("\r\n");
- pw.flush();
- sendAsFixedLength(outputStream, pending);
- }
- outputStream.flush();
- safeClose(data);
- } catch (IOException ioe) {
- // Couldn't write? No can do.
- }
- }
+ private SSLServerSocketFactory sslServerSocketFactory;
- protected void sendContentLengthHeaderIfNotAlreadyPresent(PrintWriter pw, Map<String, String> header, int size) {
- if (!headerAlreadySent(header, "content-length")) {
- pw.print("Content-Length: "+ size +"\r\n");
- }
- }
+ private String[] sslProtocols;
- protected void sendConnectionHeaderIfNotAlreadyPresent(PrintWriter pw, Map<String, String> header) {
- if (!headerAlreadySent(header, "connection")) {
- pw.print("Connection: keep-alive\r\n");
- }
+ public SecureServerSocketFactory(SSLServerSocketFactory sslServerSocketFactory, String[] sslProtocols) {
+ this.sslServerSocketFactory = sslServerSocketFactory;
+ this.sslProtocols = sslProtocols;
}
- private boolean headerAlreadySent(Map<String, String> header, String name) {
- boolean alreadySent = false;
- for (String headerName : header.keySet()) {
- alreadySent |= headerName.equalsIgnoreCase(name);
+ @Override
+ public ServerSocket create() throws IOException {
+ SSLServerSocket ss = null;
+ ss = (SSLServerSocket) this.sslServerSocketFactory.createServerSocket();
+ if (this.sslProtocols != null) {
+ ss.setEnabledProtocols(this.sslProtocols);
+ } else {
+ ss.setEnabledProtocols(ss.getSupportedProtocols());
}
- return alreadySent;
+ ss.setUseClientMode(false);
+ ss.setWantClientAuth(false);
+ ss.setNeedClientAuth(false);
+ return ss;
}
- private void sendAsChunked(OutputStream outputStream, PrintWriter pw) throws IOException {
- pw.print("Transfer-Encoding: chunked\r\n");
- pw.print("\r\n");
- pw.flush();
- int BUFFER_SIZE = 16 * 1024;
- byte[] CRLF = "\r\n".getBytes();
- byte[] buff = new byte[BUFFER_SIZE];
- int read;
- while ((read = data.read(buff)) > 0) {
- outputStream.write(String.format("%x\r\n", read).getBytes());
- outputStream.write(buff, 0, read);
- outputStream.write(CRLF);
- }
- outputStream.write(String.format("0\r\n\r\n").getBytes());
- }
+ }
- private void sendAsFixedLength(OutputStream outputStream, int pending) throws IOException {
- if (requestMethod != Method.HEAD && data != null) {
- int BUFFER_SIZE = 16 * 1024;
- byte[] buff = new byte[BUFFER_SIZE];
- while (pending > 0) {
- int read = data.read(buff, 0, ((pending > BUFFER_SIZE) ? BUFFER_SIZE : pending));
- if (read <= 0) {
- break;
- }
- outputStream.write(buff, 0, read);
- pending -= read;
- }
- }
- }
+ private static final String CONTENT_DISPOSITION_REGEX = "([ |\t]*Content-Disposition[ |\t]*:)(.*)";
- public IStatus getStatus() {
- return status;
- }
+ private static final Pattern CONTENT_DISPOSITION_PATTERN = Pattern.compile(CONTENT_DISPOSITION_REGEX, Pattern.CASE_INSENSITIVE);
- public void setStatus(Status status) {
- this.status = status;
- }
+ private static final String CONTENT_TYPE_REGEX = "([ |\t]*content-type[ |\t]*:)(.*)";
- public String getMimeType() {
- return mimeType;
- }
+ private static final Pattern CONTENT_TYPE_PATTERN = Pattern.compile(CONTENT_TYPE_REGEX, Pattern.CASE_INSENSITIVE);
- public void setMimeType(String mimeType) {
- this.mimeType = mimeType;
- }
+ private static final String CONTENT_DISPOSITION_ATTRIBUTE_REGEX = "[ |\t]*([a-zA-Z]*)[ |\t]*=[ |\t]*['|\"]([^\"^']*)['|\"]";
- public InputStream getData() {
- return data;
- }
+ private static final Pattern CONTENT_DISPOSITION_ATTRIBUTE_PATTERN = Pattern.compile(CONTENT_DISPOSITION_ATTRIBUTE_REGEX);
- public void setData(InputStream data) {
- this.data = data;
- }
+ protected class HTTPSession implements IHTTPSession {
- public Method getRequestMethod() {
- return requestMethod;
- }
+ private static final int REQUEST_BUFFER_LEN = 512;
- public void setRequestMethod(Method requestMethod) {
- this.requestMethod = requestMethod;
- }
+ private static final int MEMORY_STORE_LIMIT = 1024;
- public void setChunkedTransfer(boolean chunkedTransfer) {
- this.chunkedTransfer = chunkedTransfer;
- }
+ public static final int BUFSIZE = 8192;
- public interface IStatus {
- int getRequestStatus();
- String getDescription();
- }
+ public static final int MAX_HEADER_SIZE = 1024;
- /**
- * Some HTTP response status codes
- */
- public enum Status implements IStatus {
- SWITCH_PROTOCOL(101, "Switching Protocols"),
+ private final TempFileManager tempFileManager;
- OK(200, "OK"),
- CREATED(201, "Created"),
- ACCEPTED(202, "Accepted"),
- NO_CONTENT(204, "No Content"),
- PARTIAL_CONTENT(206, "Partial Content"),
+ private final OutputStream outputStream;
- REDIRECT(301, "Moved Permanently"),
- TEMPORARY_REDIRECT(302, "Moved Temporarily"),
- NOT_MODIFIED(304, "Not Modified"),
+ private final BufferedInputStream inputStream;
- BAD_REQUEST(400, "Bad Request"),
- UNAUTHORIZED(401, "Unauthorized"),
- FORBIDDEN(403, "Forbidden"),
- NOT_FOUND(404, "Not Found"),
- METHOD_NOT_ALLOWED(405, "Method Not Allowed"),
- RANGE_NOT_SATISFIABLE(416, "Requested Range Not Satisfiable"),
+ private int splitbyte;
- INTERNAL_ERROR(500, "Internal Server Error");
+ private int rlen;
- private final int requestStatus;
- private final String description;
+ private String uri;
- Status(int requestStatus, String description) {
- this.requestStatus = requestStatus;
- this.description = description;
- }
+ private Method method;
- @Override
- public int getRequestStatus() {
- return this.requestStatus;
- }
+ private Map<String, String> parms;
- @Override
- public String getDescription() {
- return "" + this.requestStatus + " " + description;
- }
- }
- }
+ private Map<String, String> headers;
- public static final class ResponseException extends Exception {
+ private CookieHandler cookies;
- private final Response.Status status;
+ private String queryParameterString;
- public ResponseException(Response.Status status, String message) {
- super(message);
- this.status = status;
- }
+ private String remoteIp;
- public ResponseException(Response.Status status, String message, Exception e) {
- super(message, e);
- this.status = status;
- }
+ private String protocolVersion;
- public Response.Status getStatus() {
- return status;
+ public HTTPSession(TempFileManager tempFileManager, InputStream inputStream, OutputStream outputStream) {
+ this.tempFileManager = tempFileManager;
+ this.inputStream = new BufferedInputStream(inputStream, HTTPSession.BUFSIZE);
+ this.outputStream = outputStream;
}
- }
- /**
- * Default strategy for creating and cleaning up temporary files.
- */
- private class DefaultTempFileManagerFactory implements TempFileManagerFactory {
- @Override
- public TempFileManager create() {
- return new DefaultTempFileManager();
+ public HTTPSession(TempFileManager tempFileManager, InputStream inputStream, OutputStream outputStream, InetAddress inetAddress) {
+ this.tempFileManager = tempFileManager;
+ this.inputStream = new BufferedInputStream(inputStream, HTTPSession.BUFSIZE);
+ this.outputStream = outputStream;
+ this.remoteIp = inetAddress.isLoopbackAddress() || inetAddress.isAnyLocalAddress() ? "127.0.0.1" : inetAddress.getHostAddress().toString();
+ this.headers = new HashMap<String, String>();
}
- }
- /**
- * Handles one session, i.e. parses the HTTP request and returns the response.
- */
- public interface IHTTPSession {
- void execute() throws IOException;
+ /**
+ * Decodes the sent headers and loads the data into Key/value pairs
+ */
+ private void decodeHeader(BufferedReader in, Map<String, String> pre, Map<String, String> parms, Map<String, String> headers) throws ResponseException {
+ try {
+ // Read the request line
+ String inLine = in.readLine();
+ if (inLine == null) {
+ return;
+ }
- Map<String, String> getParms();
+ StringTokenizer st = new StringTokenizer(inLine);
+ if (!st.hasMoreTokens()) {
+ throw new ResponseException(Response.Status.BAD_REQUEST, "BAD REQUEST: Syntax error. Usage: GET /example/file.html");
+ }
- Map<String, String> getHeaders();
+ pre.put("method", st.nextToken());
- /**
- * @return the path part of the URL.
- */
- String getUri();
+ if (!st.hasMoreTokens()) {
+ throw new ResponseException(Response.Status.BAD_REQUEST, "BAD REQUEST: Missing URI. Usage: GET /example/file.html");
+ }
- String getQueryParameterString();
+ String uri = st.nextToken();
- Method getMethod();
+ // Decode parameters from the URI
+ int qmi = uri.indexOf('?');
+ if (qmi >= 0) {
+ decodeParms(uri.substring(qmi + 1), parms);
+ uri = decodePercent(uri.substring(0, qmi));
+ } else {
+ uri = decodePercent(uri);
+ }
- InputStream getInputStream();
+ // If there's another token, its protocol version,
+ // followed by HTTP headers.
+ // NOTE: this now forces header names lower case since they are
+ // case insensitive and vary by client.
+ if (st.hasMoreTokens()) {
+ protocolVersion = st.nextToken();
+ } else {
+ protocolVersion = "HTTP/1.1";
+ NanoHTTPD.LOG.log(Level.FINE, "no protocol version specified, strange. Assuming HTTP/1.1.");
+ }
+ String line = in.readLine();
+ while (line != null && line.trim().length() > 0) {
+ int p = line.indexOf(':');
+ if (p >= 0) {
+ headers.put(line.substring(0, p).trim().toLowerCase(Locale.US), line.substring(p + 1).trim());
+ }
+ line = in.readLine();
+ }
- CookieHandler getCookies();
+ pre.put("uri", uri);
+ } catch (IOException ioe) {
+ throw new ResponseException(Response.Status.INTERNAL_ERROR, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage(), ioe);
+ }
+ }
/**
- * Adds the files in the request body to the files map.
- * @arg files - map to modify
+ * Decodes the Multipart Body data and put it into Key/Value pairs.
*/
- void parseBody(Map<String, String> files) throws IOException, ResponseException;
- }
+ private void decodeMultipartFormData(String boundary, String encoding, ByteBuffer fbuf, Map<String, String> parms, Map<String, String> files) throws ResponseException {
+ try {
+ int[] boundary_idxs = getBoundaryPositions(fbuf, boundary.getBytes());
+ if (boundary_idxs.length < 2) {
+ throw new ResponseException(Response.Status.BAD_REQUEST, "BAD REQUEST: Content type is multipart/form-data but contains less than two boundary strings.");
+ }
- protected class HTTPSession implements IHTTPSession {
- public static final int BUFSIZE = 8192;
- private final TempFileManager tempFileManager;
- private final OutputStream outputStream;
- private PushbackInputStream inputStream;
- private int splitbyte;
- private int rlen;
- private String uri;
- private Method method;
- private Map<String, String> parms;
- private Map<String, String> headers;
- private CookieHandler cookies;
- private String queryParameterString;
+ byte[] part_header_buff = new byte[MAX_HEADER_SIZE];
+ for (int bi = 0; bi < boundary_idxs.length - 1; bi++) {
+ fbuf.position(boundary_idxs[bi]);
+ int len = (fbuf.remaining() < MAX_HEADER_SIZE) ? fbuf.remaining() : MAX_HEADER_SIZE;
+ fbuf.get(part_header_buff, 0, len);
+ BufferedReader in = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(part_header_buff, 0, len), Charset.forName(encoding)), len);
+
+ int headerLines = 0;
+ // First line is boundary string
+ String mpline = in.readLine();
+ headerLines++;
+ if (!mpline.contains(boundary)) {
+ throw new ResponseException(Response.Status.BAD_REQUEST, "BAD REQUEST: Content type is multipart/form-data but chunk does not start with boundary.");
+ }
- public HTTPSession(TempFileManager tempFileManager, InputStream inputStream, OutputStream outputStream) {
- this.tempFileManager = tempFileManager;
- this.inputStream = new PushbackInputStream(inputStream, BUFSIZE);
- this.outputStream = outputStream;
+ String part_name = null, file_name = null, content_type = null;
+ // Parse the reset of the header lines
+ mpline = in.readLine();
+ headerLines++;
+ while (mpline != null && mpline.trim().length() > 0) {
+ Matcher matcher = CONTENT_DISPOSITION_PATTERN.matcher(mpline);
+ if (matcher.matches()) {
+ String attributeString = matcher.group(2);
+ matcher = CONTENT_DISPOSITION_ATTRIBUTE_PATTERN.matcher(attributeString);
+ while (matcher.find()) {
+ String key = matcher.group(1);
+ if (key.equalsIgnoreCase("name")) {
+ part_name = matcher.group(2);
+ } else if (key.equalsIgnoreCase("filename")) {
+ file_name = matcher.group(2);
+ }
+ }
+ }
+ matcher = CONTENT_TYPE_PATTERN.matcher(mpline);
+ if (matcher.matches()) {
+ content_type = matcher.group(2).trim();
+ }
+ mpline = in.readLine();
+ headerLines++;
+ }
+ int part_header_len = 0;
+ while (headerLines-- > 0) {
+ part_header_len = scipOverNewLine(part_header_buff, part_header_len);
+ }
+ // Read the part data
+ if (part_header_len >= len - 4) {
+ throw new ResponseException(Response.Status.INTERNAL_ERROR, "Multipart header size exceeds MAX_HEADER_SIZE.");
+ }
+ int part_data_start = boundary_idxs[bi] + part_header_len;
+ int part_data_end = boundary_idxs[bi + 1] - 4;
+
+ fbuf.position(part_data_start);
+ if (content_type == null) {
+ // Read the part into a string
+ byte[] data_bytes = new byte[part_data_end - part_data_start];
+ fbuf.get(data_bytes);
+ parms.put(part_name, new String(data_bytes, encoding));
+ } else {
+ // Read it into a file
+ String path = saveTmpFile(fbuf, part_data_start, part_data_end - part_data_start, file_name);
+ if (!files.containsKey(part_name)) {
+ files.put(part_name, path);
+ } else {
+ int count = 2;
+ while (files.containsKey(part_name + count)) {
+ count++;
+ }
+ files.put(part_name + count, path);
+ }
+ parms.put(part_name, file_name);
+ }
+ }
+ } catch (ResponseException re) {
+ throw re;
+ } catch (Exception e) {
+ throw new ResponseException(Response.Status.INTERNAL_ERROR, e.toString());
+ }
}
- public HTTPSession(TempFileManager tempFileManager, InputStream inputStream, OutputStream outputStream, InetAddress inetAddress) {
- this.tempFileManager = tempFileManager;
- this.inputStream = new PushbackInputStream(inputStream, BUFSIZE);
- this.outputStream = outputStream;
- String remoteIp = inetAddress.isLoopbackAddress() || inetAddress.isAnyLocalAddress() ? "127.0.0.1" : inetAddress.getHostAddress().toString();
- headers = new HashMap<String, String>();
+ private int scipOverNewLine(byte[] part_header_buff, int index) {
+ while (part_header_buff[index] != '\n') {
+ index++;
+ }
+ return ++index;
+ }
- headers.put("remote-addr", remoteIp);
- headers.put("http-client-ip", remoteIp);
+ /**
+ * Decodes parameters in percent-encoded URI-format ( e.g.
+ * "name=Jack%20Daniels&pass=Single%20Malt" ) and adds them to given
+ * Map. NOTE: this doesn't support multiple identical keys due to the
+ * simplicity of Map.
+ */
+ private void decodeParms(String parms, Map<String, String> p) {
+ if (parms == null) {
+ this.queryParameterString = "";
+ return;
+ }
+
+ this.queryParameterString = parms;
+ StringTokenizer st = new StringTokenizer(parms, "&");
+ while (st.hasMoreTokens()) {
+ String e = st.nextToken();
+ int sep = e.indexOf('=');
+ if (sep >= 0) {
+ p.put(decodePercent(e.substring(0, sep)).trim(), decodePercent(e.substring(sep + 1)));
+ } else {
+ p.put(decodePercent(e).trim(), "");
+ }
+ }
}
@Override
public void execute() throws IOException {
+ Response r = null;
try {
// Read the first 8192 bytes.
// The full header should fit in here.
// Apache's default header limit is 8KB.
- // Do NOT assume that a single read will get the entire header at once!
- byte[] buf = new byte[BUFSIZE];
- splitbyte = 0;
- rlen = 0;
- {
- int read = -1;
- try {
- read = inputStream.read(buf, 0, BUFSIZE);
- } catch (Exception e) {
- safeClose(inputStream);
- safeClose(outputStream);
- throw new SocketException("NanoHttpd Shutdown");
- }
- if (read == -1) {
- // socket was been closed
- safeClose(inputStream);
- safeClose(outputStream);
- throw new SocketException("NanoHttpd Shutdown");
- }
- while (read > 0) {
- rlen += read;
- splitbyte = findHeaderEnd(buf, rlen);
- if (splitbyte > 0)
- break;
- read = inputStream.read(buf, rlen, BUFSIZE - rlen);
+ // Do NOT assume that a single read will get the entire header
+ // at once!
+ byte[] buf = new byte[HTTPSession.BUFSIZE];
+ this.splitbyte = 0;
+ this.rlen = 0;
+
+ int read = -1;
+ this.inputStream.mark(HTTPSession.BUFSIZE);
+ try {
+ read = this.inputStream.read(buf, 0, HTTPSession.BUFSIZE);
+ } catch (Exception e) {
+ safeClose(this.inputStream);
+ safeClose(this.outputStream);
+ throw new SocketException("NanoHttpd Shutdown");
+ }
+ if (read == -1) {
+ // socket was been closed
+ safeClose(this.inputStream);
+ safeClose(this.outputStream);
+ throw new SocketException("NanoHttpd Shutdown");
+ }
+ while (read > 0) {
+ this.rlen += read;
+ this.splitbyte = findHeaderEnd(buf, this.rlen);
+ if (this.splitbyte > 0) {
+ break;
}
+ read = this.inputStream.read(buf, this.rlen, HTTPSession.BUFSIZE - this.rlen);
}
- if (splitbyte < rlen) {
- inputStream.unread(buf, splitbyte, rlen - splitbyte);
+ if (this.splitbyte < this.rlen) {
+ this.inputStream.reset();
+ this.inputStream.skip(this.splitbyte);
}
- parms = new HashMap<String, String>();
- if(null == headers) {
- headers = new HashMap<String, String>();
+ this.parms = new HashMap<String, String>();
+ if (null == this.headers) {
+ this.headers = new HashMap<String, String>();
+ } else {
+ this.headers.clear();
}
// Create a BufferedReader for parsing the header.
- BufferedReader hin = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(buf, 0, rlen)));
+ BufferedReader hin = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(buf, 0, this.rlen)));
// Decode the header into parms and header java properties
Map<String, String> pre = new HashMap<String, String>();
- decodeHeader(hin, pre, parms, headers);
+ decodeHeader(hin, pre, this.parms, this.headers);
+
+ if (null != this.remoteIp) {
+ this.headers.put("remote-addr", this.remoteIp);
+ this.headers.put("http-client-ip", this.remoteIp);
+ }
- method = Method.lookup(pre.get("method"));
- if (method == null) {
+ this.method = Method.lookup(pre.get("method"));
+ if (this.method == null) {
throw new ResponseException(Response.Status.BAD_REQUEST, "BAD REQUEST: Syntax error.");
}
- uri = pre.get("uri");
+ this.uri = pre.get("uri");
- cookies = new CookieHandler(headers);
+ this.cookies = new CookieHandler(this.headers);
+
+ String connection = this.headers.get("connection");
+ boolean keepAlive = protocolVersion.equals("HTTP/1.1") && (connection == null || !connection.matches("(?i).*close.*"));
// Ok, now do the serve()
- Response r = serve(this);
+
+ // TODO: long body_size = getBodySize();
+ // TODO: long pos_before_serve = this.inputStream.totalRead()
+ // (requires implementaion for totalRead())
+ r = serve(this);
+ // TODO: this.inputStream.skip(body_size -
+ // (this.inputStream.totalRead() - pos_before_serve))
+
if (r == null) {
throw new ResponseException(Response.Status.INTERNAL_ERROR, "SERVER INTERNAL ERROR: Serve() returned a null response.");
} else {
- cookies.unloadQueue(r);
- r.setRequestMethod(method);
- r.send(outputStream);
+ String acceptEncoding = this.headers.get("accept-encoding");
+ this.cookies.unloadQueue(r);
+ r.setRequestMethod(this.method);
+ r.setGzipEncoding(useGzipWhenAccepted(r) && acceptEncoding != null && acceptEncoding.contains("gzip"));
+ r.setKeepAlive(keepAlive);
+ r.send(this.outputStream);
+ }
+ if (!keepAlive || "close".equalsIgnoreCase(r.getHeader("connection"))) {
+ throw new SocketException("NanoHttpd Shutdown");
}
} catch (SocketException e) {
// throw it out to close socket object (finalAccept)
throw e;
} catch (SocketTimeoutException ste) {
- throw ste;
+ // treat socket timeouts the same way we treat socket exceptions
+ // i.e. close the stream & finalAccept object by throwing the
+ // exception up the call stack.
+ throw ste;
} catch (IOException ioe) {
- Response r = new Response(Response.Status.INTERNAL_ERROR, MIME_PLAINTEXT, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage());
- r.send(outputStream);
- safeClose(outputStream);
+ Response resp = newFixedLengthResponse(Response.Status.INTERNAL_ERROR, NanoHTTPD.MIME_PLAINTEXT, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage());
+ resp.send(this.outputStream);
+ safeClose(this.outputStream);
} catch (ResponseException re) {
- Response r = new Response(re.getStatus(), MIME_PLAINTEXT, re.getMessage());
- r.send(outputStream);
- safeClose(outputStream);
+ Response resp = newFixedLengthResponse(re.getStatus(), NanoHTTPD.MIME_PLAINTEXT, re.getMessage());
+ resp.send(this.outputStream);
+ safeClose(this.outputStream);
} finally {
- tempFileManager.clear();
+ safeClose(r);
+ this.tempFileManager.clear();
+ }
+ }
+
+ /**
+ * Find byte index separating header from body. It must be the last byte
+ * of the first two sequential new lines.
+ */
+ private int findHeaderEnd(final byte[] buf, int rlen) {
+ int splitbyte = 0;
+ while (splitbyte + 1 < rlen) {
+
+ // RFC2616
+ if (buf[splitbyte] == '\r' && buf[splitbyte + 1] == '\n' && splitbyte + 3 < rlen && buf[splitbyte + 2] == '\r' && buf[splitbyte + 3] == '\n') {
+ return splitbyte + 4;
+ }
+
+ // tolerance
+ if (buf[splitbyte] == '\n' && buf[splitbyte + 1] == '\n') {
+ return splitbyte + 2;
+ }
+ splitbyte++;
+ }
+ return 0;
+ }
+
+ /**
+ * Find the byte positions where multipart boundaries start. This reads
+ * a large block at a time and uses a temporary buffer to optimize
+ * (memory mapped) file access.
+ */
+ private int[] getBoundaryPositions(ByteBuffer b, byte[] boundary) {
+ int[] res = new int[0];
+ if (b.remaining() < boundary.length) {
+ return res;
}
+
+ int search_window_pos = 0;
+ byte[] search_window = new byte[4 * 1024 + boundary.length];
+
+ int first_fill = (b.remaining() < search_window.length) ? b.remaining() : search_window.length;
+ b.get(search_window, 0, first_fill);
+ int new_bytes = first_fill - boundary.length;
+
+ do {
+ // Search the search_window
+ for (int j = 0; j < new_bytes; j++) {
+ for (int i = 0; i < boundary.length; i++) {
+ if (search_window[j + i] != boundary[i])
+ break;
+ if (i == boundary.length - 1) {
+ // Match found, add it to results
+ int[] new_res = new int[res.length + 1];
+ System.arraycopy(res, 0, new_res, 0, res.length);
+ new_res[res.length] = search_window_pos + j;
+ res = new_res;
+ }
+ }
+ }
+ search_window_pos += new_bytes;
+
+ // Copy the end of the buffer to the start
+ System.arraycopy(search_window, search_window.length - boundary.length, search_window, 0, boundary.length);
+
+ // Refill search_window
+ new_bytes = search_window.length - boundary.length;
+ new_bytes = (b.remaining() < new_bytes) ? b.remaining() : new_bytes;
+ b.get(search_window, boundary.length, new_bytes);
+ } while (new_bytes > 0);
+ return res;
}
@Override
- public void parseBody(Map<String, String> files) throws IOException, ResponseException {
- RandomAccessFile randomAccessFile = null;
- BufferedReader in = null;
+ public CookieHandler getCookies() {
+ return this.cookies;
+ }
+
+ @Override
+ public final Map<String, String> getHeaders() {
+ return this.headers;
+ }
+
+ @Override
+ public final InputStream getInputStream() {
+ return this.inputStream;
+ }
+
+ @Override
+ public final Method getMethod() {
+ return this.method;
+ }
+
+ @Override
+ public final Map<String, String> getParms() {
+ return this.parms;
+ }
+
+ @Override
+ public String getQueryParameterString() {
+ return this.queryParameterString;
+ }
+
+ private RandomAccessFile getTmpBucket() {
try {
+ TempFile tempFile = this.tempFileManager.createTempFile(null);
+ return new RandomAccessFile(tempFile.getName(), "rw");
+ } catch (Exception e) {
+ throw new Error(e); // we won't recover, so throw an error
+ }
+ }
- randomAccessFile = getTmpBucket();
+ @Override
+ public final String getUri() {
+ return this.uri;
+ }
- long size;
- if (headers.containsKey("content-length")) {
- size = Integer.parseInt(headers.get("content-length"));
- } else if (splitbyte < rlen) {
- size = rlen - splitbyte;
+ /**
+ * Deduce body length in bytes. Either from "content-length" header or
+ * read bytes.
+ */
+ public long getBodySize() {
+ if (this.headers.containsKey("content-length")) {
+ return Long.parseLong(this.headers.get("content-length"));
+ } else if (this.splitbyte < this.rlen) {
+ return this.rlen - this.splitbyte;
+ }
+ return 0;
+ }
+
+ @Override
+ public void parseBody(Map<String, String> files) throws IOException, ResponseException {
+ RandomAccessFile randomAccessFile = null;
+ try {
+ long size = getBodySize();
+ ByteArrayOutputStream baos = null;
+ DataOutput request_data_output = null;
+
+ // Store the request in memory or a file, depending on size
+ if (size < MEMORY_STORE_LIMIT) {
+ baos = new ByteArrayOutputStream();
+ request_data_output = new DataOutputStream(baos);
} else {
- size = 0;
+ randomAccessFile = getTmpBucket();
+ request_data_output = randomAccessFile;
}
- // Now read all the body and write it to f
- byte[] buf = new byte[512];
- while (rlen >= 0 && size > 0) {
- rlen = inputStream.read(buf, 0, (int)Math.min(size, 512));
- size -= rlen;
- if (rlen > 0) {
- randomAccessFile.write(buf, 0, rlen);
+ // Read all the body and write it to request_data_output
+ byte[] buf = new byte[REQUEST_BUFFER_LEN];
+ while (this.rlen >= 0 && size > 0) {
+ this.rlen = this.inputStream.read(buf, 0, (int) Math.min(size, REQUEST_BUFFER_LEN));
+ size -= this.rlen;
+ if (this.rlen > 0) {
+ request_data_output.write(buf, 0, this.rlen);
}
}
- // Get the raw body as a byte []
- ByteBuffer fbuf = randomAccessFile.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, randomAccessFile.length());
- randomAccessFile.seek(0);
-
- // Create a BufferedReader for easily reading it as string.
- InputStream bin = new FileInputStream(randomAccessFile.getFD());
- in = new BufferedReader(new InputStreamReader(bin));
+ ByteBuffer fbuf = null;
+ if (baos != null) {
+ fbuf = ByteBuffer.wrap(baos.toByteArray(), 0, baos.size());
+ } else {
+ fbuf = randomAccessFile.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, randomAccessFile.length());
+ randomAccessFile.seek(0);
+ }
// If the method is POST, there may be parameters
// in data section, too, read it:
- if (Method.POST.equals(method)) {
+ if (Method.POST.equals(this.method)) {
String contentType = "";
- String contentTypeHeader = headers.get("content-type");
+ String contentTypeHeader = this.headers.get("content-type");
StringTokenizer st = null;
if (contentTypeHeader != null) {
@@ -1021,426 +1053,1093 @@ public abstract class NanoHTTPD {
if ("multipart/form-data".equalsIgnoreCase(contentType)) {
// Handle multipart/form-data
if (!st.hasMoreTokens()) {
- throw new ResponseException(Response.Status.BAD_REQUEST, "BAD REQUEST: Content type is multipart/form-data but boundary missing. Usage: GET /example/file.html");
+ throw new ResponseException(Response.Status.BAD_REQUEST,
+ "BAD REQUEST: Content type is multipart/form-data but boundary missing. Usage: GET /example/file.html");
}
-
- String boundaryStartString = "boundary=";
- int boundaryContentStart = contentTypeHeader.indexOf(boundaryStartString) + boundaryStartString.length();
- String boundary = contentTypeHeader.substring(boundaryContentStart, contentTypeHeader.length());
- if (boundary.startsWith("\"") && boundary.endsWith("\"")) {
- boundary = boundary.substring(1, boundary.length() - 1);
- }
-
- decodeMultipartData(boundary, fbuf, in, parms, files);
+ decodeMultipartFormData(getAttributeFromContentHeader(contentTypeHeader, BOUNDARY_PATTERN, null), //
+ getAttributeFromContentHeader(contentTypeHeader, CHARSET_PATTERN, "US-ASCII"), fbuf, this.parms, files);
} else {
- String postLine = "";
- StringBuilder postLineBuffer = new StringBuilder();
- char pbuf[] = new char[512];
- int read = in.read(pbuf);
- while (read >= 0 && !postLine.endsWith("\r\n")) {
- postLine = String.valueOf(pbuf, 0, read);
- postLineBuffer.append(postLine);
- read = in.read(pbuf);
- }
- postLine = postLineBuffer.toString().trim();
+ byte[] postBytes = new byte[fbuf.remaining()];
+ fbuf.get(postBytes);
+ String postLine = new String(postBytes).trim();
// Handle application/x-www-form-urlencoded
if ("application/x-www-form-urlencoded".equalsIgnoreCase(contentType)) {
- decodeParms(postLine, parms);
+ decodeParms(postLine, this.parms);
} else if (postLine.length() != 0) {
- // Special case for raw POST data => create a special files entry "postData" with raw content data
- files.put("postData", postLine);
+ // Special case for raw POST data => create a
+ // special files entry "postData" with raw content
+ // data
+ files.put("postData", postLine);
}
}
- } else if (Method.PUT.equals(method)) {
- files.put("content", saveTmpFile(fbuf, 0, fbuf.limit()));
+ } else if (Method.PUT.equals(this.method)) {
+ files.put("content", saveTmpFile(fbuf, 0, fbuf.limit(), null));
}
} finally {
safeClose(randomAccessFile);
- safeClose(in);
}
}
+ private String getAttributeFromContentHeader(String contentTypeHeader, Pattern pattern, String defaultValue) {
+ Matcher matcher = pattern.matcher(contentTypeHeader);
+ return matcher.find() ? matcher.group(2) : defaultValue;
+ }
+
/**
- * Decodes the sent headers and loads the data into Key/value pairs
+ * Retrieves the content of a sent file and saves it to a temporary
+ * file. The full path to the saved file is returned.
*/
- private void decodeHeader(BufferedReader in, Map<String, String> pre, Map<String, String> parms, Map<String, String> headers)
- throws ResponseException {
- try {
- // Read the request line
- String inLine = in.readLine();
- if (inLine == null) {
- return;
+ private String saveTmpFile(ByteBuffer b, int offset, int len, String filename_hint) {
+ String path = "";
+ if (len > 0) {
+ FileOutputStream fileOutputStream = null;
+ try {
+ TempFile tempFile = this.tempFileManager.createTempFile(filename_hint);
+ ByteBuffer src = b.duplicate();
+ fileOutputStream = new FileOutputStream(tempFile.getName());
+ FileChannel dest = fileOutputStream.getChannel();
+ src.position(offset).limit(offset + len);
+ dest.write(src.slice());
+ path = tempFile.getName();
+ } catch (Exception e) { // Catch exception if any
+ throw new Error(e); // we won't recover, so throw an error
+ } finally {
+ safeClose(fileOutputStream);
}
+ }
+ return path;
+ }
+ }
- StringTokenizer st = new StringTokenizer(inLine);
- if (!st.hasMoreTokens()) {
- throw new ResponseException(Response.Status.BAD_REQUEST, "BAD REQUEST: Syntax error. Usage: GET /example/file.html");
- }
+ /**
+ * Handles one session, i.e. parses the HTTP request and returns the
+ * response.
+ */
+ public interface IHTTPSession {
- pre.put("method", st.nextToken());
+ void execute() throws IOException;
- if (!st.hasMoreTokens()) {
- throw new ResponseException(Response.Status.BAD_REQUEST, "BAD REQUEST: Missing URI. Usage: GET /example/file.html");
- }
+ CookieHandler getCookies();
- String uri = st.nextToken();
+ Map<String, String> getHeaders();
- // Decode parameters from the URI
- int qmi = uri.indexOf('?');
- if (qmi >= 0) {
- decodeParms(uri.substring(qmi + 1), parms);
- uri = decodePercent(uri.substring(0, qmi));
- } else {
- uri = decodePercent(uri);
- }
+ InputStream getInputStream();
- // If there's another token, it's protocol version,
- // followed by HTTP headers. Ignore version but parse headers.
- // NOTE: this now forces header names lowercase since they are
- // case insensitive and vary by client.
- if (st.hasMoreTokens()) {
- String line = in.readLine();
- while (line != null && line.trim().length() > 0) {
- int p = line.indexOf(':');
- if (p >= 0)
- headers.put(line.substring(0, p).trim().toLowerCase(Locale.US), line.substring(p + 1).trim());
- line = in.readLine();
- }
- }
+ Method getMethod();
- pre.put("uri", uri);
- } catch (IOException ioe) {
- throw new ResponseException(Response.Status.INTERNAL_ERROR, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage(), ioe);
+ Map<String, String> getParms();
+
+ String getQueryParameterString();
+
+ /**
+ * @return the path part of the URL.
+ */
+ String getUri();
+
+ /**
+ * Adds the files in the request body to the files map.
+ *
+ * @param files
+ * map to modify
+ */
+ void parseBody(Map<String, String> files) throws IOException, ResponseException;
+ }
+
+ /**
+ * HTTP Request methods, with the ability to decode a <code>String</code>
+ * back to its enum value.
+ */
+ public enum Method {
+ GET,
+ PUT,
+ POST,
+ DELETE,
+ HEAD,
+ OPTIONS,
+ TRACE,
+ CONNECT,
+ PATCH;
+
+ static Method lookup(String method) {
+ for (Method m : Method.values()) {
+ if (m.toString().equalsIgnoreCase(method)) {
+ return m;
+ }
}
+ return null;
+ }
+ }
+
+ /**
+ * HTTP response. Return one of these from serve().
+ */
+ public static class Response implements Closeable {
+
+ public interface IStatus {
+
+ String getDescription();
+
+ int getRequestStatus();
}
/**
- * Decodes the Multipart Body data and put it into Key/Value pairs.
+ * Some HTTP response status codes
*/
- private void decodeMultipartData(String boundary, ByteBuffer fbuf, BufferedReader in, Map<String, String> parms,
- Map<String, String> files) throws ResponseException {
- try {
- int[] bpositions = getBoundaryPositions(fbuf, boundary.getBytes());
- int boundarycount = 1;
- String mpline = in.readLine();
- while (mpline != null) {
- if (!mpline.contains(boundary)) {
- throw new ResponseException(Response.Status.BAD_REQUEST, "BAD REQUEST: Content type is multipart/form-data but next chunk does not start with boundary. Usage: GET /example/file.html");
- }
- boundarycount++;
- Map<String, String> item = new HashMap<String, String>();
- mpline = in.readLine();
- while (mpline != null && mpline.trim().length() > 0) {
- int p = mpline.indexOf(':');
- if (p != -1) {
- item.put(mpline.substring(0, p).trim().toLowerCase(Locale.US), mpline.substring(p + 1).trim());
- }
- mpline = in.readLine();
- }
- if (mpline != null) {
- String contentDisposition = item.get("content-disposition");
- if (contentDisposition == null) {
- throw new ResponseException(Response.Status.BAD_REQUEST, "BAD REQUEST: Content type is multipart/form-data but no content-disposition info found. Usage: GET /example/file.html");
- }
- StringTokenizer st = new StringTokenizer(contentDisposition, ";");
- Map<String, String> disposition = new HashMap<String, String>();
- while (st.hasMoreTokens()) {
- String token = st.nextToken().trim();
- int p = token.indexOf('=');
- if (p != -1) {
- disposition.put(token.substring(0, p).trim().toLowerCase(Locale.US), token.substring(p + 1).trim());
- }
- }
- String pname = disposition.get("name");
- pname = pname.substring(1, pname.length() - 1);
-
- String value = "";
- if (item.get("content-type") == null) {
- while (mpline != null && !mpline.contains(boundary)) {
- mpline = in.readLine();
- if (mpline != null) {
- int d = mpline.indexOf(boundary);
- if (d == -1) {
- value += mpline;
- } else {
- value += mpline.substring(0, d - 2);
- }
- }
- }
- } else {
- if (boundarycount > bpositions.length) {
- throw new ResponseException(Response.Status.INTERNAL_ERROR, "Error processing request");
- }
- int offset = stripMultipartHeaders(fbuf, bpositions[boundarycount - 2]);
- String path = saveTmpFile(fbuf, offset, bpositions[boundarycount - 1] - offset - 4);
- files.put(pname, path);
- value = disposition.get("filename");
- value = value.substring(1, value.length() - 1);
- do {
- mpline = in.readLine();
- } while (mpline != null && !mpline.contains(boundary));
- }
- parms.put(pname, value);
- }
- }
- } catch (IOException ioe) {
- throw new ResponseException(Response.Status.INTERNAL_ERROR, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage(), ioe);
+ public enum Status implements IStatus {
+ SWITCH_PROTOCOL(101, "Switching Protocols"),
+ OK(200, "OK"),
+ CREATED(201, "Created"),
+ ACCEPTED(202, "Accepted"),
+ NO_CONTENT(204, "No Content"),
+ PARTIAL_CONTENT(206, "Partial Content"),
+ REDIRECT(301, "Moved Permanently"),
+ TEMPORARY_REDIRECT(302, "Moved Temporarily"),
+ NOT_MODIFIED(304, "Not Modified"),
+ BAD_REQUEST(400, "Bad Request"),
+ UNAUTHORIZED(401, "Unauthorized"),
+ FORBIDDEN(403, "Forbidden"),
+ NOT_FOUND(404, "Not Found"),
+ METHOD_NOT_ALLOWED(405, "Method Not Allowed"),
+ NOT_ACCEPTABLE(406, "Not Acceptable"),
+ REQUEST_TIMEOUT(408, "Request Timeout"),
+ CONFLICT(409, "Conflict"),
+ RANGE_NOT_SATISFIABLE(416, "Requested Range Not Satisfiable"),
+ INTERNAL_ERROR(500, "Internal Server Error"),
+ NOT_IMPLEMENTED(501, "Not Implemented"),
+ UNSUPPORTED_HTTP_VERSION(505, "HTTP Version Not Supported");
+
+ private final int requestStatus;
+
+ private final String description;
+
+ Status(int requestStatus, String description) {
+ this.requestStatus = requestStatus;
+ this.description = description;
}
+
+ @Override
+ public String getDescription() {
+ return "" + this.requestStatus + " " + this.description;
+ }
+
+ @Override
+ public int getRequestStatus() {
+ return this.requestStatus;
+ }
+
}
/**
- * Find byte index separating header from body. It must be the last byte of the first two sequential new lines.
+ * Output stream that will automatically send every write to the wrapped
+ * OutputStream according to chunked transfer:
+ * http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6.1
*/
- private int findHeaderEnd(final byte[] buf, int rlen) {
- int splitbyte = 0;
- while (splitbyte + 3 < rlen) {
- if (buf[splitbyte] == '\r' && buf[splitbyte + 1] == '\n' && buf[splitbyte + 2] == '\r' && buf[splitbyte + 3] == '\n') {
- return splitbyte + 4;
- }
- splitbyte++;
+ private static class ChunkedOutputStream extends FilterOutputStream {
+
+ public ChunkedOutputStream(OutputStream out) {
+ super(out);
}
- return 0;
+
+ @Override
+ public void write(int b) throws IOException {
+ byte[] data = {
+ (byte) b
+ };
+ write(data, 0, 1);
+ }
+
+ @Override
+ public void write(byte[] b) throws IOException {
+ write(b, 0, b.length);
+ }
+
+ @Override
+ public void write(byte[] b, int off, int len) throws IOException {
+ if (len == 0)
+ return;
+ out.write(String.format("%x\r\n", len).getBytes());
+ out.write(b, off, len);
+ out.write("\r\n".getBytes());
+ }
+
+ public void finish() throws IOException {
+ out.write("0\r\n\r\n".getBytes());
+ }
+
}
/**
- * Find the byte positions where multipart boundaries start.
+ * HTTP status code after processing, e.g. "200 OK", Status.OK
*/
- private int[] getBoundaryPositions(ByteBuffer b, byte[] boundary) {
- int matchcount = 0;
- int matchbyte = -1;
- List<Integer> matchbytes = new ArrayList<Integer>();
- for (int i = 0; i < b.limit(); i++) {
- if (b.get(i) == boundary[matchcount]) {
- if (matchcount == 0)
- matchbyte = i;
- matchcount++;
- if (matchcount == boundary.length) {
- matchbytes.add(matchbyte);
- matchcount = 0;
- matchbyte = -1;
- }
- } else {
- i -= matchcount;
- matchcount = 0;
- matchbyte = -1;
- }
+ private IStatus status;
+
+ /**
+ * MIME type of content, e.g. "text/html"
+ */
+ private String mimeType;
+
+ /**
+ * Data of the response, may be null.
+ */
+ private InputStream data;
+
+ private long contentLength;
+
+ /**
+ * Headers for the HTTP response. Use addHeader() to add lines.
+ */
+ private final Map<String, String> header = new HashMap<String, String>();
+
+ /**
+ * The request method that spawned this response.
+ */
+ private Method requestMethod;
+
+ /**
+ * Use chunkedTransfer
+ */
+ private boolean chunkedTransfer;
+
+ private boolean encodeAsGzip;
+
+ private boolean keepAlive;
+
+ /**
+ * Creates a fixed length response if totalBytes>=0, otherwise chunked.
+ */
+ protected Response(IStatus status, String mimeType, InputStream data, long totalBytes) {
+ this.status = status;
+ this.mimeType = mimeType;
+ if (data == null) {
+ this.data = new ByteArrayInputStream(new byte[0]);
+ this.contentLength = 0L;
+ } else {
+ this.data = data;
+ this.contentLength = totalBytes;
}
- int[] ret = new int[matchbytes.size()];
- for (int i = 0; i < ret.length; i++) {
- ret[i] = matchbytes.get(i);
+ this.chunkedTransfer = this.contentLength < 0;
+ keepAlive = true;
+ }
+
+ @Override
+ public void close() throws IOException {
+ if (this.data != null) {
+ this.data.close();
}
- return ret;
}
/**
- * Retrieves the content of a sent file and saves it to a temporary file. The full path to the saved file is returned.
+ * Adds given line to the header.
*/
- private String saveTmpFile(ByteBuffer b, int offset, int len) {
- String path = "";
- if (len > 0) {
- FileOutputStream fileOutputStream = null;
- try {
- TempFile tempFile = tempFileManager.createTempFile();
- ByteBuffer src = b.duplicate();
- fileOutputStream = new FileOutputStream(tempFile.getName());
- FileChannel dest = fileOutputStream.getChannel();
- src.position(offset).limit(offset + len);
- dest.write(src.slice());
- path = tempFile.getName();
- } catch (Exception e) { // Catch exception if any
- throw new Error(e); // we won't recover, so throw an error
- } finally {
- safeClose(fileOutputStream);
+ public void addHeader(String name, String value) {
+ this.header.put(name, value);
+ }
+
+ public InputStream getData() {
+ return this.data;
+ }
+
+ public String getHeader(String name) {
+ for (String headerName : header.keySet()) {
+ if (headerName.equalsIgnoreCase(name)) {
+ return header.get(headerName);
}
}
- return path;
+ return null;
}
- private RandomAccessFile getTmpBucket() {
- try {
- TempFile tempFile = tempFileManager.createTempFile();
- return new RandomAccessFile(tempFile.getName(), "rw");
- } catch (Exception e) {
- throw new Error(e); // we won't recover, so throw an error
+ public String getMimeType() {
+ return this.mimeType;
+ }
+
+ public Method getRequestMethod() {
+ return this.requestMethod;
+ }
+
+ public IStatus getStatus() {
+ return this.status;
+ }
+
+ public void setGzipEncoding(boolean encodeAsGzip) {
+ this.encodeAsGzip = encodeAsGzip;
+ }
+
+ public void setKeepAlive(boolean useKeepAlive) {
+ this.keepAlive = useKeepAlive;
+ }
+
+ private static boolean headerAlreadySent(Map<String, String> header, String name) {
+ boolean alreadySent = false;
+ for (String headerName : header.keySet()) {
+ alreadySent |= headerName.equalsIgnoreCase(name);
}
+ return alreadySent;
}
/**
- * It returns the offset separating multipart file headers from the file's data.
+ * Sends given response to the socket.
*/
- private int stripMultipartHeaders(ByteBuffer b, int offset) {
- int i;
- for (i = offset; i < b.limit(); i++) {
- if (b.get(i) == '\r' && b.get(++i) == '\n' && b.get(++i) == '\r' && b.get(++i) == '\n') {
- break;
+ protected void send(OutputStream outputStream) {
+ String mime = this.mimeType;
+ SimpleDateFormat gmtFrmt = new SimpleDateFormat("E, d MMM yyyy HH:mm:ss 'GMT'", Locale.US);
+ gmtFrmt.setTimeZone(TimeZone.getTimeZone("GMT"));
+
+ try {
+ if (this.status == null) {
+ throw new Error("sendResponse(): Status can't be null.");
}
+ PrintWriter pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(outputStream, "UTF-8")), false);
+ pw.print("HTTP/1.1 " + this.status.getDescription() + " \r\n");
+
+ if (mime != null) {
+ pw.print("Content-Type: " + mime + "\r\n");
+ }
+
+ if (this.header == null || this.header.get("Date") == null) {
+ pw.print("Date: " + gmtFrmt.format(new Date()) + "\r\n");
+ }
+
+ if (this.header != null) {
+ for (String key : this.header.keySet()) {
+ String value = this.header.get(key);
+ pw.print(key + ": " + value + "\r\n");
+ }
+ }
+
+ if (!headerAlreadySent(header, "connection")) {
+ pw.print("Connection: " + (this.keepAlive ? "keep-alive" : "close") + "\r\n");
+ }
+
+ if (headerAlreadySent(this.header, "content-length")) {
+ encodeAsGzip = false;
+ }
+
+ if (encodeAsGzip) {
+ pw.print("Content-Encoding: gzip\r\n");
+ setChunkedTransfer(true);
+ }
+
+ long pending = this.data != null ? this.contentLength : 0;
+ if (this.requestMethod != Method.HEAD && this.chunkedTransfer) {
+ pw.print("Transfer-Encoding: chunked\r\n");
+ } else if (!encodeAsGzip) {
+ pending = sendContentLengthHeaderIfNotAlreadyPresent(pw, this.header, pending);
+ }
+ pw.print("\r\n");
+ pw.flush();
+ sendBodyWithCorrectTransferAndEncoding(outputStream, pending);
+ outputStream.flush();
+ safeClose(this.data);
+ } catch (IOException ioe) {
+ NanoHTTPD.LOG.log(Level.SEVERE, "Could not send response to the client", ioe);
+ }
+ }
+
+ private void sendBodyWithCorrectTransferAndEncoding(OutputStream outputStream, long pending) throws IOException {
+ if (this.requestMethod != Method.HEAD && this.chunkedTransfer) {
+ ChunkedOutputStream chunkedOutputStream = new ChunkedOutputStream(outputStream);
+ sendBodyWithCorrectEncoding(chunkedOutputStream, -1);
+ chunkedOutputStream.finish();
+ } else {
+ sendBodyWithCorrectEncoding(outputStream, pending);
+ }
+ }
+
+ private void sendBodyWithCorrectEncoding(OutputStream outputStream, long pending) throws IOException {
+ if (encodeAsGzip) {
+ GZIPOutputStream gzipOutputStream = new GZIPOutputStream(outputStream);
+ sendBody(gzipOutputStream, -1);
+ gzipOutputStream.finish();
+ } else {
+ sendBody(outputStream, pending);
}
- return i + 1;
}
/**
- * Decodes parameters in percent-encoded URI-format ( e.g. "name=Jack%20Daniels&pass=Single%20Malt" ) and
- * adds them to given Map. NOTE: this doesn't support multiple identical keys due to the simplicity of Map.
+ * Sends the body to the specified OutputStream. The pending parameter
+ * limits the maximum amounts of bytes sent unless it is -1, in which
+ * case everything is sent.
+ *
+ * @param outputStream
+ * the OutputStream to send data to
+ * @param pending
+ * -1 to send everything, otherwise sets a max limit to the
+ * number of bytes sent
+ * @throws IOException
+ * if something goes wrong while sending the data.
*/
- private void decodeParms(String parms, Map<String, String> p) {
- if (parms == null) {
- queryParameterString = "";
- return;
+ private void sendBody(OutputStream outputStream, long pending) throws IOException {
+ long BUFFER_SIZE = 16 * 1024;
+ byte[] buff = new byte[(int) BUFFER_SIZE];
+ boolean sendEverything = pending == -1;
+ while (pending > 0 || sendEverything) {
+ long bytesToRead = sendEverything ? BUFFER_SIZE : Math.min(pending, BUFFER_SIZE);
+ int read = this.data.read(buff, 0, (int) bytesToRead);
+ if (read <= 0) {
+ break;
+ }
+ outputStream.write(buff, 0, read);
+ if (!sendEverything) {
+ pending -= read;
+ }
}
+ }
- queryParameterString = parms;
- StringTokenizer st = new StringTokenizer(parms, "&");
- while (st.hasMoreTokens()) {
- String e = st.nextToken();
- int sep = e.indexOf('=');
- if (sep >= 0) {
- p.put(decodePercent(e.substring(0, sep)).trim(),
- decodePercent(e.substring(sep + 1)));
- } else {
- p.put(decodePercent(e).trim(), "");
+ protected static long sendContentLengthHeaderIfNotAlreadyPresent(PrintWriter pw, Map<String, String> header, long size) {
+ for (String headerName : header.keySet()) {
+ if (headerName.equalsIgnoreCase("content-length")) {
+ try {
+ return Long.parseLong(header.get(headerName));
+ } catch (NumberFormatException ex) {
+ return size;
+ }
}
}
+
+ pw.print("Content-Length: " + size + "\r\n");
+ return size;
}
- @Override
- public final Map<String, String> getParms() {
- return parms;
+ public void setChunkedTransfer(boolean chunkedTransfer) {
+ this.chunkedTransfer = chunkedTransfer;
}
- public String getQueryParameterString() {
- return queryParameterString;
+ public void setData(InputStream data) {
+ this.data = data;
}
- @Override
- public final Map<String, String> getHeaders() {
- return headers;
+ public void setMimeType(String mimeType) {
+ this.mimeType = mimeType;
}
- @Override
- public final String getUri() {
- return uri;
+ public void setRequestMethod(Method requestMethod) {
+ this.requestMethod = requestMethod;
}
- @Override
- public final Method getMethod() {
- return method;
+ public void setStatus(IStatus status) {
+ this.status = status;
}
+ }
- @Override
- public final InputStream getInputStream() {
- return inputStream;
+ public static final class ResponseException extends Exception {
+
+ private static final long serialVersionUID = 6569838532917408380L;
+
+ private final Response.Status status;
+
+ public ResponseException(Response.Status status, String message) {
+ super(message);
+ this.status = status;
+ }
+
+ public ResponseException(Response.Status status, String message, Exception e) {
+ super(message, e);
+ this.status = status;
+ }
+
+ public Response.Status getStatus() {
+ return this.status;
+ }
+ }
+
+ /**
+ * The runnable that will be used for the main listening thread.
+ */
+ public class ServerRunnable implements Runnable {
+
+ private final int timeout;
+
+ private IOException bindException;
+
+ private boolean hasBinded = false;
+
+ private ServerRunnable(int timeout) {
+ this.timeout = timeout;
}
@Override
- public CookieHandler getCookies() {
- return cookies;
+ public void run() {
+ try {
+ myServerSocket.bind(hostname != null ? new InetSocketAddress(hostname, myPort) : new InetSocketAddress(myPort));
+ hasBinded = true;
+ } catch (IOException e) {
+ this.bindException = e;
+ return;
+ }
+ do {
+ try {
+ final Socket finalAccept = NanoHTTPD.this.myServerSocket.accept();
+ if (this.timeout > 0) {
+ finalAccept.setSoTimeout(this.timeout);
+ }
+ final InputStream inputStream = finalAccept.getInputStream();
+ NanoHTTPD.this.asyncRunner.exec(createClientHandler(finalAccept, inputStream));
+ } catch (IOException e) {
+ NanoHTTPD.LOG.log(Level.FINE, "Communication with the client broken", e);
+ }
+ } while (!NanoHTTPD.this.myServerSocket.isClosed());
}
}
- public static class Cookie {
- private String n, v, e;
+ /**
+ * A temp file.
+ * <p/>
+ * <p>
+ * Temp files are responsible for managing the actual temporary storage and
+ * cleaning themselves up when no longer needed.
+ * </p>
+ */
+ public interface TempFile {
- public Cookie(String name, String value, String expires) {
- n = name;
- v = value;
- e = expires;
+ public void delete() throws Exception;
+
+ public String getName();
+
+ public OutputStream open() throws Exception;
+ }
+
+ /**
+ * Temp file manager.
+ * <p/>
+ * <p>
+ * Temp file managers are created 1-to-1 with incoming requests, to create
+ * and cleanup temporary files created as a result of handling the request.
+ * </p>
+ */
+ public interface TempFileManager {
+
+ void clear();
+
+ public TempFile createTempFile(String filename_hint) throws Exception;
+ }
+
+ /**
+ * Factory to create temp file managers.
+ */
+ public interface TempFileManagerFactory {
+
+ public TempFileManager create();
+ }
+
+ /**
+ * Factory to create ServerSocketFactories.
+ */
+ public interface ServerSocketFactory {
+
+ public ServerSocket create() throws IOException;
+
+ }
+
+ /**
+ * Maximum time to wait on Socket.getInputStream().read() (in milliseconds)
+ * This is required as the Keep-Alive HTTP connections would otherwise block
+ * the socket reading thread forever (or as long the browser is open).
+ */
+ public static final int SOCKET_READ_TIMEOUT = 5000;
+
+ /**
+ * Common MIME type for dynamic content: plain text
+ */
+ public static final String MIME_PLAINTEXT = "text/plain";
+
+ /**
+ * Common MIME type for dynamic content: html
+ */
+ public static final String MIME_HTML = "text/html";
+
+ /**
+ * Pseudo-Parameter to use to store the actual query string in the
+ * parameters map for later re-processing.
+ */
+ private static final String QUERY_STRING_PARAMETER = "NanoHttpd.QUERY_STRING";
+
+ /**
+ * logger to log to.
+ */
+ private static final Logger LOG = Logger.getLogger(NanoHTTPD.class.getName());
+
+ /**
+ * Hashtable mapping (String)FILENAME_EXTENSION -> (String)MIME_TYPE
+ */
+ protected static Map<String, String> MIME_TYPES;
+
+ public static Map<String, String> mimeTypes() {
+ if (MIME_TYPES == null) {
+ MIME_TYPES = new HashMap<String, String>();
+ loadMimeTypes(MIME_TYPES, "META-INF/nanohttpd/default-mimetypes.properties");
+ loadMimeTypes(MIME_TYPES, "META-INF/nanohttpd/mimetypes.properties");
+ if (MIME_TYPES.isEmpty()) {
+ LOG.log(Level.WARNING, "no mime types found in the classpath! please provide mimetypes.properties");
+ }
}
+ return MIME_TYPES;
+ }
- public Cookie(String name, String value) {
- this(name, value, 30);
+ private static void loadMimeTypes(Map<String, String> result, String resourceName) {
+ try {
+ Enumeration<URL> resources = NanoHTTPD.class.getClassLoader().getResources(resourceName);
+ while (resources.hasMoreElements()) {
+ URL url = (URL) resources.nextElement();
+ Properties properties = new Properties();
+ InputStream stream = null;
+ try {
+ stream = url.openStream();
+ properties.load(url.openStream());
+ } catch (IOException e) {
+ LOG.log(Level.SEVERE, "could not load mimetypes from " + url, e);
+ } finally {
+ safeClose(stream);
+ }
+ result.putAll((Map) properties);
+ }
+ } catch (IOException e) {
+ LOG.log(Level.INFO, "no mime types available at " + resourceName);
}
+ };
- public Cookie(String name, String value, int numDays) {
- n = name;
- v = value;
- e = getHTTPTime(numDays);
+ /**
+ * Creates an SSLSocketFactory for HTTPS. Pass a loaded KeyStore and an
+ * array of loaded KeyManagers. These objects must properly
+ * loaded/initialized by the caller.
+ */
+ public static SSLServerSocketFactory makeSSLSocketFactory(KeyStore loadedKeyStore, KeyManager[] keyManagers) throws IOException {
+ SSLServerSocketFactory res = null;
+ try {
+ TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+ trustManagerFactory.init(loadedKeyStore);
+ SSLContext ctx = SSLContext.getInstance("TLS");
+ ctx.init(keyManagers, trustManagerFactory.getTrustManagers(), null);
+ res = ctx.getServerSocketFactory();
+ } catch (Exception e) {
+ throw new IOException(e.getMessage());
}
+ return res;
+ }
- public String getHTTPHeader() {
- String fmt = "%s=%s; expires=%s";
- return String.format(fmt, n, v, e);
+ /**
+ * Creates an SSLSocketFactory for HTTPS. Pass a loaded KeyStore and a
+ * loaded KeyManagerFactory. These objects must properly loaded/initialized
+ * by the caller.
+ */
+ public static SSLServerSocketFactory makeSSLSocketFactory(KeyStore loadedKeyStore, KeyManagerFactory loadedKeyFactory) throws IOException {
+ try {
+ return makeSSLSocketFactory(loadedKeyStore, loadedKeyFactory.getKeyManagers());
+ } catch (Exception e) {
+ throw new IOException(e.getMessage());
}
+ }
- public static String getHTTPTime(int days) {
- Calendar calendar = Calendar.getInstance();
- SimpleDateFormat dateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
- dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
- calendar.add(Calendar.DAY_OF_MONTH, days);
- return dateFormat.format(calendar.getTime());
+ /**
+ * Creates an SSLSocketFactory for HTTPS. Pass a KeyStore resource with your
+ * certificate and passphrase
+ */
+ public static SSLServerSocketFactory makeSSLSocketFactory(String keyAndTrustStoreClasspathPath, char[] passphrase) throws IOException {
+ try {
+ KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
+ InputStream keystoreStream = NanoHTTPD.class.getResourceAsStream(keyAndTrustStoreClasspathPath);
+ keystore.load(keystoreStream, passphrase);
+ KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
+ keyManagerFactory.init(keystore, passphrase);
+ return makeSSLSocketFactory(keystore, keyManagerFactory);
+ } catch (Exception e) {
+ throw new IOException(e.getMessage());
}
}
/**
- * Provides rudimentary support for cookies.
- * Doesn't support 'path', 'secure' nor 'httpOnly'.
- * Feel free to improve it and/or add unsupported features.
- *
- * @author LordFokas
+ * Get MIME type from file name extension, if possible
+ *
+ * @param uri
+ * the string representing a file
+ * @return the connected mime/type
*/
- public class CookieHandler implements Iterable<String> {
- private HashMap<String, String> cookies = new HashMap<String, String>();
- private ArrayList<Cookie> queue = new ArrayList<Cookie>();
+ public static String getMimeTypeForFile(String uri) {
+ int dot = uri.lastIndexOf('.');
+ String mime = null;
+ if (dot >= 0) {
+ mime = mimeTypes().get(uri.substring(dot + 1).toLowerCase());
+ }
+ return mime == null ? "application/octet-stream" : mime;
+ }
- public CookieHandler(Map<String, String> httpHeaders) {
- String raw = httpHeaders.get("cookie");
- if (raw != null) {
- String[] tokens = raw.split(";");
- for (String token : tokens) {
- String[] data = token.trim().split("=");
- if (data.length == 2) {
- cookies.put(data[0], data[1]);
- }
+ private static final void safeClose(Object closeable) {
+ try {
+ if (closeable != null) {
+ if (closeable instanceof Closeable) {
+ ((Closeable) closeable).close();
+ } else if (closeable instanceof Socket) {
+ ((Socket) closeable).close();
+ } else if (closeable instanceof ServerSocket) {
+ ((ServerSocket) closeable).close();
+ } else {
+ throw new IllegalArgumentException("Unknown object to close");
}
}
+ } catch (IOException e) {
+ NanoHTTPD.LOG.log(Level.SEVERE, "Could not close", e);
}
+ }
+
+ private final String hostname;
+
+ private final int myPort;
+
+ private volatile ServerSocket myServerSocket;
+
+ private ServerSocketFactory serverSocketFactory = new DefaultServerSocketFactory();
+
+ private Thread myThread;
+
+ /**
+ * Pluggable strategy for asynchronously executing requests.
+ */
+ protected AsyncRunner asyncRunner;
+
+ /**
+ * Pluggable strategy for creating and cleaning up temporary files.
+ */
+ private TempFileManagerFactory tempFileManagerFactory;
+
+ /**
+ * Constructs an HTTP server on given port.
+ */
+ public NanoHTTPD(int port) {
+ this(null, port);
+ }
+
+ // -------------------------------------------------------------------------------
+ // //
+ //
+ // Threading Strategy.
+ //
+ // -------------------------------------------------------------------------------
+ // //
+
+ /**
+ * Constructs an HTTP server on given hostname and port.
+ */
+ public NanoHTTPD(String hostname, int port) {
+ this.hostname = hostname;
+ this.myPort = port;
+ setTempFileManagerFactory(new DefaultTempFileManagerFactory());
+ setAsyncRunner(new DefaultAsyncRunner());
+ }
+
+ /**
+ * Forcibly closes all connections that are open.
+ */
+ public synchronized void closeAllConnections() {
+ stop();
+ }
+
+ /**
+ * create a instance of the client handler, subclasses can return a subclass
+ * of the ClientHandler.
+ *
+ * @param finalAccept
+ * the socket the cleint is connected to
+ * @param inputStream
+ * the input stream
+ * @return the client handler
+ */
+ protected ClientHandler createClientHandler(final Socket finalAccept, final InputStream inputStream) {
+ return new ClientHandler(inputStream, finalAccept);
+ }
+
+ /**
+ * Instantiate the server runnable, can be overwritten by subclasses to
+ * provide a subclass of the ServerRunnable.
+ *
+ * @param timeout
+ * the socet timeout to use.
+ * @return the server runnable.
+ */
+ protected ServerRunnable createServerRunnable(final int timeout) {
+ return new ServerRunnable(timeout);
+ }
- @Override public Iterator<String> iterator() {
- return cookies.keySet().iterator();
+ /**
+ * Decode parameters from a URL, handing the case where a single parameter
+ * name might have been supplied several times, by return lists of values.
+ * In general these lists will contain a single element.
+ *
+ * @param parms
+ * original <b>NanoHTTPD</b> parameters values, as passed to the
+ * <code>serve()</code> method.
+ * @return a map of <code>String</code> (parameter name) to
+ * <code>List&lt;String&gt;</code> (a list of the values supplied).
+ */
+ protected static Map<String, List<String>> decodeParameters(Map<String, String> parms) {
+ return decodeParameters(parms.get(NanoHTTPD.QUERY_STRING_PARAMETER));
+ }
+
+ // -------------------------------------------------------------------------------
+ // //
+
+ /**
+ * Decode parameters from a URL, handing the case where a single parameter
+ * name might have been supplied several times, by return lists of values.
+ * In general these lists will contain a single element.
+ *
+ * @param queryString
+ * a query string pulled from the URL.
+ * @return a map of <code>String</code> (parameter name) to
+ * <code>List&lt;String&gt;</code> (a list of the values supplied).
+ */
+ protected static Map<String, List<String>> decodeParameters(String queryString) {
+ Map<String, List<String>> parms = new HashMap<String, List<String>>();
+ if (queryString != null) {
+ StringTokenizer st = new StringTokenizer(queryString, "&");
+ while (st.hasMoreTokens()) {
+ String e = st.nextToken();
+ int sep = e.indexOf('=');
+ String propertyName = sep >= 0 ? decodePercent(e.substring(0, sep)).trim() : decodePercent(e).trim();
+ if (!parms.containsKey(propertyName)) {
+ parms.put(propertyName, new ArrayList<String>());
+ }
+ String propertyValue = sep >= 0 ? decodePercent(e.substring(sep + 1)) : null;
+ if (propertyValue != null) {
+ parms.get(propertyName).add(propertyValue);
+ }
+ }
}
+ return parms;
+ }
- /**
- * Read a cookie from the HTTP Headers.
- *
- * @param name The cookie's name.
- * @return The cookie's value if it exists, null otherwise.
- */
- public String read(String name) {
- return cookies.get(name);
+ /**
+ * Decode percent encoded <code>String</code> values.
+ *
+ * @param str
+ * the percent encoded <code>String</code>
+ * @return expanded form of the input, for example "foo%20bar" becomes
+ * "foo bar"
+ */
+ protected static String decodePercent(String str) {
+ String decoded = null;
+ try {
+ decoded = URLDecoder.decode(str, "UTF8");
+ } catch (UnsupportedEncodingException ignored) {
+ NanoHTTPD.LOG.log(Level.WARNING, "Encoding not supported, ignored", ignored);
}
+ return decoded;
+ }
- /**
- * Sets a cookie.
- *
- * @param name The cookie's name.
- * @param value The cookie's value.
- * @param expires How many days until the cookie expires.
- */
- public void set(String name, String value, int expires) {
- queue.add(new Cookie(name, value, Cookie.getHTTPTime(expires)));
+ /**
+ * @return true if the gzip compression should be used if the client
+ * accespts it. Default this option is on for text content and off
+ * for everything. Override this for custom semantics.
+ */
+ protected boolean useGzipWhenAccepted(Response r) {
+ return r.getMimeType() != null && r.getMimeType().toLowerCase().contains("text/");
+ }
+
+ public final int getListeningPort() {
+ return this.myServerSocket == null ? -1 : this.myServerSocket.getLocalPort();
+ }
+
+ public final boolean isAlive() {
+ return wasStarted() && !this.myServerSocket.isClosed() && this.myThread.isAlive();
+ }
+
+ public ServerSocketFactory getServerSocketFactory() {
+ return serverSocketFactory;
+ }
+
+ public void setServerSocketFactory(ServerSocketFactory serverSocketFactory) {
+ this.serverSocketFactory = serverSocketFactory;
+ }
+
+ public String getHostname() {
+ return hostname;
+ }
+
+ public TempFileManagerFactory getTempFileManagerFactory() {
+ return tempFileManagerFactory;
+ }
+
+ /**
+ * Call before start() to serve over HTTPS instead of HTTP
+ */
+ public void makeSecure(SSLServerSocketFactory sslServerSocketFactory, String[] sslProtocols) {
+ this.serverSocketFactory = new SecureServerSocketFactory(sslServerSocketFactory, sslProtocols);
+ }
+
+ /**
+ * Create a response with unknown length (using HTTP 1.1 chunking).
+ */
+ public static Response newChunkedResponse(IStatus status, String mimeType, InputStream data) {
+ return new Response(status, mimeType, data, -1);
+ }
+
+ /**
+ * Create a response with known length.
+ */
+ public static Response newFixedLengthResponse(IStatus status, String mimeType, InputStream data, long totalBytes) {
+ return new Response(status, mimeType, data, totalBytes);
+ }
+
+ /**
+ * Create a text response with known length.
+ */
+ public static Response newFixedLengthResponse(IStatus status, String mimeType, String txt) {
+ if (txt == null) {
+ return newFixedLengthResponse(status, mimeType, new ByteArrayInputStream(new byte[0]), 0);
+ } else {
+ byte[] bytes;
+ try {
+ bytes = txt.getBytes("UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ NanoHTTPD.LOG.log(Level.SEVERE, "encoding problem, responding nothing", e);
+ bytes = new byte[0];
+ }
+ return newFixedLengthResponse(status, mimeType, new ByteArrayInputStream(bytes), bytes.length);
}
+ }
- public void set(Cookie cookie) {
- queue.add(cookie);
+ /**
+ * Create a text response with known length.
+ */
+ public static Response newFixedLengthResponse(String msg) {
+ return newFixedLengthResponse(Status.OK, NanoHTTPD.MIME_HTML, msg);
+ }
+
+ /**
+ * Override this to customize the server.
+ * <p/>
+ * <p/>
+ * (By default, this returns a 404 "Not Found" plain text error response.)
+ *
+ * @param session
+ * The HTTP session
+ * @return HTTP response, see class Response for details
+ */
+ public Response serve(IHTTPSession session) {
+ Map<String, String> files = new HashMap<String, String>();
+ Method method = session.getMethod();
+ if (Method.PUT.equals(method) || Method.POST.equals(method)) {
+ try {
+ session.parseBody(files);
+ } catch (IOException ioe) {
+ return newFixedLengthResponse(Response.Status.INTERNAL_ERROR, NanoHTTPD.MIME_PLAINTEXT, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage());
+ } catch (ResponseException re) {
+ return newFixedLengthResponse(re.getStatus(), NanoHTTPD.MIME_PLAINTEXT, re.getMessage());
+ }
}
- /**
- * Set a cookie with an expiration date from a month ago, effectively deleting it on the client side.
- *
- * @param name The cookie name.
- */
- public void delete(String name) {
- set(name, "-delete-", -30);
+ Map<String, String> parms = session.getParms();
+ parms.put(NanoHTTPD.QUERY_STRING_PARAMETER, session.getQueryParameterString());
+ return serve(session.getUri(), method, session.getHeaders(), parms, files);
+ }
+
+ /**
+ * Override this to customize the server.
+ * <p/>
+ * <p/>
+ * (By default, this returns a 404 "Not Found" plain text error response.)
+ *
+ * @param uri
+ * Percent-decoded URI without parameters, for example
+ * "/index.cgi"
+ * @param method
+ * "GET", "POST" etc.
+ * @param parms
+ * Parsed, percent decoded parameters from URI and, in case of
+ * POST, data.
+ * @param headers
+ * Header entries, percent decoded
+ * @return HTTP response, see class Response for details
+ */
+ @Deprecated
+ public Response serve(String uri, Method method, Map<String, String> headers, Map<String, String> parms, Map<String, String> files) {
+ return newFixedLengthResponse(Response.Status.NOT_FOUND, NanoHTTPD.MIME_PLAINTEXT, "Not Found");
+ }
+
+ /**
+ * Pluggable strategy for asynchronously executing requests.
+ *
+ * @param asyncRunner
+ * new strategy for handling threads.
+ */
+ public void setAsyncRunner(AsyncRunner asyncRunner) {
+ this.asyncRunner = asyncRunner;
+ }
+
+ /**
+ * Pluggable strategy for creating and cleaning up temporary files.
+ *
+ * @param tempFileManagerFactory
+ * new strategy for handling temp files.
+ */
+ public void setTempFileManagerFactory(TempFileManagerFactory tempFileManagerFactory) {
+ this.tempFileManagerFactory = tempFileManagerFactory;
+ }
+
+ /**
+ * Start the server.
+ *
+ * @throws IOException
+ * if the socket is in use.
+ */
+ public void start() throws IOException {
+ start(NanoHTTPD.SOCKET_READ_TIMEOUT);
+ }
+
+ /**
+ * Starts the server (in setDaemon(true) mode).
+ */
+ public void start(final int timeout) throws IOException {
+ start(timeout, true);
+ }
+
+ /**
+ * Start the server.
+ *
+ * @param timeout
+ * timeout to use for socket connections.
+ * @param daemon
+ * start the thread daemon or not.
+ * @throws IOException
+ * if the socket is in use.
+ */
+ public void start(final int timeout, boolean daemon) throws IOException {
+ this.myServerSocket = this.getServerSocketFactory().create();
+ this.myServerSocket.setReuseAddress(true);
+
+ ServerRunnable serverRunnable = createServerRunnable(timeout);
+ this.myThread = new Thread(serverRunnable);
+ this.myThread.setDaemon(daemon);
+ this.myThread.setName("NanoHttpd Main Listener");
+ this.myThread.start();
+ while (!serverRunnable.hasBinded && serverRunnable.bindException == null) {
+ try {
+ Thread.sleep(10L);
+ } catch (Throwable e) {
+ // on android this may not be allowed, that's why we
+ // catch throwable the wait should be very short because we are
+ // just waiting for the bind of the socket
+ }
}
+ if (serverRunnable.bindException != null) {
+ throw serverRunnable.bindException;
+ }
+ }
- /**
- * Internally used by the webserver to add all queued cookies into the Response's HTTP Headers.
- *
- * @param response The Response object to which headers the queued cookies will be added.
- */
- public void unloadQueue(Response response) {
- for (Cookie cookie : queue) {
- response.addHeader("Set-Cookie", cookie.getHTTPHeader());
+ /**
+ * Stop the server.
+ */
+ public void stop() {
+ try {
+ safeClose(this.myServerSocket);
+ this.asyncRunner.closeAll();
+ if (this.myThread != null) {
+ this.myThread.join();
}
+ } catch (Exception e) {
+ NanoHTTPD.LOG.log(Level.SEVERE, "Could not stop all connections", e);
}
}
+
+ public final boolean wasStarted() {
+ return this.myServerSocket != null && this.myThread != null;
+ }
}
diff --git a/core/src/main/java/fi/iki/elonen/util/ServerRunner.java b/core/src/main/java/fi/iki/elonen/util/ServerRunner.java
new file mode 100644
index 0000000..e0aa3db
--- /dev/null
+++ b/core/src/main/java/fi/iki/elonen/util/ServerRunner.java
@@ -0,0 +1,75 @@
+package fi.iki.elonen.util;
+
+/*
+ * #%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.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import fi.iki.elonen.NanoHTTPD;
+
+public class ServerRunner {
+
+ /**
+ * logger to log to.
+ */
+ private static final Logger LOG = Logger.getLogger(ServerRunner.class.getName());
+
+ public static void executeInstance(NanoHTTPD server) {
+ try {
+ server.start(NanoHTTPD.SOCKET_READ_TIMEOUT, false);
+ } catch (IOException ioe) {
+ System.err.println("Couldn't start server:\n" + ioe);
+ System.exit(-1);
+ }
+
+ System.out.println("Server started, Hit Enter to stop.\n");
+
+ try {
+ System.in.read();
+ } catch (Throwable ignored) {
+ }
+
+ server.stop();
+ System.out.println("Server stopped.\n");
+ }
+
+ public static <T extends NanoHTTPD> void run(Class<T> serverClass) {
+ try {
+ executeInstance(serverClass.newInstance());
+ } catch (Exception e) {
+ ServerRunner.LOG.log(Level.SEVERE, "Cound nor create server", e);
+ }
+ }
+}
diff --git a/core/src/main/resources/META-INF/nanohttpd/default-mimetypes.properties b/core/src/main/resources/META-INF/nanohttpd/default-mimetypes.properties
new file mode 100644
index 0000000..3fb242f
--- /dev/null
+++ b/core/src/main/resources/META-INF/nanohttpd/default-mimetypes.properties
@@ -0,0 +1,30 @@
+#default mime types for nanohttpd, use META-INF/mimetypes.properties for user defined mimetypes
+css=text/css
+htm=text/html
+html=text/html
+xml=text/xml
+java=text/x-java-source, text/java
+md=text/plain
+txt=text/plain
+asc=text/plain
+gif=image/gif
+jpg=image/jpeg
+jpeg=image/jpeg
+png=image/png
+svg=image/svg+xml
+mp3=audio/mpeg
+m3u=audio/mpeg-url
+mp4=video/mp4
+ogv=video/ogg
+flv=video/x-flv
+mov=video/quicktime
+swf=application/x-shockwave-flash
+js=application/javascript
+pdf=application/pdf
+doc=application/msword
+ogg=application/x-ogg
+zip=application/octet-stream
+exe=application/octet-stream
+class=application/octet-stream
+m3u8=application/vnd.apple.mpegurl
+ts=video/mp2t \ No newline at end of file
diff --git a/core/src/main/resources/META-INF/nanohttpd/mimetypes.properties b/core/src/main/resources/META-INF/nanohttpd/mimetypes.properties
new file mode 100644
index 0000000..7166a88
--- /dev/null
+++ b/core/src/main/resources/META-INF/nanohttpd/mimetypes.properties
@@ -0,0 +1 @@
+#mime types for nanohttpd, use a file like this for user defined mimetypes \ No newline at end of file
diff --git a/core/src/site/site.xml b/core/src/site/site.xml
new file mode 100644
index 0000000..4270945
--- /dev/null
+++ b/core/src/site/site.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<project name="${project.name}">
+ <skin>
+ <groupId>org.apache.maven.skins</groupId>
+ <artifactId>maven-fluido-skin</artifactId>
+ <version>1.3.0</version>
+ </skin>
+ <bannerLeft>
+ <src>../images/nanohttpd_logo.png</src>
+ </bannerLeft>
+ <bannerRight>
+ <src>../images/nanohttpd_logo_text.png</src>
+ </bannerRight>
+ <publishDate position="left" format="yyyy-MM-dd" />
+ <version position="right" />
+ <poweredBy>
+ <logo name="Maven" href="http://maven.apache.org/"
+ img="http://maven.apache.org/images/logos/maven-feather.png" />
+ </poweredBy>
+ <custom>
+ <fluidoSkin>
+ <topBarEnabled>false</topBarEnabled>
+ <sideBarEnabled>true</sideBarEnabled>
+ <gitHub>
+ <projectId>Nanohttpd/nanohttpd</projectId>
+ <ribbonOrientation>right</ribbonOrientation>
+ <ribbonColor>black</ribbonColor>
+ </gitHub>
+ </fluidoSkin>
+ </custom>
+ <body>
+ <breadcrumbs>
+ <item name="${project.name}" href="index.html" />
+ </breadcrumbs>
+ <menu name="Documentation">
+ <item name="About" href="index.html" />
+ </menu>
+ <menu ref="modules" />
+ <menu ref="reports" />
+ </body>
+</project> \ No newline at end of file
diff --git a/core/src/test/java/fi/iki/elonen/HttpChunkedResponseTest.java b/core/src/test/java/fi/iki/elonen/HttpChunkedResponseTest.java
index c3fb1f0..8853efa 100644
--- a/core/src/test/java/fi/iki/elonen/HttpChunkedResponseTest.java
+++ b/core/src/test/java/fi/iki/elonen/HttpChunkedResponseTest.java
@@ -1,46 +1,50 @@
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 static fi.iki.elonen.NanoHTTPD.Response.Status.OK;
+
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PipedInputStream;
-import static fi.iki.elonen.NanoHTTPD.Response.Status.OK;
-
public class HttpChunkedResponseTest extends HttpServerTest {
- @org.junit.Test
- public void thatChunkedContentIsChunked() throws Exception {
- PipedInputStream pipedInputStream = new ChunkedInputStream(new String[]{
- "some",
- "thing which is longer than sixteen characters",
- "whee!",
- ""
- });
- String[] expected = {
- "HTTP/1.1 200 OK",
- "Content-Type: what/ever",
- "Date: .*",
- "Connection: keep-alive",
- "Transfer-Encoding: chunked",
- "",
- "4",
- "some",
- "2d",
- "thing which is longer than sixteen characters",
- "5",
- "whee!",
- "0",
- ""
- };
- testServer.response = new NanoHTTPD.Response(OK, "what/ever", pipedInputStream);
- testServer.response.setChunkedTransfer(true);
-
- ByteArrayOutputStream byteArrayOutputStream = invokeServer("GET / HTTP/1.0");
-
- assertResponse(byteArrayOutputStream, expected);
- }
private static class ChunkedInputStream extends PipedInputStream {
+
int chunk = 0;
+
String[] chunks;
private ChunkedInputStream(String[] chunks) {
@@ -48,12 +52,45 @@ public class HttpChunkedResponseTest extends HttpServerTest {
}
@Override
- public synchronized int read(byte[] buffer) throws IOException {
+ public synchronized int read(byte[] buffer, int off, int len) throws IOException {
// Too implementation-linked, but...
- for (int i = 0; i < chunks[chunk].length(); ++i) {
- buffer[i] = (byte) chunks[chunk].charAt(i);
+ for (int i = 0; i < this.chunks[this.chunk].length(); ++i) {
+ buffer[i] = (byte) this.chunks[this.chunk].charAt(i);
}
- return chunks[chunk++].length();
+ return this.chunks[this.chunk++].length();
}
}
+
+ @org.junit.Test
+ public void thatChunkedContentIsChunked() throws Exception {
+ PipedInputStream pipedInputStream = new ChunkedInputStream(new String[]{
+ "some",
+ "thing which is longer than sixteen characters",
+ "whee!",
+ ""
+ });
+ String[] expected = {
+ "HTTP/1.1 200 OK",
+ "Content-Type: what/ever",
+ "Date: .*",
+ "Connection: keep-alive",
+ "Transfer-Encoding: chunked",
+ "",
+ "4",
+ "some",
+ "2d",
+ "thing which is longer than sixteen characters",
+ "5",
+ "whee!",
+ "0",
+ ""
+ };
+ this.testServer.response = new NanoHTTPD(0) {
+ }.newChunkedResponse(OK, "what/ever", pipedInputStream);
+ this.testServer.response.setChunkedTransfer(true);
+
+ ByteArrayOutputStream byteArrayOutputStream = invokeServer("GET / HTTP/1.1");
+
+ assertResponse(byteArrayOutputStream, expected);
+ }
}
diff --git a/core/src/test/java/fi/iki/elonen/HttpDeleteRequestTest.java b/core/src/test/java/fi/iki/elonen/HttpDeleteRequestTest.java
index 517ad29..8ce49e1 100644
--- a/core/src/test/java/fi/iki/elonen/HttpDeleteRequestTest.java
+++ b/core/src/test/java/fi/iki/elonen/HttpDeleteRequestTest.java
@@ -1,102 +1,132 @@
package fi.iki.elonen;
-import org.junit.Test;
+/*
+ * #%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.ByteArrayOutputStream;
import java.io.InputStream;
-import java.util.List;
-import static junit.framework.Assert.*;
+import org.junit.Test;
public class HttpDeleteRequestTest extends HttpServerTest {
@Test
public void testDeleteRequestThatDoesntSendBackResponseBody_EmptyString() throws Exception {
- testServer.response = new NanoHTTPD.Response(NanoHTTPD.Response.Status.NO_CONTENT, NanoHTTPD.MIME_HTML, "");
+ this.testServer.response = NanoHTTPD.newFixedLengthResponse(NanoHTTPD.Response.Status.NO_CONTENT, NanoHTTPD.MIME_HTML, "");
- ByteArrayOutputStream outputStream = invokeServer("DELETE " + URI + " HTTP/1.1");
+ ByteArrayOutputStream outputStream = invokeServer("DELETE " + HttpServerTest.URI + " HTTP/1.1");
String[] expected = {
- "HTTP/1.1 204 No Content",
- "Content-Type: text/html",
- "Date: .*",
- "Connection: keep-alive",
- "Content-Length: 0",
- ""
+ "HTTP/1.1 204 No Content",
+ "Content-Type: text/html",
+ "Date: .*",
+ "Connection: keep-alive",
+ "Content-Length: 0",
+ ""
};
assertResponse(outputStream, expected);
}
@Test
- public void testDeleteRequestThatDoesntSendBackResponseBody_NullString() throws Exception {
- testServer.response = new NanoHTTPD.Response(NanoHTTPD.Response.Status.NO_CONTENT, NanoHTTPD.MIME_HTML, (String)null);
+ public void testDeleteRequestThatDoesntSendBackResponseBody_NullInputStream() throws Exception {
+ this.testServer.response = NanoHTTPD.newChunkedResponse(NanoHTTPD.Response.Status.NO_CONTENT, NanoHTTPD.MIME_HTML, (InputStream) null);
- ByteArrayOutputStream outputStream = invokeServer("DELETE " + URI + " HTTP/1.1");
+ ByteArrayOutputStream outputStream = invokeServer("DELETE " + HttpServerTest.URI + " HTTP/1.1");
String[] expected = {
- "HTTP/1.1 204 No Content",
- "Content-Type: text/html",
- "Date: .*",
- "Connection: keep-alive",
- "Content-Length: 0",
- ""
+ "HTTP/1.1 204 No Content",
+ "Content-Type: text/html",
+ "Date: .*",
+ "Connection: keep-alive",
+ "Content-Length: 0",
+ ""
};
assertResponse(outputStream, expected);
}
@Test
- public void testDeleteRequestThatDoesntSendBackResponseBody_NullInputStream() throws Exception {
- testServer.response = new NanoHTTPD.Response(NanoHTTPD.Response.Status.NO_CONTENT, NanoHTTPD.MIME_HTML, (InputStream)null);
+ public void testDeleteRequestThatDoesntSendBackResponseBody_NullString() throws Exception {
+ this.testServer.response = NanoHTTPD.newFixedLengthResponse(NanoHTTPD.Response.Status.NO_CONTENT, NanoHTTPD.MIME_HTML, (String) null);
- ByteArrayOutputStream outputStream = invokeServer("DELETE " + URI + " HTTP/1.1");
+ ByteArrayOutputStream outputStream = invokeServer("DELETE " + HttpServerTest.URI + " HTTP/1.1");
String[] expected = {
- "HTTP/1.1 204 No Content",
- "Content-Type: text/html",
- "Date: .*",
- "Connection: keep-alive",
- "Content-Length: 0",
- ""
+ "HTTP/1.1 204 No Content",
+ "Content-Type: text/html",
+ "Date: .*",
+ "Connection: keep-alive",
+ "Content-Length: 0",
+ ""
};
assertResponse(outputStream, expected);
}
@Test
- public void testDeleteRequestThatSendsBackResponseBody_Success() throws Exception {
- testServer.response = new NanoHTTPD.Response(NanoHTTPD.Response.Status.OK, "application/xml", "<body />");
+ public void testDeleteRequestThatSendsBackResponseBody_Accepted() throws Exception {
+ this.testServer.response = NanoHTTPD.newFixedLengthResponse(NanoHTTPD.Response.Status.ACCEPTED, "application/xml", "<body />");
- ByteArrayOutputStream outputStream = invokeServer("DELETE " + URI + " HTTP/1.1");
+ ByteArrayOutputStream outputStream = invokeServer("DELETE " + HttpServerTest.URI + " HTTP/1.1");
String[] expected = {
- "HTTP/1.1 200 OK",
- "Content-Type: application/xml",
- "Date: .*",
- "Connection: keep-alive",
- "Content-Length: 8",
- "",
- "<body />"
+ "HTTP/1.1 202 Accepted",
+ "Content-Type: application/xml",
+ "Date: .*",
+ "Connection: keep-alive",
+ "Content-Length: 8",
+ "",
+ "<body />"
};
assertResponse(outputStream, expected);
}
@Test
- public void testDeleteRequestThatSendsBackResponseBody_Accepted() throws Exception {
- testServer.response = new NanoHTTPD.Response(NanoHTTPD.Response.Status.ACCEPTED, "application/xml", "<body />");
+ public void testDeleteRequestThatSendsBackResponseBody_Success() throws Exception {
+ this.testServer.response = NanoHTTPD.newFixedLengthResponse(NanoHTTPD.Response.Status.OK, "application/xml", "<body />");
- ByteArrayOutputStream outputStream = invokeServer("DELETE " + URI + " HTTP/1.1");
+ ByteArrayOutputStream outputStream = invokeServer("DELETE " + HttpServerTest.URI + " HTTP/1.1");
String[] expected = {
- "HTTP/1.1 202 Accepted",
- "Content-Type: application/xml",
- "Date: .*",
- "Connection: keep-alive",
- "Content-Length: 8",
- "",
- "<body />"
+ "HTTP/1.1 200 OK",
+ "Content-Type: application/xml",
+ "Date: .*",
+ "Connection: keep-alive",
+ "Content-Length: 8",
+ "",
+ "<body />"
};
assertResponse(outputStream, expected);
diff --git a/core/src/test/java/fi/iki/elonen/HttpGetRequestTest.java b/core/src/test/java/fi/iki/elonen/HttpGetRequestTest.java
index 598e50b..e1a6b8b 100644
--- a/core/src/test/java/fi/iki/elonen/HttpGetRequestTest.java
+++ b/core/src/test/java/fi/iki/elonen/HttpGetRequestTest.java
@@ -1,171 +1,208 @@
package fi.iki.elonen;
-import org.junit.Test;
+/*
+ * #%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 static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertTrue;
import java.io.ByteArrayOutputStream;
import java.util.List;
-import static junit.framework.Assert.*;
+import org.junit.Test;
public class HttpGetRequestTest extends HttpServerTest {
@Test
- public void testFullyQualifiedWorkingGetRequest() throws Exception {
- ByteArrayOutputStream outputStream = invokeServer("GET " + URI + " HTTP/1.1");
-
- String[] expected = {
- "HTTP/1.1 200 OK",
- "Content-Type: text/html",
- "Date: .*",
- "Connection: keep-alive",
- "Content-Length: 0",
- ""
- };
-
- assertResponse(outputStream, expected);
+ public void testDecodingFieldWithEmptyValueAndFieldWithMissingValueGiveDifferentResults() {
+ invokeServer("GET " + HttpServerTest.URI + "?foo&bar= HTTP/1.1");
+ assertTrue(this.testServer.decodedParamters.get("foo") instanceof List);
+ assertEquals(0, this.testServer.decodedParamters.get("foo").size());
+ assertTrue(this.testServer.decodedParamters.get("bar") instanceof List);
+ assertEquals(1, this.testServer.decodedParamters.get("bar").size());
+ assertEquals("", this.testServer.decodedParamters.get("bar").get(0));
}
@Test
- public void testOutputOfServeSentBackToClient() throws Exception {
- String responseBody = "Success!";
- testServer.response = new NanoHTTPD.Response(responseBody);
- ByteArrayOutputStream outputStream = invokeServer("GET " + URI + " HTTP/1.1");
-
- String[] expected = {
- "HTTP/1.1 200 OK",
- "Content-Type: text/html",
- "Date: .*",
- "Connection: keep-alive",
- "Content-Length: 8",
- "",
- responseBody
- };
+ public void testDecodingMixtureOfParameters() {
+ invokeServer("GET " + HttpServerTest.URI + "?foo=bar&foo=baz&zot&zim= HTTP/1.1");
+ assertTrue(this.testServer.decodedParamters.get("foo") instanceof List);
+ assertEquals(2, this.testServer.decodedParamters.get("foo").size());
+ assertEquals("bar", this.testServer.decodedParamters.get("foo").get(0));
+ assertEquals("baz", this.testServer.decodedParamters.get("foo").get(1));
+ assertTrue(this.testServer.decodedParamters.get("zot") instanceof List);
+ assertEquals(0, this.testServer.decodedParamters.get("zot").size());
+ assertTrue(this.testServer.decodedParamters.get("zim") instanceof List);
+ assertEquals(1, this.testServer.decodedParamters.get("zim").size());
+ assertEquals("", this.testServer.decodedParamters.get("zim").get(0));
+ }
- assertResponse(outputStream, expected);
+ @Test
+ public void testDecodingParametersFromParameterMap() {
+ invokeServer("GET " + HttpServerTest.URI + "?foo=bar&foo=baz&zot&zim= HTTP/1.1");
+ assertEquals(this.testServer.decodedParamters, this.testServer.decodedParamtersFromParameter);
}
+ // --------------------------------------------------------------------------------------------------------
+ // //
+
@Test
- public void testEmptyHeadersSuppliedToServeMethodFromSimpleWorkingGetRequest() {
- invokeServer("GET " + URI + " HTTP/1.1");
- assertNotNull(testServer.parms);
- assertNotNull(testServer.header);
- assertNotNull(testServer.files);
- assertNotNull(testServer.uri);
+ public void testDecodingParametersWithSingleValue() {
+ invokeServer("GET " + HttpServerTest.URI + "?foo=bar&baz=zot HTTP/1.1");
+ assertEquals("foo=bar&baz=zot", this.testServer.queryParameterString);
+ assertTrue(this.testServer.decodedParamters.get("foo") instanceof List);
+ assertEquals(1, this.testServer.decodedParamters.get("foo").size());
+ assertEquals("bar", this.testServer.decodedParamters.get("foo").get(0));
+ assertTrue(this.testServer.decodedParamters.get("baz") instanceof List);
+ assertEquals(1, this.testServer.decodedParamters.get("baz").size());
+ assertEquals("zot", this.testServer.decodedParamters.get("baz").get(0));
}
@Test
- public void testSingleUserAgentHeaderSuppliedToServeMethodFromSimpleWorkingGetRequest() {
- String userAgent = "jUnit 4.8.2 Unit Test";
- invokeServer("GET " + URI + " HTTP/1.1\nUser-Agent: " + userAgent + "\n");
- assertEquals(userAgent, testServer.header.get("user-agent"));
- assertEquals(NanoHTTPD.Method.GET, testServer.method);
- assertEquals(URI, testServer.uri);
+ public void testDecodingParametersWithSingleValueAndMissingValue() {
+ invokeServer("GET " + HttpServerTest.URI + "?foo&baz=zot HTTP/1.1");
+ assertEquals("foo&baz=zot", this.testServer.queryParameterString);
+ assertTrue(this.testServer.decodedParamters.get("foo") instanceof List);
+ assertEquals(0, this.testServer.decodedParamters.get("foo").size());
+ assertTrue(this.testServer.decodedParamters.get("baz") instanceof List);
+ assertEquals(1, this.testServer.decodedParamters.get("baz").size());
+ assertEquals("zot", this.testServer.decodedParamters.get("baz").get(0));
}
@Test
- public void testMultipleHeaderSuppliedToServeMethodFromSimpleWorkingGetRequest() {
- String userAgent = "jUnit 4.8.2 Unit Test";
- String accept = "text/html";
- invokeServer("GET " + URI + " HTTP/1.1\nUser-Agent: " + userAgent + "\nAccept: " + accept);
- assertEquals(userAgent, testServer.header.get("user-agent"));
- assertEquals(accept, testServer.header.get("accept"));
+ public void testDecodingSingleFieldRepeated() {
+ invokeServer("GET " + HttpServerTest.URI + "?foo=bar&foo=baz HTTP/1.1");
+ assertTrue(this.testServer.decodedParamters.get("foo") instanceof List);
+ assertEquals(2, this.testServer.decodedParamters.get("foo").size());
+ assertEquals("bar", this.testServer.decodedParamters.get("foo").get(0));
+ assertEquals("baz", this.testServer.decodedParamters.get("foo").get(1));
}
@Test
- public void testSingleGetParameter() {
- invokeServer("GET " + URI + "?foo=bar HTTP/1.1");
- assertEquals("bar", testServer.parms.get("foo"));
+ public void testEmptyHeadersSuppliedToServeMethodFromSimpleWorkingGetRequest() {
+ invokeServer("GET " + HttpServerTest.URI + " HTTP/1.1");
+ assertNotNull(this.testServer.parms);
+ assertNotNull(this.testServer.header);
+ assertNotNull(this.testServer.files);
+ assertNotNull(this.testServer.uri);
}
@Test
- public void testSingleGetParameterWithNoValue() {
- invokeServer("GET " + URI + "?foo HTTP/1.1");
- assertEquals("", testServer.parms.get("foo"));
+ public void testFullyQualifiedWorkingGetRequest() throws Exception {
+ ByteArrayOutputStream outputStream = invokeServer("GET " + HttpServerTest.URI + " HTTP/1.1");
+
+ String[] expected = {
+ "HTTP/1.1 200 OK",
+ "Content-Type: text/html",
+ "Date: .*",
+ "Connection: keep-alive",
+ "Content-Length: 0",
+ ""
+ };
+
+ assertResponse(outputStream, expected);
}
@Test
public void testMultipleGetParameters() {
- invokeServer("GET " + URI + "?foo=bar&baz=zot HTTP/1.1");
- assertEquals("bar", testServer.parms.get("foo"));
- assertEquals("zot", testServer.parms.get("baz"));
+ invokeServer("GET " + HttpServerTest.URI + "?foo=bar&baz=zot HTTP/1.1");
+ assertEquals("bar", this.testServer.parms.get("foo"));
+ assertEquals("zot", this.testServer.parms.get("baz"));
}
@Test
public void testMultipleGetParametersWithMissingValue() {
- invokeServer("GET " + URI + "?foo=&baz=zot HTTP/1.1");
- assertEquals("", testServer.parms.get("foo"));
- assertEquals("zot", testServer.parms.get("baz"));
+ invokeServer("GET " + HttpServerTest.URI + "?foo=&baz=zot HTTP/1.1");
+ assertEquals("", this.testServer.parms.get("foo"));
+ assertEquals("zot", this.testServer.parms.get("baz"));
}
@Test
public void testMultipleGetParametersWithMissingValueAndRequestHeaders() {
- invokeServer("GET " + URI + "?foo=&baz=zot HTTP/1.1\nAccept: text/html");
- assertEquals("", testServer.parms.get("foo"));
- assertEquals("zot", testServer.parms.get("baz"));
- assertEquals("text/html", testServer.header.get("accept"));
+ invokeServer("GET " + HttpServerTest.URI + "?foo=&baz=zot HTTP/1.1\nAccept: text/html");
+ assertEquals("", this.testServer.parms.get("foo"));
+ assertEquals("zot", this.testServer.parms.get("baz"));
+ assertEquals("text/html", this.testServer.header.get("accept"));
}
@Test
- public void testDecodingParametersWithSingleValue() {
- invokeServer("GET " + URI + "?foo=bar&baz=zot HTTP/1.1");
- assertEquals("foo=bar&baz=zot", testServer.queryParameterString);
- assertTrue(testServer.decodedParamters.get("foo") instanceof List);
- assertEquals(1, testServer.decodedParamters.get("foo").size());
- assertEquals("bar", testServer.decodedParamters.get("foo").get(0));
- assertTrue(testServer.decodedParamters.get("baz") instanceof List);
- assertEquals(1, testServer.decodedParamters.get("baz").size());
- assertEquals("zot", testServer.decodedParamters.get("baz").get(0));
+ public void testMultipleHeaderSuppliedToServeMethodFromSimpleWorkingGetRequest() {
+ String userAgent = "jUnit 4.8.2 Unit Test";
+ String accept = "text/html";
+ invokeServer("GET " + HttpServerTest.URI + " HTTP/1.1\nUser-Agent: " + userAgent + "\nAccept: " + accept);
+ assertEquals(userAgent, this.testServer.header.get("user-agent"));
+ assertEquals(accept, this.testServer.header.get("accept"));
}
@Test
- public void testDecodingParametersWithSingleValueAndMissingValue() {
- invokeServer("GET " + URI + "?foo&baz=zot HTTP/1.1");
- assertEquals("foo&baz=zot", testServer.queryParameterString);
- assertTrue(testServer.decodedParamters.get("foo") instanceof List);
- assertEquals(0, testServer.decodedParamters.get("foo").size());
- assertTrue(testServer.decodedParamters.get("baz") instanceof List);
- assertEquals(1, testServer.decodedParamters.get("baz").size());
- assertEquals("zot", testServer.decodedParamters.get("baz").get(0));
- }
+ public void testOutputOfServeSentBackToClient() throws Exception {
+ String responseBody = "Success!";
+ this.testServer.response = NanoHTTPD.newFixedLengthResponse(responseBody);
+ ByteArrayOutputStream outputStream = invokeServer("GET " + HttpServerTest.URI + " HTTP/1.1");
- @Test
- public void testDecodingFieldWithEmptyValueAndFieldWithMissingValueGiveDifferentResults() {
- invokeServer("GET " + URI + "?foo&bar= HTTP/1.1");
- assertTrue(testServer.decodedParamters.get("foo") instanceof List);
- assertEquals(0, testServer.decodedParamters.get("foo").size());
- assertTrue(testServer.decodedParamters.get("bar") instanceof List);
- assertEquals(1, testServer.decodedParamters.get("bar").size());
- assertEquals("", testServer.decodedParamters.get("bar").get(0));
+ String[] expected = {
+ "HTTP/1.1 200 OK",
+ "Content-Type: text/html",
+ "Date: .*",
+ "Connection: keep-alive",
+ "Content-Length: 8",
+ "",
+ responseBody
+ };
+
+ assertResponse(outputStream, expected);
}
@Test
- public void testDecodingSingleFieldRepeated() {
- invokeServer("GET " + URI + "?foo=bar&foo=baz HTTP/1.1");
- assertTrue(testServer.decodedParamters.get("foo") instanceof List);
- assertEquals(2, testServer.decodedParamters.get("foo").size());
- assertEquals("bar", testServer.decodedParamters.get("foo").get(0));
- assertEquals("baz", testServer.decodedParamters.get("foo").get(1));
+ public void testSingleGetParameter() {
+ invokeServer("GET " + HttpServerTest.URI + "?foo=bar HTTP/1.1");
+ assertEquals("bar", this.testServer.parms.get("foo"));
}
@Test
- public void testDecodingMixtureOfParameters() {
- invokeServer("GET " + URI + "?foo=bar&foo=baz&zot&zim= HTTP/1.1");
- assertTrue(testServer.decodedParamters.get("foo") instanceof List);
- assertEquals(2, testServer.decodedParamters.get("foo").size());
- assertEquals("bar", testServer.decodedParamters.get("foo").get(0));
- assertEquals("baz", testServer.decodedParamters.get("foo").get(1));
- assertTrue(testServer.decodedParamters.get("zot") instanceof List);
- assertEquals(0, testServer.decodedParamters.get("zot").size());
- assertTrue(testServer.decodedParamters.get("zim") instanceof List);
- assertEquals(1, testServer.decodedParamters.get("zim").size());
- assertEquals("", testServer.decodedParamters.get("zim").get(0));
+ public void testSingleGetParameterWithNoValue() {
+ invokeServer("GET " + HttpServerTest.URI + "?foo HTTP/1.1");
+ assertEquals("", this.testServer.parms.get("foo"));
}
@Test
- public void testDecodingParametersFromParameterMap() {
- invokeServer("GET " + URI + "?foo=bar&foo=baz&zot&zim= HTTP/1.1");
- assertEquals(testServer.decodedParamters, testServer.decodedParamtersFromParameter);
+ public void testSingleUserAgentHeaderSuppliedToServeMethodFromSimpleWorkingGetRequest() {
+ String userAgent = "jUnit 4.8.2 Unit Test";
+ invokeServer("GET " + HttpServerTest.URI + " HTTP/1.1\nUser-Agent: " + userAgent + "\n");
+ assertEquals(userAgent, this.testServer.header.get("user-agent"));
+ assertEquals(NanoHTTPD.Method.GET, this.testServer.method);
+ assertEquals(HttpServerTest.URI, this.testServer.uri);
}
- // -------------------------------------------------------------------------------------------------------- //
}
diff --git a/core/src/test/java/fi/iki/elonen/HttpHeadRequestTest.java b/core/src/test/java/fi/iki/elonen/HttpHeadRequestTest.java
index 1c5901f..9b5983e 100644
--- a/core/src/test/java/fi/iki/elonen/HttpHeadRequestTest.java
+++ b/core/src/test/java/fi/iki/elonen/HttpHeadRequestTest.java
@@ -1,158 +1,196 @@
package fi.iki.elonen;
-import org.junit.Test;
+/*
+ * #%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 static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertTrue;
import java.io.ByteArrayOutputStream;
import java.util.List;
-import static junit.framework.Assert.*;
+import org.junit.Test;
public class HttpHeadRequestTest extends HttpServerTest {
+
@Override
- public void setUp() {
+ public void setUp() throws Exception {
super.setUp();
String responseBody = "Success!";
- testServer.response = new NanoHTTPD.Response(responseBody);
+ this.testServer.response = NanoHTTPD.newFixedLengthResponse(responseBody);
}
@Test
- public void testHeadRequestDoesntSendBackResponseBody() throws Exception {
- ByteArrayOutputStream outputStream = invokeServer("HEAD " + URI + " HTTP/1.1");
-
- String[] expected = {
- "HTTP/1.1 200 OK",
- "Content-Type: text/html",
- "Date: .*",
- "Connection: keep-alive",
- "Content-Length: 8",
- ""
- };
-
- assertResponse(outputStream, expected);
+ public void testDecodingFieldWithEmptyValueAndFieldWithMissingValueGiveDifferentResults() {
+ invokeServer("HEAD " + HttpServerTest.URI + "?foo&bar= HTTP/1.1");
+ assertTrue(this.testServer.decodedParamters.get("foo") instanceof List);
+ assertEquals(0, this.testServer.decodedParamters.get("foo").size());
+ assertTrue(this.testServer.decodedParamters.get("bar") instanceof List);
+ assertEquals(1, this.testServer.decodedParamters.get("bar").size());
+ assertEquals("", this.testServer.decodedParamters.get("bar").get(0));
}
@Test
- public void testEmptyHeadersSuppliedToServeMethodFromSimpleWorkingGetRequest() {
- invokeServer("HEAD " + URI + " HTTP/1.1");
- assertNotNull(testServer.parms);
- assertNotNull(testServer.header);
- assertNotNull(testServer.files);
- assertNotNull(testServer.uri);
+ public void testDecodingMixtureOfParameters() {
+ invokeServer("HEAD " + HttpServerTest.URI + "?foo=bar&foo=baz&zot&zim= HTTP/1.1");
+ assertTrue(this.testServer.decodedParamters.get("foo") instanceof List);
+ assertEquals(2, this.testServer.decodedParamters.get("foo").size());
+ assertEquals("bar", this.testServer.decodedParamters.get("foo").get(0));
+ assertEquals("baz", this.testServer.decodedParamters.get("foo").get(1));
+ assertTrue(this.testServer.decodedParamters.get("zot") instanceof List);
+ assertEquals(0, this.testServer.decodedParamters.get("zot").size());
+ assertTrue(this.testServer.decodedParamters.get("zim") instanceof List);
+ assertEquals(1, this.testServer.decodedParamters.get("zim").size());
+ assertEquals("", this.testServer.decodedParamters.get("zim").get(0));
}
@Test
- public void testSingleUserAgentHeaderSuppliedToServeMethodFromSimpleWorkingGetRequest() {
- String userAgent = "jUnit 4.8.2 Unit Test";
- invokeServer("HEAD " + URI + " HTTP/1.1\nUser-Agent: " + userAgent + "\n");
- assertEquals(userAgent, testServer.header.get("user-agent"));
- assertEquals(NanoHTTPD.Method.HEAD, testServer.method);
- assertEquals(URI, testServer.uri);
+ public void testDecodingParametersFromParameterMap() {
+ invokeServer("HEAD " + HttpServerTest.URI + "?foo=bar&foo=baz&zot&zim= HTTP/1.1");
+ assertEquals(this.testServer.decodedParamters, this.testServer.decodedParamtersFromParameter);
}
+ // --------------------------------------------------------------------------------------------------------
+ // //
+
@Test
- public void testMultipleHeaderSuppliedToServeMethodFromSimpleWorkingGetRequest() {
- String userAgent = "jUnit 4.8.2 Unit Test";
- String accept = "text/html";
- invokeServer("HEAD " + URI + " HTTP/1.1\nUser-Agent: " + userAgent + "\nAccept: " + accept);
- assertEquals(userAgent, testServer.header.get("user-agent"));
- assertEquals(accept, testServer.header.get("accept"));
+ public void testDecodingParametersWithSingleValue() {
+ invokeServer("HEAD " + HttpServerTest.URI + "?foo=bar&baz=zot HTTP/1.1");
+ assertEquals("foo=bar&baz=zot", this.testServer.queryParameterString);
+ assertTrue(this.testServer.decodedParamters.get("foo") instanceof List);
+ assertEquals(1, this.testServer.decodedParamters.get("foo").size());
+ assertEquals("bar", this.testServer.decodedParamters.get("foo").get(0));
+ assertTrue(this.testServer.decodedParamters.get("baz") instanceof List);
+ assertEquals(1, this.testServer.decodedParamters.get("baz").size());
+ assertEquals("zot", this.testServer.decodedParamters.get("baz").get(0));
}
@Test
- public void testSingleGetParameter() {
- invokeServer("HEAD " + URI + "?foo=bar HTTP/1.1");
- assertEquals("bar", testServer.parms.get("foo"));
+ public void testDecodingParametersWithSingleValueAndMissingValue() {
+ invokeServer("HEAD " + HttpServerTest.URI + "?foo&baz=zot HTTP/1.1");
+ assertEquals("foo&baz=zot", this.testServer.queryParameterString);
+ assertTrue(this.testServer.decodedParamters.get("foo") instanceof List);
+ assertEquals(0, this.testServer.decodedParamters.get("foo").size());
+ assertTrue(this.testServer.decodedParamters.get("baz") instanceof List);
+ assertEquals(1, this.testServer.decodedParamters.get("baz").size());
+ assertEquals("zot", this.testServer.decodedParamters.get("baz").get(0));
}
@Test
- public void testSingleGetParameterWithNoValue() {
- invokeServer("HEAD " + URI + "?foo HTTP/1.1");
- assertEquals("", testServer.parms.get("foo"));
+ public void testDecodingSingleFieldRepeated() {
+ invokeServer("HEAD " + HttpServerTest.URI + "?foo=bar&foo=baz HTTP/1.1");
+ assertTrue(this.testServer.decodedParamters.get("foo") instanceof List);
+ assertEquals(2, this.testServer.decodedParamters.get("foo").size());
+ assertEquals("bar", this.testServer.decodedParamters.get("foo").get(0));
+ assertEquals("baz", this.testServer.decodedParamters.get("foo").get(1));
}
@Test
- public void testMultipleGetParameters() {
- invokeServer("HEAD " + URI + "?foo=bar&baz=zot HTTP/1.1");
- assertEquals("bar", testServer.parms.get("foo"));
- assertEquals("zot", testServer.parms.get("baz"));
+ public void testEmptyHeadersSuppliedToServeMethodFromSimpleWorkingGetRequest() {
+ invokeServer("HEAD " + HttpServerTest.URI + " HTTP/1.1");
+ assertNotNull(this.testServer.parms);
+ assertNotNull(this.testServer.header);
+ assertNotNull(this.testServer.files);
+ assertNotNull(this.testServer.uri);
}
@Test
- public void testMultipleGetParametersWithMissingValue() {
- invokeServer("HEAD " + URI + "?foo=&baz=zot HTTP/1.1");
- assertEquals("", testServer.parms.get("foo"));
- assertEquals("zot", testServer.parms.get("baz"));
+ public void testHeadRequestDoesntSendBackResponseBody() throws Exception {
+ ByteArrayOutputStream outputStream = invokeServer("HEAD " + HttpServerTest.URI + " HTTP/1.1");
+
+ String[] expected = {
+ "HTTP/1.1 200 OK",
+ "Content-Type: text/html",
+ "Date: .*",
+ "Connection: keep-alive",
+ "Content-Length: 8",
+ ""
+ };
+
+ assertResponse(outputStream, expected);
}
@Test
- public void testMultipleGetParametersWithMissingValueAndRequestHeaders() {
- invokeServer("HEAD " + URI + "?foo=&baz=zot HTTP/1.1\nAccept: text/html");
- assertEquals("", testServer.parms.get("foo"));
- assertEquals("zot", testServer.parms.get("baz"));
- assertEquals("text/html", testServer.header.get("accept"));
+ public void testMultipleGetParameters() {
+ invokeServer("HEAD " + HttpServerTest.URI + "?foo=bar&baz=zot HTTP/1.1");
+ assertEquals("bar", this.testServer.parms.get("foo"));
+ assertEquals("zot", this.testServer.parms.get("baz"));
}
@Test
- public void testDecodingParametersWithSingleValue() {
- invokeServer("HEAD " + URI + "?foo=bar&baz=zot HTTP/1.1");
- assertEquals("foo=bar&baz=zot", testServer.queryParameterString);
- assertTrue(testServer.decodedParamters.get("foo") instanceof List);
- assertEquals(1, testServer.decodedParamters.get("foo").size());
- assertEquals("bar", testServer.decodedParamters.get("foo").get(0));
- assertTrue(testServer.decodedParamters.get("baz") instanceof List);
- assertEquals(1, testServer.decodedParamters.get("baz").size());
- assertEquals("zot", testServer.decodedParamters.get("baz").get(0));
+ public void testMultipleGetParametersWithMissingValue() {
+ invokeServer("HEAD " + HttpServerTest.URI + "?foo=&baz=zot HTTP/1.1");
+ assertEquals("", this.testServer.parms.get("foo"));
+ assertEquals("zot", this.testServer.parms.get("baz"));
}
@Test
- public void testDecodingParametersWithSingleValueAndMissingValue() {
- invokeServer("HEAD " + URI + "?foo&baz=zot HTTP/1.1");
- assertEquals("foo&baz=zot", testServer.queryParameterString);
- assertTrue(testServer.decodedParamters.get("foo") instanceof List);
- assertEquals(0, testServer.decodedParamters.get("foo").size());
- assertTrue(testServer.decodedParamters.get("baz") instanceof List);
- assertEquals(1, testServer.decodedParamters.get("baz").size());
- assertEquals("zot", testServer.decodedParamters.get("baz").get(0));
+ public void testMultipleGetParametersWithMissingValueAndRequestHeaders() {
+ invokeServer("HEAD " + HttpServerTest.URI + "?foo=&baz=zot HTTP/1.1\nAccept: text/html");
+ assertEquals("", this.testServer.parms.get("foo"));
+ assertEquals("zot", this.testServer.parms.get("baz"));
+ assertEquals("text/html", this.testServer.header.get("accept"));
}
@Test
- public void testDecodingFieldWithEmptyValueAndFieldWithMissingValueGiveDifferentResults() {
- invokeServer("HEAD " + URI + "?foo&bar= HTTP/1.1");
- assertTrue(testServer.decodedParamters.get("foo") instanceof List);
- assertEquals(0, testServer.decodedParamters.get("foo").size());
- assertTrue(testServer.decodedParamters.get("bar") instanceof List);
- assertEquals(1, testServer.decodedParamters.get("bar").size());
- assertEquals("", testServer.decodedParamters.get("bar").get(0));
+ public void testMultipleHeaderSuppliedToServeMethodFromSimpleWorkingGetRequest() {
+ String userAgent = "jUnit 4.8.2 Unit Test";
+ String accept = "text/html";
+ invokeServer("HEAD " + HttpServerTest.URI + " HTTP/1.1\nUser-Agent: " + userAgent + "\nAccept: " + accept);
+ assertEquals(userAgent, this.testServer.header.get("user-agent"));
+ assertEquals(accept, this.testServer.header.get("accept"));
}
@Test
- public void testDecodingSingleFieldRepeated() {
- invokeServer("HEAD " + URI + "?foo=bar&foo=baz HTTP/1.1");
- assertTrue(testServer.decodedParamters.get("foo") instanceof List);
- assertEquals(2, testServer.decodedParamters.get("foo").size());
- assertEquals("bar", testServer.decodedParamters.get("foo").get(0));
- assertEquals("baz", testServer.decodedParamters.get("foo").get(1));
+ public void testSingleGetParameter() {
+ invokeServer("HEAD " + HttpServerTest.URI + "?foo=bar HTTP/1.1");
+ assertEquals("bar", this.testServer.parms.get("foo"));
}
@Test
- public void testDecodingMixtureOfParameters() {
- invokeServer("HEAD " + URI + "?foo=bar&foo=baz&zot&zim= HTTP/1.1");
- assertTrue(testServer.decodedParamters.get("foo") instanceof List);
- assertEquals(2, testServer.decodedParamters.get("foo").size());
- assertEquals("bar", testServer.decodedParamters.get("foo").get(0));
- assertEquals("baz", testServer.decodedParamters.get("foo").get(1));
- assertTrue(testServer.decodedParamters.get("zot") instanceof List);
- assertEquals(0, testServer.decodedParamters.get("zot").size());
- assertTrue(testServer.decodedParamters.get("zim") instanceof List);
- assertEquals(1, testServer.decodedParamters.get("zim").size());
- assertEquals("", testServer.decodedParamters.get("zim").get(0));
+ public void testSingleGetParameterWithNoValue() {
+ invokeServer("HEAD " + HttpServerTest.URI + "?foo HTTP/1.1");
+ assertEquals("", this.testServer.parms.get("foo"));
}
@Test
- public void testDecodingParametersFromParameterMap() {
- invokeServer("HEAD " + URI + "?foo=bar&foo=baz&zot&zim= HTTP/1.1");
- assertEquals(testServer.decodedParamters, testServer.decodedParamtersFromParameter);
+ public void testSingleUserAgentHeaderSuppliedToServeMethodFromSimpleWorkingGetRequest() {
+ String userAgent = "jUnit 4.8.2 Unit Test";
+ invokeServer("HEAD " + HttpServerTest.URI + " HTTP/1.1\nUser-Agent: " + userAgent + "\n");
+ assertEquals(userAgent, this.testServer.header.get("user-agent"));
+ assertEquals(NanoHTTPD.Method.HEAD, this.testServer.method);
+ assertEquals(HttpServerTest.URI, this.testServer.uri);
}
- // -------------------------------------------------------------------------------------------------------- //
}
diff --git a/core/src/test/java/fi/iki/elonen/HttpKeepAliveTest.java b/core/src/test/java/fi/iki/elonen/HttpKeepAliveTest.java
index f349ee5..e168814 100644
--- a/core/src/test/java/fi/iki/elonen/HttpKeepAliveTest.java
+++ b/core/src/test/java/fi/iki/elonen/HttpKeepAliveTest.java
@@ -1,5 +1,38 @@
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 static junit.framework.Assert.fail;
import java.io.ByteArrayOutputStream;
@@ -10,68 +43,96 @@ import org.junit.Test;
public class HttpKeepAliveTest extends HttpServerTest {
+ private Throwable error = null;
+
@Test
public void testManyGetRequests() throws Exception {
- String request = "GET " + URI + " HTTP/1.1\r\n\r\n";
+ String request = "GET " + HttpServerTest.URI + " HTTP/1.1\r\n\r\n";
String[] expected = {
- "HTTP/1.1 200 OK",
- "Content-Type: text/html",
- "Date: .*",
- "Connection: keep-alive",
- "Content-Length: 0",
- ""
+ "HTTP/1.1 200 OK",
+ "Content-Type: text/html",
+ "Date: .*",
+ "Connection: keep-alive",
+ "Content-Length: 0",
+ ""
};
testManyRequests(request, expected);
}
-
+
@Test
public void testManyPutRequests() throws Exception {
String data = "BodyData 1\nLine 2";
- String request = "PUT " + URI + " HTTP/1.1\r\nContent-Length: " + data.length() + "\r\n\r\n" + data;
+ String request = "PUT " + HttpServerTest.URI + " HTTP/1.1\r\nContent-Length: " + data.length() + "\r\n\r\n" + data;
String[] expected = {
- "HTTP/1.1 200 OK",
- "Content-Type: text/html",
- "Date: .*",
- "Connection: keep-alive",
- "Content-Length: 0",
- ""
+ "HTTP/1.1 200 OK",
+ "Content-Type: text/html",
+ "Date: .*",
+ "Connection: keep-alive",
+ "Content-Length: 0",
+ ""
};
testManyRequests(request, expected);
}
- private Throwable error = null;
-
/**
- * Issue the given request many times to check whether an error occurs.
- * For this test, a small stack size is used, since a stack overflow is among the possible errors.
- * @param request The request to issue
- * @param expected The expected response
+ * Issue the given request many times to check whether an error occurs. For
+ * this test, a small stack size is used, since a stack overflow is among
+ * the possible errors.
+ *
+ * @param request
+ * The request to issue
+ * @param expected
+ * The expected response
*/
public void testManyRequests(final String request, final String[] expected) throws Exception {
Runnable r = new Runnable() {
+
+ @Override
public void run() {
try {
PipedOutputStream requestStream = new PipedOutputStream();
PipedInputStream inputStream = new PipedInputStream(requestStream);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
- NanoHTTPD.HTTPSession session = testServer.createSession(new TestTempFileManager(), inputStream, outputStream);
- for (int i = 0; i < 2048; i++) {
- requestStream.write(request.getBytes());
+ NanoHTTPD.DefaultTempFileManager tempFileManager = new NanoHTTPD.DefaultTempFileManager();
+ try {
+ NanoHTTPD.HTTPSession session = HttpKeepAliveTest.this.testServer.createSession(tempFileManager, inputStream, outputStream);
+ for (int i = 0; i < 2048; i++) {
+ requestStream.write(request.getBytes());
+ requestStream.flush();
+ outputStream.reset();
+ session.execute();
+ assertResponse(outputStream, expected);
+ }
+
+ // Finally, try "Connection: Close"
+ String closeReq = request.replaceAll("HTTP/1.1", "HTTP/1.1\r\nConnection: Close");
+ expected[3] = "Connection: close";
+ requestStream.write(closeReq.getBytes());
+ outputStream.reset();
requestStream.flush();
- session.execute();
+ // Server should now close the socket by throwing a
+ // SocketException:
+ try {
+ session.execute();
+ } catch (java.net.SocketException se) {
+ junit.framework.Assert.assertEquals(se.getMessage(), "NanoHttpd Shutdown");
+ }
assertResponse(outputStream, expected);
+
+ } finally {
+ tempFileManager.clear();
}
} catch (Throwable t) {
- error = t;
+ HttpKeepAliveTest.this.error = t;
}
}
};
Thread t = new Thread(null, r, "Request Thread", 1 << 17);
t.start();
t.join();
- if (error != null) {
- fail(""+error);
- error.printStackTrace();
+ if (this.error != null) {
+ fail("" + this.error);
+ this.error.printStackTrace();
}
}
}
diff --git a/core/src/test/java/fi/iki/elonen/HttpParsingTest.java b/core/src/test/java/fi/iki/elonen/HttpParsingTest.java
index 9705c5b..1f40f63 100644
--- a/core/src/test/java/fi/iki/elonen/HttpParsingTest.java
+++ b/core/src/test/java/fi/iki/elonen/HttpParsingTest.java
@@ -1,32 +1,63 @@
package fi.iki.elonen;
-import org.junit.Test;
-
-import java.net.URLDecoder;
-import java.net.URLEncoder;
+/*
+ * #%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 static junit.framework.Assert.assertEquals;
+import org.junit.Test;
+
public class HttpParsingTest extends HttpServerTest {
+
+ @Test
+ public void testMultibyteCharacterSupport() throws Exception {
+ String expected = "Chinese \u738b Letters";
+ String input = "Chinese+%e7%8e%8b+Letters";
+ assertEquals(expected, this.testServer.decodePercent(input));
+ }
+
@Test
public void testNormalCharacters() throws Exception {
for (int i = 0x20; i < 0x80; i++) {
String hex = Integer.toHexString(i);
String input = "%" + hex;
char expected = (char) i;
- assertEquals("" + expected, testServer.decodePercent(input));
+ assertEquals("" + expected, this.testServer.decodePercent(input));
}
}
@Test
- public void testMultibyteCharacterSupport() throws Exception {
- String expected = "Chinese \u738b Letters";
- String input = "Chinese+%e7%8e%8b+Letters";
- assertEquals(expected, testServer.decodePercent(input));
- }
-
- @Test
public void testPlusInQueryParams() throws Exception {
- assertEquals("foo bar", testServer.decodePercent("foo+bar"));
+ assertEquals("foo bar", this.testServer.decodePercent("foo+bar"));
}
}
diff --git a/core/src/test/java/fi/iki/elonen/HttpPostRequestTest.java b/core/src/test/java/fi/iki/elonen/HttpPostRequestTest.java
index 3cb37e0..db82e1c 100644
--- a/core/src/test/java/fi/iki/elonen/HttpPostRequestTest.java
+++ b/core/src/test/java/fi/iki/elonen/HttpPostRequestTest.java
@@ -1,157 +1,184 @@
package fi.iki.elonen;
-import org.junit.Test;
+/*
+ * #%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 static junit.framework.Assert.assertEquals;
import java.io.BufferedReader;
import java.io.FileReader;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
import java.util.UUID;
-import static junit.framework.Assert.assertEquals;
+import org.junit.Test;
public class HttpPostRequestTest extends HttpServerTest {
public static final String CONTENT_LENGTH = "Content-Length: ";
+
public static final String FIELD = "caption";
+
public static final String VALUE = "Summer vacation";
+
public static final String FIELD2 = "location";
+
public static final String VALUE2 = "Grand Canyon";
+
public static final String POST_RAW_CONTENT_FILE_ENTRY = "postData";
+
public static final String VALUE_TEST_SIMPLE_RAW_DATA_WITH_AMPHASIS = "Test raw data & Result value";
- @Test
- public void testSimpleRawPostData() throws Exception {
- String header = "POST " + URI + " HTTP/1.1\n";
- String content = VALUE_TEST_SIMPLE_RAW_DATA_WITH_AMPHASIS + "\n";
+ /**
+ * contains common preparation steps for testing POST with Multipart Form
+ *
+ * @param fileName
+ * Name of file to be uploaded
+ * @param fileContent
+ * Content of file to be uploaded
+ * @return input String with POST request complete information including
+ * header, length and content
+ */
+ private String preparePostWithMultipartForm(String fileName, String fileContent) {
+ String divider = UUID.randomUUID().toString();
+ String header = "POST " + HttpServerTest.URI + " HTTP/1.1\nContent-Type: " + "multipart/form-data, boundary=" + divider + "\r\n";
+ String content =
+ "--" + divider + "\r\n" + "Content-Disposition: form-data; name=\"" + HttpPostRequestTest.FIELD + "\"; filename=\"" + fileName + "\"\r\n"
+ + "Content-Type: image/jpeg\r\n" + "\r\n" + fileContent + "\r\n" + "--" + divider + "--\r\n";
int size = content.length() + header.length();
int contentLengthHeaderValueSize = String.valueOf(size).length();
- int contentLength = size + contentLengthHeaderValueSize + CONTENT_LENGTH.length();
- String input = header + CONTENT_LENGTH + (contentLength+4) + "\r\n\r\n" + content;
+ int contentLength = size + contentLengthHeaderValueSize + HttpPostRequestTest.CONTENT_LENGTH.length();
+ String input = header + HttpPostRequestTest.CONTENT_LENGTH + (contentLength + 5) + "\r\n\r\n" + content;
+
+ return input;
+ }
+
+ @Test
+ public void testPostWithMultipartFormUpload() throws Exception {
+ String filename = "GrandCanyon.txt";
+ String fileContent = HttpPostRequestTest.VALUE;
+ String input = preparePostWithMultipartForm(filename, fileContent);
+
invokeServer(input);
- assertEquals(0, testServer.parms.size());
- assertEquals(1, testServer.files.size());
- assertEquals(VALUE_TEST_SIMPLE_RAW_DATA_WITH_AMPHASIS, testServer.files.get(POST_RAW_CONTENT_FILE_ENTRY));
+
+ assertEquals(1, this.testServer.parms.size());
+ BufferedReader reader = new BufferedReader(new FileReader(this.testServer.files.get(HttpPostRequestTest.FIELD)));
+ List<String> lines = readLinesFromFile(reader);
+ assertLinesOfText(new String[]{
+ fileContent
+ }, lines);
}
@Test
- public void testSimplePostWithSingleMultipartFormField() throws Exception {
- String divider = UUID.randomUUID().toString();
- String header = "POST " + URI + " HTTP/1.1\nContent-Type: " +
- "multipart/form-data; boundary=" + divider + "\n";
- String content = "--" + divider + "\n" +
- "Content-Disposition: form-data; name=\""+FIELD+"\"\n" +
- "\n" +
- VALUE +"\n" +
- "--" + divider + "--\n";
- int size = content.length() + header.length();
- int contentLengthHeaderValueSize = String.valueOf(size).length();
- int contentLength = size + contentLengthHeaderValueSize + CONTENT_LENGTH.length();
- String input = header + CONTENT_LENGTH + (contentLength+4) + "\r\n\r\n" + content;
+ public void testPostWithMultipartFormUploadFilenameHasSpaces() throws Exception {
+ String fileNameWithSpace = "Grand Canyon.txt";
+ String fileContent = HttpPostRequestTest.VALUE;
+ String input = preparePostWithMultipartForm(fileNameWithSpace, fileContent);
+
invokeServer(input);
- assertEquals(1, testServer.parms.size());
- assertEquals(VALUE, testServer.parms.get(FIELD));
+ String fileNameAfter = new ArrayList<String>(this.testServer.parms.values()).get(0);
+
+ assertEquals(fileNameWithSpace, fileNameAfter);
}
@Test
public void testPostWithMultipleMultipartFormFields() throws Exception {
String divider = UUID.randomUUID().toString();
- String header = "POST " + URI + " HTTP/1.1\nContent-Type: " +
- "multipart/form-data; boundary=" + divider + "\n";
- String content = "--" + divider + "\n" +
- "Content-Disposition: form-data; name=\""+FIELD+"\"\n" +
- "\n" +
- VALUE +"\n" +"--" + divider + "\n" +
- "Content-Disposition: form-data; name=\""+FIELD2+"\"\n" +
- "\n" +
- VALUE2 +"\n" +
- "--" + divider + "--\n";
+ String header = "POST " + HttpServerTest.URI + " HTTP/1.1\nContent-Type: " + "multipart/form-data; boundary=" + divider + "\n";
+ String content =
+ "--" + divider + "\r\n" + "Content-Disposition: form-data; name=\"" + HttpPostRequestTest.FIELD + "\"\r\n" + "\r\n" + HttpPostRequestTest.VALUE + "\r\n"
+ + "--" + divider + "\r\n" + "Content-Disposition: form-data; name=\"" + HttpPostRequestTest.FIELD2 + "\"\r\n" + "\r\n" + HttpPostRequestTest.VALUE2
+ + "\r\n" + "--" + divider + "--\r\n";
int size = content.length() + header.length();
int contentLengthHeaderValueSize = String.valueOf(size).length();
- int contentLength = size + contentLengthHeaderValueSize + CONTENT_LENGTH.length();
- String input = header + CONTENT_LENGTH + (contentLength+4) + "\r\n\r\n" + content;
+ int contentLength = size + contentLengthHeaderValueSize + HttpPostRequestTest.CONTENT_LENGTH.length();
+ String input = header + HttpPostRequestTest.CONTENT_LENGTH + (contentLength + 4) + "\r\n\r\n" + content;
invokeServer(input);
- assertEquals(2, testServer.parms.size());
- assertEquals(VALUE, testServer.parms.get(FIELD));
- assertEquals(VALUE2, testServer.parms.get(FIELD2));
+ assertEquals(2, this.testServer.parms.size());
+ assertEquals(HttpPostRequestTest.VALUE, this.testServer.parms.get(HttpPostRequestTest.FIELD));
+ assertEquals(HttpPostRequestTest.VALUE2, this.testServer.parms.get(HttpPostRequestTest.FIELD2));
}
@Test
public void testPostWithMultipleMultipartFormFieldsWhereContentTypeWasSeparatedByComma() throws Exception {
String divider = UUID.randomUUID().toString();
- String header = "POST " + URI + " HTTP/1.1\nContent-Type: " +
- "multipart/form-data, boundary=" + divider + "\n";
- String content = "--" + divider + "\n" +
- "Content-Disposition: form-data; name=\""+FIELD+"\"\n" +
- "\n" +
- VALUE +"\n" +"--" + divider + "\n" +
- "Content-Disposition: form-data; name=\""+FIELD2+"\"\n" +
- "\n" +
- VALUE2 +"\n" +
- "--" + divider + "--\n";
+ String header = "POST " + HttpServerTest.URI + " HTTP/1.1\nContent-Type: " + "multipart/form-data, boundary=" + divider + "\r\n";
+ String content =
+ "--" + divider + "\r\n" + "Content-Disposition: form-data; name=\"" + HttpPostRequestTest.FIELD + "\"\r\n" + "\r\n" + HttpPostRequestTest.VALUE + "\r\n"
+ + "--" + divider + "\r\n" + "Content-Disposition: form-data; name=\"" + HttpPostRequestTest.FIELD2 + "\"\r\n" + "\r\n" + HttpPostRequestTest.VALUE2
+ + "\r\n" + "--" + divider + "--\r\n";
int size = content.length() + header.length();
int contentLengthHeaderValueSize = String.valueOf(size).length();
- int contentLength = size + contentLengthHeaderValueSize + CONTENT_LENGTH.length();
- String input = header + CONTENT_LENGTH + (contentLength+4) + "\r\n\r\n" + content;
+ int contentLength = size + contentLengthHeaderValueSize + HttpPostRequestTest.CONTENT_LENGTH.length();
+ String input = header + HttpPostRequestTest.CONTENT_LENGTH + (contentLength + 4) + "\r\n\r\n" + content;
invokeServer(input);
- assertEquals(2, testServer.parms.size());
- assertEquals(VALUE, testServer.parms.get(FIELD));
- assertEquals(VALUE2, testServer.parms.get(FIELD2));
+ assertEquals(2, this.testServer.parms.size());
+ assertEquals(HttpPostRequestTest.VALUE, this.testServer.parms.get(HttpPostRequestTest.FIELD));
+ assertEquals(HttpPostRequestTest.VALUE2, this.testServer.parms.get(HttpPostRequestTest.FIELD2));
}
-
+
@Test
- public void testPostWithMultipartFormUpload() throws Exception {
- String filename = "GrandCanyon.txt";
- String fileContent = VALUE;
- String input = preparePostWithMultipartForm(filename, fileContent);
-
+ public void testSimplePostWithSingleMultipartFormField() throws Exception {
+ String divider = UUID.randomUUID().toString();
+ String header = "POST " + HttpServerTest.URI + " HTTP/1.1\nContent-Type: " + "multipart/form-data; boundary=" + divider + "\r\n";
+ String content =
+ "--" + divider + "\r\n" + "Content-Disposition: form-data; name=\"" + HttpPostRequestTest.FIELD + "\"\r\n" + "\r\n" + HttpPostRequestTest.VALUE + "\r\n"
+ + "--" + divider + "--\r\n";
+ int size = content.length() + header.length();
+ int contentLengthHeaderValueSize = String.valueOf(size).length();
+ int contentLength = size + contentLengthHeaderValueSize + HttpPostRequestTest.CONTENT_LENGTH.length();
+ String input = header + HttpPostRequestTest.CONTENT_LENGTH + (contentLength + 4) + "\r\n\r\n" + content;
invokeServer(input);
-
- assertEquals(1, testServer.parms.size());
- BufferedReader reader = new BufferedReader(new FileReader(testServer.files.get(FIELD)));
- List<String> lines = readLinesFromFile(reader);
- assertLinesOfText(new String[]{fileContent}, lines);
+
+ assertEquals(1, this.testServer.parms.size());
+ assertEquals(HttpPostRequestTest.VALUE, this.testServer.parms.get(HttpPostRequestTest.FIELD));
}
-
+
@Test
- public void testPostWithMultipartFormUploadFilenameHasSpaces() throws Exception {
- String fileNameWithSpace = "Grand Canyon.txt";
- String fileContent = VALUE;
- String input = preparePostWithMultipartForm(fileNameWithSpace, fileContent);
-
- invokeServer(input);
-
- String fileNameAfter = new ArrayList<String>(testServer.parms.values()).get(0);
-
- assertEquals(fileNameWithSpace, fileNameAfter);
- }
-
- /**
- * contains common preparation steps for testing POST with Multipart Form
- * @param fileName Name of file to be uploaded
- * @param fileContent Content of file to be uploaded
- * @return input String with POST request complete information including header, length and content
- */
- private String preparePostWithMultipartForm(String fileName, String fileContent) {
- String divider = UUID.randomUUID().toString();
- String header = "POST " + URI + " HTTP/1.1\nContent-Type: " +
- "multipart/form-data, boundary=" + divider + "\n";
- String content = "--" + divider + "\n" +
- "Content-Disposition: form-data; name=\""+FIELD+"\"; filename=\""+fileName+"\"\n" +
- "Content-Type: image/jpeg\r\n"+
- "\r\n" +
- fileContent +"\r\n" +
- "--" + divider + "--\n";
+ public void testSimpleRawPostData() throws Exception {
+ String header = "POST " + HttpServerTest.URI + " HTTP/1.1\n";
+ String content = HttpPostRequestTest.VALUE_TEST_SIMPLE_RAW_DATA_WITH_AMPHASIS + "\r\n";
int size = content.length() + header.length();
int contentLengthHeaderValueSize = String.valueOf(size).length();
- int contentLength = size + contentLengthHeaderValueSize + CONTENT_LENGTH.length();
- String input = header + CONTENT_LENGTH + (contentLength+5) + "\r\n\r\n" + content;
-
- return input;
+ int contentLength = size + contentLengthHeaderValueSize + HttpPostRequestTest.CONTENT_LENGTH.length();
+ String input = header + HttpPostRequestTest.CONTENT_LENGTH + (contentLength + 4) + "\r\n\r\n" + content;
+ invokeServer(input);
+ assertEquals(0, this.testServer.parms.size());
+ assertEquals(1, this.testServer.files.size());
+ assertEquals(HttpPostRequestTest.VALUE_TEST_SIMPLE_RAW_DATA_WITH_AMPHASIS, this.testServer.files.get(HttpPostRequestTest.POST_RAW_CONTENT_FILE_ENTRY));
}
}
diff --git a/core/src/test/java/fi/iki/elonen/HttpPutRequestTest.java b/core/src/test/java/fi/iki/elonen/HttpPutRequestTest.java
index 912113e..54a3343 100644
--- a/core/src/test/java/fi/iki/elonen/HttpPutRequestTest.java
+++ b/core/src/test/java/fi/iki/elonen/HttpPutRequestTest.java
@@ -1,39 +1,72 @@
package fi.iki.elonen;
-import org.junit.Test;
+/*
+ * #%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 static junit.framework.Assert.assertTrue;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.FileReader;
import java.util.List;
-import static junit.framework.Assert.*;
+import org.junit.Test;
public class HttpPutRequestTest extends HttpServerTest {
@Test
public void testPutRequestSendsContent() throws Exception {
- ByteArrayOutputStream outputStream = invokeServer("PUT " + URI + " HTTP/1.1\r\n\r\nBodyData 1\nLine 2");
+ ByteArrayOutputStream outputStream = invokeServer("PUT " + HttpServerTest.URI + " HTTP/1.1\r\n\r\nBodyData 1\nLine 2");
String[] expectedOutput = {
- "HTTP/1.1 200 OK",
- "Content-Type: text/html",
- "Date: .*",
- "Connection: keep-alive",
- "Content-Length: 0",
- ""
+ "HTTP/1.1 200 OK",
+ "Content-Type: text/html",
+ "Date: .*",
+ "Connection: keep-alive",
+ "Content-Length: 0",
+ ""
};
assertResponse(outputStream, expectedOutput);
- assertTrue(testServer.files.containsKey("content"));
+ assertTrue(this.testServer.files.containsKey("content"));
BufferedReader reader = null;
try {
String[] expectedInputToServeMethodViaFile = {
- "BodyData 1",
- "Line 2"
+ "BodyData 1",
+ "Line 2"
};
- reader = new BufferedReader(new FileReader(testServer.files.get("content")));
+ reader = new BufferedReader(new FileReader(this.testServer.files.get("content")));
List<String> lines = readLinesFromFile(reader);
assertLinesOfText(expectedInputToServeMethodViaFile, lines);
} finally {
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..30fb48c
--- /dev/null
+++ b/core/src/test/java/fi/iki/elonen/HttpSSLServerTest.java
@@ -0,0 +1,89 @@
+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.SSLContext;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.ClientProtocolException;
+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());
+
+ Assert.assertEquals(9043, this.testServer.getListeningPort());
+ Assert.assertTrue(this.testServer.isAlive());
+ }
+
+ @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()), null);
+ 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 ef209d1..e8be61e 100644
--- a/core/src/test/java/fi/iki/elonen/HttpServerTest.java
+++ b/core/src/test/java/fi/iki/elonen/HttpServerTest.java
@@ -1,41 +1,143 @@
package fi.iki.elonen;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
+/*
+ * #%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.*;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.fail;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.StringReader;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import static junit.framework.Assert.*;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
/**
- * @author Paul S. Hawke (paul.hawke@gmail.com)
- * On: 3/10/13 at 8:32 PM
+ * @author Paul S. Hawke (paul.hawke@gmail.com) On: 3/10/13 at 8:32 PM
*/
public class HttpServerTest {
- public static final String URI = "http://www.myserver.org/pub/WWW/someFile.html";
- protected TestServer testServer;
- private TestTempFileManager tempFileManager;
- @Before
- public void setUp() {
- testServer = new TestServer();
- tempFileManager = new TestTempFileManager();
+ 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, String> files;
+
+ public Map<String, List<String>> decodedParamters;
+
+ public Map<String, List<String>> decodedParamtersFromParameter;
+
+ public String queryParameterString;
+
+ public TestServer() {
+ super(8192);
+ }
+
+ public TestServer(int port) {
+ super(port);
+ }
+
+ 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 Response serve(IHTTPSession session) {
+ this.uri = session.getUri();
+ this.method = session.getMethod();
+ this.header = session.getHeaders();
+ this.parms = session.getParms();
+ this.files = new HashMap<String, String>();
+ try {
+ session.parseBody(this.files);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ this.queryParameterString = session.getQueryParameterString();
+ this.decodedParamtersFromParameter = decodeParameters(this.queryParameterString);
+ this.decodedParamters = decodeParameters(session.getQueryParameterString());
+ return this.response;
+ }
}
- @After
- public void tearDown() {
- tempFileManager._clear();
+ public static class TestTempFileManager extends NanoHTTPD.DefaultTempFileManager {
+
+ public void _clear() {
+ super.clear();
+ }
+
+ @Override
+ public void clear() {
+ // ignore
+ }
}
- @Test
- public void testServerExists() {
- assertNotNull(testServer);
+ public static final String URI = "http://www.myserver.org/pub/WWW/someFile.html";
+
+ protected TestServer testServer;
+
+ protected TestTempFileManager tempFileManager;
+
+ protected void assertLinesOfText(String[] expected, List<String> lines) {
+ // assertEquals(expected.length, lines.size());
+ for (int i = 0; i < expected.length; i++) {
+ String line = lines.get(i);
+ assertTrue("Output line " + i + " doesn't match expectation.\n" + " Output: " + line + "\n" + "Expected: " + expected[i], line.matches(expected[i]));
+ }
}
protected void assertResponse(ByteArrayOutputStream outputStream, String[] expected) throws IOException {
@@ -43,34 +145,24 @@ public class HttpServerTest {
assertLinesOfText(expected, lines);
}
- protected void assertLinesOfText(String[] expected, List<String> lines) {
-// assertEquals(expected.length, lines.size());
- for (int i = 0; i < expected.length; i++) {
- String line = lines.get(i);
- assertTrue("Output line " + i + " doesn't match expectation.\n" +
- " Output: " + line + "\n" +
- "Expected: " + expected[i], line.matches(expected[i]));
- }
+ protected List<String> getOutputLines(ByteArrayOutputStream outputStream) throws IOException {
+ BufferedReader reader = new BufferedReader(new StringReader(outputStream.toString()));
+ return readLinesFromFile(reader);
}
protected ByteArrayOutputStream invokeServer(String request) {
ByteArrayInputStream inputStream = new ByteArrayInputStream(request.getBytes());
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
- NanoHTTPD.HTTPSession session = testServer.createSession(tempFileManager, inputStream, outputStream);
+ NanoHTTPD.HTTPSession session = this.testServer.createSession(this.tempFileManager, inputStream, outputStream);
try {
session.execute();
} catch (IOException e) {
- fail(""+e);
+ fail("" + e);
e.printStackTrace();
}
return outputStream;
}
- protected List<String> getOutputLines(ByteArrayOutputStream outputStream) throws IOException {
- BufferedReader reader = new BufferedReader(new StringReader(outputStream.toString()));
- return readLinesFromFile(reader);
- }
-
protected List<String> readLinesFromFile(BufferedReader reader) throws IOException {
List<String> lines = new ArrayList<String>();
String line = "";
@@ -83,60 +175,19 @@ public class HttpServerTest {
return lines;
}
- public static class TestTempFileManager extends NanoHTTPD.DefaultTempFileManager {
- public void _clear() {
- super.clear();
- }
-
- @Override
- public void clear() {
- // ignore
- }
+ @Before
+ public void setUp() throws Exception {
+ this.testServer = new TestServer();
+ this.tempFileManager = new TestTempFileManager();
}
- public static class TestServer extends NanoHTTPD {
- public Response response = new Response("");
- public String uri;
- public Method method;
- public Map<String, String> header;
- public Map<String, String> parms;
- public Map<String, String> files;
- public Map<String, List<String>> decodedParamters;
- public Map<String, List<String>> decodedParamtersFromParameter;
- public String queryParameterString;
-
- public TestServer() {
- super(8192);
- }
-
- 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 Response serve(IHTTPSession session) {
- this.uri = session.getUri();
- this.method = session.getMethod();
- this.header = session.getHeaders();
- this.parms = session.getParms();
- this.files = new HashMap<String, String>();
- try {
- session.parseBody(files);
- } catch (Exception e) {
- e.printStackTrace();
- }
- queryParameterString = session.getQueryParameterString();
- this.decodedParamtersFromParameter = decodeParameters(queryParameterString);
- this.decodedParamters = decodeParameters(session.getQueryParameterString());
- return response;
- }
+ @After
+ public void tearDown() {
+ this.tempFileManager._clear();
+ }
- @Override
- public String decodePercent(String str) {
- return super.decodePercent(str);
- }
+ @Test
+ public void testServerExists() {
+ assertNotNull(this.testServer);
}
}
diff --git a/core/src/test/java/fi/iki/elonen/HttpSessionHeadersTest.java b/core/src/test/java/fi/iki/elonen/HttpSessionHeadersTest.java
index 75a0a49..1494e28 100644
--- a/core/src/test/java/fi/iki/elonen/HttpSessionHeadersTest.java
+++ b/core/src/test/java/fi/iki/elonen/HttpSessionHeadersTest.java
@@ -1,30 +1,66 @@
package fi.iki.elonen;
-import org.junit.Test;
+/*
+ * #%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 static org.junit.Assert.assertEquals;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.net.InetAddress;
-import static org.junit.Assert.assertEquals;
+import org.junit.Ignore;
+import org.junit.Test;
public class HttpSessionHeadersTest extends HttpServerTest {
+
private static final String DUMMY_REQUEST_CONTENT = "dummy request content";
- private static final TestTempFileManager TEST_TEMP_FILE_MANAGER = new TestTempFileManager();
- @Override
- public void setUp() {
- super.setUp();
- }
+ private static final TestTempFileManager TEST_TEMP_FILE_MANAGER = new TestTempFileManager();
@Test
+ @Ignore
public void testHeadersRemoteIp() throws Exception {
- ByteArrayInputStream inputStream = new ByteArrayInputStream(DUMMY_REQUEST_CONTENT.getBytes());
+ ByteArrayInputStream inputStream = new ByteArrayInputStream(HttpSessionHeadersTest.DUMMY_REQUEST_CONTENT.getBytes());
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
- String[] ipAddresses = { "127.0.0.1", "192.168.1.1", "192.30.252.129" };
- for(String ipAddress : ipAddresses) {
+ String[] ipAddresses = {
+ "127.0.0.1",
+ "192.168.1.1",
+ "192.30.252.129"
+ };
+ for (String ipAddress : ipAddresses) {
InetAddress inetAddress = InetAddress.getByName(ipAddress);
- NanoHTTPD.HTTPSession session = testServer.createSession(TEST_TEMP_FILE_MANAGER, inputStream, outputStream, inetAddress);
+ NanoHTTPD.HTTPSession session = this.testServer.createSession(HttpSessionHeadersTest.TEST_TEMP_FILE_MANAGER, inputStream, outputStream, inetAddress);
assertEquals(ipAddress, session.getHeaders().get("remote-addr"));
assertEquals(ipAddress, session.getHeaders().get("http-client-ip"));
}
diff --git a/core/src/test/java/fi/iki/elonen/InvalidRequestTest.java b/core/src/test/java/fi/iki/elonen/InvalidRequestTest.java
new file mode 100644
index 0000000..eda60a3
--- /dev/null
+++ b/core/src/test/java/fi/iki/elonen/InvalidRequestTest.java
@@ -0,0 +1,79 @@
+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 static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertTrue;
+
+import org.junit.Test;
+
+public class InvalidRequestTest extends HttpServerTest {
+
+ @Test
+ public void testGetRequestWithoutProtocol() {
+ invokeServer("GET " + HttpServerTest.URI + "\r\nX-Important-Header: foo");
+
+ assertNotNull(this.testServer.parms);
+ assertTrue(this.testServer.header.size() > 0);
+ assertNotNull(this.testServer.files);
+ assertNotNull(this.testServer.uri);
+ }
+
+ @Test
+ public void testGetRequestWithProtocol() {
+ invokeServer("GET " + HttpServerTest.URI + " HTTP/1.1\r\nX-Important-Header: foo");
+
+ assertNotNull(this.testServer.parms);
+ assertTrue(this.testServer.header.size() > 0);
+ assertNotNull(this.testServer.files);
+ assertNotNull(this.testServer.uri);
+ }
+
+ @Test
+ public void testPostRequestWithoutProtocol() {
+ invokeServer("POST " + HttpServerTest.URI + "\r\nContent-Length: 123");
+ assertNotNull(this.testServer.parms);
+ assertTrue(this.testServer.header.size() > 0);
+ assertNotNull(this.testServer.files);
+ assertNotNull(this.testServer.uri);
+ }
+
+ @Test
+ public void testPostRequestWithProtocol() {
+ invokeServer("POST " + HttpServerTest.URI + " HTTP/1.1\r\nContent-Length: 123");
+ assertNotNull(this.testServer.parms);
+ assertTrue(this.testServer.header.size() > 0);
+ assertNotNull(this.testServer.files);
+ assertNotNull(this.testServer.uri);
+ }
+}
diff --git a/core/src/test/java/fi/iki/elonen/JavaIOTempDirExistTest.java b/core/src/test/java/fi/iki/elonen/JavaIOTempDirExistTest.java
new file mode 100644
index 0000000..b586bc2
--- /dev/null
+++ b/core/src/test/java/fi/iki/elonen/JavaIOTempDirExistTest.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 java.util.UUID;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import fi.iki.elonen.NanoHTTPD.DefaultTempFile;
+
+/**
+ * Created by Victor Nikiforov on 10/16/15.
+ */
+public class JavaIOTempDirExistTest {
+
+ @Test
+ public void testJavaIoTempDefault() throws Exception {
+ String tmpdir = System.getProperty("java.io.tmpdir");
+ NanoHTTPD.DefaultTempFileManager manager = new NanoHTTPD.DefaultTempFileManager();
+ DefaultTempFile tempFile = (DefaultTempFile) manager.createTempFile("xx");
+ File tempFileBackRef = new File(tempFile.getName());
+ Assert.assertEquals(tempFileBackRef.getParentFile(), new File(tmpdir));
+
+ // force an exception
+ tempFileBackRef.delete();
+ Exception e = null;
+ try {
+ tempFile.delete();
+ } catch (Exception ex) {
+ e = ex;
+ }
+ Assert.assertNotNull(e);
+ manager.clear();
+ }
+
+ @Test
+ public void testJavaIoTempSpecific() throws IOException {
+ final String tmpdir = System.getProperty("java.io.tmpdir");
+ try {
+ String tempFileName = UUID.randomUUID().toString();
+ File newDir = new File("target", tempFileName);
+ System.setProperty("java.io.tmpdir", newDir.getAbsolutePath());
+ Assert.assertEquals(false, newDir.exists());
+ new NanoHTTPD.DefaultTempFileManager();
+ Assert.assertEquals(true, newDir.exists());
+ newDir.delete();
+ } finally {
+ System.setProperty("java.io.tmpdir", tmpdir);
+ }
+
+ }
+
+}
diff --git a/core/src/test/java/fi/iki/elonen/MimeTest.java b/core/src/test/java/fi/iki/elonen/MimeTest.java
new file mode 100644
index 0000000..046ef00
--- /dev/null
+++ b/core/src/test/java/fi/iki/elonen/MimeTest.java
@@ -0,0 +1,62 @@
+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 org.junit.Assert;
+import org.junit.Test;
+
+public class MimeTest {
+
+ @Test
+ public void testExistingMimeType() throws Exception {
+ Assert.assertEquals("text/html", NanoHTTPD.getMimeTypeForFile("xxxx.html"));
+ }
+
+ @Test
+ public void testNotExistingMimeType() throws Exception {
+ Assert.assertNull(NanoHTTPD.mimeTypes().get("notExistent"));
+ Assert.assertEquals("application/octet-stream", NanoHTTPD.getMimeTypeForFile("xxxx.notExistent"));
+ }
+
+ @Test
+ public void testOverwritenMimeType() throws Exception {
+ Assert.assertEquals("video/wrongOverwrite", NanoHTTPD.getMimeTypeForFile("xxxx.ts"));
+ }
+
+ @Test
+ public void testManualMimeType() throws Exception {
+ NanoHTTPD.mimeTypes().put("flv", "video/manualOverwrite");
+ Assert.assertEquals("video/manualOverwrite", NanoHTTPD.getMimeTypeForFile("xxxx.flv"));
+ }
+}
diff --git a/core/src/test/java/fi/iki/elonen/SSLServerSocketFactoryTest.java b/core/src/test/java/fi/iki/elonen/SSLServerSocketFactoryTest.java
new file mode 100644
index 0000000..1722058
--- /dev/null
+++ b/core/src/test/java/fi/iki/elonen/SSLServerSocketFactoryTest.java
@@ -0,0 +1,90 @@
+package fi.iki.elonen;
+
+import java.io.File;
+
+/*
+ * #%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.IOException;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.ClientProtocolException;
+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;
+
+import fi.iki.elonen.NanoHTTPD.SecureServerSocketFactory;
+
+public class SSLServerSocketFactoryTest 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());
+
+ Assert.assertEquals(9043, this.testServer.getListeningPort());
+ Assert.assertTrue(this.testServer.isAlive());
+ }
+
+ @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.setServerSocketFactory(new SecureServerSocketFactory(NanoHTTPD.makeSSLSocketFactory("/keystore.jks", "password".toCharArray()), null));
+ 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/ServerSocketFactoryTest.java b/core/src/test/java/fi/iki/elonen/ServerSocketFactoryTest.java
new file mode 100644
index 0000000..17112ef
--- /dev/null
+++ b/core/src/test/java/fi/iki/elonen/ServerSocketFactoryTest.java
@@ -0,0 +1,102 @@
+package fi.iki.elonen;
+
+import java.io.File;
+
+/*
+ * #%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.IOException;
+import java.net.ServerSocket;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import fi.iki.elonen.HttpServerTest.TestServer;
+import fi.iki.elonen.NanoHTTPD.SecureServerSocketFactory;
+
+public class ServerSocketFactoryTest extends NanoHTTPD {
+
+ public static final int PORT = 8192;
+
+ public ServerSocketFactoryTest() {
+ super(PORT);
+
+ this.setServerSocketFactory(new TestFactory());
+ }
+
+ @Test
+ public void isCustomServerSocketFactory() {
+ System.out.println("CustomServerSocketFactory test");
+ Assert.assertTrue(this.getServerSocketFactory() instanceof TestFactory);
+ }
+
+ @Test
+ public void testCreateServerSocket() {
+ System.out.println("CreateServerSocket test");
+ ServerSocket ss = null;
+ try {
+ ss = this.getServerSocketFactory().create();
+ } catch (IOException e) {
+ }
+ Assert.assertTrue(ss != null);
+ }
+
+ @Test
+ public void testSSLServerSocketFail() {
+ String[] protocols = {
+ ""
+ };
+ System.setProperty("javax.net.ssl.trustStore", new File("src/test/resources/keystore.jks").getAbsolutePath());
+ ServerSocketFactory ssFactory = new SecureServerSocketFactory(null, protocols);
+ ServerSocket ss = null;
+ try {
+ ss = ssFactory.create();
+ } catch (Exception e) {
+ }
+ Assert.assertTrue(ss == null);
+
+ }
+
+ private class TestFactory implements ServerSocketFactory {
+
+ @Override
+ public ServerSocket create() {
+ try {
+ return new ServerSocket();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+ }
+}
diff --git a/core/src/test/java/fi/iki/elonen/integration/CookieIntegrationTest.java b/core/src/test/java/fi/iki/elonen/integration/CookieIntegrationTest.java
index 0d54b37..5cf5719 100644
--- a/core/src/test/java/fi/iki/elonen/integration/CookieIntegrationTest.java
+++ b/core/src/test/java/fi/iki/elonen/integration/CookieIntegrationTest.java
@@ -1,6 +1,45 @@
package fi.iki.elonen.integration;
-import fi.iki.elonen.NanoHTTPD;
+/*
+ * #%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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.List;
+
import org.apache.http.client.CookieStore;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.methods.HttpGet;
@@ -8,78 +47,77 @@ import org.apache.http.impl.client.BasicResponseHandler;
import org.apache.http.impl.cookie.BasicClientCookie;
import org.junit.Test;
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.List;
-
-import static org.junit.Assert.*;
+import fi.iki.elonen.NanoHTTPD;
/**
- * @author Paul S. Hawke (paul.hawke@gmail.com)
- * On: 9/2/13 at 10:10 PM
+ * @author Paul S. Hawke (paul.hawke@gmail.com) On: 9/2/13 at 10:10 PM
*/
public class CookieIntegrationTest extends IntegrationTestBase<CookieIntegrationTest.CookieTestServer> {
- @Test
- public void testNoCookies() throws Exception {
- HttpGet httpget = new HttpGet("http://localhost:8192/");
- ResponseHandler<String> responseHandler = new BasicResponseHandler();
- httpclient.execute(httpget, responseHandler);
+ public static class CookieTestServer extends NanoHTTPD {
- CookieStore cookies = httpclient.getCookieStore();
- assertEquals(0, cookies.getCookies().size());
+ List<Cookie> cookiesReceived = new ArrayList<Cookie>();
+
+ List<Cookie> cookiesToSend = new ArrayList<Cookie>();
+
+ public CookieTestServer() {
+ super(8192);
+ }
+
+ @Override
+ public Response serve(IHTTPSession session) {
+ CookieHandler cookies = session.getCookies();
+ for (String cookieName : cookies) {
+ this.cookiesReceived.add(new Cookie(cookieName, cookies.read(cookieName)));
+ }
+ for (Cookie c : this.cookiesToSend) {
+ cookies.set(c);
+ }
+ return newFixedLengthResponse("Cookies!");
+ }
+ }
+
+ @Override
+ public CookieTestServer createTestServer() {
+ return new CookieTestServer();
}
@Test
public void testCookieSentBackToClient() throws Exception {
- testServer.cookiesToSend.add(new NanoHTTPD.Cookie("name", "value", 30));
+ this.testServer.cookiesToSend.add(new NanoHTTPD.Cookie("name", "value", 30));
HttpGet httpget = new HttpGet("http://localhost:8192/");
ResponseHandler<String> responseHandler = new BasicResponseHandler();
- httpclient.execute(httpget, responseHandler);
+ this.httpclient.execute(httpget, responseHandler);
- CookieStore cookies = httpclient.getCookieStore();
+ CookieStore cookies = this.httpclient.getCookieStore();
assertEquals(1, cookies.getCookies().size());
assertEquals("name", cookies.getCookies().get(0).getName());
assertEquals("value", cookies.getCookies().get(0).getValue());
}
@Test
+ public void testNoCookies() throws Exception {
+ HttpGet httpget = new HttpGet("http://localhost:8192/");
+ ResponseHandler<String> responseHandler = new BasicResponseHandler();
+ this.httpclient.execute(httpget, responseHandler);
+
+ CookieStore cookies = this.httpclient.getCookieStore();
+ assertEquals(0, cookies.getCookies().size());
+ }
+
+ @Test
public void testServerReceivesCookiesSentFromClient() throws Exception {
BasicClientCookie clientCookie = new BasicClientCookie("name", "value");
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.DAY_OF_YEAR, 100);
clientCookie.setExpiryDate(calendar.getTime());
clientCookie.setDomain("localhost");
- httpclient.getCookieStore().addCookie(clientCookie);
+ this.httpclient.getCookieStore().addCookie(clientCookie);
HttpGet httpget = new HttpGet("http://localhost:8192/");
ResponseHandler<String> responseHandler = new BasicResponseHandler();
- httpclient.execute(httpget, responseHandler);
+ this.httpclient.execute(httpget, responseHandler);
- assertEquals(1, testServer.cookiesReceived.size());
- assertTrue(testServer.cookiesReceived.get(0).getHTTPHeader().contains("name=value"));
- }
-
- @Override public CookieTestServer createTestServer() {
- return new CookieTestServer();
- }
-
- public static class CookieTestServer extends NanoHTTPD {
- List<Cookie> cookiesReceived = new ArrayList<Cookie>();
- List<Cookie> cookiesToSend = new ArrayList<Cookie>();
-
- public CookieTestServer() {
- super(8192);
- }
-
- @Override public Response serve(IHTTPSession session) {
- CookieHandler cookies = session.getCookies();
- for (String cookieName : cookies) {
- cookiesReceived.add(new Cookie(cookieName, cookies.read(cookieName)));
- }
- for (Cookie c : cookiesToSend) {
- cookies.set(c);
- }
- return new Response("Cookies!");
- }
+ assertEquals(1, this.testServer.cookiesReceived.size());
+ assertTrue(this.testServer.cookiesReceived.get(0).getHTTPHeader().contains("name=value"));
}
}
diff --git a/core/src/test/java/fi/iki/elonen/integration/GZipIntegrationTest.java b/core/src/test/java/fi/iki/elonen/integration/GZipIntegrationTest.java
new file mode 100644
index 0000000..a278406
--- /dev/null
+++ b/core/src/test/java/fi/iki/elonen/integration/GZipIntegrationTest.java
@@ -0,0 +1,168 @@
+package fi.iki.elonen.integration;
+
+/*
+ * #%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 static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.http.Header;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.DecompressingHttpClient;
+import org.apache.http.util.EntityUtils;
+import org.junit.Test;
+
+import fi.iki.elonen.NanoHTTPD;
+
+public class GZipIntegrationTest extends IntegrationTestBase<GZipIntegrationTest.TestServer> {
+
+ public static class TestServer extends NanoHTTPD {
+
+ public Response response;
+
+ public TestServer() {
+ super(8192);
+ }
+
+ @Override
+ public Response serve(IHTTPSession session) {
+ return response;
+ }
+
+ @Override
+ protected boolean useGzipWhenAccepted(Response r) {
+ return true;
+ }
+ }
+
+ @Override
+ public TestServer createTestServer() {
+ return new TestServer();
+ }
+
+ @Test
+ public void contentEncodingShouldBeAddedToFixedLengthResponses() throws IOException {
+ testServer.response = NanoHTTPD.newFixedLengthResponse("This is a test");
+ HttpGet request = new HttpGet("http://localhost:8192/");
+ request.addHeader("Accept-encoding", "gzip");
+ HttpResponse response = httpclient.execute(request);
+ Header contentEncoding = response.getFirstHeader("content-encoding");
+ assertNotNull("Content-Encoding should be set", contentEncoding);
+ assertEquals("gzip", contentEncoding.getValue());
+ }
+
+ @Test
+ public void contentEncodingShouldBeAddedToChunkedResponses() throws IOException {
+ InputStream data = new ByteArrayInputStream("This is a test".getBytes("UTF-8"));
+ testServer.response = NanoHTTPD.newChunkedResponse(NanoHTTPD.Response.Status.OK, "text/plain", data);
+ HttpGet request = new HttpGet("http://localhost:8192/");
+ request.addHeader("Accept-encoding", "gzip");
+ HttpResponse response = httpclient.execute(request);
+ Header contentEncoding = response.getFirstHeader("content-encoding");
+ assertNotNull("Content-Encoding should be set", contentEncoding);
+ assertEquals("gzip", contentEncoding.getValue());
+ }
+
+ @Test
+ public void shouldFindCorrectAcceptEncodingAmongMany() throws IOException {
+ testServer.response = NanoHTTPD.newFixedLengthResponse("This is a test");
+ HttpGet request = new HttpGet("http://localhost:8192/");
+ request.addHeader("Accept-encoding", "deflate,gzip");
+ HttpResponse response = httpclient.execute(request);
+ Header contentEncoding = response.getFirstHeader("content-encoding");
+ assertNotNull("Content-Encoding should be set", contentEncoding);
+ assertEquals("gzip", contentEncoding.getValue());
+ }
+
+ @Test
+ public void contentLengthShouldBeRemovedFromZippedResponses() throws IOException {
+ testServer.response = NanoHTTPD.newFixedLengthResponse("This is a test");
+ HttpGet request = new HttpGet("http://localhost:8192/");
+ request.addHeader("Accept-encoding", "gzip");
+ HttpResponse response = httpclient.execute(request);
+ Header contentLength = response.getFirstHeader("content-length");
+ assertNull("Content-Length should not be set when gzipping response", contentLength);
+ }
+
+ @Test
+ public void fixedLengthContentIsEncodedProperly() throws IOException {
+ testServer.response = NanoHTTPD.newFixedLengthResponse("This is a test");
+ HttpGet request = new HttpGet("http://localhost:8192/");
+ request.addHeader("Accept-encoding", "gzip");
+ HttpResponse response = new DecompressingHttpClient(httpclient).execute(request);
+ assertEquals("This is a test", EntityUtils.toString(response.getEntity()));
+ }
+
+ @Test
+ public void chunkedContentIsEncodedProperly() throws IOException {
+ InputStream data = new ByteArrayInputStream("This is a test".getBytes("UTF-8"));
+ testServer.response = NanoHTTPD.newChunkedResponse(NanoHTTPD.Response.Status.OK, "text/plain", data);
+ HttpGet request = new HttpGet("http://localhost:8192/");
+ request.addHeader("Accept-encoding", "gzip");
+ HttpResponse response = new DecompressingHttpClient(httpclient).execute(request);
+ assertEquals("This is a test", EntityUtils.toString(response.getEntity()));
+ }
+
+ @Test
+ public void noGzipWithoutAcceptEncoding() throws IOException {
+ testServer.response = NanoHTTPD.newFixedLengthResponse("This is a test");
+ HttpGet request = new HttpGet("http://localhost:8192/");
+ HttpResponse response = httpclient.execute(request);
+ Header contentEncoding = response.getFirstHeader("content-encoding");
+ assertThat(contentEncoding, is(nullValue()));
+ assertEquals("This is a test", EntityUtils.toString(response.getEntity()));
+ }
+
+ @Test
+ public void contentShouldNotBeGzippedIfContentLengthIsAddedManually() throws IOException {
+ testServer.response = NanoHTTPD.newFixedLengthResponse("This is a test");
+ testServer.response.addHeader("Content-Length", "" + ("This is a test".getBytes("UTF-8").length));
+ HttpGet request = new HttpGet("http://localhost:8192/");
+ request.addHeader("Accept-encoding", "gzip");
+ HttpResponse response = httpclient.execute(request);
+ Header contentEncoding = response.getFirstHeader("content-encoding");
+ assertNull("Content-Encoding should not be set when manually setting content-length", contentEncoding);
+ assertEquals("This is a test", EntityUtils.toString(response.getEntity()));
+
+ }
+
+}
diff --git a/core/src/test/java/fi/iki/elonen/integration/GetAndPostIntegrationTest.java b/core/src/test/java/fi/iki/elonen/integration/GetAndPostIntegrationTest.java
index bc0a9d9..eef2f22 100644
--- a/core/src/test/java/fi/iki/elonen/integration/GetAndPostIntegrationTest.java
+++ b/core/src/test/java/fi/iki/elonen/integration/GetAndPostIntegrationTest.java
@@ -1,8 +1,49 @@
package fi.iki.elonen.integration;
-import fi.iki.elonen.NanoHTTPD;
+/*
+ * #%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 static org.junit.Assert.assertEquals;
+
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
-import org.apache.http.client.HttpClient;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
@@ -11,62 +52,66 @@ import org.apache.http.entity.mime.HttpMultipartMode;
import org.apache.http.entity.mime.MultipartEntity;
import org.apache.http.entity.mime.content.StringBody;
import org.apache.http.impl.client.BasicResponseHandler;
-import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
-import org.junit.After;
-import org.junit.Before;
+import org.apache.http.util.EntityUtils;
import org.junit.Test;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-
-import static org.junit.Assert.assertEquals;
+import fi.iki.elonen.NanoHTTPD;
+import fi.iki.elonen.NanoHTTPD.Response.Status;
/**
- * @author Paul S. Hawke (paul.hawke@gmail.com)
- * On: 5/19/13 at 5:36 PM
+ * @author Paul S. Hawke (paul.hawke@gmail.com) On: 5/19/13 at 5:36 PM
*/
public class GetAndPostIntegrationTest extends IntegrationTestBase<GetAndPostIntegrationTest.TestServer> {
- @Test
- public void testSimpleGetRequest() throws Exception {
- testServer.response = "testSimpleGetRequest";
+ public static class TestServer extends NanoHTTPD {
- HttpGet httpget = new HttpGet("http://localhost:8192/");
- ResponseHandler<String> responseHandler = new BasicResponseHandler();
- String responseBody = httpclient.execute(httpget, responseHandler);
+ public String response;
- assertEquals("GET:testSimpleGetRequest", responseBody);
- }
+ public TestServer() {
+ super(8192);
+ }
- @Test
- public void testGetRequestWithParameters() throws Exception {
- testServer.response = "testGetRequestWithParameters";
+ @Override
+ public Response serve(String uri, Method method, Map<String, String> header, Map<String, String> parms, Map<String, String> files) {
+ StringBuilder sb = new StringBuilder(String.valueOf(method) + ':' + this.response);
- HttpGet httpget = new HttpGet("http://localhost:8192/?age=120&gender=Male");
- ResponseHandler<String> responseHandler = new BasicResponseHandler();
- String responseBody = httpclient.execute(httpget, responseHandler);
+ if (parms.size() > 1) {
+ parms.remove("NanoHttpd.QUERY_STRING");
+ sb.append("-params=").append(parms.size());
+ List<String> p = new ArrayList<String>(parms.keySet());
+ Collections.sort(p);
+ for (String k : p) {
+ sb.append(';').append(k).append('=').append(parms.get(k));
+ }
+ }
+ if ("/chin".equals(uri)) {
+ return newFixedLengthResponse(Status.OK, "application/octet-stream", sb.toString());
+ } else {
+ return newFixedLengthResponse(sb.toString());
+ }
+ }
+ }
- assertEquals("GET:testGetRequestWithParameters-params=2;age=120;gender=Male", responseBody);
+ @Override
+ public TestServer createTestServer() {
+ return new TestServer();
}
@Test
- public void testPostWithNoParameters() throws Exception {
- testServer.response = "testPostWithNoParameters";
+ public void testGetRequestWithParameters() throws Exception {
+ this.testServer.response = "testGetRequestWithParameters";
- HttpPost httppost = new HttpPost("http://localhost:8192/");
+ HttpGet httpget = new HttpGet("http://localhost:8192/?age=120&gender=Male");
ResponseHandler<String> responseHandler = new BasicResponseHandler();
- String responseBody = httpclient.execute(httppost, responseHandler);
+ String responseBody = this.httpclient.execute(httpget, responseHandler);
- assertEquals("POST:testPostWithNoParameters", responseBody);
+ assertEquals("GET:testGetRequestWithParameters-params=2;age=120;gender=Male", responseBody);
}
@Test
public void testPostRequestWithFormEncodedParameters() throws Exception {
- testServer.response = "testPostRequestWithFormEncodedParameters";
+ this.testServer.response = "testPostRequestWithFormEncodedParameters";
HttpPost httppost = new HttpPost("http://localhost:8192/");
List<NameValuePair> postParameters = new ArrayList<NameValuePair>();
@@ -75,14 +120,14 @@ public class GetAndPostIntegrationTest extends IntegrationTestBase<GetAndPostInt
httppost.setEntity(new UrlEncodedFormEntity(postParameters));
ResponseHandler<String> responseHandler = new BasicResponseHandler();
- String responseBody = httpclient.execute(httppost, responseHandler);
+ String responseBody = this.httpclient.execute(httppost, responseHandler);
assertEquals("POST:testPostRequestWithFormEncodedParameters-params=2;age=120;gender=Male", responseBody);
}
@Test
public void testPostRequestWithMultipartEncodedParameters() throws Exception {
- testServer.response = "testPostRequestWithMultipartEncodedParameters";
+ this.testServer.response = "testPostRequestWithMultipartEncodedParameters";
HttpPost httppost = new HttpPost("http://localhost:8192/");
MultipartEntity reqEntity = new MultipartEntity(HttpMultipartMode.BROWSER_COMPATIBLE);
@@ -91,37 +136,53 @@ public class GetAndPostIntegrationTest extends IntegrationTestBase<GetAndPostInt
httppost.setEntity(reqEntity);
ResponseHandler<String> responseHandler = new BasicResponseHandler();
- String responseBody = httpclient.execute(httppost, responseHandler);
+ String responseBody = this.httpclient.execute(httppost, responseHandler);
assertEquals("POST:testPostRequestWithMultipartEncodedParameters-params=2;age=120;gender=Male", responseBody);
}
- @Override public TestServer createTestServer() {
- return new TestServer();
+ @Test
+ public void testPostWithNoParameters() throws Exception {
+ this.testServer.response = "testPostWithNoParameters";
+
+ HttpPost httppost = new HttpPost("http://localhost:8192/");
+ ResponseHandler<String> responseHandler = new BasicResponseHandler();
+ String responseBody = this.httpclient.execute(httppost, responseHandler);
+
+ assertEquals("POST:testPostWithNoParameters", responseBody);
}
- public static class TestServer extends NanoHTTPD {
- public String response;
+ @Test
+ public void testSimpleGetRequest() throws Exception {
+ this.testServer.response = "testSimpleGetRequest";
- public TestServer() {
- super(8192);
- }
+ HttpGet httpget = new HttpGet("http://localhost:8192/");
+ ResponseHandler<String> responseHandler = new BasicResponseHandler();
+ String responseBody = this.httpclient.execute(httpget, responseHandler);
- @Override
- public Response serve(String uri, Method method, Map<String, String> header, Map<String, String> parms, Map<String, String> files) {
- StringBuilder sb = new StringBuilder(String.valueOf(method) + ':' + response);
+ assertEquals("GET:testSimpleGetRequest", responseBody);
+ }
- if (parms.size() > 1) {
- parms.remove("NanoHttpd.QUERY_STRING");
- sb.append("-params=").append(parms.size());
- List<String> p = new ArrayList<String>(parms.keySet());
- Collections.sort(p);
- for (String k : p) {
- sb.append(';').append(k).append('=').append(parms.get(k));
- }
+ @Test
+ public void testPostRequestWithMultipartExtremEncodedParameters() throws Exception {
+ this.testServer.response = "testPostRequestWithMultipartEncodedParameters";
+
+ HttpPost httppost = new HttpPost("http://localhost:8192/chin");
+ MultipartEntity reqEntity = new MultipartEntity(HttpMultipartMode.BROWSER_COMPATIBLE, "sfsadfasdf", Charset.forName("UTF-8"));
+ reqEntity.addPart("specialString", new StringBody("拖拉图片到浏览器,可以实现预览功能", "text/plain", Charset.forName("UTF-8")));
+ reqEntity.addPart("gender", new StringBody("图片名称", Charset.forName("UTF-8")) {
+
+ @Override
+ public String getFilename() {
+ return "图片名称";
}
+ });
+ httppost.setEntity(reqEntity);
+ HttpResponse response = this.httpclient.execute(httppost);
- return new Response(sb.toString());
- }
+ HttpEntity entity = response.getEntity();
+ String responseBody = EntityUtils.toString(entity, "UTF-8");
+
+ assertEquals("POST:testPostRequestWithMultipartEncodedParameters-params=2;gender=图片名称;specialString=拖拉图片到浏览器,可以实现预览功能", responseBody);
}
}
diff --git a/core/src/test/java/fi/iki/elonen/integration/IntegrationTestBase.java b/core/src/test/java/fi/iki/elonen/integration/IntegrationTestBase.java
index eb34033..fd3d6d3 100644
--- a/core/src/test/java/fi/iki/elonen/integration/IntegrationTestBase.java
+++ b/core/src/test/java/fi/iki/elonen/integration/IntegrationTestBase.java
@@ -1,27 +1,63 @@
package fi.iki.elonen.integration;
-import fi.iki.elonen.NanoHTTPD;
-import org.apache.http.client.HttpClient;
+/*
+ * #%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.IOException;
+
import org.apache.http.impl.client.DefaultHttpClient;
import org.junit.After;
import org.junit.Before;
-import java.io.IOException;
+import fi.iki.elonen.NanoHTTPD;
/**
- * @author Paul S. Hawke (paul.hawke@gmail.com)
- * On: 9/2/13 at 10:02 PM
+ * @author Paul S. Hawke (paul.hawke@gmail.com) On: 9/2/13 at 10:02 PM
*/
public abstract class IntegrationTestBase<T extends NanoHTTPD> {
+
protected DefaultHttpClient httpclient;
+
protected T testServer;
+ public abstract T createTestServer();
+
@Before
public void setUp() {
- testServer = createTestServer();
- httpclient = new DefaultHttpClient();
+ this.testServer = createTestServer();
+ this.httpclient = new DefaultHttpClient();
try {
- testServer.start();
+ this.testServer.start();
} catch (IOException e) {
e.printStackTrace();
}
@@ -29,9 +65,7 @@ public abstract class IntegrationTestBase<T extends NanoHTTPD> {
@After
public void tearDown() {
- httpclient.getConnectionManager().shutdown();
- testServer.stop();
+ this.httpclient.getConnectionManager().shutdown();
+ this.testServer.stop();
}
-
- public abstract T createTestServer();
}
diff --git a/core/src/test/java/fi/iki/elonen/integration/PutStreamIntegrationTest.java b/core/src/test/java/fi/iki/elonen/integration/PutStreamIntegrationTest.java
index 1e260b2..41b84fd 100644
--- a/core/src/test/java/fi/iki/elonen/integration/PutStreamIntegrationTest.java
+++ b/core/src/test/java/fi/iki/elonen/integration/PutStreamIntegrationTest.java
@@ -1,5 +1,38 @@
package fi.iki.elonen.integration;
+/*
+ * #%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 static org.junit.Assert.assertEquals;
import java.io.DataInputStream;
@@ -16,34 +49,13 @@ import fi.iki.elonen.NanoHTTPD;
public class PutStreamIntegrationTest extends IntegrationTestBase<PutStreamIntegrationTest.TestServer> {
- @Test
- public void testSimplePutRequest() throws Exception {
- String expected = "This HttpPut request has a content-length of 48.";
-
- HttpPut httpput = new HttpPut("http://localhost:8192/");
- httpput.setEntity(new ByteArrayEntity(expected.getBytes()));
- ResponseHandler<String> responseHandler = new BasicResponseHandler();
- String responseBody = httpclient.execute(httpput, responseHandler);
-
- assertEquals("PUT:" + expected, responseBody);
- }
-
- @Override public TestServer createTestServer() {
- return new TestServer();
- }
-
public static class TestServer extends NanoHTTPD {
+
public TestServer() {
super(8192);
}
@Override
- public Response serve(String uri, Method method, Map<String, String> headers, Map<String, String> parms, Map<String, String> files)
- {
- throw new UnsupportedOperationException();
- }
-
- @Override
public Response serve(IHTTPSession session) {
Method method = session.getMethod();
Map<String, String> headers = session.getHeaders();
@@ -54,13 +66,34 @@ public class PutStreamIntegrationTest extends IntegrationTestBase<PutStreamInteg
DataInputStream dataInputStream = new DataInputStream(session.getInputStream());
body = new byte[contentLength];
dataInputStream.readFully(body, 0, contentLength);
- }
- catch(IOException e) {
- return new Response(Response.Status.INTERNAL_ERROR, MIME_PLAINTEXT, e.getMessage());
+ } catch (IOException e) {
+ return newFixedLengthResponse(Response.Status.INTERNAL_ERROR, NanoHTTPD.MIME_PLAINTEXT, e.getMessage());
}
String response = String.valueOf(method) + ':' + new String(body);
- return new Response(response);
+ return newFixedLengthResponse(response);
+ }
+
+ @Override
+ public Response serve(String uri, Method method, Map<String, String> headers, Map<String, String> parms, Map<String, String> files) {
+ throw new UnsupportedOperationException();
}
}
+
+ @Override
+ public TestServer createTestServer() {
+ return new TestServer();
+ }
+
+ @Test
+ public void testSimplePutRequest() throws Exception {
+ String expected = "This HttpPut request has a content-length of 48.";
+
+ HttpPut httpput = new HttpPut("http://localhost:8192/");
+ httpput.setEntity(new ByteArrayEntity(expected.getBytes()));
+ ResponseHandler<String> responseHandler = new BasicResponseHandler();
+ String responseBody = this.httpclient.execute(httpput, responseHandler);
+
+ assertEquals("PUT:" + expected, responseBody);
+ }
}
diff --git a/core/src/test/java/fi/iki/elonen/integration/ShutdownTest.java b/core/src/test/java/fi/iki/elonen/integration/ShutdownTest.java
index 0fcb275..cd305ea 100644
--- a/core/src/test/java/fi/iki/elonen/integration/ShutdownTest.java
+++ b/core/src/test/java/fi/iki/elonen/integration/ShutdownTest.java
@@ -1,9 +1,39 @@
package fi.iki.elonen.integration;
-import static org.junit.Assert.*;
-import fi.iki.elonen.NanoHTTPD;
+/*
+ * #%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 org.junit.Test;
+import static org.junit.Assert.fail;
import java.io.IOException;
import java.io.InputStream;
@@ -11,8 +41,24 @@ import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
+import org.junit.Test;
+
+import fi.iki.elonen.NanoHTTPD;
+
public class ShutdownTest {
+ private class TestServer extends NanoHTTPD {
+
+ public TestServer() {
+ super(8092);
+ }
+
+ @Override
+ public Response serve(IHTTPSession session) {
+ return newFixedLengthResponse("Whatever");
+ }
+ }
+
@Test
public void connectionsAreClosedWhenServerStops() throws IOException {
TestServer server = new TestServer();
@@ -38,16 +84,4 @@ public class ShutdownTest {
in.close();
}
- private class TestServer extends NanoHTTPD {
-
- public TestServer() {
- super(8092);
- }
-
- @Override
- public Response serve(IHTTPSession session) {
- return new Response("Whatever");
- }
- }
-
}
diff --git a/core/src/test/resources/META-INF/nanohttpd/mimetypes.properties b/core/src/test/resources/META-INF/nanohttpd/mimetypes.properties
new file mode 100644
index 0000000..2f353d8
--- /dev/null
+++ b/core/src/test/resources/META-INF/nanohttpd/mimetypes.properties
@@ -0,0 +1,3 @@
+#test mime types for nanohttpd
+blabla=text/blabla
+ts=video/wrongOverwrite \ No newline at end of file
diff --git a/core/src/test/resources/file-upload-test.htm b/core/src/test/resources/file-upload-test.htm
index 7d553bf..e64c516 100644
--- a/core/src/test/resources/file-upload-test.htm
+++ b/core/src/test/resources/file-upload-test.htm
@@ -1,3 +1,35 @@
+<!--
+ #%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%
+ -->
<html>
<body>
<p>This is a file upload test for NanoHTTPD.</p>
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/core/src/test/resources/multipart-form-test.htm b/core/src/test/resources/multipart-form-test.htm
index eab4dc7..8504475 100644
--- a/core/src/test/resources/multipart-form-test.htm
+++ b/core/src/test/resources/multipart-form-test.htm
@@ -1,3 +1,35 @@
+<!--
+ #%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%
+ -->
<html>
<body>
<p>This is a multipart-form test for NanoHTTPD.</p>