From 9058464950a9734da0a7ff2dc47f3081bbb5117c Mon Sep 17 00:00:00 2001 From: ritchie Date: Sun, 10 May 2015 13:41:19 +0200 Subject: after the last pull request in this series the formatter is now active --- core/src/main/java/fi/iki/elonen/NanoHTTPD.java | 548 +++++++++++++++--------- 1 file changed, 337 insertions(+), 211 deletions(-) (limited to 'core/src/main/java') diff --git a/core/src/main/java/fi/iki/elonen/NanoHTTPD.java b/core/src/main/java/fi/iki/elonen/NanoHTTPD.java index 5132bbf..0705196 100644 --- a/core/src/main/java/fi/iki/elonen/NanoHTTPD.java +++ b/core/src/main/java/fi/iki/elonen/NanoHTTPD.java @@ -72,7 +72,6 @@ import java.util.TimeZone; import java.util.logging.Level; import java.util.logging.Logger; - import java.security.KeyStore; import javax.net.ssl.*; @@ -81,7 +80,10 @@ import javax.net.ssl.*; *

*

* NanoHTTPD - *

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

+ *

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

*

*

* Features + limitations: @@ -90,8 +92,10 @@ import javax.net.ssl.*; *

  • 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)
  • + *
  • 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)
  • @@ -106,7 +110,8 @@ import javax.net.ssl.*; *
  • 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
  • + *
  • All header names are converted to lower case so they don't vary between + * browsers/clients
  • *

    * *

    @@ -118,43 +123,56 @@ import javax.net.ssl.*; *

    * *

    - * See the separate "LICENSE.md" file for the distribution license (Modified BSD licence) + * See the separate "LICENSE.md" file for the distribution license (Modified BSD + * licence) */ public abstract class NanoHTTPD { + /** * Maximum time to wait on Socket.getInputStream().read() (in milliseconds) - * This is required as the Keep-Alive HTTP connections would otherwise - * block the socket reading thread forever (or as long the browser is open). + * 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. + * Pseudo-Parameter to use to store the actual query string in the + * parameters map for later re-processing. */ private static final String QUERY_STRING_PARAMETER = "NanoHttpd.QUERY_STRING"; - - /** - * logger to log to. - */ - private static Logger LOG = Logger.getLogger(NanoHTTPD.class.getName()); - + + /** + * logger to log to. + */ + private static Logger LOG = Logger.getLogger(NanoHTTPD.class.getName()); + private final String hostname; + private final int myPort; + private ServerSocket myServerSocket; + private Set openConnections = new HashSet(); + private SSLServerSocketFactory sslServerSocketFactory; + private Thread myThread; + /** * Pluggable strategy for asynchronously executing requests. */ private AsyncRunner asyncRunner; + /** * Pluggable strategy for creating and cleaning up temporary files. */ @@ -182,86 +200,84 @@ public abstract class NanoHTTPD { try { closeable.close(); } catch (IOException e) { - LOG.log(Level.SEVERE, "Could not close",e); + LOG.log(Level.SEVERE, "Could not close", e); } } } - /** - * Creates an SSLSocketFactory for HTTPS. - * - * Pass a KeyStore resource with your certificate and passphrase - */ - public static SSLServerSocketFactory makeSSLSocketFactory(String keyAndTrustStoreClasspathPath, char[] passphrase) throws IOException { - SSLServerSocketFactory res = null; - try { - KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType()); - InputStream keystoreStream = NanoHTTPD.class.getResourceAsStream(keyAndTrustStoreClasspathPath); - keystore.load(keystoreStream, passphrase); - TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); - trustManagerFactory.init(keystore); - KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); - keyManagerFactory.init(keystore, passphrase); - SSLContext ctx = SSLContext.getInstance("TLS"); - ctx.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null); - res = ctx.getServerSocketFactory(); - } 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 { - SSLServerSocketFactory res = null; - try { - TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); - trustManagerFactory.init(loadedKeyStore); - SSLContext ctx = SSLContext.getInstance("TLS"); - ctx.init(loadedKeyFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null); - res = ctx.getServerSocketFactory(); - } catch (Exception e) { - throw new IOException(e.getMessage()); - } - return res; - } - - /** - * 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; - } - - /** - * Call before start() to serve over HTTPS instead of HTTP - */ - public void makeSecure(SSLServerSocketFactory sslServerSocketFactory) { - this.sslServerSocketFactory = sslServerSocketFactory; - } + /** + * Creates an SSLSocketFactory for HTTPS. Pass a KeyStore resource with your + * certificate and passphrase + */ + public static SSLServerSocketFactory makeSSLSocketFactory(String keyAndTrustStoreClasspathPath, char[] passphrase) throws IOException { + SSLServerSocketFactory res = null; + try { + KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType()); + InputStream keystoreStream = NanoHTTPD.class.getResourceAsStream(keyAndTrustStoreClasspathPath); + keystore.load(keystoreStream, passphrase); + TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + trustManagerFactory.init(keystore); + KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + keyManagerFactory.init(keystore, passphrase); + SSLContext ctx = SSLContext.getInstance("TLS"); + ctx.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null); + res = ctx.getServerSocketFactory(); + } 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 { + SSLServerSocketFactory res = null; + try { + TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + trustManagerFactory.init(loadedKeyStore); + SSLContext ctx = SSLContext.getInstance("TLS"); + ctx.init(loadedKeyFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null); + res = ctx.getServerSocketFactory(); + } catch (Exception e) { + throw new IOException(e.getMessage()); + } + return res; + } + + /** + * 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; + } + + /** + * Call before start() to serve over HTTPS instead of HTTP + */ + public void makeSecure(SSLServerSocketFactory sslServerSocketFactory) { + this.sslServerSocketFactory = sslServerSocketFactory; + } /** * Start the server. - * - * @throws IOException if the socket is in use. + * + * @throws IOException + * if the socket is in use. */ public void start() throws IOException { if (sslServerSocketFactory != null) { @@ -275,6 +291,7 @@ public abstract class NanoHTTPD { myServerSocket.bind((hostname != null) ? new InetSocketAddress(hostname, myPort) : new InetSocketAddress(myPort)); myThread = new Thread(new Runnable() { + @Override public void run() { do { @@ -284,6 +301,7 @@ public abstract class NanoHTTPD { finalAccept.setSoTimeout(SOCKET_READ_TIMEOUT); final InputStream inputStream = finalAccept.getInputStream(); asyncRunner.exec(new Runnable() { + @Override public void run() { OutputStream outputStream = null; @@ -295,13 +313,15 @@ public abstract class NanoHTTPD { 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 + // 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)) { - LOG.log(Level.FINE, "Communication with the client broken", e); + if (!(e instanceof SocketException && "NanoHttpd Shutdown".equals(e.getMessage())) && !(e instanceof SocketTimeoutException)) { + LOG.log(Level.FINE, "Communication with the client broken", e); } } finally { safeClose(outputStream); @@ -312,7 +332,7 @@ public abstract class NanoHTTPD { } }); } catch (IOException e) { - LOG.log(Level.FINE, "Communication with the client broken", e); + LOG.log(Level.FINE, "Communication with the client broken", e); } } while (!myServerSocket.isClosed()); } @@ -333,14 +353,15 @@ public abstract class NanoHTTPD { myThread.join(); } } catch (Exception e) { - LOG.log(Level.SEVERE, "Could not stop all connections", e); + LOG.log(Level.SEVERE, "Could not stop all connections", e); } } /** * Registers that a new connection has been set up. - * - * @param socket the {@link Socket} for the connection. + * + * @param socket + * the {@link Socket} for the connection. */ public synchronized void registerConnection(Socket socket) { openConnections.add(socket); @@ -348,7 +369,7 @@ public abstract class NanoHTTPD { /** * Registers that a connection has been closed - * + * * @param socket * the {@link Socket} for the connection. */ @@ -382,16 +403,21 @@ public abstract class NanoHTTPD { *

    *

    * (By default, this returns a 404 "Not Found" plain text error response.) - * - * @param uri Percent-decoded URI without parameters, for example "/index.cgi" - * @param method "GET", "POST" etc. - * @param parms Parsed, percent decoded parameters from URI and, in case of POST, data. - * @param headers Header entries, percent decoded + * + * @param uri + * Percent-decoded URI without parameters, for example + * "/index.cgi" + * @param method + * "GET", "POST" etc. + * @param parms + * Parsed, percent decoded parameters from URI and, in case of + * POST, data. + * @param headers + * Header entries, percent decoded * @return HTTP response, see class Response for details */ @Deprecated - public Response serve(String uri, Method method, Map headers, Map parms, - Map files) { + public Response serve(String uri, Method method, Map headers, Map parms, Map files) { return new Response(Response.Status.NOT_FOUND, MIME_PLAINTEXT, "Not Found"); } @@ -400,8 +426,9 @@ public abstract class NanoHTTPD { *

    *

    * (By default, this returns a 404 "Not Found" plain text error response.) - * - * @param session The HTTP session + * + * @param session + * The HTTP session * @return HTTP response, see class Response for details */ public Response serve(IHTTPSession session) { @@ -424,39 +451,46 @@ public abstract class NanoHTTPD { /** * Decode percent encoded String values. - * - * @param str the percent encoded String - * @return expanded form of the input, for example "foo%20bar" becomes "foo bar" + * + * @param str + * the percent encoded String + * @return expanded form of the input, for example "foo%20bar" becomes + * "foo bar" */ protected String decodePercent(String str) { String decoded = null; try { decoded = URLDecoder.decode(str, "UTF8"); } catch (UnsupportedEncodingException ignored) { - LOG.log(Level.WARNING, "Encoding not supported, ignored", ignored); + LOG.log(Level.WARNING, "Encoding not supported, ignored", ignored); } return decoded; } /** - * Decode parameters from a URL, handing the case where a single parameter name might have been - * supplied several times, by return lists of values. In general these lists will contain a single - * element. - * - * @param parms original 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). + * 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 Map> decodeParameters(Map parms) { return this.decodeParameters(parms.get(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). + * 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 Map> decodeParameters(String queryString) { Map> parms = new HashMap>(); @@ -478,41 +512,53 @@ public abstract class NanoHTTPD { return parms; } - // ------------------------------------------------------------------------------- // + // ------------------------------------------------------------------------------- + // // // // Threading Strategy. // - // ------------------------------------------------------------------------------- // + // ------------------------------------------------------------------------------- + // // /** * Pluggable strategy for asynchronously executing requests. - * - * @param asyncRunner new strategy for handling threads. + * + * @param asyncRunner + * new strategy for handling threads. */ public void setAsyncRunner(AsyncRunner asyncRunner) { this.asyncRunner = asyncRunner; } - // ------------------------------------------------------------------------------- // + // ------------------------------------------------------------------------------- + // // // // Temp file handling strategy. // - // ------------------------------------------------------------------------------- // + // ------------------------------------------------------------------------------- + // // /** * Pluggable strategy for creating and cleaning up temporary files. - * - * @param tempFileManagerFactory new strategy for handling temp files. + * + * @param tempFileManagerFactory + * new strategy for handling temp files. */ public void setTempFileManagerFactory(TempFileManagerFactory tempFileManagerFactory) { this.tempFileManagerFactory = tempFileManagerFactory; } /** - * HTTP Request methods, with the ability to decode a String back to its enum value. + * HTTP Request methods, with the ability to decode a String + * back to its enum value. */ public enum Method { - GET, PUT, POST, DELETE, HEAD, OPTIONS; + GET, + PUT, + POST, + DELETE, + HEAD, + OPTIONS; static Method lookup(String method) { for (Method m : Method.values()) { @@ -528,6 +574,7 @@ public abstract class NanoHTTPD { * Pluggable strategy for asynchronously executing requests. */ public interface AsyncRunner { + void exec(Runnable code); } @@ -535,18 +582,23 @@ public abstract class NanoHTTPD { * Factory to create temp file managers. */ public interface TempFileManagerFactory { + TempFileManager create(); } - // ------------------------------------------------------------------------------- // + // ------------------------------------------------------------------------------- + // // /** * 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.

    + *

    + * 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 TempFileManager { + TempFile createTempFile() throws Exception; void clear(); @@ -555,10 +607,13 @@ public abstract class NanoHTTPD { /** * A temp file. *

    - *

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

    + *

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

    */ public interface TempFile { + OutputStream open() throws Exception; void delete() throws Exception; @@ -569,11 +624,14 @@ public abstract class NanoHTTPD { /** * 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.

    + *

    + * 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 static class DefaultAsyncRunner implements AsyncRunner { + private long requestCount; @Override @@ -589,14 +647,17 @@ public abstract class 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).

    + *

    + * 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 static class DefaultTempFileManager implements TempFileManager { + private final String tmpdir; + private final List tempFiles; public DefaultTempFileManager() { @@ -617,7 +678,7 @@ public abstract class NanoHTTPD { try { file.delete(); } catch (Exception ignored) { - LOG.log(Level.WARNING, "could not delete file ", ignored); + LOG.log(Level.WARNING, "could not delete file ", ignored); } } tempFiles.clear(); @@ -627,11 +688,15 @@ public abstract class NanoHTTPD { /** * Default strategy for creating and cleaning up temporary files. *

    - *

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

    + *

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

    */ public static class DefaultTempFile implements TempFile { + private File file; + private OutputStream fstream; public DefaultTempFile(String tempdir) throws IOException { @@ -660,33 +725,40 @@ public abstract class NanoHTTPD { * HTTP response. Return one of these from serve(). */ public static class Response { + /** * 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; + /** * Headers for the HTTP response. Use addHeader() to add lines. */ private Map header = new HashMap(); + /** * The request method that spawned this response. */ private Method requestMethod; + /** * Use chunkedTransfer */ private boolean chunkedTransfer; /** - * Default constructor: response = Status.OK, mime = MIME_HTML and your supplied message + * Default constructor: response = Status.OK, mime = MIME_HTML and your + * supplied message */ public Response(String msg) { this(Status.OK, MIME_HTML, msg); @@ -710,7 +782,7 @@ public abstract class NanoHTTPD { try { this.data = txt != null ? new ByteArrayInputStream(txt.getBytes("UTF-8")) : null; } catch (java.io.UnsupportedEncodingException uee) { - LOG.log(Level.SEVERE,"encoding problem",uee); + LOG.log(Level.SEVERE, "encoding problem", uee); } } @@ -769,7 +841,7 @@ public abstract class NanoHTTPD { outputStream.flush(); safeClose(data); } catch (IOException ioe) { - LOG.log(Level.SEVERE, "Could not send response to the client", ioe); + LOG.log(Level.SEVERE, "Could not send response to the client", ioe); } } @@ -784,7 +856,7 @@ public abstract class NanoHTTPD { } } - pw.print("Content-Length: "+ size +"\r\n"); + pw.print("Content-Length: " + size + "\r\n"); return size; } @@ -870,7 +942,9 @@ public abstract class NanoHTTPD { } public interface IStatus { + int getRequestStatus(); + String getDescription(); } @@ -878,11 +952,24 @@ public abstract class NanoHTTPD { * 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"), REDIRECT(301, - "Moved Permanently"), NOT_MODIFIED(304, "Not Modified"), BAD_REQUEST(400, "Bad Request"), UNAUTHORIZED(401, - "Unauthorized"), FORBIDDEN(403, "Forbidden"), NOT_FOUND(404, "Not Found"), METHOD_NOT_ALLOWED(405, "Method Not Allowed"), RANGE_NOT_SATISFIABLE(416, - "Requested Range Not Satisfiable"), INTERNAL_ERROR(500, "Internal Server Error"); + SWITCH_PROTOCOL(101, "Switching Protocols"), + OK(200, "OK"), + CREATED(201, "Created"), + ACCEPTED(202, "Accepted"), + NO_CONTENT(204, "No Content"), + PARTIAL_CONTENT(206, "Partial Content"), + REDIRECT(301, "Moved Permanently"), + NOT_MODIFIED(304, "Not Modified"), + BAD_REQUEST(400, "Bad Request"), + UNAUTHORIZED(401, "Unauthorized"), + FORBIDDEN(403, "Forbidden"), + NOT_FOUND(404, "Not Found"), + METHOD_NOT_ALLOWED(405, "Method Not Allowed"), + RANGE_NOT_SATISFIABLE(416, "Requested Range Not Satisfiable"), + INTERNAL_ERROR(500, "Internal Server Error"); + private final int requestStatus; + private final String description; Status(int requestStatus, String description) { @@ -903,6 +990,7 @@ public abstract class NanoHTTPD { } public static final class ResponseException extends Exception { + private static final long serialVersionUID = 6569838532917408380L; private final Response.Status status; @@ -926,6 +1014,7 @@ public abstract class NanoHTTPD { * Default strategy for creating and cleaning up temporary files. */ private class DefaultTempFileManagerFactory implements TempFileManagerFactory { + @Override public TempFileManager create() { return new DefaultTempFileManager(); @@ -933,9 +1022,11 @@ public abstract class NanoHTTPD { } /** - * Handles one session, i.e. parses the HTTP request and returns the response. + * Handles one session, i.e. parses the HTTP request and returns the + * response. */ public interface IHTTPSession { + void execute() throws IOException; Map getParms(); @@ -957,24 +1048,39 @@ public abstract class NanoHTTPD { /** * Adds the files in the request body to the files map. - * @param files map to modify + * + * @param files + * map to modify */ void parseBody(Map files) throws IOException, ResponseException; } protected class HTTPSession implements IHTTPSession { + public static final int BUFSIZE = 8192; + private final TempFileManager tempFileManager; + private final OutputStream outputStream; + private PushbackInputStream inputStream; + private int splitbyte; + private int rlen; + private String uri; + private Method method; + private Map parms; + private Map headers; + private CookieHandler cookies; + private String queryParameterString; + private String remoteIp; public HTTPSession(TempFileManager tempFileManager, InputStream inputStream, OutputStream outputStream) { @@ -997,7 +1103,8 @@ public abstract class NanoHTTPD { // 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! + // Do NOT assume that a single read will get the entire header + // at once! byte[] buf = new byte[BUFSIZE]; splitbyte = 0; rlen = 0; @@ -1030,17 +1137,16 @@ public abstract class NanoHTTPD { } parms = new HashMap(); - if(null == headers) { + if (null == headers) { headers = new HashMap(); } else { headers.clear(); } if (null != remoteIp) { - headers.put("remote-addr", remoteIp); - headers.put("http-client-ip", remoteIp); - } - + headers.put("remote-addr", remoteIp); + headers.put("http-client-ip", remoteIp); + } // Create a BufferedReader for parsing the header. BufferedReader hin = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(buf, 0, rlen))); @@ -1074,7 +1180,7 @@ public abstract class NanoHTTPD { // 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; + throw ste; } catch (IOException ioe) { Response r = new Response(Response.Status.INTERNAL_ERROR, MIME_PLAINTEXT, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage()); r.send(outputStream); @@ -1108,7 +1214,7 @@ public abstract class NanoHTTPD { // Now read all the body and write it to f byte[] buf = new byte[512]; while (rlen >= 0 && size > 0) { - rlen = inputStream.read(buf, 0, (int)Math.min(size, 512)); + rlen = inputStream.read(buf, 0, (int) Math.min(size, 512)); size -= rlen; if (rlen > 0) { randomAccessFile.write(buf, 0, rlen); @@ -1140,7 +1246,8 @@ public abstract class NanoHTTPD { if ("multipart/form-data".equalsIgnoreCase(contentType)) { // Handle multipart/form-data if (!st.hasMoreTokens()) { - throw new ResponseException(Response.Status.BAD_REQUEST, "BAD REQUEST: Content type is multipart/form-data but boundary missing. Usage: GET /example/file.html"); + throw new ResponseException(Response.Status.BAD_REQUEST, + "BAD REQUEST: Content type is multipart/form-data but boundary missing. Usage: GET /example/file.html"); } String boundaryStartString = "boundary="; @@ -1164,10 +1271,12 @@ public abstract class NanoHTTPD { postLine = postLineBuffer.toString().trim(); // Handle application/x-www-form-urlencoded if ("application/x-www-form-urlencoded".equalsIgnoreCase(contentType)) { - decodeParms(postLine, parms); + decodeParms(postLine, parms); } else if (postLine.length() != 0) { - // Special case for raw POST data => create a special files entry "postData" with raw content data - files.put("postData", postLine); + // Special case for raw POST data => create a + // special files entry "postData" with raw content + // data + files.put("postData", postLine); } } } else if (Method.PUT.equals(method)) { @@ -1182,8 +1291,7 @@ public abstract class NanoHTTPD { /** * 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 { + private void decodeHeader(BufferedReader in, Map pre, Map parms, Map headers) throws ResponseException { try { // Read the request line String inLine = in.readLine(); @@ -1237,15 +1345,15 @@ public abstract class NanoHTTPD { /** * Decodes the Multipart Body data and put it into Key/Value pairs. */ - private void decodeMultipartData(String boundary, ByteBuffer fbuf, BufferedReader in, Map parms, - Map files) throws ResponseException { + private void decodeMultipartData(String boundary, ByteBuffer fbuf, BufferedReader in, Map parms, Map files) throws ResponseException { try { int[] bpositions = getBoundaryPositions(fbuf, boundary.getBytes()); int boundarycount = 1; String mpline = in.readLine(); while (mpline != null) { if (!mpline.contains(boundary)) { - throw new ResponseException(Response.Status.BAD_REQUEST, "BAD REQUEST: Content type is multipart/form-data but next chunk does not start with boundary. Usage: GET /example/file.html"); + throw new ResponseException(Response.Status.BAD_REQUEST, + "BAD REQUEST: Content type is multipart/form-data but next chunk does not start with boundary. Usage: GET /example/file.html"); } boundarycount++; Map item = new HashMap(); @@ -1260,7 +1368,8 @@ public abstract class NanoHTTPD { if (mpline != null) { String contentDisposition = item.get("content-disposition"); if (contentDisposition == null) { - throw new ResponseException(Response.Status.BAD_REQUEST, "BAD REQUEST: Content type is multipart/form-data but no content-disposition info found. Usage: GET /example/file.html"); + throw new ResponseException(Response.Status.BAD_REQUEST, + "BAD REQUEST: Content type is multipart/form-data but no content-disposition info found. Usage: GET /example/file.html"); } StringTokenizer st = new StringTokenizer(contentDisposition, ";"); Map disposition = new HashMap(); @@ -1293,14 +1402,14 @@ public abstract class NanoHTTPD { } int offset = stripMultipartHeaders(fbuf, bpositions[boundarycount - 2]); String path = saveTmpFile(fbuf, offset, bpositions[boundarycount - 1] - offset - 4); - if(!files.containsKey(pname)) { - files.put(pname, path); + if (!files.containsKey(pname)) { + files.put(pname, path); } else { - int count = 2; - while(files.containsKey(pname+count)) { - count++; - } - files.put(pname+count, path); + int count = 2; + while (files.containsKey(pname + count)) { + count++; + } + files.put(pname + count, path); } value = disposition.get("filename"); value = value.substring(1, value.length() - 1); @@ -1317,7 +1426,8 @@ public abstract class NanoHTTPD { } /** - * Find byte index separating header from body. It must be the last byte of the first two sequential new lines. + * 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; @@ -1361,7 +1471,8 @@ public abstract class NanoHTTPD { } /** - * Retrieves the content of a sent file and saves it to a temporary file. The full path to the saved file is returned. + * 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 path = ""; @@ -1389,12 +1500,13 @@ public abstract class NanoHTTPD { TempFile tempFile = tempFileManager.createTempFile(); return new RandomAccessFile(tempFile.getName(), "rw"); } catch (Exception e) { - throw new Error(e); // we won't recover, so throw an error + throw new Error(e); // we won't recover, so throw an error } } /** - * It returns the offset separating multipart file headers from the file's data. + * It returns the offset separating multipart file headers from the + * file's data. */ private int stripMultipartHeaders(ByteBuffer b, int offset) { int i; @@ -1407,8 +1519,10 @@ public abstract class NanoHTTPD { } /** - * Decodes parameters in percent-encoded URI-format ( e.g. "name=Jack%20Daniels&pass=Single%20Malt" ) and - * adds them to given Map. NOTE: this doesn't support multiple identical keys due to the simplicity of Map. + * Decodes parameters in percent-encoded URI-format ( e.g. + * "name=Jack%20Daniels&pass=Single%20Malt" ) and adds them to given + * Map. NOTE: this doesn't support multiple identical keys due to the + * simplicity of Map. */ private void decodeParms(String parms, Map p) { if (parms == null) { @@ -1422,8 +1536,7 @@ public abstract class NanoHTTPD { String e = st.nextToken(); int sep = e.indexOf('='); if (sep >= 0) { - p.put(decodePercent(e.substring(0, sep)).trim(), - decodePercent(e.substring(sep + 1))); + p.put(decodePercent(e.substring(0, sep)).trim(), decodePercent(e.substring(sep + 1))); } else { p.put(decodePercent(e).trim(), ""); } @@ -1435,7 +1548,7 @@ public abstract class NanoHTTPD { return parms; } - public String getQueryParameterString() { + public String getQueryParameterString() { return queryParameterString; } @@ -1466,6 +1579,7 @@ public abstract class NanoHTTPD { } public static class Cookie { + private String n, v, e; public Cookie(String name, String value, String expires) { @@ -1499,14 +1613,16 @@ public abstract class NanoHTTPD { } /** - * Provides rudimentary support for cookies. - * Doesn't support 'path', 'secure' nor 'httpOnly'. - * Feel free to improve it and/or add unsupported features. - * + * Provides rudimentary support for cookies. Doesn't support 'path', + * 'secure' nor 'httpOnly'. Feel free to improve it and/or add unsupported + * features. + * * @author LordFokas */ public class CookieHandler implements Iterable { + private HashMap cookies = new HashMap(); + private ArrayList queue = new ArrayList(); public CookieHandler(Map httpHeaders) { @@ -1522,14 +1638,16 @@ public abstract class NanoHTTPD { } } - @Override public Iterator iterator() { + @Override + public Iterator iterator() { return cookies.keySet().iterator(); } /** * Read a cookie from the HTTP Headers. - * - * @param name The cookie's name. + * + * @param name + * The cookie's name. * @return The cookie's value if it exists, null otherwise. */ public String read(String name) { @@ -1538,10 +1656,13 @@ public abstract class NanoHTTPD { /** * Sets a cookie. - * - * @param name The cookie's name. - * @param value The cookie's value. - * @param expires How many days until the cookie expires. + * + * @param name + * The cookie's name. + * @param value + * The cookie's value. + * @param expires + * How many days until the cookie expires. */ public void set(String name, String value, int expires) { queue.add(new Cookie(name, value, Cookie.getHTTPTime(expires))); @@ -1552,18 +1673,23 @@ public abstract class NanoHTTPD { } /** - * Set a cookie with an expiration date from a month ago, effectively deleting it on the client side. - * - * @param name The cookie name. + * 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); } /** - * 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. + * Internally used by the webserver to add all queued cookies into the + * Response's HTTP Headers. + * + * @param response + * The Response object to which headers the queued cookies + * will be added. */ public void unloadQueue(Response response) { for (Cookie cookie : queue) { -- cgit v1.2.3