Repository: NanoHttpd/nanohttpd Branch: master Commit: efb2ebf85a2b Files: 155 Total size: 606.7 KB Directory structure: gitextract_2uva_0s3/ ├── .gitignore ├── .travis.yml ├── LICENSE.md ├── README.md ├── build.gradle ├── core/ │ ├── .gitignore │ ├── build.gradle │ ├── pom.xml │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── nanohttpd/ │ │ │ ├── protocols/ │ │ │ │ └── http/ │ │ │ │ ├── ClientHandler.java │ │ │ │ ├── HTTPSession.java │ │ │ │ ├── IHTTPSession.java │ │ │ │ ├── NanoHTTPD.java │ │ │ │ ├── ServerRunnable.java │ │ │ │ ├── content/ │ │ │ │ │ ├── ContentType.java │ │ │ │ │ ├── Cookie.java │ │ │ │ │ └── CookieHandler.java │ │ │ │ ├── request/ │ │ │ │ │ └── Method.java │ │ │ │ ├── response/ │ │ │ │ │ ├── ChunkedOutputStream.java │ │ │ │ │ ├── IStatus.java │ │ │ │ │ ├── Response.java │ │ │ │ │ └── Status.java │ │ │ │ ├── sockets/ │ │ │ │ │ ├── DefaultServerSocketFactory.java │ │ │ │ │ └── SecureServerSocketFactory.java │ │ │ │ ├── tempfiles/ │ │ │ │ │ ├── DefaultTempFile.java │ │ │ │ │ ├── DefaultTempFileManager.java │ │ │ │ │ ├── DefaultTempFileManagerFactory.java │ │ │ │ │ ├── ITempFile.java │ │ │ │ │ └── ITempFileManager.java │ │ │ │ └── threading/ │ │ │ │ ├── DefaultAsyncRunner.java │ │ │ │ └── IAsyncRunner.java │ │ │ └── util/ │ │ │ ├── IFactory.java │ │ │ ├── IFactoryThrowing.java │ │ │ ├── IHandler.java │ │ │ └── ServerRunner.java │ │ └── resources/ │ │ └── META-INF/ │ │ └── nanohttpd/ │ │ ├── default-mimetypes.properties │ │ └── mimetypes.properties │ ├── site/ │ │ └── site.xml │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── nanohttpd/ │ │ └── junit/ │ │ └── protocols/ │ │ └── http/ │ │ ├── BadRequestTest.java │ │ ├── CookieHandlerTest.java │ │ ├── CookieTest.java │ │ ├── HttpChunkedResponseTest.java │ │ ├── HttpDeleteRequestTest.java │ │ ├── HttpGetRequestTest.java │ │ ├── HttpHeadRequestTest.java │ │ ├── HttpKeepAliveTest.java │ │ ├── HttpParsingTest.java │ │ ├── HttpPostRequestTest.java │ │ ├── HttpPutRequestTest.java │ │ ├── HttpSSLServerTest.java │ │ ├── HttpServerTest.java │ │ ├── HttpSessionHeadersTest.java │ │ ├── HttpSessionTest.java │ │ ├── InvalidRequestTest.java │ │ ├── JavaIOTempDirExistTest.java │ │ ├── LoadKeyStoreTest.java │ │ ├── MimeTest.java │ │ ├── SSLServerSocketFactoryTest.java │ │ ├── ServerSocketFactoryTest.java │ │ ├── StatusTest.java │ │ └── integration/ │ │ ├── CookieIntegrationTest.java │ │ ├── GZipIntegrationTest.java │ │ ├── GetAndPostIntegrationTest.java │ │ ├── IntegrationTestBase.java │ │ ├── PutStreamIntegrationTest.java │ │ └── ShutdownTest.java │ └── resources/ │ ├── META-INF/ │ │ └── nanohttpd/ │ │ └── mimetypes.properties │ ├── file-upload-test.htm │ ├── keystore.jks │ └── multipart-form-test.htm ├── fileupload/ │ ├── .gitignore │ ├── build.gradle │ ├── pom.xml │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── org/ │ │ └── nanohttpd/ │ │ └── fileupload/ │ │ └── NanoFileUpload.java │ └── test/ │ └── java/ │ └── org/ │ └── nanohttpd/ │ └── junit/ │ └── fileupload/ │ └── TestNanoFileUpLoad.java ├── markdown-plugin/ │ ├── .gitignore │ ├── build.gradle │ ├── pom.xml │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── nanohttpd/ │ │ │ └── markdown/ │ │ │ ├── MarkdownWebServerPlugin.java │ │ │ └── MarkdownWebServerPluginInfo.java │ │ └── resources/ │ │ └── META-INF/ │ │ └── services/ │ │ └── org.nanohttpd.webserver.WebServerPluginInfo │ └── site/ │ └── site.xml ├── nanohttpd release perform.launch ├── nanohttpd release prepare.launch ├── nanolets/ │ ├── .gitignore │ ├── build.gradle │ ├── pom.xml │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── org/ │ │ └── nanohttpd/ │ │ └── router/ │ │ └── RouterNanoHTTPD.java │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── nanohttpd/ │ │ └── junit/ │ │ └── router/ │ │ ├── AppNanolets.java │ │ └── TestNanolets.java │ └── resources/ │ ├── blabla.html │ ├── dir/ │ │ ├── blabla.html │ │ └── index.htm │ └── exception.html ├── pom.xml ├── relocation/ │ ├── nanohttpd/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── resources/ │ │ └── relocated.txt │ ├── nanohttpd-samples/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── resources/ │ │ └── relocated.txt │ ├── nanohttpd-webserver/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── resources/ │ │ └── relocated.txt │ ├── nanohttpd-webserver-markdown-plugin/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── resources/ │ │ └── relocated.txt │ ├── nanohttpd-websocket/ │ │ ├── pom.xml │ │ └── src/ │ │ └── main/ │ │ └── resources/ │ │ └── relocated.txt │ └── pom.xml ├── samples/ │ ├── .gitignore │ ├── build.gradle │ ├── pom.xml │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── org/ │ │ └── nanohttpd/ │ │ └── samples/ │ │ ├── http/ │ │ │ ├── DebugServer.java │ │ │ └── HelloServer.java │ │ └── tempfiles/ │ │ └── TempFilesServer.java │ └── site/ │ └── site.xml ├── settings.gradle ├── settings.xml ├── src/ │ ├── main/ │ │ ├── checkstyle/ │ │ │ ├── checkstyle-suppressions.xml │ │ │ └── nanohttpd-style.xml │ │ └── formatter/ │ │ └── formatter.xml │ └── site/ │ ├── markdown/ │ │ └── index.md │ └── site.xml ├── webserver/ │ ├── .gitignore │ ├── build.gradle │ ├── pom.xml │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── org/ │ │ └── nanohttpd/ │ │ └── webserver/ │ │ ├── InternalRewrite.java │ │ ├── SimpleWebServer.java │ │ ├── WebServerPlugin.java │ │ └── WebServerPluginInfo.java │ ├── site/ │ │ └── site.xml │ └── test/ │ ├── java/ │ │ └── org/ │ │ └── nanohttpd/ │ │ └── junit/ │ │ └── webserver/ │ │ ├── AbstractTestHttpServer.java │ │ ├── DummyPlugin.java │ │ ├── DummyPluginInfo.java │ │ ├── TestCorsHttpServer.java │ │ ├── TestCorsHttpServerWithSingleOrigin.java │ │ └── TestHttpServer.java │ └── resources/ │ ├── META-INF/ │ │ └── services/ │ │ └── org.nanohttpd.webserver.WebServerPluginInfo │ └── testdir/ │ ├── test.html │ └── testdir/ │ ├── different.xml │ └── index.html └── websocket/ ├── .gitignore ├── build.gradle ├── pom.xml └── src/ ├── main/ │ └── java/ │ └── org/ │ └── nanohttpd/ │ ├── protocols/ │ │ └── websockets/ │ │ ├── CloseCode.java │ │ ├── CloseFrame.java │ │ ├── NanoWSD.java │ │ ├── OpCode.java │ │ ├── State.java │ │ ├── WebSocket.java │ │ ├── WebSocketException.java │ │ └── WebSocketFrame.java │ └── samples/ │ └── websockets/ │ ├── DebugWebSocketServer.java │ └── EchoSocketSample.java ├── site/ │ └── site.xml └── test/ ├── java/ │ └── org/ │ └── nanohttpd/ │ └── junit/ │ └── protocols/ │ └── websockets/ │ ├── EchoWebSocketsTest.java │ ├── SimpleEchoSocket.java │ └── WebSocketResponseHandlerTest.java └── resources/ └── echo-test.html ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ out build target *.iml *.ipr *.iws *.class .idea .classpath .project .gradle gradle/ gradlew gradlew.bat # Vim Backup/Swap Files *~ .swp .*.swp /.settings/ /LICENSE.txt ================================================ FILE: .travis.yml ================================================ language: java jdk: - openjdk7 before_install: - cat ~/.m2/settings.xml - rm ~/.m2/settings.xml install: - mvn install -DskipTests=true script: - mvn test; export MAVEN_RESULT=$? - if [ "$MAVEN_RESULT" -ne 0 ]; then exit 1; fi - if [ "$TRAVIS_BRANCH" = "master" -a "$TRAVIS_PULL_REQUEST" = "false" ]; then mvn clean deploy --quiet --settings settings.xml; fi after_success: - mvn clean test jacoco:report coveralls:report notifications: email: - richard.vannieuwenhoven@adesso.at - elonen@iki.fi - diogo.duarte@techie.com env: global: - secure: "OtD0z3y4/OjSzg8irVD8v/u0TElcw8AiCCXb7a0UQEnTpGxcf5DOdkvHv0hF4xjHKFtlMMHxevW+a4C4NuFR8it8ZJ/i2m24reB28JicDcRQY9nwV/BR/T08CRG9KDz5EuTHgfPJDF0y+5MiVNwJVhHFviBWKuXyIuYouJ5IHgc=" - secure: "tgTVycNLwYDM3U0EVK1TkffylQHfZihvSDC9QoZEo+wz9aBBLoAtUJP7DWltRQFOkfTeGltGHEfGM2/qkIG6Wz+hNFG/fveHqyI01JWXc64d7yBm7agaCP5uTtt2wjaZ7ZK5Mps5QoufblYu+j9gb2v31t9IdsJ9PUs0+wgE/WU=" ================================================ FILE: LICENSE.md ================================================ Copyright (c) 2012-2013 by Paul S. Hawke, 2001,2005-2013 by Jarno Elonen, 2010 by Konstantinos Togias All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * 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. * Neither the name of the NanoHttpd organization 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. ================================================ FILE: README.md ================================================ ## NanoHTTPD – a tiny web server in Java *NanoHTTPD* is a light-weight HTTP server designed for embedding in other applications, released under a Modified BSD licence. It is being developed at Github and uses Apache Maven for builds & unit testing: * Build status: [![Build Status](https://api.travis-ci.org/NanoHttpd/nanohttpd.png)](https://travis-ci.org/NanoHttpd/nanohttpd) * Coverage Status: [![Coverage Status](https://coveralls.io/repos/NanoHttpd/nanohttpd/badge.svg)](https://coveralls.io/r/NanoHttpd/nanohttpd) * Current central released version: [![Maven Central](https://maven-badges.herokuapp.com/maven-central/org.nanohttpd/nanohttpd/badge.svg)](https://maven-badges.herokuapp.com/maven-central/org.nanohttpd/nanohttpd) ## Quickstart We'll create a custom HTTP server project using Maven for build/dep system. This tutorial assumes you are using a Unix variant and a shell. First, install Maven and Java SDK if not already installed. Then run: mvn compile mvn exec:java -pl webserver -Dexec.mainClass="org.nanohttpd.webserver.SimpleWebServer" You should now have a HTTP file server running on . ### Custom web app Let's raise the bar and build a custom web application next: mvn archetype:generate -DgroupId=com.example -DartifactId=myHellopApp -DinteractiveMode=false cd myHellopApp Edit `pom.xml`, and add this between \: org.nanohttpd nanohttpd 2.2.0 Edit `src/main/java/com/example/App.java` and replace it with: ```java package com.example; import java.io.IOException; import java.util.Map; import fi.iki.elonen.NanoHTTPD; // NOTE: If you're using NanoHTTPD >= 3.0.0 the namespace is different, // instead of the above import use the following: // import org.nanohttpd.NanoHTTPD; public class App extends NanoHTTPD { public App() throws IOException { super(8080); start(NanoHTTPD.SOCKET_READ_TIMEOUT, false); System.out.println("\nRunning! Point your browsers to http://localhost:8080/ \n"); } public static void main(String[] args) { try { new App(); } catch (IOException ioe) { System.err.println("Couldn't start server:\n" + ioe); } } @Override public Response serve(IHTTPSession session) { String msg = "

Hello server

\n"; Map parms = session.getParms(); if (parms.get("username") == null) { msg += "
\n

Your name:

\n" + "
\n"; } else { msg += "

Hello, " + parms.get("username") + "!

"; } return newFixedLengthResponse(msg + "\n"); } } ``` Compile and run the server: mvn compile mvn exec:java -Dexec.mainClass="com.example.App" If it started ok, point your browser at and enjoy a web server that asks your name and replies with a greeting. ### Nanolets Nanolets are like servlets only that they have a extremely low profile. They offer an easy to use system for a more complex server application. This text has to be extended with an example, so for now take a look at the unit tests for the usage. ## Status We are currently in the process of stabilizing NanoHTTPD from the many pull requests and feature requests that were integrated over the last few months. The next release will come soon, and there will not be any more "intended" major changes before the next release. If you want to use the bleeding edge version, you can clone it from Github, or get it from sonatype.org (see "Maven dependencies / Living on the edge" below). ## Project structure NanoHTTPD project currently consist of four parts: * `/core` – Fully functional HTTP(s) server consisting of one (1) Java file, ready to be customized/inherited for your own project. * `/samples` – Simple examples on how to customize NanoHTTPD. See *HelloServer.java* for a killer app that greets you enthusiastically! * `/websocket` – Websocket implementation, also in a single Java file. Depends on core. * `/webserver` – Standalone file server. Run & enjoy. A popular use seems to be serving files out off an Android device. * `/nanolets` – Standalone nano app server, giving a servlet like system to the implementor. * `/fileupload` – integration of the apache common file upload library. ## Features ### Core * Only one Java file, providing HTTP 1.1 support. * No fixed config files, logging, authorization etc. (Implement by yourself if you need them. Errors are passed to java.util.logging, though.) * Support for HTTPS (SSL). * Basic support for cookies. * Supports parameter parsing of GET and POST methods. * Some built-in support for HEAD, POST and DELETE requests. You can easily implement/customize any HTTP method, though. * Supports file upload. Uses memory for small uploads, temp files for large ones. * Never caches anything. * Does not limit bandwidth, request time or simultaneous connections by default. * All header names are converted to lower case so they don't vary between browsers/clients. * Persistent connections (Connection "keep-alive") support allowing multiple requests to be served over a single socket connection. ### Websocket * Tested on Firefox, Chrome and IE. ### Webserver * Default code serves files and shows (prints on console) all HTTP parameters and headers. * Supports both dynamic content and file serving. * File server supports directory listing, `index.html` and `index.htm`. * File server supports partial content (streaming & continue download). * File server supports ETags. * File server does the 301 redirection trick for directories without `/`. * File server serves also very long files without memory overhead. * Contains a built-in list of most common MIME types. * Runtime extension support (extensions that serve particular MIME types) - example extension that serves Markdown formatted files. Simply including an extension JAR in the webserver classpath is enough for the extension to be loaded. * Simple [CORS](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing) support via `--cors` parameter * by default serves `Access-Control-Allow-Headers: origin,accept,content-type` * possibility to set `Access-Control-Allow-Headers` by setting System property: `AccessControlAllowHeader` * _example: _ `-DAccessControlAllowHeader=origin,accept,content-type,Authorization` * possible values: * `--cors`: activates CORS support, `Access-Control-Allow-Origin` will be set to `*`. * `--cors=some_value`: `Access-Control-Allow-Origin` will be set to `some_value`. **_CORS argument examples_** * `--cors=http://appOne.company.com` * `--cors="http://appOne.company.com, http://appTwo.company.com"`: note the double quotes so that the two URLs are considered part of a single argument. ## Maven dependencies NanoHTTPD is a Maven based project and deployed to central. Most development environments have means to access the central repository. The coordinates to use in Maven are: org.nanohttpd nanohttpd CURRENT_VERSION (Replace `CURRENT_VERSION` with whatever is reported latest at .) The coordinates for your development environment should correspond to these. When looking for an older version take care because we switched groupId from *com.nanohttpd* to *org.nanohttpd* in mid 2015. Next it depends what you are using NanoHTTPD for, there are three main usages. ## Gradle dependencies In gradle you can use NanoHTTPD the same way because gradle accesses the same central repository: dependencies { runtime( [group: 'org.nanohttpd', name: 'nanohttpd', version: 'CURRENT_VERSION'], ) } (Replace `CURRENT_VERSION` with whatever is reported latest at .) Just replace the name with the artifact id of the module you want to use and gradle will find it for you. ### Develop your own specialized HTTP service For a specialized HTTP (HTTPS) service you can use the module with artifactId *nanohttpd*. org.nanohttpd nanohttpd CURRENT_VERSION Here you write your own subclass of *org.nanohttpd.NanoHTTPD* to configure and to serve the requests. ### Develop a websocket based service For a specialized websocket service you can use the module with artifactId *nanohttpd-websocket*. org.nanohttpd nanohttpd-websocket CURRENT_VERSION Here you write your own subclass of *org.nanohttpd.NanoWebSocketServer* to configure and to serve the websocket requests. A small standard echo example is included as *org.nanohttpd.samples.echo.DebugWebSocketServer*. You can use it as a starting point to implement your own services. ### Develop a custom HTTP file server For a more classic approach, perhaps to just create a HTTP server serving mostly service files from your disk, you can use the module with artifactId *nanohttpd-webserver*. org.nanohttpd nanohttpd-webserver CURRENT_VERSION The included class *org.nanohttpd.SimpleWebServer* is intended to be used as a starting point for your own implementation but it also can be used as is. Starting the class as is will start a HTTP server on port 8080 and publishing the current directory. ### Living on the edge The latest Github master version can be fetched through sonatype.org: nanohttpd org.nanohttpd XXXXX-SNAPSHOT ... sonatype-snapshots https://oss.sonatype.org/content/repositories/snapshots true ### generating an self signed SSL certificate Just a hint how to generate a certificate for localhost. keytool -genkey -keyalg RSA -alias selfsigned -keystore keystore.jks -storepass password -validity 360 -keysize 2048 -ext SAN=DNS:localhost,IP:127.0.0.1 -validity 9999 This will generate a keystore file named 'keystore.jks' with a self signed certificate for a host named localhost with the IP address 127.0.0.1 . Now you can use: server.makeSecure(NanoHTTPD.makeSSLSocketFactory("/keystore.jks", "password".toCharArray()), null); Before you start the server to make NanoHTTPD serve HTTPS connections, when you make sure 'keystore.jks' is in your classpath. ----- *Thank you to everyone who has reported bugs and suggested fixes.* ================================================ FILE: build.gradle ================================================ allprojects { apply plugin: 'java' apply plugin: 'idea' group = 'org.nanohttpd' version = '2.3.2-SNAPSHOT' } subprojects { apply plugin: 'java' sourceCompatibility = 1.7 targetCompatibility = 1.7 repositories { jcenter() } dependencies { testCompile group: 'junit', name: 'junit', version: '4.12' } } task wrapper(type: Wrapper) { gradleVersion = "4.4.1" } ================================================ FILE: core/.gitignore ================================================ /.settings/ /LICENSE.txt ================================================ FILE: core/build.gradle ================================================ description = 'NanoHttpd-Core' dependencies { testCompile group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.2.5' testCompile group: 'org.apache.httpcomponents', name: 'httpmime', version: '4.2.5' } task wrapper(type: Wrapper) { gradleVersion = "4.4.1" } ================================================ FILE: core/pom.xml ================================================ 4.0.0 org.nanohttpd nanohttpd-project 2.3.2-SNAPSHOT nanohttpd jar NanoHttpd-Core org.apache.httpcomponents httpclient 4.2.5 test org.apache.httpcomponents httpmime 4.2.5 test 0.82 ================================================ FILE: core/src/main/java/org/nanohttpd/protocols/http/ClientHandler.java ================================================ package org.nanohttpd.protocols.http; /* * #%L * NanoHttpd-Core * %% * Copyright (C) 2012 - 2016 nanohttpd * %% * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * 3. Neither the name of the nanohttpd nor the names of its contributors * may be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. * #L% */ import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; import java.net.SocketException; import java.net.SocketTimeoutException; import java.util.logging.Level; import org.nanohttpd.protocols.http.tempfiles.ITempFileManager; /** * The runnable that will be used for every new client connection. */ public class ClientHandler implements Runnable { private final NanoHTTPD httpd; private final InputStream inputStream; private final Socket acceptSocket; public ClientHandler(NanoHTTPD httpd, InputStream inputStream, Socket acceptSocket) { this.httpd = httpd; this.inputStream = inputStream; this.acceptSocket = acceptSocket; } public void close() { NanoHTTPD.safeClose(this.inputStream); NanoHTTPD.safeClose(this.acceptSocket); } @Override public void run() { OutputStream outputStream = null; try { outputStream = this.acceptSocket.getOutputStream(); ITempFileManager tempFileManager = httpd.getTempFileManagerFactory().create(); HTTPSession session = new HTTPSession(httpd, 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.SEVERE, "Communication with the client broken, or an bug in the handler code", e); } } finally { NanoHTTPD.safeClose(outputStream); NanoHTTPD.safeClose(this.inputStream); NanoHTTPD.safeClose(this.acceptSocket); httpd.asyncRunner.closed(this); } } } ================================================ FILE: core/src/main/java/org/nanohttpd/protocols/http/HTTPSession.java ================================================ package org.nanohttpd.protocols.http; /* * #%L * NanoHttpd-Core * %% * Copyright (C) 2012 - 2016 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.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataOutput; import java.io.DataOutputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.RandomAccessFile; import java.net.InetAddress; import java.net.SocketException; import java.net.SocketTimeoutException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.StringTokenizer; import java.util.logging.Level; import java.util.regex.Matcher; import javax.net.ssl.SSLException; import org.nanohttpd.protocols.http.NanoHTTPD.ResponseException; import org.nanohttpd.protocols.http.content.ContentType; import org.nanohttpd.protocols.http.content.CookieHandler; import org.nanohttpd.protocols.http.request.Method; import org.nanohttpd.protocols.http.response.Response; import org.nanohttpd.protocols.http.response.Status; import org.nanohttpd.protocols.http.tempfiles.ITempFile; import org.nanohttpd.protocols.http.tempfiles.ITempFileManager; public class HTTPSession implements IHTTPSession { public static final String POST_DATA = "postData"; private static final int REQUEST_BUFFER_LEN = 512; private static final int MEMORY_STORE_LIMIT = 1024; public static final int BUFSIZE = 8192; public static final int MAX_HEADER_SIZE = 1024; private final NanoHTTPD httpd; private final ITempFileManager tempFileManager; private final OutputStream outputStream; private final BufferedInputStream inputStream; private int splitbyte; private int rlen; private String uri; private Method method; private Map> parms; private Map headers; private CookieHandler cookies; private String queryParameterString; private String remoteIp; private String protocolVersion; public HTTPSession(NanoHTTPD httpd, ITempFileManager tempFileManager, InputStream inputStream, OutputStream outputStream) { this.httpd = httpd; this.tempFileManager = tempFileManager; this.inputStream = new BufferedInputStream(inputStream, HTTPSession.BUFSIZE); this.outputStream = outputStream; } public HTTPSession(NanoHTTPD httpd, ITempFileManager tempFileManager, InputStream inputStream, OutputStream outputStream, InetAddress inetAddress) { this.httpd = httpd; 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(); } /** * Decodes the sent headers and loads the data into Key/value pairs */ private void decodeHeader(BufferedReader in, Map pre, Map> parms, Map headers) throws ResponseException { try { // Read the request line String inLine = in.readLine(); if (inLine == null) { return; } StringTokenizer st = new StringTokenizer(inLine); if (!st.hasMoreTokens()) { throw new ResponseException(Status.BAD_REQUEST, "BAD REQUEST: Syntax error. Usage: GET /example/file.html"); } pre.put("method", st.nextToken()); if (!st.hasMoreTokens()) { throw new ResponseException(Status.BAD_REQUEST, "BAD REQUEST: Missing URI. Usage: GET /example/file.html"); } String uri = st.nextToken(); // Decode parameters from the URI int qmi = uri.indexOf('?'); if (qmi >= 0) { decodeParms(uri.substring(qmi + 1), parms); uri = NanoHTTPD.decodePercent(uri.substring(0, qmi)); } else { uri = NanoHTTPD.decodePercent(uri); } // 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().isEmpty()) { 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(); } pre.put("uri", uri); } catch (IOException ioe) { throw new ResponseException(Status.INTERNAL_ERROR, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage(), ioe); } } /** * Decodes the Multipart Body data and put it into Key/Value pairs. */ private void decodeMultipartFormData(ContentType contentType, ByteBuffer fbuf, Map> parms, Map files) throws ResponseException { int pcount = 0; try { int[] boundaryIdxs = getBoundaryPositions(fbuf, contentType.getBoundary().getBytes()); if (boundaryIdxs.length < 2) { throw new ResponseException(Status.BAD_REQUEST, "BAD REQUEST: Content type is multipart/form-data but contains less than two boundary strings."); } byte[] partHeaderBuff = new byte[MAX_HEADER_SIZE]; for (int boundaryIdx = 0; boundaryIdx < boundaryIdxs.length - 1; boundaryIdx++) { fbuf.position(boundaryIdxs[boundaryIdx]); int len = (fbuf.remaining() < MAX_HEADER_SIZE) ? fbuf.remaining() : MAX_HEADER_SIZE; fbuf.get(partHeaderBuff, 0, len); BufferedReader in = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(partHeaderBuff, 0, len), Charset.forName(contentType.getEncoding())), len); int headerLines = 0; // First line is boundary string String mpline = in.readLine(); headerLines++; if (mpline == null || !mpline.contains(contentType.getBoundary())) { throw new ResponseException(Status.BAD_REQUEST, "BAD REQUEST: Content type is multipart/form-data but chunk does not start with boundary."); } String partName = null, fileName = null, partContentType = null; // Parse the reset of the header lines mpline = in.readLine(); headerLines++; while (mpline != null && mpline.trim().length() > 0) { Matcher matcher = NanoHTTPD.CONTENT_DISPOSITION_PATTERN.matcher(mpline); if (matcher.matches()) { String attributeString = matcher.group(2); matcher = NanoHTTPD.CONTENT_DISPOSITION_ATTRIBUTE_PATTERN.matcher(attributeString); while (matcher.find()) { String key = matcher.group(1); if ("name".equalsIgnoreCase(key)) { partName = matcher.group(2); } else if ("filename".equalsIgnoreCase(key)) { fileName = matcher.group(2); // add these two line to support multiple // files uploaded using the same field Id if (!fileName.isEmpty()) { if (pcount > 0) partName = partName + String.valueOf(pcount++); else pcount++; } } } } matcher = NanoHTTPD.CONTENT_TYPE_PATTERN.matcher(mpline); if (matcher.matches()) { partContentType = matcher.group(2).trim(); } mpline = in.readLine(); headerLines++; } int partHeaderLength = 0; while (headerLines-- > 0) { partHeaderLength = scipOverNewLine(partHeaderBuff, partHeaderLength); } // Read the part data if (partHeaderLength >= len - 4) { throw new ResponseException(Status.INTERNAL_ERROR, "Multipart header size exceeds MAX_HEADER_SIZE."); } int partDataStart = boundaryIdxs[boundaryIdx] + partHeaderLength; int partDataEnd = boundaryIdxs[boundaryIdx + 1] - 4; fbuf.position(partDataStart); List values = parms.get(partName); if (values == null) { values = new ArrayList(); parms.put(partName, values); } if (partContentType == null) { // Read the part into a string byte[] data_bytes = new byte[partDataEnd - partDataStart]; fbuf.get(data_bytes); values.add(new String(data_bytes, contentType.getEncoding())); } else { // Read it into a file String path = saveTmpFile(fbuf, partDataStart, partDataEnd - partDataStart, fileName); if (!files.containsKey(partName)) { files.put(partName, path); } else { int count = 2; while (files.containsKey(partName + count)) { count++; } files.put(partName + count, path); } values.add(fileName); } } } catch (ResponseException re) { throw re; } catch (Exception e) { throw new ResponseException(Status.INTERNAL_ERROR, e.toString()); } } private int scipOverNewLine(byte[] partHeaderBuff, int index) { while (partHeaderBuff[index] != '\n') { index++; } return ++index; } /** * Decodes parameters in percent-encoded URI-format ( e.g. * "name=Jack%20Daniels&pass=Single%20Malt" ) and adds them to given Map. */ private void decodeParms(String parms, Map> 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('='); String key = null; String value = null; if (sep >= 0) { key = NanoHTTPD.decodePercent(e.substring(0, sep)).trim(); value = NanoHTTPD.decodePercent(e.substring(sep + 1)); } else { key = NanoHTTPD.decodePercent(e).trim(); value = ""; } List values = p.get(key); if (values == null) { values = new ArrayList(); p.put(key, values); } values.add(value); } } @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[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 (SSLException e) { throw e; } catch (IOException e) { NanoHTTPD.safeClose(this.inputStream); NanoHTTPD.safeClose(this.outputStream); throw new SocketException("NanoHttpd Shutdown"); } if (read == -1) { // socket was been closed NanoHTTPD.safeClose(this.inputStream); NanoHTTPD.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 (this.splitbyte < this.rlen) { this.inputStream.reset(); this.inputStream.skip(this.splitbyte); } this.parms = new HashMap>(); if (null == this.headers) { this.headers = new HashMap(); } else { this.headers.clear(); } // Create a BufferedReader for parsing the header. BufferedReader hin = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(buf, 0, this.rlen))); // Decode the header into parms and header java properties Map pre = new HashMap(); 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); } this.method = Method.lookup(pre.get("method")); if (this.method == null) { throw new ResponseException(Status.BAD_REQUEST, "BAD REQUEST: Syntax error. HTTP verb " + pre.get("method") + " unhandled."); } this.uri = pre.get("uri"); this.cookies = new CookieHandler(this.headers); String connection = this.headers.get("connection"); boolean keepAlive = "HTTP/1.1".equals(protocolVersion) && (connection == null || !connection.matches("(?i).*close.*")); // Ok, now do the serve() // TODO: long body_size = getBodySize(); // TODO: long pos_before_serve = this.inputStream.totalRead() // (requires implementation for totalRead()) r = httpd.handle(this); // TODO: this.inputStream.skip(body_size - // (this.inputStream.totalRead() - pos_before_serve)) if (r == null) { throw new ResponseException(Status.INTERNAL_ERROR, "SERVER INTERNAL ERROR: Serve() returned a null response."); } else { String acceptEncoding = this.headers.get("accept-encoding"); this.cookies.unloadQueue(r); r.setRequestMethod(this.method); if (acceptEncoding == null || !acceptEncoding.contains("gzip")) { r.setUseGzip(false); } r.setKeepAlive(keepAlive); r.send(this.outputStream); } if (!keepAlive || r.isCloseConnection()) { throw new SocketException("NanoHttpd Shutdown"); } } catch (SocketException e) { // throw it out to close socket object (finalAccept) throw e; } catch (SocketTimeoutException 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 (SSLException ssle) { Response resp = Response.newFixedLengthResponse(Status.INTERNAL_ERROR, NanoHTTPD.MIME_PLAINTEXT, "SSL PROTOCOL FAILURE: " + ssle.getMessage()); resp.send(this.outputStream); NanoHTTPD.safeClose(this.outputStream); } catch (IOException ioe) { Response resp = Response.newFixedLengthResponse(Status.INTERNAL_ERROR, NanoHTTPD.MIME_PLAINTEXT, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage()); resp.send(this.outputStream); NanoHTTPD.safeClose(this.outputStream); } catch (ResponseException re) { Response resp = Response.newFixedLengthResponse(re.getStatus(), NanoHTTPD.MIME_PLAINTEXT, re.getMessage()); resp.send(this.outputStream); NanoHTTPD.safeClose(this.outputStream); } finally { NanoHTTPD.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 CookieHandler getCookies() { return this.cookies; } @Override public final Map getHeaders() { return this.headers; } @Override public final InputStream getInputStream() { return this.inputStream; } @Override public final Method getMethod() { return this.method; } /** * @deprecated use {@link #getParameters()} instead. */ @Override @Deprecated public final Map getParms() { Map result = new HashMap(); for (String key : this.parms.keySet()) { result.put(key, this.parms.get(key).get(0)); } return result; } @Override public final Map> getParameters() { return this.parms; } @Override public String getQueryParameterString() { return this.queryParameterString; } private RandomAccessFile getTmpBucket() { try { ITempFile 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 } } @Override public final String getUri() { return this.uri; } /** * 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 files) throws IOException, ResponseException { RandomAccessFile randomAccessFile = null; try { long size = getBodySize(); ByteArrayOutputStream baos = null; DataOutput requestDataOutput = null; // Store the request in memory or a file, depending on size if (size < MEMORY_STORE_LIMIT) { baos = new ByteArrayOutputStream(); requestDataOutput = new DataOutputStream(baos); } else { randomAccessFile = getTmpBucket(); requestDataOutput = randomAccessFile; } // 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) { requestDataOutput.write(buf, 0, this.rlen); } } 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(this.method)) { ContentType contentType = new ContentType(this.headers.get("content-type")); if (contentType.isMultipart()) { String boundary = contentType.getBoundary(); if (boundary == null) { throw new ResponseException(Status.BAD_REQUEST, "BAD REQUEST: Content type is multipart/form-data but boundary missing. Usage: GET /example/file.html"); } decodeMultipartFormData(contentType, fbuf, this.parms, files); } else { byte[] postBytes = new byte[fbuf.remaining()]; fbuf.get(postBytes); String postLine = new String(postBytes, contentType.getEncoding()).trim(); // Handle application/x-www-form-urlencoded if ("application/x-www-form-urlencoded".equalsIgnoreCase(contentType.getContentType())) { 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(POST_DATA, postLine); } } } else if (Method.PUT.equals(this.method)) { files.put("content", saveTmpFile(fbuf, 0, fbuf.limit(), null)); } } finally { NanoHTTPD.safeClose(randomAccessFile); } } /** * Retrieves the content of a sent file and saves it to a temporary file. * The full path to the saved file is returned. */ private String saveTmpFile(ByteBuffer b, int offset, int len, String filename_hint) { String path = ""; if (len > 0) { FileOutputStream fileOutputStream = null; try { ITempFile 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 { NanoHTTPD.safeClose(fileOutputStream); } } return path; } @Override public String getRemoteIpAddress() { return this.remoteIp; } } ================================================ FILE: core/src/main/java/org/nanohttpd/protocols/http/IHTTPSession.java ================================================ package org.nanohttpd.protocols.http; /* * #%L * NanoHttpd-Core * %% * Copyright (C) 2012 - 2016 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.io.InputStream; import java.util.List; import java.util.Map; import org.nanohttpd.protocols.http.NanoHTTPD.ResponseException; import org.nanohttpd.protocols.http.content.CookieHandler; import org.nanohttpd.protocols.http.request.Method; /** * Handles one session, i.e. parses the HTTP request and returns the response. */ public interface IHTTPSession { void execute() throws IOException; CookieHandler getCookies(); Map getHeaders(); InputStream getInputStream(); Method getMethod(); /** * This method will only return the first value for a given parameter. You * will want to use getParameters if you expect multiple values for a given * key. * * @deprecated use {@link #getParameters()} instead. */ @Deprecated Map getParms(); Map> getParameters(); 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 files) throws IOException, ResponseException; /** * Get the remote ip address of the requester. * * @return the IP address. */ String getRemoteIpAddress(); } ================================================ FILE: core/src/main/java/org/nanohttpd/protocols/http/NanoHTTPD.java ================================================ package org.nanohttpd.protocols.http; /* * #%L * NanoHttpd-Core * %% * Copyright (C) 2012 - 2016 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.Closeable; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.net.ServerSocket; import java.net.Socket; import java.net.URL; import java.net.URLDecoder; import java.security.KeyStore; import java.util.ArrayList; import java.util.Enumeration; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.StringTokenizer; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Pattern; import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLServerSocketFactory; import javax.net.ssl.TrustManagerFactory; import org.nanohttpd.protocols.http.response.Response; import org.nanohttpd.protocols.http.response.Status; import org.nanohttpd.protocols.http.sockets.DefaultServerSocketFactory; import org.nanohttpd.protocols.http.sockets.SecureServerSocketFactory; import org.nanohttpd.protocols.http.tempfiles.DefaultTempFileManagerFactory; import org.nanohttpd.protocols.http.tempfiles.ITempFileManager; import org.nanohttpd.protocols.http.threading.DefaultAsyncRunner; import org.nanohttpd.protocols.http.threading.IAsyncRunner; import org.nanohttpd.util.IFactory; import org.nanohttpd.util.IFactoryThrowing; import org.nanohttpd.util.IHandler; /** * A simple, tiny, nicely embeddable HTTP server in Java *

*

* NanoHTTPD *

* Copyright (c) 2012-2013 by Paul S. Hawke, 2001,2005-2013 by Jarno Elonen, * 2010 by Konstantinos Togias *

*

*

* Features + limitations: *

    *

    *

  • Only one Java file
  • *
  • Java 5 compatible
  • *
  • Released as open source, Modified BSD licence
  • *
  • No fixed config files, logging, authorization etc. (Implement yourself if * you need them.)
  • *
  • Supports parameter parsing of GET and POST methods (+ rudimentary PUT * support in 1.25)
  • *
  • Supports both dynamic content and file serving
  • *
  • Supports file upload (since version 1.2, 2010)
  • *
  • Supports partial content (streaming)
  • *
  • Supports ETags
  • *
  • Never caches anything
  • *
  • Doesn't limit bandwidth, request time or simultaneous connections
  • *
  • Default code serves files and shows all HTTP parameters and headers
  • *
  • File server supports directory listing, index.html and index.htm
  • *
  • File server supports partial content (streaming)
  • *
  • File server supports ETags
  • *
  • File server does the 301 redirection trick for directories without '/'
  • *
  • File server supports simple skipping for files (continue download)
  • *
  • File server serves also very long files without memory overhead
  • *
  • Contains a built-in list of most common MIME types
  • *
  • All header names are converted to lower case so they don't vary between * browsers/clients
  • *

    *

*

*

* How to use: *

    *

    *

  • Subclass and implement serve() and embed to your own program
  • *

    *

*

* See the separate "LICENSE.md" file for the distribution license (Modified BSD * licence) */ public abstract class NanoHTTPD { public static final String CONTENT_DISPOSITION_REGEX = "([ |\t]*Content-Disposition[ |\t]*:)(.*)"; public static final Pattern CONTENT_DISPOSITION_PATTERN = Pattern.compile(CONTENT_DISPOSITION_REGEX, Pattern.CASE_INSENSITIVE); public static final String CONTENT_TYPE_REGEX = "([ |\t]*content-type[ |\t]*:)(.*)"; public static final Pattern CONTENT_TYPE_PATTERN = Pattern.compile(CONTENT_TYPE_REGEX, Pattern.CASE_INSENSITIVE); public static final String CONTENT_DISPOSITION_ATTRIBUTE_REGEX = "[ |\t]*([a-zA-Z]*)[ |\t]*=[ |\t]*['|\"]([^\"^']*)['|\"]"; public static final Pattern CONTENT_DISPOSITION_ATTRIBUTE_PATTERN = Pattern.compile(CONTENT_DISPOSITION_ATTRIBUTE_REGEX); public static final class ResponseException extends Exception { private static final long serialVersionUID = 6569838532917408380L; private final Status status; public ResponseException(Status status, String message) { super(message); this.status = status; } public ResponseException(Status status, String message, Exception e) { super(message, e); this.status = status; } public Status getStatus() { return this.status; } } /** * 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. */ public static final Logger LOG = Logger.getLogger(NanoHTTPD.class.getName()); /** * Hashtable mapping (String)FILENAME_EXTENSION -> (String)MIME_TYPE */ protected static Map MIME_TYPES; public static Map mimeTypes() { if (MIME_TYPES == null) { MIME_TYPES = new HashMap(); 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; } @SuppressWarnings({ "unchecked", "rawtypes" }) private static void loadMimeTypes(Map result, String resourceName) { try { Enumeration 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(stream); } 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); } }; /** * 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; } /** * 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()); } } /** * 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); if (keystoreStream == null) { throw new IOException("Unable to load keystore from classpath: " + 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()); } } /** * Get MIME type from file name extension, if possible * * @param uri * the string representing a file * @return the connected mime/type */ 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 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); } } public final String hostname; public final int myPort; private volatile ServerSocket myServerSocket; public ServerSocket getMyServerSocket() { return myServerSocket; } private IFactoryThrowing serverSocketFactory = new DefaultServerSocketFactory(); private Thread myThread; private IHandler httpHandler; protected List> interceptors = new ArrayList>(4); /** * Pluggable strategy for asynchronously executing requests. */ protected IAsyncRunner asyncRunner; /** * Pluggable strategy for creating and cleaning up temporary files. */ private IFactory 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()); // creates a default handler that redirects to deprecated serve(); this.httpHandler = new IHandler() { @Override public Response handle(IHTTPSession input) { return NanoHTTPD.this.serve(input); } }; } public void setHTTPHandler(IHandler handler) { this.httpHandler = handler; } public void addHTTPInterceptor(IHandler interceptor) { interceptors.add(interceptor); } /** * 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(this, 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(this, timeout); } /** * 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 NanoHTTPD parameters values, as passed to the * serve() method. * @return a map of String (parameter name) to * List<String> (a list of the values supplied). */ protected static Map> decodeParameters(Map 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 String (parameter name) to * List<String> (a list of the values supplied). */ protected static Map> decodeParameters(String queryString) { Map> parms = new HashMap>(); 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 propertyValue = sep >= 0 ? decodePercent(e.substring(sep + 1)) : null; if (propertyValue != null) { parms.get(propertyName).add(propertyValue); } } } return parms; } /** * Decode percent encoded String values. * * @param str * the percent encoded String * @return expanded form of the input, for example "foo%20bar" becomes * "foo bar" */ public 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; } 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 IFactoryThrowing getServerSocketFactory() { return serverSocketFactory; } public void setServerSocketFactory(IFactoryThrowing serverSocketFactory) { this.serverSocketFactory = serverSocketFactory; } public String getHostname() { return hostname; } public IFactory 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); } /** * This is the "master" method that delegates requests to handlers and makes * sure there is a response to every request. You are not supposed to call * or override this method in any circumstances. But no one will stop you if * you do. I'm a Javadoc, not Code Police. * * @param session * the incoming session * @return a response to the incoming session */ public Response handle(IHTTPSession session) { for (IHandler interceptor : interceptors) { Response response = interceptor.handle(session); if (response != null) return response; } return httpHandler.handle(session); } /** * Override this to customize the server. *

*

* (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 */ @Deprecated protected Response serve(IHTTPSession session) { return Response.newFixedLengthResponse(Status.NOT_FOUND, NanoHTTPD.MIME_PLAINTEXT, "Not Found"); } /** * Pluggable strategy for asynchronously executing requests. * * @param asyncRunner * new strategy for handling threads. */ public void setAsyncRunner(IAsyncRunner asyncRunner) { this.asyncRunner = asyncRunner; } /** * Pluggable strategy for creating and cleaning up temporary files. * * @param tempFileManagerFactory * new strategy for handling temp files. */ public void setTempFileManagerFactory(IFactory 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.getBindException() == 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.getBindException() != null) { throw serverRunnable.getBindException(); } } /** * 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; } } ================================================ FILE: core/src/main/java/org/nanohttpd/protocols/http/ServerRunnable.java ================================================ package org.nanohttpd.protocols.http; /* * #%L * NanoHttpd-Core * %% * Copyright (C) 2012 - 2016 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.io.InputStream; import java.net.InetSocketAddress; import java.net.Socket; import java.util.logging.Level; /** * The runnable that will be used for the main listening thread. */ public class ServerRunnable implements Runnable { private NanoHTTPD httpd; private final int timeout; private IOException bindException; private boolean hasBinded = false; public ServerRunnable(NanoHTTPD httpd, int timeout) { this.httpd = httpd; this.timeout = timeout; } @Override public void run() { try { httpd.getMyServerSocket().bind(httpd.hostname != null ? new InetSocketAddress(httpd.hostname, httpd.myPort) : new InetSocketAddress(httpd.myPort)); hasBinded = true; } catch (IOException e) { this.bindException = e; return; } do { try { final Socket finalAccept = httpd.getMyServerSocket().accept(); if (this.timeout > 0) { finalAccept.setSoTimeout(this.timeout); } final InputStream inputStream = finalAccept.getInputStream(); httpd.asyncRunner.exec(httpd.createClientHandler(finalAccept, inputStream)); } catch (IOException e) { NanoHTTPD.LOG.log(Level.FINE, "Communication with the client broken", e); } } while (!httpd.getMyServerSocket().isClosed()); } public IOException getBindException() { return bindException; } public boolean hasBinded() { return hasBinded; } } ================================================ FILE: core/src/main/java/org/nanohttpd/protocols/http/content/ContentType.java ================================================ package org.nanohttpd.protocols.http.content; /* * #%L * NanoHttpd-Core * %% * Copyright (C) 2012 - 2016 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.util.regex.Matcher; import java.util.regex.Pattern; public class ContentType { private static final String ASCII_ENCODING = "US-ASCII"; private static final String MULTIPART_FORM_DATA_HEADER = "multipart/form-data"; private static final String CONTENT_REGEX = "[ |\t]*([^/^ ^;^,]+/[^ ^;^,]+)"; private static final Pattern MIME_PATTERN = Pattern.compile(CONTENT_REGEX, Pattern.CASE_INSENSITIVE); private static final String CHARSET_REGEX = "[ |\t]*(charset)[ |\t]*=[ |\t]*['|\"]?([^\"^'^;^,]*)['|\"]?"; private static final Pattern CHARSET_PATTERN = Pattern.compile(CHARSET_REGEX, Pattern.CASE_INSENSITIVE); private static final String BOUNDARY_REGEX = "[ |\t]*(boundary)[ |\t]*=[ |\t]*['|\"]?([^\"^'^;^,]*)['|\"]?"; private static final Pattern BOUNDARY_PATTERN = Pattern.compile(BOUNDARY_REGEX, Pattern.CASE_INSENSITIVE); private final String contentTypeHeader; private final String contentType; private final String encoding; private final String boundary; public ContentType(String contentTypeHeader) { this.contentTypeHeader = contentTypeHeader; if (contentTypeHeader != null) { contentType = getDetailFromContentHeader(contentTypeHeader, MIME_PATTERN, "", 1); encoding = getDetailFromContentHeader(contentTypeHeader, CHARSET_PATTERN, null, 2); } else { contentType = ""; encoding = "UTF-8"; } if (MULTIPART_FORM_DATA_HEADER.equalsIgnoreCase(contentType)) { boundary = getDetailFromContentHeader(contentTypeHeader, BOUNDARY_PATTERN, null, 2); } else { boundary = null; } } private String getDetailFromContentHeader(String contentTypeHeader, Pattern pattern, String defaultValue, int group) { Matcher matcher = pattern.matcher(contentTypeHeader); return matcher.find() ? matcher.group(group) : defaultValue; } public String getContentTypeHeader() { return contentTypeHeader; } public String getContentType() { return contentType; } public String getEncoding() { return encoding == null ? ASCII_ENCODING : encoding; } public String getBoundary() { return boundary; } public boolean isMultipart() { return MULTIPART_FORM_DATA_HEADER.equalsIgnoreCase(contentType); } public ContentType tryUTF8() { if (encoding == null) { return new ContentType(this.contentTypeHeader + "; charset=UTF-8"); } return this; } } ================================================ FILE: core/src/main/java/org/nanohttpd/protocols/http/content/Cookie.java ================================================ package org.nanohttpd.protocols.http.content; /* * #%L * NanoHttpd-Core * %% * Copyright (C) 2012 - 2016 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.text.SimpleDateFormat; import java.util.Calendar; import java.util.Locale; import java.util.TimeZone; /** * A simple cookie representation. This is old code and is flawed in many ways. * * @author LordFokas */ public class Cookie { 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()); } private final String n, v, e; public Cookie(String name, String value) { this(name, value, 30); } public Cookie(String name, String value, int numDays) { this.n = name; this.v = value; this.e = getHTTPTime(numDays); } public Cookie(String name, String value, String expires) { this.n = name; this.v = value; this.e = expires; } public String getHTTPHeader() { String fmt = "%s=%s; expires=%s"; return String.format(fmt, this.n, this.v, this.e); } } ================================================ FILE: core/src/main/java/org/nanohttpd/protocols/http/content/CookieHandler.java ================================================ package org.nanohttpd.protocols.http.content; /* * #%L * NanoHttpd-Core * %% * Copyright (C) 2012 - 2016 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.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import org.nanohttpd.protocols.http.response.Response; /** * Provides rudimentary support for cookies. Doesn't support 'path', 'secure' * nor 'httpOnly'. Feel free to improve it and/or add unsupported features. This * is old code and it's flawed in many ways. * * @author LordFokas */ public class CookieHandler implements Iterable { private final HashMap cookies = new HashMap(); private final ArrayList queue = new ArrayList(); public CookieHandler(Map 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]); } } } } /** * 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); } @Override public Iterator iterator() { return this.cookies.keySet().iterator(); } /** * 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); } 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.addCookieHeader(cookie.getHTTPHeader()); } } } ================================================ FILE: core/src/main/java/org/nanohttpd/protocols/http/request/Method.java ================================================ package org.nanohttpd.protocols.http.request; /* * #%L * NanoHttpd-Core * %% * Copyright (C) 2012 - 2016 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% */ /** * HTTP Request methods, with the ability to decode a String back * to its enum value. */ public enum Method { GET, PUT, POST, DELETE, HEAD, OPTIONS, TRACE, CONNECT, PATCH, PROPFIND, PROPPATCH, MKCOL, MOVE, COPY, LOCK, UNLOCK, NOTIFY, SUBSCRIBE; public static Method lookup(String method) { if (method == null) return null; try { return valueOf(method); } catch (IllegalArgumentException e) { // TODO: Log it? return null; } } } ================================================ FILE: core/src/main/java/org/nanohttpd/protocols/http/response/ChunkedOutputStream.java ================================================ package org.nanohttpd.protocols.http.response; /* * #%L * NanoHttpd-Core * %% * Copyright (C) 2012 - 2016 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.FilterOutputStream; import java.io.IOException; import java.io.OutputStream; /** * 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 */ public class ChunkedOutputStream extends FilterOutputStream { public ChunkedOutputStream(OutputStream out) { super(out); } @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()); } } ================================================ FILE: core/src/main/java/org/nanohttpd/protocols/http/response/IStatus.java ================================================ package org.nanohttpd.protocols.http.response; /* * #%L * NanoHttpd-Core * %% * Copyright (C) 2012 - 2016 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% */ public interface IStatus { String getDescription(); int getRequestStatus(); } ================================================ FILE: core/src/main/java/org/nanohttpd/protocols/http/response/Response.java ================================================ package org.nanohttpd.protocols.http.response; /* * #%L * NanoHttpd-Core * %% * Copyright (C) 2012 - 2016 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.BufferedWriter; import java.io.ByteArrayInputStream; import java.io.Closeable; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.UnsupportedEncodingException; import java.nio.charset.Charset; import java.nio.charset.CharsetEncoder; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.TimeZone; import java.util.logging.Level; import java.util.zip.GZIPOutputStream; import org.nanohttpd.protocols.http.NanoHTTPD; import org.nanohttpd.protocols.http.content.ContentType; import org.nanohttpd.protocols.http.request.Method; /** * HTTP response. Return one of these from serve(). */ public class Response implements Closeable { /** * HTTP status code after processing, e.g. "200 OK", Status.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; private long contentLength; /** * Headers for the HTTP response. Use addHeader() to add lines. the * lowercase map is automatically kept up to date. */ @SuppressWarnings("serial") private final Map header = new HashMap() { public String put(String key, String value) { lowerCaseHeader.put(key == null ? key : key.toLowerCase(), value); return super.put(key, value); }; }; /** * copy of the header map with all the keys lowercase for faster searching. */ private final Map lowerCaseHeader = new HashMap(); /** * The request method that spawned this response. */ private Method requestMethod; /** * Use chunkedTransfer */ private boolean chunkedTransfer; private boolean keepAlive; private List cookieHeaders; private GzipUsage gzipUsage = GzipUsage.DEFAULT; private static enum GzipUsage { DEFAULT, ALWAYS, NEVER; } /** * Creates a fixed length response if totalBytes>=0, otherwise chunked. */ @SuppressWarnings({ "rawtypes", "unchecked" }) 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; } this.chunkedTransfer = this.contentLength < 0; this.keepAlive = true; this.cookieHeaders = new ArrayList(10); } @Override public void close() throws IOException { if (this.data != null) { this.data.close(); } } /** * Adds a cookie header to the list. Should not be called manually, this is * an internal utility. */ public void addCookieHeader(String cookie) { cookieHeaders.add(cookie); } /** * Should not be called manually. This is an internally utility for JUnit * test purposes. * * @return All unloaded cookie headers. */ public List getCookieHeaders() { return cookieHeaders; } /** * Adds given line to the header. */ public void addHeader(String name, String value) { this.header.put(name, value); } /** * Indicate to close the connection after the Response has been sent. * * @param close * {@code true} to hint connection closing, {@code false} to let * connection be closed by client. */ public void closeConnection(boolean close) { if (close) this.header.put("connection", "close"); else this.header.remove("connection"); } /** * @return {@code true} if connection is to be closed after this Response * has been sent. */ public boolean isCloseConnection() { return "close".equals(getHeader("connection")); } public InputStream getData() { return this.data; } public String getHeader(String name) { return this.lowerCaseHeader.get(name.toLowerCase()); } public String getMimeType() { return this.mimeType; } public Method getRequestMethod() { return this.requestMethod; } public IStatus getStatus() { return this.status; } public void setKeepAlive(boolean useKeepAlive) { this.keepAlive = useKeepAlive; } /** * Sends given response to the socket. */ public void send(OutputStream outputStream) { 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, new ContentType(this.mimeType).getEncoding())), false); pw.append("HTTP/1.1 ").append(this.status.getDescription()).append(" \r\n"); if (this.mimeType != null) { printHeader(pw, "Content-Type", this.mimeType); } if (getHeader("date") == null) { printHeader(pw, "Date", gmtFrmt.format(new Date())); } for (Entry entry : this.header.entrySet()) { printHeader(pw, entry.getKey(), entry.getValue()); } for (String cookieHeader : this.cookieHeaders) { printHeader(pw, "Set-Cookie", cookieHeader); } if (getHeader("connection") == null) { printHeader(pw, "Connection", (this.keepAlive ? "keep-alive" : "close")); } if (getHeader("content-length") != null) { setUseGzip(false); } if (useGzipWhenAccepted()) { printHeader(pw, "Content-Encoding", "gzip"); setChunkedTransfer(true); } long pending = this.data != null ? this.contentLength : 0; if (this.requestMethod != Method.HEAD && this.chunkedTransfer) { printHeader(pw, "Transfer-Encoding", "chunked"); } else if (!useGzipWhenAccepted()) { pending = sendContentLengthHeaderIfNotAlreadyPresent(pw, pending); } pw.append("\r\n"); pw.flush(); sendBodyWithCorrectTransferAndEncoding(outputStream, pending); outputStream.flush(); NanoHTTPD.safeClose(this.data); } catch (IOException ioe) { NanoHTTPD.LOG.log(Level.SEVERE, "Could not send response to the client", ioe); } } @SuppressWarnings("static-method") protected void printHeader(PrintWriter pw, String key, String value) { pw.append(key).append(": ").append(value).append("\r\n"); } protected long sendContentLengthHeaderIfNotAlreadyPresent(PrintWriter pw, long defaultSize) { String contentLengthString = getHeader("content-length"); long size = defaultSize; if (contentLengthString != null) { try { size = Long.parseLong(contentLengthString); } catch (NumberFormatException ex) { NanoHTTPD.LOG.severe("content-length was no number " + contentLengthString); } }else{ pw.print("Content-Length: " + size + "\r\n"); } return size; } private void sendBodyWithCorrectTransferAndEncoding(OutputStream outputStream, long pending) throws IOException { if (this.requestMethod != Method.HEAD && this.chunkedTransfer) { ChunkedOutputStream chunkedOutputStream = new ChunkedOutputStream(outputStream); sendBodyWithCorrectEncoding(chunkedOutputStream, -1); try { chunkedOutputStream.finish(); } catch (Exception e) { if(this.data != null) { this.data.close(); } } } else { sendBodyWithCorrectEncoding(outputStream, pending); } } private void sendBodyWithCorrectEncoding(OutputStream outputStream, long pending) throws IOException { if (useGzipWhenAccepted()) { GZIPOutputStream gzipOutputStream = null; try { gzipOutputStream = new GZIPOutputStream(outputStream); } catch (Exception e) { if(this.data != null) { this.data.close(); } } if (gzipOutputStream != null) { sendBody(gzipOutputStream, -1); gzipOutputStream.finish(); } } else { sendBody(outputStream, pending); } } /** * 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 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; } try { outputStream.write(buff, 0, read); } catch (Exception e) { if(this.data != null) { this.data.close(); } } if (!sendEverything) { pending -= read; } } } public void setChunkedTransfer(boolean chunkedTransfer) { this.chunkedTransfer = chunkedTransfer; } public void setData(InputStream data) { this.data = data; } public void setMimeType(String mimeType) { this.mimeType = mimeType; } public void setRequestMethod(Method requestMethod) { this.requestMethod = requestMethod; } public void setStatus(IStatus status) { this.status = status; } /** * 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); } public static Response newFixedLengthResponse(IStatus status, String mimeType, byte[] data) { return newFixedLengthResponse(status, mimeType, new ByteArrayInputStream(data), data.length); } /** * 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) { ContentType contentType = new ContentType(mimeType); if (txt == null) { return newFixedLengthResponse(status, mimeType, new ByteArrayInputStream(new byte[0]), 0); } else { byte[] bytes; try { CharsetEncoder newEncoder = Charset.forName(contentType.getEncoding()).newEncoder(); if (!newEncoder.canEncode(txt)) { contentType = contentType.tryUTF8(); } bytes = txt.getBytes(contentType.getEncoding()); } catch (UnsupportedEncodingException e) { NanoHTTPD.LOG.log(Level.SEVERE, "encoding problem, responding nothing", e); bytes = new byte[0]; } return newFixedLengthResponse(status, contentType.getContentTypeHeader(), new ByteArrayInputStream(bytes), bytes.length); } } /** * Create a text response with known length. */ public static Response newFixedLengthResponse(String msg) { return newFixedLengthResponse(Status.OK, NanoHTTPD.MIME_HTML, msg); } public Response setUseGzip(boolean useGzip) { gzipUsage = useGzip ? GzipUsage.ALWAYS : GzipUsage.NEVER; return this; } // If a Gzip usage has been enforced, use it. // Else decide whether or not to use Gzip. public boolean useGzipWhenAccepted() { if (gzipUsage == GzipUsage.DEFAULT) return getMimeType() != null && (getMimeType().toLowerCase().contains("text/") || getMimeType().toLowerCase().contains("/json")); else return gzipUsage == GzipUsage.ALWAYS; } } ================================================ FILE: core/src/main/java/org/nanohttpd/protocols/http/response/Status.java ================================================ package org.nanohttpd.protocols.http.response; /* * #%L * NanoHttpd-Core * %% * Copyright (C) 2012 - 2016 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% */ /** * Some HTTP response status codes */ 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"), MULTI_STATUS(207, "Multi-Status"), REDIRECT(301, "Moved Permanently"), /** * Many user agents mishandle 302 in ways that violate the RFC1945 spec * (i.e., redirect a POST to a GET). 303 and 307 were added in RFC2616 to * address this. You should prefer 303 and 307 unless the calling user agent * does not support 303 and 307 functionality */ @Deprecated FOUND(302, "Found"), REDIRECT_SEE_OTHER(303, "See Other"), NOT_MODIFIED(304, "Not Modified"), TEMPORARY_REDIRECT(307, "Temporary Redirect"), 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"), GONE(410, "Gone"), LENGTH_REQUIRED(411, "Length Required"), PRECONDITION_FAILED(412, "Precondition Failed"), PAYLOAD_TOO_LARGE(413, "Payload Too Large"), UNSUPPORTED_MEDIA_TYPE(415, "Unsupported Media Type"), RANGE_NOT_SATISFIABLE(416, "Requested Range Not Satisfiable"), EXPECTATION_FAILED(417, "Expectation Failed"), TOO_MANY_REQUESTS(429, "Too Many Requests"), INTERNAL_ERROR(500, "Internal Server Error"), NOT_IMPLEMENTED(501, "Not Implemented"), SERVICE_UNAVAILABLE(503, "Service Unavailable"), 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; } public static Status lookup(int requestStatus) { for (Status status : Status.values()) { if (status.getRequestStatus() == requestStatus) { return status; } } return null; } @Override public String getDescription() { return "" + this.requestStatus + " " + this.description; } @Override public int getRequestStatus() { return this.requestStatus; } } ================================================ FILE: core/src/main/java/org/nanohttpd/protocols/http/sockets/DefaultServerSocketFactory.java ================================================ package org.nanohttpd.protocols.http.sockets; /* * #%L * NanoHttpd-Core * %% * Copyright (C) 2012 - 2016 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.nanohttpd.util.IFactoryThrowing; /** * Creates a normal ServerSocket for TCP connections */ public class DefaultServerSocketFactory implements IFactoryThrowing { @Override public ServerSocket create() throws IOException { return new ServerSocket(); } } ================================================ FILE: core/src/main/java/org/nanohttpd/protocols/http/sockets/SecureServerSocketFactory.java ================================================ package org.nanohttpd.protocols.http.sockets; /* * #%L * NanoHttpd-Core * %% * Copyright (C) 2012 - 2016 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 javax.net.ssl.SSLServerSocket; import javax.net.ssl.SSLServerSocketFactory; import org.nanohttpd.util.IFactoryThrowing; /** * Creates a new SSLServerSocket */ public class SecureServerSocketFactory implements IFactoryThrowing { private SSLServerSocketFactory sslServerSocketFactory; private String[] sslProtocols; public SecureServerSocketFactory(SSLServerSocketFactory sslServerSocketFactory, String[] sslProtocols) { this.sslServerSocketFactory = sslServerSocketFactory; this.sslProtocols = sslProtocols; } @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()); } ss.setUseClientMode(false); ss.setWantClientAuth(false); ss.setNeedClientAuth(false); return ss; } } ================================================ FILE: core/src/main/java/org/nanohttpd/protocols/http/tempfiles/DefaultTempFile.java ================================================ package org.nanohttpd.protocols.http.tempfiles; /* * #%L * NanoHttpd-Core * %% * Copyright (C) 2012 - 2016 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.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import org.nanohttpd.protocols.http.NanoHTTPD; /** * Default strategy for creating and cleaning up temporary files. *

*

* By default, files are created by File.createTempFile() in the * directory specified. *

*/ public class DefaultTempFile implements ITempFile { private final File file; private final OutputStream fstream; public DefaultTempFile(File tempdir) throws IOException { this.file = File.createTempFile("NanoHTTPD-", "", tempdir); this.fstream = new FileOutputStream(this.file); } @Override public void delete() throws Exception { NanoHTTPD.safeClose(this.fstream); if (!this.file.delete()) { throw new Exception("could not delete temporary file: " + this.file.getAbsolutePath()); } } @Override public String getName() { return this.file.getAbsolutePath(); } @Override public OutputStream open() throws Exception { return this.fstream; } } ================================================ FILE: core/src/main/java/org/nanohttpd/protocols/http/tempfiles/DefaultTempFileManager.java ================================================ package org.nanohttpd.protocols.http.tempfiles; /* * #%L * NanoHttpd-Core * %% * Copyright (C) 2012 - 2016 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.util.ArrayList; import java.util.List; import java.util.logging.Level; import org.nanohttpd.protocols.http.NanoHTTPD; /** * Default strategy for creating and cleaning up temporary files. *

*

* This class stores its files in the standard location (that is, wherever * java.io.tmpdir points to). Files are added to an internal list, * and deleted when no longer needed (that is, when clear() is * invoked at the end of processing a request). *

*/ public class DefaultTempFileManager implements ITempFileManager { private final File tmpdir; private final List tempFiles; public DefaultTempFileManager() { this.tmpdir = new File(System.getProperty("java.io.tmpdir")); if (!tmpdir.exists()) { tmpdir.mkdirs(); } this.tempFiles = new ArrayList(); } @Override public void clear() { for (ITempFile file : this.tempFiles) { try { file.delete(); } catch (Exception ignored) { NanoHTTPD.LOG.log(Level.WARNING, "could not delete file ", ignored); } } this.tempFiles.clear(); } @Override public ITempFile createTempFile(String filename_hint) throws Exception { DefaultTempFile tempFile = new DefaultTempFile(this.tmpdir); this.tempFiles.add(tempFile); return tempFile; } } ================================================ FILE: core/src/main/java/org/nanohttpd/protocols/http/tempfiles/DefaultTempFileManagerFactory.java ================================================ package org.nanohttpd.protocols.http.tempfiles; /* * #%L * NanoHttpd-Core * %% * Copyright (C) 2012 - 2016 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.nanohttpd.util.IFactory; /** * Default strategy for creating and cleaning up temporary files. */ public class DefaultTempFileManagerFactory implements IFactory { @Override public ITempFileManager create() { return new DefaultTempFileManager(); } } ================================================ FILE: core/src/main/java/org/nanohttpd/protocols/http/tempfiles/ITempFile.java ================================================ package org.nanohttpd.protocols.http.tempfiles; /* * #%L * NanoHttpd-Core * %% * Copyright (C) 2012 - 2016 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.OutputStream; /** * A temp file. *

*

* Temp files are responsible for managing the actual temporary storage and * cleaning themselves up when no longer needed. *

*/ public interface ITempFile { public void delete() throws Exception; public String getName(); public OutputStream open() throws Exception; } ================================================ FILE: core/src/main/java/org/nanohttpd/protocols/http/tempfiles/ITempFileManager.java ================================================ package org.nanohttpd.protocols.http.tempfiles; /* * #%L * NanoHttpd-Core * %% * Copyright (C) 2012 - 2016 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% */ /** * Temp file manager. *

*

* 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. *

*/ public interface ITempFileManager { void clear(); public ITempFile createTempFile(String filename_hint) throws Exception; } ================================================ FILE: core/src/main/java/org/nanohttpd/protocols/http/threading/DefaultAsyncRunner.java ================================================ package org.nanohttpd.protocols.http.threading; /* * #%L * NanoHttpd-Core * %% * Copyright (C) 2012 - 2016 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.util.ArrayList; import java.util.Collections; import java.util.List; import org.nanohttpd.protocols.http.ClientHandler; /** * Default threading strategy for NanoHTTPD. *

*

* By default, the server spawns a new Thread for every incoming request. These * are set to daemon status, and named according to the request number. * The name is useful when profiling the application. *

*/ public class DefaultAsyncRunner implements IAsyncRunner { protected long requestCount; private final List running = Collections.synchronizedList(new ArrayList()); /** * @return a list with currently running clients. */ public List getRunning() { return running; } @Override public void closeAll() { // copy of the list for concurrency for (ClientHandler clientHandler : new ArrayList(this.running)) { clientHandler.close(); } } @Override public void closed(ClientHandler clientHandler) { this.running.remove(clientHandler); } @Override public void exec(ClientHandler clientHandler) { ++this.requestCount; this.running.add(clientHandler); createThread(clientHandler).start(); } protected Thread createThread(ClientHandler clientHandler) { Thread t = new Thread(clientHandler); t.setDaemon(true); t.setName("NanoHttpd Request Processor (#" + this.requestCount + ")"); return t; } } ================================================ FILE: core/src/main/java/org/nanohttpd/protocols/http/threading/IAsyncRunner.java ================================================ package org.nanohttpd.protocols.http.threading; /* * #%L * NanoHttpd-Core * %% * Copyright (C) 2012 - 2016 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.nanohttpd.protocols.http.ClientHandler; /** * Pluggable strategy for asynchronously executing requests. */ public interface IAsyncRunner { void closeAll(); void closed(ClientHandler clientHandler); void exec(ClientHandler code); } ================================================ FILE: core/src/main/java/org/nanohttpd/util/IFactory.java ================================================ package org.nanohttpd.util; /* * #%L * NanoHttpd-Core * %% * Copyright (C) 2012 - 2016 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% */ /** * Represents a simple factory * * @author LordFokas * @param * The Type of object to create */ public interface IFactory { T create(); } ================================================ FILE: core/src/main/java/org/nanohttpd/util/IFactoryThrowing.java ================================================ package org.nanohttpd.util; /* * #%L * NanoHttpd-Core * %% * Copyright (C) 2012 - 2016 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% */ /** * Represents a factory that can throw an exception instead of actually creating * an object * * @author LordFokas * @param * The Type of object to create * @param * The base Type of exceptions that can be thrown */ public interface IFactoryThrowing { T create() throws E; } ================================================ FILE: core/src/main/java/org/nanohttpd/util/IHandler.java ================================================ package org.nanohttpd.util; /* * #%L * NanoHttpd-Core * %% * Copyright (C) 2012 - 2016 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% */ /** * Defines a generic handler that returns an object of type O when given an * object of type I. * * @author LordFokas * @param * The input type. * @param * The output type. */ public interface IHandler { public O handle(I input); } ================================================ FILE: core/src/main/java/org/nanohttpd/util/ServerRunner.java ================================================ package org.nanohttpd.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 org.nanohttpd.protocols.http.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 void run(Class serverClass) { try { executeInstance(serverClass.newInstance()); } catch (Exception e) { ServerRunner.LOG.log(Level.SEVERE, "Could not create server", e); } } } ================================================ FILE: core/src/main/resources/META-INF/nanohttpd/default-mimetypes.properties ================================================ #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 ================================================ FILE: core/src/main/resources/META-INF/nanohttpd/mimetypes.properties ================================================ #mime types for nanohttpd, use a file like this for user defined mimetypes ================================================ FILE: core/src/site/site.xml ================================================ org.apache.maven.skins maven-fluido-skin 1.3.0 ../images/nanohttpd_logo.png ../images/nanohttpd_logo_text.png false true Nanohttpd/nanohttpd right black ================================================ FILE: core/src/test/java/org/nanohttpd/junit/protocols/http/BadRequestTest.java ================================================ package org.nanohttpd.junit.protocols.http; /* * #%L * NanoHttpd-Core * %% * Copyright (C) 2012 - 2016 nanohttpd * %% * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * 3. Neither the name of the nanohttpd nor the names of its contributors * may be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. * #L% */ import java.io.ByteArrayOutputStream; import java.io.IOException; import org.junit.Test; public class BadRequestTest extends HttpServerTest { @Test public void testEmptyRequest() throws IOException { ByteArrayOutputStream outputStream = invokeServer("\n\n"); String[] expected = new String[]{ "HTTP/1.1 400 Bad Request" }; assertResponse(outputStream, expected); } @Test public void testInvalidMethod() throws IOException { ByteArrayOutputStream outputStream = invokeServer("GETT http://example.com"); String[] expected = new String[]{ "HTTP/1.1 400 Bad Request" }; assertResponse(outputStream, expected); } @Test public void testMissingURI() throws IOException { ByteArrayOutputStream outputStream = invokeServer("GET"); String[] expected = new String[]{ "HTTP/1.1 400 Bad Request" }; assertResponse(outputStream, expected); } } ================================================ FILE: core/src/test/java/org/nanohttpd/junit/protocols/http/CookieHandlerTest.java ================================================ package org.nanohttpd.junit.protocols.http; /* * #%L * NanoHttpd-Core * %% * Copyright (C) 2012 - 2016 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.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashSet; import java.util.Locale; import java.util.Set; import java.util.TimeZone; import org.junit.Test; import org.nanohttpd.protocols.http.HTTPSession; import org.nanohttpd.protocols.http.content.CookieHandler; import org.nanohttpd.protocols.http.response.Response; public class CookieHandlerTest extends HttpServerTest { @Test public void testCookieHeaderCorrectlyParsed() throws IOException { StringBuilder requestBuilder = new StringBuilder(); requestBuilder.append("GET " + HttpServerTest.URI + " HTTP/1.1").append(System.getProperty("line.separator")).append("Cookie: theme=light; sessionToken=abc123"); ByteArrayInputStream inputStream = new ByteArrayInputStream(requestBuilder.toString().getBytes()); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); HTTPSession session = this.testServer.createSession(this.tempFileManager, inputStream, outputStream); session.execute(); Set allCookies = new HashSet(); CookieHandler cookieHandler = session.getCookies(); for (String cookie : cookieHandler) { allCookies.add(cookie); } assertTrue("cookie specified in header not correctly parsed", allCookies.contains("theme")); assertTrue("cookie specified in header not correctly parsed", allCookies.contains("sessionToken")); assertEquals("cookie value not correctly parsed", "light", cookieHandler.read("theme")); assertEquals("cookie value not correctly parsed", "abc123", cookieHandler.read("sessionToken")); } @Test public void testCookieHeaderWithSpecialCharactersCorrectlyParsed() throws IOException { StringBuilder requestBuilder = new StringBuilder(); // not including ; = and , requestBuilder.append("GET " + HttpServerTest.URI + " HTTP/1.1").append(System.getProperty("line.separator")) .append("Cookie: theme=light; sessionToken=abc123!@#$%^&*()-_+{}[]\\|:\"'<>.?/"); ByteArrayInputStream inputStream = new ByteArrayInputStream(requestBuilder.toString().getBytes()); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); HTTPSession session = this.testServer.createSession(this.tempFileManager, inputStream, outputStream); session.execute(); Set allCookies = new HashSet(); CookieHandler cookieHandler = session.getCookies(); for (String cookie : cookieHandler) { allCookies.add(cookie); } assertTrue("cookie specified in header not correctly parsed", allCookies.contains("theme")); assertTrue("cookie specified in header not correctly parsed", allCookies.contains("sessionToken")); assertEquals("cookie value not correctly parsed", "light", cookieHandler.read("theme")); assertEquals("cookie value not correctly parsed", "abc123!@#$%^&*()-_+{}[]\\|:\"'<>.?/", cookieHandler.read("sessionToken")); } @Test public void testUnloadQueue() throws IOException { StringBuilder requestBuilder = new StringBuilder(); requestBuilder.append("GET " + HttpServerTest.URI + " HTTP/1.1").append(System.getProperty("line.separator")).append("Cookie: theme=light; sessionToken=abc123"); ByteArrayInputStream inputStream = new ByteArrayInputStream(requestBuilder.toString().getBytes()); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); HTTPSession session = this.testServer.createSession(this.tempFileManager, inputStream, outputStream); session.execute(); CookieHandler cookieHandler = session.getCookies(); Response response = Response.newFixedLengthResponse(""); cookieHandler.set("name", "value", 30); cookieHandler.unloadQueue(response); String setCookieHeader = response.getCookieHeaders().get(0); assertTrue("unloadQueue did not set the cookies correctly", setCookieHeader.startsWith("name=value; expires=")); } @Test public void testDelete() throws IOException, ParseException { StringBuilder requestBuilder = new StringBuilder(); requestBuilder.append("GET " + HttpServerTest.URI + " HTTP/1.1").append(System.getProperty("line.separator")).append("Cookie: theme=light; sessionToken=abc123"); ByteArrayInputStream inputStream = new ByteArrayInputStream(requestBuilder.toString().getBytes()); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); HTTPSession session = this.testServer.createSession(this.tempFileManager, inputStream, outputStream); session.execute(); CookieHandler cookieHandler = session.getCookies(); Response response = Response.newFixedLengthResponse(""); cookieHandler.delete("name"); cookieHandler.unloadQueue(response); String setCookieHeader = response.getCookieHeaders().get(0); SimpleDateFormat dateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US); dateFormat.setTimeZone(TimeZone.getTimeZone("GMT")); String dateString = setCookieHeader.split(";")[1].split("=")[1].trim(); Date date = dateFormat.parse(dateString); assertTrue("Deleted cookie's expiry time should be a time in the past", date.compareTo(new Date()) < 0); } } ================================================ FILE: core/src/test/java/org/nanohttpd/junit/protocols/http/CookieTest.java ================================================ package org.nanohttpd.junit.protocols.http; /* * #%L * NanoHttpd-Core * %% * Copyright (C) 2012 - 2016 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.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import java.util.Random; import org.junit.Test; import org.nanohttpd.protocols.http.content.Cookie; public class CookieTest { @Test public void testGetHTTPTime() { Random random = new Random(); int randomExpirationTime = random.nextInt(100); assertNotNull("getHTTPTime should return a non-null value for " + randomExpirationTime + " days", Cookie.getHTTPTime(randomExpirationTime)); } @Test public void testCookieWithNoExplicitExpirationTime() { Cookie cookie = new Cookie("CookieKey", "CookieValue"); assertTrue("Cookie header should contain cookie key", cookie.getHTTPHeader().contains("CookieKey")); assertTrue("Cookie header should contain cookie value", cookie.getHTTPHeader().contains("CookieValue")); } @Test public void testCookieWithExplicitExpirationTime() { Cookie cookie = new Cookie("CookieKey", "CookieValue", 40); assertFalse("The default 30 days expires string should not be avaialbe in the cookie header" + " because the expiry has been specified as 40 days", cookie .getHTTPHeader().contains(Cookie.getHTTPTime(30))); assertTrue("Cookie header should contain cookie key", cookie.getHTTPHeader().contains("CookieKey")); assertTrue("Cookie header should contain cookie value", cookie.getHTTPHeader().contains("CookieValue")); } @Test public void testCookieWithExpiresString() { Random random = new Random(); int randomExpirationTime = random.nextInt(100); String expiresString = Cookie.getHTTPTime(randomExpirationTime); Cookie cookie = new Cookie("CookieKey", "CookieValue", expiresString); assertTrue("Cookie should contain the expirs string passed in the constructor", cookie.getHTTPHeader().contains(expiresString)); assertTrue("Cookie header should contain cookie key", cookie.getHTTPHeader().contains("CookieKey")); assertTrue("Cookie header should contain cookie value", cookie.getHTTPHeader().contains("CookieValue")); } } ================================================ FILE: core/src/test/java/org/nanohttpd/junit/protocols/http/HttpChunkedResponseTest.java ================================================ package org.nanohttpd.junit.protocols.http; /* * #%L * NanoHttpd-Core * %% * Copyright (C) 2012 - 2016 nanohttpd * %% * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * 3. Neither the name of the nanohttpd nor the names of its contributors * may be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. * #L% */ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.PipedInputStream; import org.nanohttpd.protocols.http.response.Response; import org.nanohttpd.protocols.http.response.Status; public class HttpChunkedResponseTest extends HttpServerTest { private static class ChunkedInputStream extends PipedInputStream { int chunk = 0; String[] chunks; private ChunkedInputStream(String[] chunks) { this.chunks = chunks; } @Override public synchronized int read(byte[] buffer, int off, int len) throws IOException { // Too implementation-linked, but... for (int i = 0; i < this.chunks[this.chunk].length(); ++i) { buffer[i] = (byte) this.chunks[this.chunk].charAt(i); } 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 = Response.newChunkedResponse(Status.OK, "what/ever", pipedInputStream); this.testServer.response.setChunkedTransfer(true); ByteArrayOutputStream byteArrayOutputStream = invokeServer("GET / HTTP/1.1"); assertResponse(byteArrayOutputStream, expected); } } ================================================ FILE: core/src/test/java/org/nanohttpd/junit/protocols/http/HttpDeleteRequestTest.java ================================================ package org.nanohttpd.junit.protocols.http; /* * #%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 org.junit.Test; import org.nanohttpd.protocols.http.NanoHTTPD; import org.nanohttpd.protocols.http.response.Response; import org.nanohttpd.protocols.http.response.Status; public class HttpDeleteRequestTest extends HttpServerTest { @Test public void testDeleteRequestThatDoesntSendBackResponseBody_EmptyString() throws Exception { this.testServer.response = Response.newFixedLengthResponse(Status.NO_CONTENT, NanoHTTPD.MIME_HTML, ""); 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", "" }; assertResponse(outputStream, expected); } @Test public void testDeleteRequestThatDoesntSendBackResponseBody_NullInputStream() throws Exception { this.testServer.response = Response.newChunkedResponse(Status.NO_CONTENT, NanoHTTPD.MIME_HTML, (InputStream) null); 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", "" }; assertResponse(outputStream, expected); } @Test public void testDeleteRequestThatDoesntSendBackResponseBody_NullString() throws Exception { this.testServer.response = Response.newFixedLengthResponse(Status.NO_CONTENT, NanoHTTPD.MIME_HTML, (String) null); 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", "" }; assertResponse(outputStream, expected); } @Test public void testDeleteRequestThatSendsBackResponseBody_Accepted() throws Exception { this.testServer.response = Response.newFixedLengthResponse(Status.ACCEPTED, "application/xml", ""); 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", "", "" }; assertResponse(outputStream, expected); } @Test public void testDeleteRequestThatSendsBackResponseBody_Success() throws Exception { this.testServer.response = Response.newFixedLengthResponse(Status.OK, "application/xml", ""); 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", "", "" }; assertResponse(outputStream, expected); } } ================================================ FILE: core/src/test/java/org/nanohttpd/junit/protocols/http/HttpGetRequestTest.java ================================================ package org.nanohttpd.junit.protocols.http; /* * #%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.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import java.io.ByteArrayOutputStream; import java.util.List; import org.junit.Test; import org.nanohttpd.protocols.http.request.Method; import org.nanohttpd.protocols.http.response.Response; public class HttpGetRequestTest extends HttpServerTest { @Test 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 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)); } @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 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 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 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 testEmptyHeadersSuppliedToServeMethodFromSimpleWorkingGetRequest() { invokeServer("GET " + HttpServerTest.URI + " HTTP/1.1"); assertNotNull(this.testServer.parms); assertNotNull(this.testServer.parameters); assertNotNull(this.testServer.header); assertNotNull(this.testServer.files); assertNotNull(this.testServer.uri); } @Test 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 " + HttpServerTest.URI + "?foo=bar&baz=zot HTTP/1.1"); assertEquals("bar", this.testServer.parms.get("foo")); assertEquals("zot", this.testServer.parms.get("baz")); assertEquals("bar", this.testServer.parameters.get("foo").get(0)); assertEquals("zot", this.testServer.parameters.get("baz").get(0)); } @Test public void testMultipleGetParametersWithMissingValue() { invokeServer("GET " + HttpServerTest.URI + "?foo=&baz=zot HTTP/1.1"); assertEquals("", this.testServer.parms.get("foo")); assertEquals("zot", this.testServer.parms.get("baz")); assertEquals("", this.testServer.parameters.get("foo").get(0)); assertEquals("zot", this.testServer.parameters.get("baz").get(0)); } @Test public void testMultipleGetParametersWithMissingValueAndRequestHeaders() { 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("", this.testServer.parameters.get("foo").get(0)); assertEquals("zot", this.testServer.parameters.get("baz").get(0)); assertEquals("text/html", this.testServer.header.get("accept")); } @Test 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 testOutputOfServeSentBackToClient() throws Exception { String responseBody = "Success!"; this.testServer.response = Response.newFixedLengthResponse(responseBody); 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: 8", "", responseBody }; assertResponse(outputStream, expected); } @Test public void testSingleGetParameter() { invokeServer("GET " + HttpServerTest.URI + "?foo=bar HTTP/1.1"); assertEquals("bar", this.testServer.parms.get("foo")); assertEquals("bar", this.testServer.parameters.get("foo").get(0)); } @Test public void testMultipleValueGetParameter() { invokeServer("GET " + HttpServerTest.URI + "?foo=bar&foo=baz HTTP/1.1"); assertEquals("bar", this.testServer.parms.get("foo")); assertEquals(2, this.testServer.parameters.get("foo").size()); assertEquals("bar", this.testServer.parameters.get("foo").get(0)); assertEquals("baz", this.testServer.parameters.get("foo").get(1)); } @Test public void testSingleGetParameterWithNoValue() { invokeServer("GET " + HttpServerTest.URI + "?foo HTTP/1.1"); assertEquals("", this.testServer.parms.get("foo")); assertEquals("", this.testServer.parameters.get("foo").get(0)); } @Test 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(Method.GET, this.testServer.method); assertEquals(HttpServerTest.URI, this.testServer.uri); } @Test public void testGetQueryParameterContainsSpace() { invokeServer("GET " + HttpServerTest.URI + "?foo=bar%20baz HTTP/1.1"); assertEquals("Parameter count in URL and decodedParameters should match.", 1, this.testServer.decodedParamters.size()); assertEquals("The query parameter value with space decoding incorrect", "bar baz", this.testServer.decodedParamters.get("foo").get(0)); } @Test public void testGetQueryParameterContainsQuestionMark() { invokeServer("GET " + HttpServerTest.URI + "?foo=bar%3F HTTP/1.1"); assertEquals("Parameter count in URL and decodedParameters should match.", 1, this.testServer.decodedParamters.size()); assertEquals("The query parameter value with question mark decoding incorrect", "bar?", this.testServer.decodedParamters.get("foo").get(0)); } @Test public void testGetQueryParameterContainsAmpersand() { invokeServer("GET " + HttpServerTest.URI + "?foo=bar%26 HTTP/1.1"); assertEquals("Parameter count in URL and decodedParameters should match.", 1, this.testServer.decodedParamters.size()); assertEquals("The query parameter value with ampersand decoding incorrect", "bar&", this.testServer.decodedParamters.get("foo").get(0)); } @Test public void testGetQueryParameterContainsSpecialCharactersSingleFieldRepeated() { invokeServer("GET " + HttpServerTest.URI + "?foo=bar%20baz&foo=bar%3F&foo=bar%26 HTTP/1.1"); assertEquals("Parameter count in URL and decodedParameters should match.", 1, this.testServer.decodedParamters.size()); String[] parametersAsArray = this.testServer.decodedParamters.get("foo").toArray(new String[0]); String[] expected = new String[]{ "bar baz", "bar?", "bar&" }; assertArrayEquals("Repeated parameter not decoded correctly", expected, parametersAsArray); } } ================================================ FILE: core/src/test/java/org/nanohttpd/junit/protocols/http/HttpHeadRequestTest.java ================================================ package org.nanohttpd.junit.protocols.http; /* * #%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 org.junit.Test; import org.nanohttpd.protocols.http.request.Method; import org.nanohttpd.protocols.http.response.Response; public class HttpHeadRequestTest extends HttpServerTest { @Override public void setUp() throws Exception { super.setUp(); String responseBody = "Success!"; this.testServer.response = Response.newFixedLengthResponse(responseBody); } @Test 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 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 testDecodingParametersFromParameterMap() { invokeServer("HEAD " + HttpServerTest.URI + "?foo=bar&foo=baz&zot&zim= HTTP/1.1"); assertEquals(this.testServer.decodedParamters, this.testServer.decodedParamtersFromParameter); } // -------------------------------------------------------------------------------------------------------- // // @Test 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 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 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 testEmptyHeadersSuppliedToServeMethodFromSimpleWorkingGetRequest() { invokeServer("HEAD " + HttpServerTest.URI + " HTTP/1.1"); assertNotNull(this.testServer.parms); assertNotNull(this.testServer.parameters); assertNotNull(this.testServer.header); assertNotNull(this.testServer.files); assertNotNull(this.testServer.uri); } @Test 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 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")); assertEquals("bar", this.testServer.parameters.get("foo").get(0)); assertEquals("zot", this.testServer.parameters.get("baz").get(0)); } @Test 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")); assertEquals("", this.testServer.parameters.get("foo").get(0)); assertEquals("zot", this.testServer.parameters.get("baz").get(0)); } @Test 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("", this.testServer.parameters.get("foo").get(0)); assertEquals("zot", this.testServer.parameters.get("baz").get(0)); assertEquals("text/html", this.testServer.header.get("accept")); } @Test 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 testSingleGetParameter() { invokeServer("HEAD " + HttpServerTest.URI + "?foo=bar HTTP/1.1"); assertEquals("bar", this.testServer.parms.get("foo")); assertEquals("bar", this.testServer.parameters.get("foo").get(0)); } @Test public void testMultipleValueGetParameter() { invokeServer("HEAD " + HttpServerTest.URI + "?foo=bar&foo=baz HTTP/1.1"); assertEquals("bar", this.testServer.parms.get("foo")); assertEquals(2, this.testServer.parameters.get("foo").size()); assertEquals("bar", this.testServer.parameters.get("foo").get(0)); assertEquals("baz", this.testServer.parameters.get("foo").get(1)); } @Test public void testSingleGetParameterWithNoValue() { invokeServer("HEAD " + HttpServerTest.URI + "?foo HTTP/1.1"); assertEquals("", this.testServer.parms.get("foo")); assertEquals("", this.testServer.parameters.get("foo").get(0)); } @Test 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(Method.HEAD, this.testServer.method); assertEquals(HttpServerTest.URI, this.testServer.uri); } } ================================================ FILE: core/src/test/java/org/nanohttpd/junit/protocols/http/HttpKeepAliveTest.java ================================================ package org.nanohttpd.junit.protocols.http; /* * #%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; import java.io.PipedInputStream; import java.io.PipedOutputStream; import org.junit.Test; import org.nanohttpd.protocols.http.HTTPSession; import org.nanohttpd.protocols.http.tempfiles.DefaultTempFileManager; public class HttpKeepAliveTest extends HttpServerTest { private Throwable error = null; @Test public void testManyGetRequests() throws Exception { 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", "" }; testManyRequests(request, expected); } @Test public void testManyPutRequests() throws Exception { String data = "BodyData 1\nLine 2"; 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", "" }; testManyRequests(request, expected); } /** * 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(); DefaultTempFileManager tempFileManager = new DefaultTempFileManager(); try { 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(); // 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) { HttpKeepAliveTest.this.error = t; } } }; Thread t = new Thread(null, r, "Request Thread", 1 << 17); t.start(); t.join(); if (this.error != null) { fail("" + this.error); this.error.printStackTrace(); } } } ================================================ FILE: core/src/test/java/org/nanohttpd/junit/protocols/http/HttpParsingTest.java ================================================ package org.nanohttpd.junit.protocols.http; /* * #%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, this.testServer.decodePercent(input)); } } @Test public void testPlusInQueryParams() throws Exception { assertEquals("foo bar", this.testServer.decodePercent("foo+bar")); } } ================================================ FILE: core/src/test/java/org/nanohttpd/junit/protocols/http/HttpPostRequestTest.java ================================================ package org.nanohttpd.junit.protocols.http; /* * #%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.BufferedReader; import java.io.FileReader; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.UUID; 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"; /** * 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 + 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(1, this.testServer.parms.size()); assertEquals(1, this.testServer.parameters.size()); BufferedReader reader = new BufferedReader(new FileReader(this.testServer.files.get(HttpPostRequestTest.FIELD))); List lines = readLinesFromFile(reader); assertLinesOfText(new String[]{ fileContent }, lines); } @Test public void testPostWithMultipartFormUploadFilenameHasSpaces() throws Exception { String fileNameWithSpace = "Grand Canyon.txt"; String fileContent = HttpPostRequestTest.VALUE; String input = preparePostWithMultipartForm(fileNameWithSpace, fileContent); invokeServer(input); String fileNameAfter = new ArrayList(this.testServer.parms.values()).get(0); assertEquals(fileNameWithSpace, fileNameAfter); fileNameAfter = new ArrayList(this.testServer.parameters.values().iterator().next()).get(0); assertEquals(fileNameWithSpace, fileNameAfter); } @Test public void testPostWithMultipleMultipartFormFields() throws Exception { String divider = UUID.randomUUID().toString(); 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 + HttpPostRequestTest.CONTENT_LENGTH.length(); String input = header + HttpPostRequestTest.CONTENT_LENGTH + (contentLength + 4) + "\r\n\r\n" + content; invokeServer(input); assertEquals(2, this.testServer.parms.size()); assertEquals(HttpPostRequestTest.VALUE, this.testServer.parms.get(HttpPostRequestTest.FIELD)); assertEquals(HttpPostRequestTest.VALUE2, this.testServer.parms.get(HttpPostRequestTest.FIELD2)); assertEquals(2, this.testServer.parameters.size()); assertEquals(HttpPostRequestTest.VALUE, this.testServer.parameters.get(HttpPostRequestTest.FIELD).get(0)); assertEquals(HttpPostRequestTest.VALUE2, this.testServer.parameters.get(HttpPostRequestTest.FIELD2).get(0)); } @Test public void testPostWithMultipleMultipartFormFieldsWhereContentTypeWasSeparatedByComma() 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" + "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 + HttpPostRequestTest.CONTENT_LENGTH.length(); String input = header + HttpPostRequestTest.CONTENT_LENGTH + (contentLength + 4) + "\r\n\r\n" + content; invokeServer(input); assertEquals(2, this.testServer.parms.size()); assertEquals(HttpPostRequestTest.VALUE, this.testServer.parms.get(HttpPostRequestTest.FIELD)); assertEquals(HttpPostRequestTest.VALUE2, this.testServer.parms.get(HttpPostRequestTest.FIELD2)); assertEquals(2, this.testServer.parameters.size()); assertEquals(HttpPostRequestTest.VALUE, this.testServer.parameters.get(HttpPostRequestTest.FIELD).get(0)); assertEquals(HttpPostRequestTest.VALUE2, this.testServer.parameters.get(HttpPostRequestTest.FIELD2).get(0)); } @Test 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, this.testServer.parms.size()); assertEquals(HttpPostRequestTest.VALUE, this.testServer.parms.get(HttpPostRequestTest.FIELD)); assertEquals(1, this.testServer.parameters.size()); assertEquals(HttpPostRequestTest.VALUE, this.testServer.parameters.get(HttpPostRequestTest.FIELD).get(0)); } @Test 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 + 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(0, this.testServer.parameters.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)); } @Test public void testPostWithMultipartFormFieldsAndFile() throws IOException { String fileName = "GrandCanyon.txt"; String fileContent = HttpPostRequestTest.VALUE; String divider = UUID.randomUUID().toString(); 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 + "\"; filename=\"" + fileName + "\"\r\n" + "Content-Type: image/jpeg\r\n" + "\r\n" + fileContent + "\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 + HttpPostRequestTest.CONTENT_LENGTH.length(); String input = header + HttpPostRequestTest.CONTENT_LENGTH + (contentLength + 4) + "\r\n\r\n" + content; invokeServer(input); assertEquals("Parms count did not match.", 2, this.testServer.parms.size()); assertEquals("Parameters count did not match.", 2, this.testServer.parameters.size()); assertEquals("Param value did not match", HttpPostRequestTest.VALUE2, this.testServer.parms.get(HttpPostRequestTest.FIELD2)); assertEquals("Parameter value did not match", HttpPostRequestTest.VALUE2, this.testServer.parameters.get(HttpPostRequestTest.FIELD2).get(0)); BufferedReader reader = new BufferedReader(new FileReader(this.testServer.files.get(HttpPostRequestTest.FIELD))); List lines = readLinesFromFile(reader); assertLinesOfText(new String[]{ fileContent }, lines); } @Test public void testPostWithMultipartFormUploadMultipleFiles() throws IOException { String fileName = "GrandCanyon.txt"; String fileContent = HttpPostRequestTest.VALUE; String file2Name = "AnotherPhoto.txt"; String file2Content = HttpPostRequestTest.VALUE2; String divider = UUID.randomUUID().toString(); 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 + "\"; filename=\"" + fileName + "\"\r\n" // + "Content-Type: image/jpeg\r\n" + "\r\n" // + fileContent + "\r\n" // + "--" + divider + "\r\n" // + "Content-Disposition: form-data; name=\"" + HttpPostRequestTest.FIELD2 + "\"; filename=\"" + file2Name + "\"\r\n" // + "Content-Type: image/jpeg\r\n" + "\r\n" // + file2Content + "\r\n" // + "\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("Parm count did not match.", 2, this.testServer.parms.size()); assertEquals("Parameter count did not match.", 2, this.testServer.parameters.size()); BufferedReader reader = new BufferedReader(new FileReader(this.testServer.files.get(HttpPostRequestTest.FIELD))); List lines = readLinesFromFile(reader); assertLinesOfText(new String[]{ fileContent }, lines); String fileName2 = this.testServer.files.get(HttpPostRequestTest.FIELD2); int testNumber = 0; while (fileName2 == null && testNumber < 5) { testNumber++; fileName2 = this.testServer.files.get(HttpPostRequestTest.FIELD2 + testNumber); } reader = new BufferedReader(new FileReader(fileName2)); lines = readLinesFromFile(reader); assertLinesOfText(new String[]{ file2Content }, lines); } @Test public void testPostWithMultipartFormUploadFileWithMultilineContent() throws Exception { String filename = "GrandCanyon.txt"; String lineSeparator = "\n"; String fileContent = HttpPostRequestTest.VALUE + lineSeparator + HttpPostRequestTest.VALUE + lineSeparator + HttpPostRequestTest.VALUE; String input = preparePostWithMultipartForm(filename, fileContent); invokeServer(input); assertEquals("Parm count did not match.", 1, this.testServer.parms.size()); assertEquals("Parameter count did not match.", 1, this.testServer.parameters.size()); BufferedReader reader = new BufferedReader(new FileReader(this.testServer.files.get(HttpPostRequestTest.FIELD))); List lines = readLinesFromFile(reader); assertLinesOfText(fileContent.split(lineSeparator), lines); } } ================================================ FILE: core/src/test/java/org/nanohttpd/junit/protocols/http/HttpPutRequestTest.java ================================================ package org.nanohttpd.junit.protocols.http; /* * #%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 org.junit.Test; public class HttpPutRequestTest extends HttpServerTest { @Test public void testPutRequestSendsContent() throws Exception { 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", "" }; assertResponse(outputStream, expectedOutput); assertTrue(this.testServer.files.containsKey("content")); BufferedReader reader = null; try { String[] expectedInputToServeMethodViaFile = { "BodyData 1", "Line 2" }; reader = new BufferedReader(new FileReader(this.testServer.files.get("content"))); List lines = readLinesFromFile(reader); assertLinesOfText(expectedInputToServeMethodViaFile, lines); } finally { if (reader != null) { reader.close(); } } } } ================================================ FILE: core/src/test/java/org/nanohttpd/junit/protocols/http/HttpSSLServerTest.java ================================================ package org.nanohttpd.junit.protocols.http; /* * #%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 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 org.nanohttpd.protocols.http.NanoHTTPD; 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()); } /** * using http to connect to https. * * @throws ClientProtocolException * @throws IOException */ @Test(expected = ClientProtocolException.class) public void testHttpOnSSLConnection() throws ClientProtocolException, IOException { DefaultHttpClient httpclient = new DefaultHttpClient(); HttpTrace httphead = new HttpTrace("http://localhost:9043/index.html"); httpclient.execute(httphead); } @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(); } } ================================================ FILE: core/src/test/java/org/nanohttpd/junit/protocols/http/HttpServerTest.java ================================================ package org.nanohttpd.junit.protocols.http; import static org.junit.Assert.assertEquals; /* * #%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.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; 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 org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.mime.MultipartEntity; import org.apache.http.entity.mime.content.FileBody; import org.apache.http.entity.mime.content.StringBody; import org.apache.http.impl.client.DefaultHttpClient; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.nanohttpd.protocols.http.HTTPSession; import org.nanohttpd.protocols.http.IHTTPSession; import org.nanohttpd.protocols.http.NanoHTTPD; import org.nanohttpd.protocols.http.request.Method; import org.nanohttpd.protocols.http.response.Response; import org.nanohttpd.protocols.http.tempfiles.DefaultTempFileManager; import org.nanohttpd.protocols.http.tempfiles.ITempFileManager; /** * @author Paul S. Hawke (paul.hawke@gmail.com) On: 3/10/13 at 8:32 PM */ public class HttpServerTest { public static class TestServer extends NanoHTTPD { public Response response = Response.newFixedLengthResponse(""); public String uri; public Method method; public Map header; public Map parms; public Map> parameters; public Map files; public Map> decodedParamters; public Map> decodedParamtersFromParameter; public String queryParameterString; public TestServer() { super(8192); } public TestServer(int port) { super(port); } public HTTPSession createSession(ITempFileManager tempFileManager, InputStream inputStream, OutputStream outputStream) { return new HTTPSession(this, tempFileManager, inputStream, outputStream); } public HTTPSession createSession(ITempFileManager tempFileManager, InputStream inputStream, OutputStream outputStream, InetAddress inetAddress) { return new HTTPSession(this, tempFileManager, inputStream, outputStream, inetAddress); } @Override public Response serve(IHTTPSession session) { this.uri = session.getUri(); this.method = session.getMethod(); this.header = session.getHeaders(); this.files = new HashMap(); try { session.parseBody(this.files); } catch (Exception e) { e.printStackTrace(); } this.parms = session.getParms(); this.parameters = session.getParameters(); this.queryParameterString = session.getQueryParameterString(); this.decodedParamtersFromParameter = decodeParameters(this.queryParameterString); this.decodedParamters = decodeParameters(session.getQueryParameterString()); return this.response; } } public static class TestTempFileManager extends DefaultTempFileManager { public void _clear() { super.clear(); } @Override public void clear() { // ignore } } 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 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 { List lines = getOutputLines(outputStream); assertLinesOfText(expected, lines); } protected List 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(); HTTPSession session = this.testServer.createSession(this.tempFileManager, inputStream, outputStream); try { session.execute(); } catch (IOException e) { fail("" + e); e.printStackTrace(); } return outputStream; } protected List readLinesFromFile(BufferedReader reader) throws IOException { List lines = new ArrayList(); String line = ""; while (line != null) { line = reader.readLine(); if (line != null) { lines.add(line.trim()); } } return lines; } @Before public void setUp() throws Exception { this.testServer = new TestServer(); this.tempFileManager = new TestTempFileManager(); } @After public void tearDown() { this.tempFileManager._clear(); } @Test public void testServerExists() { assertNotNull(this.testServer); } @Test public void testMultipartFormData() throws IOException { final int testPort = 4589; NanoHTTPD server = null; try { server = new NanoHTTPD(testPort) { final Map files = new HashMap(); @Override public Response serve(IHTTPSession session) { StringBuilder responseMsg = new StringBuilder(); try { session.parseBody(this.files); for (String key : files.keySet()) { responseMsg.append(key); } } catch (Exception e) { responseMsg.append(e.getMessage()); } return Response.newFixedLengthResponse(responseMsg.toString()); } }; server.start(NanoHTTPD.SOCKET_READ_TIMEOUT, false); HttpClient httpclient = new DefaultHttpClient(); HttpPost httppost = new HttpPost("http://localhost:" + testPort); final String fileName = "file-upload-test.htm"; FileBody bin = new FileBody(new File(getClass().getClassLoader().getResource(fileName).getFile())); StringBody comment = new StringBody("Filename: " + fileName); MultipartEntity reqEntity = new MultipartEntity(); reqEntity.addPart("bin", bin); reqEntity.addPart("comment", comment); httppost.setEntity(reqEntity); HttpResponse response = httpclient.execute(httppost); HttpEntity entity = response.getEntity(); if (entity != null) { InputStream instream = entity.getContent(); BufferedReader reader = new BufferedReader(new InputStreamReader(instream, "UTF-8")); String line = reader.readLine(); assertNotNull(line, "Invalid server reponse"); assertEquals("Server failed multi-part data parse" + line, "bincomment", line); reader.close(); instream.close(); } } finally { if (server != null) { server.stop(); } } } @Test public void testTempFileInterface() throws IOException { final int testPort = 4589; NanoHTTPD server = new NanoHTTPD(testPort) { final Map files = new HashMap(); @Override public Response serve(IHTTPSession session) { String responseMsg = "pass"; try { session.parseBody(this.files); for (String key : files.keySet()) { if (!(new File(files.get(key))).exists()) { responseMsg = "fail"; } } } catch (Exception e) { responseMsg = e.getMessage(); } return Response.newFixedLengthResponse(responseMsg.toString()); } }; server.start(NanoHTTPD.SOCKET_READ_TIMEOUT, false); HttpClient httpclient = new DefaultHttpClient(); HttpPost httppost = new HttpPost("http://localhost:" + testPort); final String fileName = "file-upload-test.htm"; FileBody bin = new FileBody(new File(getClass().getClassLoader().getResource(fileName).getFile())); StringBody comment = new StringBody("Filename: " + fileName); MultipartEntity reqEntity = new MultipartEntity(); reqEntity.addPart("bin", bin); reqEntity.addPart("comment", comment); httppost.setEntity(reqEntity); HttpResponse response = httpclient.execute(httppost); HttpEntity entity = response.getEntity(); if (entity != null) { InputStream instream = entity.getContent(); BufferedReader reader = new BufferedReader(new InputStreamReader(instream, "UTF-8")); String line = reader.readLine(); assertNotNull(line, "Invalid server reponse"); assertEquals("Server file check failed: " + line, "pass", line); reader.close(); instream.close(); } else { fail("No server response"); } server.stop(); } } ================================================ FILE: core/src/test/java/org/nanohttpd/junit/protocols/http/HttpSessionHeadersTest.java ================================================ package org.nanohttpd.junit.protocols.http; /* * #%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.assertNotNull; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.net.InetAddress; import org.junit.Test; import org.nanohttpd.protocols.http.HTTPSession; 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(); @Test public void testHeadersRemoteIp() throws Exception { ByteArrayInputStream inputStream = new ByteArrayInputStream(HttpSessionHeadersTest.DUMMY_REQUEST_CONTENT.getBytes()); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); String[] ipAddresses = { "127.0.0.1", "8.8.8.8", }; for (String ipAddress : ipAddresses) { InetAddress inetAddress = InetAddress.getByName(ipAddress); HTTPSession session = this.testServer.createSession(HttpSessionHeadersTest.TEST_TEMP_FILE_MANAGER, inputStream, outputStream, inetAddress); assertEquals(ipAddress, session.getRemoteIpAddress()); } } } ================================================ FILE: core/src/test/java/org/nanohttpd/junit/protocols/http/HttpSessionTest.java ================================================ package org.nanohttpd.junit.protocols.http; /* * #%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 java.net.UnknownHostException; import org.junit.Test; import org.nanohttpd.protocols.http.HTTPSession; public class HttpSessionTest extends HttpServerTest { private static final String DUMMY_REQUEST_CONTENT = "dummy request content"; private static final TestTempFileManager TEST_TEMP_FILE_MANAGER = new TestTempFileManager(); @Test public void testSessionRemoteIPAddress() throws UnknownHostException { ByteArrayInputStream inputStream = new ByteArrayInputStream(HttpSessionTest.DUMMY_REQUEST_CONTENT.getBytes()); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); InetAddress inetAddress = InetAddress.getByName("127.0.0.1"); HTTPSession session = this.testServer.createSession(HttpSessionTest.TEST_TEMP_FILE_MANAGER, inputStream, outputStream, inetAddress); assertEquals("127.0.0.1", session.getRemoteIpAddress()); } } ================================================ FILE: core/src/test/java/org/nanohttpd/junit/protocols/http/InvalidRequestTest.java ================================================ package org.nanohttpd.junit.protocols.http; /* * #%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); assertNotNull(this.testServer.parameters); 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); assertNotNull(this.testServer.parameters); 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); assertNotNull(this.testServer.parameters); 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); assertNotNull(this.testServer.parameters); assertTrue(this.testServer.header.size() > 0); assertNotNull(this.testServer.files); assertNotNull(this.testServer.uri); } } ================================================ FILE: core/src/test/java/org/nanohttpd/junit/protocols/http/JavaIOTempDirExistTest.java ================================================ package org.nanohttpd.junit.protocols.http; /* * #%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 org.nanohttpd.protocols.http.tempfiles.DefaultTempFile; import org.nanohttpd.protocols.http.tempfiles.DefaultTempFileManager; /** * Created by Victor Nikiforov on 10/16/15. */ public class JavaIOTempDirExistTest { @Test public void testJavaIoTempDefault() throws Exception { String tmpdir = System.getProperty("java.io.tmpdir"); DefaultTempFileManager manager = new 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 DefaultTempFileManager(); Assert.assertEquals(true, newDir.exists()); newDir.delete(); } finally { System.setProperty("java.io.tmpdir", tmpdir); } } } ================================================ FILE: core/src/test/java/org/nanohttpd/junit/protocols/http/LoadKeyStoreTest.java ================================================ package org.nanohttpd.junit.protocols.http; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import java.io.IOException; import java.io.InputStream; import javax.net.ssl.SSLServerSocketFactory; /* * #%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.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.nanohttpd.protocols.http.NanoHTTPD; public class LoadKeyStoreTest { @Rule public ExpectedException thrown = ExpectedException.none(); @Test public void loadKeyStoreFromResources() throws Exception { String keyStorePath = "/keystore.jks"; InputStream resourceAsStream = this.getClass().getResourceAsStream(keyStorePath); assertNotNull(resourceAsStream); SSLServerSocketFactory sslServerSocketFactory = NanoHTTPD.makeSSLSocketFactory(keyStorePath, "password".toCharArray()); assertNotNull(sslServerSocketFactory); } @Test public void loadKeyStoreFromResourcesWrongPassword() throws Exception { String keyStorePath = "/keystore.jks"; InputStream resourceAsStream = this.getClass().getResourceAsStream(keyStorePath); assertNotNull(resourceAsStream); thrown.expect(IOException.class); NanoHTTPD.makeSSLSocketFactory(keyStorePath, "wrongpassword".toCharArray()); } @Test public void loadNonExistentKeyStoreFromResources() throws Exception { String nonExistentPath = "/nokeystorehere.jks"; InputStream resourceAsStream = this.getClass().getResourceAsStream(nonExistentPath); assertNull(resourceAsStream); thrown.expect(IOException.class); NanoHTTPD.makeSSLSocketFactory(nonExistentPath, "".toCharArray()); } } ================================================ FILE: core/src/test/java/org/nanohttpd/junit/protocols/http/MimeTest.java ================================================ package org.nanohttpd.junit.protocols.http; /* * #%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; import org.nanohttpd.protocols.http.NanoHTTPD; 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")); } } ================================================ FILE: core/src/test/java/org/nanohttpd/junit/protocols/http/SSLServerSocketFactoryTest.java ================================================ package org.nanohttpd.junit.protocols.http; 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.util.Arrays; import javax.net.ssl.SSLServerSocket; 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 org.nanohttpd.protocols.http.NanoHTTPD; import org.nanohttpd.protocols.http.sockets.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()); } @Test public void testCreatePassesTheProtocolsToServerSocket() throws IOException { // first find the supported protocols SecureServerSocketFactory secureServerSocketFactory = new SecureServerSocketFactory(NanoHTTPD.makeSSLSocketFactory("/keystore.jks", "password".toCharArray()), null); SSLServerSocket socket = (SSLServerSocket) secureServerSocketFactory.create(); String[] protocols = socket.getSupportedProtocols(); // remove one element from supported protocols if (protocols.length > 0) { protocols = Arrays.copyOfRange(protocols, 0, protocols.length - 1); } // test secureServerSocketFactory = new SecureServerSocketFactory(NanoHTTPD.makeSSLSocketFactory("/keystore.jks", "password".toCharArray()), protocols); socket = (SSLServerSocket) secureServerSocketFactory.create(); Assert.assertArrayEquals("Enabled protocols specified in the factory were not set to the socket.", protocols, socket.getEnabledProtocols()); } @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(); } } ================================================ FILE: core/src/test/java/org/nanohttpd/junit/protocols/http/ServerSocketFactoryTest.java ================================================ package org.nanohttpd.junit.protocols.http; 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 org.nanohttpd.protocols.http.NanoHTTPD; import org.nanohttpd.protocols.http.sockets.SecureServerSocketFactory; import org.nanohttpd.util.IFactoryThrowing; 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()); IFactoryThrowing ssFactory = new SecureServerSocketFactory(null, protocols); ServerSocket ss = null; try { ss = ssFactory.create(); } catch (Exception e) { } Assert.assertTrue(ss == null); } private class TestFactory implements IFactoryThrowing { @Override public ServerSocket create() { try { return new ServerSocket(); } catch (IOException e) { e.printStackTrace(); } return null; } } } ================================================ FILE: core/src/test/java/org/nanohttpd/junit/protocols/http/StatusTest.java ================================================ package org.nanohttpd.junit.protocols.http; import java.util.HashMap; import java.util.Map; /* * #%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; import org.nanohttpd.protocols.http.response.Status; public class StatusTest { @Test public void testMessages() { // These are values where the name of the enum does not match the status // code description. // By default you should not need to add any new values to this map if // you // make the name of the enum name match the status code description. Map overrideValues = new HashMap(); overrideValues.put(Status.INTERNAL_ERROR, "500 Internal Server Error"); overrideValues.put(Status.SWITCH_PROTOCOL, "101 Switching Protocols"); overrideValues.put(Status.OK, "200 OK"); overrideValues.put(Status.MULTI_STATUS, "207 Multi-Status"); overrideValues.put(Status.REDIRECT, "301 Moved Permanently"); overrideValues.put(Status.REDIRECT_SEE_OTHER, "303 See Other"); overrideValues.put(Status.RANGE_NOT_SATISFIABLE, "416 Requested Range Not Satisfiable"); overrideValues.put(Status.UNSUPPORTED_HTTP_VERSION, "505 HTTP Version Not Supported"); for (Status status : Status.values()) { if (overrideValues.containsKey(status)) { Assert.assertEquals(overrideValues.get(status), status.getDescription()); } else { Assert.assertEquals(getExpectedMessage(status), status.getDescription()); } } } private String getExpectedMessage(Status status) { String name = status.name().toLowerCase(); String[] words = name.split("_"); StringBuilder builder = new StringBuilder(); builder.append(status.getRequestStatus()); builder.append(' '); for (int i = 0; i < words.length; i++) { builder.append(Character.toUpperCase(words[i].charAt(0))); builder.append(words[i].substring(1)); builder.append(' '); } return builder.toString().trim(); } @Test public void testLookup() throws Exception { Assert.assertEquals(Status.SWITCH_PROTOCOL, Status.lookup(101)); Assert.assertEquals(Status.OK, Status.lookup(200)); Assert.assertEquals(Status.CREATED, Status.lookup(201)); Assert.assertEquals(Status.ACCEPTED, Status.lookup(202)); Assert.assertEquals(Status.NO_CONTENT, Status.lookup(204)); Assert.assertEquals(Status.PARTIAL_CONTENT, Status.lookup(206)); Assert.assertEquals(Status.MULTI_STATUS, Status.lookup(207)); Assert.assertEquals(Status.REDIRECT, Status.lookup(301)); Assert.assertEquals(Status.FOUND, Status.lookup(302)); Assert.assertEquals(Status.REDIRECT_SEE_OTHER, Status.lookup(303)); Assert.assertEquals(Status.NOT_MODIFIED, Status.lookup(304)); Assert.assertEquals(Status.TEMPORARY_REDIRECT, Status.lookup(307)); Assert.assertEquals(Status.BAD_REQUEST, Status.lookup(400)); Assert.assertEquals(Status.UNAUTHORIZED, Status.lookup(401)); Assert.assertEquals(Status.FORBIDDEN, Status.lookup(403)); Assert.assertEquals(Status.NOT_FOUND, Status.lookup(404)); Assert.assertEquals(Status.METHOD_NOT_ALLOWED, Status.lookup(405)); Assert.assertEquals(Status.NOT_ACCEPTABLE, Status.lookup(406)); Assert.assertEquals(Status.REQUEST_TIMEOUT, Status.lookup(408)); Assert.assertEquals(Status.CONFLICT, Status.lookup(409)); Assert.assertEquals(Status.GONE, Status.lookup(410)); Assert.assertEquals(Status.LENGTH_REQUIRED, Status.lookup(411)); Assert.assertEquals(Status.PRECONDITION_FAILED, Status.lookup(412)); Assert.assertEquals(Status.PAYLOAD_TOO_LARGE, Status.lookup(413)); Assert.assertEquals(Status.UNSUPPORTED_MEDIA_TYPE, Status.lookup(415)); Assert.assertEquals(Status.RANGE_NOT_SATISFIABLE, Status.lookup(416)); Assert.assertEquals(Status.EXPECTATION_FAILED, Status.lookup(417)); Assert.assertEquals(Status.TOO_MANY_REQUESTS, Status.lookup(429)); Assert.assertEquals(Status.INTERNAL_ERROR, Status.lookup(500)); Assert.assertEquals(Status.NOT_IMPLEMENTED, Status.lookup(501)); Assert.assertEquals(Status.SERVICE_UNAVAILABLE, Status.lookup(503)); Assert.assertEquals(Status.UNSUPPORTED_HTTP_VERSION, Status.lookup(505)); } } ================================================ FILE: core/src/test/java/org/nanohttpd/junit/protocols/http/integration/CookieIntegrationTest.java ================================================ package org.nanohttpd.junit.protocols.http.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 static org.junit.Assert.assertTrue; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.List; import org.apache.http.client.CookieStore; import org.apache.http.client.ResponseHandler; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.BasicResponseHandler; import org.apache.http.impl.cookie.BasicClientCookie; import org.junit.Test; import org.nanohttpd.protocols.http.IHTTPSession; import org.nanohttpd.protocols.http.NanoHTTPD; import org.nanohttpd.protocols.http.content.Cookie; import org.nanohttpd.protocols.http.content.CookieHandler; import org.nanohttpd.protocols.http.response.Response; /** * @author Paul S. Hawke (paul.hawke@gmail.com) On: 9/2/13 at 10:10 PM */ public class CookieIntegrationTest extends IntegrationTestBase { public static class CookieTestServer extends NanoHTTPD { List cookiesReceived = new ArrayList(); List cookiesToSend = new ArrayList(); 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 Response.newFixedLengthResponse("Cookies!"); } } @Override public CookieTestServer createTestServer() { return new CookieTestServer(); } @Test public void testCookieSentBackToClient() throws Exception { this.testServer.cookiesToSend.add(new Cookie("name", "value", 30)); HttpGet httpget = new HttpGet("http://localhost:8192/"); ResponseHandler responseHandler = new BasicResponseHandler(); this.httpclient.execute(httpget, responseHandler); 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 testMultipleCookieSentBackToClient() throws Exception { this.testServer.cookiesToSend.add(new Cookie("name0", "value0", 30)); this.testServer.cookiesToSend.add(new Cookie("name1", "value1", 30)); this.testServer.cookiesToSend.add(new Cookie("name2", "value2", 30)); this.testServer.cookiesToSend.add(new Cookie("name3", "value3", 30)); HttpGet httpget = new HttpGet("http://localhost:8192/"); ResponseHandler responseHandler = new BasicResponseHandler(); this.httpclient.execute(httpget, responseHandler); assertEquals(4, this.httpclient.getCookieStore().getCookies().size()); } @Test public void testNoCookies() throws Exception { HttpGet httpget = new HttpGet("http://localhost:8192/"); ResponseHandler 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"); this.httpclient.getCookieStore().addCookie(clientCookie); HttpGet httpget = new HttpGet("http://localhost:8192/"); ResponseHandler responseHandler = new BasicResponseHandler(); this.httpclient.execute(httpget, responseHandler); assertEquals(1, this.testServer.cookiesReceived.size()); assertTrue(this.testServer.cookiesReceived.get(0).getHTTPHeader().contains("name=value")); } @Test public void testServerReceivesMultipleCookiesSentFromClient() throws Exception { Calendar calendar = Calendar.getInstance(); calendar.add(Calendar.DAY_OF_YEAR, 100); Date date = calendar.getTime(); BasicClientCookie clientCookie0 = new BasicClientCookie("name0", "value0"); BasicClientCookie clientCookie1 = new BasicClientCookie("name1", "value1"); BasicClientCookie clientCookie2 = new BasicClientCookie("name2", "value2"); BasicClientCookie clientCookie3 = new BasicClientCookie("name3", "value3"); clientCookie0.setExpiryDate(date); clientCookie0.setDomain("localhost"); clientCookie1.setExpiryDate(date); clientCookie1.setDomain("localhost"); clientCookie2.setExpiryDate(date); clientCookie2.setDomain("localhost"); clientCookie3.setExpiryDate(date); clientCookie3.setDomain("localhost"); this.httpclient.getCookieStore().addCookie(clientCookie0); this.httpclient.getCookieStore().addCookie(clientCookie1); this.httpclient.getCookieStore().addCookie(clientCookie2); this.httpclient.getCookieStore().addCookie(clientCookie3); HttpGet httpget = new HttpGet("http://localhost:8192/"); ResponseHandler responseHandler = new BasicResponseHandler(); this.httpclient.execute(httpget, responseHandler); assertEquals(4, this.testServer.cookiesReceived.size()); } } ================================================ FILE: core/src/test/java/org/nanohttpd/junit/protocols/http/integration/GZipIntegrationTest.java ================================================ package org.nanohttpd.junit.protocols.http.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 org.nanohttpd.protocols.http.IHTTPSession; import org.nanohttpd.protocols.http.NanoHTTPD; import org.nanohttpd.protocols.http.response.Response; import org.nanohttpd.protocols.http.response.Status; public class GZipIntegrationTest extends IntegrationTestBase { public static class TestServer extends NanoHTTPD { public Response response; public TestServer() { super(8192); } @Override public Response serve(IHTTPSession session) { return response.setUseGzip(true); } } @Override public TestServer createTestServer() { return new TestServer(); } @Test public void contentEncodingShouldBeAddedToFixedLengthResponses() throws IOException { testServer.response = Response.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 = Response.newChunkedResponse(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 = Response.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 = Response.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 = Response.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 = Response.newChunkedResponse(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 = Response.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 = Response.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())); } } ================================================ FILE: core/src/test/java/org/nanohttpd/junit/protocols/http/integration/GetAndPostIntegrationTest.java ================================================ package org.nanohttpd.junit.protocols.http.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 static org.junit.Assert.fail; import java.io.IOException; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; 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.ResponseHandler; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; 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.message.BasicNameValuePair; import org.apache.http.util.EntityUtils; import org.junit.Test; import org.nanohttpd.protocols.http.IHTTPSession; import org.nanohttpd.protocols.http.NanoHTTPD; import org.nanohttpd.protocols.http.request.Method; import org.nanohttpd.protocols.http.response.Response; import org.nanohttpd.protocols.http.response.Status; /** * @author Paul S. Hawke (paul.hawke@gmail.com) On: 5/19/13 at 5:36 PM */ public class GetAndPostIntegrationTest extends IntegrationTestBase { public static class TestServer extends NanoHTTPD { public String response; public TestServer() { super(8192); } @Override public Response serve(IHTTPSession session) { StringBuilder sb = new StringBuilder(String.valueOf(session.getMethod()) + ':' + this.response); Method method = session.getMethod(); Map files = new HashMap(); if (Method.PUT.equals(method) || Method.POST.equals(method)) { try { session.parseBody(files); } catch (Exception e) { fail(e.getMessage()); } } String uri = session.getUri(); Map parms = session.getParms(); if (parms.size() > 1) { parms.remove("NanoHttpd.QUERY_STRING"); sb.append("-params=").append(parms.size()); List p = new ArrayList(parms.keySet()); Collections.sort(p); for (String k : p) { sb.append(';').append(k).append('=').append(parms.get(k)); } } if ("/encodingtest".equals(uri)) { return Response.newFixedLengthResponse(Status.OK, MIME_HTML, "Testé çaTesté ça"); } else if ("/chin".equals(uri)) { return Response.newFixedLengthResponse(Status.OK, "application/octet-stream", sb.toString()); } else { return Response.newFixedLengthResponse(sb.toString()); } } } @Override public TestServer createTestServer() { return new TestServer(); } @Test public void testGetRequestWithParameters() throws Exception { this.testServer.response = "testGetRequestWithParameters"; HttpGet httpget = new HttpGet("http://localhost:8192/?age=120&gender=Male"); ResponseHandler responseHandler = new BasicResponseHandler(); String responseBody = this.httpclient.execute(httpget, responseHandler); assertEquals("GET:testGetRequestWithParameters-params=2;age=120;gender=Male", responseBody); } @Test public void testPostRequestWithFormEncodedParameters() throws Exception { this.testServer.response = "testPostRequestWithFormEncodedParameters"; HttpPost httppost = new HttpPost("http://localhost:8192/"); List postParameters = new ArrayList(); postParameters.add(new BasicNameValuePair("age", "120")); postParameters.add(new BasicNameValuePair("gender", "Male")); httppost.setEntity(new UrlEncodedFormEntity(postParameters)); ResponseHandler responseHandler = new BasicResponseHandler(); String responseBody = this.httpclient.execute(httppost, responseHandler); assertEquals("POST:testPostRequestWithFormEncodedParameters-params=2;age=120;gender=Male", responseBody); } @Test public void testPostRequestWithMultipartEncodedParameters() throws Exception { this.testServer.response = "testPostRequestWithMultipartEncodedParameters"; HttpPost httppost = new HttpPost("http://localhost:8192/"); MultipartEntity reqEntity = new MultipartEntity(HttpMultipartMode.BROWSER_COMPATIBLE); reqEntity.addPart("age", new StringBody("120")); reqEntity.addPart("gender", new StringBody("Male")); httppost.setEntity(reqEntity); ResponseHandler responseHandler = new BasicResponseHandler(); String responseBody = this.httpclient.execute(httppost, responseHandler); assertEquals("POST:testPostRequestWithMultipartEncodedParameters-params=2;age=120;gender=Male", responseBody); } @Test public void testPostWithNoParameters() throws Exception { this.testServer.response = "testPostWithNoParameters"; HttpPost httppost = new HttpPost("http://localhost:8192/"); ResponseHandler responseHandler = new BasicResponseHandler(); String responseBody = this.httpclient.execute(httppost, responseHandler); assertEquals("POST:testPostWithNoParameters", responseBody); } @Test public void testSimpleGetRequest() throws Exception { this.testServer.response = "testSimpleGetRequest"; HttpGet httpget = new HttpGet("http://localhost:8192/"); ResponseHandler responseHandler = new BasicResponseHandler(); String responseBody = this.httpclient.execute(httpget, responseHandler); assertEquals("GET:testSimpleGetRequest", responseBody); } @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); HttpEntity entity = response.getEntity(); String responseBody = EntityUtils.toString(entity, "UTF-8"); assertEquals("POST:testPostRequestWithMultipartEncodedParameters-params=2;gender=图片名称;specialString=拖拉图片到浏览器,可以实现预览功能", responseBody); } @Test public void testPostRequestWithEncodedParameters() throws Exception { this.testServer.response = "testPostRequestWithEncodedParameters"; HttpPost httppost = new HttpPost("http://localhost:8192/encodingtest"); HttpResponse response = this.httpclient.execute(httppost); HttpEntity entity = response.getEntity(); String responseBody = EntityUtils.toString(entity); assertEquals("Testé çaTesté ça", responseBody); } } ================================================ FILE: core/src/test/java/org/nanohttpd/junit/protocols/http/integration/IntegrationTestBase.java ================================================ package org.nanohttpd.junit.protocols.http.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 java.io.IOException; import org.apache.http.impl.client.DefaultHttpClient; import org.junit.After; import org.junit.Before; import org.nanohttpd.protocols.http.NanoHTTPD; /** * @author Paul S. Hawke (paul.hawke@gmail.com) On: 9/2/13 at 10:02 PM */ public abstract class IntegrationTestBase { protected DefaultHttpClient httpclient; protected T testServer; public abstract T createTestServer(); @Before public void setUp() { this.testServer = createTestServer(); this.httpclient = new DefaultHttpClient(); try { this.testServer.start(); } catch (IOException e) { e.printStackTrace(); } } @After public void tearDown() { this.httpclient.getConnectionManager().shutdown(); this.testServer.stop(); } } ================================================ FILE: core/src/test/java/org/nanohttpd/junit/protocols/http/integration/PutStreamIntegrationTest.java ================================================ package org.nanohttpd.junit.protocols.http.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; import java.io.IOException; import java.util.Map; import org.apache.http.client.ResponseHandler; import org.apache.http.client.methods.HttpPut; import org.apache.http.entity.ByteArrayEntity; import org.apache.http.impl.client.BasicResponseHandler; import org.junit.Test; import org.nanohttpd.protocols.http.IHTTPSession; import org.nanohttpd.protocols.http.NanoHTTPD; import org.nanohttpd.protocols.http.request.Method; import org.nanohttpd.protocols.http.response.Response; import org.nanohttpd.protocols.http.response.Status; public class PutStreamIntegrationTest extends IntegrationTestBase { public static class TestServer extends NanoHTTPD { public TestServer() { super(8192); } @Override public Response serve(IHTTPSession session) { Method method = session.getMethod(); Map headers = session.getHeaders(); int contentLength = Integer.parseInt(headers.get("content-length")); byte[] body; try { DataInputStream dataInputStream = new DataInputStream(session.getInputStream()); body = new byte[contentLength]; dataInputStream.readFully(body, 0, contentLength); } catch (IOException e) { return Response.newFixedLengthResponse(Status.INTERNAL_ERROR, NanoHTTPD.MIME_PLAINTEXT, e.getMessage()); } String response = String.valueOf(method) + ':' + new String(body); return Response.newFixedLengthResponse(response); } } @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 responseHandler = new BasicResponseHandler(); String responseBody = this.httpclient.execute(httpput, responseHandler); assertEquals("PUT:" + expected, responseBody); } } ================================================ FILE: core/src/test/java/org/nanohttpd/junit/protocols/http/integration/ShutdownTest.java ================================================ package org.nanohttpd.junit.protocols.http.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.fail; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import org.junit.Test; import org.nanohttpd.protocols.http.IHTTPSession; import org.nanohttpd.protocols.http.NanoHTTPD; import org.nanohttpd.protocols.http.response.Response; public class ShutdownTest { private class TestServer extends NanoHTTPD { public TestServer() { super(8092); } @Override public Response serve(IHTTPSession session) { return Response.newFixedLengthResponse("Whatever"); } } @Test public void connectionsAreClosedWhenServerStops() throws IOException { TestServer server = new TestServer(); server.start(); makeRequest(); server.stop(); try { makeRequest(); fail("Connection should be closed!"); } catch (IOException e) { // Expected exception } } private void makeRequest() throws MalformedURLException, IOException { HttpURLConnection connection = (HttpURLConnection) new URL("http://localhost:8092/").openConnection(); // Keep-alive seems to be on by default, but just in case that changes. connection.addRequestProperty("Connection", "keep-alive"); InputStream in = connection.getInputStream(); while (in.available() > 0) { in.read(); } in.close(); } } ================================================ FILE: core/src/test/resources/META-INF/nanohttpd/mimetypes.properties ================================================ #test mime types for nanohttpd blabla=text/blabla ts=video/wrongOverwrite ================================================ FILE: core/src/test/resources/file-upload-test.htm ================================================

This is a file upload test for NanoHTTPD.





================================================ FILE: core/src/test/resources/multipart-form-test.htm ================================================

This is a multipart-form test for NanoHTTPD.




================================================ FILE: fileupload/.gitignore ================================================ /.settings/ /LICENSE.txt ================================================ FILE: fileupload/build.gradle ================================================ description = 'NanoHttpd-apache file upload integration' dependencies { compile group: 'commons-fileupload', name: 'commons-fileupload', version: '1.3.1' compileOnly project(':nanohttpd') compileOnly group: 'javax.servlet', name: 'servlet-api', version: '2.5' testCompile project(':nanohttpd') testCompile group: 'javax.servlet', name: 'servlet-api', version: '2.5' testCompile group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.4.1' testCompile group: 'org.apache.httpcomponents', name: 'httpmime', version: '4.4.1' } task wrapper(type: Wrapper) { gradleVersion = "4.4.1" } ================================================ FILE: fileupload/pom.xml ================================================ nanohttpd-project org.nanohttpd 2.3.2-SNAPSHOT 4.0.0 nanohttpd-apache-fileupload NanoHttpd-apache file upload integration nanohttpd-apache-fileupload integrates the apache file upload framework into nanohttpd org.nanohttpd nanohttpd 2.3.2-SNAPSHOT provided commons-fileupload commons-fileupload 1.3.1 javax.servlet servlet-api 2.5 provided org.apache.httpcomponents httpclient 4.4.1 test org.apache.httpcomponents httpmime 4.4.1 test 0.99 ================================================ FILE: fileupload/src/main/java/org/nanohttpd/fileupload/NanoFileUpload.java ================================================ package org.nanohttpd.fileupload; /* * #%L * NanoHttpd-apache file upload integration * %% * Copyright (C) 2012 - 2016 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.io.InputStream; import java.util.List; import java.util.Map; import org.apache.commons.fileupload.FileItem; import org.apache.commons.fileupload.FileItemFactory; import org.apache.commons.fileupload.FileItemIterator; import org.apache.commons.fileupload.FileUpload; import org.apache.commons.fileupload.FileUploadBase; import org.apache.commons.fileupload.FileUploadException; import org.apache.commons.fileupload.UploadContext; import org.nanohttpd.protocols.http.IHTTPSession; import org.nanohttpd.protocols.http.request.Method; /** * @author victor & ritchieGitHub */ public class NanoFileUpload extends FileUpload { public static class NanoHttpdContext implements UploadContext { private IHTTPSession session; public NanoHttpdContext(IHTTPSession session) { this.session = session; } @Override public long contentLength() { long size; try { String cl1 = session.getHeaders().get("content-length"); size = Long.parseLong(cl1); } catch (NumberFormatException var4) { size = -1L; } return size; } @Override public String getCharacterEncoding() { return "UTF-8"; } @Override public String getContentType() { return this.session.getHeaders().get("content-type"); } @Override public int getContentLength() { return (int) contentLength(); } @Override public InputStream getInputStream() throws IOException { return session.getInputStream(); } } public static final boolean isMultipartContent(IHTTPSession session) { return session.getMethod() == Method.POST && FileUploadBase.isMultipartContent(new NanoHttpdContext(session)); } public NanoFileUpload(FileItemFactory fileItemFactory) { super(fileItemFactory); } public List parseRequest(IHTTPSession session) throws FileUploadException { return this.parseRequest(new NanoHttpdContext(session)); } public Map> parseParameterMap(IHTTPSession session) throws FileUploadException { return this.parseParameterMap(new NanoHttpdContext(session)); } public FileItemIterator getItemIterator(IHTTPSession session) throws FileUploadException, IOException { return super.getItemIterator(new NanoHttpdContext(session)); } } ================================================ FILE: fileupload/src/test/java/org/nanohttpd/junit/fileupload/TestNanoFileUpLoad.java ================================================ package org.nanohttpd.junit.fileupload; /* * #%L * NanoHttpd-apache file upload integration * %% * Copyright (C) 2012 - 2015 nanohttpd * %% * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * 3. Neither the name of the nanohttpd nor the names of its contributors * may be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. * #L% */ import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.InetAddress; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.fileupload.FileItem; import org.apache.commons.fileupload.FileItemIterator; import org.apache.commons.fileupload.FileItemStream; import org.apache.commons.fileupload.disk.DiskFileItemFactory; import org.apache.commons.fileupload.util.Streams; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpTrace; import org.apache.http.entity.ContentType; import org.apache.http.entity.mime.HttpMultipartMode; import org.apache.http.entity.mime.MultipartEntityBuilder; import org.apache.http.entity.mime.content.FileBody; import org.apache.http.entity.mime.content.StringBody; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.FixMethodOrder; import org.junit.Test; import org.nanohttpd.fileupload.NanoFileUpload; import org.nanohttpd.protocols.http.HTTPSession; import org.nanohttpd.protocols.http.IHTTPSession; import org.nanohttpd.protocols.http.NanoHTTPD; import org.nanohttpd.protocols.http.request.Method; import org.nanohttpd.protocols.http.response.Response; import org.nanohttpd.protocols.http.response.Status; import org.nanohttpd.protocols.http.tempfiles.ITempFileManager; /** * very strange but if the file upload is the first request the test fails. * * @author ritchieGitHub */ @FixMethodOrder public class TestNanoFileUpLoad { private static final String UPLOAD_JAVA_FILE = "src/test/java/" + TestNanoFileUpLoad.class.getName().replace('.', '/') + ".java"; protected TestServer testServer; public static class TestServer extends NanoHTTPD { public Response response = Response.newFixedLengthResponse(""); public String uri; public Method method; public Map header; public Map parms; public Map> files; public Map> decodedParamters; public Map> decodedParamtersFromParameter; public String queryParameterString; public TestServer() { super(8192); uploader = new NanoFileUpload(new DiskFileItemFactory()); } public HTTPSession createSession(ITempFileManager tempFileManager, InputStream inputStream, OutputStream outputStream) { return new HTTPSession(this, tempFileManager, inputStream, outputStream); } public HTTPSession createSession(ITempFileManager tempFileManager, InputStream inputStream, OutputStream outputStream, InetAddress inetAddress) { return new HTTPSession(this, tempFileManager, inputStream, outputStream, inetAddress); } NanoFileUpload uploader; @Override public Response serve(IHTTPSession session) { this.uri = session.getUri(); this.method = session.getMethod(); this.header = session.getHeaders(); this.parms = session.getParms(); if (NanoFileUpload.isMultipartContent(session)) { try { if ("/uploadFile1".equals(this.uri)) { session.getHeaders().put("content-length", "AA"); files = uploader.parseParameterMap(session); } if ("/uploadFile2".equals(this.uri)) { files = new HashMap>(); List parseRequest = uploader.parseRequest(session); files.put(parseRequest.get(0).getFieldName(), parseRequest); } if ("/uploadFile3".equals(this.uri)) { files = new HashMap>(); FileItemIterator iter = uploader.getItemIterator(session); while (iter.hasNext()) { FileItemStream item = iter.next(); final String fileName = item.getName(); FileItem fileItem = uploader.getFileItemFactory().createItem(item.getFieldName(), item.getContentType(), item.isFormField(), fileName); files.put(fileItem.getFieldName(), Arrays.asList(new FileItem[]{ fileItem })); try { Streams.copy(item.openStream(), fileItem.getOutputStream(), true); } catch (Exception e) { } fileItem.setHeaders(item.getHeaders()); } } } catch (Exception e) { this.response.setStatus(Status.INTERNAL_ERROR); e.printStackTrace(); } } this.queryParameterString = session.getQueryParameterString(); this.decodedParamtersFromParameter = decodeParameters(this.queryParameterString); this.decodedParamters = decodeParameters(session.getQueryParameterString()); return this.response; } } @Test public void testNormalRequest() throws Exception { CloseableHttpClient httpclient = HttpClients.createDefault(); HttpTrace httphead = new HttpTrace("http://localhost:8192/index.html"); CloseableHttpResponse response = httpclient.execute(httphead); Assert.assertEquals(200, response.getStatusLine().getStatusCode()); response.close(); } @Test public void testPostWithMultipartFormUpload1() throws Exception { CloseableHttpClient httpclient = HttpClients.createDefault(); String textFileName = UPLOAD_JAVA_FILE; HttpPost post = new HttpPost("http://localhost:8192/uploadFile1"); executeUpload(httpclient, textFileName, post); FileItem file = this.testServer.files.get("upfile").get(0); Assert.assertEquals(file.getSize(), new File(textFileName).length()); } @Test public void testPostWithMultipartFormUpload2() throws Exception { CloseableHttpClient httpclient = HttpClients.createDefault(); String textFileName = UPLOAD_JAVA_FILE; HttpPost post = new HttpPost("http://localhost:8192/uploadFile2"); executeUpload(httpclient, textFileName, post); FileItem file = this.testServer.files.get("upfile").get(0); Assert.assertEquals(file.getSize(), new File(textFileName).length()); } @Test public void testPostWithMultipartFormUpload3() throws Exception { CloseableHttpClient httpclient = HttpClients.createDefault(); String textFileName = UPLOAD_JAVA_FILE; HttpPost post = new HttpPost("http://localhost:8192/uploadFile3"); executeUpload(httpclient, textFileName, post); FileItem file = this.testServer.files.get("upfile").get(0); Assert.assertEquals(file.getSize(), new File(textFileName).length()); } private void executeUpload(CloseableHttpClient httpclient, String textFileName, HttpPost post) throws IOException, ClientProtocolException { FileBody fileBody = new FileBody(new File(textFileName), ContentType.DEFAULT_BINARY); StringBody stringBody1 = new StringBody("Message 1", ContentType.MULTIPART_FORM_DATA); MultipartEntityBuilder builder = MultipartEntityBuilder.create(); builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE); builder.addPart("upfile", fileBody); builder.addPart("text1", stringBody1); HttpEntity entity = builder.build(); // post.setEntity(entity); HttpResponse response = httpclient.execute(post); Assert.assertEquals(200, response.getStatusLine().getStatusCode()); } @Before public void setUp() throws IOException { this.testServer = new TestServer(); this.testServer.start(); try { long start = System.currentTimeMillis(); Thread.sleep(100L); while (!this.testServer.wasStarted()) { Thread.sleep(100L); if (System.currentTimeMillis() - start > 2000) { Assert.fail("could not start server"); } } } catch (InterruptedException e) { } } @After public void tearDown() { this.testServer.stop(); } } ================================================ FILE: markdown-plugin/.gitignore ================================================ /.settings/ /LICENSE.txt ================================================ FILE: markdown-plugin/build.gradle ================================================ description = 'NanoHttpd-Webserver-Markdown-Plugin' dependencies { compile group: 'org.pegdown', name: 'pegdown', version: '1.4.1' compileOnly project(':nanohttpd') compileOnly project(':nanohttpd-webserver') } task wrapper(type: Wrapper) { gradleVersion = "4.4.1" } ================================================ FILE: markdown-plugin/pom.xml ================================================ 4.0.0 org.nanohttpd nanohttpd-project 2.3.2-SNAPSHOT nanohttpd-webserver-markdown-plugin jar NanoHttpd-Webserver-Markdown-Plugin https://github.com/NanoHttpd/nanohttpd ${project.groupId} nanohttpd ${project.version} provided ${project.groupId} nanohttpd-webserver ${project.version} provided org.pegdown pegdown 1.4.1 org.apache.maven.plugins maven-assembly-plugin 2.2-beta-5 jar-with-dependencies fi.iki.elonen.SimpleWebServer package single 0.0 ================================================ FILE: markdown-plugin/src/main/java/org/nanohttpd/markdown/MarkdownWebServerPlugin.java ================================================ package org.nanohttpd.markdown; /* * #%L * NanoHttpd-Webserver-Markdown-Plugin * %% * Copyright (C) 2012 - 2016 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.BufferedReader; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import org.nanohttpd.protocols.http.IHTTPSession; import org.nanohttpd.protocols.http.NanoHTTPD; import org.nanohttpd.protocols.http.response.Response; import org.nanohttpd.protocols.http.response.Status; import org.nanohttpd.webserver.WebServerPlugin; import org.pegdown.PegDownProcessor; /** * @author Paul S. Hawke (paul.hawke@gmail.com) On: 9/13/13 at 4:03 AM */ public class MarkdownWebServerPlugin implements WebServerPlugin { /** * logger to log to. */ private static final Logger LOG = Logger.getLogger(MarkdownWebServerPlugin.class.getName()); private final PegDownProcessor processor; public MarkdownWebServerPlugin() { this.processor = new PegDownProcessor(); } @Override public boolean canServeUri(String uri, File rootDir) { File f = new File(rootDir, uri); return f.exists(); } @Override public void initialize(Map commandLineOptions) { } private String readSource(File file) { FileReader fileReader = null; BufferedReader reader = null; try { fileReader = new FileReader(file); reader = new BufferedReader(fileReader); String line = null; StringBuilder sb = new StringBuilder(); do { line = reader.readLine(); if (line != null) { sb.append(line).append("\n"); } } while (line != null); reader.close(); return sb.toString(); } catch (Exception e) { MarkdownWebServerPlugin.LOG.log(Level.SEVERE, "could not read source", e); return null; } finally { try { if (fileReader != null) { fileReader.close(); } if (reader != null) { reader.close(); } } catch (IOException ignored) { MarkdownWebServerPlugin.LOG.log(Level.FINEST, "close failed", ignored); } } } @Override public Response serveFile(String uri, Map headers, IHTTPSession session, File file, String mimeType) { String markdownSource = readSource(file); byte[] bytes; try { bytes = this.processor.markdownToHtml(markdownSource).getBytes("UTF-8"); } catch (UnsupportedEncodingException e) { MarkdownWebServerPlugin.LOG.log(Level.SEVERE, "encoding problem, responding nothing", e); bytes = new byte[0]; } return markdownSource == null ? null : Response.newFixedLengthResponse(Status.OK, NanoHTTPD.MIME_HTML, new ByteArrayInputStream(bytes), bytes.length); } } ================================================ FILE: markdown-plugin/src/main/java/org/nanohttpd/markdown/MarkdownWebServerPluginInfo.java ================================================ package org.nanohttpd.markdown; import org.nanohttpd.webserver.WebServerPlugin; import org.nanohttpd.webserver.WebServerPluginInfo; /* * #%L * NanoHttpd-Webserver-Markdown-Plugin * %% * 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% */ /** * @author Paul S. Hawke (paul.hawke@gmail.com) On: 9/13/13 at 4:01 AM */ public class MarkdownWebServerPluginInfo implements WebServerPluginInfo { @Override public String[] getIndexFilesForMimeType(String mime) { return new String[]{ "index.md" }; } @Override public String[] getMimeTypes() { return new String[]{ "text/markdown" }; } @Override public WebServerPlugin getWebServerPlugin(String mimeType) { return new MarkdownWebServerPlugin(); } } ================================================ FILE: markdown-plugin/src/main/resources/META-INF/services/org.nanohttpd.webserver.WebServerPluginInfo ================================================ org.nanohttpd.markdown.MarkdownWebServerPluginInfo ================================================ FILE: markdown-plugin/src/site/site.xml ================================================ org.apache.maven.skins maven-fluido-skin 1.3.0 ../images/nanohttpd_logo.png ../images/nanohttpd_logo_text.png false true Nanohttpd/nanohttpd right black ================================================ FILE: nanohttpd release perform.launch ================================================ ================================================ FILE: nanohttpd release prepare.launch ================================================ ================================================ FILE: nanolets/.gitignore ================================================ /.settings/ /LICENSE.txt ================================================ FILE: nanolets/build.gradle ================================================ description = 'NanoHttpd-nano application server' dependencies { compile project(':nanohttpd') testCompile group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.4.1' } ================================================ FILE: nanolets/pom.xml ================================================ 4.0.0 org.nanohttpd nanohttpd-project 2.3.2-SNAPSHOT nanohttpd-nanolets jar NanoHttpd-nano application server nanohttpd-nanolets add a very easy to use version of servlets into nanohttpd. ${project.groupId} nanohttpd ${project.version} org.apache.httpcomponents httpclient 4.4.1 test 0.96 ================================================ FILE: nanolets/src/main/java/org/nanohttpd/router/RouterNanoHTTPD.java ================================================ package org.nanohttpd.router; /* * #%L * NanoHttpd-Samples * %% * Copyright (C) 2012 - 2015 nanohttpd * %% * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * 3. Neither the name of the nanohttpd nor the names of its contributors * may be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. * #L% */ import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.PriorityQueue; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.nanohttpd.protocols.http.IHTTPSession; import org.nanohttpd.protocols.http.NanoHTTPD; import org.nanohttpd.protocols.http.response.IStatus; import org.nanohttpd.protocols.http.response.Response; import org.nanohttpd.protocols.http.response.Status; /** * @author vnnv * @author ritchieGitHub */ public class RouterNanoHTTPD extends NanoHTTPD { /** * logger to log to. */ private static final Logger LOG = Logger.getLogger(RouterNanoHTTPD.class.getName()); public interface UriResponder { public Response get(UriResource uriResource, Map urlParams, IHTTPSession session); public Response put(UriResource uriResource, Map urlParams, IHTTPSession session); public Response post(UriResource uriResource, Map urlParams, IHTTPSession session); public Response delete(UriResource uriResource, Map urlParams, IHTTPSession session); public Response other(String method, UriResource uriResource, Map urlParams, IHTTPSession session); } /** * General nanolet to inherit from if you provide stream data, only chucked * responses will be generated. */ public static abstract class DefaultStreamHandler implements UriResponder { public abstract String getMimeType(); public abstract IStatus getStatus(); public abstract InputStream getData(); public Response get(UriResource uriResource, Map urlParams, IHTTPSession session) { return Response.newChunkedResponse(getStatus(), getMimeType(), getData()); } public Response post(UriResource uriResource, Map urlParams, IHTTPSession session) { return get(uriResource, urlParams, session); } public Response put(UriResource uriResource, Map urlParams, IHTTPSession session) { return get(uriResource, urlParams, session); } public Response delete(UriResource uriResource, Map urlParams, IHTTPSession session) { return get(uriResource, urlParams, session); } public Response other(String method, UriResource uriResource, Map urlParams, IHTTPSession session) { return get(uriResource, urlParams, session); } } /** * General nanolet to inherit from if you provide text or html data, only * fixed size responses will be generated. */ public static abstract class DefaultHandler extends DefaultStreamHandler { public abstract String getText(); public abstract IStatus getStatus(); public Response get(UriResource uriResource, Map urlParams, IHTTPSession session) { return Response.newFixedLengthResponse(getStatus(), getMimeType(), getText()); } @Override public InputStream getData() { throw new IllegalStateException("this method should not be called in a text based nanolet"); } } /** * General nanolet to print debug info's as a html page. */ public static class GeneralHandler extends DefaultHandler { @Override public String getText() { throw new IllegalStateException("this method should not be called"); } @Override public String getMimeType() { return "text/html"; } @Override public IStatus getStatus() { return Status.OK; } public Response get(UriResource uriResource, Map urlParams, IHTTPSession session) { StringBuilder text = new StringBuilder(""); text.append("

Url: "); text.append(session.getUri()); text.append("


"); Map queryParams = session.getParms(); if (queryParams.size() > 0) { for (Map.Entry entry : queryParams.entrySet()) { String key = entry.getKey(); String value = entry.getValue(); text.append("

Param '"); text.append(key); text.append("' = "); text.append(value); text.append("

"); } } else { text.append("

no params in url


"); } return Response.newFixedLengthResponse(getStatus(), getMimeType(), text.toString()); } } /** * General nanolet to print debug info's as a html page. */ public static class StaticPageHandler extends DefaultHandler { private static String[] getPathArray(String uri) { String array[] = uri.split("/"); ArrayList pathArray = new ArrayList(); for (String s : array) { if (s.length() > 0) pathArray.add(s); } return pathArray.toArray(new String[]{}); } @Override public String getText() { throw new IllegalStateException("this method should not be called"); } @Override public String getMimeType() { throw new IllegalStateException("this method should not be called"); } @Override public IStatus getStatus() { return Status.OK; } public Response get(UriResource uriResource, Map urlParams, IHTTPSession session) { String baseUri = uriResource.getUri(); String realUri = normalizeUri(session.getUri()); for (int index = 0; index < Math.min(baseUri.length(), realUri.length()); index++) { if (baseUri.charAt(index) != realUri.charAt(index)) { realUri = normalizeUri(realUri.substring(index)); break; } } File fileOrdirectory = uriResource.initParameter(File.class); for (String pathPart : getPathArray(realUri)) { fileOrdirectory = new File(fileOrdirectory, pathPart); } if (fileOrdirectory.isDirectory()) { fileOrdirectory = new File(fileOrdirectory, "index.html"); if (!fileOrdirectory.exists()) { fileOrdirectory = new File(fileOrdirectory.getParentFile(), "index.htm"); } } if (!fileOrdirectory.exists() || !fileOrdirectory.isFile()) { return new Error404UriHandler().get(uriResource, urlParams, session); } else { try { return Response.newChunkedResponse(getStatus(), getMimeTypeForFile(fileOrdirectory.getName()), fileToInputStream(fileOrdirectory)); } catch (IOException ioe) { return Response.newFixedLengthResponse(Status.REQUEST_TIMEOUT, "text/plain", (String) null); } } } protected BufferedInputStream fileToInputStream(File fileOrdirectory) throws IOException { return new BufferedInputStream(new FileInputStream(fileOrdirectory)); } } /** * Handling error 404 - unrecognized urls */ public static class Error404UriHandler extends DefaultHandler { public String getText() { return "

Error 404: the requested page doesn't exist.

"; } @Override public String getMimeType() { return "text/html"; } @Override public IStatus getStatus() { return Status.NOT_FOUND; } } /** * Handling index */ public static class IndexHandler extends DefaultHandler { public String getText() { return "

Hello world!

"; } @Override public String getMimeType() { return "text/html"; } @Override public IStatus getStatus() { return Status.OK; } } public static class NotImplementedHandler extends DefaultHandler { public String getText() { return "

The uri is mapped in the router, but no handler is specified.
Status: Not implemented!

"; } @Override public String getMimeType() { return "text/html"; } @Override public IStatus getStatus() { return Status.OK; } } public static String normalizeUri(String value) { if (value == null) { return value; } if (value.startsWith("/")) { value = value.substring(1); } if (value.endsWith("/")) { value = value.substring(0, value.length() - 1); } return value; } public static class UriResource implements Comparable { private static final Pattern PARAM_PATTERN = Pattern.compile("(?<=(^|/)):[a-zA-Z0-9_-]+(?=(/|$))"); private static final String PARAM_MATCHER = "([A-Za-z0-9\\-\\._~:/?#\\[\\]@!\\$&'\\(\\)\\*\\+,;=\\s]+)"; private static final Map EMPTY = Collections.unmodifiableMap(new HashMap()); private final String uri; private final Pattern uriPattern; private int priority; private final Class handler; private final Object[] initParameter; private final List uriParams = new ArrayList(); public UriResource(String uri, int priority, Class handler, Object... initParameter) { this(uri, handler, initParameter); this.priority = priority + uriParams.size() * 1000; } public UriResource(String uri, Class handler, Object... initParameter) { this.handler = handler; this.initParameter = initParameter; if (uri != null) { this.uri = normalizeUri(uri); parse(); this.uriPattern = createUriPattern(); } else { this.uriPattern = null; this.uri = null; } } private void parse() { } private Pattern createUriPattern() { String patternUri = uri; Matcher matcher = PARAM_PATTERN.matcher(patternUri); int start = 0; while (matcher.find(start)) { uriParams.add(patternUri.substring(matcher.start() + 1, matcher.end())); patternUri = new StringBuilder(patternUri.substring(0, matcher.start()))// .append(PARAM_MATCHER)// .append(patternUri.substring(matcher.end())).toString(); start = matcher.start() + PARAM_MATCHER.length(); matcher = PARAM_PATTERN.matcher(patternUri); } return Pattern.compile(patternUri); } public Response process(Map urlParams, IHTTPSession session) { String error = "General error!"; if (handler != null) { try { Object object = handler.newInstance(); if (object instanceof UriResponder) { UriResponder responder = (UriResponder) object; switch (session.getMethod()) { case GET: return responder.get(this, urlParams, session); case POST: return responder.post(this, urlParams, session); case PUT: return responder.put(this, urlParams, session); case DELETE: return responder.delete(this, urlParams, session); default: return responder.other(session.getMethod().toString(), this, urlParams, session); } } else { return Response.newFixedLengthResponse(Status.OK, "text/plain", // new StringBuilder("Return: ")// .append(handler.getCanonicalName())// .append(".toString() -> ")// .append(object)// .toString()); } } catch (Exception e) { error = "Error: " + e.getClass().getName() + " : " + e.getMessage(); LOG.log(Level.SEVERE, error, e); } } return Response.newFixedLengthResponse(Status.INTERNAL_ERROR, "text/plain", error); } @Override public String toString() { return new StringBuilder("UrlResource{uri='").append((uri == null ? "/" : uri))// .append("', urlParts=").append(uriParams)// .append('}')// .toString(); } public String getUri() { return uri; } public T initParameter(Class paramClazz) { return initParameter(0, paramClazz); } public T initParameter(int parameterIndex, Class paramClazz) { if (initParameter.length > parameterIndex) { return paramClazz.cast(initParameter[parameterIndex]); } LOG.severe("init parameter index not available " + parameterIndex); return null; } public Map match(String url) { Matcher matcher = uriPattern.matcher(url); if (matcher.matches()) { if (uriParams.size() > 0) { Map result = new HashMap(); for (int i = 1; i <= matcher.groupCount(); i++) { result.put(uriParams.get(i - 1), matcher.group(i)); } return result; } else { return EMPTY; } } return null; } @Override public int compareTo(UriResource that) { if (that == null) { return 1; } else if (this.priority > that.priority) { return 1; } else if (this.priority < that.priority) { return -1; } else { return 0; } } public void setPriority(int priority) { this.priority = priority; } } public static interface IRoutePrioritizer { void addRoute(String url, int priority, Class handler, Object... initParameter); void removeRoute(String url); Collection getPrioritizedRoutes(); void setNotImplemented(Class notImplemented); } public static abstract class BaseRoutePrioritizer implements IRoutePrioritizer { protected Class notImplemented; protected final Collection mappings; public BaseRoutePrioritizer() { this.mappings = newMappingCollection(); this.notImplemented = NotImplementedHandler.class; } @Override public void addRoute(String url, int priority, Class handler, Object... initParameter) { if (url != null) { if (handler != null) { mappings.add(new UriResource(url, priority + mappings.size(), handler, initParameter)); } else { mappings.add(new UriResource(url, priority + mappings.size(), notImplemented)); } } } public void removeRoute(String url) { String uriToDelete = normalizeUri(url); Iterator iter = mappings.iterator(); while (iter.hasNext()) { UriResource uriResource = iter.next(); if (uriToDelete.equals(uriResource.getUri())) { iter.remove(); break; } } } @Override public Collection getPrioritizedRoutes() { return Collections.unmodifiableCollection(mappings); } @Override public void setNotImplemented(Class handler) { notImplemented = handler; } protected abstract Collection newMappingCollection(); } public static class ProvidedPriorityRoutePrioritizer extends BaseRoutePrioritizer { @Override public void addRoute(String url, int priority, Class handler, Object... initParameter) { if (url != null) { UriResource resource = null; if (handler != null) { resource = new UriResource(url, handler, initParameter); } else { resource = new UriResource(url, handler, notImplemented); } resource.setPriority(priority); mappings.add(resource); } } @Override protected Collection newMappingCollection() { return new PriorityQueue(); } } public static class DefaultRoutePrioritizer extends BaseRoutePrioritizer { protected Collection newMappingCollection() { return new PriorityQueue(); } } public static class InsertionOrderRoutePrioritizer extends BaseRoutePrioritizer { protected Collection newMappingCollection() { return new ArrayList(); } } public static class UriRouter { private UriResource error404Url; private IRoutePrioritizer routePrioritizer; public UriRouter() { this.routePrioritizer = new DefaultRoutePrioritizer(); } /** * Search in the mappings if the given url matches some of the rules If * there are more than one marches returns the rule with less parameters * e.g. mapping 1 = /user/:id mapping 2 = /user/help if the incoming uri * is www.example.com/user/help - mapping 2 is returned if the incoming * uri is www.example.com/user/3232 - mapping 1 is returned * * @param url * @return */ public Response process(IHTTPSession session) { String work = normalizeUri(session.getUri()); Map params = null; UriResource uriResource = error404Url; for (UriResource u : routePrioritizer.getPrioritizedRoutes()) { params = u.match(work); if (params != null) { uriResource = u; break; } } return uriResource.process(params, session); } private void addRoute(String url, int priority, Class handler, Object... initParameter) { routePrioritizer.addRoute(url, priority, handler, initParameter); } private void removeRoute(String url) { routePrioritizer.removeRoute(url); } public void setNotFoundHandler(Class handler) { error404Url = new UriResource(null, 100, handler); } public void setNotImplemented(Class handler) { routePrioritizer.setNotImplemented(handler); } public void setRoutePrioritizer(IRoutePrioritizer routePrioritizer) { this.routePrioritizer = routePrioritizer; } } private UriRouter router; public RouterNanoHTTPD(int port) { super(port); router = new UriRouter(); } public RouterNanoHTTPD(String hostname, int port) { super(hostname, port); router = new UriRouter(); } /** * default routings, they are over writable. * *
     * router.setNotFoundHandler(GeneralHandler.class);
     * 
*/ public void addMappings() { router.setNotImplemented(NotImplementedHandler.class); router.setNotFoundHandler(Error404UriHandler.class); router.addRoute("/", Integer.MAX_VALUE / 2, IndexHandler.class); router.addRoute("/index.html", Integer.MAX_VALUE / 2, IndexHandler.class); } public void addRoute(String url, Class handler, Object... initParameter) { router.addRoute(url, 100, handler, initParameter); } public void setNotImplementedHandler(Class handler) { router.setNotImplemented(handler); } public void setNotFoundHandler(Class handler) { router.setNotFoundHandler(handler); } public void removeRoute(String url) { router.removeRoute(url); } public void setRoutePrioritizer(IRoutePrioritizer routePrioritizer) { router.setRoutePrioritizer(routePrioritizer); } @Override public Response serve(IHTTPSession session) { // Try to find match return router.process(session); } } ================================================ FILE: nanolets/src/test/java/org/nanohttpd/junit/router/AppNanolets.java ================================================ package org.nanohttpd.junit.router; /* * #%L * NanoHttpd-Samples * %% * Copyright (C) 2012 - 2015 nanohttpd * %% * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * 3. Neither the name of the nanohttpd nor the names of its contributors * may be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. * #L% */ /** * Created by vnnv on 7/17/15. * Simple httpd server based on NanoHTTPD * Read the source. Everything is there. */ import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.util.Map; import org.nanohttpd.protocols.http.IHTTPSession; import org.nanohttpd.protocols.http.response.IStatus; import org.nanohttpd.protocols.http.response.Response; import org.nanohttpd.protocols.http.response.Status; import org.nanohttpd.router.RouterNanoHTTPD; import org.nanohttpd.router.RouterNanoHTTPD.DefaultHandler; import org.nanohttpd.router.RouterNanoHTTPD.DefaultStreamHandler; import org.nanohttpd.router.RouterNanoHTTPD.GeneralHandler; import org.nanohttpd.router.RouterNanoHTTPD.StaticPageHandler; import org.nanohttpd.router.RouterNanoHTTPD.UriResource; import org.nanohttpd.router.RouterNanoHTTPD.UriResponder; import org.nanohttpd.util.ServerRunner; public class AppNanolets extends RouterNanoHTTPD { private static final int PORT = 9090; public static class UserHandler extends DefaultHandler { @Override public String getText() { return "not implemented"; } public String getText(Map urlParams, IHTTPSession session) { String text = "User handler. Method: " + session.getMethod().toString() + "
"; text += "

Uri parameters:

"; for (Map.Entry entry : urlParams.entrySet()) { String key = entry.getKey(); String value = entry.getValue(); text += "
Param: " + key + " Value: " + value + "
"; } text += "

Query parameters:

"; for (Map.Entry entry : session.getParms().entrySet()) { String key = entry.getKey(); String value = entry.getValue(); text += "
Query Param: " + key + " Value: " + value + "
"; } text += ""; return text; } @Override public String getMimeType() { return "text/html"; } @Override public IStatus getStatus() { return Status.OK; } public Response get(UriResource uriResource, Map urlParams, IHTTPSession session) { String text = getText(urlParams, session); ByteArrayInputStream inp = new ByteArrayInputStream(text.getBytes()); int size = text.getBytes().length; return Response.newFixedLengthResponse(getStatus(), getMimeType(), inp, size); } } static public class StreamUrl extends DefaultStreamHandler { @Override public String getMimeType() { return "text/plain"; } @Override public IStatus getStatus() { return Status.OK; } @Override public InputStream getData() { return new ByteArrayInputStream("a stream of data ;-)".getBytes()); } } public static class StaticPageTestHandler extends StaticPageHandler { @Override protected BufferedInputStream fileToInputStream(File fileOrdirectory) throws IOException { if ("exception.html".equals(fileOrdirectory.getName())) { throw new IOException("trigger something wrong"); } return super.fileToInputStream(fileOrdirectory); } } /** * Create the server instance */ public AppNanolets() throws IOException { super(PORT); addMappings(); System.out.println("\nRunning! Point your browers to http://localhost:" + PORT + "/ \n"); } /** * Add the routes Every route is an absolute path Parameters starts with ":" * Handler class should implement @UriResponder interface If the handler not * implement UriResponder interface - toString() is used */ @Override public void addMappings() { super.addMappings(); addRoute("/user", UserHandler.class); addRoute("/user", UserHandler.class); // add it twice to execute the // priority == priority case addRoute("/user/help", GeneralHandler.class); addRoute("/user/:id", UserHandler.class); addRoute("/general/:param1/:param2", GeneralHandler.class); addRoute("/photos/:customer_id/:photo_id", null); addRoute("/test", String.class); addRoute("/interface", UriResponder.class); // this will cause an error // when called addRoute("/toBeDeleted", String.class); removeRoute("/toBeDeleted"); addRoute("/stream", StreamUrl.class); addRoute("/browse/(.)+", StaticPageTestHandler.class, new File("src/test/resources").getAbsoluteFile()); } /** * Main entry point * * @param args */ public static void main(String[] args) { ServerRunner.run(AppNanolets.class); } } ================================================ FILE: nanolets/src/test/java/org/nanohttpd/junit/router/TestNanolets.java ================================================ package org.nanohttpd.junit.router; /* * #%L * NanoHttpd nano application server * %% * Copyright (C) 2012 - 2015 nanohttpd * %% * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * 3. Neither the name of the nanohttpd nor the names of its contributors * may be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. * #L% */ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.PipedInputStream; import java.io.PipedOutputStream; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.http.HttpEntity; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpDelete; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPut; import org.apache.http.client.methods.HttpTrace; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.nanohttpd.protocols.http.response.Status; import org.nanohttpd.router.RouterNanoHTTPD; import org.nanohttpd.router.RouterNanoHTTPD.DefaultRoutePrioritizer; import org.nanohttpd.router.RouterNanoHTTPD.Error404UriHandler; import org.nanohttpd.router.RouterNanoHTTPD.GeneralHandler; import org.nanohttpd.router.RouterNanoHTTPD.IndexHandler; import org.nanohttpd.router.RouterNanoHTTPD.InsertionOrderRoutePrioritizer; import org.nanohttpd.router.RouterNanoHTTPD.NotImplementedHandler; import org.nanohttpd.router.RouterNanoHTTPD.ProvidedPriorityRoutePrioritizer; import org.nanohttpd.router.RouterNanoHTTPD.StaticPageHandler; import org.nanohttpd.router.RouterNanoHTTPD.UriResource; import org.nanohttpd.router.RouterNanoHTTPD.UriResponder; import org.nanohttpd.router.RouterNanoHTTPD.UriRouter; public class TestNanolets { private static PipedOutputStream stdIn; private static Thread serverStartThread; @BeforeClass public static void setUp() throws Exception { stdIn = new PipedOutputStream(); System.setIn(new PipedInputStream(stdIn)); serverStartThread = new Thread(new Runnable() { @Override public void run() { String[] args = {}; AppNanolets.main(args); } }); serverStartThread.start(); // give the server some tine to start. Thread.sleep(200); } public static void main(String[] args) { { String uri = "def"; Pattern.compile("([A-Za-z0-9\\-\\._~:/?#\\[\\]@!\\$&'\\(\\)\\*\\+,;=]+)"); Pattern URI_PATTERN = Pattern.compile("([A-Za-z0-9\\-\\._~:/?#\\[\\]@!\\$&'\\(\\)\\*\\+,;=]+)"); System.out.println(URI_PATTERN.matcher(uri).matches()); } String uri = "photos/abc/def"; Pattern URI_PATTERN = Pattern.compile("photos/([A-Za-z0-9\\-\\._~:/?#\\[\\]@!\\$&'\\(\\)\\*\\+,;=]+)/([A-Za-z0-9\\-\\._~:/?#\\[\\]@!\\$&'\\(\\)\\*\\+,;=]+)"); Matcher matcher = URI_PATTERN.matcher(uri); System.out.println("--------------->" + "/" + uri); while (matcher.matches()) { System.out.println(matcher.group()); } // for (int index = 0; index < matcher.groupCount(); index++) { // System.out.println(matcher.group()); // } } @Test public void doSomeBasicMethodTest() throws Exception { CloseableHttpClient httpclient = HttpClients.createDefault(); HttpGet httpget = new HttpGet("http://localhost:9090/user/blabla"); CloseableHttpResponse response = httpclient.execute(httpget); HttpEntity entity = response.getEntity(); String string = new String(readContents(entity), "UTF-8"); Assert.assertEquals( "User handler. Method: GET

Uri parameters:

Param: id Value: blabla

Query parameters:

", string); response.close(); HttpPost httppost = new HttpPost("http://localhost:9090/user/blabla"); response = httpclient.execute(httppost); entity = response.getEntity(); string = new String(readContents(entity), "UTF-8"); Assert.assertEquals( "User handler. Method: POST

Uri parameters:

Param: id Value: blabla

Query parameters:

", string); response.close(); HttpPut httpgput = new HttpPut("http://localhost:9090/user/blabla"); response = httpclient.execute(httpgput); entity = response.getEntity(); string = new String(readContents(entity), "UTF-8"); Assert.assertEquals( "User handler. Method: PUT

Uri parameters:

Param: id Value: blabla

Query parameters:

", string); response.close(); HttpDelete httpdelete = new HttpDelete("http://localhost:9090/user/blabla"); response = httpclient.execute(httpdelete); entity = response.getEntity(); string = new String(readContents(entity), "UTF-8"); Assert.assertEquals( "User handler. Method: DELETE

Uri parameters:

Param: id Value: blabla

Query parameters:

", string); response.close(); } @Test public void doEncodedRequest() throws ClientProtocolException, IOException { CloseableHttpClient httpclient = HttpClients.createDefault(); HttpGet httpget = new HttpGet("http://localhost:9090/general/param%201/param%202"); CloseableHttpResponse response = httpclient.execute(httpget); Assert.assertEquals(Status.OK.getRequestStatus(), response.getStatusLine().getStatusCode()); } @Test public void doNonRouterRequest() throws Exception { CloseableHttpClient httpclient = HttpClients.createDefault(); HttpGet httpget = new HttpGet("http://localhost:9090/test"); CloseableHttpResponse response = httpclient.execute(httpget); HttpEntity entity = response.getEntity(); String string = new String(readContents(entity), "UTF-8"); Assert.assertEquals("Return: java.lang.String.toString() -> ", string); response.close(); } @Test public void doExceptionRequest() throws Exception { CloseableHttpClient httpclient = HttpClients.createDefault(); HttpGet httpget = new HttpGet("http://localhost:9090/interface"); CloseableHttpResponse response = httpclient.execute(httpget); HttpEntity entity = response.getEntity(); String string = new String(readContents(entity), "UTF-8"); Assert.assertEquals("Error: java.lang.InstantiationException : org.nanohttpd.router.RouterNanoHTTPD$UriResponder", string); response.close(); } @Test public void doDeletedRoute() throws Exception { CloseableHttpClient httpclient = HttpClients.createDefault(); HttpGet httpget = new HttpGet("http://localhost:9090/toBeDeleted"); CloseableHttpResponse response = httpclient.execute(httpget); HttpEntity entity = response.getEntity(); String string = new String(readContents(entity), "UTF-8"); Assert.assertEquals("

Error 404: the requested page doesn't exist.

", string); response.close(); } @Test public void doUriSelection1() throws Exception { CloseableHttpClient httpclient = HttpClients.createDefault(); HttpGet httpget = new HttpGet("http://localhost:9090/user/help"); CloseableHttpResponse response = httpclient.execute(httpget); HttpEntity entity = response.getEntity(); String string = new String(readContents(entity), "UTF-8"); Assert.assertEquals("

Url: /user/help


no params in url


", string); response.close(); } @Test public void doStreamOfData() throws Exception { CloseableHttpClient httpclient = HttpClients.createDefault(); HttpGet httpget = new HttpGet("http://localhost:9090/stream"); CloseableHttpResponse response = httpclient.execute(httpget); HttpEntity entity = response.getEntity(); String string = new String(readContents(entity), "UTF-8"); Assert.assertEquals("a stream of data ;-)", string); response.close(); } @Test(expected = IllegalStateException.class) public void illegalMethod1() throws Exception { new AppNanolets.UserHandler().getData(); } @Test(expected = IllegalStateException.class) public void illegalMethod2() throws Exception { new RouterNanoHTTPD.GeneralHandler().getText(); } @Test(expected = IllegalStateException.class) public void illegalMethod3() throws Exception { new RouterNanoHTTPD.StaticPageHandler().getText(); } @Test(expected = IllegalStateException.class) public void illegalMethod4() throws Exception { new RouterNanoHTTPD.StaticPageHandler().getMimeType(); } @Test(expected = ClassCastException.class) public void checkIniParameter1() throws Exception { new RouterNanoHTTPD.UriResource("browse", 100, null, "init").initParameter(String.class); new RouterNanoHTTPD.UriResource("browse", 100, null, "init").initParameter(Integer.class); } @Test public void checkIniParameter2() throws Exception { Assert.assertEquals("init", new RouterNanoHTTPD.UriResource("browse", 100, null, "init").initParameter(String.class)); Assert.assertNull(new RouterNanoHTTPD.UriResource("browse", 100, null).initParameter(String.class)); } @Test public void doGeneralParams() throws Exception { CloseableHttpClient httpclient = HttpClients.createDefault(); HttpGet httpget = new HttpGet("http://localhost:9090/general/value1/value2?param3=value3¶m4=value4"); CloseableHttpResponse response = httpclient.execute(httpget); HttpEntity entity = response.getEntity(); String string = new String(readContents(entity), "UTF-8"); Assert.assertEquals("

Url: /general/value1/value2


Param 'param3' = value3

Param 'param4' = value4

", string); response.close(); } @Test public void doIndexHandler() throws Exception { CloseableHttpClient httpclient = HttpClients.createDefault(); HttpGet httpget = new HttpGet("http://localhost:9090/index.html"); CloseableHttpResponse response = httpclient.execute(httpget); HttpEntity entity = response.getEntity(); String string = new String(readContents(entity), "UTF-8"); Assert.assertEquals("

Hello world!

", string); response.close(); } @Test public void doMissingHandler() throws Exception { CloseableHttpClient httpclient = HttpClients.createDefault(); HttpGet httpget = new HttpGet("http://localhost:9090/photos/abc/def"); CloseableHttpResponse response = httpclient.execute(httpget); HttpEntity entity = response.getEntity(); String string = new String(readContents(entity), "UTF-8"); Assert.assertEquals("

The uri is mapped in the router, but no handler is specified.
Status: Not implemented!

", string); response.close(); } @Test public void uriToString() throws Exception { Assert.assertEquals(// "UrlResource{uri='photos/:customer_id/:photo_id', urlParts=[customer_id, photo_id]}",// new UriResource("/photos/:customer_id/:photo_id", 100, GeneralHandler.class).toString()); } @Test public void doOtherMethod() throws Exception { CloseableHttpClient httpclient = HttpClients.createDefault(); HttpTrace httphead = new HttpTrace("http://localhost:9090/index.html"); CloseableHttpResponse response = httpclient.execute(httphead); HttpEntity entity = response.getEntity(); String string = new String(readContents(entity), "UTF-8"); Assert.assertEquals("

Hello world!

", string); response.close(); } @Test public void normalize() throws Exception { Assert.assertNull(RouterNanoHTTPD.normalizeUri(null)); Assert.assertEquals("", RouterNanoHTTPD.normalizeUri("/")); Assert.assertEquals("xxx/yyy", RouterNanoHTTPD.normalizeUri("/xxx/yyy")); Assert.assertEquals("xxx/yyy", RouterNanoHTTPD.normalizeUri("/xxx/yyy/")); } private byte[] readContents(HttpEntity entity) throws IOException { InputStream instream = entity.getContent(); return readContents(instream); } private byte[] readContents(InputStream instream) throws IOException { byte[] bytes; ByteArrayOutputStream out = new ByteArrayOutputStream(); try { byte[] buffer = new byte[1024]; int count; while ((count = instream.read(buffer)) >= 0) { out.write(buffer, 0, count); } bytes = out.toByteArray(); } finally { instream.close(); } return bytes; } @Test public void staticFiles() throws Exception { CloseableHttpClient httpclient = HttpClients.createDefault(); HttpTrace httphead = new HttpTrace("http://localhost:9090/browse/blabla.html"); CloseableHttpResponse response = httpclient.execute(httphead); HttpEntity entity = response.getEntity(); String string = new String(readContents(entity), "UTF-8"); Assert.assertEquals("

just a page

", string); response.close(); httphead = new HttpTrace("http://localhost:9090/browse/dir/blabla.html"); response = httpclient.execute(httphead); entity = response.getEntity(); string = new String(readContents(entity), "UTF-8"); Assert.assertEquals("

just an other page

", string); response.close(); httphead = new HttpTrace("http://localhost:9090/browse/dir/nanohttpd_logo.png"); response = httpclient.execute(httphead); entity = response.getEntity(); Assert.assertEquals("image/png", entity.getContentType().getValue()); response.close(); httphead = new HttpTrace("http://localhost:9090/browse/dir/xxx.html"); response = httpclient.execute(httphead); entity = response.getEntity(); string = new String(readContents(entity), "UTF-8"); Assert.assertEquals("

Error 404: the requested page doesn't exist.

", string); response.close(); httphead = new HttpTrace("http://localhost:9090/browse/dir/"); response = httpclient.execute(httphead); entity = response.getEntity(); string = new String(readContents(entity), "UTF-8"); Assert.assertEquals("

just an index page

", string); response.close(); httphead = new HttpTrace("http://localhost:9090/browse/exception.html"); response = httpclient.execute(httphead); Assert.assertEquals(Status.REQUEST_TIMEOUT.getRequestStatus(), response.getStatusLine().getStatusCode()); entity = response.getEntity(); string = new String(readContents(entity), "UTF-8"); Assert.assertEquals("", string); response.close(); } @AfterClass public static void tearDown() throws Exception { stdIn.write("\n\n".getBytes()); serverStartThread.join(2000); Assert.assertFalse(serverStartThread.isAlive()); } @Test public void testGeneralHandlerGetStatus() { Assert.assertEquals("GeneralHandler#getStatus should return OK status", Status.OK, new RouterNanoHTTPD.GeneralHandler().getStatus()); } @Test public void testStaticPageHandlerGetStatus() { Assert.assertEquals("StaticPageHandler#getStatus should return OK status", Status.OK, new RouterNanoHTTPD.StaticPageHandler().getStatus()); } @Test public void testError404UriHandlerGetStatus() { Assert.assertEquals("Error404UriHandler#getStatus should return NOT_FOUND status", Status.NOT_FOUND, new RouterNanoHTTPD.Error404UriHandler().getStatus()); } @Test public void testError404UriHandlerGetMimeType() { Assert.assertEquals("Error404UriHandler mime type should be text/html", "text/html", new RouterNanoHTTPD.Error404UriHandler().getMimeType()); } @Test public void testNotImplementedHandlerGetStatus() { Assert.assertEquals("NotImplementedHandler#getStatus should return OK status", Status.OK, new RouterNanoHTTPD.NotImplementedHandler().getStatus()); } @Test public void testIndexHandlerGetStatus() { Assert.assertEquals("IndexHandler#getStatus should return OK status", Status.OK, new RouterNanoHTTPD.IndexHandler().getStatus()); } @Test public void testIndexHandlerGetMimeType() { Assert.assertEquals("IndexHandler mime type should be text/html", "text/html", new RouterNanoHTTPD.IndexHandler().getMimeType()); } @Test public void testNotImplementedHandlerGetMimeType() { Assert.assertEquals("NotImplementedHandler mime type should be text/html", "text/html", new RouterNanoHTTPD.NotImplementedHandler().getMimeType()); } @Test public void testBaseRoutePrioritizerAddNullRoute() { DefaultRoutePrioritizer routePrioritizer = new DefaultRoutePrioritizer(); routePrioritizer.addRoute(null, 100, null); Assert.assertEquals(0, routePrioritizer.getPrioritizedRoutes().size()); } @Test public void testInsertionOrderRoutePrioritizer() throws IOException { InsertionOrderRoutePrioritizer routePrioritizer = new InsertionOrderRoutePrioritizer(); Class handler1 = String.class; Class handler2 = Boolean.class; Class handler3 = Long.class; ArrayList> classes = new ArrayList>(); classes.add(handler1); classes.add(handler2); classes.add(handler3); routePrioritizer.addRoute("/user", 100, handler1); routePrioritizer.addRoute("/user", 100, handler2); routePrioritizer.addRoute("/user", 100, handler3); List prioritizedResources = new ArrayList(); prioritizedResources.addAll(routePrioritizer.getPrioritizedRoutes()); for (int i = 0; i < classes.size(); i++) { Class handler = classes.get(i); UriResource resource = prioritizedResources.get(i); InputStream inputStream = resource.process(null, null).getData(); byte[] bytes = new byte[inputStream.available()]; inputStream.read(bytes); String message = new String(bytes); Assert.assertTrue(message.contains(handler.getCanonicalName())); } } @Test public void testProvidedPriorityRoutePrioritizerNullUri() { ProvidedPriorityRoutePrioritizer routePrioritizer = new ProvidedPriorityRoutePrioritizer(); Assert.assertEquals(0, routePrioritizer.getPrioritizedRoutes().size()); routePrioritizer.addRoute(null, 100, null); Assert.assertEquals(0, routePrioritizer.getPrioritizedRoutes().size()); } @Test public void testProvidedPriorityRoutePrioritizerNullHandler() { ProvidedPriorityRoutePrioritizer routePrioritizer = new ProvidedPriorityRoutePrioritizer(); Assert.assertEquals(0, routePrioritizer.getPrioritizedRoutes().size()); routePrioritizer.addRoute("/help", 100, null); Assert.assertEquals(1, routePrioritizer.getPrioritizedRoutes().size()); } @Test public void testProvidedPriorityRoutePrioritizer() throws IOException { ProvidedPriorityRoutePrioritizer routePrioritizer = new ProvidedPriorityRoutePrioritizer(); Class handler1 = String.class; Class handler2 = Boolean.class; Class handler3 = Long.class; ArrayList> classes = new ArrayList>(); classes.add(handler2); classes.add(handler1); classes.add(handler3); routePrioritizer.addRoute("/user", 101, handler1); routePrioritizer.addRoute("/user", 100, handler2); routePrioritizer.addRoute("/user", 102, handler3); List prioritizedResources = new ArrayList(); prioritizedResources.addAll(routePrioritizer.getPrioritizedRoutes()); for (int i = 0; i < classes.size(); i++) { Class handler = classes.get(i); UriResource resource = prioritizedResources.get(i); InputStream inputStream = resource.process(null, null).getData(); byte[] bytes = new byte[inputStream.available()]; inputStream.read(bytes); String message = new String(bytes); Assert.assertTrue(message.contains(handler.getCanonicalName())); } } @Test public void testUriResourceComparator() { UriResource r1 = new UriResource("uri", null); r1.setPriority(100); Assert.assertTrue(r1.compareTo(null) >= 1); UriResource r2 = new UriResource("uri", null); r2.setPriority(100); Assert.assertEquals(0, r1.compareTo(r2)); r2.setPriority(99); Assert.assertTrue(r1.compareTo(r2) >= 1); r2.setPriority(101); Assert.assertTrue(r1.compareTo(r2) <= 1); } @Test public void testUriResourceMatch() { UriResource resource = new RouterNanoHTTPD.UriResource("browse", 100, null, "init"); Assert.assertNull("UriResource should not match incorrect URL, and thus, should not return a URI parameter map", resource.match("/xyz/pqr/")); Assert.assertNotNull("UriResource should match the correct URL, and thus, should return a URI parameter map", resource.match("browse")); } @Test public void testRoutePrioritizerRemoveRouteNoRouteMatches() { DefaultRoutePrioritizer prioritizer = new DefaultRoutePrioritizer(); prioritizer.addRoute("/world", 100, NotImplementedHandler.class); prioritizer.removeRoute("/hello"); Assert.assertEquals(1, prioritizer.getPrioritizedRoutes().size()); } @Test public void testHandlerSetters() throws Exception { final UriResponder notFoundHandler = new GeneralHandler() { }; final UriResponder notImplementedHandler = new GeneralHandler() { }; TestRouter router = new TestRouter(); RouterNanoHTTPD routerNanoHttpd = new RouterNanoHTTPD(9999); Field routerField = RouterNanoHTTPD.class.getDeclaredField("router"); routerField.setAccessible(true); routerField.set(routerNanoHttpd, router); routerNanoHttpd.setNotFoundHandler(notFoundHandler.getClass()); routerNanoHttpd.setNotImplementedHandler(notImplementedHandler.getClass()); Assert.assertEquals(notFoundHandler.getClass(), router.notFoundHandlerClass); Assert.assertEquals(notImplementedHandler.getClass(), router.notImplementedHandlerClass); } private static final class TestRouter extends UriRouter { private Class notFoundHandlerClass; private Class notImplementedHandlerClass; @Override public void setNotFoundHandler(Class handler) { notFoundHandlerClass = handler; } @Override public void setNotImplemented(Class handler) { notImplementedHandlerClass = handler; } } } ================================================ FILE: nanolets/src/test/resources/blabla.html ================================================

just a page

================================================ FILE: nanolets/src/test/resources/dir/blabla.html ================================================

just an other page

================================================ FILE: nanolets/src/test/resources/dir/index.htm ================================================

just an index page

================================================ FILE: nanolets/src/test/resources/exception.html ================================================ this will throw an io exception ================================================ FILE: pom.xml ================================================ 4.0.0 org.sonatype.oss oss-parent 7 org.nanohttpd nanohttpd-project 2.3.2-SNAPSHOT pom NanoHttpd-Project NanoHttpd is a light-weight HTTP server designed for embedding in other applications. http://www.nanohttpd.org scm:git:https://github.com/NanoHttpd/nanohttpd.git scm:git:https://github.com/NanoHttpd/nanohttpd.git https://github.com/NanoHttpd/nanohttpd HEAD nanohttpd sonatype-nexus-staging nanohttpd sonytype Maven 2 repository https://oss.sonatype.org/service/local/staging/deploy/maven2 sonatype-nexus-staging nanohttpd sonytype Snapshot Maven 2 repository https://oss.sonatype.org/content/repositories/snapshots private-deploy private Project Site scp://nanohttpd.org:33333/home/nanohttpd/public_html 2012 psh Paul Hawke http://www.linkedin.com/in/paulhawke Administrator Developer https://avatars2.githubusercontent.com/u/407647 elonen Jarno Elonen elonen [at] iki [dot] fi Administrator Developer https://avatars0.githubusercontent.com/u/2125766 ritchieGitHub Richard van Nieuwenhoven ritchie [at] gmx [dot] at Administrator Developer http://www.gravatar.com/avatar/9e2c2e7aa94335b72952a4b2d56bfc89.png LordFokas Diogo Duarte diogo.duarte [at] techie [dot] com Administrator Developer https://avatars1.githubusercontent.com/u/3021352 github https://github.com/NanoHttpd/nanohttpd/issues core samples webserver websocket markdown-plugin nanolets fileupload The BSD 3-Clause License http://opensource.org/licenses/BSD-3-Clause repo org.apache.maven.wagon wagon-ssh 2.7 org.apache.maven.plugins maven-compiler-plugin 3.3 1.7 1.7 org.apache.maven.plugins maven-javadoc-plugin 2.10.1 false false attach-javadocs jar org.apache.maven.plugins maven-release-plugin 2.5.1 forked-path org.tinyjee.dim doxia-include-macro 1.1 initialize-doxia-include-macro pre-site initialize org.apache.maven.plugins maven-site-plugin 3.4 false org.apache.maven.wagon wagon-ssh 2.7 org.tinyjee.dim doxia-include-macro 1.1 net.ju-n.maven.doxia doxia-module-markdown 1.0.0 org.codehaus.mojo license-maven-plugin 1.7 false first update-file-header update-project-license process-sources bsd_3 src/main/java src/test/java org.apache.maven.plugins maven-surefire-plugin 2.18.1 1 false org.jacoco jacoco-maven-plugin 0.7.4.201502262128 default-prepare-agent prepare-agent default-report prepare-package report default-check check BUNDLE LINE COVEREDRATIO ${minimal.coverage} org.apache.maven.plugins maven-project-info-reports-plugin 2.8 false false index dependencies project-team mailing-list cim issue-tracking license scm org.apache.maven.plugins maven-javadoc-plugin 2.10.1 false false javadoc-no-fork org.apache.maven.plugins maven-jxr-plugin 2.4 true ${nanohttpd.nonjavamodule} org.apache.maven.plugins maven-pmd-plugin 3.2 true UTF-8 1.7 false 50 org.codehaus.mojo taglist-maven-plugin 2.4 org.codehaus.mojo findbugs-maven-plugin 3.0.0 org.jacoco jacoco-maven-plugin 0.7.4.201502262128 junit junit 4.12 test release-sign-artifacts performRelease true org.apache.maven.plugins maven-gpg-plugin sign-artifacts verify sign java src/main/java org.apache.maven.plugins maven-checkstyle-plugin 2.15 ${project.basedir}/../src/main/checkstyle/nanohttpd-style.xml ${project.basedir}/../src/main/checkstyle/checkstyle-suppressions.xml UTF-8 true false true com.googlecode.maven-java-formatter-plugin maven-java-formatter-plugin 0.4 format ${project.basedir}/../src/main/formatter/formatter.xml LF org.apache.maven.plugins maven-checkstyle-plugin 2.15 checkstyle parent-build core/pom.xml org.jacoco jacoco-maven-plugin 0.7.4.201502262128 merge-report package merge ${project.basedir}/.. *.exec org.eluder.coveralls coveralls-maven-plugin 3.1.0 use 1.7 compiler /usr/lib/jvm/java-7-openjdk-amd64/bin/javac org.apache.maven.plugins maven-compiler-plugin 3.3 1.7 1.7 true true /usr/lib/jvm/java-7-openjdk-amd64/bin/javac 0.77 ================================================ FILE: relocation/nanohttpd/pom.xml ================================================ 4.0.0 com.nanohttpd nanohttpd-project 2.2.0 nanohttpd org.nanohttpd ================================================ FILE: relocation/nanohttpd/src/main/resources/relocated.txt ================================================ this project was relocated to the groupid org.nanohttpd starting with version 2.2.0 ================================================ FILE: relocation/nanohttpd-samples/pom.xml ================================================ 4.0.0 com.nanohttpd nanohttpd-project 2.2.0 nanohttpd-samples org.nanohttpd ================================================ FILE: relocation/nanohttpd-samples/src/main/resources/relocated.txt ================================================ this project was relocated to the groupid org.nanohttpd starting with version 2.2.0 ================================================ FILE: relocation/nanohttpd-webserver/pom.xml ================================================ 4.0.0 com.nanohttpd nanohttpd-project 2.2.0 nanohttpd-webserver org.nanohttpd ================================================ FILE: relocation/nanohttpd-webserver/src/main/resources/relocated.txt ================================================ this project was relocated to the groupid org.nanohttpd starting with version 2.2.0 ================================================ FILE: relocation/nanohttpd-webserver-markdown-plugin/pom.xml ================================================ 4.0.0 com.nanohttpd nanohttpd-project 2.2.0 nanohttpd-webserver-markdown-plugin org.nanohttpd ================================================ FILE: relocation/nanohttpd-webserver-markdown-plugin/src/main/resources/relocated.txt ================================================ this project was relocated to the groupid org.nanohttpd starting with version 2.2.0 ================================================ FILE: relocation/nanohttpd-websocket/pom.xml ================================================ 4.0.0 com.nanohttpd nanohttpd-project 2.2.0 nanohttpd-websocket org.nanohttpd ================================================ FILE: relocation/nanohttpd-websocket/src/main/resources/relocated.txt ================================================ this project was relocated to the groupid org.nanohttpd starting with version 2.2.0 ================================================ FILE: relocation/pom.xml ================================================ 4.0.0 org.sonatype.oss oss-parent 7 com.nanohttpd nanohttpd-project 2.2.0 pom sonatype-nexus-staging nanohttpd sonytype Maven 2 repository https://oss.sonatype.org/service/local/staging/deploy/maven2 org.nanohttpd org.apache.maven.plugins maven-gpg-plugin sign-artifacts verify sign nanohttpd nanohttpd-webserver nanohttpd-webserver-markdown-plugin nanohttpd-samples nanohttpd-websocket ================================================ FILE: samples/.gitignore ================================================ /.settings/ /LICENSE.txt ================================================ FILE: samples/build.gradle ================================================ description = 'NanoHttpd-Samples' dependencies { compile project(':nanohttpd') compile project(':nanohttpd-webserver') } task wrapper(type: Wrapper) { gradleVersion = "4.4.1" } ================================================ FILE: samples/pom.xml ================================================ 4.0.0 org.nanohttpd nanohttpd-project 2.3.2-SNAPSHOT nanohttpd-samples samples for nanohttpd jar NanoHttpd-Samples ${project.groupId} nanohttpd ${project.version} ${project.groupId} nanohttpd-webserver ${project.version} 0.0 ================================================ FILE: samples/src/main/java/org/nanohttpd/samples/http/DebugServer.java ================================================ package org.nanohttpd.samples.http; /* * #%L * NanoHttpd-Samples * %% * Copyright (C) 2012 - 2015 nanohttpd * %% * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * 3. Neither the name of the nanohttpd nor the names of its contributors * may be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. * #L% */ import java.util.HashMap; import java.util.List; import java.util.Map; import org.nanohttpd.protocols.http.IHTTPSession; import org.nanohttpd.protocols.http.NanoHTTPD; import org.nanohttpd.protocols.http.response.Response; import org.nanohttpd.util.ServerRunner; public class DebugServer extends NanoHTTPD { public static void main(String[] args) { ServerRunner.run(DebugServer.class); } public DebugServer() { super(8080); } private void listItem(StringBuilder sb, Map.Entry entry) { sb.append("
  • ").append(entry.getKey()).append(" = ").append(entry.getValue()).append("
  • "); } @Override public Response serve(IHTTPSession session) { Map> decodedQueryParameters = decodeParameters(session.getQueryParameterString()); StringBuilder sb = new StringBuilder(); sb.append(""); sb.append("Debug Server"); sb.append(""); sb.append("

    Debug Server

    "); sb.append("

    URI = ").append(String.valueOf(session.getUri())).append("
    "); sb.append("Method = ").append(String.valueOf(session.getMethod())).append("

    "); sb.append("

    Headers

    ").append(toString(session.getHeaders())).append("

    "); sb.append("

    Parms

    ").append(toString(session.getParms())).append("

    "); sb.append("

    Parms (multi values?)

    ").append(toString(decodedQueryParameters)).append("

    "); try { Map files = new HashMap(); session.parseBody(files); sb.append("

    Files

    ").append(toString(files)).append("

    "); } catch (Exception e) { e.printStackTrace(); } sb.append(""); sb.append(""); return Response.newFixedLengthResponse(sb.toString()); } private String toString(Map map) { if (map.size() == 0) { return ""; } return unsortedList(map); } private String unsortedList(Map map) { StringBuilder sb = new StringBuilder(); sb.append("
      "); for (Map.Entry entry : map.entrySet()) { listItem(sb, entry); } sb.append("
    "); return sb.toString(); } } ================================================ FILE: samples/src/main/java/org/nanohttpd/samples/http/HelloServer.java ================================================ package org.nanohttpd.samples.http; /* * #%L * NanoHttpd-Samples * %% * Copyright (C) 2012 - 2015 nanohttpd * %% * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * 3. Neither the name of the nanohttpd nor the names of its contributors * may be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. * #L% */ import java.util.Map; import java.util.logging.Logger; import org.nanohttpd.protocols.http.IHTTPSession; import org.nanohttpd.protocols.http.NanoHTTPD; import org.nanohttpd.protocols.http.request.Method; import org.nanohttpd.protocols.http.response.Response; import org.nanohttpd.util.ServerRunner; /** * An example of subclassing NanoHTTPD to make a custom HTTP server. */ public class HelloServer extends NanoHTTPD { /** * logger to log to. */ private static final Logger LOG = Logger.getLogger(HelloServer.class.getName()); public static void main(String[] args) { ServerRunner.run(HelloServer.class); } public HelloServer() { super(8080); } @Override public Response serve(IHTTPSession session) { Method method = session.getMethod(); String uri = session.getUri(); HelloServer.LOG.info(method + " '" + uri + "' "); String msg = "

    Hello server

    \n"; Map parms = session.getParms(); if (parms.get("username") == null) { msg += "
    \n" + "

    Your name:

    \n" + "
    \n"; } else { msg += "

    Hello, " + parms.get("username") + "!

    "; } msg += "\n"; return Response.newFixedLengthResponse(msg); } } ================================================ FILE: samples/src/main/java/org/nanohttpd/samples/tempfiles/TempFilesServer.java ================================================ package org.nanohttpd.samples.tempfiles; /* * #%L * NanoHttpd-Samples * %% * Copyright (C) 2012 - 2015 nanohttpd * %% * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * 3. Neither the name of the nanohttpd nor the names of its contributors * may be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. * #L% */ import java.io.File; import java.util.ArrayList; import java.util.List; import org.nanohttpd.protocols.http.tempfiles.DefaultTempFile; import org.nanohttpd.protocols.http.tempfiles.ITempFile; import org.nanohttpd.protocols.http.tempfiles.ITempFileManager; import org.nanohttpd.samples.http.DebugServer; import org.nanohttpd.util.IFactory; import org.nanohttpd.util.ServerRunner; /** * @author Paul S. Hawke (paul.hawke@gmail.com) On: 3/9/13 at 12:47 AM */ public class TempFilesServer extends DebugServer { private static class ExampleManager implements ITempFileManager { private final File tmpdir; private final List tempFiles; private ExampleManager() { this.tmpdir = new File(System.getProperty("java.io.tmpdir")); this.tempFiles = new ArrayList(); } @Override public void clear() { if (!this.tempFiles.isEmpty()) { System.out.println("Cleaning up:"); } for (ITempFile file : this.tempFiles) { try { System.out.println(" " + file.getName()); file.delete(); } catch (Exception ignored) { } } this.tempFiles.clear(); } @Override public ITempFile createTempFile(String filename_hint) throws Exception { DefaultTempFile tempFile = new DefaultTempFile(this.tmpdir); this.tempFiles.add(tempFile); System.out.println("Created tempFile: " + tempFile.getName()); return tempFile; } } private static class ExampleManagerFactory implements IFactory { @Override public ITempFileManager create() { return new ExampleManager(); } } public static void main(String[] args) { TempFilesServer server = new TempFilesServer(); server.setTempFileManagerFactory(new ExampleManagerFactory()); ServerRunner.executeInstance(server); } } ================================================ FILE: samples/src/site/site.xml ================================================ org.apache.maven.skins maven-fluido-skin 1.3.0 ../images/nanohttpd_logo.png ../images/nanohttpd_logo_text.png false true Nanohttpd/nanohttpd right black ================================================ FILE: settings.gradle ================================================ rootProject.name = 'nanohttpd-project' include ':nanohttpd' include ':nanohttpd-samples' include ':nanohttpd-webserver' include ':nanohttpd-websocket' include ':nanohttpd-webserver-markdown-plugin' include ':nanohttpd-nanolets' include ':nanohttpd-apache-fileupload' project(':nanohttpd').projectDir = "$rootDir/core" as File project(':nanohttpd-samples').projectDir = "$rootDir/samples" as File project(':nanohttpd-webserver').projectDir = "$rootDir/webserver" as File project(':nanohttpd-websocket').projectDir = "$rootDir/websocket" as File project(':nanohttpd-webserver-markdown-plugin').projectDir = "$rootDir/markdown-plugin" as File project(':nanohttpd-nanolets').projectDir = "$rootDir/nanolets" as File project(':nanohttpd-apache-fileupload').projectDir = "$rootDir/fileupload" as File ================================================ FILE: settings.xml ================================================ sonatype-nexus-staging ${env.CI_DEPLOY_USERNAME} ${env.CI_DEPLOY_PASSWORD} ================================================ FILE: src/main/checkstyle/checkstyle-suppressions.xml ================================================ ================================================ FILE: src/main/checkstyle/nanohttpd-style.xml ================================================ ================================================ FILE: src/main/formatter/formatter.xml ================================================ ================================================ FILE: src/site/markdown/index.md ================================================ ## NanoHTTPD – a tiny web server in Java *NanoHTTPD* is a light-weight HTTP server designed for embedding in other applications, released under a Modified BSD licence. It is being developed at Github and uses Apache Maven for builds & unit testing: * Build status: [![Build Status](https://api.travis-ci.org/NanoHttpd/nanohttpd.png)](https://travis-ci.org/NanoHttpd/nanohttpd) * Coverage Status: [![Coverage Status](https://coveralls.io/repos/NanoHttpd/nanohttpd/badge.svg)](https://coveralls.io/r/NanoHttpd/nanohttpd) * Current central released version: [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.nanohttpd/nanohttpd/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.nanohttpd/nanohttpd) ## Quickstart We'll create a custom HTTP server project using Maven for build/dep system. This tutorial assumes you are using a Unix variant and a shell. First, install Maven and Java SDK if not already installed. Then run: mvn compile mvn exec:java -pl webserver -Dexec.mainClass="fi.iki.elonen.SimpleWebServer" You should now have a HTTP file server running on . ### Custom web app Let's raise the bar and build a custom web application next: mvn archetype:generate -DgroupId=com.example -DartifactId=myHellopApp -DinteractiveMode=false cd myHellopApp Edit `pom.xml`, and add this between \: org.nanohttpd nanohttpd 2.2.0 Edit `src/main/java/com/example/App.java` and replace it with: ```java package com.example; import java.io.IOException; import java.util.Map; import fi.iki.elonen.NanoHTTPD; public class App extends NanoHTTPD { public App() throws IOException { super(8080); start(NanoHTTPD.SOCKET_READ_TIMEOUT, false); System.out.println("\nRunning! Point your browers to http://localhost:8080/ \n"); } public static void main(String[] args) { try { new App(); } catch (IOException ioe) { System.err.println("Couldn't start server:\n" + ioe); } } @Override public Response serve(IHTTPSession session) { String msg = "

    Hello server

    \n"; Map parms = session.getParms(); if (parms.get("username") == null) { msg += "
    \n

    Your name:

    \n" + "
    \n"; } else { msg += "

    Hello, " + parms.get("username") + "!

    "; } return newFixedLengthResponse(msg + "\n"); } } ``` Compile and run the server: mvn compile mvn exec:java -Dexec.mainClass="com.example.App" If it started ok, point your browser at and enjoy a web server that asks your name and replies with a greeting. ### Nanolets Nanolets are like sevlet's only that they have a extrem low profile. They offer an easy to use system for a more complex server application. This text has to be extrended with an example, so for now take a look at the unit tests for the usage. ## Status We are currently in the process of stabilizing NanoHttpd from the many pull requests and feature requests that were integrated over the last few months. The next release will come soon, and there will not be any more "intended" major changes before the next release. If you want to use the bleeding edge version, you can clone it from Github, or get it from sonatype.org (see "Maven dependencies / Living on the edge" below). ## Project structure NanoHTTPD project currently consist of four parts: * `/core` – Fully functional HTTP(s) server consisting of one (1) Java file, ready to be customized/inherited for your own project * `/samples` – Simple examples on how to customize NanoHTTPD. See *HelloServer.java* for a killer app that greets you enthusiastically! * `/websocket` – Websocket implementation, also in a single Java file. Depends on core. * `/webserver` – Standalone file server. Run & enjoy. A popular use seems to be serving files out off an Android device. * `/nanolets` – Standalone nano app server, giving a servlet like system to the implementor. * `/fileupload` – integration of the apache common file upload library. ## Features ### Core * Only one Java file, providing HTTP 1.1 support. * No fixed config files, logging, authorization etc. (Implement by yourself if you need them. Errors are passed to java.util.logging, though.) * Support for HTTPS (SSL) * Basic support for cookies * Supports parameter parsing of GET and POST methods. * Some built-in support for HEAD, POST and DELETE requests. You can easily implement/customize any HTTP method, though. * Supports file upload. Uses memory for small uploads, temp files for large ones. * Never caches anything. * Does not limit bandwidth, request time or simultaneous connections by default. * All header names are converted to lower case so they don't vary between browsers/clients. * Persistent connections (Connection "keep-alive") support allowing multiple requests to be served over a single socket connection. ### Websocket * Tested on Firefox, Chrome and IE. ### Webserver * Default code serves files and shows (prints on console) all HTTP parameters and headers. * Supports both dynamic content and file serving. * File server supports directory listing, `index.html` and `index.htm`. * File server supports partial content (streaming & continue download). * File server supports ETags. * File server does the 301 redirection trick for directories without `/`. * File server serves also very long files without memory overhead. * Contains a built-in list of most common MIME types. * Runtime extension support (extensions that serve particular MIME types) - example extension that serves Markdown formatted files. Simply including an extension JAR in the webserver classpath is enough for the extension to be loaded. * Simple [CORS](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing) support via `--cors` paramater * by default serves `Access-Control-Allow-Headers: origin,accept,content-type` * possibility to set `Access-Control-Allow-Headers` by setting System property: `AccessControlAllowHeader` * _example: _ `-DAccessControlAllowHeader=origin,accept,content-type,Authorization` * possible values: * `--cors`: activates CORS support, `Access-Control-Allow-Origin` will be set to `*` * `--cors=some_value`: `Access-Control-Allow-Origin` will be set to `some_value`. **_CORS argument examples_** * `--cors=http://appOne.company.com` * `--cors="http://appOne.company.com, http://appTwo.company.com"`: note the double quotes so that the 2 URLs are considered part of a single argument. ## Maven dependencies NanoHTTPD is a Maven based project and deployed to central. Most development environments have means to access the central repository. The coordinates to use in Maven are: org.nanohttpd nanohttpd 2.2.0 The coordinates for your development environment should correspond to these. When looking for an older version take care because we switched groupId from *com.nanohttpd* to *org.nanohttpd* in mid 2015. Next it depends what you are useing nanohttpd for, there are tree main usages. ## Gradle dependencies In gradle you can use nano http the same way because gradle accesses the same central repository: dependencies { runtime( [group: 'org.nanohttpd', name: 'nanohttpd', version: '2.2.0'], ) } Just replace the name with the artifact id of the module you want to use and gradle will find it for you. ### Develop your own specialized HTTP service For a specialized HTTP (HTTPS) service you can use the module with artifactId *nanohttpd*. org.nanohttpd nanohttpd 2.2.0VERSION Here you write your own subclass of *fi.iki.elonen.NanoHTTPD* to configure and to serve the requests. ### Develop a websocket based service For a specialized websocket service you can use the module with artifactId *nanohttpd-websocket*. org.nanohttpd nanohttpd-websocket 2.2.0 Here you write your own subclass of *fi.iki.elonen.NanoWebSocketServer* to configure and to serve the websocket requests. A small standard echo example is included as *fi.iki.elonen.samples.echo.DebugWebSocketServer*. You can use it as a starting point to implement your own services. ### Develop a custom HTTP file server For a more classic aproach, perhaps to just create a HTTP server serving mostly service files from your disk, you can use the module with artifactId *nanohttpd-webserver*. org.nanohttpd nanohttpd-webserver 2.2.0 The included class *fi.iki.elonen.SimpleWebServer* is intended to be used as a starting point for your own implementation but it also can be used as is. Staring the class as is will start a http server on port 8080 and publishing the current directory. ### Living on the edge The latest Github master version can be fetched through sonatype.org: nanohttpd org.nanohttpd XXXXX-SNAPSHOT ... sonatype-snapshots https://oss.sonatype.org/content/repositories/snapshots true ### generating an self signed ssl certificate Just a hint how to generate a certificate for localhost. keytool -genkey -keyalg RSA -alias selfsigned -keystore keystore.jks -storepass password -validity 360 -keysize 2048 -ext SAN=DNS:localhost,IP:127.0.0.1 -validity 9999 This will generate a keystore file named 'keystore.jks' with a self signed certificate for a host named localhost with the ip adress 127.0.0.1 . Now you can use: server.makeSecure(NanoHTTPD.makeSSLSocketFactory("/keystore.jks", "password".toCharArray()), null); Before you start the server to make Nanohttpd serve https connections, when you make sure 'keystore.jks' is in your classpath . ----- *Thank you to everyone who has reported bugs and suggested fixes.* ================================================ FILE: src/site/site.xml ================================================ org.apache.maven.skins maven-fluido-skin 1.3.0 images/nanohttpd_logo.png images/nanohttpd_logo_text.png false true Nanohttpd/nanohttpd right black ================================================ FILE: webserver/.gitignore ================================================ /.settings/ /LICENSE.txt ================================================ FILE: webserver/build.gradle ================================================ description = 'NanoHttpd-Webserver' dependencies { compile project(':nanohttpd') testCompile group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.4.1' } task wrapper(type: Wrapper) { gradleVersion = "4.4.1" } ================================================ FILE: webserver/pom.xml ================================================ 4.0.0 org.nanohttpd nanohttpd-project 2.3.2-SNAPSHOT nanohttpd-webserver jar NanoHttpd-Webserver nanohttpd-webserver can serve any local directory as a webserver using nanohttpd. ${project.groupId} nanohttpd ${project.version} org.apache.httpcomponents httpclient 4.4.1 test org.apache.maven.plugins maven-assembly-plugin 2.2-beta-5 jar-with-dependencies fi.iki.elonen.SimpleWebServer package single 0.75 ================================================ FILE: webserver/src/main/java/org/nanohttpd/webserver/InternalRewrite.java ================================================ package org.nanohttpd.webserver; /* * #%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.ByteArrayInputStream; import java.util.Map; import org.nanohttpd.protocols.http.NanoHTTPD; import org.nanohttpd.protocols.http.response.Response; import org.nanohttpd.protocols.http.response.Status; /** * @author Paul S. Hawke (paul.hawke@gmail.com) On: 9/15/13 at 2:52 PM */ public class InternalRewrite extends Response { private final String uri; private final Map headers; public InternalRewrite(Map headers, String uri) { super(Status.OK, NanoHTTPD.MIME_HTML, new ByteArrayInputStream(new byte[0]), 0); this.headers = headers; this.uri = uri; } public Map getHeaders() { return this.headers; } public String getUri() { return this.uri; } } ================================================ FILE: webserver/src/main/java/org/nanohttpd/webserver/SimpleWebServer.java ================================================ package org.nanohttpd.webserver; /* * #%L * NanoHttpd-Webserver * %% * Copyright (C) 2012 - 2015 nanohttpd * %% * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * 3. Neither the name of the nanohttpd nor the names of its contributors * may be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. * #L% */ import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FilenameFilter; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.ServiceLoader; import java.util.StringTokenizer; import org.nanohttpd.protocols.http.IHTTPSession; import org.nanohttpd.protocols.http.NanoHTTPD; import org.nanohttpd.protocols.http.request.Method; import org.nanohttpd.protocols.http.response.IStatus; import org.nanohttpd.protocols.http.response.Response; import org.nanohttpd.protocols.http.response.Status; import org.nanohttpd.util.ServerRunner; public class SimpleWebServer extends NanoHTTPD { /** * Default Index file names. */ @SuppressWarnings("serial") public static final List INDEX_FILE_NAMES = new ArrayList() { { add("index.html"); add("index.htm"); } }; /** * The distribution licence */ private static final String LICENCE; static { mimeTypes(); String text; try { InputStream stream = SimpleWebServer.class.getResourceAsStream("/LICENSE.txt"); ByteArrayOutputStream bytes = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int count; while ((count = stream.read(buffer)) >= 0) { bytes.write(buffer, 0, count); } text = bytes.toString("UTF-8"); } catch (Exception e) { text = "unknown"; } LICENCE = text; } private static Map mimeTypeHandlers = new HashMap(); /** * Starts as a standalone file server and waits for Enter. */ public static void main(String[] args) { // Defaults int port = 8080; String host = null; // bind to all interfaces by default List rootDirs = new ArrayList(); boolean quiet = false; String cors = null; Map options = new HashMap(); // Parse command-line, with short and long versions of the options. for (int i = 0; i < args.length; ++i) { if ("-h".equalsIgnoreCase(args[i]) || "--host".equalsIgnoreCase(args[i])) { host = args[i + 1]; } else if ("-p".equalsIgnoreCase(args[i]) || "--port".equalsIgnoreCase(args[i])) { port = Integer.parseInt(args[i + 1]); } else if ("-q".equalsIgnoreCase(args[i]) || "--quiet".equalsIgnoreCase(args[i])) { quiet = true; } else if ("-d".equalsIgnoreCase(args[i]) || "--dir".equalsIgnoreCase(args[i])) { rootDirs.add(new File(args[i + 1]).getAbsoluteFile()); } else if (args[i].startsWith("--cors")) { cors = "*"; int equalIdx = args[i].indexOf('='); if (equalIdx > 0) { cors = args[i].substring(equalIdx + 1); } } else if ("--licence".equalsIgnoreCase(args[i])) { System.out.println(SimpleWebServer.LICENCE + "\n"); } else if (args[i].startsWith("-X:")) { int dot = args[i].indexOf('='); if (dot > 0) { String name = args[i].substring(0, dot); String value = args[i].substring(dot + 1, args[i].length()); options.put(name, value); } } } if (rootDirs.isEmpty()) { rootDirs.add(new File(".").getAbsoluteFile()); } options.put("host", host); options.put("port", "" + port); options.put("quiet", String.valueOf(quiet)); StringBuilder sb = new StringBuilder(); for (File dir : rootDirs) { if (sb.length() > 0) { sb.append(":"); } try { sb.append(dir.getCanonicalPath()); } catch (IOException ignored) { } } options.put("home", sb.toString()); ServiceLoader serviceLoader = ServiceLoader.load(WebServerPluginInfo.class); for (WebServerPluginInfo info : serviceLoader) { String[] mimeTypes = info.getMimeTypes(); for (String mime : mimeTypes) { String[] indexFiles = info.getIndexFilesForMimeType(mime); if (!quiet) { System.out.print("# Found plugin for Mime type: \"" + mime + "\""); if (indexFiles != null) { System.out.print(" (serving index files: "); for (String indexFile : indexFiles) { System.out.print(indexFile + " "); } } System.out.println(")."); } registerPluginForMimeType(indexFiles, mime, info.getWebServerPlugin(mime), options); } } ServerRunner.executeInstance(new SimpleWebServer(host, port, rootDirs, quiet, cors)); } protected static void registerPluginForMimeType(String[] indexFiles, String mimeType, WebServerPlugin plugin, Map commandLineOptions) { if (mimeType == null || plugin == null) { return; } if (indexFiles != null) { for (String filename : indexFiles) { int dot = filename.lastIndexOf('.'); if (dot >= 0) { String extension = filename.substring(dot + 1).toLowerCase(); mimeTypes().put(extension, mimeType); } } SimpleWebServer.INDEX_FILE_NAMES.addAll(Arrays.asList(indexFiles)); } SimpleWebServer.mimeTypeHandlers.put(mimeType, plugin); plugin.initialize(commandLineOptions); } private final boolean quiet; private final String cors; protected List rootDirs; public SimpleWebServer(String host, int port, File wwwroot, boolean quiet, String cors) { this(host, port, Collections.singletonList(wwwroot), quiet, cors); } public SimpleWebServer(String host, int port, File wwwroot, boolean quiet) { this(host, port, Collections.singletonList(wwwroot), quiet, null); } public SimpleWebServer(String host, int port, List wwwroots, boolean quiet) { this(host, port, wwwroots, quiet, null); } public SimpleWebServer(String host, int port, List wwwroots, boolean quiet, String cors) { super(host, port); this.quiet = quiet; this.cors = cors; this.rootDirs = new ArrayList(wwwroots); init(); } private boolean canServeUri(String uri, File homeDir) { boolean canServeUri; File f = new File(homeDir, uri); canServeUri = f.exists(); if (!canServeUri) { WebServerPlugin plugin = SimpleWebServer.mimeTypeHandlers.get(getMimeTypeForFile(uri)); if (plugin != null) { canServeUri = plugin.canServeUri(uri, homeDir); } } return canServeUri; } /** * URL-encodes everything between "/"-characters. Encodes spaces as '%20' * instead of '+'. */ private String encodeUri(String uri) { String newUri = ""; StringTokenizer st = new StringTokenizer(uri, "/ ", true); while (st.hasMoreTokens()) { String tok = st.nextToken(); if ("/".equals(tok)) { newUri += "/"; } else if (" ".equals(tok)) { newUri += "%20"; } else { try { newUri += URLEncoder.encode(tok, "UTF-8"); } catch (UnsupportedEncodingException ignored) { } } } return newUri; } private String findIndexFileInDirectory(File directory) { for (String fileName : SimpleWebServer.INDEX_FILE_NAMES) { File indexFile = new File(directory, fileName); if (indexFile.isFile()) { return fileName; } } return null; } protected Response getForbiddenResponse(String s) { return Response.newFixedLengthResponse(Status.FORBIDDEN, NanoHTTPD.MIME_PLAINTEXT, "FORBIDDEN: " + s); } protected Response getInternalErrorResponse(String s) { return Response.newFixedLengthResponse(Status.INTERNAL_ERROR, NanoHTTPD.MIME_PLAINTEXT, "INTERNAL ERROR: " + s); } protected Response getNotFoundResponse() { return Response.newFixedLengthResponse(Status.NOT_FOUND, NanoHTTPD.MIME_PLAINTEXT, "Error 404, file not found."); } /** * Used to initialize and customize the server. */ public void init() { } protected String listDirectory(String uri, File f) { String heading = "Directory " + uri; StringBuilder msg = new StringBuilder("" + heading + "" + "

    " + heading + "

    "); String up = null; if (uri.length() > 1) { String u = uri.substring(0, uri.length() - 1); int slash = u.lastIndexOf('/'); if (slash >= 0 && slash < u.length()) { up = uri.substring(0, slash + 1); } } List files = Arrays.asList(f.list(new FilenameFilter() { @Override public boolean accept(File dir, String name) { return new File(dir, name).isFile(); } })); Collections.sort(files); List directories = Arrays.asList(f.list(new FilenameFilter() { @Override public boolean accept(File dir, String name) { return new File(dir, name).isDirectory(); } })); Collections.sort(directories); if (up != null || directories.size() + files.size() > 0) { msg.append("
      "); if (up != null || directories.size() > 0) { msg.append("
      "); if (up != null) { msg.append("
    • ..
    • "); } for (String directory : directories) { String dir = directory + "/"; msg.append("
    • ").append(dir).append("
    • "); } msg.append("
      "); } if (files.size() > 0) { msg.append("
      "); for (String file : files) { msg.append("
    • ").append(file).append(""); File curFile = new File(f, file); long len = curFile.length(); msg.append(" ("); if (len < 1024) { msg.append(len).append(" bytes"); } else if (len < 1024 * 1024) { msg.append(len / 1024).append(".").append(len % 1024 / 10 % 100).append(" KB"); } else { msg.append(len / (1024 * 1024)).append(".").append(len % (1024 * 1024) / 10000 % 100).append(" MB"); } msg.append(")
    • "); } msg.append("
      "); } msg.append("
    "); } msg.append(""); return msg.toString(); } public static Response newFixedLengthResponse(IStatus status, String mimeType, String message) { Response response = Response.newFixedLengthResponse(status, mimeType, message); response.addHeader("Accept-Ranges", "bytes"); return response; } private Response respond(Map headers, IHTTPSession session, String uri) { // First let's handle CORS OPTION query Response r; if (cors != null && Method.OPTIONS.equals(session.getMethod())) { r = Response.newFixedLengthResponse(Status.OK, MIME_PLAINTEXT, null, 0); } else { r = defaultRespond(headers, session, uri); } if (cors != null) { r = addCORSHeaders(headers, r, cors); } return r; } private Response defaultRespond(Map headers, IHTTPSession session, String uri) { // Remove URL arguments uri = uri.trim().replace(File.separatorChar, '/'); if (uri.indexOf('?') >= 0) { uri = uri.substring(0, uri.indexOf('?')); } // Prohibit getting out of current directory if (uri.contains("../")) { return getForbiddenResponse("Won't serve ../ for security reasons."); } boolean canServeUri = false; File homeDir = null; for (int i = 0; !canServeUri && i < this.rootDirs.size(); i++) { homeDir = this.rootDirs.get(i); canServeUri = canServeUri(uri, homeDir); } if (!canServeUri) { return getNotFoundResponse(); } // Browsers get confused without '/' after the directory, send a // redirect. File f = new File(homeDir, uri); if (f.isDirectory() && !uri.endsWith("/")) { uri += "/"; Response res = newFixedLengthResponse(Status.REDIRECT, NanoHTTPD.MIME_HTML, "Redirected: " + uri + ""); res.addHeader("Location", uri); return res; } if (f.isDirectory()) { // First look for index files (index.html, index.htm, etc) and if // none found, list the directory if readable. String indexFile = findIndexFileInDirectory(f); if (indexFile == null) { if (f.canRead()) { // No index file, list the directory if it is readable return newFixedLengthResponse(Status.OK, NanoHTTPD.MIME_HTML, listDirectory(uri, f)); } else { return getForbiddenResponse("No directory listing."); } } else { return respond(headers, session, uri + indexFile); } } String mimeTypeForFile = getMimeTypeForFile(uri); WebServerPlugin plugin = SimpleWebServer.mimeTypeHandlers.get(mimeTypeForFile); Response response = null; if (plugin != null && plugin.canServeUri(uri, homeDir)) { response = plugin.serveFile(uri, headers, session, f, mimeTypeForFile); if (response != null && response instanceof InternalRewrite) { InternalRewrite rewrite = (InternalRewrite) response; return respond(rewrite.getHeaders(), session, rewrite.getUri()); } } else { response = serveFile(uri, headers, f, mimeTypeForFile); } return response != null ? response : getNotFoundResponse(); } @Override public Response serve(IHTTPSession session) { Map header = session.getHeaders(); Map parms = session.getParms(); String uri = session.getUri(); if (!this.quiet) { System.out.println(session.getMethod() + " '" + uri + "' "); Iterator e = header.keySet().iterator(); while (e.hasNext()) { String value = e.next(); System.out.println(" HDR: '" + value + "' = '" + header.get(value) + "'"); } e = parms.keySet().iterator(); while (e.hasNext()) { String value = e.next(); System.out.println(" PRM: '" + value + "' = '" + parms.get(value) + "'"); } } for (File homeDir : this.rootDirs) { // Make sure we won't die of an exception later if (!homeDir.isDirectory()) { return getInternalErrorResponse("given path is not a directory (" + homeDir + ")."); } } return respond(Collections.unmodifiableMap(header), session, uri); } /** * Serves file from homeDir and its' subdirectories (only). Uses only URI, * ignores all headers and HTTP parameters. */ Response serveFile(String uri, Map header, File file, String mime) { Response res; try { // Calculate etag String etag = Integer.toHexString((file.getAbsolutePath() + file.lastModified() + "" + file.length()).hashCode()); // Support (simple) skipping: long startFrom = 0; long endAt = -1; String range = header.get("range"); if (range != null) { if (range.startsWith("bytes=")) { range = range.substring("bytes=".length()); int minus = range.indexOf('-'); try { if (minus > 0) { startFrom = Long.parseLong(range.substring(0, minus)); endAt = Long.parseLong(range.substring(minus + 1)); } } catch (NumberFormatException ignored) { } } } // get if-range header. If present, it must match etag or else we // should ignore the range request String ifRange = header.get("if-range"); boolean headerIfRangeMissingOrMatching = (ifRange == null || etag.equals(ifRange)); String ifNoneMatch = header.get("if-none-match"); boolean headerIfNoneMatchPresentAndMatching = ifNoneMatch != null && ("*".equals(ifNoneMatch) || ifNoneMatch.equals(etag)); // Change return code and add Content-Range header when skipping is // requested long fileLen = file.length(); if (headerIfRangeMissingOrMatching && range != null && startFrom >= 0 && startFrom < fileLen) { // range request that matches current etag // and the startFrom of the range is satisfiable if (headerIfNoneMatchPresentAndMatching) { // range request that matches current etag // and the startFrom of the range is satisfiable // would return range from file // respond with not-modified res = newFixedLengthResponse(Status.NOT_MODIFIED, mime, ""); res.addHeader("ETag", etag); } else { if (endAt < 0) { endAt = fileLen - 1; } long newLen = endAt - startFrom + 1; if (newLen < 0) { newLen = 0; } FileInputStream fis = new FileInputStream(file); fis.skip(startFrom); res = Response.newFixedLengthResponse(Status.PARTIAL_CONTENT, mime, fis, newLen); res.addHeader("Accept-Ranges", "bytes"); res.addHeader("Content-Length", "" + newLen); res.addHeader("Content-Range", "bytes " + startFrom + "-" + endAt + "/" + fileLen); res.addHeader("ETag", etag); } } else { if (headerIfRangeMissingOrMatching && range != null && startFrom >= fileLen) { // return the size of the file // 4xx responses are not trumped by if-none-match res = newFixedLengthResponse(Status.RANGE_NOT_SATISFIABLE, NanoHTTPD.MIME_PLAINTEXT, ""); res.addHeader("Content-Range", "bytes */" + fileLen); res.addHeader("ETag", etag); } else if (range == null && headerIfNoneMatchPresentAndMatching) { // full-file-fetch request // would return entire file // respond with not-modified res = newFixedLengthResponse(Status.NOT_MODIFIED, mime, ""); res.addHeader("ETag", etag); } else if (!headerIfRangeMissingOrMatching && headerIfNoneMatchPresentAndMatching) { // range request that doesn't match current etag // would return entire (different) file // respond with not-modified res = newFixedLengthResponse(Status.NOT_MODIFIED, mime, ""); res.addHeader("ETag", etag); } else { // supply the file res = newFixedFileResponse(file, mime); res.addHeader("Content-Length", "" + fileLen); res.addHeader("ETag", etag); } } } catch (IOException ioe) { res = getForbiddenResponse("Reading file failed."); } return res; } private Response newFixedFileResponse(File file, String mime) throws FileNotFoundException { Response res; res = Response.newFixedLengthResponse(Status.OK, mime, new FileInputStream(file), (int) file.length()); res.addHeader("Accept-Ranges", "bytes"); return res; } protected Response addCORSHeaders(Map queryHeaders, Response resp, String cors) { resp.addHeader("Access-Control-Allow-Origin", cors); resp.addHeader("Access-Control-Allow-Headers", calculateAllowHeaders(queryHeaders)); resp.addHeader("Access-Control-Allow-Credentials", "true"); resp.addHeader("Access-Control-Allow-Methods", ALLOWED_METHODS); resp.addHeader("Access-Control-Max-Age", "" + MAX_AGE); return resp; } private String calculateAllowHeaders(Map queryHeaders) { // here we should use the given asked headers // but NanoHttpd uses a Map whereas it is possible for requester to send // several time the same header // let's just use default values for this version return System.getProperty(ACCESS_CONTROL_ALLOW_HEADER_PROPERTY_NAME, DEFAULT_ALLOWED_HEADERS); } private final static String ALLOWED_METHODS = "GET, POST, PUT, DELETE, OPTIONS, HEAD"; private final static int MAX_AGE = 42 * 60 * 60; // explicitly relax visibility to package for tests purposes public final static String DEFAULT_ALLOWED_HEADERS = "origin,accept,content-type"; public final static String ACCESS_CONTROL_ALLOW_HEADER_PROPERTY_NAME = "AccessControlAllowHeader"; } ================================================ FILE: webserver/src/main/java/org/nanohttpd/webserver/WebServerPlugin.java ================================================ package org.nanohttpd.webserver; /* * #%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.File; import java.util.Map; import org.nanohttpd.protocols.http.IHTTPSession; import org.nanohttpd.protocols.http.response.Response; /** * @author Paul S. Hawke (paul.hawke@gmail.com) On: 9/14/13 at 8:09 AM */ public interface WebServerPlugin { boolean canServeUri(String uri, File rootDir); void initialize(Map commandLineOptions); Response serveFile(String uri, Map headers, IHTTPSession session, File file, String mimeType); } ================================================ FILE: webserver/src/main/java/org/nanohttpd/webserver/WebServerPluginInfo.java ================================================ package org.nanohttpd.webserver; /* * #%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% */ /** * @author Paul S. Hawke (paul.hawke@gmail.com) On: 9/14/13 at 8:09 AM */ public interface WebServerPluginInfo { String[] getIndexFilesForMimeType(String mime); String[] getMimeTypes(); WebServerPlugin getWebServerPlugin(String mimeType); } ================================================ FILE: webserver/src/site/site.xml ================================================ org.apache.maven.skins maven-fluido-skin 1.3.0 ../images/nanohttpd_logo.png ../images/nanohttpd_logo_text.png false true Nanohttpd/nanohttpd right black ================================================ FILE: webserver/src/test/java/org/nanohttpd/junit/webserver/AbstractTestHttpServer.java ================================================ package org.nanohttpd.junit.webserver; /* * #%L * NanoHttpd-Webserver * %% * Copyright (C) 2012 - 2015 nanohttpd * %% * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * 3. Neither the name of the nanohttpd nor the names of its contributors * may be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. * #L% */ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import org.apache.http.HttpEntity; /** * @author Matthieu Brouillard [matthieu@brouillard.fr] */ public class AbstractTestHttpServer { protected byte[] readContents(HttpEntity entity) throws IOException { InputStream instream = entity.getContent(); return readContents(instream); } protected byte[] readContents(InputStream instream) throws IOException { byte[] bytes; ByteArrayOutputStream out = new ByteArrayOutputStream(); try { byte[] buffer = new byte[1024]; int count; while ((count = instream.read(buffer)) >= 0) { out.write(buffer, 0, count); } bytes = out.toByteArray(); } finally { instream.close(); } return bytes; } } ================================================ FILE: webserver/src/test/java/org/nanohttpd/junit/webserver/DummyPlugin.java ================================================ package org.nanohttpd.junit.webserver; /* * #%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.ByteArrayInputStream; import java.io.File; import java.io.InputStream; import java.util.Map; import org.nanohttpd.protocols.http.IHTTPSession; import org.nanohttpd.protocols.http.response.Response; import org.nanohttpd.protocols.http.response.Status; import org.nanohttpd.webserver.InternalRewrite; import org.nanohttpd.webserver.WebServerPlugin; public class DummyPlugin implements WebServerPlugin { @Override public boolean canServeUri(String uri, File rootDir) { return true; } @Override public void initialize(Map commandLineOptions) { } @Override public Response serveFile(String uri, Map headers, IHTTPSession session, File file, String mimeType) { if (uri.contains("rewrite")) { return new InternalRewrite(headers, "/testdir/test.html"); } byte[] bytes = "".getBytes(); InputStream data = new ByteArrayInputStream(bytes); return Response.newFixedLengthResponse(Status.OK, "text/xml", data, bytes.length); } } ================================================ FILE: webserver/src/test/java/org/nanohttpd/junit/webserver/DummyPluginInfo.java ================================================ package org.nanohttpd.junit.webserver; import org.nanohttpd.webserver.WebServerPlugin; import org.nanohttpd.webserver.WebServerPluginInfo; /* * #%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% */ public class DummyPluginInfo implements WebServerPluginInfo { @Override public String[] getIndexFilesForMimeType(String mime) { return new String[]{ "index.xml" }; } @Override public String[] getMimeTypes() { return new String[]{ "text/xml" }; } @Override public WebServerPlugin getWebServerPlugin(String mimeType) { return new DummyPlugin(); } } ================================================ FILE: webserver/src/test/java/org/nanohttpd/junit/webserver/TestCorsHttpServer.java ================================================ package org.nanohttpd.junit.webserver; /* * #%L * NanoHttpd-Webserver * %% * Copyright (C) 2012 - 2016 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.PipedInputStream; import java.io.PipedOutputStream; import org.apache.http.HttpEntity; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpOptions; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.nanohttpd.webserver.SimpleWebServer; /** * @author Matthieu Brouillard [matthieu@brouillard.fr] */ public class TestCorsHttpServer extends AbstractTestHttpServer { private static PipedOutputStream stdIn; private static Thread serverStartThread; @BeforeClass public static void setUp() throws Exception { stdIn = new PipedOutputStream(); System.setIn(new PipedInputStream(stdIn)); serverStartThread = new Thread(new Runnable() { @Override public void run() { String[] args = { "--host", "localhost", "--port", "9090", "--dir", "src/test/resources", "--cors" }; SimpleWebServer.main(args); } }); serverStartThread.start(); // give the server some tine to start. Thread.sleep(100); } @AfterClass public static void tearDown() throws Exception { stdIn.write("\n\n".getBytes()); serverStartThread.join(2000); Assert.assertFalse(serverStartThread.isAlive()); } @Test public void doTestOption() throws Exception { CloseableHttpClient httpclient = HttpClients.createDefault(); HttpOptions httpOption = new HttpOptions("http://localhost:9090/xxx/yyy.html"); CloseableHttpResponse response = httpclient.execute(httpOption); Assert.assertEquals(200, response.getStatusLine().getStatusCode()); Assert.assertNotNull("Cors should have added a header: Access-Control-Allow-Origin", response.getLastHeader("Access-Control-Allow-Origin")); Assert.assertEquals("Cors should have added a header: Access-Control-Allow-Origin: *", "*", response.getLastHeader("Access-Control-Allow-Origin").getValue()); response.close(); } @Test public void doSomeBasicTest() throws Exception { CloseableHttpClient httpclient = HttpClients.createDefault(); HttpGet httpget = new HttpGet("http://localhost:9090/testdir/test.html"); CloseableHttpResponse response = httpclient.execute(httpget); HttpEntity entity = response.getEntity(); String string = new String(readContents(entity), "UTF-8"); Assert.assertNotNull("Cors should have added a header: Access-Control-Allow-Origin", response.getLastHeader("Access-Control-Allow-Origin")); Assert.assertEquals("Cors should have added a header: Access-Control-Allow-Origin: *", "*", response.getLastHeader("Access-Control-Allow-Origin").getValue()); Assert.assertEquals("\n\ndummy\n\n\n\t

    it works

    \n\n", string); response.close(); } @Test public void testAccessControlAllowHeaderUsesDefaultsWithoutSystemProperty() throws Exception { Assert.assertNull("no System " + SimpleWebServer.ACCESS_CONTROL_ALLOW_HEADER_PROPERTY_NAME + " shoudl be set", System.getProperty(SimpleWebServer.ACCESS_CONTROL_ALLOW_HEADER_PROPERTY_NAME)); CloseableHttpClient httpclient = HttpClients.createDefault(); HttpOptions httpOption = new HttpOptions("http://localhost:9090/xxx/yyy.html"); CloseableHttpResponse response = httpclient.execute(httpOption); Assert.assertEquals(200, response.getStatusLine().getStatusCode()); Assert.assertEquals("Cors should have added a header: Access-Control-Allow-Headers: " + SimpleWebServer.DEFAULT_ALLOWED_HEADERS, SimpleWebServer.DEFAULT_ALLOWED_HEADERS, response.getLastHeader("Access-Control-Allow-Headers").getValue()); response.close(); } @Test public void testAccessControlAllowHeaderUsesSystemPropertyWhenSet() throws Exception { Assert.assertNull("no System " + SimpleWebServer.ACCESS_CONTROL_ALLOW_HEADER_PROPERTY_NAME + " shoudl be set", System.getProperty(SimpleWebServer.ACCESS_CONTROL_ALLOW_HEADER_PROPERTY_NAME)); final String expectedValue = "origin"; System.setProperty(SimpleWebServer.ACCESS_CONTROL_ALLOW_HEADER_PROPERTY_NAME, expectedValue); try { CloseableHttpClient httpclient = HttpClients.createDefault(); HttpOptions httpOption = new HttpOptions("http://localhost:9090/xxx/yyy.html"); CloseableHttpResponse response = httpclient.execute(httpOption); Assert.assertEquals(200, response.getStatusLine().getStatusCode()); Assert.assertEquals("Cors should have added a header: Access-Control-Allow-Headers: " + expectedValue, expectedValue, response.getLastHeader("Access-Control-Allow-Headers").getValue()); response.close(); } finally { System.clearProperty(SimpleWebServer.ACCESS_CONTROL_ALLOW_HEADER_PROPERTY_NAME); } } } ================================================ FILE: webserver/src/test/java/org/nanohttpd/junit/webserver/TestCorsHttpServerWithSingleOrigin.java ================================================ package org.nanohttpd.junit.webserver; /* * #%L * NanoHttpd-Webserver * %% * Copyright (C) 2012 - 2016 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.PipedInputStream; import java.io.PipedOutputStream; import org.apache.http.HttpEntity; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpOptions; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.nanohttpd.webserver.SimpleWebServer; /** * @author Matthieu Brouillard [matthieu@brouillard.fr] */ public class TestCorsHttpServerWithSingleOrigin extends AbstractTestHttpServer { private static PipedOutputStream stdIn; private static Thread serverStartThread; @BeforeClass public static void setUp() throws Exception { stdIn = new PipedOutputStream(); System.setIn(new PipedInputStream(stdIn)); serverStartThread = new Thread(new Runnable() { @Override public void run() { String[] args = { "--host", "localhost", "--port", "9090", "--dir", "src/test/resources", "--cors=http://localhost:9090" }; SimpleWebServer.main(args); } }); serverStartThread.start(); // give the server some tine to start. Thread.sleep(100); } @AfterClass public static void tearDown() throws Exception { stdIn.write("\n\n".getBytes()); serverStartThread.join(2000); Assert.assertFalse(serverStartThread.isAlive()); } @Test public void doTestOption() throws Exception { CloseableHttpClient httpclient = HttpClients.createDefault(); HttpOptions httpOption = new HttpOptions("http://localhost:9090/xxx/yyy.html"); CloseableHttpResponse response = httpclient.execute(httpOption); Assert.assertEquals(200, response.getStatusLine().getStatusCode()); Assert.assertNotNull("Cors should have added a header: Access-Control-Allow-Origin", response.getLastHeader("Access-Control-Allow-Origin")); Assert.assertEquals("Cors should have added a header: Access-Control-Allow-Origin: http://localhost:9090", "http://localhost:9090", response.getLastHeader("Access-Control-Allow-Origin").getValue()); response.close(); } @Test public void doSomeBasicTest() throws Exception { CloseableHttpClient httpclient = HttpClients.createDefault(); HttpGet httpget = new HttpGet("http://localhost:9090/testdir/test.html"); CloseableHttpResponse response = httpclient.execute(httpget); HttpEntity entity = response.getEntity(); String string = new String(readContents(entity), "UTF-8"); Assert.assertNotNull("Cors should have added a header: Access-Control-Allow-Origin", response.getLastHeader("Access-Control-Allow-Origin")); Assert.assertEquals("Cors should have added a header: Access-Control-Allow-Origin: http://localhost:9090", "http://localhost:9090", response.getLastHeader("Access-Control-Allow-Origin").getValue()); Assert.assertEquals("\n\ndummy\n\n\n\t

    it works

    \n\n", string); response.close(); } } ================================================ FILE: webserver/src/test/java/org/nanohttpd/junit/webserver/TestHttpServer.java ================================================ package org.nanohttpd.junit.webserver; /* * #%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 static org.hamcrest.CoreMatchers.allOf; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.not; import java.io.FileInputStream; import java.io.IOException; import java.io.PipedInputStream; import java.io.PipedOutputStream; import java.io.UnsupportedEncodingException; import org.apache.http.HttpEntity; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.nanohttpd.webserver.SimpleWebServer; public class TestHttpServer extends AbstractTestHttpServer { private static PipedOutputStream stdIn; private static Thread serverStartThread; @BeforeClass public static void setUp() throws Exception { stdIn = new PipedOutputStream(); System.setIn(new PipedInputStream(stdIn)); serverStartThread = new Thread(new Runnable() { @Override public void run() { String[] args = { "--host", "localhost", "--port", "9090", "--dir", "src/test/resources" }; SimpleWebServer.main(args); } }); serverStartThread.start(); // give the server some tine to start. Thread.sleep(100); } @AfterClass public static void tearDown() throws Exception { stdIn.write("\n\n".getBytes()); serverStartThread.join(2000); Assert.assertFalse(serverStartThread.isAlive()); } @Test public void doTest404() throws Exception { CloseableHttpClient httpclient = HttpClients.createDefault(); HttpGet httpget = new HttpGet("http://localhost:9090/xxx/yyy.html"); CloseableHttpResponse response = httpclient.execute(httpget); Assert.assertEquals(404, response.getStatusLine().getStatusCode()); response.close(); } @Test public void doPlugin() throws Exception { CloseableHttpClient httpclient = HttpClients.createDefault(); HttpGet httpget = new HttpGet("http://localhost:9090/index.xml"); CloseableHttpResponse response = httpclient.execute(httpget); String string = new String(readContents(response.getEntity()), "UTF-8"); Assert.assertEquals("", string); response.close(); httpget = new HttpGet("http://localhost:9090/testdir/testdir/different.xml"); response = httpclient.execute(httpget); string = new String(readContents(response.getEntity()), "UTF-8"); Assert.assertEquals("", string); response.close(); } @Test public void doSomeBasicTest() throws Exception { CloseableHttpClient httpclient = HttpClients.createDefault(); HttpGet httpget = new HttpGet("http://localhost:9090/testdir/test.html"); CloseableHttpResponse response = httpclient.execute(httpget); HttpEntity entity = response.getEntity(); String string = new String(readContents(entity), "UTF-8"); Assert.assertEquals("\n\ndummy\n\n\n\t

    it works

    \n\n", string); response.close(); httpget = new HttpGet("http://localhost:9090/"); response = httpclient.execute(httpget); entity = response.getEntity(); string = new String(readContents(entity), "UTF-8"); Assert.assertTrue(string.indexOf("testdir") > 0); response.close(); httpget = new HttpGet("http://localhost:9090/testdir"); response = httpclient.execute(httpget); entity = response.getEntity(); string = new String(readContents(entity), "UTF-8"); Assert.assertTrue(string.indexOf("test.html") > 0); response.close(); httpget = new HttpGet("http://localhost:9090/testdir/testpdf.pdf"); response = httpclient.execute(httpget); entity = response.getEntity(); byte[] actual = readContents(entity); byte[] expected = readContents(new FileInputStream("src/test/resources/testdir/testpdf.pdf")); Assert.assertArrayEquals(expected, actual); response.close(); } @Test public void doArgumentTest() throws InterruptedException, UnsupportedEncodingException, IOException { final String testPort = "9458"; Thread testServer = new Thread(new Runnable() { @Override public void run() { String[] args = { "-h", "localhost", "-p", testPort, "-d", "src/test/resources" }; SimpleWebServer.main(args); } }); testServer.start(); Thread.sleep(200); HttpGet httpget = new HttpGet("http://localhost:" + testPort + "/"); CloseableHttpClient httpclient = HttpClients.createDefault(); CloseableHttpResponse response = null; try { response = httpclient.execute(httpget); HttpEntity entity = response.getEntity(); String str = new String(readContents(entity), "UTF-8"); Assert.assertTrue("The response entity didn't contain the string 'testdir'", str.indexOf("testdir") >= 0); } finally { if (response != null) response.close(); } } @Test public void testURLContainsParentDirectory() throws ClientProtocolException, IOException { CloseableHttpResponse response = null; try { CloseableHttpClient httpClient = HttpClients.createDefault(); HttpGet httpGet = new HttpGet("http://localhost:9090/../test.html"); response = httpClient.execute(httpGet); Assert.assertEquals("The response status should be 403(Forbidden), " + "since the server won't serve requests with '../' due to security reasons", 403, response .getStatusLine().getStatusCode()); } finally { if (response != null) { response.close(); } } } @Test public void testIndexFileIsShownWhenURLEndsWithDirectory() throws ClientProtocolException, IOException { CloseableHttpResponse response = null; try { CloseableHttpClient httpClient = HttpClients.createDefault(); HttpGet httpGet = new HttpGet("http://localhost:9090/testdir/testdir"); response = httpClient.execute(httpGet); HttpEntity entity = response.getEntity(); String responseString = new String(readContents(entity), "UTF-8"); Assert.assertThat("When the URL ends with a directory, and if an index.html file is present in that directory," + " the server should respond with that file", responseString, containsString("Simple index file")); } finally { if (response != null) { response.close(); } } } @Test public void testPluginInternalRewrite() throws ClientProtocolException, IOException { CloseableHttpResponse response = null; try { CloseableHttpClient httpClient = HttpClients.createDefault(); HttpGet httpGet = new HttpGet("http://localhost:9090/rewrite/index.xml"); response = httpClient.execute(httpGet); HttpEntity entity = response.getEntity(); String responseString = new String(readContents(entity), "UTF-8"); Assert.assertThat("If a plugin returns an InternalRewrite from the serveFile method, the rewritten request should be served", responseString, allOf(containsString("dummy"), containsString("it works"))); } finally { if (response != null) { response.close(); } } } @Test public void testRangeHeaderWithStartPositionOnly() throws ClientProtocolException, IOException { CloseableHttpResponse response = null; try { HttpGet httpGet = new HttpGet("http://localhost:9090/testdir/test.html"); httpGet.addHeader("range", "bytes=10-"); CloseableHttpClient httpClient = HttpClients.createDefault(); response = httpClient.execute(httpGet); HttpEntity entity = response.getEntity(); String responseString = new String(readContents(entity), "UTF-8"); Assert.assertThat("The data from the beginning of the file should have been skipped as specified in the 'range' header", responseString, not(containsString(""))); Assert.assertThat("The response should contain the data from the end of the file since end position was not given in the 'range' header", responseString, containsString("")); Assert.assertEquals("The content length should be the length starting from the requested byte", "74", response.getHeaders("Content-Length")[0].getValue()); Assert.assertEquals("The 'Content-Range' header should contain the correct lengths and offsets based on the range served", "bytes 10-83/84", response.getHeaders("Content-Range")[0].getValue()); Assert.assertEquals("Response status for a successful range request should be PARTIAL_CONTENT(206)", 206, response.getStatusLine().getStatusCode()); } finally { if (response != null) { response.close(); } } } @Test public void testRangeStartGreaterThanFileLength() throws ClientProtocolException, IOException { CloseableHttpResponse response = null; try { HttpGet httpGet = new HttpGet("http://localhost:9090/testdir/test.html"); httpGet.addHeader("range", "bytes=1000-"); CloseableHttpClient httpClient = HttpClients.createDefault(); response = httpClient.execute(httpGet); Assert.assertEquals("Response status for a request with 'range' header value which exceeds file length should be RANGE_NOT_SATISFIABLE(416)", 416, response .getStatusLine().getStatusCode()); Assert.assertEquals("The 'Content-Range' header should contain the correct lengths and offsets based on the range served", "bytes */84", response.getHeaders("Content-Range")[0].getValue()); } finally { if (response != null) { response.close(); } } } @Test public void testRangeHeaderWithStartAndEndPosition() throws ClientProtocolException, IOException { CloseableHttpResponse response = null; try { HttpGet httpGet = new HttpGet("http://localhost:9090/testdir/test.html"); httpGet.addHeader("range", "bytes=10-40"); CloseableHttpClient httpClient = HttpClients.createDefault(); response = httpClient.execute(httpGet); HttpEntity entity = response.getEntity(); String responseString = new String(readContents(entity), "UTF-8"); Assert.assertThat("The data from the beginning of the file should have been skipped as specified in the 'range' header", responseString, not(containsString(""))); Assert.assertThat("The data from the end of the file should have been skipped as specified in the 'range' header", responseString, not(containsString(""))); Assert.assertEquals("The 'Content-Length' should be the length from the requested start position to end position", "31", response.getHeaders("Content-Length")[0].getValue()); Assert.assertEquals("The 'Contnet-Range' header should contain the correct lengths and offsets based on the range served", "bytes 10-40/84", response.getHeaders("Content-Range")[0].getValue()); Assert.assertEquals("Response status for a successful request with 'range' header should be PARTIAL_CONTENT(206)", 206, response.getStatusLine().getStatusCode()); } finally { if (response != null) { response.close(); } } } @Test public void testIfNoneMatchHeader() throws ClientProtocolException, IOException { CloseableHttpResponse response = null; try { HttpGet httpGet = new HttpGet("http://localhost:9090/testdir/test.html"); httpGet.addHeader("if-none-match", "*"); CloseableHttpClient httpClient = HttpClients.createDefault(); response = httpClient.execute(httpGet); Assert.assertEquals("The response status to a reqeuest with 'if-non-match=*' header should be NOT_MODIFIED(304), if the file exists", 304, response .getStatusLine().getStatusCode()); } finally { if (response != null) { response.close(); } } } @Test public void testRangeHeaderAndIfNoneMatchHeader() throws ClientProtocolException, IOException { CloseableHttpResponse response = null; try { HttpGet httpGet = new HttpGet("http://localhost:9090/testdir/test.html"); httpGet.addHeader("range", "bytes=10-20"); httpGet.addHeader("if-none-match", "*"); CloseableHttpClient httpClient = HttpClients.createDefault(); response = httpClient.execute(httpGet); Assert.assertEquals("The response status to a reqeuest with 'if-non-match=*' header and 'range' header should be NOT_MODIFIED(304)," + " if the file exists, because 'if-non-match' header should be given priority", 304, response.getStatusLine().getStatusCode()); } finally { if (response != null) { response.close(); } } } } ================================================ FILE: webserver/src/test/resources/META-INF/services/org.nanohttpd.webserver.WebServerPluginInfo ================================================ org.nanohttpd.junit.webserver.DummyPluginInfo ================================================ FILE: webserver/src/test/resources/testdir/test.html ================================================ dummy

    it works

    ================================================ FILE: webserver/src/test/resources/testdir/testdir/different.xml ================================================ This sould not show up ;-) ================================================ FILE: webserver/src/test/resources/testdir/testdir/index.html ================================================ Simple index file ================================================ FILE: websocket/.gitignore ================================================ /.settings/ /LICENSE.txt ================================================ FILE: websocket/build.gradle ================================================ description = 'NanoHttpd-Websocket' dependencies { compile project(':nanohttpd') testCompile group: 'org.mockito', name: 'mockito-all', version: '1.9.5' testCompile group: 'org.eclipse.jetty.websocket', name: 'websocket-client', version: '9.3.0.M2' } task wrapper(type: Wrapper) { gradleVersion = "4.4.1" } ================================================ FILE: websocket/pom.xml ================================================ 4.0.0 org.nanohttpd nanohttpd-project 2.3.2-SNAPSHOT nanohttpd-websocket jar NanoHttpd-Websocket nanohttpd-websocket is a very low profile websocket server based on nanohttpd. org.apache.maven.plugins maven-jar-plugin default-jar **/samples/** echo-jar package jar echo true fi.iki.elonen.samples.echo.EchoSocketSample ${project.groupId} nanohttpd ${project.version} org.mockito mockito-all 1.9.5 test org.eclipse.jetty.websocket websocket-client 9.3.0.M2 test 0.67 ================================================ FILE: websocket/src/main/java/org/nanohttpd/protocols/websockets/CloseCode.java ================================================ package org.nanohttpd.protocols.websockets; /* * #%L * NanoHttpd-Websocket * %% * Copyright (C) 2012 - 2016 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% */ public enum CloseCode { NormalClosure(1000), GoingAway(1001), ProtocolError(1002), UnsupportedData(1003), NoStatusRcvd(1005), AbnormalClosure(1006), InvalidFramePayloadData(1007), PolicyViolation(1008), MessageTooBig(1009), MandatoryExt(1010), InternalServerError(1011), TLSHandshake(1015); public static CloseCode find(int value) { for (CloseCode code : values()) { if (code.getValue() == value) { return code; } } return null; } private final int code; private CloseCode(int code) { this.code = code; } public int getValue() { return this.code; } } ================================================ FILE: websocket/src/main/java/org/nanohttpd/protocols/websockets/CloseFrame.java ================================================ package org.nanohttpd.protocols.websockets; /* * #%L * NanoHttpd-Websocket * %% * Copyright (C) 2012 - 2016 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.nio.charset.CharacterCodingException; public class CloseFrame extends WebSocketFrame { private static byte[] generatePayload(CloseCode code, String closeReason) throws CharacterCodingException { if (code != null) { byte[] reasonBytes = text2Binary(closeReason); byte[] payload = new byte[reasonBytes.length + 2]; payload[0] = (byte) (code.getValue() >> 8 & 0xFF); payload[1] = (byte) (code.getValue() & 0xFF); System.arraycopy(reasonBytes, 0, payload, 2, reasonBytes.length); return payload; } else { return new byte[0]; } } private CloseCode _closeCode; private String _closeReason; public CloseFrame(CloseCode code, String closeReason) throws CharacterCodingException { super(OpCode.Close, true, generatePayload(code, closeReason)); } public CloseFrame(WebSocketFrame wrap) throws CharacterCodingException { super(wrap); assert wrap.getOpCode() == OpCode.Close; if (wrap.getBinaryPayload().length >= 2) { this._closeCode = CloseCode.find((wrap.getBinaryPayload()[0] & 0xFF) << 8 | wrap.getBinaryPayload()[1] & 0xFF); this._closeReason = binary2Text(getBinaryPayload(), 2, getBinaryPayload().length - 2); } } public CloseCode getCloseCode() { return this._closeCode; } public String getCloseReason() { return this._closeReason; } } ================================================ FILE: websocket/src/main/java/org/nanohttpd/protocols/websockets/NanoWSD.java ================================================ package org.nanohttpd.protocols.websockets; /* * #%L * NanoHttpd-Websocket * %% * Copyright (C) 2012 - 2016 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.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Map; import java.util.logging.Logger; import org.nanohttpd.protocols.http.IHTTPSession; import org.nanohttpd.protocols.http.NanoHTTPD; import org.nanohttpd.protocols.http.response.Response; import org.nanohttpd.protocols.http.response.Status; import org.nanohttpd.util.IHandler; public abstract class NanoWSD extends NanoHTTPD { /** * logger to log to. */ public static final Logger LOG = Logger.getLogger(NanoWSD.class.getName()); public static final String HEADER_UPGRADE = "upgrade"; public static final String HEADER_UPGRADE_VALUE = "websocket"; public static final String HEADER_CONNECTION = "connection"; public static final String HEADER_CONNECTION_VALUE = "Upgrade"; public static final String HEADER_WEBSOCKET_VERSION = "sec-websocket-version"; public static final String HEADER_WEBSOCKET_VERSION_VALUE = "13"; public static final String HEADER_WEBSOCKET_KEY = "sec-websocket-key"; public static final String HEADER_WEBSOCKET_ACCEPT = "sec-websocket-accept"; public static final String HEADER_WEBSOCKET_PROTOCOL = "sec-websocket-protocol"; private final static String WEBSOCKET_KEY_MAGIC = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; private final static char[] ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray(); /** * Translates the specified byte array into Base64 string. *

    * Android has android.util.Base64, sun has sun.misc.Base64Encoder, Java 8 * hast java.util.Base64, I have this from stackoverflow: * http://stackoverflow.com/a/4265472 *

    * * @param buf * the byte array (not null) * @return the translated Base64 string (not null) */ private static String encodeBase64(byte[] buf) { int size = buf.length; char[] ar = new char[(size + 2) / 3 * 4]; int a = 0; int i = 0; while (i < size) { byte b0 = buf[i++]; byte b1 = i < size ? buf[i++] : 0; byte b2 = i < size ? buf[i++] : 0; int mask = 0x3F; ar[a++] = NanoWSD.ALPHABET[b0 >> 2 & mask]; ar[a++] = NanoWSD.ALPHABET[(b0 << 4 | (b1 & 0xFF) >> 4) & mask]; ar[a++] = NanoWSD.ALPHABET[(b1 << 2 | (b2 & 0xFF) >> 6) & mask]; ar[a++] = NanoWSD.ALPHABET[b2 & mask]; } switch (size % 3) { case 1: ar[--a] = '='; case 2: ar[--a] = '='; } return new String(ar); } public static String makeAcceptKey(String key) throws NoSuchAlgorithmException { MessageDigest md = MessageDigest.getInstance("SHA-1"); String text = key + NanoWSD.WEBSOCKET_KEY_MAGIC; md.update(text.getBytes(), 0, text.length()); byte[] sha1hash = md.digest(); return encodeBase64(sha1hash); } protected final class Interceptor implements IHandler { public Interceptor() { } @Override public Response handle(IHTTPSession input) { return handleWebSocket(input); } } public NanoWSD(int port) { super(port); addHTTPInterceptor(new Interceptor()); } public NanoWSD(String hostname, int port) { super(hostname, port); addHTTPInterceptor(new Interceptor()); } private boolean isWebSocketConnectionHeader(Map headers) { String connection = headers.get(NanoWSD.HEADER_CONNECTION); return connection != null && connection.toLowerCase().contains(NanoWSD.HEADER_CONNECTION_VALUE.toLowerCase()); } protected boolean isWebsocketRequested(IHTTPSession session) { Map headers = session.getHeaders(); String upgrade = headers.get(NanoWSD.HEADER_UPGRADE); boolean isCorrectConnection = isWebSocketConnectionHeader(headers); boolean isUpgrade = NanoWSD.HEADER_UPGRADE_VALUE.equalsIgnoreCase(upgrade); return isUpgrade && isCorrectConnection; } // --------------------------------Listener-------------------------------- protected abstract WebSocket openWebSocket(IHTTPSession handshake); public Response handleWebSocket(final IHTTPSession session) { Map headers = session.getHeaders(); if (isWebsocketRequested(session)) { if (!NanoWSD.HEADER_WEBSOCKET_VERSION_VALUE.equalsIgnoreCase(headers.get(NanoWSD.HEADER_WEBSOCKET_VERSION))) { return Response.newFixedLengthResponse(Status.BAD_REQUEST, NanoHTTPD.MIME_PLAINTEXT, "Invalid Websocket-Version " + headers.get(NanoWSD.HEADER_WEBSOCKET_VERSION)); } if (!headers.containsKey(NanoWSD.HEADER_WEBSOCKET_KEY)) { return Response.newFixedLengthResponse(Status.BAD_REQUEST, NanoHTTPD.MIME_PLAINTEXT, "Missing Websocket-Key"); } WebSocket webSocket = openWebSocket(session); Response handshakeResponse = webSocket.getHandshakeResponse(); try { handshakeResponse.addHeader(NanoWSD.HEADER_WEBSOCKET_ACCEPT, makeAcceptKey(headers.get(NanoWSD.HEADER_WEBSOCKET_KEY))); } catch (NoSuchAlgorithmException e) { return Response.newFixedLengthResponse(Status.INTERNAL_ERROR, NanoHTTPD.MIME_PLAINTEXT, "The SHA-1 Algorithm required for websockets is not available on the server."); } if (headers.containsKey(NanoWSD.HEADER_WEBSOCKET_PROTOCOL)) { handshakeResponse.addHeader(NanoWSD.HEADER_WEBSOCKET_PROTOCOL, headers.get(NanoWSD.HEADER_WEBSOCKET_PROTOCOL).split(",")[0]); } return handshakeResponse; } else { return null; } } } ================================================ FILE: websocket/src/main/java/org/nanohttpd/protocols/websockets/OpCode.java ================================================ package org.nanohttpd.protocols.websockets; /* * #%L * NanoHttpd-Websocket * %% * Copyright (C) 2012 - 2016 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% */ public enum OpCode { Continuation(0), Text(1), Binary(2), Close(8), Ping(9), Pong(10); public static OpCode find(byte value) { for (OpCode opcode : values()) { if (opcode.getValue() == value) { return opcode; } } return null; } private final byte code; private OpCode(int code) { this.code = (byte) code; } public byte getValue() { return this.code; } public boolean isControlFrame() { return this == Close || this == Ping || this == Pong; } } ================================================ FILE: websocket/src/main/java/org/nanohttpd/protocols/websockets/State.java ================================================ package org.nanohttpd.protocols.websockets; /* * #%L * NanoHttpd-Websocket * %% * Copyright (C) 2012 - 2016 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% */ public enum State { UNCONNECTED, CONNECTING, OPEN, CLOSING, CLOSED } ================================================ FILE: websocket/src/main/java/org/nanohttpd/protocols/websockets/WebSocket.java ================================================ package org.nanohttpd.protocols.websockets; /* * #%L * NanoHttpd-Websocket * %% * Copyright (C) 2012 - 2016 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.io.InputStream; import java.io.OutputStream; import java.nio.charset.CharacterCodingException; import java.util.LinkedList; import java.util.List; import java.util.logging.Level; import org.nanohttpd.protocols.http.IHTTPSession; import org.nanohttpd.protocols.http.response.Response; import org.nanohttpd.protocols.http.response.Status; public abstract class WebSocket { private final InputStream in; private OutputStream out; private OpCode continuousOpCode = null; private final List continuousFrames = new LinkedList(); private State state = State.UNCONNECTED; protected boolean enforceNoGzip = true; private final IHTTPSession handshakeRequest; private final Response handshakeResponse = new Response(Status.SWITCH_PROTOCOL, null, (InputStream) null, 0) { @Override public void send(OutputStream out) { WebSocket.this.out = out; WebSocket.this.state = State.CONNECTING; super.send(out); WebSocket.this.state = State.OPEN; WebSocket.this.onOpen(); readWebsocket(); } }; public WebSocket(IHTTPSession handshakeRequest) { this.handshakeRequest = handshakeRequest; this.in = handshakeRequest.getInputStream(); if (enforceNoGzip) handshakeResponse.setUseGzip(false); this.handshakeResponse.addHeader(NanoWSD.HEADER_UPGRADE, NanoWSD.HEADER_UPGRADE_VALUE); this.handshakeResponse.addHeader(NanoWSD.HEADER_CONNECTION, NanoWSD.HEADER_CONNECTION_VALUE); } public boolean isOpen() { return state == State.OPEN; } protected abstract void onOpen(); protected abstract void onClose(CloseCode code, String reason, boolean initiatedByRemote); protected abstract void onMessage(WebSocketFrame message); protected abstract void onPong(WebSocketFrame pong); protected abstract void onException(IOException exception); /** * Debug method. Do not Override unless for debug purposes! * * @param frame * The received WebSocket Frame. */ protected void debugFrameReceived(WebSocketFrame frame) { } /** * Debug method. Do not Override unless for debug purposes!
    * This method is called before actually sending the frame. * * @param frame * The sent WebSocket Frame. */ protected void debugFrameSent(WebSocketFrame frame) { } public void close(CloseCode code, String reason, boolean initiatedByRemote) throws IOException { State oldState = this.state; this.state = State.CLOSING; if (oldState == State.OPEN) { sendFrame(new CloseFrame(code, reason)); } else { doClose(code, reason, initiatedByRemote); } } private void doClose(CloseCode code, String reason, boolean initiatedByRemote) { if (this.state == State.CLOSED) { return; } if (this.in != null) { try { this.in.close(); } catch (IOException e) { NanoWSD.LOG.log(Level.FINE, "close failed", e); } } if (this.out != null) { try { this.out.close(); } catch (IOException e) { NanoWSD.LOG.log(Level.FINE, "close failed", e); } } this.state = State.CLOSED; onClose(code, reason, initiatedByRemote); } // --------------------------------IO-------------------------------------- public IHTTPSession getHandshakeRequest() { return this.handshakeRequest; } public Response getHandshakeResponse() { return this.handshakeResponse; } private void handleCloseFrame(WebSocketFrame frame) throws IOException { CloseCode code = CloseCode.NormalClosure; String reason = ""; if (frame instanceof CloseFrame) { code = ((CloseFrame) frame).getCloseCode(); reason = ((CloseFrame) frame).getCloseReason(); } if (this.state == State.CLOSING) { // Answer for my requested close doClose(code, reason, false); } else { close(code, reason, true); } } private void handleFrameFragment(WebSocketFrame frame) throws IOException { if (frame.getOpCode() != OpCode.Continuation) { // First if (this.continuousOpCode != null) { throw new WebSocketException(CloseCode.ProtocolError, "Previous continuous frame sequence not completed."); } this.continuousOpCode = frame.getOpCode(); this.continuousFrames.clear(); this.continuousFrames.add(frame); } else if (frame.isFin()) { // Last if (this.continuousOpCode == null) { throw new WebSocketException(CloseCode.ProtocolError, "Continuous frame sequence was not started."); } this.continuousFrames.add(frame); onMessage(new WebSocketFrame(this.continuousOpCode, this.continuousFrames)); this.continuousOpCode = null; this.continuousFrames.clear(); } else if (this.continuousOpCode == null) { // Unexpected throw new WebSocketException(CloseCode.ProtocolError, "Continuous frame sequence was not started."); } else { // Intermediate this.continuousFrames.add(frame); } } private void handleWebsocketFrame(WebSocketFrame frame) throws IOException { debugFrameReceived(frame); if (frame.getOpCode() == OpCode.Close) { handleCloseFrame(frame); } else if (frame.getOpCode() == OpCode.Ping) { sendFrame(new WebSocketFrame(OpCode.Pong, true, frame.getBinaryPayload())); } else if (frame.getOpCode() == OpCode.Pong) { onPong(frame); } else if (!frame.isFin() || frame.getOpCode() == OpCode.Continuation) { handleFrameFragment(frame); } else if (this.continuousOpCode != null) { throw new WebSocketException(CloseCode.ProtocolError, "Continuous frame sequence not completed."); } else if (frame.getOpCode() == OpCode.Text || frame.getOpCode() == OpCode.Binary) { onMessage(frame); } else { throw new WebSocketException(CloseCode.ProtocolError, "Non control or continuous frame expected."); } } // --------------------------------Close----------------------------------- public void ping(byte[] payload) throws IOException { sendFrame(new WebSocketFrame(OpCode.Ping, true, payload)); } // --------------------------------Public // Facade--------------------------- private void readWebsocket() { try { while (this.state == State.OPEN) { handleWebsocketFrame(WebSocketFrame.read(this.in)); } } catch (CharacterCodingException e) { onException(e); doClose(CloseCode.InvalidFramePayloadData, e.toString(), false); } catch (IOException e) { onException(e); if (e instanceof WebSocketException) { doClose(((WebSocketException) e).getCode(), ((WebSocketException) e).getReason(), false); } } finally { doClose(CloseCode.InternalServerError, "Handler terminated without closing the connection.", false); } } public void send(byte[] payload) throws IOException { sendFrame(new WebSocketFrame(OpCode.Binary, true, payload)); } public void send(String payload) throws IOException { sendFrame(new WebSocketFrame(OpCode.Text, true, payload)); } public synchronized void sendFrame(WebSocketFrame frame) throws IOException { debugFrameSent(frame); frame.write(this.out); } } ================================================ FILE: websocket/src/main/java/org/nanohttpd/protocols/websockets/WebSocketException.java ================================================ package org.nanohttpd.protocols.websockets; /* * #%L * NanoHttpd-Websocket * %% * Copyright (C) 2012 - 2016 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; public class WebSocketException extends IOException { private static final long serialVersionUID = 1L; private final CloseCode code; private final String reason; public WebSocketException(CloseCode code, String reason) { this(code, reason, null); } public WebSocketException(CloseCode code, String reason, Exception cause) { super(code + ": " + reason, cause); this.code = code; this.reason = reason; } public WebSocketException(Exception cause) { this(CloseCode.InternalServerError, cause.toString(), cause); } public CloseCode getCode() { return this.code; } public String getReason() { return this.reason; } } ================================================ FILE: websocket/src/main/java/org/nanohttpd/protocols/websockets/WebSocketFrame.java ================================================ package org.nanohttpd.protocols.websockets; /* * #%L * NanoHttpd-Websocket * %% * Copyright (C) 2012 - 2016 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.EOFException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.charset.CharacterCodingException; import java.nio.charset.Charset; import java.util.Arrays; import java.util.List; public class WebSocketFrame { public static final Charset TEXT_CHARSET = Charset.forName("UTF-8"); public static String binary2Text(byte[] payload) throws CharacterCodingException { return new String(payload, WebSocketFrame.TEXT_CHARSET); } public static String binary2Text(byte[] payload, int offset, int length) throws CharacterCodingException { return new String(payload, offset, length, WebSocketFrame.TEXT_CHARSET); } private static int checkedRead(int read) throws IOException { if (read < 0) { throw new EOFException(); } return read; } public static WebSocketFrame read(InputStream in) throws IOException { byte head = (byte) checkedRead(in.read()); boolean fin = (head & 0x80) != 0; OpCode opCode = OpCode.find((byte) (head & 0x0F)); if ((head & 0x70) != 0) { throw new WebSocketException(CloseCode.ProtocolError, "The reserved bits (" + Integer.toBinaryString(head & 0x70) + ") must be 0."); } if (opCode == null) { throw new WebSocketException(CloseCode.ProtocolError, "Received frame with reserved/unknown opcode " + (head & 0x0F) + "."); } else if (opCode.isControlFrame() && !fin) { throw new WebSocketException(CloseCode.ProtocolError, "Fragmented control frame."); } WebSocketFrame frame = new WebSocketFrame(opCode, fin); frame.readPayloadInfo(in); frame.readPayload(in); if (frame.getOpCode() == OpCode.Close) { return new CloseFrame(frame); } else { return frame; } } public static byte[] text2Binary(String payload) throws CharacterCodingException { return payload.getBytes(WebSocketFrame.TEXT_CHARSET); } private OpCode opCode; private boolean fin; private byte[] maskingKey; private byte[] payload; // --------------------------------GETTERS--------------------------------- private transient int _payloadLength; private transient String _payloadString; private WebSocketFrame(OpCode opCode, boolean fin) { setOpCode(opCode); setFin(fin); } public WebSocketFrame(OpCode opCode, boolean fin, byte[] payload) { this(opCode, fin, payload, null); } public WebSocketFrame(OpCode opCode, boolean fin, byte[] payload, byte[] maskingKey) { this(opCode, fin); setMaskingKey(maskingKey); setBinaryPayload(payload); } public WebSocketFrame(OpCode opCode, boolean fin, String payload) throws CharacterCodingException { this(opCode, fin, payload, null); } public WebSocketFrame(OpCode opCode, boolean fin, String payload, byte[] maskingKey) throws CharacterCodingException { this(opCode, fin); setMaskingKey(maskingKey); setTextPayload(payload); } public WebSocketFrame(OpCode opCode, List fragments) throws WebSocketException { setOpCode(opCode); setFin(true); long _payloadLength = 0; for (WebSocketFrame inter : fragments) { _payloadLength += inter.getBinaryPayload().length; } if (_payloadLength < 0 || _payloadLength > Integer.MAX_VALUE) { throw new WebSocketException(CloseCode.MessageTooBig, "Max frame length has been exceeded."); } this._payloadLength = (int) _payloadLength; byte[] payload = new byte[this._payloadLength]; int offset = 0; for (WebSocketFrame inter : fragments) { System.arraycopy(inter.getBinaryPayload(), 0, payload, offset, inter.getBinaryPayload().length); offset += inter.getBinaryPayload().length; } setBinaryPayload(payload); } public WebSocketFrame(WebSocketFrame clone) { setOpCode(clone.getOpCode()); setFin(clone.isFin()); setBinaryPayload(clone.getBinaryPayload()); setMaskingKey(clone.getMaskingKey()); } public byte[] getBinaryPayload() { return this.payload; } public byte[] getMaskingKey() { return this.maskingKey; } public OpCode getOpCode() { return this.opCode; } // --------------------------------SERIALIZATION--------------------------- public String getTextPayload() { if (this._payloadString == null) { try { this._payloadString = binary2Text(getBinaryPayload()); } catch (CharacterCodingException e) { throw new RuntimeException("Undetected CharacterCodingException", e); } } return this._payloadString; } public boolean isFin() { return this.fin; } public boolean isMasked() { return this.maskingKey != null && this.maskingKey.length == 4; } private String payloadToString() { if (this.payload == null) { return "null"; } else { final StringBuilder sb = new StringBuilder(); sb.append('[').append(this.payload.length).append("b] "); if (getOpCode() == OpCode.Text) { String text = getTextPayload(); if (text.length() > 100) { sb.append(text.substring(0, 100)).append("..."); } else { sb.append(text); } } else { sb.append("0x"); for (int i = 0; i < Math.min(this.payload.length, 50); ++i) { sb.append(Integer.toHexString(this.payload[i] & 0xFF)); } if (this.payload.length > 50) { sb.append("..."); } } return sb.toString(); } } private void readPayload(InputStream in) throws IOException { this.payload = new byte[this._payloadLength]; int read = 0; while (read < this._payloadLength) { read += checkedRead(in.read(this.payload, read, this._payloadLength - read)); } if (isMasked()) { for (int i = 0; i < this.payload.length; i++) { this.payload[i] ^= this.maskingKey[i % 4]; } } // Test for Unicode errors if (getOpCode() == OpCode.Text) { this._payloadString = binary2Text(getBinaryPayload()); } } // --------------------------------ENCODING-------------------------------- private void readPayloadInfo(InputStream in) throws IOException { byte b = (byte) checkedRead(in.read()); boolean masked = (b & 0x80) != 0; this._payloadLength = (byte) (0x7F & b); if (this._payloadLength == 126) { // checkedRead must return int for this to work this._payloadLength = (checkedRead(in.read()) << 8 | checkedRead(in.read())) & 0xFFFF; if (this._payloadLength < 126) { throw new WebSocketException(CloseCode.ProtocolError, "Invalid data frame 2byte length. (not using minimal length encoding)"); } } else if (this._payloadLength == 127) { long _payloadLength = (long) checkedRead(in.read()) << 56 | (long) checkedRead(in.read()) << 48 | (long) checkedRead(in.read()) << 40 | (long) checkedRead(in.read()) << 32 | checkedRead(in.read()) << 24 | checkedRead(in.read()) << 16 | checkedRead(in.read()) << 8 | checkedRead(in.read()); if (_payloadLength < 65536) { throw new WebSocketException(CloseCode.ProtocolError, "Invalid data frame 4byte length. (not using minimal length encoding)"); } if (_payloadLength < 0 || _payloadLength > Integer.MAX_VALUE) { throw new WebSocketException(CloseCode.MessageTooBig, "Max frame length has been exceeded."); } this._payloadLength = (int) _payloadLength; } if (this.opCode.isControlFrame()) { if (this._payloadLength > 125) { throw new WebSocketException(CloseCode.ProtocolError, "Control frame with payload length > 125 bytes."); } if (this.opCode == OpCode.Close && this._payloadLength == 1) { throw new WebSocketException(CloseCode.ProtocolError, "Received close frame with payload len 1."); } } if (masked) { this.maskingKey = new byte[4]; int read = 0; while (read < this.maskingKey.length) { read += checkedRead(in.read(this.maskingKey, read, this.maskingKey.length - read)); } } } public void setBinaryPayload(byte[] payload) { this.payload = payload; this._payloadLength = payload.length; this._payloadString = null; } public void setFin(boolean fin) { this.fin = fin; } public void setMaskingKey(byte[] maskingKey) { if (maskingKey != null && maskingKey.length != 4) { throw new IllegalArgumentException("MaskingKey " + Arrays.toString(maskingKey) + " hasn't length 4"); } this.maskingKey = maskingKey; } public void setOpCode(OpCode opcode) { this.opCode = opcode; } public void setTextPayload(String payload) throws CharacterCodingException { this.payload = text2Binary(payload); this._payloadLength = payload.length(); this._payloadString = payload; } // --------------------------------CONSTANTS------------------------------- public void setUnmasked() { setMaskingKey(null); } @Override public String toString() { final StringBuilder sb = new StringBuilder("WS["); sb.append(getOpCode()); sb.append(", ").append(isFin() ? "fin" : "inter"); sb.append(", ").append(isMasked() ? "masked" : "unmasked"); sb.append(", ").append(payloadToString()); sb.append(']'); return sb.toString(); } // ------------------------------------------------------------------------ public void write(OutputStream out) throws IOException { byte header = 0; if (this.fin) { header |= 0x80; } header |= this.opCode.getValue() & 0x0F; out.write(header); this._payloadLength = getBinaryPayload().length; if (this._payloadLength <= 125) { out.write(isMasked() ? 0x80 | (byte) this._payloadLength : (byte) this._payloadLength); } else if (this._payloadLength <= 0xFFFF) { out.write(isMasked() ? 0xFE : 126); out.write(this._payloadLength >>> 8); out.write(this._payloadLength); } else { out.write(isMasked() ? 0xFF : 127); out.write(this._payloadLength >>> 56 & 0); // integer only // contains // 31 bit out.write(this._payloadLength >>> 48 & 0); out.write(this._payloadLength >>> 40 & 0); out.write(this._payloadLength >>> 32 & 0); out.write(this._payloadLength >>> 24); out.write(this._payloadLength >>> 16); out.write(this._payloadLength >>> 8); out.write(this._payloadLength); } if (isMasked()) { out.write(this.maskingKey); for (int i = 0; i < this._payloadLength; i++) { out.write(getBinaryPayload()[i] ^ this.maskingKey[i % 4]); } } else { out.write(getBinaryPayload()); } out.flush(); } } ================================================ FILE: websocket/src/main/java/org/nanohttpd/samples/websockets/DebugWebSocketServer.java ================================================ package org.nanohttpd.samples.websockets; /* * #%L * NanoHttpd-Websocket * %% * 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 org.nanohttpd.protocols.http.IHTTPSession; import org.nanohttpd.protocols.websockets.CloseCode; import org.nanohttpd.protocols.websockets.NanoWSD; import org.nanohttpd.protocols.websockets.WebSocket; import org.nanohttpd.protocols.websockets.WebSocketFrame; /** * @author Paul S. Hawke (paul.hawke@gmail.com) On: 4/23/14 at 10:31 PM */ public class DebugWebSocketServer extends NanoWSD { /** * logger to log to. */ private static final Logger LOG = Logger.getLogger(DebugWebSocketServer.class.getName()); private final boolean debug; public DebugWebSocketServer(int port, boolean debug) { super(port); this.debug = debug; } @Override protected WebSocket openWebSocket(IHTTPSession handshake) { return new DebugWebSocket(this, handshake); } private static class DebugWebSocket extends WebSocket { private final DebugWebSocketServer server; public DebugWebSocket(DebugWebSocketServer server, IHTTPSession handshakeRequest) { super(handshakeRequest); this.server = server; } @Override protected void onOpen() { } @Override protected void onClose(CloseCode code, String reason, boolean initiatedByRemote) { if (server.debug) { System.out.println("C [" + (initiatedByRemote ? "Remote" : "Self") + "] " + (code != null ? code : "UnknownCloseCode[" + code + "]") + (reason != null && !reason.isEmpty() ? ": " + reason : "")); } } @Override protected void onMessage(WebSocketFrame message) { try { message.setUnmasked(); sendFrame(message); } catch (IOException e) { throw new RuntimeException(e); } } @Override protected void onPong(WebSocketFrame pong) { if (server.debug) { System.out.println("P " + pong); } } @Override protected void onException(IOException exception) { DebugWebSocketServer.LOG.log(Level.SEVERE, "exception occured", exception); } @Override protected void debugFrameReceived(WebSocketFrame frame) { if (server.debug) { System.out.println("R " + frame); } } @Override protected void debugFrameSent(WebSocketFrame frame) { if (server.debug) { System.out.println("S " + frame); } } } } ================================================ FILE: websocket/src/main/java/org/nanohttpd/samples/websockets/EchoSocketSample.java ================================================ package org.nanohttpd.samples.websockets; /* * #%L * NanoHttpd-Websocket * %% * 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.nanohttpd.protocols.websockets.NanoWSD; public class EchoSocketSample { public static void main(String[] args) throws IOException { final boolean debugMode = args.length >= 2 && "-d".equals(args[1].toLowerCase()); NanoWSD ws = new DebugWebSocketServer(args.length > 0 ? Integer.parseInt(args[0]) : 9090, debugMode); ws.start(); System.out.println("Server started, hit Enter to stop.\n"); try { System.in.read(); } catch (IOException ignored) { } ws.stop(); System.out.println("Server stopped.\n"); } } ================================================ FILE: websocket/src/site/site.xml ================================================ org.apache.maven.skins maven-fluido-skin 1.3.0 ../images/nanohttpd_logo.png ../images/nanohttpd_logo_text.png false true Nanohttpd/nanohttpd right black ================================================ FILE: websocket/src/test/java/org/nanohttpd/junit/protocols/websockets/EchoWebSocketsTest.java ================================================ package org.nanohttpd.junit.protocols.websockets; /* * #%L * NanoHttpd-Websocket * %% * 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.assertFalse; import static org.junit.Assert.fail; import java.io.IOException; import java.io.PipedInputStream; import java.io.PipedOutputStream; import java.net.URI; import java.util.concurrent.TimeUnit; import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; import org.eclipse.jetty.websocket.client.WebSocketClient; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.nanohttpd.protocols.websockets.NanoWSD; import org.nanohttpd.samples.websockets.DebugWebSocketServer; import org.nanohttpd.samples.websockets.EchoSocketSample; public class EchoWebSocketsTest { private static NanoWSD server; @BeforeClass public static void setUp() throws Exception { EchoWebSocketsTest.server = new DebugWebSocketServer(9191, true); EchoWebSocketsTest.server.start(); } @AfterClass public static void tearDown() throws Exception { EchoWebSocketsTest.server.stop(); } @Test public void testDirectoryArgument() throws IOException, InterruptedException { final String testPort = "9458"; PipedOutputStream stdIn = new PipedOutputStream(); System.setIn(new PipedInputStream(stdIn)); Thread testServer = new Thread(new Runnable() { @Override public void run() { String[] args = { testPort, "-d" }; try { EchoSocketSample.main(args); } catch (IOException e) { fail("Exception: " + e.getMessage()); } } }); testServer.start(); Thread.sleep(1000); stdIn.write(System.getProperty("line.separator").getBytes()); testServer.join(1000); assertFalse("Test server failed to close", testServer.isAlive()); } @Test public void testWebsocketClient() throws Exception { String destUri = "ws://localhost:9191"; WebSocketClient client = new WebSocketClient(); SimpleEchoSocket socket = new SimpleEchoSocket(); socket.getToSendMessages().add("Hello"); socket.getToSendMessages().add("Thanks for the conversation."); socket.getToSendMessages().add(createString(31000)); socket.getToSendMessages().add(createString(65400)); try { client.start(); URI echoUri = new URI(destUri); ClientUpgradeRequest request = new ClientUpgradeRequest(); client.connect(socket, echoUri, request); System.out.printf("Connecting to : %s%n", echoUri); socket.awaitClose(5, TimeUnit.SECONDS); } catch (Throwable t) { t.printStackTrace(); } finally { try { client.stop(); } catch (Exception e) { e.printStackTrace(); } } Assert.assertEquals(4, socket.getReceivedMessages().size()); Assert.assertEquals("Hello", socket.getReceivedMessages().get(0)); Assert.assertEquals("Thanks for the conversation.", socket.getReceivedMessages().get(1)); } private String createString(int i) { StringBuilder builder = new StringBuilder(); while (builder.length() < i) { builder.append("A very long text."); } return builder.toString(); } } ================================================ FILE: websocket/src/test/java/org/nanohttpd/junit/protocols/websockets/SimpleEchoSocket.java ================================================ package org.nanohttpd.junit.protocols.websockets; /* * #%L * NanoHttpd-Websocket * %% * 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.util.ArrayList; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.StatusCode; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; import org.eclipse.jetty.websocket.api.annotations.WebSocket; /** * Basic Echo Client Socket */ @WebSocket(maxTextMessageSize = 64 * 1024) public class SimpleEchoSocket { private final List receivedMessages = new ArrayList(); private final List toSendMessages = new ArrayList(); private final CountDownLatch closeLatch; public SimpleEchoSocket() { this.closeLatch = new CountDownLatch(1); } public boolean awaitClose(int duration, TimeUnit unit) throws InterruptedException { return this.closeLatch.await(duration, unit); } public List getReceivedMessages() { return this.receivedMessages; } public List getToSendMessages() { return this.toSendMessages; } @OnWebSocketClose public void onClose(int statusCode, String reason) { System.out.printf("Connection closed: %d - %s%n", statusCode, reason); this.closeLatch.countDown(); } @OnWebSocketConnect public void onConnect(Session session) { System.out.printf("Got connect: %s%n", session); try { Future fut; for (String message : this.toSendMessages) { fut = session.getRemote().sendStringByFuture(message); fut.get(5, TimeUnit.SECONDS); } session.close(StatusCode.NORMAL, "I'm done"); } catch (Throwable t) { t.printStackTrace(); } } @OnWebSocketMessage public void onMessage(String msg) { System.out.printf("Got msg: %s%n", msg); this.receivedMessages.add(msg); } } ================================================ FILE: websocket/src/test/java/org/nanohttpd/junit/protocols/websockets/WebSocketResponseHandlerTest.java ================================================ package org.nanohttpd.junit.protocols.websockets; /* * #%L * NanoHttpd-Websocket * %% * 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.assertNull; import static org.mockito.Mockito.when; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.runners.MockitoJUnitRunner; import org.nanohttpd.protocols.http.IHTTPSession; import org.nanohttpd.protocols.http.response.Response; import org.nanohttpd.protocols.http.response.Status; import org.nanohttpd.protocols.websockets.CloseCode; import org.nanohttpd.protocols.websockets.NanoWSD; import org.nanohttpd.protocols.websockets.OpCode; import org.nanohttpd.protocols.websockets.WebSocket; import org.nanohttpd.protocols.websockets.WebSocketFrame; import org.nanohttpd.util.IHandler; @RunWith(MockitoJUnitRunner.class) public class WebSocketResponseHandlerTest { @Mock private IHTTPSession session; private MockedWSD nanoWebSocketServer; private Map headers; private static class MockedWSD extends NanoWSD { public MockedWSD(int port) { super(port); } public MockedWSD(String hostname, int port) { super(hostname, port); } // This is to work around Mockito being a little bitch. public void initialize() { interceptors = new ArrayList>(); addHTTPInterceptor(new Interceptor()); setHTTPHandler(new IHandler() { @Override public Response handle(IHTTPSession input) { return serve(input); } }); } @Override protected WebSocket openWebSocket(IHTTPSession handshake) { return new WebSocket(handshake) { // Dummy websocket inner class. @Override protected void onPong(WebSocketFrame pong) { } @Override protected void onOpen() { } @Override protected void onMessage(WebSocketFrame message) { } @Override protected void onException(IOException exception) { } @Override protected void onClose(CloseCode code, String reason, boolean initiatedByRemote) { } }; } } @Before public void setUp() { // Be careful. // This does NOT call any constructors, instead, directly creates // the object in memory. I wasted 3 fucking hours attempting to // debug this. ~ LordFokas this.nanoWebSocketServer = Mockito.mock(MockedWSD.class, Mockito.CALLS_REAL_METHODS); // this could have been avoided if Mockito had a way to call fucking // constructors!! this.nanoWebSocketServer.initialize(); this.headers = new HashMap(); this.headers.put("upgrade", "websocket"); this.headers.put("connection", "Upgrade"); this.headers.put("sec-websocket-key", "x3JJHMbDL1EzLkh9GBhXDw=="); this.headers.put("sec-websocket-protocol", "chat, superchat"); this.headers.put("sec-websocket-version", "13"); when(this.session.getHeaders()).thenReturn(this.headers); } @Test public void testConnectionHeaderHandlesKeepAlive_FixingFirefoxConnectIssue() { this.headers.put("connection", "keep-alive, Upgrade"); Response handshakeResponse = this.nanoWebSocketServer.handle(this.session); assertNotNull(handshakeResponse); } @Test public void testHandshakeReturnsResponseWithExpectedHeaders() { Response handshakeResponse = this.nanoWebSocketServer.handle(this.session); assertNotNull(handshakeResponse); assertEquals(handshakeResponse.getHeader(NanoWSD.HEADER_WEBSOCKET_ACCEPT), "HSmrc0sMlYUkAGmm5OPpG2HaGWk="); assertEquals(handshakeResponse.getHeader(NanoWSD.HEADER_WEBSOCKET_PROTOCOL), "chat"); } @Test public void testMissingKeyReturnsErrorResponse() { this.headers.remove("sec-websocket-key"); Response handshakeResponse = this.nanoWebSocketServer.handle(this.session); assertNotNull(handshakeResponse); assertEquals(Status.BAD_REQUEST, handshakeResponse.getStatus()); } @Test public void testWrongConnectionHeaderReturnsNullResponse() { this.headers.put("connection", "Junk"); Response handshakeResponse = this.nanoWebSocketServer.handle(this.session); assertNull(handshakeResponse.getHeader(NanoWSD.HEADER_UPGRADE)); } @Test public void testWrongUpgradeHeaderReturnsNullResponse() { this.headers.put("upgrade", "not a websocket"); Response handshakeResponse = this.nanoWebSocketServer.handle(this.session); assertNull(handshakeResponse.getHeader(NanoWSD.HEADER_UPGRADE)); } @Test public void testWrongWebsocketVersionReturnsErrorResponse() { this.headers.put("sec-websocket-version", "12"); Response handshakeResponse = this.nanoWebSocketServer.handle(this.session); assertNotNull(handshakeResponse); assertEquals(Status.BAD_REQUEST, handshakeResponse.getStatus()); } @Test public void testSetMaskingKeyThrowsExceptionMaskingKeyLengthIsNotFour() { WebSocketFrame webSocketFrame = new WebSocketFrame(OpCode.Text, true, new byte[0]); for (int maskingKeyLength = 0; maskingKeyLength < 10; maskingKeyLength++) { if (maskingKeyLength == 4) continue; try { webSocketFrame.setMaskingKey(new byte[maskingKeyLength]); Assert.fail("IllegalArgumentException expected but not thrown"); } catch (IllegalArgumentException e) { } } } @Test public void testIsMasked() { WebSocketFrame webSocketFrame = new WebSocketFrame(OpCode.Text, true, new byte[0]); Assert.assertFalse("isMasked should return true if masking key is not set.", webSocketFrame.isMasked()); webSocketFrame.setMaskingKey(new byte[4]); Assert.assertTrue("isMasked should return true if correct masking key is set.", webSocketFrame.isMasked()); } @Test public void testWriteWhenNotMasked() throws IOException { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); WebSocketFrame webSocketFrame = new WebSocketFrame(OpCode.Text, true, "payload".getBytes()); webSocketFrame.write(byteArrayOutputStream); byte[] writtenBytes = byteArrayOutputStream.toByteArray(); Assert.assertEquals(9, writtenBytes.length); Assert.assertEquals("Header byte incorrect.", -127, writtenBytes[0]); Assert.assertEquals("Payload length byte incorrect.", 7, writtenBytes[1]); Assert.assertArrayEquals(new byte[]{ -127, 7, 112, 97, 121, 108, 111, 97, 100 }, writtenBytes); } @Test public void testWriteWhenMasked() throws IOException { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); WebSocketFrame webSocketFrame = new WebSocketFrame(OpCode.Binary, true, "payload".getBytes()); webSocketFrame.setMaskingKey(new byte[]{ 12, 45, 33, 32 }); webSocketFrame.write(byteArrayOutputStream); byte[] writtenBytes = byteArrayOutputStream.toByteArray(); Assert.assertEquals(13, writtenBytes.length); Assert.assertEquals("Header byte incorrect.", -126, writtenBytes[0]); Assert.assertEquals("Payload length byte incorrect.", -121, writtenBytes[1]); Assert.assertArrayEquals(new byte[]{ -126, -121, 12, 45, 33, 32, 124, 76, 88, 76, 99, 76, 69 }, writtenBytes); } @Test public void testWriteWhenNotMaskedPayloadLengthGreaterThan125() throws IOException { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); WebSocketFrame webSocketFrame = new WebSocketFrame(OpCode.Ping, true, new byte[257]); webSocketFrame.write(byteArrayOutputStream); byte[] writtenBytes = byteArrayOutputStream.toByteArray(); Assert.assertEquals(261, writtenBytes.length); Assert.assertEquals("Header byte incorrect.", -119, writtenBytes[0]); Assert.assertArrayEquals("Payload length bytes incorrect.", new byte[]{ 126, 1, 1 }, new byte[]{ writtenBytes[1], writtenBytes[2], writtenBytes[3] }); } @Test public void testWriteWhenMaskedPayloadLengthGreaterThan125() throws IOException { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); WebSocketFrame webSocketFrame = new WebSocketFrame(OpCode.Ping, false, new byte[257]); webSocketFrame.setMaskingKey(new byte[]{ 19, 25, 79, 11 }); webSocketFrame.write(byteArrayOutputStream); byte[] writtenBytes = byteArrayOutputStream.toByteArray(); Assert.assertEquals(265, writtenBytes.length); Assert.assertEquals("Header byte incorrect.", 9, writtenBytes[0]); Assert.assertArrayEquals("Payload length bytes incorrect.", new byte[]{ -2, 1, 1 }, new byte[]{ writtenBytes[1], writtenBytes[2], writtenBytes[3] }); } @Test public void testWriteWhenNotMaskedPayloadLengthGreaterThan65535() throws IOException { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); WebSocketFrame webSocketFrame = new WebSocketFrame(OpCode.Ping, true, new byte[65536]); webSocketFrame.write(byteArrayOutputStream); byte[] writtenBytes = byteArrayOutputStream.toByteArray(); Assert.assertEquals(65546, writtenBytes.length); Assert.assertEquals("Header byte incorrect.", -119, writtenBytes[0]); Assert.assertArrayEquals("Payload length bytes incorrect.", new byte[]{ 127, 0, 0, 0, 0, 0, 1, 0, 0 }, Arrays.copyOfRange(writtenBytes, 1, 10)); } } ================================================ FILE: websocket/src/test/resources/echo-test.html ================================================ WebSocket Test

    WebSocket Test