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 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; 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; /** * A simple, tiny, nicely embeddable HTTP server in Java * <p/> * <p/> * NanoHTTPD * <p> * Copyright (c) 2012-2013 by Paul S. Hawke, 2001,2005-2013 by Jarno Elonen, * 2010 by Konstantinos Togias * </p> * <p/> * <p/> * <b>Features + limitations: </b> * <ul> * <p/> * <li>Only one Java file</li> * <li>Java 5 compatible</li> * <li>Released as open source, Modified BSD licence</li> * <li>No fixed config files, logging, authorization etc. (Implement yourself if * you need them.)</li> * <li>Supports parameter parsing of GET and POST methods (+ rudimentary PUT * support in 1.25)</li> * <li>Supports both dynamic content and file serving</li> * <li>Supports file upload (since version 1.2, 2010)</li> * <li>Supports partial content (streaming)</li> * <li>Supports ETags</li> * <li>Never caches anything</li> * <li>Doesn't limit bandwidth, request time or simultaneous connections</li> * <li>Default code serves files and shows all HTTP parameters and headers</li> * <li>File server supports directory listing, index.html and index.htm</li> * <li>File server supports partial content (streaming)</li> * <li>File server supports ETags</li> * <li>File server does the 301 redirection trick for directories without '/'</li> * <li>File server supports simple skipping for files (continue download)</li> * <li>File server serves also very long files without memory overhead</li> * <li>Contains a built-in list of most common MIME types</li> * <li>All header names are converted to lower case so they don't vary between * browsers/clients</li> * <p/> * </ul> * <p/> * <p/> * <b>How to use: </b> * <ul> * <p/> * <li>Subclass and implement serve() and embed to your own program</li> * <p/> * </ul> * <p/> * 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<String, String> MIME_TYPES; public static Map<String, String> mimeTypes() { if (MIME_TYPES == null) { MIME_TYPES = new HashMap<String, String>(); loadMimeTypes(MIME_TYPES, "META-INF/nanohttpd/default-mimetypes.properties"); loadMimeTypes(MIME_TYPES, "META-INF/nanohttpd/mimetypes.properties"); if (MIME_TYPES.isEmpty()) { LOG.log(Level.WARNING, "no mime types found in the classpath! please provide mimetypes.properties"); } } return MIME_TYPES; } @SuppressWarnings({ "unchecked", "rawtypes" }) private static void loadMimeTypes(Map<String, String> result, String resourceName) { try { Enumeration<URL> resources = NanoHTTPD.class.getClassLoader().getResources(resourceName); while (resources.hasMoreElements()) { URL url = (URL) resources.nextElement(); Properties properties = new Properties(); InputStream stream = null; try { stream = url.openStream(); properties.load(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<ServerSocket, IOException> serverSocketFactory = new DefaultServerSocketFactory(); private Thread myThread; private IHandler<IHTTPSession, Response> httpHandler; protected List<IHandler<IHTTPSession, Response>> interceptors = new ArrayList<IHandler<IHTTPSession, Response>>(4); /** * Pluggable strategy for asynchronously executing requests. */ protected IAsyncRunner asyncRunner; /** * Pluggable strategy for creating and cleaning up temporary files. */ private IFactory<ITempFileManager> 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<IHTTPSession, Response>() { @Override public Response handle(IHTTPSession input) { return NanoHTTPD.this.serve(input); } }; } public void setHTTPHandler(IHandler<IHTTPSession, Response> handler) { this.httpHandler = handler; } public void addHTTPInterceptor(IHandler<IHTTPSession, Response> 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 <b>NanoHTTPD</b> parameters values, as passed to the * <code>serve()</code> method. * @return a map of <code>String</code> (parameter name) to * <code>List<String></code> (a list of the values supplied). */ protected static Map<String, List<String>> decodeParameters(Map<String, String> parms) { return decodeParameters(parms.get(NanoHTTPD.QUERY_STRING_PARAMETER)); } // ------------------------------------------------------------------------------- // // /** * Decode parameters from a URL, handing the case where a single parameter * name might have been supplied several times, by return lists of values. * In general these lists will contain a single element. * * @param queryString * a query string pulled from the URL. * @return a map of <code>String</code> (parameter name) to * <code>List<String></code> (a list of the values supplied). */ protected static Map<String, List<String>> decodeParameters(String queryString) { Map<String, List<String>> parms = new HashMap<String, List<String>>(); if (queryString != null) { StringTokenizer st = new StringTokenizer(queryString, "&"); while (st.hasMoreTokens()) { String e = st.nextToken(); int sep = e.indexOf('='); String propertyName = sep >= 0 ? decodePercent(e.substring(0, sep)).trim() : decodePercent(e).trim(); if (!parms.containsKey(propertyName)) { parms.put(propertyName, new ArrayList<String>()); } String propertyValue = sep >= 0 ? decodePercent(e.substring(sep + 1)) : null; if (propertyValue != null) { parms.get(propertyName).add(propertyValue); } } } return parms; } /** * Decode percent encoded <code>String</code> values. * * @param str * the percent encoded <code>String</code> * @return expanded form of the input, for example "foo%20bar" becomes * "foo bar" */ 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<ServerSocket, IOException> getServerSocketFactory() { return serverSocketFactory; } public void setServerSocketFactory(IFactoryThrowing<ServerSocket, IOException> serverSocketFactory) { this.serverSocketFactory = serverSocketFactory; } public String getHostname() { return hostname; } public IFactory<ITempFileManager> 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<IHTTPSession, Response> interceptor : interceptors) { Response response = interceptor.handle(session); if (response != null) return response; } return httpHandler.handle(session); } /** * Override this to customize the server. * <p/> * <p/> * (By default, this returns a 404 "Not Found" plain text error response.) * * @param session * The HTTP session * @return HTTP response, see class Response for details */ @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<ITempFileManager> 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; } }