0001: /*
0002: HttpdBase4J: An embeddable Java web server framework that supports HTTP, HTTPS,
0003: templated content and serving content from inside a jar or archive.
0004: Copyright (C) 2007 Donald Munro
0005:
0006: This library is free software; you can redistribute it and/or
0007: modify it under the terms of the GNU Lesser General Public
0008: License as published by the Free Software Foundation; either
0009: version 2.1 of the License, or (at your option) any later version.
0010:
0011: This library is distributed in the hope that it will be useful,
0012: but WITHOUT ANY WARRANTY; without even the implied warranty of
0013: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0014: Lesser General Public License for more details.
0015:
0016: You should have received a copy of the GNU Lesser General Public
0017: License along with this library; if not,see http://www.gnu.org/licenses/lgpl.txt
0018: */
0019:
0020: package net.homeip.donaldm.httpdbase4j;
0021:
0022: import java.io.BufferedReader;
0023: import java.io.FileNotFoundException;
0024: import java.io.IOException;
0025: import java.io.InputStream;
0026: import java.io.InputStreamReader;
0027: import java.io.PrintStream;
0028: import java.lang.reflect.Method;
0029: import java.net.InetSocketAddress;
0030: import java.net.ServerSocket;
0031: import java.security.KeyManagementException;
0032: import java.security.KeyStore;
0033: import java.security.KeyStoreException;
0034: import java.security.NoSuchAlgorithmException;
0035: import java.security.UnrecoverableKeyException;
0036: import java.security.cert.CertificateException;
0037: import java.util.ArrayList;
0038: import java.util.HashMap;
0039: import java.util.Iterator;
0040: import java.util.Map;
0041: import java.util.TreeSet;
0042: import java.util.concurrent.ExecutorService;
0043: import java.util.concurrent.Executors;
0044: import java.util.concurrent.LinkedBlockingQueue;
0045: import java.util.concurrent.ThreadFactory;
0046: import java.util.concurrent.ThreadPoolExecutor;
0047: import java.util.concurrent.TimeUnit;
0048: import java.util.concurrent.atomic.AtomicLong;
0049:
0050: import javax.net.ssl.KeyManagerFactory;
0051: import javax.net.ssl.SSLContext;
0052: import javax.net.ssl.SSLParameters;
0053: import javax.net.ssl.TrustManagerFactory;
0054:
0055: import com.sun.net.httpserver.Authenticator;
0056: import com.sun.net.httpserver.HttpContext;
0057: import com.sun.net.httpserver.HttpExchange;
0058: import com.sun.net.httpserver.HttpHandler;
0059: import com.sun.net.httpserver.HttpServer;
0060: import com.sun.net.httpserver.HttpsConfigurator;
0061: import com.sun.net.httpserver.HttpsParameters;
0062: import com.sun.net.httpserver.HttpsServer;
0063:
0064: import de.schlichtherle.io.FileInputStream;
0065:
0066: /**
0067: * An embeddable Java web server that supports HTTP, HTTPS, templated
0068: * content and serving content from inside a jar or archive (Supported types: jar, zip,
0069: * tar, tar.gz, tar.bz2).
0070: * Based on the com.sun.net.httpserver.classes only available in Java 1.6.
0071: * (@see com.sun.net.httpserver or @link http://java.sun.com/javase/6/docs/jre/api/net/httpserver/spec/overview-summary.html
0072: * Also supports templated generation of content using the StringTemplate
0073: * library @link http://www.stringtemplate.org
0074: * </p>
0075: * <b>Usage</b><br>
0076: * <p>
0077: * To create a simple embedded web server on port 8088 with a home directory
0078: * at homeDir in the local filesystem and a root url at / ie / maps onto homeDir:
0079: * </p><br>
0080: * <code>
0081: * homeDir = new java.io.File("./htdocs");
0082: * httpd = new FileHttpd(homeDir, 10); // Creates a server with a threadpool of 10
0083: * httpd.setLogger(System.err); // Log to console
0084: * httpd.start(8088, "/");
0085: * </code>
0086: * <p>
0087: * To create a HTTPS embedded web server on port 8088 with a home directory
0088: * at homeDir in the local filesystem and a root url at / ie / maps onto homeDir:
0089: * </p><br>
0090: * <code>
0091: * homeDir = new java.io.File("./htdocs");
0092: * httpd = new FileHttpd(homeDir, 10);
0093: * m_httpd.start(8089, "/", keystore, password);
0094: * </code><br>
0095: * <p>
0096: * HttpdBase4J also supports serving content from inside the class path ie from
0097: * in the jar file.
0098: * </p>
0099: * <p>
0100: * The code below creates a server with content located in directory
0101: * resources/htdocs in a jar in the classpath (normally the main jar).
0102: * </p>
0103: * <code>
0104: * httpd = new ArchiveHttpd("/resources/htdocs", 10);
0105: * httpd.start(8088, "/");
0106: * </code><br>
0107: * <p>
0108: * HttpdBase4J also supports serving content from a specified archive file
0109: * Supported formats: jar, zip, tar, tar.gz and tar.bz2.
0110: * </p>
0111: * The code below creates a server with content located in directory
0112: * resources/htdocs in archive file content.zip.
0113: * </p>
0114: * <code>
0115: * httpd = new ArchiveHttpd(new File("content.zip"), "/resources/htdocs", 10);
0116: * httpd.start(8088, "/");
0117: * </code><br>
0118: * <p>
0119: * Templated content can also be created. Currently the StringTemplate library
0120: * (@see http://www.stringtemplate.org) is used but it should be relatively
0121: * easy to create user derived classes for other template implementations.
0122: * </p>
0123: * <p>
0124: * To create an HTTP embedded web server on port 8088 serving templated content
0125: * from resources/htdocs in the classpath and having template file handler
0126: * (A Java class implenting the Templatable interface that is used to fill the
0127: * templates) in net.homeip.donaldm.test.templates:
0128: * </p><br>
0129: * <code>
0130: * httpd = new ArchiveHttpd("resources/htdocs", 10);
0131: * StringTemplateHandler stHandler = new ArchiveStringTemplateHandler(httpd,
0132: "net.homeip.donaldm.test.templates");
0133: * httpd.addHandler(".st", stHandler); // .st extension = template files
0134: * httpd.start(m_port, "/");
0135: * </code><br>
0136: * <p>
0137: * To implement a embedded web server with POST handler for URL /post:
0138: * </p><br>
0139: * <code>
0140: * m_httpd = new FileHttpd(homeDir, 10);
0141: * httpd.addPostHandler("/post", postHandler); postHandler implements Postable
0142: * httpd.start(8088, "/");
0143: * </code>
0144: * <br>
0145: * <p>The Httpd class also provides many overidable methods:</p><br>
0146: * <code>
0147: * httpd = new TestOverideHttpd(m_homeDir, 10);
0148: * httpd.start(m_port, "/");
0149: * <br>
0150: * class TestOverideHttpd
0151: * {
0152: * public HttpResponse onServeHeaders(long id, HttpExchange ex, Request request)
0153: * {
0154: * //Create or amend content
0155: * }
0156: * public InputStream onServeBody(long id, HttpExchange ex, Request request)
0157: * {
0158: * // Return amended or created content
0159: * }
0160: *
0161: * }
0162: * </code><br>
0163: * <p>
0164: * Some of the overidable methods include: onAllowDirectoryBrowse, onCreateExecutor,
0165: * onCreateRequestHandler, onFileNotFound, onAllowDirectoryBrowse, onListDirectory,
0166: * onPreServe, onPostServe etc. See the documention of HttpHandleable</p><br>
0167: * @see FileHttpd
0168: * @see ArchiveHttpd
0169: * @see HttpHandleable
0170: * @see Postable
0171: * @author Donald Munro
0172: */
0173: abstract public class Httpd implements HttpHandleable, Postable
0174: //==============================================================
0175: {
0176: /**
0177: * Thread model for the server. SINGLE, MULTI or POOL.
0178: */
0179: public enum ThreadModel {
0180: SINGLE, MULTI, POOL
0181: };
0182:
0183: public static String EOL = System.getProperty("line.separator");
0184:
0185: /**
0186: * The HTTP server class. May be either HttpServer or HttpsServer
0187: */
0188: protected HttpServer m_http = null;
0189:
0190: protected int m_port = 8080;
0191:
0192: protected HttpContext m_context = null;
0193:
0194: protected HttpHandler m_requestHandler = null;
0195:
0196: /**
0197: * Verbose logging switch
0198: */
0199: protected boolean m_isVerbose = false;
0200:
0201: /**
0202: * The file names recognised as default to use if no filename is
0203: * specified in the URI. Defaults to index.html, index.htm
0204: */
0205: protected ArrayList<String> m_defaultFiles = new ArrayList<String>();
0206:
0207: /**
0208: * Maps file extensions onto handlers
0209: * @see Httpd#addHandler
0210: */
0211: protected Map<String, HttpHandleable> m_handlerMap = new HashMap<String, HttpHandleable>();
0212: /**
0213: * Maps POST handlers onto URLs or extensions.
0214: * @see Httpd#addPostHandler
0215: */
0216: protected Map<String, Postable> m_postHandlerMap = new HashMap<String, Postable>();
0217:
0218: protected boolean m_mustCache = true;
0219:
0220: /*
0221: * The threading model used by this server.
0222: */
0223: protected ThreadModel m_threadModel = ThreadModel.MULTI;
0224:
0225: /*
0226: * The executor used to create server threads.
0227: */
0228: protected ExecutorService m_executor = null;
0229:
0230: protected int m_poolMax = 50;
0231:
0232: protected int m_poolSize = 5;
0233:
0234: protected boolean m_isStarted = false;
0235:
0236: static private AtomicLong m_sequence = new AtomicLong(0);
0237:
0238: public Httpd()
0239: //------------
0240: {
0241: m_defaultFiles.add("index.html");
0242: m_defaultFiles.add("index.htm");
0243: }
0244:
0245: public boolean isStarted() {
0246: return m_isStarted;
0247: }
0248:
0249: public void setCaching(boolean b) {
0250: m_mustCache = b;
0251: }
0252:
0253: public boolean getCaching() {
0254: return m_mustCache;
0255: }
0256:
0257: abstract public String getHomePath();
0258:
0259: protected void setDefaultPoolSizes()
0260: //-----------------------------------
0261: {
0262: switch (m_threadModel) {
0263: case MULTI:
0264: m_poolSize = 5;
0265: m_poolMax = Integer.MAX_VALUE;
0266: break;
0267:
0268: case POOL:
0269: m_poolSize = 10;
0270: m_poolMax = 10;
0271: break;
0272: }
0273: }
0274:
0275: /**
0276: *
0277: * @param b true to set verbose mode on otherwise false
0278: */
0279: public void setVerbose(boolean b) {
0280: m_isVerbose = b;
0281: }
0282:
0283: /**
0284: *
0285: * @return verbose mode
0286: */
0287: public boolean getVerbose() {
0288: return m_isVerbose;
0289: }
0290:
0291: /**
0292: *
0293: * @return The next id to use as a transaction id
0294: */
0295: public static long getNextSequence() {
0296: return m_sequence.getAndIncrement();
0297: }
0298:
0299: /**
0300: * @return The TCP port for this server
0301: */
0302: public int getPort()
0303: //------------------
0304: {
0305: return m_port;
0306: }
0307:
0308: /**
0309: * Set the Authenticator class used for HTTP authentication.
0310: * @param authenticator The authenticator to use
0311: */
0312: public void setAuthenticator(Authenticator authenticator)
0313: //-------------------------------------------------------
0314: {
0315: if (m_context != null)
0316: m_context.setAuthenticator(authenticator);
0317: }
0318:
0319: /**
0320: * Should only be used before calling start.
0321: * @param threadModel The threading model to use
0322: */
0323: public void setThreadModel(ThreadModel threadModel)
0324: //-------------------------------------------------
0325: {
0326: if (m_isStarted)
0327: return;
0328: m_threadModel = threadModel;
0329: m_poolSize = 5;
0330: m_poolMax = 50;
0331: }
0332:
0333: /**
0334: * Sets thread model to POOL and sets the constant pool size to the specified
0335: * value.
0336: * Should only be used before calling start.
0337: * @param size The size of the fixed size thread pool
0338: */
0339: public void setThreadPool(int size)
0340: //---------------------------------
0341: {
0342: if (m_isStarted)
0343: return;
0344: m_threadModel = ThreadModel.POOL;
0345: m_poolSize = size;
0346: m_poolMax = size;
0347: }
0348:
0349: /**
0350: * Sets thread model to POOL and sets the pool size and max pool size to the
0351: * specified values.
0352: * Should only be used before calling start.
0353: * @param size The size of the thread pool
0354: * @param max The maximum size of the thread pool
0355: */
0356: public void setThreadPool(int size, int max)
0357: //-----------------------------------------
0358: {
0359: if (m_isStarted)
0360: return;
0361: m_threadModel = ThreadModel.POOL;
0362: m_poolSize = size;
0363: m_poolMax = max;
0364: }
0365:
0366: /**
0367: * Add a handler for an extension.
0368: * @param extension The file extension (including the .)
0369: * @return The handler for <i>extension</i> or null if no handler found
0370: **/
0371: public HttpHandleable getHandler(String extension)
0372: //------------------------------------------------
0373: {
0374: extension = extension.trim();
0375: if (!extension.startsWith("."))
0376: extension = "." + extension;
0377: return m_handlerMap.get(extension);
0378: }
0379:
0380: /**
0381: * Add a handler for an extension.
0382: * @param extension The file extension (including the .)
0383: * @param handler A class implementing the HttpHandleable interface.
0384: **/
0385: public void addHandler(String extension, HttpHandleable handler)
0386: //----------------------------------------------
0387: {
0388: extension = extension.trim();
0389: if (!extension.startsWith("."))
0390: extension = "." + extension;
0391: m_handlerMap.put(extension, handler);
0392: }
0393:
0394: /**
0395: * Remove a handler for an extension.
0396: * @param extension The file extension (including the .)
0397: * @return The handler that was removed
0398: **/
0399: public HttpHandleable removeHandler(String extension)
0400: //----------------------------------------------
0401: {
0402: extension = extension.trim();
0403: if (!extension.startsWith("."))
0404: extension = "." + extension;
0405: return m_handlerMap.remove(extension);
0406: }
0407:
0408: /**
0409: * Add a POST handler. If the name parameter starts with a full stop ('.')
0410: * then it is assumed to be a file extension and maps all requests with that
0411: * extension to the supplied handler. If name does not start with a . then
0412: * it maps a specific request URI onto the supplied handler.<br>
0413: * EG httpd.addPostHandler(".st", myHandler);<br>
0414: * httpd.addPostHandler("/invoices/new.st", myHandler);<br>
0415: * @param name A file extension (including the .) or a full request uri
0416: * @param handler A class implementing the Postable interface.
0417: *
0418: **/
0419: public void addPostHandler(String name, Postable handler)
0420: //------------------------------------------------------------------
0421: {
0422: m_postHandlerMap.put(name, handler);
0423: }
0424:
0425: /**
0426: * Remove a POST handler.
0427: * @see Httpd#addPostHandler
0428: * @param name A file extension (including the .) or a full request uri
0429: * @return The POST handler that was removed
0430: */
0431: public Postable removePostHandler(String name)
0432: //-------------------------------------------------
0433: {
0434: return m_postHandlerMap.remove(name);
0435: }
0436:
0437: /**
0438: * Start a standard (non HTTPS) server on the supplied port using the
0439: * supplied path as the root URI path.
0440: * @param port The TCP port for the server
0441: * @param root The root URI path. The first character of path must be '/'.
0442: * @return true if the server started successfully otherwise false
0443: * @throws java.io.IOException
0444: * @throws java.lang.NoSuchFieldException
0445: */
0446: public boolean start(int port, String root) throws IOException,
0447: NoSuchFieldException
0448: //-----------------------------------------
0449: {
0450: return start(port, root, null);
0451: }
0452:
0453: /**
0454: * Start a standard (non HTTPS) server on the supplied port using the
0455: * supplied path as the root URI path and the supplied Authenticator class
0456: * for HTTP authentication.
0457: * @param port The TCP port for the server
0458: * @param root The root URI path. The first character of path must be '/'.
0459: * @param authenticator The Authenticator derived class to use for
0460: * authentication.
0461: * @see com.sun.net.httpserver.Authenticator or
0462: * @link http://java.sun.com/javase/6/docs/jre/api/net/httpserver/spec/overview-summary.html
0463: * @return true if the server started successfully otherwise false
0464: * @throws java.io.IOException
0465: * @throws java.lang.NoSuchFieldException
0466: */
0467: public boolean start(int port, String root,
0468: Authenticator authenticator) throws IOException,
0469: NoSuchFieldException
0470: //----------------------------------------------------------------------
0471: {
0472: m_http = HttpServer.create(new InetSocketAddress(port), 20);
0473: m_http.setExecutor(onCreateExecutor());
0474: m_requestHandler = onCreateRequestHandler();
0475: m_context = m_http.createContext(root, m_requestHandler);
0476: if (authenticator != null)
0477: m_context.setAuthenticator(authenticator);
0478: m_http.start();
0479: m_port = port;
0480: m_isStarted = true;
0481: return true;
0482: }
0483:
0484: /**
0485: * Start an HTTPS server on the supplied port using the
0486: * supplied root as the root URI path and the supplied keystore and password
0487: * for SSL certificates.
0488: * @param port The TCP port for the server
0489: * @param root The root URI path. The first character of path must be '/'.
0490: * @param keystore SSL certificate keystore
0491: * @param password SSL certificate
0492: * @return true if the server started successfully otherwise false
0493: * @throws java.security.KeyStoreException
0494: * @throws java.security.NoSuchAlgorithmException
0495: * @throws java.security.cert.CertificateException
0496: * @throws java.security.UnrecoverableKeyException
0497: * @throws java.security.KeyManagementException
0498: * @throws java.lang.NoSuchFieldException
0499: * @throws java.io.FileNotFoundException
0500: * @throws java.io.IOException
0501: * @see javax.net.ssl.SSLContext
0502: */
0503: public boolean start(int port, String root, String keystore,
0504: String password) throws KeyStoreException,
0505: NoSuchAlgorithmException, CertificateException,
0506: UnrecoverableKeyException, KeyManagementException,
0507: NoSuchFieldException, FileNotFoundException, IOException
0508: //--------------------------------------------------------------------------
0509: {
0510: return start(port, root, null, keystore, password);
0511: }
0512:
0513: /**
0514: * Start an HTTPS server on the supplied port using the
0515: * supplied root as the root URI path and the supplied keystore and password
0516: * for SSL certificates.
0517: * @param port The TCP port for the server
0518: * @param root The root URI path. The first character of path must be '/'.
0519: * @param authenticator The Authenticator derived class to use for
0520: * authentication.
0521: * @see com.sun.net.httpserver.Authenticator or
0522: * @link http://java.sun.com/javase/6/docs/jre/api/net/httpserver/spec/overview-summary.html
0523: * @param keystore SSL certificate keystore
0524: * @param password SSL certificate
0525: * @return true if the server started successfully otherwise false
0526: * @throws java.security.KeyStoreException
0527: * @throws java.security.NoSuchAlgorithmException
0528: * @throws java.security.cert.CertificateException
0529: * @throws java.security.UnrecoverableKeyException
0530: * @throws java.security.KeyManagementException
0531: * @throws java.lang.NoSuchFieldException
0532: * @throws java.io.FileNotFoundException
0533: * @throws java.io.IOException
0534: * @see javax.net.ssl.SSLContext
0535: */
0536: public boolean start(int port, String root,
0537: Authenticator authenticator, String keystore,
0538: String password) throws KeyStoreException,
0539: NoSuchAlgorithmException, CertificateException,
0540: UnrecoverableKeyException, KeyManagementException,
0541: NoSuchFieldException, FileNotFoundException, IOException
0542: //--------------------------------------------------------------------------
0543: {
0544: SSLContext ssl = onCreateSSLConfiguration(keystore, password);
0545: if (ssl == null)
0546: return false;
0547:
0548: m_http = HttpsServer.create(new InetSocketAddress(port), 20);
0549: ((HttpsServer) m_http)
0550: .setHttpsConfigurator(new HttpsConfigurator(ssl) {
0551: public void configure(HttpsParameters params) {
0552: SSLContext c = getSSLContext();
0553: onSetSSLParameters(c, params);
0554: }
0555: });
0556: m_http.setExecutor(onCreateExecutor());
0557: m_requestHandler = onCreateRequestHandler();
0558: m_context = m_http.createContext(root, m_requestHandler);
0559: if (authenticator != null)
0560: m_context.setAuthenticator(authenticator);
0561: m_http.start();
0562: m_port = port;
0563: m_isStarted = true;
0564: return true;
0565: }
0566:
0567: /**
0568: * Overide to create SSLContext.
0569: * @param keystore SSL certificate keystore
0570: * @param password SSL certificate
0571: * @return The SSLContext to use for HTTPS connections.
0572: * @throws java.security.NoSuchAlgorithmException
0573: * @throws java.security.KeyStoreException
0574: * @throws java.io.FileNotFoundException
0575: * @throws java.io.IOException
0576: * @throws java.security.cert.CertificateException
0577: * @throws java.security.UnrecoverableKeyException
0578: * @throws java.security.KeyManagementException
0579: */
0580: protected SSLContext onCreateSSLConfiguration(String keystore,
0581: String password) throws NoSuchAlgorithmException,
0582: KeyStoreException, FileNotFoundException, IOException,
0583: CertificateException, UnrecoverableKeyException,
0584: KeyManagementException
0585: //------------------------------------------------------------------------
0586: {
0587: SSLContext ssl = null;
0588: if (keystore == null) { // See TODO: HttpdBase4J-00001 below
0589: java.io.File certificate = null;
0590: String home = System.getProperty("java.home", null);
0591: java.io.File homeDir = new java.io.File(home);
0592: java.io.File binDir = new java.io.File(home, "bin");
0593: java.io.File keytool = null;
0594: if (System.getProperty("os.name").toLowerCase().contains(
0595: "windows"))
0596: keytool = new java.io.File(binDir, "keytool.exe");
0597: else
0598: keytool = new java.io.File(binDir, "keytool");
0599: Runtime r = Runtime.getRuntime();
0600: Process p = null;
0601: certificate = new java.io.File("cert");
0602: certificate.mkdirs();
0603: certificate = new java.io.File(certificate, "ssl-key");
0604: certificate.delete();
0605: if ((password == null) || (password.length() <= 6))
0606: password = "stupidwasteoftime";
0607: String command = keytool.getAbsolutePath()
0608: + " -selfcert -keystore "
0609: + certificate.getAbsolutePath()
0610: + " -genkey -dname cn=Ossama,ou=AlQueada,o=AlQueada,c=US "
0611: + "-alias HttpdBase4J -keyalg RSA " + "-storepass "
0612: + password + " -keypass " + password;
0613: StringBuilder stdout = new StringBuilder(), stderr = new StringBuilder();
0614: if (r != null) {
0615: BufferedReader input = null, error = null;
0616: int status = 1;
0617: try {
0618: p = r.exec(command);
0619: input = new BufferedReader(new InputStreamReader(p
0620: .getInputStream()));
0621: error = new BufferedReader(new InputStreamReader(p
0622: .getErrorStream()));
0623: String line;
0624: if (stdout != null)
0625: while ((line = input.readLine()) != null)
0626: stdout.append(line);
0627: if (stderr != null)
0628: while ((line = error.readLine()) != null)
0629: stderr.append(line);
0630: p.waitFor();
0631: status = p.exitValue();
0632: if (status != 0) {
0633: Httpd
0634: .Log(
0635: LogLevel.ERROR,
0636: "Error creating SSL certificate:"
0637: + System
0638: .getProperty("line.separator")
0639: + command
0640: + System
0641: .getProperty("line.separator")
0642: + stdout.toString()
0643: + System
0644: .getProperty("line.separator")
0645: + stderr.toString(),
0646: null);
0647: return null;
0648: }
0649: } catch (Exception e) {
0650: Httpd
0651: .Log(
0652: LogLevel.ERROR,
0653: "Exception creating SSL certificate:"
0654: + System
0655: .getProperty("line.separator")
0656: + command
0657: + System
0658: .getProperty("line.separator")
0659: + e.getMessage()
0660: + System
0661: .getProperty("line.separator")
0662: + stdout.toString()
0663: + System
0664: .getProperty("line.separator")
0665: + stderr.toString(), null);
0666: return null;
0667: } finally {
0668: if (input != null)
0669: try {
0670: input.close();
0671: } catch (Exception e) {
0672: }
0673: if (error != null)
0674: try {
0675: error.close();
0676: } catch (Exception e) {
0677: }
0678: keystore = certificate.getAbsolutePath();
0679: }
0680: }
0681: }
0682: // keytool -selfcert -keystore cert/ssl-key -genkey
0683: // -dname "cn=Ossama,ou=AlQueada,o=AlQueada,c=US"
0684: // -alias HttpdBase4J -keyalg RSA -storepass mypassword -keypass mypassword
0685: char[] passphrase = password.toCharArray();
0686: KeyStore ks = KeyStore.getInstance("JKS");
0687: ks.load(new FileInputStream(keystore), passphrase);
0688: KeyManagerFactory kmf = KeyManagerFactory
0689: .getInstance("SunX509");
0690: kmf.init(ks, passphrase);
0691: TrustManagerFactory tmf = TrustManagerFactory
0692: .getInstance("SunX509");
0693: tmf.init(ks);
0694: ssl = SSLContext.getInstance("TLS");
0695: ssl.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
0696: return ssl;
0697: }
0698:
0699: /* TODO: HttpdBase4J-00001 Setting up a TrustManager to trust all certificates
0700: * does not appear to work.
0701: protected SSLContext onCreateSSLConfiguration(String keystore,
0702: String password) throws NoSuchAlgorithmException,
0703: KeyStoreException, FileNotFoundException, IOException,
0704: CertificateException, UnrecoverableKeyException,
0705: KeyManagementException
0706: //------------------------------------------------------------------------
0707: {
0708: SSLContext ssl = null;
0709: if (keystore == null)
0710: {
0711: try
0712: {
0713: new URL("https://0.0.0.0/").getContent();
0714: }
0715: catch (IOException guaranteedException) { }
0716: TrustManager[] trustAllCerts = new TrustManager[] {
0717: new X509TrustManager()
0718: {
0719: public java.security.cert.X509Certificate[] getAcceptedIssuers()
0720: {
0721: return null;
0722: }
0723:
0724: public void checkClientTrusted(
0725: java.security.cert.X509Certificate[] certs, String authType)
0726: {}
0727:
0728: public void checkServerTrusted(
0729: java.security.cert.X509Certificate[] certs, String authType)
0730: {}
0731: } };
0732: ssl = SSLContext.getInstance("SSLv3");
0733: //ssl = SSLContext.getInstance("TLS");
0734: ssl.init(null, trustAllCerts, new java.security.SecureRandom());
0735: HttpsURLConnection.setDefaultSSLSocketFactory(ssl.getSocketFactory());
0736: HttpsURLConnection.setDefaultHostnameVerifier(
0737: new HostnameVerifier()
0738: {
0739: public boolean verify(String host, SSLSession sslSession)
0740: {
0741: return true;
0742: }
0743:
0744: });
0745: }
0746: else
0747: {
0748: // keytool -selfcert -keystore cert/ssl-key -genkey
0749: // -dname "cn=Me,ou=ALQueada,o=AlQueada, c=US"
0750: // -alias HttpdBase4J -keyalg RSA
0751: char[] passphrase = password.toCharArray();
0752: KeyStore ks = KeyStore.getInstance("JKS");
0753: ks.load(new FileInputStream(keystore), passphrase);
0754: KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
0755: kmf.init(ks, passphrase);
0756: TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
0757: tmf.init(ks);
0758: ssl = SSLContext.getInstance("TLS");
0759: ssl.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
0760: }
0761: return ssl;
0762: } */
0763:
0764: /** Called every time a HTTPS connection is made to set the SSL
0765: * parameters. Overide to customize these parameters.
0766: *
0767: * @param c The SSLContext for this connection
0768: * @param params The HTTPS paramaters for this connection
0769: */
0770: protected void onSetSSLParameters(SSLContext c,
0771: HttpsParameters params)
0772: //---------------------------------------------------------------------
0773: {
0774: //InetSocketAddress remote = params.getClientAddress()
0775: SSLParameters sslparams = c.getDefaultSSLParameters();
0776: //if (remote.equals (...) )
0777: params.setSSLParameters(sslparams);
0778: }
0779:
0780: /**
0781: * Attempt to stop this server.
0782: *
0783: * @param timeout Amount of time (in seconds) to wait for the server to stop.
0784: * @return true if stopped succesfully, otherwise false
0785: */
0786: public boolean stop(int timeout)
0787: //------------------------------
0788: {
0789: if (!m_isStarted)
0790: return false;
0791: if (timeout < 0)
0792: timeout = 5;
0793: m_http.stop(timeout);
0794: ServerSocket ss = null;
0795: try {
0796: ss = new ServerSocket(m_port, 5);
0797: } catch (IOException ex) {
0798: return false;
0799: } finally {
0800: if (ss != null)
0801: try {
0802: ss.close();
0803: } catch (Exception e) {
0804: }
0805: }
0806: m_isStarted = false;
0807: return true;
0808: }
0809:
0810: /**
0811: * Overide to create the request handler
0812: * @return The request handler
0813: */
0814: abstract protected HttpHandler onCreateRequestHandler();
0815:
0816: private LinkedBlockingQueue<Runnable> m_blockingQueue = new LinkedBlockingQueue<Runnable>();
0817:
0818: private ThreadFactory m_threadFactory = new ThreadFactory()
0819: //=========================================================
0820: {
0821: public Thread newThread(Runnable r) {
0822: Thread t = new Thread(r);
0823: t.setDaemon(true);
0824: t.setName("Httpd:" + m_port);
0825: return t;
0826:
0827: }
0828: };
0829:
0830: /**
0831: * Overide to create the thread executor.
0832: * @return The ExecutorService thread executor service to use for thread
0833: * creation.
0834: * @throws java.lang.NoSuchFieldException
0835: */
0836: protected ExecutorService onCreateExecutor()
0837: throws NoSuchFieldException
0838: //---------------------------------------------------------------------
0839: {
0840: switch (m_threadModel) {
0841: case SINGLE:
0842: m_executor = Executors
0843: .newSingleThreadExecutor(m_threadFactory);
0844: break;
0845:
0846: case MULTI:
0847: //m_executor = Executors.newCachedThreadPool();
0848: m_blockingQueue = new LinkedBlockingQueue<Runnable>();
0849: m_executor = new ThreadPoolExecutor(m_poolSize,
0850: Integer.MAX_VALUE, 50000L, TimeUnit.MILLISECONDS,
0851: m_blockingQueue, m_threadFactory);
0852: break;
0853:
0854: case POOL:
0855: //m_executor = Executors.newFixedThreadPool(m_threadPoolCount);
0856: m_blockingQueue = new LinkedBlockingQueue<Runnable>();
0857: m_executor = new ThreadPoolExecutor(m_poolSize, m_poolMax,
0858: 50000L, TimeUnit.MILLISECONDS, m_blockingQueue,
0859: m_threadFactory);
0860: break;
0861:
0862: default:
0863: m_executor = null;
0864: throw new NoSuchFieldException("Invalid thread model "
0865: + m_threadModel.toString());
0866: }
0867: return m_executor;
0868: }
0869:
0870: /**
0871: * Default directory list. Overide to customise the directory listing
0872: * output.
0873: * @param request The Request instance
0874: * @return A String ncontaining the HTML for the directory listing output.
0875: */
0876: public String onListDirectory(Request request)
0877: //--------------------------------------------
0878: {
0879: StringBuffer html = new StringBuffer("<html><head><title>"
0880: + "Directory listing: "
0881: + request.getURI().toASCIIString()
0882: + "</title></head><body>");
0883: html.append("<H1>");
0884: html.append("Directory: ");
0885: html.append(request.getName());
0886: html.append("</H1>");
0887:
0888: TreeSet<DirItemInterface> set = request
0889: .getDirListDirectories(DirItemInterface.SORTBY.NAME);
0890: for (Iterator<DirItemInterface> i = set.iterator(); i.hasNext();) {
0891: DirItemInterface di = i.next();
0892: html.append("<a href=\"");
0893: html.append(di.getName());
0894: html.append("\">");
0895: html.append(" [");
0896: html.append(di.getName());
0897: html.append("] </a><br>");
0898: }
0899:
0900: set = request.getDirListFiles(DirItemInterface.SORTBY.NAME);
0901: for (Iterator<DirItemInterface> i = set.iterator(); i.hasNext();) {
0902: DirItemInterface di = i.next();
0903: html.append("<a href=\"");
0904: html.append(di.getName());
0905: html.append("\">");
0906: html.append(" [");
0907: html.append(di.getName());
0908: html.append("] </a><br>");
0909: }
0910:
0911: html.append("</body></html>");
0912: return html.toString();
0913: }
0914:
0915: public boolean onIsCacheable(long id, HttpExchange ex,
0916: Request request)
0917: //---------------------------------------------------------------------
0918: {
0919: if (!m_mustCache)
0920: return false;
0921: return request.isCacheable();
0922: }
0923:
0924: public java.io.File onGetCachedFile(long id, HttpExchange ex,
0925: Request request)
0926: //--------------------------------------------------------------------
0927: {
0928: return null;
0929: }
0930:
0931: public boolean onPreServe(long id, HttpExchange ex, Request request) {
0932: return true;
0933: }
0934:
0935: public HttpResponse onServeHeaders(long id, HttpExchange ex,
0936: Request request) {
0937: return null;
0938: }
0939:
0940: public InputStream onServeBody(long id, HttpExchange ex,
0941: Request request) {
0942: return null;
0943: }
0944:
0945: public void onPostServe(long id, HttpExchange ex, Request request,
0946: boolean isOK) {
0947: }
0948:
0949: public Request onFileNotFound(long id, HttpExchange ex,
0950: Request request) {
0951: return null;
0952: }
0953:
0954: public Object onHandlePost(long id, HttpExchange ex,
0955: Request request, HttpResponse response, java.io.File dir,
0956: Object... extraParameters) {
0957: return null;
0958: }
0959:
0960: public boolean onAllowDirectoryBrowse(String directory)
0961: //-------------------------------------------------
0962: {
0963: return true;
0964: }
0965:
0966: public void addDefaultFile(String file)
0967: //----------------------------------------
0968: {
0969: if (m_defaultFiles == null)
0970: m_defaultFiles = new ArrayList<String>();
0971: file = file.trim();
0972: if (!m_defaultFiles.contains(file))
0973: m_defaultFiles.add(file);
0974: }
0975:
0976: static private PrintStream m_logStream = null;
0977: static protected Object m_logger = null;
0978: static protected Method m_errorMethod = null;
0979: static protected Method m_infoMethod = null;
0980: static protected Method m_debugMethod = null;
0981:
0982: /**
0983: * Logger level. ERROR, INFO or DEBUG
0984: */
0985: static protected enum LogLevel {
0986: ERROR, INFO, DEBUG
0987: }
0988:
0989: /**
0990: * Set a logger to use. The logger must either implement the slf4j interface
0991: * (@link http://www.slf4j.org) or be a PrintStream instance for logging.
0992: * This method uses reflection to invoke logging methods so applications
0993: * which don't require logging don't need any logging jar files in their
0994: * classpath. Note: this probably won't be a good idea in high volume
0995: * applications.
0996: *
0997: * @param ologger The logger to use (must implement the slf4j interface
0998: * (http://www.slf4j.org) (or be an instance of PrintStream for simple
0999: * "logging" to the console. Note if setLogger is called twice, once with a
1000: * PrintStream and once with an slf4j logger then it will print to the
1001: * stream and log although the same effect can normally be achieved by
1002: * configuring the logger.
1003: *
1004: * @return true if logger successfully set otherwise false.
1005: */
1006: @SuppressWarnings("unchecked")
1007: public boolean setLogger(Object ologger)
1008: //--------------------------------------
1009: {
1010: if (ologger instanceof PrintStream) {
1011: m_logStream = (PrintStream) ologger;
1012: return true;
1013: }
1014: try {
1015: Class loggerClass = Class.forName("org.slf4j.Logger");
1016: m_logger = ologger;
1017: Class[] parameterTypes = new Class[2];
1018: parameterTypes[0] = Class.forName("java.lang.String");
1019: parameterTypes[1] = Class.forName("java.lang.Throwable");
1020: m_errorMethod = loggerClass.cast(m_logger).getClass()
1021: .getMethod("error", parameterTypes);
1022: m_infoMethod = loggerClass.cast(m_logger).getClass()
1023: .getMethod("info", parameterTypes);
1024: m_debugMethod = loggerClass.cast(m_logger).getClass()
1025: .getMethod("debug", parameterTypes);
1026: } catch (Throwable t) {
1027: System.err
1028: .println("setLogger: Error getting logging methods "
1029: + " Check org.slf4j.Logger is in class path ("
1030: + t.getMessage() + ")");
1031: t.printStackTrace(System.err);
1032: m_logger = null;
1033: return false;
1034: }
1035: return true;
1036: }
1037:
1038: /**
1039: * Log errors, information or warnings.
1040: *
1041: * @param level The log level chosen from the LogLevelenum viz ERROR, INFO or
1042: * DEBUG
1043: * @param message - The message to be logged
1044: * @param e - The Java exception to be logged. Can be null.
1045: */
1046: static protected void Log(LogLevel level, String message,
1047: Throwable e)
1048: //--------------------------------------------------------------------
1049: {
1050: if (m_logStream != null) {
1051: synchronized (m_logStream) {
1052: m_logStream.println(level.toString() + ": " + message);
1053: if (e != null)
1054: e.printStackTrace(m_logStream);
1055: }
1056: }
1057: if (m_logger == null)
1058: return;
1059: try {
1060: Object[] parameters = new Object[2];
1061: parameters[0] = message;
1062: parameters[1] = e;
1063: switch (level) {
1064: case ERROR:
1065: m_errorMethod.invoke(m_logger, parameters);
1066: break;
1067: case INFO:
1068: m_infoMethod.invoke(m_logger, parameters);
1069: break;
1070: case DEBUG:
1071: m_debugMethod.invoke(m_logger, parameters);
1072: break;
1073: }
1074: } catch (Exception ee) {
1075: System.err.println("Could not invoke logger method ("
1076: + ee.getMessage() + ")");
1077: ee.printStackTrace(System.err);
1078: }
1079: }
1080:
1081: /*
1082: public static void main(String[] args) throws UnrecoverableKeyException,
1083: KeyManagementException, KeyStoreException,
1084: NoSuchAlgorithmException, CertificateException,
1085: FileNotFoundException, NoSuchFieldException, IOException
1086: //--------------------------------------
1087: {
1088: if (args.length <= 0)
1089: {
1090: System.err.println("Home directory must be specified as first arg");
1091: System.exit(1);
1092: }
1093: java.io.File homeDir = new java.io.File(args[0]);
1094:
1095: int port = 8088;
1096: if (args.length > 1) port = Integer.parseInt(args[1]);
1097: Httpd httpsd = new Httpd(homeDir, 10);
1098: httpsd.setVerbose(true);
1099: httpsd.setLogger(System.err);
1100: //httpsd.start(port, "/", "cert/ssl-key", "bigwaves");
1101: httpsd.start(port, "/", null, null);
1102: System.out.println("Httpds running on port " + port);
1103:
1104: Httpd httpd = new Httpd(homeDir, 10);
1105: httpd.setVerbose(true);
1106: httpd.setLogger(System.err);
1107: httpd.start(port-1, "/");
1108: System.out.println("Httpd running on port " + (port-1));
1109: }
1110:
1111: public static void main( String[] args )
1112: {
1113: java.io.File propertiesFile = null, homeDir =new java.io.File("."),
1114: SSLhomeDir =new java.io.File("."),
1115: templateDir =new java.io.File(homeDir, "templates");
1116: int port = 6502, sslport = 8086;
1117: // keytool -selfcert -keystore cert/ssl-key -genkey -dname "cn=Me, ou=ALQueada, o=AlQueada, c=US" -alias NanoHTTPD -keyalg RSA
1118: String keystore = "cert/ssl-key", password = "bigwaves";
1119: if (args.length > 0)
1120: {
1121: propertiesFile = new java.io.File(args[0]);
1122: if (! propertiesFile.exists())
1123: {
1124: System.err.println("Property file " +
1125: propertiesFile.getAbsolutePath() +" not found");
1126: return;
1127: }
1128: else
1129: {
1130: Properties properties = new Properties();
1131: try
1132: {
1133: properties.load(new FileInputStream(propertiesFile));
1134: }
1135: catch (Exception e)
1136: {
1137: System.err.println("Error reading from properties file " +
1138: propertiesFile.getAbsolutePath() + ". " +
1139: e.getMessage());
1140: return;
1141: }
1142: homeDir = new java.io.File(properties.getProperty("home", "."));
1143: SSLhomeDir = new java.io.File(properties.getProperty("sslhome", "."));
1144: templateDir =new java.io.File(properties.getProperty("templates",
1145: new java.io.File(homeDir, "templates").getAbsolutePath()));
1146: port = Integer.parseInt(properties.getProperty("port", "6502"));
1147: sslport = Integer.parseInt(properties.getProperty("sslport", "8086"));
1148: keystore = properties.getProperty("keystore", "cert/ssl-key");
1149: password = properties.getProperty("password", "bigwaves");
1150: }
1151: }
1152:
1153: StringTemplateHandler httpd = null, httpsd = null;
1154: try
1155: {
1156: httpd = new StringTemplateHandler(homeDir,
1157: "net.homeip.donaldm.TestNG.handlers",
1158: 10, 20);
1159: httpsd = new StringTemplateHandler(homeDir,
1160: "net.homeip.donaldm.TestNG.handlers",
1161: 10, 20);
1162: }
1163: catch (Exception e)
1164: {
1165: System.err.println("Exception creating server");
1166: e.printStackTrace(System.err);
1167: return;
1168: }
1169: httpd.setLogger(System.out);
1170: httpsd.setLogger(System.out);
1171:
1172: try
1173: {
1174: if ( (port > 0) && (httpd.start(port, "/")) )
1175: System.out.println("HTTP server running on port " + port + ". Home "
1176: + homeDir.getAbsolutePath());
1177: if ( (sslport > 0) && (httpsd.start(sslport, "/", password, keystore )) )
1178: System.out.println("HTTPS server running on port " + sslport +
1179: ". Home " + homeDir.getAbsolutePath());
1180: }
1181: catch (Exception e)
1182: {
1183: System.err.println("Exception starting server on port " + port);
1184: e.printStackTrace(System.err);
1185: return;
1186: }
1187:
1188:
1189: try { System.in.read(); } catch( Throwable t ) {};
1190: httpd.stop(10000);
1191: httpsd.stop(10000);
1192: }
1193:
1194: }*/
1195: }
|