net.apexes.wsonrpc.server.nano.http.NanoHTTPD Maven / Gradle / Ivy
The newest version!
package net.apexes.wsonrpc.server.nano.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 net.apexes.wsonrpc.server.nano.http.response.Response;
import net.apexes.wsonrpc.server.nano.http.response.Status;
import net.apexes.wsonrpc.server.nano.http.sockets.DefaultServerSocketFactory;
import net.apexes.wsonrpc.server.nano.http.sockets.SecureServerSocketFactory;
import net.apexes.wsonrpc.server.nano.http.tempfiles.DefaultTempFileManagerFactory;
import net.apexes.wsonrpc.server.nano.http.tempfiles.ITempFileManager;
import net.apexes.wsonrpc.server.nano.http.threading.DefaultAsyncRunner;
import net.apexes.wsonrpc.server.nano.http.threading.IAsyncRunner;
import net.apexes.wsonrpc.server.nano.util.IFactory;
import net.apexes.wsonrpc.server.nano.util.IFactoryThrowing;
import net.apexes.wsonrpc.server.nano.util.IHandler;
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 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;
/**
* 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 void safeClose(Object closeable) {
try {
if (closeable != null) {
if (closeable instanceof Closeable) {
((Closeable) 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
*/
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;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy