diff options
31 files changed, 857 insertions, 668 deletions
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.*; * <p/> * <p/> * NanoHTTPD - * <p>Copyright (c) 2012-2013 by Paul S. Hawke, 2001,2005-2013 by Jarno Elonen, 2010 by Konstantinos Togias</p> + * <p> + * Copyright (c) 2012-2013 by Paul S. Hawke, 2001,2005-2013 by Jarno Elonen, + * 2010 by Konstantinos Togias + * </p> * <p/> * <p/> * <b>Features + limitations: </b> @@ -90,8 +92,10 @@ import javax.net.ssl.*; * <li>Only one Java file</li> * <li>Java 5 compatible</li> * <li>Released as open source, Modified BSD licence</li> - * <li>No fixed config files, logging, authorization etc. (Implement yourself if you need them.)</li> - * <li>Supports parameter parsing of GET and POST methods (+ rudimentary PUT support in 1.25)</li> + * <li>No fixed config files, logging, authorization etc. (Implement yourself if + * you need them.)</li> + * <li>Supports parameter parsing of GET and POST methods (+ rudimentary PUT + * support in 1.25)</li> * <li>Supports both dynamic content and file serving</li> * <li>Supports file upload (since version 1.2, 2010)</li> * <li>Supports partial content (streaming)</li> @@ -106,7 +110,8 @@ import javax.net.ssl.*; * <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> + * <li>All header names are converted to lower case so they don't vary between + * browsers/clients</li> * <p/> * </ul> * <p/> @@ -118,43 +123,56 @@ import javax.net.ssl.*; * <p/> * </ul> * <p/> - * See the separate "LICENSE.md" file for the distribution license (Modified BSD licence) + * See the separate "LICENSE.md" file for the distribution license (Modified BSD + * licence) */ public abstract class NanoHTTPD { + /** * Maximum time to wait on Socket.getInputStream().read() (in milliseconds) - * This is required as the Keep-Alive HTTP connections would otherwise - * block the socket reading thread forever (or as long the browser is open). + * 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<Socket> openConnections = new HashSet<Socket>(); + 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 { * <p/> * <p/> * (By default, this returns a 404 "Not Found" plain text error response.) - * - * @param uri Percent-decoded URI without parameters, for example "/index.cgi" - * @param method "GET", "POST" etc. - * @param parms Parsed, percent decoded parameters from URI and, in case of POST, data. - * @param headers Header entries, percent decoded + * + * @param uri + * Percent-decoded URI without parameters, for example + * "/index.cgi" + * @param method + * "GET", "POST" etc. + * @param parms + * Parsed, percent decoded parameters from URI and, in case of + * POST, data. + * @param headers + * Header entries, percent decoded * @return HTTP response, see class Response for details */ @Deprecated - public Response serve(String uri, Method method, Map<String, String> headers, Map<String, String> parms, - Map<String, String> files) { + public Response serve(String uri, Method method, Map<String, String> headers, Map<String, String> parms, Map<String, String> files) { return new Response(Response.Status.NOT_FOUND, MIME_PLAINTEXT, "Not Found"); } @@ -400,8 +426,9 @@ public abstract class NanoHTTPD { * <p/> * <p/> * (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 <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" + * + * @param str + * the percent encoded <code>String</code> + * @return expanded form of the input, for example "foo%20bar" becomes + * "foo bar" */ protected String decodePercent(String str) { String decoded = null; try { decoded = URLDecoder.decode(str, "UTF8"); } catch (UnsupportedEncodingException ignored) { - 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 <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). + * 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 Map<String, List<String>> decodeParameters(Map<String, String> 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 <code>String</code> (parameter name) to <code>List<String></code> (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 <code>String</code> (parameter name) to + * <code>List<String></code> (a list of the values supplied). */ protected Map<String, List<String>> decodeParameters(String queryString) { Map<String, List<String>> parms = new HashMap<String, List<String>>(); @@ -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 <code>String</code> back to its enum value. + * HTTP Request methods, with the ability to decode a <code>String</code> + * back to its enum value. */ public enum Method { - GET, PUT, POST, DELETE, HEAD, OPTIONS; + 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. * <p/> - * <p>Temp file managers are created 1-to-1 with incoming requests, to create and cleanup - * temporary files created as a result of handling the request.</p> + * <p> + * Temp file managers are created 1-to-1 with incoming requests, to create + * and cleanup temporary files created as a result of handling the request. + * </p> */ public interface TempFileManager { + TempFile createTempFile() throws Exception; void clear(); @@ -555,10 +607,13 @@ public abstract class NanoHTTPD { /** * A temp file. * <p/> - * <p>Temp files are responsible for managing the actual temporary storage and cleaning - * themselves up when no longer needed.</p> + * <p> + * Temp files are responsible for managing the actual temporary storage and + * cleaning themselves up when no longer needed. + * </p> */ public interface TempFile { + OutputStream open() throws Exception; void delete() throws Exception; @@ -569,11 +624,14 @@ public abstract class NanoHTTPD { /** * Default threading strategy for NanoHTTPD. * <p/> - * <p>By default, the server spawns a new Thread for every incoming request. These are set - * to <i>daemon</i> status, and named according to the request number. The name is - * useful when profiling the application.</p> + * <p> + * By default, the server spawns a new Thread for every incoming request. + * These are set to <i>daemon</i> status, and named according to the request + * number. The name is useful when profiling the application. + * </p> */ public static class DefaultAsyncRunner implements AsyncRunner { + private long requestCount; @Override @@ -589,14 +647,17 @@ public abstract class NanoHTTPD { /** * Default strategy for creating and cleaning up temporary files. * <p/> - * <p>This class stores its files in the standard location (that is, - * wherever <code>java.io.tmpdir</code> points to). Files are added - * to an internal list, and deleted when no longer needed (that is, - * when <code>clear()</code> is invoked at the end of processing a - * request).</p> + * <p> + * This class stores its files in the standard location (that is, wherever + * <code>java.io.tmpdir</code> points to). Files are added to an internal + * list, and deleted when no longer needed (that is, when + * <code>clear()</code> is invoked at the end of processing a request). + * </p> */ public static class DefaultTempFileManager implements TempFileManager { + private final String tmpdir; + private final List<TempFile> 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. * <p/> - * <p>By default, files are created by <code>File.createTempFile()</code> in - * the directory specified.</p> + * <p> + * By default, files are created by <code>File.createTempFile()</code> in + * the directory specified. + * </p> */ public static class DefaultTempFile implements TempFile { + private File file; + private OutputStream fstream; public DefaultTempFile(String tempdir) throws IOException { @@ -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<String, String> header = new HashMap<String, String>(); + /** * 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<String, String> 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<String, String> 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<String, String> parms; + private Map<String, String> 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<String, String>(); - if(null == headers) { + if (null == headers) { headers = new HashMap<String, String>(); } 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<String, String> pre, Map<String, String> parms, Map<String, String> headers) - throws ResponseException { + private void decodeHeader(BufferedReader in, Map<String, String> pre, Map<String, String> parms, Map<String, String> headers) throws ResponseException { try { // Read the request line String inLine = in.readLine(); @@ -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<String, String> parms, - Map<String, String> files) throws ResponseException { + private void decodeMultipartData(String boundary, ByteBuffer fbuf, BufferedReader in, Map<String, String> parms, Map<String, String> files) throws ResponseException { try { int[] bpositions = getBoundaryPositions(fbuf, boundary.getBytes()); int boundarycount = 1; String mpline = in.readLine(); while (mpline != null) { if (!mpline.contains(boundary)) { - throw new ResponseException(Response.Status.BAD_REQUEST, "BAD REQUEST: Content type is multipart/form-data but next chunk does not start with boundary. Usage: GET /example/file.html"); + throw new ResponseException(Response.Status.BAD_REQUEST, + "BAD REQUEST: Content type is multipart/form-data but next chunk does not start with boundary. Usage: GET /example/file.html"); } boundarycount++; Map<String, String> item = new HashMap<String, String>(); @@ -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<String, String> disposition = new HashMap<String, String>(); @@ -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<String, String> 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<String> { + private HashMap<String, String> cookies = new HashMap<String, String>(); + private ArrayList<Cookie> queue = new ArrayList<Cookie>(); public CookieHandler(Map<String, String> httpHeaders) { @@ -1522,14 +1638,16 @@ public abstract class NanoHTTPD { } } - @Override public Iterator<String> iterator() { + @Override + public Iterator<String> 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) { diff --git a/core/src/test/java/fi/iki/elonen/HttpChunkedResponseTest.java b/core/src/test/java/fi/iki/elonen/HttpChunkedResponseTest.java index ab0ba3e..779e43f 100644 --- a/core/src/test/java/fi/iki/elonen/HttpChunkedResponseTest.java +++ b/core/src/test/java/fi/iki/elonen/HttpChunkedResponseTest.java @@ -40,29 +40,30 @@ import java.io.PipedInputStream; import static fi.iki.elonen.NanoHTTPD.Response.Status.OK; public class HttpChunkedResponseTest extends HttpServerTest { + @org.junit.Test public void thatChunkedContentIsChunked() throws Exception { PipedInputStream pipedInputStream = new ChunkedInputStream(new String[]{ - "some", - "thing which is longer than sixteen characters", - "whee!", - "" + "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", - "" + "HTTP/1.1 200 OK", + "Content-Type: what/ever", + "Date: .*", + "Connection: keep-alive", + "Transfer-Encoding: chunked", + "", + "4", + "some", + "2d", + "thing which is longer than sixteen characters", + "5", + "whee!", + "0", + "" }; testServer.response = new NanoHTTPD.Response(OK, "what/ever", pipedInputStream); testServer.response.setChunkedTransfer(true); @@ -73,7 +74,9 @@ public class HttpChunkedResponseTest extends HttpServerTest { } private static class ChunkedInputStream extends PipedInputStream { + int chunk = 0; + String[] chunks; private ChunkedInputStream(String[] chunks) { diff --git a/core/src/test/java/fi/iki/elonen/HttpDeleteRequestTest.java b/core/src/test/java/fi/iki/elonen/HttpDeleteRequestTest.java index b9883a3..4756b1d 100644 --- a/core/src/test/java/fi/iki/elonen/HttpDeleteRequestTest.java +++ b/core/src/test/java/fi/iki/elonen/HttpDeleteRequestTest.java @@ -50,12 +50,12 @@ public class HttpDeleteRequestTest extends HttpServerTest { ByteArrayOutputStream outputStream = invokeServer("DELETE " + URI + " HTTP/1.1"); String[] expected = { - "HTTP/1.1 204 No Content", - "Content-Type: text/html", - "Date: .*", - "Connection: keep-alive", - "Content-Length: 0", - "" + "HTTP/1.1 204 No Content", + "Content-Type: text/html", + "Date: .*", + "Connection: keep-alive", + "Content-Length: 0", + "" }; assertResponse(outputStream, expected); @@ -63,17 +63,17 @@ public class HttpDeleteRequestTest extends HttpServerTest { @Test public void testDeleteRequestThatDoesntSendBackResponseBody_NullString() throws Exception { - testServer.response = new NanoHTTPD.Response(NanoHTTPD.Response.Status.NO_CONTENT, NanoHTTPD.MIME_HTML, (String)null); + testServer.response = new NanoHTTPD.Response(NanoHTTPD.Response.Status.NO_CONTENT, NanoHTTPD.MIME_HTML, (String) null); ByteArrayOutputStream outputStream = invokeServer("DELETE " + URI + " HTTP/1.1"); String[] expected = { - "HTTP/1.1 204 No Content", - "Content-Type: text/html", - "Date: .*", - "Connection: keep-alive", - "Content-Length: 0", - "" + "HTTP/1.1 204 No Content", + "Content-Type: text/html", + "Date: .*", + "Connection: keep-alive", + "Content-Length: 0", + "" }; assertResponse(outputStream, expected); @@ -81,17 +81,17 @@ public class HttpDeleteRequestTest extends HttpServerTest { @Test public void testDeleteRequestThatDoesntSendBackResponseBody_NullInputStream() throws Exception { - testServer.response = new NanoHTTPD.Response(NanoHTTPD.Response.Status.NO_CONTENT, NanoHTTPD.MIME_HTML, (InputStream)null); + testServer.response = new NanoHTTPD.Response(NanoHTTPD.Response.Status.NO_CONTENT, NanoHTTPD.MIME_HTML, (InputStream) null); ByteArrayOutputStream outputStream = invokeServer("DELETE " + URI + " HTTP/1.1"); String[] expected = { - "HTTP/1.1 204 No Content", - "Content-Type: text/html", - "Date: .*", - "Connection: keep-alive", - "Content-Length: 0", - "" + "HTTP/1.1 204 No Content", + "Content-Type: text/html", + "Date: .*", + "Connection: keep-alive", + "Content-Length: 0", + "" }; assertResponse(outputStream, expected); @@ -104,13 +104,13 @@ public class HttpDeleteRequestTest extends HttpServerTest { ByteArrayOutputStream outputStream = invokeServer("DELETE " + URI + " HTTP/1.1"); String[] expected = { - "HTTP/1.1 200 OK", - "Content-Type: application/xml", - "Date: .*", - "Connection: keep-alive", - "Content-Length: 8", - "", - "<body />" + "HTTP/1.1 200 OK", + "Content-Type: application/xml", + "Date: .*", + "Connection: keep-alive", + "Content-Length: 8", + "", + "<body />" }; assertResponse(outputStream, expected); @@ -123,13 +123,13 @@ public class HttpDeleteRequestTest extends HttpServerTest { ByteArrayOutputStream outputStream = invokeServer("DELETE " + URI + " HTTP/1.1"); String[] expected = { - "HTTP/1.1 202 Accepted", - "Content-Type: application/xml", - "Date: .*", - "Connection: keep-alive", - "Content-Length: 8", - "", - "<body />" + "HTTP/1.1 202 Accepted", + "Content-Type: application/xml", + "Date: .*", + "Connection: keep-alive", + "Content-Length: 8", + "", + "<body />" }; assertResponse(outputStream, expected); diff --git a/core/src/test/java/fi/iki/elonen/HttpGetRequestTest.java b/core/src/test/java/fi/iki/elonen/HttpGetRequestTest.java index aa31397..9980d2e 100644 --- a/core/src/test/java/fi/iki/elonen/HttpGetRequestTest.java +++ b/core/src/test/java/fi/iki/elonen/HttpGetRequestTest.java @@ -47,12 +47,12 @@ public class HttpGetRequestTest extends HttpServerTest { ByteArrayOutputStream outputStream = invokeServer("GET " + URI + " HTTP/1.1"); String[] expected = { - "HTTP/1.1 200 OK", - "Content-Type: text/html", - "Date: .*", - "Connection: keep-alive", - "Content-Length: 0", - "" + "HTTP/1.1 200 OK", + "Content-Type: text/html", + "Date: .*", + "Connection: keep-alive", + "Content-Length: 0", + "" }; assertResponse(outputStream, expected); @@ -65,13 +65,13 @@ public class HttpGetRequestTest extends HttpServerTest { ByteArrayOutputStream outputStream = invokeServer("GET " + URI + " HTTP/1.1"); String[] expected = { - "HTTP/1.1 200 OK", - "Content-Type: text/html", - "Date: .*", - "Connection: keep-alive", - "Content-Length: 8", - "", - responseBody + "HTTP/1.1 200 OK", + "Content-Type: text/html", + "Date: .*", + "Connection: keep-alive", + "Content-Length: 8", + "", + responseBody }; assertResponse(outputStream, expected); @@ -199,6 +199,7 @@ public class HttpGetRequestTest extends HttpServerTest { invokeServer("GET " + URI + "?foo=bar&foo=baz&zot&zim= HTTP/1.1"); assertEquals(testServer.decodedParamters, testServer.decodedParamtersFromParameter); } - // -------------------------------------------------------------------------------------------------------- // + // -------------------------------------------------------------------------------------------------------- + // // } diff --git a/core/src/test/java/fi/iki/elonen/HttpHeadRequestTest.java b/core/src/test/java/fi/iki/elonen/HttpHeadRequestTest.java index 2368211..2602fd9 100644 --- a/core/src/test/java/fi/iki/elonen/HttpHeadRequestTest.java +++ b/core/src/test/java/fi/iki/elonen/HttpHeadRequestTest.java @@ -41,6 +41,7 @@ import java.util.List; import static junit.framework.Assert.*; public class HttpHeadRequestTest extends HttpServerTest { + @Override public void setUp() { super.setUp(); @@ -53,12 +54,12 @@ public class HttpHeadRequestTest extends HttpServerTest { ByteArrayOutputStream outputStream = invokeServer("HEAD " + URI + " HTTP/1.1"); String[] expected = { - "HTTP/1.1 200 OK", - "Content-Type: text/html", - "Date: .*", - "Connection: keep-alive", - "Content-Length: 8", - "" + "HTTP/1.1 200 OK", + "Content-Type: text/html", + "Date: .*", + "Connection: keep-alive", + "Content-Length: 8", + "" }; assertResponse(outputStream, expected); @@ -186,6 +187,7 @@ public class HttpHeadRequestTest extends HttpServerTest { invokeServer("HEAD " + URI + "?foo=bar&foo=baz&zot&zim= HTTP/1.1"); assertEquals(testServer.decodedParamters, testServer.decodedParamtersFromParameter); } - // -------------------------------------------------------------------------------------------------------- // + // -------------------------------------------------------------------------------------------------------- + // // } diff --git a/core/src/test/java/fi/iki/elonen/HttpKeepAliveTest.java b/core/src/test/java/fi/iki/elonen/HttpKeepAliveTest.java index a20f60a..829d5c5 100644 --- a/core/src/test/java/fi/iki/elonen/HttpKeepAliveTest.java +++ b/core/src/test/java/fi/iki/elonen/HttpKeepAliveTest.java @@ -47,41 +47,46 @@ public class HttpKeepAliveTest extends HttpServerTest { public void testManyGetRequests() throws Exception { String request = "GET " + URI + " HTTP/1.1\r\n\r\n"; String[] expected = { - "HTTP/1.1 200 OK", - "Content-Type: text/html", - "Date: .*", - "Connection: keep-alive", - "Content-Length: 0", - "" + "HTTP/1.1 200 OK", + "Content-Type: text/html", + "Date: .*", + "Connection: keep-alive", + "Content-Length: 0", + "" }; testManyRequests(request, expected); } - + @Test public void testManyPutRequests() throws Exception { String data = "BodyData 1\nLine 2"; String request = "PUT " + URI + " HTTP/1.1\r\nContent-Length: " + data.length() + "\r\n\r\n" + data; String[] expected = { - "HTTP/1.1 200 OK", - "Content-Type: text/html", - "Date: .*", - "Connection: keep-alive", - "Content-Length: 0", - "" + "HTTP/1.1 200 OK", + "Content-Type: text/html", + "Date: .*", + "Connection: keep-alive", + "Content-Length: 0", + "" }; testManyRequests(request, expected); } private Throwable error = null; - + /** - * Issue the given request many times to check whether an error occurs. - * For this test, a small stack size is used, since a stack overflow is among the possible errors. - * @param request The request to issue - * @param expected The expected response + * Issue the given request many times to check whether an error occurs. For + * this test, a small stack size is used, since a stack overflow is among + * the possible errors. + * + * @param request + * The request to issue + * @param expected + * The expected response */ public void testManyRequests(final String request, final String[] expected) throws Exception { Runnable r = new Runnable() { + public void run() { try { PipedOutputStream requestStream = new PipedOutputStream(); @@ -108,7 +113,7 @@ public class HttpKeepAliveTest extends HttpServerTest { t.start(); t.join(); if (error != null) { - fail(""+error); + fail("" + error); error.printStackTrace(); } } diff --git a/core/src/test/java/fi/iki/elonen/HttpParsingTest.java b/core/src/test/java/fi/iki/elonen/HttpParsingTest.java index 7780651..9879bbe 100644 --- a/core/src/test/java/fi/iki/elonen/HttpParsingTest.java +++ b/core/src/test/java/fi/iki/elonen/HttpParsingTest.java @@ -41,6 +41,7 @@ import java.net.URLEncoder; import static junit.framework.Assert.assertEquals; public class HttpParsingTest extends HttpServerTest { + @Test public void testNormalCharacters() throws Exception { for (int i = 0x20; i < 0x80; i++) { diff --git a/core/src/test/java/fi/iki/elonen/HttpPostRequestTest.java b/core/src/test/java/fi/iki/elonen/HttpPostRequestTest.java index 4853b00..c83a18d 100644 --- a/core/src/test/java/fi/iki/elonen/HttpPostRequestTest.java +++ b/core/src/test/java/fi/iki/elonen/HttpPostRequestTest.java @@ -47,11 +47,17 @@ import static junit.framework.Assert.assertEquals; public class HttpPostRequestTest extends HttpServerTest { public static final String CONTENT_LENGTH = "Content-Length: "; + public static final String FIELD = "caption"; + public static final String VALUE = "Summer vacation"; + public static final String FIELD2 = "location"; + public static final String VALUE2 = "Grand Canyon"; + public static final String POST_RAW_CONTENT_FILE_ENTRY = "postData"; + public static final String VALUE_TEST_SIMPLE_RAW_DATA_WITH_AMPHASIS = "Test raw data & Result value"; @Test @@ -61,7 +67,7 @@ public class HttpPostRequestTest extends HttpServerTest { int size = content.length() + header.length(); int contentLengthHeaderValueSize = String.valueOf(size).length(); int contentLength = size + contentLengthHeaderValueSize + CONTENT_LENGTH.length(); - String input = header + CONTENT_LENGTH + (contentLength+4) + "\r\n\r\n" + content; + String input = header + CONTENT_LENGTH + (contentLength + 4) + "\r\n\r\n" + content; invokeServer(input); assertEquals(0, testServer.parms.size()); assertEquals(1, testServer.files.size()); @@ -71,17 +77,12 @@ public class HttpPostRequestTest extends HttpServerTest { @Test public void testSimplePostWithSingleMultipartFormField() throws Exception { String divider = UUID.randomUUID().toString(); - String header = "POST " + URI + " HTTP/1.1\nContent-Type: " + - "multipart/form-data; boundary=" + divider + "\n"; - String content = "--" + divider + "\n" + - "Content-Disposition: form-data; name=\""+FIELD+"\"\n" + - "\n" + - VALUE +"\n" + - "--" + divider + "--\n"; + String header = "POST " + URI + " HTTP/1.1\nContent-Type: " + "multipart/form-data; boundary=" + divider + "\n"; + String content = "--" + divider + "\n" + "Content-Disposition: form-data; name=\"" + FIELD + "\"\n" + "\n" + VALUE + "\n" + "--" + divider + "--\n"; int size = content.length() + header.length(); int contentLengthHeaderValueSize = String.valueOf(size).length(); int contentLength = size + contentLengthHeaderValueSize + CONTENT_LENGTH.length(); - String input = header + CONTENT_LENGTH + (contentLength+4) + "\r\n\r\n" + content; + String input = header + CONTENT_LENGTH + (contentLength + 4) + "\r\n\r\n" + content; invokeServer(input); assertEquals(1, testServer.parms.size()); @@ -91,20 +92,14 @@ public class HttpPostRequestTest extends HttpServerTest { @Test public void testPostWithMultipleMultipartFormFields() throws Exception { String divider = UUID.randomUUID().toString(); - String header = "POST " + URI + " HTTP/1.1\nContent-Type: " + - "multipart/form-data; boundary=" + divider + "\n"; - String content = "--" + divider + "\n" + - "Content-Disposition: form-data; name=\""+FIELD+"\"\n" + - "\n" + - VALUE +"\n" +"--" + divider + "\n" + - "Content-Disposition: form-data; name=\""+FIELD2+"\"\n" + - "\n" + - VALUE2 +"\n" + - "--" + divider + "--\n"; + String header = "POST " + URI + " HTTP/1.1\nContent-Type: " + "multipart/form-data; boundary=" + divider + "\n"; + String content = + "--" + divider + "\n" + "Content-Disposition: form-data; name=\"" + FIELD + "\"\n" + "\n" + VALUE + "\n" + "--" + divider + "\n" + + "Content-Disposition: form-data; name=\"" + FIELD2 + "\"\n" + "\n" + VALUE2 + "\n" + "--" + divider + "--\n"; int size = content.length() + header.length(); int contentLengthHeaderValueSize = String.valueOf(size).length(); int contentLength = size + contentLengthHeaderValueSize + CONTENT_LENGTH.length(); - String input = header + CONTENT_LENGTH + (contentLength+4) + "\r\n\r\n" + content; + String input = header + CONTENT_LENGTH + (contentLength + 4) + "\r\n\r\n" + content; invokeServer(input); assertEquals(2, testServer.parms.size()); @@ -115,75 +110,71 @@ public class HttpPostRequestTest extends HttpServerTest { @Test public void testPostWithMultipleMultipartFormFieldsWhereContentTypeWasSeparatedByComma() throws Exception { String divider = UUID.randomUUID().toString(); - String header = "POST " + URI + " HTTP/1.1\nContent-Type: " + - "multipart/form-data, boundary=" + divider + "\n"; - String content = "--" + divider + "\n" + - "Content-Disposition: form-data; name=\""+FIELD+"\"\n" + - "\n" + - VALUE +"\n" +"--" + divider + "\n" + - "Content-Disposition: form-data; name=\""+FIELD2+"\"\n" + - "\n" + - VALUE2 +"\n" + - "--" + divider + "--\n"; + String header = "POST " + URI + " HTTP/1.1\nContent-Type: " + "multipart/form-data, boundary=" + divider + "\n"; + String content = + "--" + divider + "\n" + "Content-Disposition: form-data; name=\"" + FIELD + "\"\n" + "\n" + VALUE + "\n" + "--" + divider + "\n" + + "Content-Disposition: form-data; name=\"" + FIELD2 + "\"\n" + "\n" + VALUE2 + "\n" + "--" + divider + "--\n"; int size = content.length() + header.length(); int contentLengthHeaderValueSize = String.valueOf(size).length(); int contentLength = size + contentLengthHeaderValueSize + CONTENT_LENGTH.length(); - String input = header + CONTENT_LENGTH + (contentLength+4) + "\r\n\r\n" + content; + String input = header + CONTENT_LENGTH + (contentLength + 4) + "\r\n\r\n" + content; invokeServer(input); assertEquals(2, testServer.parms.size()); assertEquals(VALUE, testServer.parms.get(FIELD)); assertEquals(VALUE2, testServer.parms.get(FIELD2)); } - + @Test public void testPostWithMultipartFormUpload() throws Exception { String filename = "GrandCanyon.txt"; String fileContent = VALUE; String input = preparePostWithMultipartForm(filename, fileContent); - + invokeServer(input); - + assertEquals(1, testServer.parms.size()); BufferedReader reader = new BufferedReader(new FileReader(testServer.files.get(FIELD))); List<String> lines = readLinesFromFile(reader); - assertLinesOfText(new String[]{fileContent}, lines); + assertLinesOfText(new String[]{ + fileContent + }, lines); } - + @Test public void testPostWithMultipartFormUploadFilenameHasSpaces() throws Exception { - String fileNameWithSpace = "Grand Canyon.txt"; - String fileContent = VALUE; - String input = preparePostWithMultipartForm(fileNameWithSpace, fileContent); - - invokeServer(input); - - String fileNameAfter = new ArrayList<String>(testServer.parms.values()).get(0); - - assertEquals(fileNameWithSpace, fileNameAfter); + String fileNameWithSpace = "Grand Canyon.txt"; + String fileContent = VALUE; + String input = preparePostWithMultipartForm(fileNameWithSpace, fileContent); + + invokeServer(input); + + String fileNameAfter = new ArrayList<String>(testServer.parms.values()).get(0); + + assertEquals(fileNameWithSpace, fileNameAfter); } - + /** * contains common preparation steps for testing POST with Multipart Form - * @param fileName Name of file to be uploaded - * @param fileContent Content of file to be uploaded - * @return input String with POST request complete information including header, length and content + * + * @param fileName + * Name of file to be uploaded + * @param fileContent + * Content of file to be uploaded + * @return input String with POST request complete information including + * header, length and content */ private String preparePostWithMultipartForm(String fileName, String fileContent) { String divider = UUID.randomUUID().toString(); - String header = "POST " + URI + " HTTP/1.1\nContent-Type: " + - "multipart/form-data, boundary=" + divider + "\n"; - String content = "--" + divider + "\n" + - "Content-Disposition: form-data; name=\""+FIELD+"\"; filename=\""+fileName+"\"\n" + - "Content-Type: image/jpeg\r\n"+ - "\r\n" + - fileContent +"\r\n" + - "--" + divider + "--\n"; + String header = "POST " + URI + " HTTP/1.1\nContent-Type: " + "multipart/form-data, boundary=" + divider + "\n"; + String content = + "--" + divider + "\n" + "Content-Disposition: form-data; name=\"" + FIELD + "\"; filename=\"" + fileName + "\"\n" + "Content-Type: image/jpeg\r\n" + "\r\n" + + fileContent + "\r\n" + "--" + divider + "--\n"; int size = content.length() + header.length(); int contentLengthHeaderValueSize = String.valueOf(size).length(); int contentLength = size + contentLengthHeaderValueSize + CONTENT_LENGTH.length(); - String input = header + CONTENT_LENGTH + (contentLength+5) + "\r\n\r\n" + content; - + String input = header + CONTENT_LENGTH + (contentLength + 5) + "\r\n\r\n" + content; + return input; } diff --git a/core/src/test/java/fi/iki/elonen/HttpPutRequestTest.java b/core/src/test/java/fi/iki/elonen/HttpPutRequestTest.java index 71f3f97..e2755f9 100644 --- a/core/src/test/java/fi/iki/elonen/HttpPutRequestTest.java +++ b/core/src/test/java/fi/iki/elonen/HttpPutRequestTest.java @@ -49,12 +49,12 @@ public class HttpPutRequestTest extends HttpServerTest { ByteArrayOutputStream outputStream = invokeServer("PUT " + URI + " HTTP/1.1\r\n\r\nBodyData 1\nLine 2"); String[] expectedOutput = { - "HTTP/1.1 200 OK", - "Content-Type: text/html", - "Date: .*", - "Connection: keep-alive", - "Content-Length: 0", - "" + "HTTP/1.1 200 OK", + "Content-Type: text/html", + "Date: .*", + "Connection: keep-alive", + "Content-Length: 0", + "" }; assertResponse(outputStream, expectedOutput); @@ -63,8 +63,8 @@ public class HttpPutRequestTest extends HttpServerTest { BufferedReader reader = null; try { String[] expectedInputToServeMethodViaFile = { - "BodyData 1", - "Line 2" + "BodyData 1", + "Line 2" }; reader = new BufferedReader(new FileReader(testServer.files.get("content"))); List<String> lines = readLinesFromFile(reader); diff --git a/core/src/test/java/fi/iki/elonen/HttpServerTest.java b/core/src/test/java/fi/iki/elonen/HttpServerTest.java index 82454bc..f7fbb7b 100644 --- a/core/src/test/java/fi/iki/elonen/HttpServerTest.java +++ b/core/src/test/java/fi/iki/elonen/HttpServerTest.java @@ -47,12 +47,14 @@ import java.util.Map; import static junit.framework.Assert.*; /** - * @author Paul S. Hawke (paul.hawke@gmail.com) - * On: 3/10/13 at 8:32 PM + * @author Paul S. Hawke (paul.hawke@gmail.com) On: 3/10/13 at 8:32 PM */ public class HttpServerTest { + public static final String URI = "http://www.myserver.org/pub/WWW/someFile.html"; + protected TestServer testServer; + private TestTempFileManager tempFileManager; @Before @@ -77,12 +79,10 @@ public class HttpServerTest { } protected void assertLinesOfText(String[] expected, List<String> lines) { -// assertEquals(expected.length, lines.size()); + // 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])); + assertTrue("Output line " + i + " doesn't match expectation.\n" + " Output: " + line + "\n" + "Expected: " + expected[i], line.matches(expected[i])); } } @@ -93,7 +93,7 @@ public class HttpServerTest { try { session.execute(); } catch (IOException e) { - fail(""+e); + fail("" + e); e.printStackTrace(); } return outputStream; @@ -117,6 +117,7 @@ public class HttpServerTest { } public static class TestTempFileManager extends NanoHTTPD.DefaultTempFileManager { + public void _clear() { super.clear(); } @@ -128,14 +129,23 @@ public class HttpServerTest { } public static class TestServer extends NanoHTTPD { + public Response response = new Response(""); + public String uri; + public Method method; + public Map<String, String> header; + public Map<String, String> parms; + public Map<String, String> files; + public Map<String, List<String>> decodedParamters; + public Map<String, List<String>> decodedParamtersFromParameter; + public String queryParameterString; public TestServer() { @@ -150,7 +160,8 @@ public class HttpServerTest { return new HTTPSession(tempFileManager, inputStream, outputStream, inetAddress); } - @Override public Response serve(IHTTPSession session) { + @Override + public Response serve(IHTTPSession session) { this.uri = session.getUri(); this.method = session.getMethod(); this.header = session.getHeaders(); diff --git a/core/src/test/java/fi/iki/elonen/HttpSessionHeadersTest.java b/core/src/test/java/fi/iki/elonen/HttpSessionHeadersTest.java index a3d0f81..3402533 100644 --- a/core/src/test/java/fi/iki/elonen/HttpSessionHeadersTest.java +++ b/core/src/test/java/fi/iki/elonen/HttpSessionHeadersTest.java @@ -43,7 +43,9 @@ import java.net.InetAddress; import static org.junit.Assert.assertEquals; public class HttpSessionHeadersTest extends HttpServerTest { + private static final String DUMMY_REQUEST_CONTENT = "dummy request content"; + private static final TestTempFileManager TEST_TEMP_FILE_MANAGER = new TestTempFileManager(); @Override @@ -56,8 +58,12 @@ public class HttpSessionHeadersTest extends HttpServerTest { public void testHeadersRemoteIp() throws Exception { ByteArrayInputStream inputStream = new ByteArrayInputStream(DUMMY_REQUEST_CONTENT.getBytes()); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - String[] ipAddresses = { "127.0.0.1", "192.168.1.1", "192.30.252.129" }; - for(String ipAddress : ipAddresses) { + String[] ipAddresses = { + "127.0.0.1", + "192.168.1.1", + "192.30.252.129" + }; + for (String ipAddress : ipAddresses) { InetAddress inetAddress = InetAddress.getByName(ipAddress); NanoHTTPD.HTTPSession session = testServer.createSession(TEST_TEMP_FILE_MANAGER, inputStream, outputStream, inetAddress); assertEquals(ipAddress, session.getHeaders().get("remote-addr")); diff --git a/core/src/test/java/fi/iki/elonen/InvalidRequestTest.java b/core/src/test/java/fi/iki/elonen/InvalidRequestTest.java index fd3d196..2c40cd1 100644 --- a/core/src/test/java/fi/iki/elonen/InvalidRequestTest.java +++ b/core/src/test/java/fi/iki/elonen/InvalidRequestTest.java @@ -41,7 +41,7 @@ public class InvalidRequestTest extends HttpServerTest { @Test public void testGetRequestWithoutProtocol() { - invokeServer("GET " + URI + "\r\nX-Important-Header: foo" ); + invokeServer("GET " + URI + "\r\nX-Important-Header: foo"); assertNotNull(testServer.parms); assertTrue(testServer.header.size() > 0); @@ -58,10 +58,9 @@ public class InvalidRequestTest extends HttpServerTest { assertNotNull(testServer.uri); } - @Test public void testGetRequestWithProtocol() { - invokeServer("GET " + URI + " HTTP/1.1\r\nX-Important-Header: foo" ); + invokeServer("GET " + URI + " HTTP/1.1\r\nX-Important-Header: foo"); assertNotNull(testServer.parms); assertTrue(testServer.header.size() > 0); @@ -77,4 +76,4 @@ public class InvalidRequestTest extends HttpServerTest { assertNotNull(testServer.files); assertNotNull(testServer.uri); } -}
\ No newline at end of file +} diff --git a/core/src/test/java/fi/iki/elonen/integration/CookieIntegrationTest.java b/core/src/test/java/fi/iki/elonen/integration/CookieIntegrationTest.java index 7a8cfcb..f765a38 100644 --- a/core/src/test/java/fi/iki/elonen/integration/CookieIntegrationTest.java +++ b/core/src/test/java/fi/iki/elonen/integration/CookieIntegrationTest.java @@ -48,8 +48,7 @@ import java.util.List; import static org.junit.Assert.*; /** - * @author Paul S. Hawke (paul.hawke@gmail.com) - * On: 9/2/13 at 10:10 PM + * @author Paul S. Hawke (paul.hawke@gmail.com) On: 9/2/13 at 10:10 PM */ public class CookieIntegrationTest extends IntegrationTestBase<CookieIntegrationTest.CookieTestServer> { @@ -92,19 +91,23 @@ public class CookieIntegrationTest extends IntegrationTestBase<CookieIntegration assertTrue(testServer.cookiesReceived.get(0).getHTTPHeader().contains("name=value")); } - @Override public CookieTestServer createTestServer() { + @Override + public CookieTestServer createTestServer() { return new CookieTestServer(); } public static class CookieTestServer extends NanoHTTPD { + List<Cookie> cookiesReceived = new ArrayList<Cookie>(); + List<Cookie> cookiesToSend = new ArrayList<Cookie>(); public CookieTestServer() { super(8192); } - @Override public Response serve(IHTTPSession session) { + @Override + public Response serve(IHTTPSession session) { CookieHandler cookies = session.getCookies(); for (String cookieName : cookies) { cookiesReceived.add(new Cookie(cookieName, cookies.read(cookieName))); diff --git a/core/src/test/java/fi/iki/elonen/integration/GetAndPostIntegrationTest.java b/core/src/test/java/fi/iki/elonen/integration/GetAndPostIntegrationTest.java index 97adafa..ea4041d 100644 --- a/core/src/test/java/fi/iki/elonen/integration/GetAndPostIntegrationTest.java +++ b/core/src/test/java/fi/iki/elonen/integration/GetAndPostIntegrationTest.java @@ -59,8 +59,7 @@ import java.util.Map; import static org.junit.Assert.assertEquals; /** - * @author Paul S. Hawke (paul.hawke@gmail.com) - * On: 5/19/13 at 5:36 PM + * @author Paul S. Hawke (paul.hawke@gmail.com) On: 5/19/13 at 5:36 PM */ public class GetAndPostIntegrationTest extends IntegrationTestBase<GetAndPostIntegrationTest.TestServer> { @@ -129,11 +128,13 @@ public class GetAndPostIntegrationTest extends IntegrationTestBase<GetAndPostInt assertEquals("POST:testPostRequestWithMultipartEncodedParameters-params=2;age=120;gender=Male", responseBody); } - @Override public TestServer createTestServer() { + @Override + public TestServer createTestServer() { return new TestServer(); } public static class TestServer extends NanoHTTPD { + public String response; public TestServer() { diff --git a/core/src/test/java/fi/iki/elonen/integration/IntegrationTestBase.java b/core/src/test/java/fi/iki/elonen/integration/IntegrationTestBase.java index 6940f48..9ad6b73 100644 --- a/core/src/test/java/fi/iki/elonen/integration/IntegrationTestBase.java +++ b/core/src/test/java/fi/iki/elonen/integration/IntegrationTestBase.java @@ -42,11 +42,12 @@ import org.junit.Before; import java.io.IOException; /** - * @author Paul S. Hawke (paul.hawke@gmail.com) - * On: 9/2/13 at 10:02 PM + * @author Paul S. Hawke (paul.hawke@gmail.com) On: 9/2/13 at 10:02 PM */ public abstract class IntegrationTestBase<T extends NanoHTTPD> { + protected DefaultHttpClient httpclient; + protected T testServer; @Before diff --git a/core/src/test/java/fi/iki/elonen/integration/PutStreamIntegrationTest.java b/core/src/test/java/fi/iki/elonen/integration/PutStreamIntegrationTest.java index 8a80b01..6bdde4d 100644 --- a/core/src/test/java/fi/iki/elonen/integration/PutStreamIntegrationTest.java +++ b/core/src/test/java/fi/iki/elonen/integration/PutStreamIntegrationTest.java @@ -61,18 +61,19 @@ public class PutStreamIntegrationTest extends IntegrationTestBase<PutStreamInteg assertEquals("PUT:" + expected, responseBody); } - @Override public TestServer createTestServer() { + @Override + public TestServer createTestServer() { return new TestServer(); } public static class TestServer extends NanoHTTPD { + public TestServer() { super(8192); } @Override - public Response serve(String uri, Method method, Map<String, String> headers, Map<String, String> parms, Map<String, String> files) - { + public Response serve(String uri, Method method, Map<String, String> headers, Map<String, String> parms, Map<String, String> files) { throw new UnsupportedOperationException(); } @@ -87,8 +88,7 @@ public class PutStreamIntegrationTest extends IntegrationTestBase<PutStreamInteg DataInputStream dataInputStream = new DataInputStream(session.getInputStream()); body = new byte[contentLength]; dataInputStream.readFully(body, 0, contentLength); - } - catch(IOException e) { + } catch (IOException e) { return new Response(Response.Status.INTERNAL_ERROR, MIME_PLAINTEXT, e.getMessage()); } diff --git a/markdown-plugin/src/main/java/fi/iki/elonen/MarkdownWebServerPlugin.java b/markdown-plugin/src/main/java/fi/iki/elonen/MarkdownWebServerPlugin.java index 6c1fc14..92640a9 100644 --- a/markdown-plugin/src/main/java/fi/iki/elonen/MarkdownWebServerPlugin.java +++ b/markdown-plugin/src/main/java/fi/iki/elonen/MarkdownWebServerPlugin.java @@ -47,26 +47,27 @@ import java.util.logging.Logger; import org.pegdown.PegDownProcessor; /** - * @author Paul S. Hawke (paul.hawke@gmail.com) - * On: 9/13/13 at 4:03 AM + * @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 Logger LOG = Logger.getLogger(MarkdownWebServerPlugin.class.getName()); - + private final PegDownProcessor processor; public MarkdownWebServerPlugin() { processor = new PegDownProcessor(); } - @Override public void initialize(Map<String, String> commandLineOptions) { + @Override + public void initialize(Map<String, String> commandLineOptions) { } - @Override public boolean canServeUri(String uri, File rootDir) { + @Override + public boolean canServeUri(String uri, File rootDir) { File f = new File(rootDir, uri); return f.exists(); } @@ -74,8 +75,7 @@ public class MarkdownWebServerPlugin implements WebServerPlugin { @Override public NanoHTTPD.Response serveFile(String uri, Map<String, String> headers, NanoHTTPD.IHTTPSession session, File file, String mimeType) { String markdownSource = readSource(file); - return markdownSource == null ? null : - new NanoHTTPD.Response(OK, MIME_HTML, processor.markdownToHtml(markdownSource)); + return markdownSource == null ? null : new NanoHTTPD.Response(OK, MIME_HTML, processor.markdownToHtml(markdownSource)); } private String readSource(File file) { @@ -95,7 +95,7 @@ public class MarkdownWebServerPlugin implements WebServerPlugin { reader.close(); return sb.toString(); } catch (Exception e) { - LOG.log(Level.SEVERE, "could not read source",e); + LOG.log(Level.SEVERE, "could not read source", e); return null; } finally { try { @@ -106,7 +106,7 @@ public class MarkdownWebServerPlugin implements WebServerPlugin { reader.close(); } } catch (IOException ignored) { - LOG.log(Level.FINEST, "close failed",ignored); + LOG.log(Level.FINEST, "close failed", ignored); } } } diff --git a/markdown-plugin/src/main/java/fi/iki/elonen/MarkdownWebServerPluginInfo.java b/markdown-plugin/src/main/java/fi/iki/elonen/MarkdownWebServerPluginInfo.java index 35bd1a3..d7f1c4c 100644 --- a/markdown-plugin/src/main/java/fi/iki/elonen/MarkdownWebServerPluginInfo.java +++ b/markdown-plugin/src/main/java/fi/iki/elonen/MarkdownWebServerPluginInfo.java @@ -34,19 +34,26 @@ package fi.iki.elonen; */ /** - * @author Paul S. Hawke (paul.hawke@gmail.com) - * On: 9/13/13 at 4:01 AM + * @author Paul S. Hawke (paul.hawke@gmail.com) On: 9/13/13 at 4:01 AM */ public class MarkdownWebServerPluginInfo implements WebServerPluginInfo { - @Override public String[] getMimeTypes() { - return new String[]{"text/markdown"}; + + @Override + public String[] getMimeTypes() { + return new String[]{ + "text/markdown" + }; } - @Override public String[] getIndexFilesForMimeType(String mime) { - return new String[]{"index.md"}; + @Override + public String[] getIndexFilesForMimeType(String mime) { + return new String[]{ + "index.md" + }; } - @Override public WebServerPlugin getWebServerPlugin(String mimeType) { + @Override + public WebServerPlugin getWebServerPlugin(String mimeType) { return new MarkdownWebServerPlugin(); } } @@ -361,6 +361,25 @@ <exists>src/main/java</exists> </file> </activation> + <build> + <plugins> + <plugin> + <groupId>com.googlecode.maven-java-formatter-plugin</groupId> + <artifactId>maven-java-formatter-plugin</artifactId> + <version>0.4</version> + <executions> + <execution> + <goals> + <goal>format</goal> + </goals> + </execution> + </executions> + <configuration> + <configFile>${project.basedir}/../src/main/formatter/formatter.xml</configFile> + </configuration> + </plugin> + </plugins> + </build> <reporting> <plugins> <plugin> @@ -386,27 +405,5 @@ </plugins> </reporting> </profile> - <profile> - <id>format</id> - <build> - <plugins> - <plugin> - <groupId>com.googlecode.maven-java-formatter-plugin</groupId> - <artifactId>maven-java-formatter-plugin</artifactId> - <version>0.4</version> - <executions> - <execution> - <goals> - <goal>format</goal> - </goals> - </execution> - </executions> - <configuration> - <configFile>${project.basedir}/../src/main/formatter/formatter.xml</configFile> - </configuration> - </plugin> - </plugins> - </build> - </profile> </profiles> </project> diff --git a/samples/src/main/java/fi/iki/elonen/HelloServer.java b/samples/src/main/java/fi/iki/elonen/HelloServer.java index 0a212e0..a01d9a8 100644 --- a/samples/src/main/java/fi/iki/elonen/HelloServer.java +++ b/samples/src/main/java/fi/iki/elonen/HelloServer.java @@ -41,36 +41,34 @@ import java.util.logging.Logger; */ public class HelloServer extends NanoHTTPD { - /** - * logger to log to. - */ - private static Logger LOG = Logger.getLogger(HelloServer.class.getName()); + /** + * logger to log to. + */ + private static Logger LOG = Logger.getLogger(HelloServer.class.getName()); - public HelloServer() { - super(8080); - } + public HelloServer() { + super(8080); + } - @Override - public Response serve(IHTTPSession session) { - Method method = session.getMethod(); - String uri = session.getUri(); - LOG.info(method + " '" + uri + "' "); + @Override + public Response serve(IHTTPSession session) { + Method method = session.getMethod(); + String uri = session.getUri(); + LOG.info(method + " '" + uri + "' "); - String msg = "<html><body><h1>Hello server</h1>\n"; - Map<String, String> parms = session.getParms(); - if (parms.get("username") == null) - msg += "<form action='?' method='get'>\n" - + " <p>Your name: <input type='text' name='username'></p>\n" - + "</form>\n"; - else - msg += "<p>Hello, " + parms.get("username") + "!</p>"; + String msg = "<html><body><h1>Hello server</h1>\n"; + Map<String, String> parms = session.getParms(); + if (parms.get("username") == null) + msg += "<form action='?' method='get'>\n" + " <p>Your name: <input type='text' name='username'></p>\n" + "</form>\n"; + else + msg += "<p>Hello, " + parms.get("username") + "!</p>"; - msg += "</body></html>\n"; + msg += "</body></html>\n"; - return new NanoHTTPD.Response(msg); - } + return new NanoHTTPD.Response(msg); + } - public static void main(String[] args) { - ServerRunner.run(HelloServer.class); - } + public static void main(String[] args) { + ServerRunner.run(HelloServer.class); + } } diff --git a/samples/src/main/java/fi/iki/elonen/TempFilesServer.java b/samples/src/main/java/fi/iki/elonen/TempFilesServer.java index 6eba33e..2e0828f 100644 --- a/samples/src/main/java/fi/iki/elonen/TempFilesServer.java +++ b/samples/src/main/java/fi/iki/elonen/TempFilesServer.java @@ -39,10 +39,10 @@ import java.util.List; import fi.iki.elonen.debug.DebugServer; /** - * @author Paul S. Hawke (paul.hawke@gmail.com) - * On: 3/9/13 at 12:47 AM + * @author Paul S. Hawke (paul.hawke@gmail.com) On: 3/9/13 at 12:47 AM */ public class TempFilesServer extends DebugServer { + public static void main(String[] args) { TempFilesServer server = new TempFilesServer(); server.setTempFileManagerFactory(new ExampleManagerFactory()); @@ -50,6 +50,7 @@ public class TempFilesServer extends DebugServer { } private static class ExampleManagerFactory implements TempFileManagerFactory { + @Override public TempFileManager create() { return new ExampleManager(); @@ -57,7 +58,9 @@ public class TempFilesServer extends DebugServer { } private static class ExampleManager implements TempFileManager { + private final String tmpdir; + private final List<TempFile> tempFiles; private ExampleManager() { @@ -80,9 +83,10 @@ public class TempFilesServer extends DebugServer { } for (TempFile file : tempFiles) { try { - System.out.println(" "+file.getName()); + System.out.println(" " + file.getName()); file.delete(); - } catch (Exception ignored) {} + } catch (Exception ignored) { + } } tempFiles.clear(); } diff --git a/samples/src/main/java/fi/iki/elonen/debug/DebugServer.java b/samples/src/main/java/fi/iki/elonen/debug/DebugServer.java index 6988598..7780576 100644 --- a/samples/src/main/java/fi/iki/elonen/debug/DebugServer.java +++ b/samples/src/main/java/fi/iki/elonen/debug/DebugServer.java @@ -41,6 +41,7 @@ import fi.iki.elonen.NanoHTTPD; import fi.iki.elonen.ServerRunner; public class DebugServer extends NanoHTTPD { + public DebugServer() { super(8080); } @@ -49,9 +50,9 @@ public class DebugServer extends NanoHTTPD { ServerRunner.run(DebugServer.class); } - @Override public Response serve(IHTTPSession session) { - Map<String, List<String>> decodedQueryParameters = - decodeParameters(session.getQueryParameterString()); + @Override + public Response serve(IHTTPSession session) { + Map<String, List<String>> decodedQueryParameters = decodeParameters(session.getQueryParameterString()); StringBuilder sb = new StringBuilder(); sb.append("<html>"); @@ -59,26 +60,20 @@ public class DebugServer extends NanoHTTPD { sb.append("<body>"); sb.append("<h1>Debug Server</h1>"); - sb.append("<p><blockquote><b>URI</b> = ").append( - String.valueOf(session.getUri())).append("<br />"); + sb.append("<p><blockquote><b>URI</b> = ").append(String.valueOf(session.getUri())).append("<br />"); - sb.append("<b>Method</b> = ").append( - String.valueOf(session.getMethod())).append("</blockquote></p>"); + sb.append("<b>Method</b> = ").append(String.valueOf(session.getMethod())).append("</blockquote></p>"); - sb.append("<h3>Headers</h3><p><blockquote>"). - append(toString(session.getHeaders())).append("</blockquote></p>"); + sb.append("<h3>Headers</h3><p><blockquote>").append(toString(session.getHeaders())).append("</blockquote></p>"); - sb.append("<h3>Parms</h3><p><blockquote>"). - append(toString(session.getParms())).append("</blockquote></p>"); + sb.append("<h3>Parms</h3><p><blockquote>").append(toString(session.getParms())).append("</blockquote></p>"); - sb.append("<h3>Parms (multi values?)</h3><p><blockquote>"). - append(toString(decodedQueryParameters)).append("</blockquote></p>"); + sb.append("<h3>Parms (multi values?)</h3><p><blockquote>").append(toString(decodedQueryParameters)).append("</blockquote></p>"); try { Map<String, String> files = new HashMap<String, String>(); session.parseBody(files); - sb.append("<h3>Files</h3><p><blockquote>"). - append(toString(files)).append("</blockquote></p>"); + sb.append("<h3>Files</h3><p><blockquote>").append(toString(files)).append("</blockquote></p>"); } catch (Exception e) { e.printStackTrace(); } @@ -106,7 +101,6 @@ public class DebugServer extends NanoHTTPD { } private void listItem(StringBuilder sb, Map.Entry<String, ? extends Object> entry) { - sb.append("<li><code><b>").append(entry.getKey()). - append("</b> = ").append(entry.getValue()).append("</code></li>"); + sb.append("<li><code><b>").append(entry.getKey()).append("</b> = ").append(entry.getValue()).append("</code></li>"); } } diff --git a/webserver/src/main/java/fi/iki/elonen/InternalRewrite.java b/webserver/src/main/java/fi/iki/elonen/InternalRewrite.java index ad6b16e..f5dcd91 100644 --- a/webserver/src/main/java/fi/iki/elonen/InternalRewrite.java +++ b/webserver/src/main/java/fi/iki/elonen/InternalRewrite.java @@ -38,11 +38,12 @@ import java.util.Map; import fi.iki.elonen.NanoHTTPD.Response; /** - * @author Paul S. Hawke (paul.hawke@gmail.com) - * On: 9/15/13 at 2:52 PM + * @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<String, String> headers; public InternalRewrite(Map<String, String> headers, String uri) { diff --git a/webserver/src/main/java/fi/iki/elonen/ServerRunner.java b/webserver/src/main/java/fi/iki/elonen/ServerRunner.java index cfb1d6f..2a4c699 100644 --- a/webserver/src/main/java/fi/iki/elonen/ServerRunner.java +++ b/webserver/src/main/java/fi/iki/elonen/ServerRunner.java @@ -38,16 +38,17 @@ import java.util.logging.Level; import java.util.logging.Logger; public class ServerRunner { + /** * logger to log to. */ private static Logger LOG = Logger.getLogger(ServerRunner.class.getName()); - + public static <T extends NanoHTTPD> void run(Class<T> serverClass) { try { executeInstance((NanoHTTPD) serverClass.newInstance()); } catch (Exception e) { - LOG.log(Level.SEVERE, "Cound nor create server",e); + LOG.log(Level.SEVERE, "Cound nor create server", e); } } diff --git a/webserver/src/main/java/fi/iki/elonen/SimpleWebServer.java b/webserver/src/main/java/fi/iki/elonen/SimpleWebServer.java index 13306d8..147bbf9 100644 --- a/webserver/src/main/java/fi/iki/elonen/SimpleWebServer.java +++ b/webserver/src/main/java/fi/iki/elonen/SimpleWebServer.java @@ -51,84 +51,81 @@ import java.util.ServiceLoader; import java.util.StringTokenizer; public class SimpleWebServer extends NanoHTTPD { + /** * Common mime type for dynamic content: binary */ public static final String MIME_DEFAULT_BINARY = "application/octet-stream"; + /** * Default Index file names. */ @SuppressWarnings("serial") - public static final List<String> INDEX_FILE_NAMES = new ArrayList<String>() {{ - add("index.html"); - add("index.htm"); - }}; + public static final List<String> INDEX_FILE_NAMES = new ArrayList<String>() { + + { + add("index.html"); + add("index.htm"); + } + }; + /** * Hashtable mapping (String)FILENAME_EXTENSION -> (String)MIME_TYPE */ @SuppressWarnings("serial") - private static final Map<String, String> MIME_TYPES = new HashMap<String, String>() {{ - put("css", "text/css"); - put("htm", "text/html"); - put("html", "text/html"); - put("xml", "text/xml"); - put("java", "text/x-java-source, text/java"); - put("md", "text/plain"); - put("txt", "text/plain"); - put("asc", "text/plain"); - put("gif", "image/gif"); - put("jpg", "image/jpeg"); - put("jpeg", "image/jpeg"); - put("png", "image/png"); - put("mp3", "audio/mpeg"); - put("m3u", "audio/mpeg-url"); - put("mp4", "video/mp4"); - put("ogv", "video/ogg"); - put("flv", "video/x-flv"); - put("mov", "video/quicktime"); - put("swf", "application/x-shockwave-flash"); - put("js", "application/javascript"); - put("pdf", "application/pdf"); - put("doc", "application/msword"); - put("ogg", "application/x-ogg"); - put("zip", "application/octet-stream"); - put("exe", "application/octet-stream"); - put("class", "application/octet-stream"); - }}; + private static final Map<String, String> MIME_TYPES = new HashMap<String, String>() { + + { + put("css", "text/css"); + put("htm", "text/html"); + put("html", "text/html"); + put("xml", "text/xml"); + put("java", "text/x-java-source, text/java"); + put("md", "text/plain"); + put("txt", "text/plain"); + put("asc", "text/plain"); + put("gif", "image/gif"); + put("jpg", "image/jpeg"); + put("jpeg", "image/jpeg"); + put("png", "image/png"); + put("mp3", "audio/mpeg"); + put("m3u", "audio/mpeg-url"); + put("mp4", "video/mp4"); + put("ogv", "video/ogg"); + put("flv", "video/x-flv"); + put("mov", "video/quicktime"); + put("swf", "application/x-shockwave-flash"); + put("js", "application/javascript"); + put("pdf", "application/pdf"); + put("doc", "application/msword"); + put("ogg", "application/x-ogg"); + put("zip", "application/octet-stream"); + put("exe", "application/octet-stream"); + put("class", "application/octet-stream"); + } + }; + /** * The distribution licence */ - private static final String LICENCE = - "Copyright (c) 2012-2013 by Paul S. Hawke, 2001,2005-2013 by Jarno Elonen, 2010 by Konstantinos Togias\n" - + "\n" - + "Redistribution and use in source and binary forms, with or without\n" - + "modification, are permitted provided that the following conditions\n" - + "are met:\n" - + "\n" - + "Redistributions of source code must retain the above copyright notice,\n" - + "this list of conditions and the following disclaimer. Redistributions in\n" - + "binary form must reproduce the above copyright notice, this list of\n" - + "conditions and the following disclaimer in the documentation and/or other\n" - + "materials provided with the distribution. The name of the author may not\n" - + "be used to endorse or promote products derived from this software without\n" - + "specific prior written permission. \n" - + " \n" - + "THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR\n" - + "IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES\n" - + "OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.\n" - + "IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,\n" - + "INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT\n" - + "NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n" - + "DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n" - + "THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n" - + "(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n" + private static final String LICENCE = "Copyright (c) 2012-2013 by Paul S. Hawke, 2001,2005-2013 by Jarno Elonen, 2010 by Konstantinos Togias\n" + "\n" + + "Redistribution and use in source and binary forms, with or without\n" + "modification, are permitted provided that the following conditions\n" + "are met:\n" + + "\n" + "Redistributions of source code must retain the above copyright notice,\n" + "this list of conditions and the following disclaimer. Redistributions in\n" + + "binary form must reproduce the above copyright notice, this list of\n" + "conditions and the following disclaimer in the documentation and/or other\n" + + "materials provided with the distribution. The name of the author may not\n" + "be used to endorse or promote products derived from this software without\n" + + "specific prior written permission. \n" + " \n" + "THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR\n" + + "IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES\n" + "OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.\n" + + "IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,\n" + "INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT\n" + + "NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n" + "DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n" + + "THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n" + "(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n" + "OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."; + private static Map<String, WebServerPlugin> mimeTypeHandlers = new HashMap<String, WebServerPlugin>(); + private final boolean quiet; protected List<File> rootDirs; - public SimpleWebServer(String host, int port, File wwwroot, boolean quiet) { super(host, port); this.quiet = quiet; @@ -146,9 +143,9 @@ public class SimpleWebServer extends NanoHTTPD { this.init(); } - /** - * Used to initialize and customize the server. - */ + /** + * Used to initialize and customize the server. + */ public void init() { } @@ -191,7 +188,7 @@ public class SimpleWebServer extends NanoHTTPD { } options.put("host", host); - options.put("port", ""+port); + options.put("port", "" + port); options.put("quiet", String.valueOf(quiet)); StringBuilder sb = new StringBuilder(); for (File dir : rootDirs) { @@ -200,7 +197,8 @@ public class SimpleWebServer extends NanoHTTPD { } try { sb.append(dir.getCanonicalPath()); - } catch (IOException ignored) {} + } catch (IOException ignored) { + } } options.put("home", sb.toString()); @@ -246,7 +244,8 @@ public class SimpleWebServer extends NanoHTTPD { } /** - * URL-encodes everything between "/"-characters. Encodes spaces as '%20' instead of '+'. + * URL-encodes everything between "/"-characters. Encodes spaces as '%20' + * instead of '+'. */ private String encodeUri(String uri) { String newUri = ""; @@ -318,18 +317,19 @@ public class SimpleWebServer extends NanoHTTPD { return getNotFoundResponse(); } - // Browsers get confused without '/' after the directory, send a redirect. + // Browsers get confused without '/' after the directory, send a + // redirect. File f = new File(homeDir, uri); if (f.isDirectory() && !uri.endsWith("/")) { uri += "/"; - Response res = createResponse(Response.Status.REDIRECT, NanoHTTPD.MIME_HTML, "<html><body>Redirected: <a href=\"" + - uri + "\">" + uri + "</a></body></html>"); + Response res = createResponse(Response.Status.REDIRECT, NanoHTTPD.MIME_HTML, "<html><body>Redirected: <a href=\"" + uri + "\">" + uri + "</a></body></html>"); 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. + // 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()) { @@ -359,18 +359,15 @@ public class SimpleWebServer extends NanoHTTPD { } protected Response getNotFoundResponse() { - return createResponse(Response.Status.NOT_FOUND, NanoHTTPD.MIME_PLAINTEXT, - "Error 404, file not found."); + return createResponse(Response.Status.NOT_FOUND, NanoHTTPD.MIME_PLAINTEXT, "Error 404, file not found."); } protected Response getForbiddenResponse(String s) { - return createResponse(Response.Status.FORBIDDEN, NanoHTTPD.MIME_PLAINTEXT, "FORBIDDEN: " - + s); + return createResponse(Response.Status.FORBIDDEN, NanoHTTPD.MIME_PLAINTEXT, "FORBIDDEN: " + s); } protected Response getInternalErrorResponse(String s) { - return createResponse(Response.Status.INTERNAL_ERROR, NanoHTTPD.MIME_PLAINTEXT, - "INTERNAL ERROR: " + s); + return createResponse(Response.Status.INTERNAL_ERROR, NanoHTTPD.MIME_PLAINTEXT, "INTERNAL ERROR: " + s); } private boolean canServeUri(String uri, File homeDir) { @@ -388,7 +385,8 @@ public class SimpleWebServer extends NanoHTTPD { } /** - * Serves file from homeDir and its' subdirectories (only). Uses only URI, ignores all headers and HTTP parameters. + * Serves file from homeDir and its' subdirectories (only). Uses only URI, + * ignores all headers and HTTP parameters. */ Response serveFile(String uri, Map<String, String> header, File file, String mime) { Response res; @@ -414,7 +412,8 @@ public class SimpleWebServer extends NanoHTTPD { } } - // Change return code and add Content-Range header when skipping is requested + // Change return code and add Content-Range header when skipping is + // requested long fileLen = file.length(); if (range != null && startFrom >= 0) { if (startFrom >= fileLen) { @@ -432,6 +431,7 @@ public class SimpleWebServer extends NanoHTTPD { final long dataLen = newLen; FileInputStream fis = new FileInputStream(file) { + @Override public int available() throws IOException { return (int) dataLen; @@ -496,12 +496,9 @@ public class SimpleWebServer extends NanoHTTPD { protected String listDirectory(String uri, File f) { String heading = "Directory " + uri; - StringBuilder msg = new StringBuilder("<html><head><title>" + heading + "</title><style><!--\n" + - "span.dirname { font-weight: bold; }\n" + - "span.filesize { font-size: 75%; }\n" + - "// -->\n" + - "</style>" + - "</head><body><h1>" + heading + "</h1>"); + StringBuilder msg = + new StringBuilder("<html><head><title>" + heading + "</title><style><!--\n" + "span.dirname { font-weight: bold; }\n" + "span.filesize { font-size: 75%; }\n" + + "// -->\n" + "</style>" + "</head><body><h1>" + heading + "</h1>"); String up = null; if (uri.length() > 1) { @@ -513,6 +510,7 @@ public class SimpleWebServer extends NanoHTTPD { } List<String> files = Arrays.asList(f.list(new FilenameFilter() { + @Override public boolean accept(File dir, String name) { return new File(dir, name).isFile(); @@ -520,6 +518,7 @@ public class SimpleWebServer extends NanoHTTPD { })); Collections.sort(files); List<String> directories = Arrays.asList(f.list(new FilenameFilter() { + @Override public boolean accept(File dir, String name) { return new File(dir, name).isDirectory(); @@ -535,7 +534,8 @@ public class SimpleWebServer extends NanoHTTPD { } for (String directory : directories) { String dir = directory + "/"; - msg.append("<li><a rel=\"directory\" href=\"").append(encodeUri(uri + dir)).append("\"><span class=\"dirname\">").append(dir).append("</span></a></b></li>"); + msg.append("<li><a rel=\"directory\" href=\"").append(encodeUri(uri + dir)).append("\"><span class=\"dirname\">").append(dir) + .append("</span></a></b></li>"); } msg.append("</section>"); } diff --git a/webserver/src/main/java/fi/iki/elonen/WebServerPlugin.java b/webserver/src/main/java/fi/iki/elonen/WebServerPlugin.java index 3835de9..cdfc3ff 100644 --- a/webserver/src/main/java/fi/iki/elonen/WebServerPlugin.java +++ b/webserver/src/main/java/fi/iki/elonen/WebServerPlugin.java @@ -39,9 +39,8 @@ import java.util.Map; import fi.iki.elonen.NanoHTTPD.IHTTPSession; /** -* @author Paul S. Hawke (paul.hawke@gmail.com) -* On: 9/14/13 at 8:09 AM -*/ + * @author Paul S. Hawke (paul.hawke@gmail.com) On: 9/14/13 at 8:09 AM + */ public interface WebServerPlugin { void initialize(Map<String, String> commandLineOptions); diff --git a/webserver/src/main/java/fi/iki/elonen/WebServerPluginInfo.java b/webserver/src/main/java/fi/iki/elonen/WebServerPluginInfo.java index 4e9a007..c1ef6e8 100644 --- a/webserver/src/main/java/fi/iki/elonen/WebServerPluginInfo.java +++ b/webserver/src/main/java/fi/iki/elonen/WebServerPluginInfo.java @@ -34,10 +34,10 @@ package fi.iki.elonen; */ /** -* @author Paul S. Hawke (paul.hawke@gmail.com) -* On: 9/14/13 at 8:09 AM -*/ + * @author Paul S. Hawke (paul.hawke@gmail.com) On: 9/14/13 at 8:09 AM + */ public interface WebServerPluginInfo { + String[] getMimeTypes(); String[] getIndexFilesForMimeType(String mime); diff --git a/websocket/src/main/java/fi/iki/elonen/NanoWebSocketServer.java b/websocket/src/main/java/fi/iki/elonen/NanoWebSocketServer.java index 9c3b8c7..8163c10 100644 --- a/websocket/src/main/java/fi/iki/elonen/NanoWebSocketServer.java +++ b/websocket/src/main/java/fi/iki/elonen/NanoWebSocketServer.java @@ -53,23 +53,32 @@ import fi.iki.elonen.NanoWebSocketServer.WebSocketFrame.CloseFrame; import fi.iki.elonen.NanoWebSocketServer.WebSocketFrame.OpCode; public abstract class NanoWebSocketServer extends NanoHTTPD { + /** * logger to log to. */ private static Logger LOG = Logger.getLogger(NanoWebSocketServer.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"; - + + 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"; + public NanoWebSocketServer(int port) { super(port); } @@ -83,13 +92,11 @@ public abstract class NanoWebSocketServer extends NanoHTTPD { Map<String, String> headers = session.getHeaders(); if (isWebsocketRequested(session)) { if (!HEADER_WEBSOCKET_VERSION_VALUE.equalsIgnoreCase(headers.get(HEADER_WEBSOCKET_VERSION))) { - return new Response(Response.Status.BAD_REQUEST, NanoHTTPD.MIME_PLAINTEXT, - "Invalid Websocket-Version " + headers.get(HEADER_WEBSOCKET_VERSION)); + return new Response(Response.Status.BAD_REQUEST, NanoHTTPD.MIME_PLAINTEXT, "Invalid Websocket-Version " + headers.get(HEADER_WEBSOCKET_VERSION)); } if (!headers.containsKey(HEADER_WEBSOCKET_KEY)) { - return new Response(Response.Status.BAD_REQUEST, NanoHTTPD.MIME_PLAINTEXT, - "Missing Websocket-Key"); + return new Response(Response.Status.BAD_REQUEST, NanoHTTPD.MIME_PLAINTEXT, "Missing Websocket-Key"); } WebSocket webSocket = openWebSocket(session); @@ -97,8 +104,7 @@ public abstract class NanoWebSocketServer extends NanoHTTPD { try { handshakeResponse.addHeader(HEADER_WEBSOCKET_ACCEPT, makeAcceptKey(headers.get(HEADER_WEBSOCKET_KEY))); } catch (NoSuchAlgorithmException e) { - return new Response(Response.Status.INTERNAL_ERROR, NanoHTTPD.MIME_PLAINTEXT, - "The SHA-1 Algorithm required for websockets is not available on the server."); + return new Response(Response.Status.INTERNAL_ERROR, NanoHTTPD.MIME_PLAINTEXT, "The SHA-1 Algorithm required for websockets is not available on the server."); } if (headers.containsKey(HEADER_WEBSOCKET_PROTOCOL)) { @@ -123,7 +129,7 @@ public abstract class NanoWebSocketServer extends NanoHTTPD { String connection = headers.get(HEADER_CONNECTION); return (connection != null && connection.toLowerCase().contains(HEADER_CONNECTION_VALUE.toLowerCase())); } - + public WebSocket openWebSocket(IHTTPSession handshake) { return new WebSocket(handshake); } @@ -141,11 +147,13 @@ public abstract class NanoWebSocketServer extends NanoHTTPD { /** * Translates the specified byte array into Base64 string. * <p> - * 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 + * 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 * </p> - * - * @param buf the byte array (not null) + * + * @param buf + * the byte array (not null) * @return the translated Base64 string (not null) */ private static String encodeBase64(byte[] buf) { @@ -172,21 +180,31 @@ public abstract class NanoWebSocketServer extends NanoHTTPD { } return new String(ar); } - + public static enum State { - UNCONNECTED, CONNECTING, OPEN, CLOSING, CLOSED + UNCONNECTED, + CONNECTING, + OPEN, + CLOSING, + CLOSED } public class WebSocket { + private InputStream in; + private OutputStream out; + private WebSocketFrame.OpCode continuousOpCode = null; + private List<WebSocketFrame> continuousFrames = new LinkedList<WebSocketFrame>(); + private State state = State.UNCONNECTED; + private final NanoHTTPD.IHTTPSession handshakeRequest; - private final NanoHTTPD.Response handshakeResponse = new NanoHTTPD.Response( - NanoHTTPD.Response.Status.SWITCH_PROTOCOL, null, (InputStream) null) { + private final NanoHTTPD.Response handshakeResponse = new NanoHTTPD.Response(NanoHTTPD.Response.Status.SWITCH_PROTOCOL, null, (InputStream) null) { + @Override protected void send(OutputStream out) { WebSocket.this.out = out; @@ -201,8 +219,8 @@ public abstract class NanoWebSocketServer extends NanoHTTPD { this.handshakeRequest = handshakeRequest; this.in = handshakeRequest.getInputStream(); - handshakeResponse.addHeader(HEADER_UPGRADE, HEADER_UPGRADE_VALUE); - handshakeResponse.addHeader(HEADER_CONNECTION, HEADER_CONNECTION_VALUE); + handshakeResponse.addHeader(HEADER_UPGRADE, HEADER_UPGRADE_VALUE); + handshakeResponse.addHeader(HEADER_CONNECTION, HEADER_CONNECTION_VALUE); } public NanoHTTPD.IHTTPSession getHandshakeRequest() { @@ -234,7 +252,7 @@ public abstract class NanoWebSocketServer extends NanoHTTPD { } private void handleWebsocketFrame(WebSocketFrame frame) throws IOException { - onFrameReceived(frame); + onFrameReceived(frame); if (frame.getOpCode() == OpCode.Close) { handleCloseFrame(frame); } else if (frame.getOpCode() == OpCode.Ping) { @@ -260,10 +278,10 @@ public abstract class NanoWebSocketServer extends NanoHTTPD { reason = ((CloseFrame) frame).getCloseReason(); } if (state == State.CLOSING) { - //Answer for my requested close + // Answer for my requested close doClose(code, reason, false); } else { - //Answer close request from other endpoint and close self + // Answer close request from other endpoint and close self State oldState = state; state = State.CLOSING; if (oldState == State.OPEN) { @@ -275,7 +293,7 @@ public abstract class NanoWebSocketServer extends NanoHTTPD { private void handleFrameFragment(WebSocketFrame frame) throws IOException { if (frame.getOpCode() != OpCode.Continuation) { - //First + // First if (continuousOpCode != null) { throw new WebSocketException(CloseCode.ProtocolError, "Previous continuous frame sequence not completed."); } @@ -283,7 +301,7 @@ public abstract class NanoWebSocketServer extends NanoHTTPD { continuousFrames.clear(); continuousFrames.add(frame); } else if (frame.isFin()) { - //Last + // Last if (continuousOpCode == null) { throw new WebSocketException(CloseCode.ProtocolError, "Continuous frame sequence was not started."); } @@ -291,16 +309,16 @@ public abstract class NanoWebSocketServer extends NanoHTTPD { continuousOpCode = null; continuousFrames.clear(); } else if (continuousOpCode == null) { - //Unexpected + // Unexpected throw new WebSocketException(CloseCode.ProtocolError, "Continuous frame sequence was not started."); } else { - //Intermediate + // Intermediate continuousFrames.add(frame); } } public synchronized void sendFrame(WebSocketFrame frame) throws IOException { - onSendFrame(frame); + onSendFrame(frame); frame.write(out); } @@ -314,53 +332,58 @@ public abstract class NanoWebSocketServer extends NanoHTTPD { try { in.close(); } catch (IOException e) { - LOG.log(Level.FINE, "close failed",e); + LOG.log(Level.FINE, "close failed", e); } } if (out != null) { try { out.close(); } catch (IOException e) { - LOG.log(Level.FINE, "close failed",e); + LOG.log(Level.FINE, "close failed", e); } } state = State.CLOSED; onClose(this, code, reason, initiatedByRemote); } - // --------------------------------Public Facade--------------------------- - + // --------------------------------Public + // Facade--------------------------- + public void ping(byte[] payload) throws IOException { - sendFrame(new WebSocketFrame(OpCode.Ping, true, payload)); + sendFrame(new WebSocketFrame(OpCode.Ping, true, payload)); } - + public void send(byte[] payload) throws IOException { - sendFrame(new WebSocketFrame(OpCode.Binary, true, payload)); + sendFrame(new WebSocketFrame(OpCode.Binary, true, payload)); } - + public void send(String payload) throws IOException { - sendFrame(new WebSocketFrame(OpCode.Text, true, payload)); + sendFrame(new WebSocketFrame(OpCode.Text, true, payload)); } - + public void close(CloseCode code, String reason) throws IOException { - State oldState = state; - state = State.CLOSING; - if (oldState == State.OPEN) { - sendFrame(new CloseFrame(code, reason)); - } else { - doClose(code, reason, false); - } + State oldState = state; + state = State.CLOSING; + if (oldState == State.OPEN) { + sendFrame(new CloseFrame(code, reason)); + } else { + doClose(code, reason, false); + } } } - + public static class WebSocketFrame { + private OpCode opCode; + private boolean fin; + private byte[] maskingKey; private byte[] payload; private transient int _payloadLength; + private transient String _payloadString; private WebSocketFrame(OpCode opCode, boolean fin) { @@ -524,11 +547,10 @@ public abstract class NanoWebSocketServer extends NanoHTTPD { throw new WebSocketException(CloseCode.ProtocolError, "Invalid data frame 2byte length. (not using minimal length encoding)"); } } else if (_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()); + 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)"); } @@ -569,7 +591,7 @@ public abstract class NanoWebSocketServer extends NanoHTTPD { } } - //Test for Unicode errors + // Test for Unicode errors if (getOpCode() == OpCode.Text) { _payloadString = binary2Text(getBinaryPayload()); } @@ -592,7 +614,8 @@ public abstract class NanoWebSocketServer extends NanoHTTPD { out.write(_payloadLength); } else { out.write(isMasked() ? 0xFF : 127); - out.write(_payloadLength >>> 56 & 0); //integer only contains 31 bit + out.write(_payloadLength >>> 56 & 0); // integer only contains + // 31 bit out.write(_payloadLength >>> 48 & 0); out.write(_payloadLength >>> 40 & 0); out.write(_payloadLength >>> 32 & 0); @@ -602,7 +625,6 @@ public abstract class NanoWebSocketServer extends NanoHTTPD { out.write(_payloadLength); } - if (isMasked()) { out.write(maskingKey); for (int i = 0; i < _payloadLength; i++) { @@ -619,15 +641,15 @@ public abstract class NanoWebSocketServer extends NanoHTTPD { public static final Charset TEXT_CHARSET = Charset.forName("UTF-8"); public static String binary2Text(byte[] payload) throws CharacterCodingException { - return new String(payload, TEXT_CHARSET); + return new String(payload, TEXT_CHARSET); } public static String binary2Text(byte[] payload, int offset, int length) throws CharacterCodingException { - return new String(payload, offset, length, TEXT_CHARSET); + return new String(payload, offset, length, TEXT_CHARSET); } public static byte[] text2Binary(String payload) throws CharacterCodingException { - return payload.getBytes(TEXT_CHARSET); + return payload.getBytes(TEXT_CHARSET); } @Override @@ -642,7 +664,8 @@ public abstract class NanoWebSocketServer extends NanoHTTPD { } private String payloadToString() { - if (payload == null) return "null"; + if (payload == null) + return "null"; else { final StringBuilder sb = new StringBuilder(); sb.append('[').append(payload.length).append("b] "); @@ -666,7 +689,12 @@ public abstract class NanoWebSocketServer extends NanoHTTPD { // --------------------------------CONSTANTS------------------------------- public static enum OpCode { - Continuation(0), Text(1), Binary(2), Close(8), Ping(9), Pong(10); + Continuation(0), + Text(1), + Binary(2), + Close(8), + Ping(9), + Pong(10); private final byte code; @@ -693,9 +721,18 @@ public abstract class NanoWebSocketServer extends NanoHTTPD { } public static 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); + NormalClosure(1000), + GoingAway(1001), + ProtocolError(1002), + UnsupportedData(1003), + NoStatusRcvd(1005), + AbnormalClosure(1006), + InvalidFramePayloadData(1007), + PolicyViolation(1008), + MessageTooBig(1009), + MandatoryExt(1010), + InternalServerError(1011), + TLSHandshake(1015); private final int code; @@ -720,15 +757,16 @@ public abstract class NanoWebSocketServer extends NanoHTTPD { // ------------------------------------------------------------------------ public static class CloseFrame extends WebSocketFrame { + private CloseCode _closeCode; + private String _closeReason; private CloseFrame(WebSocketFrame wrap) throws CharacterCodingException { super(wrap); assert wrap.getOpCode() == OpCode.Close; if (wrap.getBinaryPayload().length >= 2) { - _closeCode = CloseCode.find((wrap.getBinaryPayload()[0] & 0xFF) << 8 | - (wrap.getBinaryPayload()[1] & 0xFF)); + _closeCode = CloseCode.find((wrap.getBinaryPayload()[0] & 0xFF) << 8 | (wrap.getBinaryPayload()[1] & 0xFF)); _closeReason = binary2Text(getBinaryPayload(), 2, getBinaryPayload().length - 2); } } @@ -761,9 +799,11 @@ public abstract class NanoWebSocketServer extends NanoHTTPD { } public static class WebSocketException extends IOException { + private static final long serialVersionUID = 1L; - - private CloseCode code; + + private CloseCode code; + private String reason; public WebSocketException(Exception cause) { @@ -788,7 +828,7 @@ public abstract class NanoWebSocketServer extends NanoHTTPD { return reason; } } - + // --------------------------------Listener-------------------------------- protected abstract void onPong(WebSocket webSocket, WebSocketFrame pongFrame); @@ -798,13 +838,12 @@ public abstract class NanoWebSocketServer extends NanoHTTPD { protected abstract void onClose(WebSocket webSocket, CloseCode code, String reason, boolean initiatedByRemote); protected abstract void onException(WebSocket webSocket, IOException e); - + protected void onFrameReceived(WebSocketFrame webSocket) { - // only for debugging + // only for debugging } public void onSendFrame(WebSocketFrame webSocket) { - // only for debugging + // only for debugging } } - diff --git a/websocket/src/main/java/fi/iki/elonen/samples/echo/DebugWebSocketServer.java b/websocket/src/main/java/fi/iki/elonen/samples/echo/DebugWebSocketServer.java index 8aad58e..68c1a7e 100644 --- a/websocket/src/main/java/fi/iki/elonen/samples/echo/DebugWebSocketServer.java +++ b/websocket/src/main/java/fi/iki/elonen/samples/echo/DebugWebSocketServer.java @@ -40,15 +40,15 @@ import java.util.logging.Logger; import fi.iki.elonen.NanoWebSocketServer; /** -* @author Paul S. Hawke (paul.hawke@gmail.com) -* On: 4/23/14 at 10:31 PM -*/ + * @author Paul S. Hawke (paul.hawke@gmail.com) On: 4/23/14 at 10:31 PM + */ public class DebugWebSocketServer extends NanoWebSocketServer { + /** * logger to log to. */ private static Logger LOG = Logger.getLogger(DebugWebSocketServer.class.getName()); - + private final boolean debug; public DebugWebSocketServer(int port, boolean debug) { @@ -76,15 +76,14 @@ public class DebugWebSocketServer extends NanoWebSocketServer { @Override protected void onClose(WebSocket socket, WebSocketFrame.CloseCode code, String reason, boolean initiatedByRemote) { if (debug) { - System.out.println("C [" + (initiatedByRemote ? "Remote" : "Self") + "] " + - (code != null ? code : "UnknownCloseCode[" + code + "]") + - (reason != null && !reason.isEmpty() ? ": " + reason : "")); + System.out.println("C [" + (initiatedByRemote ? "Remote" : "Self") + "] " + (code != null ? code : "UnknownCloseCode[" + code + "]") + + (reason != null && !reason.isEmpty() ? ": " + reason : "")); } } @Override protected void onException(WebSocket socket, IOException e) { - LOG.log(Level.SEVERE,"exception occured",e); + LOG.log(Level.SEVERE, "exception occured", e); } @Override diff --git a/websocket/src/main/java/fi/iki/elonen/samples/echo/EchoSocketSample.java b/websocket/src/main/java/fi/iki/elonen/samples/echo/EchoSocketSample.java index 9a90815..3060146 100644 --- a/websocket/src/main/java/fi/iki/elonen/samples/echo/EchoSocketSample.java +++ b/websocket/src/main/java/fi/iki/elonen/samples/echo/EchoSocketSample.java @@ -38,6 +38,7 @@ import java.io.IOException; import fi.iki.elonen.NanoWebSocketServer; public class EchoSocketSample { + public static void main(String[] args) throws IOException { final boolean debugMode = args.length >= 2 && args[1].toLowerCase().equals("-d"); NanoWebSocketServer ws = new DebugWebSocketServer(args.length > 0 ? Integer.parseInt(args[0]) : 9090, debugMode); @@ -52,4 +53,3 @@ public class EchoSocketSample { } } - diff --git a/websocket/src/test/java/fi/iki/elonen/WebSocketResponseHandlerTest.java b/websocket/src/test/java/fi/iki/elonen/WebSocketResponseHandlerTest.java index 7519646..ec14156 100644 --- a/websocket/src/test/java/fi/iki/elonen/WebSocketResponseHandlerTest.java +++ b/websocket/src/test/java/fi/iki/elonen/WebSocketResponseHandlerTest.java @@ -61,8 +61,8 @@ public class WebSocketResponseHandlerTest { @Before public void setUp() { - nanoWebSocketServer = Mockito.mock(NanoWebSocketServer.class, Mockito.CALLS_REAL_METHODS); - + nanoWebSocketServer = Mockito.mock(NanoWebSocketServer.class, Mockito.CALLS_REAL_METHODS); + headers = new HashMap<String, String>(); headers.put("upgrade", "websocket"); headers.put("connection", "Upgrade"); |