NanoHTTPD.java revision 30fb85f55cbd8df3005e652da3781f51294baf90
1269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawkepackage fi.iki.elonen; 2269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke 38b3f33ce61812b29a5cacd1e7e6d350b40531f9britchie/* 48b3f33ce61812b29a5cacd1e7e6d350b40531f9britchie * #%L 58b3f33ce61812b29a5cacd1e7e6d350b40531f9britchie * NanoHttpd-Core 68b3f33ce61812b29a5cacd1e7e6d350b40531f9britchie * %% 78b3f33ce61812b29a5cacd1e7e6d350b40531f9britchie * Copyright (C) 2012 - 2015 nanohttpd 88b3f33ce61812b29a5cacd1e7e6d350b40531f9britchie * %% 98b3f33ce61812b29a5cacd1e7e6d350b40531f9britchie * Redistribution and use in source and binary forms, with or without modification, 108b3f33ce61812b29a5cacd1e7e6d350b40531f9britchie * are permitted provided that the following conditions are met: 118b3f33ce61812b29a5cacd1e7e6d350b40531f9britchie * 128b3f33ce61812b29a5cacd1e7e6d350b40531f9britchie * 1. Redistributions of source code must retain the above copyright notice, this 138b3f33ce61812b29a5cacd1e7e6d350b40531f9britchie * list of conditions and the following disclaimer. 148b3f33ce61812b29a5cacd1e7e6d350b40531f9britchie * 158b3f33ce61812b29a5cacd1e7e6d350b40531f9britchie * 2. Redistributions in binary form must reproduce the above copyright notice, 168b3f33ce61812b29a5cacd1e7e6d350b40531f9britchie * this list of conditions and the following disclaimer in the documentation 178b3f33ce61812b29a5cacd1e7e6d350b40531f9britchie * and/or other materials provided with the distribution. 188b3f33ce61812b29a5cacd1e7e6d350b40531f9britchie * 198b3f33ce61812b29a5cacd1e7e6d350b40531f9britchie * 3. Neither the name of the nanohttpd nor the names of its contributors 208b3f33ce61812b29a5cacd1e7e6d350b40531f9britchie * may be used to endorse or promote products derived from this software without 218b3f33ce61812b29a5cacd1e7e6d350b40531f9britchie * specific prior written permission. 228b3f33ce61812b29a5cacd1e7e6d350b40531f9britchie * 238b3f33ce61812b29a5cacd1e7e6d350b40531f9britchie * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 248b3f33ce61812b29a5cacd1e7e6d350b40531f9britchie * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 258b3f33ce61812b29a5cacd1e7e6d350b40531f9britchie * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 268b3f33ce61812b29a5cacd1e7e6d350b40531f9britchie * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 278b3f33ce61812b29a5cacd1e7e6d350b40531f9britchie * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 288b3f33ce61812b29a5cacd1e7e6d350b40531f9britchie * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 298b3f33ce61812b29a5cacd1e7e6d350b40531f9britchie * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 308b3f33ce61812b29a5cacd1e7e6d350b40531f9britchie * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 318b3f33ce61812b29a5cacd1e7e6d350b40531f9britchie * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 328b3f33ce61812b29a5cacd1e7e6d350b40531f9britchie * OF THE POSSIBILITY OF SUCH DAMAGE. 338b3f33ce61812b29a5cacd1e7e6d350b40531f9britchie * #L% 348b3f33ce61812b29a5cacd1e7e6d350b40531f9britchie */ 358b3f33ce61812b29a5cacd1e7e6d350b40531f9britchie 363526d931ae635536355e46a6b242405d309c70f3ritchieimport java.io.BufferedReader; 373526d931ae635536355e46a6b242405d309c70f3ritchieimport java.io.ByteArrayInputStream; 383526d931ae635536355e46a6b242405d309c70f3ritchieimport java.io.Closeable; 393526d931ae635536355e46a6b242405d309c70f3ritchieimport java.io.File; 403526d931ae635536355e46a6b242405d309c70f3ritchieimport java.io.FileInputStream; 413526d931ae635536355e46a6b242405d309c70f3ritchieimport java.io.FileOutputStream; 423526d931ae635536355e46a6b242405d309c70f3ritchieimport java.io.IOException; 433526d931ae635536355e46a6b242405d309c70f3ritchieimport java.io.InputStream; 443526d931ae635536355e46a6b242405d309c70f3ritchieimport java.io.InputStreamReader; 453526d931ae635536355e46a6b242405d309c70f3ritchieimport java.io.OutputStream; 463526d931ae635536355e46a6b242405d309c70f3ritchieimport java.io.PrintWriter; 473526d931ae635536355e46a6b242405d309c70f3ritchieimport java.io.PushbackInputStream; 483526d931ae635536355e46a6b242405d309c70f3ritchieimport java.io.RandomAccessFile; 493526d931ae635536355e46a6b242405d309c70f3ritchieimport java.io.UnsupportedEncodingException; 50b01d2c9086c3df2b63bd1eca602320cd7f45d24cSherif Moursiimport java.net.InetAddress; 51b01d2c9086c3df2b63bd1eca602320cd7f45d24cSherif Moursiimport java.net.InetSocketAddress; 52b01d2c9086c3df2b63bd1eca602320cd7f45d24cSherif Moursiimport java.net.ServerSocket; 53b01d2c9086c3df2b63bd1eca602320cd7f45d24cSherif Moursiimport java.net.Socket; 54b01d2c9086c3df2b63bd1eca602320cd7f45d24cSherif Moursiimport java.net.SocketException; 5513736e18ec88e3df74d055e061fb324e04778ad6Paul Hawkeimport java.net.SocketTimeoutException; 56b01d2c9086c3df2b63bd1eca602320cd7f45d24cSherif Moursiimport java.net.URLDecoder; 57dfea30a36949bc24a698ac39d8a714fc8f1c9e82Paul Hawkeimport java.nio.ByteBuffer; 58dfea30a36949bc24a698ac39d8a714fc8f1c9e82Paul Hawkeimport java.nio.channels.FileChannel; 5930fb85f55cbd8df3005e652da3781f51294baf90ritchieimport java.security.KeyStore; 607b88819e82b89ac3476ce060903468076a73de8fPaul Hawkeimport java.text.SimpleDateFormat; 61b01d2c9086c3df2b63bd1eca602320cd7f45d24cSherif Moursiimport java.util.ArrayList; 62b01d2c9086c3df2b63bd1eca602320cd7f45d24cSherif Moursiimport java.util.Calendar; 63b01d2c9086c3df2b63bd1eca602320cd7f45d24cSherif Moursiimport java.util.Date; 64b01d2c9086c3df2b63bd1eca602320cd7f45d24cSherif Moursiimport java.util.HashMap; 65b01d2c9086c3df2b63bd1eca602320cd7f45d24cSherif Moursiimport java.util.HashSet; 66b01d2c9086c3df2b63bd1eca602320cd7f45d24cSherif Moursiimport java.util.Iterator; 67b01d2c9086c3df2b63bd1eca602320cd7f45d24cSherif Moursiimport java.util.List; 68b01d2c9086c3df2b63bd1eca602320cd7f45d24cSherif Moursiimport java.util.Locale; 69b01d2c9086c3df2b63bd1eca602320cd7f45d24cSherif Moursiimport java.util.Map; 70b01d2c9086c3df2b63bd1eca602320cd7f45d24cSherif Moursiimport java.util.Set; 71b01d2c9086c3df2b63bd1eca602320cd7f45d24cSherif Moursiimport java.util.StringTokenizer; 72b01d2c9086c3df2b63bd1eca602320cd7f45d24cSherif Moursiimport java.util.TimeZone; 733526d931ae635536355e46a6b242405d309c70f3ritchieimport java.util.logging.Level; 743526d931ae635536355e46a6b242405d309c70f3ritchieimport java.util.logging.Logger; 75269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke 7630fb85f55cbd8df3005e652da3781f51294baf90ritchieimport javax.net.ssl.KeyManager; 7730fb85f55cbd8df3005e652da3781f51294baf90ritchieimport javax.net.ssl.KeyManagerFactory; 7830fb85f55cbd8df3005e652da3781f51294baf90ritchieimport javax.net.ssl.SSLContext; 7930fb85f55cbd8df3005e652da3781f51294baf90ritchieimport javax.net.ssl.SSLServerSocket; 8030fb85f55cbd8df3005e652da3781f51294baf90ritchieimport javax.net.ssl.SSLServerSocketFactory; 8130fb85f55cbd8df3005e652da3781f51294baf90ritchieimport javax.net.ssl.TrustManagerFactory; 82f7eb2ae15b4d921ae23e20cae59f36b21056b2fcAaron Davidson 83269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke/** 845b4919a7af60ff39d1d47dea7e668fbde333dd84Paul Hawke * A simple, tiny, nicely embeddable HTTP server in Java 85d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke * <p/> 86d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke * <p/> 87b814145cf66e94b7ce5511077ca0ebcff6e67268Paul Hawke * NanoHTTPD 889058464950a9734da0a7ff2dc47f3081bbb5117critchie * <p> 899058464950a9734da0a7ff2dc47f3081bbb5117critchie * Copyright (c) 2012-2013 by Paul S. Hawke, 2001,2005-2013 by Jarno Elonen, 909058464950a9734da0a7ff2dc47f3081bbb5117critchie * 2010 by Konstantinos Togias 919058464950a9734da0a7ff2dc47f3081bbb5117critchie * </p> 92d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke * <p/> 93d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke * <p/> 94269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * <b>Features + limitations: </b> 95269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * <ul> 96d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke * <p/> 97269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * <li>Only one Java file</li> 9801ecef8887ed32be34ef7442a656a0b3330ecb8dPaul Hawke * <li>Java 5 compatible</li> 99269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * <li>Released as open source, Modified BSD licence</li> 1009058464950a9734da0a7ff2dc47f3081bbb5117critchie * <li>No fixed config files, logging, authorization etc. (Implement yourself if 1019058464950a9734da0a7ff2dc47f3081bbb5117critchie * you need them.)</li> 1029058464950a9734da0a7ff2dc47f3081bbb5117critchie * <li>Supports parameter parsing of GET and POST methods (+ rudimentary PUT 1039058464950a9734da0a7ff2dc47f3081bbb5117critchie * support in 1.25)</li> 104269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * <li>Supports both dynamic content and file serving</li> 105269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * <li>Supports file upload (since version 1.2, 2010)</li> 106269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * <li>Supports partial content (streaming)</li> 107269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * <li>Supports ETags</li> 108269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * <li>Never caches anything</li> 109269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * <li>Doesn't limit bandwidth, request time or simultaneous connections</li> 110269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * <li>Default code serves files and shows all HTTP parameters and headers</li> 111269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * <li>File server supports directory listing, index.html and index.htm</li> 112269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * <li>File server supports partial content (streaming)</li> 113269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * <li>File server supports ETags</li> 114269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * <li>File server does the 301 redirection trick for directories without '/'</li> 115269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * <li>File server supports simple skipping for files (continue download)</li> 116269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * <li>File server serves also very long files without memory overhead</li> 117cf230611ca85378ae2b0e8130dace5766edfd295Philipp Wiesemann * <li>Contains a built-in list of most common MIME types</li> 1189058464950a9734da0a7ff2dc47f3081bbb5117critchie * <li>All header names are converted to lower case so they don't vary between 1199058464950a9734da0a7ff2dc47f3081bbb5117critchie * browsers/clients</li> 120d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke * <p/> 121269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * </ul> 122d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke * <p/> 123d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke * <p/> 12401ecef8887ed32be34ef7442a656a0b3330ecb8dPaul Hawke * <b>How to use: </b> 125269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * <ul> 126d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke * <p/> 12701ecef8887ed32be34ef7442a656a0b3330ecb8dPaul Hawke * <li>Subclass and implement serve() and embed to your own program</li> 128d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke * <p/> 129269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke * </ul> 130d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke * <p/> 1319058464950a9734da0a7ff2dc47f3081bbb5117critchie * See the separate "LICENSE.md" file for the distribution license (Modified BSD 1329058464950a9734da0a7ff2dc47f3081bbb5117critchie * licence) 133269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke */ 134d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawkepublic abstract class NanoHTTPD { 1359058464950a9734da0a7ff2dc47f3081bbb5117critchie 136269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke /** 13730fb85f55cbd8df3005e652da3781f51294baf90ritchie * Pluggable strategy for asynchronously executing requests. 138293e04675f3d4f427c125a26e831d70d5011c79bhflicka */ 13930fb85f55cbd8df3005e652da3781f51294baf90ritchie public interface AsyncRunner { 1409058464950a9734da0a7ff2dc47f3081bbb5117critchie 14130fb85f55cbd8df3005e652da3781f51294baf90ritchie void exec(Runnable code); 14230fb85f55cbd8df3005e652da3781f51294baf90ritchie } 1439058464950a9734da0a7ff2dc47f3081bbb5117critchie 14430fb85f55cbd8df3005e652da3781f51294baf90ritchie public static class Cookie { 1459058464950a9734da0a7ff2dc47f3081bbb5117critchie 14630fb85f55cbd8df3005e652da3781f51294baf90ritchie public static String getHTTPTime(int days) { 14730fb85f55cbd8df3005e652da3781f51294baf90ritchie Calendar calendar = Calendar.getInstance(); 14830fb85f55cbd8df3005e652da3781f51294baf90ritchie SimpleDateFormat dateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US); 14930fb85f55cbd8df3005e652da3781f51294baf90ritchie dateFormat.setTimeZone(TimeZone.getTimeZone("GMT")); 15030fb85f55cbd8df3005e652da3781f51294baf90ritchie calendar.add(Calendar.DAY_OF_MONTH, days); 15130fb85f55cbd8df3005e652da3781f51294baf90ritchie return dateFormat.format(calendar.getTime()); 15230fb85f55cbd8df3005e652da3781f51294baf90ritchie } 15330fb85f55cbd8df3005e652da3781f51294baf90ritchie 15430fb85f55cbd8df3005e652da3781f51294baf90ritchie private final String n, v, e; 15530fb85f55cbd8df3005e652da3781f51294baf90ritchie 15630fb85f55cbd8df3005e652da3781f51294baf90ritchie public Cookie(String name, String value) { 15730fb85f55cbd8df3005e652da3781f51294baf90ritchie this(name, value, 30); 15830fb85f55cbd8df3005e652da3781f51294baf90ritchie } 15930fb85f55cbd8df3005e652da3781f51294baf90ritchie 16030fb85f55cbd8df3005e652da3781f51294baf90ritchie public Cookie(String name, String value, int numDays) { 16130fb85f55cbd8df3005e652da3781f51294baf90ritchie this.n = name; 16230fb85f55cbd8df3005e652da3781f51294baf90ritchie this.v = value; 16330fb85f55cbd8df3005e652da3781f51294baf90ritchie this.e = getHTTPTime(numDays); 16430fb85f55cbd8df3005e652da3781f51294baf90ritchie } 16530fb85f55cbd8df3005e652da3781f51294baf90ritchie 16630fb85f55cbd8df3005e652da3781f51294baf90ritchie public Cookie(String name, String value, String expires) { 16730fb85f55cbd8df3005e652da3781f51294baf90ritchie this.n = name; 16830fb85f55cbd8df3005e652da3781f51294baf90ritchie this.v = value; 16930fb85f55cbd8df3005e652da3781f51294baf90ritchie this.e = expires; 17030fb85f55cbd8df3005e652da3781f51294baf90ritchie } 17130fb85f55cbd8df3005e652da3781f51294baf90ritchie 17230fb85f55cbd8df3005e652da3781f51294baf90ritchie public String getHTTPHeader() { 17330fb85f55cbd8df3005e652da3781f51294baf90ritchie String fmt = "%s=%s; expires=%s"; 17430fb85f55cbd8df3005e652da3781f51294baf90ritchie return String.format(fmt, this.n, this.v, this.e); 17530fb85f55cbd8df3005e652da3781f51294baf90ritchie } 17630fb85f55cbd8df3005e652da3781f51294baf90ritchie } 1779058464950a9734da0a7ff2dc47f3081bbb5117critchie 1789058464950a9734da0a7ff2dc47f3081bbb5117critchie /** 17930fb85f55cbd8df3005e652da3781f51294baf90ritchie * Provides rudimentary support for cookies. Doesn't support 'path', 18030fb85f55cbd8df3005e652da3781f51294baf90ritchie * 'secure' nor 'httpOnly'. Feel free to improve it and/or add unsupported 18130fb85f55cbd8df3005e652da3781f51294baf90ritchie * features. 18230fb85f55cbd8df3005e652da3781f51294baf90ritchie * 18330fb85f55cbd8df3005e652da3781f51294baf90ritchie * @author LordFokas 1849058464950a9734da0a7ff2dc47f3081bbb5117critchie */ 18530fb85f55cbd8df3005e652da3781f51294baf90ritchie public class CookieHandler implements Iterable<String> { 1869058464950a9734da0a7ff2dc47f3081bbb5117critchie 18730fb85f55cbd8df3005e652da3781f51294baf90ritchie private final HashMap<String, String> cookies = new HashMap<String, String>(); 1889058464950a9734da0a7ff2dc47f3081bbb5117critchie 18930fb85f55cbd8df3005e652da3781f51294baf90ritchie private final ArrayList<Cookie> queue = new ArrayList<Cookie>(); 1909058464950a9734da0a7ff2dc47f3081bbb5117critchie 19130fb85f55cbd8df3005e652da3781f51294baf90ritchie public CookieHandler(Map<String, String> httpHeaders) { 19230fb85f55cbd8df3005e652da3781f51294baf90ritchie String raw = httpHeaders.get("cookie"); 19330fb85f55cbd8df3005e652da3781f51294baf90ritchie if (raw != null) { 19430fb85f55cbd8df3005e652da3781f51294baf90ritchie String[] tokens = raw.split(";"); 19530fb85f55cbd8df3005e652da3781f51294baf90ritchie for (String token : tokens) { 19630fb85f55cbd8df3005e652da3781f51294baf90ritchie String[] data = token.trim().split("="); 19730fb85f55cbd8df3005e652da3781f51294baf90ritchie if (data.length == 2) { 19830fb85f55cbd8df3005e652da3781f51294baf90ritchie this.cookies.put(data[0], data[1]); 19930fb85f55cbd8df3005e652da3781f51294baf90ritchie } 20030fb85f55cbd8df3005e652da3781f51294baf90ritchie } 20130fb85f55cbd8df3005e652da3781f51294baf90ritchie } 20230fb85f55cbd8df3005e652da3781f51294baf90ritchie } 2039058464950a9734da0a7ff2dc47f3081bbb5117critchie 20430fb85f55cbd8df3005e652da3781f51294baf90ritchie /** 20530fb85f55cbd8df3005e652da3781f51294baf90ritchie * Set a cookie with an expiration date from a month ago, effectively 20630fb85f55cbd8df3005e652da3781f51294baf90ritchie * deleting it on the client side. 20730fb85f55cbd8df3005e652da3781f51294baf90ritchie * 20830fb85f55cbd8df3005e652da3781f51294baf90ritchie * @param name 20930fb85f55cbd8df3005e652da3781f51294baf90ritchie * The cookie name. 21030fb85f55cbd8df3005e652da3781f51294baf90ritchie */ 21130fb85f55cbd8df3005e652da3781f51294baf90ritchie public void delete(String name) { 21230fb85f55cbd8df3005e652da3781f51294baf90ritchie set(name, "-delete-", -30); 21330fb85f55cbd8df3005e652da3781f51294baf90ritchie } 2149058464950a9734da0a7ff2dc47f3081bbb5117critchie 21530fb85f55cbd8df3005e652da3781f51294baf90ritchie @Override 21630fb85f55cbd8df3005e652da3781f51294baf90ritchie public Iterator<String> iterator() { 21730fb85f55cbd8df3005e652da3781f51294baf90ritchie return this.cookies.keySet().iterator(); 21830fb85f55cbd8df3005e652da3781f51294baf90ritchie } 2199058464950a9734da0a7ff2dc47f3081bbb5117critchie 22030fb85f55cbd8df3005e652da3781f51294baf90ritchie /** 22130fb85f55cbd8df3005e652da3781f51294baf90ritchie * Read a cookie from the HTTP Headers. 22230fb85f55cbd8df3005e652da3781f51294baf90ritchie * 22330fb85f55cbd8df3005e652da3781f51294baf90ritchie * @param name 22430fb85f55cbd8df3005e652da3781f51294baf90ritchie * The cookie's name. 22530fb85f55cbd8df3005e652da3781f51294baf90ritchie * @return The cookie's value if it exists, null otherwise. 22630fb85f55cbd8df3005e652da3781f51294baf90ritchie */ 22730fb85f55cbd8df3005e652da3781f51294baf90ritchie public String read(String name) { 22830fb85f55cbd8df3005e652da3781f51294baf90ritchie return this.cookies.get(name); 22930fb85f55cbd8df3005e652da3781f51294baf90ritchie } 2309058464950a9734da0a7ff2dc47f3081bbb5117critchie 23130fb85f55cbd8df3005e652da3781f51294baf90ritchie public void set(Cookie cookie) { 23230fb85f55cbd8df3005e652da3781f51294baf90ritchie this.queue.add(cookie); 23330fb85f55cbd8df3005e652da3781f51294baf90ritchie } 2349058464950a9734da0a7ff2dc47f3081bbb5117critchie 23530fb85f55cbd8df3005e652da3781f51294baf90ritchie /** 23630fb85f55cbd8df3005e652da3781f51294baf90ritchie * Sets a cookie. 23730fb85f55cbd8df3005e652da3781f51294baf90ritchie * 23830fb85f55cbd8df3005e652da3781f51294baf90ritchie * @param name 23930fb85f55cbd8df3005e652da3781f51294baf90ritchie * The cookie's name. 24030fb85f55cbd8df3005e652da3781f51294baf90ritchie * @param value 24130fb85f55cbd8df3005e652da3781f51294baf90ritchie * The cookie's value. 24230fb85f55cbd8df3005e652da3781f51294baf90ritchie * @param expires 24330fb85f55cbd8df3005e652da3781f51294baf90ritchie * How many days until the cookie expires. 24430fb85f55cbd8df3005e652da3781f51294baf90ritchie */ 24530fb85f55cbd8df3005e652da3781f51294baf90ritchie public void set(String name, String value, int expires) { 24630fb85f55cbd8df3005e652da3781f51294baf90ritchie this.queue.add(new Cookie(name, value, Cookie.getHTTPTime(expires))); 24730fb85f55cbd8df3005e652da3781f51294baf90ritchie } 248269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke 24930fb85f55cbd8df3005e652da3781f51294baf90ritchie /** 25030fb85f55cbd8df3005e652da3781f51294baf90ritchie * Internally used by the webserver to add all queued cookies into the 25130fb85f55cbd8df3005e652da3781f51294baf90ritchie * Response's HTTP Headers. 25230fb85f55cbd8df3005e652da3781f51294baf90ritchie * 25330fb85f55cbd8df3005e652da3781f51294baf90ritchie * @param response 25430fb85f55cbd8df3005e652da3781f51294baf90ritchie * The Response object to which headers the queued cookies 25530fb85f55cbd8df3005e652da3781f51294baf90ritchie * will be added. 25630fb85f55cbd8df3005e652da3781f51294baf90ritchie */ 25730fb85f55cbd8df3005e652da3781f51294baf90ritchie public void unloadQueue(Response response) { 25830fb85f55cbd8df3005e652da3781f51294baf90ritchie for (Cookie cookie : this.queue) { 25930fb85f55cbd8df3005e652da3781f51294baf90ritchie response.addHeader("Set-Cookie", cookie.getHTTPHeader()); 26030fb85f55cbd8df3005e652da3781f51294baf90ritchie } 26130fb85f55cbd8df3005e652da3781f51294baf90ritchie } 262f639f201819cb2652cfcad67ed8633e0609cb440Paul Hawke } 263f639f201819cb2652cfcad67ed8633e0609cb440Paul Hawke 2645b4919a7af60ff39d1d47dea7e668fbde333dd84Paul Hawke /** 26530fb85f55cbd8df3005e652da3781f51294baf90ritchie * Default threading strategy for NanoHTTPD. 26630fb85f55cbd8df3005e652da3781f51294baf90ritchie * <p/> 26730fb85f55cbd8df3005e652da3781f51294baf90ritchie * <p> 26830fb85f55cbd8df3005e652da3781f51294baf90ritchie * By default, the server spawns a new Thread for every incoming request. 26930fb85f55cbd8df3005e652da3781f51294baf90ritchie * These are set to <i>daemon</i> status, and named according to the request 27030fb85f55cbd8df3005e652da3781f51294baf90ritchie * number. The name is useful when profiling the application. 27130fb85f55cbd8df3005e652da3781f51294baf90ritchie * </p> 2725b4919a7af60ff39d1d47dea7e668fbde333dd84Paul Hawke */ 27330fb85f55cbd8df3005e652da3781f51294baf90ritchie public static class DefaultAsyncRunner implements AsyncRunner { 2743f2aa2e2e0e02fe7e0151eb64d31265b578885ecPaul Hawke 27530fb85f55cbd8df3005e652da3781f51294baf90ritchie private long requestCount; 27630fb85f55cbd8df3005e652da3781f51294baf90ritchie 27730fb85f55cbd8df3005e652da3781f51294baf90ritchie @Override 27830fb85f55cbd8df3005e652da3781f51294baf90ritchie public void exec(Runnable code) { 27930fb85f55cbd8df3005e652da3781f51294baf90ritchie ++this.requestCount; 28030fb85f55cbd8df3005e652da3781f51294baf90ritchie Thread t = new Thread(code); 28130fb85f55cbd8df3005e652da3781f51294baf90ritchie t.setDaemon(true); 28230fb85f55cbd8df3005e652da3781f51294baf90ritchie t.setName("NanoHttpd Request Processor (#" + this.requestCount + ")"); 28330fb85f55cbd8df3005e652da3781f51294baf90ritchie t.start(); 284c7fd739559d0f33a7fdbc831183acc5e2b0b15bbPaul Hawke } 285c7fd739559d0f33a7fdbc831183acc5e2b0b15bbPaul Hawke } 286c7fd739559d0f33a7fdbc831183acc5e2b0b15bbPaul Hawke 2879058464950a9734da0a7ff2dc47f3081bbb5117critchie /** 28830fb85f55cbd8df3005e652da3781f51294baf90ritchie * Default strategy for creating and cleaning up temporary files. 28930fb85f55cbd8df3005e652da3781f51294baf90ritchie * <p/> 29030fb85f55cbd8df3005e652da3781f51294baf90ritchie * <p> 29130fb85f55cbd8df3005e652da3781f51294baf90ritchie * By default, files are created by <code>File.createTempFile()</code> in 29230fb85f55cbd8df3005e652da3781f51294baf90ritchie * the directory specified. 29330fb85f55cbd8df3005e652da3781f51294baf90ritchie * </p> 2949058464950a9734da0a7ff2dc47f3081bbb5117critchie */ 29530fb85f55cbd8df3005e652da3781f51294baf90ritchie public static class DefaultTempFile implements TempFile { 29630fb85f55cbd8df3005e652da3781f51294baf90ritchie 29730fb85f55cbd8df3005e652da3781f51294baf90ritchie private final File file; 29830fb85f55cbd8df3005e652da3781f51294baf90ritchie 29930fb85f55cbd8df3005e652da3781f51294baf90ritchie private final OutputStream fstream; 30030fb85f55cbd8df3005e652da3781f51294baf90ritchie 30130fb85f55cbd8df3005e652da3781f51294baf90ritchie public DefaultTempFile(String tempdir) throws IOException { 30230fb85f55cbd8df3005e652da3781f51294baf90ritchie this.file = File.createTempFile("NanoHTTPD-", "", new File(tempdir)); 30330fb85f55cbd8df3005e652da3781f51294baf90ritchie this.fstream = new FileOutputStream(this.file); 3049058464950a9734da0a7ff2dc47f3081bbb5117critchie } 3059058464950a9734da0a7ff2dc47f3081bbb5117critchie 30630fb85f55cbd8df3005e652da3781f51294baf90ritchie @Override 30730fb85f55cbd8df3005e652da3781f51294baf90ritchie public void delete() throws Exception { 30830fb85f55cbd8df3005e652da3781f51294baf90ritchie safeClose(this.fstream); 30930fb85f55cbd8df3005e652da3781f51294baf90ritchie this.file.delete(); 3109058464950a9734da0a7ff2dc47f3081bbb5117critchie } 3119058464950a9734da0a7ff2dc47f3081bbb5117critchie 31230fb85f55cbd8df3005e652da3781f51294baf90ritchie @Override 31330fb85f55cbd8df3005e652da3781f51294baf90ritchie public String getName() { 31430fb85f55cbd8df3005e652da3781f51294baf90ritchie return this.file.getAbsolutePath(); 3159058464950a9734da0a7ff2dc47f3081bbb5117critchie } 3169058464950a9734da0a7ff2dc47f3081bbb5117critchie 31730fb85f55cbd8df3005e652da3781f51294baf90ritchie @Override 31830fb85f55cbd8df3005e652da3781f51294baf90ritchie public OutputStream open() throws Exception { 31930fb85f55cbd8df3005e652da3781f51294baf90ritchie return this.fstream; 32030fb85f55cbd8df3005e652da3781f51294baf90ritchie } 3219058464950a9734da0a7ff2dc47f3081bbb5117critchie } 322f7eb2ae15b4d921ae23e20cae59f36b21056b2fcAaron Davidson 3233f2aa2e2e0e02fe7e0151eb64d31265b578885ecPaul Hawke /** 32430fb85f55cbd8df3005e652da3781f51294baf90ritchie * Default strategy for creating and cleaning up temporary files. 32530fb85f55cbd8df3005e652da3781f51294baf90ritchie * <p/> 32630fb85f55cbd8df3005e652da3781f51294baf90ritchie * <p> 32730fb85f55cbd8df3005e652da3781f51294baf90ritchie * This class stores its files in the standard location (that is, wherever 32830fb85f55cbd8df3005e652da3781f51294baf90ritchie * <code>java.io.tmpdir</code> points to). Files are added to an internal 32930fb85f55cbd8df3005e652da3781f51294baf90ritchie * list, and deleted when no longer needed (that is, when 33030fb85f55cbd8df3005e652da3781f51294baf90ritchie * <code>clear()</code> is invoked at the end of processing a request). 33130fb85f55cbd8df3005e652da3781f51294baf90ritchie * </p> 3323f2aa2e2e0e02fe7e0151eb64d31265b578885ecPaul Hawke */ 33330fb85f55cbd8df3005e652da3781f51294baf90ritchie public static class DefaultTempFileManager implements TempFileManager { 334f39ce1dd765bb79b0a83a8e6dd921f0d3986ebf6Aaron Davidson 33530fb85f55cbd8df3005e652da3781f51294baf90ritchie private final String tmpdir; 3369058464950a9734da0a7ff2dc47f3081bbb5117critchie 33730fb85f55cbd8df3005e652da3781f51294baf90ritchie private final List<TempFile> tempFiles; 3389058464950a9734da0a7ff2dc47f3081bbb5117critchie 33930fb85f55cbd8df3005e652da3781f51294baf90ritchie public DefaultTempFileManager() { 34030fb85f55cbd8df3005e652da3781f51294baf90ritchie this.tmpdir = System.getProperty("java.io.tmpdir"); 34130fb85f55cbd8df3005e652da3781f51294baf90ritchie this.tempFiles = new ArrayList<TempFile>(); 34230fb85f55cbd8df3005e652da3781f51294baf90ritchie } 343d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke 34430fb85f55cbd8df3005e652da3781f51294baf90ritchie @Override 34530fb85f55cbd8df3005e652da3781f51294baf90ritchie public void clear() { 34630fb85f55cbd8df3005e652da3781f51294baf90ritchie for (TempFile file : this.tempFiles) { 34730fb85f55cbd8df3005e652da3781f51294baf90ritchie try { 34830fb85f55cbd8df3005e652da3781f51294baf90ritchie file.delete(); 34930fb85f55cbd8df3005e652da3781f51294baf90ritchie } catch (Exception ignored) { 35030fb85f55cbd8df3005e652da3781f51294baf90ritchie NanoHTTPD.LOG.log(Level.WARNING, "could not delete file ", ignored); 35130fb85f55cbd8df3005e652da3781f51294baf90ritchie } 352fe8be79fa8d8b131d5d39e77ae0baabd177d5ec1Paul Hawke } 35330fb85f55cbd8df3005e652da3781f51294baf90ritchie this.tempFiles.clear(); 354269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke } 355269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke 35630fb85f55cbd8df3005e652da3781f51294baf90ritchie @Override 35730fb85f55cbd8df3005e652da3781f51294baf90ritchie public TempFile createTempFile() throws Exception { 35830fb85f55cbd8df3005e652da3781f51294baf90ritchie DefaultTempFile tempFile = new DefaultTempFile(this.tmpdir); 35930fb85f55cbd8df3005e652da3781f51294baf90ritchie this.tempFiles.add(tempFile); 36030fb85f55cbd8df3005e652da3781f51294baf90ritchie return tempFile; 36130fb85f55cbd8df3005e652da3781f51294baf90ritchie } 3620a35219489d8f19d13197bc60eec168dd6b3089dAlbin Theander } 3630a35219489d8f19d13197bc60eec168dd6b3089dAlbin Theander 3640a35219489d8f19d13197bc60eec168dd6b3089dAlbin Theander /** 36530fb85f55cbd8df3005e652da3781f51294baf90ritchie * Default strategy for creating and cleaning up temporary files. 3660a35219489d8f19d13197bc60eec168dd6b3089dAlbin Theander */ 36730fb85f55cbd8df3005e652da3781f51294baf90ritchie private class DefaultTempFileManagerFactory implements TempFileManagerFactory { 3680a35219489d8f19d13197bc60eec168dd6b3089dAlbin Theander 36930fb85f55cbd8df3005e652da3781f51294baf90ritchie @Override 37030fb85f55cbd8df3005e652da3781f51294baf90ritchie public TempFileManager create() { 37130fb85f55cbd8df3005e652da3781f51294baf90ritchie return new DefaultTempFileManager(); 3720a35219489d8f19d13197bc60eec168dd6b3089dAlbin Theander } 3730a35219489d8f19d13197bc60eec168dd6b3089dAlbin Theander } 3748dd183554e56783ecf7cd6b8529cf807b9eb813cPaul Hawke 37530fb85f55cbd8df3005e652da3781f51294baf90ritchie protected class HTTPSession implements IHTTPSession { 3768dd183554e56783ecf7cd6b8529cf807b9eb813cPaul Hawke 37730fb85f55cbd8df3005e652da3781f51294baf90ritchie public static final int BUFSIZE = 8192; 3788dd183554e56783ecf7cd6b8529cf807b9eb813cPaul Hawke 37930fb85f55cbd8df3005e652da3781f51294baf90ritchie private final TempFileManager tempFileManager; 3808dd183554e56783ecf7cd6b8529cf807b9eb813cPaul Hawke 38130fb85f55cbd8df3005e652da3781f51294baf90ritchie private final OutputStream outputStream; 3829788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke 38330fb85f55cbd8df3005e652da3781f51294baf90ritchie private final PushbackInputStream inputStream; 38493441a0455c683a17622ec2da6768c326e6d7a48Martin M Reed 38530fb85f55cbd8df3005e652da3781f51294baf90ritchie private int splitbyte; 38693441a0455c683a17622ec2da6768c326e6d7a48Martin M Reed 38730fb85f55cbd8df3005e652da3781f51294baf90ritchie private int rlen; 388f639f201819cb2652cfcad67ed8633e0609cb440Paul Hawke 38930fb85f55cbd8df3005e652da3781f51294baf90ritchie private String uri; 390f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke 39130fb85f55cbd8df3005e652da3781f51294baf90ritchie private Method method; 392f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke 39330fb85f55cbd8df3005e652da3781f51294baf90ritchie private Map<String, String> parms; 3945b4919a7af60ff39d1d47dea7e668fbde333dd84Paul Hawke 39530fb85f55cbd8df3005e652da3781f51294baf90ritchie private Map<String, String> headers; 3965b4919a7af60ff39d1d47dea7e668fbde333dd84Paul Hawke 39730fb85f55cbd8df3005e652da3781f51294baf90ritchie private CookieHandler cookies; 3985b4919a7af60ff39d1d47dea7e668fbde333dd84Paul Hawke 39930fb85f55cbd8df3005e652da3781f51294baf90ritchie private String queryParameterString; 4005b4919a7af60ff39d1d47dea7e668fbde333dd84Paul Hawke 40130fb85f55cbd8df3005e652da3781f51294baf90ritchie private String remoteIp; 4028dd183554e56783ecf7cd6b8529cf807b9eb813cPaul Hawke 40330fb85f55cbd8df3005e652da3781f51294baf90ritchie public HTTPSession(TempFileManager tempFileManager, InputStream inputStream, OutputStream outputStream) { 40430fb85f55cbd8df3005e652da3781f51294baf90ritchie this.tempFileManager = tempFileManager; 40530fb85f55cbd8df3005e652da3781f51294baf90ritchie this.inputStream = new PushbackInputStream(inputStream, HTTPSession.BUFSIZE); 40630fb85f55cbd8df3005e652da3781f51294baf90ritchie this.outputStream = outputStream; 4078dd183554e56783ecf7cd6b8529cf807b9eb813cPaul Hawke } 4088dd183554e56783ecf7cd6b8529cf807b9eb813cPaul Hawke 40930fb85f55cbd8df3005e652da3781f51294baf90ritchie public HTTPSession(TempFileManager tempFileManager, InputStream inputStream, OutputStream outputStream, InetAddress inetAddress) { 41030fb85f55cbd8df3005e652da3781f51294baf90ritchie this.tempFileManager = tempFileManager; 41130fb85f55cbd8df3005e652da3781f51294baf90ritchie this.inputStream = new PushbackInputStream(inputStream, HTTPSession.BUFSIZE); 41230fb85f55cbd8df3005e652da3781f51294baf90ritchie this.outputStream = outputStream; 41330fb85f55cbd8df3005e652da3781f51294baf90ritchie this.remoteIp = inetAddress.isLoopbackAddress() || inetAddress.isAnyLocalAddress() ? "127.0.0.1" : inetAddress.getHostAddress().toString(); 41430fb85f55cbd8df3005e652da3781f51294baf90ritchie this.headers = new HashMap<String, String>(); 4159788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke } 4169788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke 4179788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke /** 41830fb85f55cbd8df3005e652da3781f51294baf90ritchie * Decodes the sent headers and loads the data into Key/value pairs 4199788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke */ 42030fb85f55cbd8df3005e652da3781f51294baf90ritchie private void decodeHeader(BufferedReader in, Map<String, String> pre, Map<String, String> parms, Map<String, String> headers) throws ResponseException { 4219788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke try { 42230fb85f55cbd8df3005e652da3781f51294baf90ritchie // Read the request line 42330fb85f55cbd8df3005e652da3781f51294baf90ritchie String inLine = in.readLine(); 42430fb85f55cbd8df3005e652da3781f51294baf90ritchie if (inLine == null) { 42530fb85f55cbd8df3005e652da3781f51294baf90ritchie return; 42630fb85f55cbd8df3005e652da3781f51294baf90ritchie } 427fe8be79fa8d8b131d5d39e77ae0baabd177d5ec1Paul Hawke 42830fb85f55cbd8df3005e652da3781f51294baf90ritchie StringTokenizer st = new StringTokenizer(inLine); 42930fb85f55cbd8df3005e652da3781f51294baf90ritchie if (!st.hasMoreTokens()) { 43030fb85f55cbd8df3005e652da3781f51294baf90ritchie throw new ResponseException(Response.Status.BAD_REQUEST, "BAD REQUEST: Syntax error. Usage: GET /example/file.html"); 43130fb85f55cbd8df3005e652da3781f51294baf90ritchie } 4329788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke 43330fb85f55cbd8df3005e652da3781f51294baf90ritchie pre.put("method", st.nextToken()); 4347f0727787957c2f093412c01d165846842ec2425Paul Hawke 43530fb85f55cbd8df3005e652da3781f51294baf90ritchie if (!st.hasMoreTokens()) { 43630fb85f55cbd8df3005e652da3781f51294baf90ritchie throw new ResponseException(Response.Status.BAD_REQUEST, "BAD REQUEST: Missing URI. Usage: GET /example/file.html"); 4377f0727787957c2f093412c01d165846842ec2425Paul Hawke } 4387f0727787957c2f093412c01d165846842ec2425Paul Hawke 43930fb85f55cbd8df3005e652da3781f51294baf90ritchie String uri = st.nextToken(); 4407f0727787957c2f093412c01d165846842ec2425Paul Hawke 44130fb85f55cbd8df3005e652da3781f51294baf90ritchie // Decode parameters from the URI 44230fb85f55cbd8df3005e652da3781f51294baf90ritchie int qmi = uri.indexOf('?'); 44330fb85f55cbd8df3005e652da3781f51294baf90ritchie if (qmi >= 0) { 44430fb85f55cbd8df3005e652da3781f51294baf90ritchie decodeParms(uri.substring(qmi + 1), parms); 44530fb85f55cbd8df3005e652da3781f51294baf90ritchie uri = decodePercent(uri.substring(0, qmi)); 44630fb85f55cbd8df3005e652da3781f51294baf90ritchie } else { 44730fb85f55cbd8df3005e652da3781f51294baf90ritchie uri = decodePercent(uri); 4487f0727787957c2f093412c01d165846842ec2425Paul Hawke } 4497f0727787957c2f093412c01d165846842ec2425Paul Hawke 45030fb85f55cbd8df3005e652da3781f51294baf90ritchie // If there's another token, its protocol version, 45130fb85f55cbd8df3005e652da3781f51294baf90ritchie // followed by HTTP headers. Ignore version but parse headers. 45230fb85f55cbd8df3005e652da3781f51294baf90ritchie // NOTE: this now forces header names lower case since they are 45330fb85f55cbd8df3005e652da3781f51294baf90ritchie // case insensitive and vary by client. 45430fb85f55cbd8df3005e652da3781f51294baf90ritchie if (!st.hasMoreTokens()) { 45530fb85f55cbd8df3005e652da3781f51294baf90ritchie NanoHTTPD.LOG.log(Level.FINE, "no protocol version specified, strange.."); 45630fb85f55cbd8df3005e652da3781f51294baf90ritchie } 45730fb85f55cbd8df3005e652da3781f51294baf90ritchie String line = in.readLine(); 45830fb85f55cbd8df3005e652da3781f51294baf90ritchie while (line != null && line.trim().length() > 0) { 45930fb85f55cbd8df3005e652da3781f51294baf90ritchie int p = line.indexOf(':'); 46030fb85f55cbd8df3005e652da3781f51294baf90ritchie if (p >= 0) { 46130fb85f55cbd8df3005e652da3781f51294baf90ritchie headers.put(line.substring(0, p).trim().toLowerCase(Locale.US), line.substring(p + 1).trim()); 4627f0727787957c2f093412c01d165846842ec2425Paul Hawke } 46330fb85f55cbd8df3005e652da3781f51294baf90ritchie line = in.readLine(); 4647f0727787957c2f093412c01d165846842ec2425Paul Hawke } 4657f0727787957c2f093412c01d165846842ec2425Paul Hawke 46630fb85f55cbd8df3005e652da3781f51294baf90ritchie pre.put("uri", uri); 4677f0727787957c2f093412c01d165846842ec2425Paul Hawke } catch (IOException ioe) { 46830fb85f55cbd8df3005e652da3781f51294baf90ritchie throw new ResponseException(Response.Status.INTERNAL_ERROR, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage(), ioe); 4697f0727787957c2f093412c01d165846842ec2425Paul Hawke } 4707f0727787957c2f093412c01d165846842ec2425Paul Hawke } 4717f0727787957c2f093412c01d165846842ec2425Paul Hawke 47230fb85f55cbd8df3005e652da3781f51294baf90ritchie /** 47330fb85f55cbd8df3005e652da3781f51294baf90ritchie * Decodes the Multipart Body data and put it into Key/Value pairs. 47430fb85f55cbd8df3005e652da3781f51294baf90ritchie */ 47530fb85f55cbd8df3005e652da3781f51294baf90ritchie private void decodeMultipartData(String boundary, ByteBuffer fbuf, BufferedReader in, Map<String, String> parms, Map<String, String> files) throws ResponseException { 47630fb85f55cbd8df3005e652da3781f51294baf90ritchie try { 47730fb85f55cbd8df3005e652da3781f51294baf90ritchie int[] bpositions = getBoundaryPositions(fbuf, boundary.getBytes()); 47830fb85f55cbd8df3005e652da3781f51294baf90ritchie int boundarycount = 1; 47930fb85f55cbd8df3005e652da3781f51294baf90ritchie String mpline = in.readLine(); 48030fb85f55cbd8df3005e652da3781f51294baf90ritchie while (mpline != null) { 48130fb85f55cbd8df3005e652da3781f51294baf90ritchie if (!mpline.contains(boundary)) { 48230fb85f55cbd8df3005e652da3781f51294baf90ritchie throw new ResponseException(Response.Status.BAD_REQUEST, 48330fb85f55cbd8df3005e652da3781f51294baf90ritchie "BAD REQUEST: Content type is multipart/form-data but next chunk does not start with boundary. Usage: GET /example/file.html"); 48430fb85f55cbd8df3005e652da3781f51294baf90ritchie } 48530fb85f55cbd8df3005e652da3781f51294baf90ritchie boundarycount++; 48630fb85f55cbd8df3005e652da3781f51294baf90ritchie Map<String, String> item = new HashMap<String, String>(); 48730fb85f55cbd8df3005e652da3781f51294baf90ritchie mpline = in.readLine(); 48830fb85f55cbd8df3005e652da3781f51294baf90ritchie while (mpline != null && mpline.trim().length() > 0) { 48930fb85f55cbd8df3005e652da3781f51294baf90ritchie int p = mpline.indexOf(':'); 49030fb85f55cbd8df3005e652da3781f51294baf90ritchie if (p != -1) { 49130fb85f55cbd8df3005e652da3781f51294baf90ritchie item.put(mpline.substring(0, p).trim().toLowerCase(Locale.US), mpline.substring(p + 1).trim()); 49230fb85f55cbd8df3005e652da3781f51294baf90ritchie } 49330fb85f55cbd8df3005e652da3781f51294baf90ritchie mpline = in.readLine(); 49430fb85f55cbd8df3005e652da3781f51294baf90ritchie } 49530fb85f55cbd8df3005e652da3781f51294baf90ritchie if (mpline != null) { 49630fb85f55cbd8df3005e652da3781f51294baf90ritchie String contentDisposition = item.get("content-disposition"); 49730fb85f55cbd8df3005e652da3781f51294baf90ritchie if (contentDisposition == null) { 49830fb85f55cbd8df3005e652da3781f51294baf90ritchie throw new ResponseException(Response.Status.BAD_REQUEST, 49930fb85f55cbd8df3005e652da3781f51294baf90ritchie "BAD REQUEST: Content type is multipart/form-data but no content-disposition info found. Usage: GET /example/file.html"); 50030fb85f55cbd8df3005e652da3781f51294baf90ritchie } 50130fb85f55cbd8df3005e652da3781f51294baf90ritchie StringTokenizer st = new StringTokenizer(contentDisposition, ";"); 50230fb85f55cbd8df3005e652da3781f51294baf90ritchie Map<String, String> disposition = new HashMap<String, String>(); 50330fb85f55cbd8df3005e652da3781f51294baf90ritchie while (st.hasMoreTokens()) { 50430fb85f55cbd8df3005e652da3781f51294baf90ritchie String token = st.nextToken().trim(); 50530fb85f55cbd8df3005e652da3781f51294baf90ritchie int p = token.indexOf('='); 50630fb85f55cbd8df3005e652da3781f51294baf90ritchie if (p != -1) { 50730fb85f55cbd8df3005e652da3781f51294baf90ritchie disposition.put(token.substring(0, p).trim().toLowerCase(Locale.US), token.substring(p + 1).trim()); 50830fb85f55cbd8df3005e652da3781f51294baf90ritchie } 50930fb85f55cbd8df3005e652da3781f51294baf90ritchie } 51030fb85f55cbd8df3005e652da3781f51294baf90ritchie String pname = disposition.get("name"); 51130fb85f55cbd8df3005e652da3781f51294baf90ritchie pname = pname.substring(1, pname.length() - 1); 51230fb85f55cbd8df3005e652da3781f51294baf90ritchie 51330fb85f55cbd8df3005e652da3781f51294baf90ritchie String value = ""; 51430fb85f55cbd8df3005e652da3781f51294baf90ritchie if (item.get("content-type") == null) { 51530fb85f55cbd8df3005e652da3781f51294baf90ritchie while (mpline != null && !mpline.contains(boundary)) { 51630fb85f55cbd8df3005e652da3781f51294baf90ritchie mpline = in.readLine(); 51730fb85f55cbd8df3005e652da3781f51294baf90ritchie if (mpline != null) { 51830fb85f55cbd8df3005e652da3781f51294baf90ritchie int d = mpline.indexOf(boundary); 51930fb85f55cbd8df3005e652da3781f51294baf90ritchie if (d == -1) { 52030fb85f55cbd8df3005e652da3781f51294baf90ritchie value += mpline; 52130fb85f55cbd8df3005e652da3781f51294baf90ritchie } else { 52230fb85f55cbd8df3005e652da3781f51294baf90ritchie value += mpline.substring(0, d - 2); 52330fb85f55cbd8df3005e652da3781f51294baf90ritchie } 52430fb85f55cbd8df3005e652da3781f51294baf90ritchie } 52530fb85f55cbd8df3005e652da3781f51294baf90ritchie } 52630fb85f55cbd8df3005e652da3781f51294baf90ritchie } else { 52730fb85f55cbd8df3005e652da3781f51294baf90ritchie if (boundarycount > bpositions.length) { 52830fb85f55cbd8df3005e652da3781f51294baf90ritchie throw new ResponseException(Response.Status.INTERNAL_ERROR, "Error processing request"); 52930fb85f55cbd8df3005e652da3781f51294baf90ritchie } 53030fb85f55cbd8df3005e652da3781f51294baf90ritchie int offset = stripMultipartHeaders(fbuf, bpositions[boundarycount - 2]); 53130fb85f55cbd8df3005e652da3781f51294baf90ritchie String path = saveTmpFile(fbuf, offset, bpositions[boundarycount - 1] - offset - 4); 53230fb85f55cbd8df3005e652da3781f51294baf90ritchie if (!files.containsKey(pname)) { 53330fb85f55cbd8df3005e652da3781f51294baf90ritchie files.put(pname, path); 53430fb85f55cbd8df3005e652da3781f51294baf90ritchie } else { 53530fb85f55cbd8df3005e652da3781f51294baf90ritchie int count = 2; 53630fb85f55cbd8df3005e652da3781f51294baf90ritchie while (files.containsKey(pname + count)) { 53730fb85f55cbd8df3005e652da3781f51294baf90ritchie count++; 53830fb85f55cbd8df3005e652da3781f51294baf90ritchie } 53930fb85f55cbd8df3005e652da3781f51294baf90ritchie files.put(pname + count, path); 54030fb85f55cbd8df3005e652da3781f51294baf90ritchie } 54130fb85f55cbd8df3005e652da3781f51294baf90ritchie value = disposition.get("filename"); 54230fb85f55cbd8df3005e652da3781f51294baf90ritchie value = value.substring(1, value.length() - 1); 54330fb85f55cbd8df3005e652da3781f51294baf90ritchie do { 54430fb85f55cbd8df3005e652da3781f51294baf90ritchie mpline = in.readLine(); 54530fb85f55cbd8df3005e652da3781f51294baf90ritchie } while (mpline != null && !mpline.contains(boundary)); 54630fb85f55cbd8df3005e652da3781f51294baf90ritchie } 54730fb85f55cbd8df3005e652da3781f51294baf90ritchie parms.put(pname, value); 54898d6d146214026ab242ce1de320bd7d203d7a5f3Dan Pomeroy } 54998d6d146214026ab242ce1de320bd7d203d7a5f3Dan Pomeroy } 55030fb85f55cbd8df3005e652da3781f51294baf90ritchie } catch (IOException ioe) { 55130fb85f55cbd8df3005e652da3781f51294baf90ritchie throw new ResponseException(Response.Status.INTERNAL_ERROR, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage(), ioe); 552c6c080a6f4377fb18fc869111714e9514c36331aPaul Hawke } 553fe8be79fa8d8b131d5d39e77ae0baabd177d5ec1Paul Hawke } 554fe8be79fa8d8b131d5d39e77ae0baabd177d5ec1Paul Hawke 55530fb85f55cbd8df3005e652da3781f51294baf90ritchie /** 55630fb85f55cbd8df3005e652da3781f51294baf90ritchie * Decodes parameters in percent-encoded URI-format ( e.g. 55730fb85f55cbd8df3005e652da3781f51294baf90ritchie * "name=Jack%20Daniels&pass=Single%20Malt" ) and adds them to given 55830fb85f55cbd8df3005e652da3781f51294baf90ritchie * Map. NOTE: this doesn't support multiple identical keys due to the 55930fb85f55cbd8df3005e652da3781f51294baf90ritchie * simplicity of Map. 56030fb85f55cbd8df3005e652da3781f51294baf90ritchie */ 56130fb85f55cbd8df3005e652da3781f51294baf90ritchie private void decodeParms(String parms, Map<String, String> p) { 56230fb85f55cbd8df3005e652da3781f51294baf90ritchie if (parms == null) { 56330fb85f55cbd8df3005e652da3781f51294baf90ritchie this.queryParameterString = ""; 56430fb85f55cbd8df3005e652da3781f51294baf90ritchie return; 565c6c080a6f4377fb18fc869111714e9514c36331aPaul Hawke } 566c6c080a6f4377fb18fc869111714e9514c36331aPaul Hawke 56730fb85f55cbd8df3005e652da3781f51294baf90ritchie this.queryParameterString = parms; 56830fb85f55cbd8df3005e652da3781f51294baf90ritchie StringTokenizer st = new StringTokenizer(parms, "&"); 56930fb85f55cbd8df3005e652da3781f51294baf90ritchie while (st.hasMoreTokens()) { 57030fb85f55cbd8df3005e652da3781f51294baf90ritchie String e = st.nextToken(); 57130fb85f55cbd8df3005e652da3781f51294baf90ritchie int sep = e.indexOf('='); 57230fb85f55cbd8df3005e652da3781f51294baf90ritchie if (sep >= 0) { 57330fb85f55cbd8df3005e652da3781f51294baf90ritchie p.put(decodePercent(e.substring(0, sep)).trim(), decodePercent(e.substring(sep + 1))); 57430fb85f55cbd8df3005e652da3781f51294baf90ritchie } else { 57530fb85f55cbd8df3005e652da3781f51294baf90ritchie p.put(decodePercent(e).trim(), ""); 57630fb85f55cbd8df3005e652da3781f51294baf90ritchie } 577fe8be79fa8d8b131d5d39e77ae0baabd177d5ec1Paul Hawke } 578fe8be79fa8d8b131d5d39e77ae0baabd177d5ec1Paul Hawke } 579fe8be79fa8d8b131d5d39e77ae0baabd177d5ec1Paul Hawke 58030fb85f55cbd8df3005e652da3781f51294baf90ritchie @Override 58130fb85f55cbd8df3005e652da3781f51294baf90ritchie public void execute() throws IOException { 58230fb85f55cbd8df3005e652da3781f51294baf90ritchie try { 58330fb85f55cbd8df3005e652da3781f51294baf90ritchie // Read the first 8192 bytes. 58430fb85f55cbd8df3005e652da3781f51294baf90ritchie // The full header should fit in here. 58530fb85f55cbd8df3005e652da3781f51294baf90ritchie // Apache's default header limit is 8KB. 58630fb85f55cbd8df3005e652da3781f51294baf90ritchie // Do NOT assume that a single read will get the entire header 58730fb85f55cbd8df3005e652da3781f51294baf90ritchie // at once! 58830fb85f55cbd8df3005e652da3781f51294baf90ritchie byte[] buf = new byte[HTTPSession.BUFSIZE]; 58930fb85f55cbd8df3005e652da3781f51294baf90ritchie this.splitbyte = 0; 59030fb85f55cbd8df3005e652da3781f51294baf90ritchie this.rlen = 0; 59130fb85f55cbd8df3005e652da3781f51294baf90ritchie { 59230fb85f55cbd8df3005e652da3781f51294baf90ritchie int read = -1; 59330fb85f55cbd8df3005e652da3781f51294baf90ritchie try { 59430fb85f55cbd8df3005e652da3781f51294baf90ritchie read = this.inputStream.read(buf, 0, HTTPSession.BUFSIZE); 59530fb85f55cbd8df3005e652da3781f51294baf90ritchie } catch (Exception e) { 59630fb85f55cbd8df3005e652da3781f51294baf90ritchie safeClose(this.inputStream); 59730fb85f55cbd8df3005e652da3781f51294baf90ritchie safeClose(this.outputStream); 59830fb85f55cbd8df3005e652da3781f51294baf90ritchie throw new SocketException("NanoHttpd Shutdown"); 59930fb85f55cbd8df3005e652da3781f51294baf90ritchie } 60030fb85f55cbd8df3005e652da3781f51294baf90ritchie if (read == -1) { 60130fb85f55cbd8df3005e652da3781f51294baf90ritchie // socket was been closed 60230fb85f55cbd8df3005e652da3781f51294baf90ritchie safeClose(this.inputStream); 60330fb85f55cbd8df3005e652da3781f51294baf90ritchie safeClose(this.outputStream); 60430fb85f55cbd8df3005e652da3781f51294baf90ritchie throw new SocketException("NanoHttpd Shutdown"); 60530fb85f55cbd8df3005e652da3781f51294baf90ritchie } 60630fb85f55cbd8df3005e652da3781f51294baf90ritchie while (read > 0) { 60730fb85f55cbd8df3005e652da3781f51294baf90ritchie this.rlen += read; 60830fb85f55cbd8df3005e652da3781f51294baf90ritchie this.splitbyte = findHeaderEnd(buf, this.rlen); 60930fb85f55cbd8df3005e652da3781f51294baf90ritchie if (this.splitbyte > 0) { 61030fb85f55cbd8df3005e652da3781f51294baf90ritchie break; 61130fb85f55cbd8df3005e652da3781f51294baf90ritchie } 61230fb85f55cbd8df3005e652da3781f51294baf90ritchie read = this.inputStream.read(buf, this.rlen, HTTPSession.BUFSIZE - this.rlen); 61330fb85f55cbd8df3005e652da3781f51294baf90ritchie } 61430fb85f55cbd8df3005e652da3781f51294baf90ritchie } 61530fb85f55cbd8df3005e652da3781f51294baf90ritchie 61630fb85f55cbd8df3005e652da3781f51294baf90ritchie if (this.splitbyte < this.rlen) { 61730fb85f55cbd8df3005e652da3781f51294baf90ritchie this.inputStream.unread(buf, this.splitbyte, this.rlen - this.splitbyte); 61830fb85f55cbd8df3005e652da3781f51294baf90ritchie } 61930fb85f55cbd8df3005e652da3781f51294baf90ritchie 62030fb85f55cbd8df3005e652da3781f51294baf90ritchie this.parms = new HashMap<String, String>(); 62130fb85f55cbd8df3005e652da3781f51294baf90ritchie if (null == this.headers) { 62230fb85f55cbd8df3005e652da3781f51294baf90ritchie this.headers = new HashMap<String, String>(); 62330fb85f55cbd8df3005e652da3781f51294baf90ritchie } else { 62430fb85f55cbd8df3005e652da3781f51294baf90ritchie this.headers.clear(); 62530fb85f55cbd8df3005e652da3781f51294baf90ritchie } 62630fb85f55cbd8df3005e652da3781f51294baf90ritchie 62730fb85f55cbd8df3005e652da3781f51294baf90ritchie if (null != this.remoteIp) { 62830fb85f55cbd8df3005e652da3781f51294baf90ritchie this.headers.put("remote-addr", this.remoteIp); 62930fb85f55cbd8df3005e652da3781f51294baf90ritchie this.headers.put("http-client-ip", this.remoteIp); 63030fb85f55cbd8df3005e652da3781f51294baf90ritchie } 63130fb85f55cbd8df3005e652da3781f51294baf90ritchie 63230fb85f55cbd8df3005e652da3781f51294baf90ritchie // Create a BufferedReader for parsing the header. 63330fb85f55cbd8df3005e652da3781f51294baf90ritchie BufferedReader hin = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(buf, 0, this.rlen))); 63430fb85f55cbd8df3005e652da3781f51294baf90ritchie 63530fb85f55cbd8df3005e652da3781f51294baf90ritchie // Decode the header into parms and header java properties 63630fb85f55cbd8df3005e652da3781f51294baf90ritchie Map<String, String> pre = new HashMap<String, String>(); 63730fb85f55cbd8df3005e652da3781f51294baf90ritchie decodeHeader(hin, pre, this.parms, this.headers); 63830fb85f55cbd8df3005e652da3781f51294baf90ritchie 63930fb85f55cbd8df3005e652da3781f51294baf90ritchie this.method = Method.lookup(pre.get("method")); 64030fb85f55cbd8df3005e652da3781f51294baf90ritchie if (this.method == null) { 64130fb85f55cbd8df3005e652da3781f51294baf90ritchie throw new ResponseException(Response.Status.BAD_REQUEST, "BAD REQUEST: Syntax error."); 64230fb85f55cbd8df3005e652da3781f51294baf90ritchie } 64330fb85f55cbd8df3005e652da3781f51294baf90ritchie 64430fb85f55cbd8df3005e652da3781f51294baf90ritchie this.uri = pre.get("uri"); 64530fb85f55cbd8df3005e652da3781f51294baf90ritchie 64630fb85f55cbd8df3005e652da3781f51294baf90ritchie this.cookies = new CookieHandler(this.headers); 64730fb85f55cbd8df3005e652da3781f51294baf90ritchie 64830fb85f55cbd8df3005e652da3781f51294baf90ritchie // Ok, now do the serve() 64930fb85f55cbd8df3005e652da3781f51294baf90ritchie Response r = serve(this); 65030fb85f55cbd8df3005e652da3781f51294baf90ritchie if (r == null) { 65130fb85f55cbd8df3005e652da3781f51294baf90ritchie throw new ResponseException(Response.Status.INTERNAL_ERROR, "SERVER INTERNAL ERROR: Serve() returned a null response."); 65230fb85f55cbd8df3005e652da3781f51294baf90ritchie } else { 65330fb85f55cbd8df3005e652da3781f51294baf90ritchie this.cookies.unloadQueue(r); 65430fb85f55cbd8df3005e652da3781f51294baf90ritchie r.setRequestMethod(this.method); 65530fb85f55cbd8df3005e652da3781f51294baf90ritchie r.send(this.outputStream); 65630fb85f55cbd8df3005e652da3781f51294baf90ritchie } 65730fb85f55cbd8df3005e652da3781f51294baf90ritchie } catch (SocketException e) { 65830fb85f55cbd8df3005e652da3781f51294baf90ritchie // throw it out to close socket object (finalAccept) 65930fb85f55cbd8df3005e652da3781f51294baf90ritchie throw e; 66030fb85f55cbd8df3005e652da3781f51294baf90ritchie } catch (SocketTimeoutException ste) { 66130fb85f55cbd8df3005e652da3781f51294baf90ritchie // treat socket timeouts the same way we treat socket exceptions 66230fb85f55cbd8df3005e652da3781f51294baf90ritchie // i.e. close the stream & finalAccept object by throwing the 66330fb85f55cbd8df3005e652da3781f51294baf90ritchie // exception up the call stack. 66430fb85f55cbd8df3005e652da3781f51294baf90ritchie throw ste; 66530fb85f55cbd8df3005e652da3781f51294baf90ritchie } catch (IOException ioe) { 66630fb85f55cbd8df3005e652da3781f51294baf90ritchie Response r = new Response(Response.Status.INTERNAL_ERROR, NanoHTTPD.MIME_PLAINTEXT, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage()); 66730fb85f55cbd8df3005e652da3781f51294baf90ritchie r.send(this.outputStream); 66830fb85f55cbd8df3005e652da3781f51294baf90ritchie safeClose(this.outputStream); 66930fb85f55cbd8df3005e652da3781f51294baf90ritchie } catch (ResponseException re) { 67030fb85f55cbd8df3005e652da3781f51294baf90ritchie Response r = new Response(re.getStatus(), NanoHTTPD.MIME_PLAINTEXT, re.getMessage()); 67130fb85f55cbd8df3005e652da3781f51294baf90ritchie r.send(this.outputStream); 67230fb85f55cbd8df3005e652da3781f51294baf90ritchie safeClose(this.outputStream); 67330fb85f55cbd8df3005e652da3781f51294baf90ritchie } finally { 67430fb85f55cbd8df3005e652da3781f51294baf90ritchie this.tempFileManager.clear(); 675fe1961e39809c90a76422332e91af67a5c65c31cSleekWeasel } 676fe1961e39809c90a76422332e91af67a5c65c31cSleekWeasel } 677fe1961e39809c90a76422332e91af67a5c65c31cSleekWeasel 67830fb85f55cbd8df3005e652da3781f51294baf90ritchie /** 67930fb85f55cbd8df3005e652da3781f51294baf90ritchie * Find byte index separating header from body. It must be the last byte 68030fb85f55cbd8df3005e652da3781f51294baf90ritchie * of the first two sequential new lines. 68130fb85f55cbd8df3005e652da3781f51294baf90ritchie */ 68230fb85f55cbd8df3005e652da3781f51294baf90ritchie private int findHeaderEnd(final byte[] buf, int rlen) { 68330fb85f55cbd8df3005e652da3781f51294baf90ritchie int splitbyte = 0; 68430fb85f55cbd8df3005e652da3781f51294baf90ritchie while (splitbyte + 3 < rlen) { 68530fb85f55cbd8df3005e652da3781f51294baf90ritchie if (buf[splitbyte] == '\r' && buf[splitbyte + 1] == '\n' && buf[splitbyte + 2] == '\r' && buf[splitbyte + 3] == '\n') { 68630fb85f55cbd8df3005e652da3781f51294baf90ritchie return splitbyte + 4; 687fe1961e39809c90a76422332e91af67a5c65c31cSleekWeasel } 68830fb85f55cbd8df3005e652da3781f51294baf90ritchie splitbyte++; 689fe1961e39809c90a76422332e91af67a5c65c31cSleekWeasel } 69030fb85f55cbd8df3005e652da3781f51294baf90ritchie return 0; 691fe1961e39809c90a76422332e91af67a5c65c31cSleekWeasel } 692fe1961e39809c90a76422332e91af67a5c65c31cSleekWeasel 69330fb85f55cbd8df3005e652da3781f51294baf90ritchie /** 69430fb85f55cbd8df3005e652da3781f51294baf90ritchie * Find the byte positions where multipart boundaries start. 69530fb85f55cbd8df3005e652da3781f51294baf90ritchie */ 69630fb85f55cbd8df3005e652da3781f51294baf90ritchie private int[] getBoundaryPositions(ByteBuffer b, byte[] boundary) { 69730fb85f55cbd8df3005e652da3781f51294baf90ritchie int matchcount = 0; 69830fb85f55cbd8df3005e652da3781f51294baf90ritchie int matchbyte = -1; 69930fb85f55cbd8df3005e652da3781f51294baf90ritchie List<Integer> matchbytes = new ArrayList<Integer>(); 70030fb85f55cbd8df3005e652da3781f51294baf90ritchie for (int i = 0; i < b.limit(); i++) { 70130fb85f55cbd8df3005e652da3781f51294baf90ritchie if (b.get(i) == boundary[matchcount]) { 70230fb85f55cbd8df3005e652da3781f51294baf90ritchie if (matchcount == 0) { 70330fb85f55cbd8df3005e652da3781f51294baf90ritchie matchbyte = i; 70430fb85f55cbd8df3005e652da3781f51294baf90ritchie } 70530fb85f55cbd8df3005e652da3781f51294baf90ritchie matchcount++; 70630fb85f55cbd8df3005e652da3781f51294baf90ritchie if (matchcount == boundary.length) { 70730fb85f55cbd8df3005e652da3781f51294baf90ritchie matchbytes.add(matchbyte); 70830fb85f55cbd8df3005e652da3781f51294baf90ritchie matchcount = 0; 70930fb85f55cbd8df3005e652da3781f51294baf90ritchie matchbyte = -1; 71030fb85f55cbd8df3005e652da3781f51294baf90ritchie } 71130fb85f55cbd8df3005e652da3781f51294baf90ritchie } else { 71230fb85f55cbd8df3005e652da3781f51294baf90ritchie i -= matchcount; 71330fb85f55cbd8df3005e652da3781f51294baf90ritchie matchcount = 0; 71430fb85f55cbd8df3005e652da3781f51294baf90ritchie matchbyte = -1; 71530fb85f55cbd8df3005e652da3781f51294baf90ritchie } 71630fb85f55cbd8df3005e652da3781f51294baf90ritchie } 71730fb85f55cbd8df3005e652da3781f51294baf90ritchie int[] ret = new int[matchbytes.size()]; 71830fb85f55cbd8df3005e652da3781f51294baf90ritchie for (int i = 0; i < ret.length; i++) { 71930fb85f55cbd8df3005e652da3781f51294baf90ritchie ret[i] = matchbytes.get(i); 72030fb85f55cbd8df3005e652da3781f51294baf90ritchie } 72130fb85f55cbd8df3005e652da3781f51294baf90ritchie return ret; 722c6f3f15bbf5a0ccbff5a4f17e53804c0d3888446Paul Hawke } 723c6f3f15bbf5a0ccbff5a4f17e53804c0d3888446Paul Hawke 72430fb85f55cbd8df3005e652da3781f51294baf90ritchie @Override 72530fb85f55cbd8df3005e652da3781f51294baf90ritchie public CookieHandler getCookies() { 72630fb85f55cbd8df3005e652da3781f51294baf90ritchie return this.cookies; 727c6f3f15bbf5a0ccbff5a4f17e53804c0d3888446Paul Hawke } 728c6f3f15bbf5a0ccbff5a4f17e53804c0d3888446Paul Hawke 72930fb85f55cbd8df3005e652da3781f51294baf90ritchie @Override 73030fb85f55cbd8df3005e652da3781f51294baf90ritchie public final Map<String, String> getHeaders() { 73130fb85f55cbd8df3005e652da3781f51294baf90ritchie return this.headers; 732c6f3f15bbf5a0ccbff5a4f17e53804c0d3888446Paul Hawke } 733c6f3f15bbf5a0ccbff5a4f17e53804c0d3888446Paul Hawke 73430fb85f55cbd8df3005e652da3781f51294baf90ritchie @Override 73530fb85f55cbd8df3005e652da3781f51294baf90ritchie public final InputStream getInputStream() { 73630fb85f55cbd8df3005e652da3781f51294baf90ritchie return this.inputStream; 737c6f3f15bbf5a0ccbff5a4f17e53804c0d3888446Paul Hawke } 738c6f3f15bbf5a0ccbff5a4f17e53804c0d3888446Paul Hawke 73930fb85f55cbd8df3005e652da3781f51294baf90ritchie @Override 74030fb85f55cbd8df3005e652da3781f51294baf90ritchie public final Method getMethod() { 74130fb85f55cbd8df3005e652da3781f51294baf90ritchie return this.method; 742c6f3f15bbf5a0ccbff5a4f17e53804c0d3888446Paul Hawke } 743c6f3f15bbf5a0ccbff5a4f17e53804c0d3888446Paul Hawke 74430fb85f55cbd8df3005e652da3781f51294baf90ritchie @Override 74530fb85f55cbd8df3005e652da3781f51294baf90ritchie public final Map<String, String> getParms() { 74630fb85f55cbd8df3005e652da3781f51294baf90ritchie return this.parms; 747c6f3f15bbf5a0ccbff5a4f17e53804c0d3888446Paul Hawke } 748c6f3f15bbf5a0ccbff5a4f17e53804c0d3888446Paul Hawke 74930fb85f55cbd8df3005e652da3781f51294baf90ritchie @Override 75030fb85f55cbd8df3005e652da3781f51294baf90ritchie public String getQueryParameterString() { 75130fb85f55cbd8df3005e652da3781f51294baf90ritchie return this.queryParameterString; 752c6f3f15bbf5a0ccbff5a4f17e53804c0d3888446Paul Hawke } 753c6f3f15bbf5a0ccbff5a4f17e53804c0d3888446Paul Hawke 75430fb85f55cbd8df3005e652da3781f51294baf90ritchie private RandomAccessFile getTmpBucket() { 75530fb85f55cbd8df3005e652da3781f51294baf90ritchie try { 75630fb85f55cbd8df3005e652da3781f51294baf90ritchie TempFile tempFile = this.tempFileManager.createTempFile(); 75730fb85f55cbd8df3005e652da3781f51294baf90ritchie return new RandomAccessFile(tempFile.getName(), "rw"); 75830fb85f55cbd8df3005e652da3781f51294baf90ritchie } catch (Exception e) { 75930fb85f55cbd8df3005e652da3781f51294baf90ritchie throw new Error(e); // we won't recover, so throw an error 7609788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke } 7619788c5b7900b4c7ff27355f69fee4e14630eb412Paul Hawke } 7627e4e4ae652c755faea626f1e2538d495f96e648esynapticloop 763bc0de3e38a4edadd6704a6c8ccb0c5ee738b8f54SleekWeasel @Override 76430fb85f55cbd8df3005e652da3781f51294baf90ritchie public final String getUri() { 76530fb85f55cbd8df3005e652da3781f51294baf90ritchie return this.uri; 76693441a0455c683a17622ec2da6768c326e6d7a48Martin M Reed } 76793441a0455c683a17622ec2da6768c326e6d7a48Martin M Reed 768bc0de3e38a4edadd6704a6c8ccb0c5ee738b8f54SleekWeasel @Override 769bc0de3e38a4edadd6704a6c8ccb0c5ee738b8f54SleekWeasel public void parseBody(Map<String, String> files) throws IOException, ResponseException { 77093441a0455c683a17622ec2da6768c326e6d7a48Martin M Reed RandomAccessFile randomAccessFile = null; 77193441a0455c683a17622ec2da6768c326e6d7a48Martin M Reed BufferedReader in = null; 77293441a0455c683a17622ec2da6768c326e6d7a48Martin M Reed try { 77393441a0455c683a17622ec2da6768c326e6d7a48Martin M Reed 774412e2e136193da510e859f73f9ff2ac21565df69james.mcclure randomAccessFile = getTmpBucket(); 775d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke 776745614b606ebcca5f69f5c84bde1774ca2f11cc5Martin M Reed long size; 77730fb85f55cbd8df3005e652da3781f51294baf90ritchie if (this.headers.containsKey("content-length")) { 77830fb85f55cbd8df3005e652da3781f51294baf90ritchie size = Integer.parseInt(this.headers.get("content-length")); 77930fb85f55cbd8df3005e652da3781f51294baf90ritchie } else if (this.splitbyte < this.rlen) { 78030fb85f55cbd8df3005e652da3781f51294baf90ritchie size = this.rlen - this.splitbyte; 781745614b606ebcca5f69f5c84bde1774ca2f11cc5Martin M Reed } else { 782d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke size = 0; 7830cb0846c31d1eaf3fcd5d2382ca595abfb2dda38Paul Hawke } 784d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke 785d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke // Now read all the body and write it to f 786745614b606ebcca5f69f5c84bde1774ca2f11cc5Martin M Reed byte[] buf = new byte[512]; 78730fb85f55cbd8df3005e652da3781f51294baf90ritchie while (this.rlen >= 0 && size > 0) { 78830fb85f55cbd8df3005e652da3781f51294baf90ritchie this.rlen = this.inputStream.read(buf, 0, (int) Math.min(size, 512)); 78930fb85f55cbd8df3005e652da3781f51294baf90ritchie size -= this.rlen; 79030fb85f55cbd8df3005e652da3781f51294baf90ritchie if (this.rlen > 0) { 79130fb85f55cbd8df3005e652da3781f51294baf90ritchie randomAccessFile.write(buf, 0, this.rlen); 7920cb0846c31d1eaf3fcd5d2382ca595abfb2dda38Paul Hawke } 793269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke } 794269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke 795269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke // Get the raw body as a byte [] 796412e2e136193da510e859f73f9ff2ac21565df69james.mcclure ByteBuffer fbuf = randomAccessFile.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, randomAccessFile.length()); 797412e2e136193da510e859f73f9ff2ac21565df69james.mcclure randomAccessFile.seek(0); 798269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke 799269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke // Create a BufferedReader for easily reading it as string. 800412e2e136193da510e859f73f9ff2ac21565df69james.mcclure InputStream bin = new FileInputStream(randomAccessFile.getFD()); 80193441a0455c683a17622ec2da6768c326e6d7a48Martin M Reed in = new BufferedReader(new InputStreamReader(bin)); 802269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke 803269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke // If the method is POST, there may be parameters 804269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke // in data section, too, read it: 80530fb85f55cbd8df3005e652da3781f51294baf90ritchie if (Method.POST.equals(this.method)) { 806269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke String contentType = ""; 80730fb85f55cbd8df3005e652da3781f51294baf90ritchie String contentTypeHeader = this.headers.get("content-type"); 8082b54eda5fee8430268a99d73b061e790362db544Tom Hermann 8092c48103a2fc2ef4ccbd5d441da96dc77c7736109Paul Hawke StringTokenizer st = null; 8102c48103a2fc2ef4ccbd5d441da96dc77c7736109Paul Hawke if (contentTypeHeader != null) { 8112c48103a2fc2ef4ccbd5d441da96dc77c7736109Paul Hawke st = new StringTokenizer(contentTypeHeader, ",; "); 8122c48103a2fc2ef4ccbd5d441da96dc77c7736109Paul Hawke if (st.hasMoreTokens()) { 8132c48103a2fc2ef4ccbd5d441da96dc77c7736109Paul Hawke contentType = st.nextToken(); 8142c48103a2fc2ef4ccbd5d441da96dc77c7736109Paul Hawke } 815269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke } 816269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke 8170277bbd03983f15110fcf0d039a3e276d0808e29Paul Hawke if ("multipart/form-data".equalsIgnoreCase(contentType)) { 818269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke // Handle multipart/form-data 8190cb0846c31d1eaf3fcd5d2382ca595abfb2dda38Paul Hawke if (!st.hasMoreTokens()) { 8209058464950a9734da0a7ff2dc47f3081bbb5117critchie throw new ResponseException(Response.Status.BAD_REQUEST, 8219058464950a9734da0a7ff2dc47f3081bbb5117critchie "BAD REQUEST: Content type is multipart/form-data but boundary missing. Usage: GET /example/file.html"); 8220cb0846c31d1eaf3fcd5d2382ca595abfb2dda38Paul Hawke } 8232b54eda5fee8430268a99d73b061e790362db544Tom Hermann 8244e1ae19daee9a8e9437476af6b07a6aba950802cPaul Hawke String boundaryStartString = "boundary="; 8252b54eda5fee8430268a99d73b061e790362db544Tom Hermann int boundaryContentStart = contentTypeHeader.indexOf(boundaryStartString) + boundaryStartString.length(); 8264e1ae19daee9a8e9437476af6b07a6aba950802cPaul Hawke String boundary = contentTypeHeader.substring(boundaryContentStart, contentTypeHeader.length()); 827220e1a21e7bbb831d06551c72799dfedc1db979fPaul Hawke if (boundary.startsWith("\"") && boundary.endsWith("\"")) { 8284e1ae19daee9a8e9437476af6b07a6aba950802cPaul Hawke boundary = boundary.substring(1, boundary.length() - 1); 8294e1ae19daee9a8e9437476af6b07a6aba950802cPaul Hawke } 830269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke 83130fb85f55cbd8df3005e652da3781f51294baf90ritchie decodeMultipartData(boundary, fbuf, in, this.parms, files); 832269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke } else { 833269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke String postLine = ""; 83435b077b8e55a8de374f4f314e151c9cb65eb050aTim Wright StringBuilder postLineBuffer = new StringBuilder(); 835269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke char pbuf[] = new char[512]; 836269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke int read = in.read(pbuf); 837269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke while (read >= 0 && !postLine.endsWith("\r\n")) { 83835b077b8e55a8de374f4f314e151c9cb65eb050aTim Wright postLine = String.valueOf(pbuf, 0, read); 83935b077b8e55a8de374f4f314e151c9cb65eb050aTim Wright postLineBuffer.append(postLine); 840269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke read = in.read(pbuf); 841269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke } 84235b077b8e55a8de374f4f314e151c9cb65eb050aTim Wright postLine = postLineBuffer.toString().trim(); 843e9a1b5310ed98d7c80095fbefd856a0b1277a1d2unknown // Handle application/x-www-form-urlencoded 844e9a1b5310ed98d7c80095fbefd856a0b1277a1d2unknown if ("application/x-www-form-urlencoded".equalsIgnoreCase(contentType)) { 84530fb85f55cbd8df3005e652da3781f51294baf90ritchie decodeParms(postLine, this.parms); 846e9a1b5310ed98d7c80095fbefd856a0b1277a1d2unknown } else if (postLine.length() != 0) { 8479058464950a9734da0a7ff2dc47f3081bbb5117critchie // Special case for raw POST data => create a 8489058464950a9734da0a7ff2dc47f3081bbb5117critchie // special files entry "postData" with raw content 8499058464950a9734da0a7ff2dc47f3081bbb5117critchie // data 8509058464950a9734da0a7ff2dc47f3081bbb5117critchie files.put("postData", postLine); 851e9a1b5310ed98d7c80095fbefd856a0b1277a1d2unknown } 852269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke } 85330fb85f55cbd8df3005e652da3781f51294baf90ritchie } else if (Method.PUT.equals(this.method)) { 854dfea30a36949bc24a698ac39d8a714fc8f1c9e82Paul Hawke files.put("content", saveTmpFile(fbuf, 0, fbuf.limit())); 855b814145cf66e94b7ce5511077ca0ebcff6e67268Paul Hawke } 8564e1ae19daee9a8e9437476af6b07a6aba950802cPaul Hawke } finally { 8579cd5d3b4438667394ab98895c75b4e1a2f8e76a0Martin M Reed safeClose(randomAccessFile); 85893441a0455c683a17622ec2da6768c326e6d7a48Martin M Reed safeClose(in); 859269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke } 860269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke } 861269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke 862269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke /** 86330fb85f55cbd8df3005e652da3781f51294baf90ritchie * Retrieves the content of a sent file and saves it to a temporary 86430fb85f55cbd8df3005e652da3781f51294baf90ritchie * file. The full path to the saved file is returned. 865d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke */ 86630fb85f55cbd8df3005e652da3781f51294baf90ritchie private String saveTmpFile(ByteBuffer b, int offset, int len) { 86730fb85f55cbd8df3005e652da3781f51294baf90ritchie String path = ""; 86830fb85f55cbd8df3005e652da3781f51294baf90ritchie if (len > 0) { 86930fb85f55cbd8df3005e652da3781f51294baf90ritchie FileOutputStream fileOutputStream = null; 87030fb85f55cbd8df3005e652da3781f51294baf90ritchie try { 87130fb85f55cbd8df3005e652da3781f51294baf90ritchie TempFile tempFile = this.tempFileManager.createTempFile(); 87230fb85f55cbd8df3005e652da3781f51294baf90ritchie ByteBuffer src = b.duplicate(); 87330fb85f55cbd8df3005e652da3781f51294baf90ritchie fileOutputStream = new FileOutputStream(tempFile.getName()); 87430fb85f55cbd8df3005e652da3781f51294baf90ritchie FileChannel dest = fileOutputStream.getChannel(); 87530fb85f55cbd8df3005e652da3781f51294baf90ritchie src.position(offset).limit(offset + len); 87630fb85f55cbd8df3005e652da3781f51294baf90ritchie dest.write(src.slice()); 87730fb85f55cbd8df3005e652da3781f51294baf90ritchie path = tempFile.getName(); 87830fb85f55cbd8df3005e652da3781f51294baf90ritchie } catch (Exception e) { // Catch exception if any 87930fb85f55cbd8df3005e652da3781f51294baf90ritchie throw new Error(e); // we won't recover, so throw an error 88030fb85f55cbd8df3005e652da3781f51294baf90ritchie } finally { 88130fb85f55cbd8df3005e652da3781f51294baf90ritchie safeClose(fileOutputStream); 8820cb0846c31d1eaf3fcd5d2382ca595abfb2dda38Paul Hawke } 88330fb85f55cbd8df3005e652da3781f51294baf90ritchie } 88430fb85f55cbd8df3005e652da3781f51294baf90ritchie return path; 88530fb85f55cbd8df3005e652da3781f51294baf90ritchie } 8860cb0846c31d1eaf3fcd5d2382ca595abfb2dda38Paul Hawke 88730fb85f55cbd8df3005e652da3781f51294baf90ritchie /** 88830fb85f55cbd8df3005e652da3781f51294baf90ritchie * It returns the offset separating multipart file headers from the 88930fb85f55cbd8df3005e652da3781f51294baf90ritchie * file's data. 89030fb85f55cbd8df3005e652da3781f51294baf90ritchie */ 89130fb85f55cbd8df3005e652da3781f51294baf90ritchie private int stripMultipartHeaders(ByteBuffer b, int offset) { 89230fb85f55cbd8df3005e652da3781f51294baf90ritchie int i; 89330fb85f55cbd8df3005e652da3781f51294baf90ritchie for (i = offset; i < b.limit(); i++) { 89430fb85f55cbd8df3005e652da3781f51294baf90ritchie if (b.get(i) == '\r' && b.get(++i) == '\n' && b.get(++i) == '\r' && b.get(++i) == '\n') { 89530fb85f55cbd8df3005e652da3781f51294baf90ritchie break; 8960cb0846c31d1eaf3fcd5d2382ca595abfb2dda38Paul Hawke } 89730fb85f55cbd8df3005e652da3781f51294baf90ritchie } 89830fb85f55cbd8df3005e652da3781f51294baf90ritchie return i + 1; 89930fb85f55cbd8df3005e652da3781f51294baf90ritchie } 90030fb85f55cbd8df3005e652da3781f51294baf90ritchie } 901269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke 90230fb85f55cbd8df3005e652da3781f51294baf90ritchie /** 90330fb85f55cbd8df3005e652da3781f51294baf90ritchie * Handles one session, i.e. parses the HTTP request and returns the 90430fb85f55cbd8df3005e652da3781f51294baf90ritchie * response. 90530fb85f55cbd8df3005e652da3781f51294baf90ritchie */ 90630fb85f55cbd8df3005e652da3781f51294baf90ritchie public interface IHTTPSession { 907269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke 90830fb85f55cbd8df3005e652da3781f51294baf90ritchie void execute() throws IOException; 909269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke 91030fb85f55cbd8df3005e652da3781f51294baf90ritchie CookieHandler getCookies(); 911269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke 91230fb85f55cbd8df3005e652da3781f51294baf90ritchie Map<String, String> getHeaders(); 913269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke 91430fb85f55cbd8df3005e652da3781f51294baf90ritchie InputStream getInputStream(); 91530fb85f55cbd8df3005e652da3781f51294baf90ritchie 91630fb85f55cbd8df3005e652da3781f51294baf90ritchie Method getMethod(); 91730fb85f55cbd8df3005e652da3781f51294baf90ritchie 91830fb85f55cbd8df3005e652da3781f51294baf90ritchie Map<String, String> getParms(); 91930fb85f55cbd8df3005e652da3781f51294baf90ritchie 92030fb85f55cbd8df3005e652da3781f51294baf90ritchie String getQueryParameterString(); 921269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke 922269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke /** 92330fb85f55cbd8df3005e652da3781f51294baf90ritchie * @return the path part of the URL. 924d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke */ 92530fb85f55cbd8df3005e652da3781f51294baf90ritchie String getUri(); 926269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke 927269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke /** 92830fb85f55cbd8df3005e652da3781f51294baf90ritchie * Adds the files in the request body to the files map. 92930fb85f55cbd8df3005e652da3781f51294baf90ritchie * 93030fb85f55cbd8df3005e652da3781f51294baf90ritchie * @param files 93130fb85f55cbd8df3005e652da3781f51294baf90ritchie * map to modify 932d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke */ 93330fb85f55cbd8df3005e652da3781f51294baf90ritchie void parseBody(Map<String, String> files) throws IOException, ResponseException; 93430fb85f55cbd8df3005e652da3781f51294baf90ritchie } 93530fb85f55cbd8df3005e652da3781f51294baf90ritchie 93630fb85f55cbd8df3005e652da3781f51294baf90ritchie /** 93730fb85f55cbd8df3005e652da3781f51294baf90ritchie * HTTP Request methods, with the ability to decode a <code>String</code> 93830fb85f55cbd8df3005e652da3781f51294baf90ritchie * back to its enum value. 93930fb85f55cbd8df3005e652da3781f51294baf90ritchie */ 94030fb85f55cbd8df3005e652da3781f51294baf90ritchie public enum Method { 94130fb85f55cbd8df3005e652da3781f51294baf90ritchie GET, 94230fb85f55cbd8df3005e652da3781f51294baf90ritchie PUT, 94330fb85f55cbd8df3005e652da3781f51294baf90ritchie POST, 94430fb85f55cbd8df3005e652da3781f51294baf90ritchie DELETE, 94530fb85f55cbd8df3005e652da3781f51294baf90ritchie HEAD, 94630fb85f55cbd8df3005e652da3781f51294baf90ritchie OPTIONS; 94730fb85f55cbd8df3005e652da3781f51294baf90ritchie 94830fb85f55cbd8df3005e652da3781f51294baf90ritchie static Method lookup(String method) { 94930fb85f55cbd8df3005e652da3781f51294baf90ritchie for (Method m : Method.values()) { 95030fb85f55cbd8df3005e652da3781f51294baf90ritchie if (m.toString().equalsIgnoreCase(method)) { 95130fb85f55cbd8df3005e652da3781f51294baf90ritchie return m; 952269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke } 953269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke } 95430fb85f55cbd8df3005e652da3781f51294baf90ritchie return null; 95530fb85f55cbd8df3005e652da3781f51294baf90ritchie } 95630fb85f55cbd8df3005e652da3781f51294baf90ritchie } 95730fb85f55cbd8df3005e652da3781f51294baf90ritchie 95830fb85f55cbd8df3005e652da3781f51294baf90ritchie /** 95930fb85f55cbd8df3005e652da3781f51294baf90ritchie * HTTP response. Return one of these from serve(). 96030fb85f55cbd8df3005e652da3781f51294baf90ritchie */ 96130fb85f55cbd8df3005e652da3781f51294baf90ritchie public static class Response { 96230fb85f55cbd8df3005e652da3781f51294baf90ritchie 96330fb85f55cbd8df3005e652da3781f51294baf90ritchie public interface IStatus { 96430fb85f55cbd8df3005e652da3781f51294baf90ritchie 96530fb85f55cbd8df3005e652da3781f51294baf90ritchie String getDescription(); 96630fb85f55cbd8df3005e652da3781f51294baf90ritchie 96730fb85f55cbd8df3005e652da3781f51294baf90ritchie int getRequestStatus(); 968269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke } 969269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke 970269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke /** 97130fb85f55cbd8df3005e652da3781f51294baf90ritchie * Some HTTP response status codes 972d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke */ 97330fb85f55cbd8df3005e652da3781f51294baf90ritchie public enum Status implements IStatus { 97430fb85f55cbd8df3005e652da3781f51294baf90ritchie SWITCH_PROTOCOL(101, "Switching Protocols"), 97530fb85f55cbd8df3005e652da3781f51294baf90ritchie OK(200, "OK"), 97630fb85f55cbd8df3005e652da3781f51294baf90ritchie CREATED(201, "Created"), 97730fb85f55cbd8df3005e652da3781f51294baf90ritchie ACCEPTED(202, "Accepted"), 97830fb85f55cbd8df3005e652da3781f51294baf90ritchie NO_CONTENT(204, "No Content"), 97930fb85f55cbd8df3005e652da3781f51294baf90ritchie PARTIAL_CONTENT(206, "Partial Content"), 98030fb85f55cbd8df3005e652da3781f51294baf90ritchie REDIRECT(301, "Moved Permanently"), 98130fb85f55cbd8df3005e652da3781f51294baf90ritchie NOT_MODIFIED(304, "Not Modified"), 98230fb85f55cbd8df3005e652da3781f51294baf90ritchie BAD_REQUEST(400, "Bad Request"), 98330fb85f55cbd8df3005e652da3781f51294baf90ritchie UNAUTHORIZED(401, "Unauthorized"), 98430fb85f55cbd8df3005e652da3781f51294baf90ritchie FORBIDDEN(403, "Forbidden"), 98530fb85f55cbd8df3005e652da3781f51294baf90ritchie NOT_FOUND(404, "Not Found"), 98630fb85f55cbd8df3005e652da3781f51294baf90ritchie METHOD_NOT_ALLOWED(405, "Method Not Allowed"), 98730fb85f55cbd8df3005e652da3781f51294baf90ritchie RANGE_NOT_SATISFIABLE(416, "Requested Range Not Satisfiable"), 98830fb85f55cbd8df3005e652da3781f51294baf90ritchie INTERNAL_ERROR(500, "Internal Server Error"); 98930fb85f55cbd8df3005e652da3781f51294baf90ritchie 99030fb85f55cbd8df3005e652da3781f51294baf90ritchie private final int requestStatus; 99130fb85f55cbd8df3005e652da3781f51294baf90ritchie 99230fb85f55cbd8df3005e652da3781f51294baf90ritchie private final String description; 99330fb85f55cbd8df3005e652da3781f51294baf90ritchie 99430fb85f55cbd8df3005e652da3781f51294baf90ritchie Status(int requestStatus, String description) { 99530fb85f55cbd8df3005e652da3781f51294baf90ritchie this.requestStatus = requestStatus; 99630fb85f55cbd8df3005e652da3781f51294baf90ritchie this.description = description; 997269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke } 99830fb85f55cbd8df3005e652da3781f51294baf90ritchie 99930fb85f55cbd8df3005e652da3781f51294baf90ritchie @Override 100030fb85f55cbd8df3005e652da3781f51294baf90ritchie public String getDescription() { 100130fb85f55cbd8df3005e652da3781f51294baf90ritchie return "" + this.requestStatus + " " + this.description; 100230fb85f55cbd8df3005e652da3781f51294baf90ritchie } 100330fb85f55cbd8df3005e652da3781f51294baf90ritchie 100430fb85f55cbd8df3005e652da3781f51294baf90ritchie @Override 100530fb85f55cbd8df3005e652da3781f51294baf90ritchie public int getRequestStatus() { 100630fb85f55cbd8df3005e652da3781f51294baf90ritchie return this.requestStatus; 1007269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke } 1008269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke } 1009269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke 1010269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke /** 101130fb85f55cbd8df3005e652da3781f51294baf90ritchie * HTTP status code after processing, e.g. "200 OK", Status.OK 1012d22b56d68169cbf5fcf9ddfb30b54fb5f155cc40Paul Hawke */ 101330fb85f55cbd8df3005e652da3781f51294baf90ritchie private IStatus status; 1014269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke 101530fb85f55cbd8df3005e652da3781f51294baf90ritchie /** 101630fb85f55cbd8df3005e652da3781f51294baf90ritchie * MIME type of content, e.g. "text/html" 101730fb85f55cbd8df3005e652da3781f51294baf90ritchie */ 101830fb85f55cbd8df3005e652da3781f51294baf90ritchie private String mimeType; 101930fb85f55cbd8df3005e652da3781f51294baf90ritchie 102030fb85f55cbd8df3005e652da3781f51294baf90ritchie /** 102130fb85f55cbd8df3005e652da3781f51294baf90ritchie * Data of the response, may be null. 102230fb85f55cbd8df3005e652da3781f51294baf90ritchie */ 102330fb85f55cbd8df3005e652da3781f51294baf90ritchie private InputStream data; 102430fb85f55cbd8df3005e652da3781f51294baf90ritchie 102530fb85f55cbd8df3005e652da3781f51294baf90ritchie /** 102630fb85f55cbd8df3005e652da3781f51294baf90ritchie * Headers for the HTTP response. Use addHeader() to add lines. 102730fb85f55cbd8df3005e652da3781f51294baf90ritchie */ 102830fb85f55cbd8df3005e652da3781f51294baf90ritchie private final Map<String, String> header = new HashMap<String, String>(); 102930fb85f55cbd8df3005e652da3781f51294baf90ritchie 103030fb85f55cbd8df3005e652da3781f51294baf90ritchie /** 103130fb85f55cbd8df3005e652da3781f51294baf90ritchie * The request method that spawned this response. 103230fb85f55cbd8df3005e652da3781f51294baf90ritchie */ 103330fb85f55cbd8df3005e652da3781f51294baf90ritchie private Method requestMethod; 103430fb85f55cbd8df3005e652da3781f51294baf90ritchie 103530fb85f55cbd8df3005e652da3781f51294baf90ritchie /** 103630fb85f55cbd8df3005e652da3781f51294baf90ritchie * Use chunkedTransfer 103730fb85f55cbd8df3005e652da3781f51294baf90ritchie */ 103830fb85f55cbd8df3005e652da3781f51294baf90ritchie private boolean chunkedTransfer; 103930fb85f55cbd8df3005e652da3781f51294baf90ritchie 104030fb85f55cbd8df3005e652da3781f51294baf90ritchie /** 104130fb85f55cbd8df3005e652da3781f51294baf90ritchie * Basic constructor. 104230fb85f55cbd8df3005e652da3781f51294baf90ritchie */ 104330fb85f55cbd8df3005e652da3781f51294baf90ritchie public Response(IStatus status, String mimeType, InputStream data) { 104430fb85f55cbd8df3005e652da3781f51294baf90ritchie this.status = status; 104530fb85f55cbd8df3005e652da3781f51294baf90ritchie this.mimeType = mimeType; 104630fb85f55cbd8df3005e652da3781f51294baf90ritchie this.data = data; 104730fb85f55cbd8df3005e652da3781f51294baf90ritchie } 104830fb85f55cbd8df3005e652da3781f51294baf90ritchie 104930fb85f55cbd8df3005e652da3781f51294baf90ritchie /** 105030fb85f55cbd8df3005e652da3781f51294baf90ritchie * Convenience method that makes an InputStream out of given text. 105130fb85f55cbd8df3005e652da3781f51294baf90ritchie */ 105230fb85f55cbd8df3005e652da3781f51294baf90ritchie public Response(IStatus status, String mimeType, String txt) { 105330fb85f55cbd8df3005e652da3781f51294baf90ritchie this.status = status; 105430fb85f55cbd8df3005e652da3781f51294baf90ritchie this.mimeType = mimeType; 1055dfea30a36949bc24a698ac39d8a714fc8f1c9e82Paul Hawke try { 105630fb85f55cbd8df3005e652da3781f51294baf90ritchie this.data = txt != null ? new ByteArrayInputStream(txt.getBytes("UTF-8")) : null; 105730fb85f55cbd8df3005e652da3781f51294baf90ritchie } catch (java.io.UnsupportedEncodingException uee) { 105830fb85f55cbd8df3005e652da3781f51294baf90ritchie NanoHTTPD.LOG.log(Level.SEVERE, "encoding problem", uee); 1059dfea30a36949bc24a698ac39d8a714fc8f1c9e82Paul Hawke } 1060dfea30a36949bc24a698ac39d8a714fc8f1c9e82Paul Hawke } 1061dfea30a36949bc24a698ac39d8a714fc8f1c9e82Paul Hawke 1062269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke /** 106330fb85f55cbd8df3005e652da3781f51294baf90ritchie * Default constructor: response = Status.OK, mime = MIME_HTML and your 106430fb85f55cbd8df3005e652da3781f51294baf90ritchie * supplied message 1065269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke */ 106630fb85f55cbd8df3005e652da3781f51294baf90ritchie public Response(String msg) { 106730fb85f55cbd8df3005e652da3781f51294baf90ritchie this(Status.OK, NanoHTTPD.MIME_HTML, msg); 1068269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke } 1069269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke 1070269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke /** 107130fb85f55cbd8df3005e652da3781f51294baf90ritchie * Adds given line to the header. 1072269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke */ 107330fb85f55cbd8df3005e652da3781f51294baf90ritchie public void addHeader(String name, String value) { 107430fb85f55cbd8df3005e652da3781f51294baf90ritchie this.header.put(name, value); 107530fb85f55cbd8df3005e652da3781f51294baf90ritchie } 107630fb85f55cbd8df3005e652da3781f51294baf90ritchie 107730fb85f55cbd8df3005e652da3781f51294baf90ritchie public InputStream getData() { 107830fb85f55cbd8df3005e652da3781f51294baf90ritchie return this.data; 107930fb85f55cbd8df3005e652da3781f51294baf90ritchie } 108030fb85f55cbd8df3005e652da3781f51294baf90ritchie 108130fb85f55cbd8df3005e652da3781f51294baf90ritchie public String getHeader(String name) { 108230fb85f55cbd8df3005e652da3781f51294baf90ritchie return this.header.get(name); 108330fb85f55cbd8df3005e652da3781f51294baf90ritchie } 108430fb85f55cbd8df3005e652da3781f51294baf90ritchie 108530fb85f55cbd8df3005e652da3781f51294baf90ritchie public String getMimeType() { 108630fb85f55cbd8df3005e652da3781f51294baf90ritchie return this.mimeType; 108730fb85f55cbd8df3005e652da3781f51294baf90ritchie } 108830fb85f55cbd8df3005e652da3781f51294baf90ritchie 108930fb85f55cbd8df3005e652da3781f51294baf90ritchie public Method getRequestMethod() { 109030fb85f55cbd8df3005e652da3781f51294baf90ritchie return this.requestMethod; 109130fb85f55cbd8df3005e652da3781f51294baf90ritchie } 109230fb85f55cbd8df3005e652da3781f51294baf90ritchie 109330fb85f55cbd8df3005e652da3781f51294baf90ritchie public IStatus getStatus() { 109430fb85f55cbd8df3005e652da3781f51294baf90ritchie return this.status; 109530fb85f55cbd8df3005e652da3781f51294baf90ritchie } 109630fb85f55cbd8df3005e652da3781f51294baf90ritchie 109730fb85f55cbd8df3005e652da3781f51294baf90ritchie private boolean headerAlreadySent(Map<String, String> header, String name) { 109830fb85f55cbd8df3005e652da3781f51294baf90ritchie boolean alreadySent = false; 109930fb85f55cbd8df3005e652da3781f51294baf90ritchie for (String headerName : header.keySet()) { 110030fb85f55cbd8df3005e652da3781f51294baf90ritchie alreadySent |= headerName.equalsIgnoreCase(name); 1101f29e141959a4bbb738453c3462518e52ebb218cePaul Hawke } 110230fb85f55cbd8df3005e652da3781f51294baf90ritchie return alreadySent; 110330fb85f55cbd8df3005e652da3781f51294baf90ritchie } 1104269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke 110530fb85f55cbd8df3005e652da3781f51294baf90ritchie /** 110630fb85f55cbd8df3005e652da3781f51294baf90ritchie * Sends given response to the socket. 110730fb85f55cbd8df3005e652da3781f51294baf90ritchie */ 110830fb85f55cbd8df3005e652da3781f51294baf90ritchie protected void send(OutputStream outputStream) { 110930fb85f55cbd8df3005e652da3781f51294baf90ritchie String mime = this.mimeType; 111030fb85f55cbd8df3005e652da3781f51294baf90ritchie SimpleDateFormat gmtFrmt = new SimpleDateFormat("E, d MMM yyyy HH:mm:ss 'GMT'", Locale.US); 111130fb85f55cbd8df3005e652da3781f51294baf90ritchie gmtFrmt.setTimeZone(TimeZone.getTimeZone("GMT")); 111230fb85f55cbd8df3005e652da3781f51294baf90ritchie 111330fb85f55cbd8df3005e652da3781f51294baf90ritchie try { 111430fb85f55cbd8df3005e652da3781f51294baf90ritchie if (this.status == null) { 111530fb85f55cbd8df3005e652da3781f51294baf90ritchie throw new Error("sendResponse(): Status can't be null."); 111630fb85f55cbd8df3005e652da3781f51294baf90ritchie } 111730fb85f55cbd8df3005e652da3781f51294baf90ritchie PrintWriter pw = new PrintWriter(outputStream); 111830fb85f55cbd8df3005e652da3781f51294baf90ritchie pw.print("HTTP/1.1 " + this.status.getDescription() + " \r\n"); 111930fb85f55cbd8df3005e652da3781f51294baf90ritchie 112030fb85f55cbd8df3005e652da3781f51294baf90ritchie if (mime != null) { 112130fb85f55cbd8df3005e652da3781f51294baf90ritchie pw.print("Content-Type: " + mime + "\r\n"); 112230fb85f55cbd8df3005e652da3781f51294baf90ritchie } 112330fb85f55cbd8df3005e652da3781f51294baf90ritchie 112430fb85f55cbd8df3005e652da3781f51294baf90ritchie if (this.header == null || this.header.get("Date") == null) { 112530fb85f55cbd8df3005e652da3781f51294baf90ritchie pw.print("Date: " + gmtFrmt.format(new Date()) + "\r\n"); 112630fb85f55cbd8df3005e652da3781f51294baf90ritchie } 112730fb85f55cbd8df3005e652da3781f51294baf90ritchie 112830fb85f55cbd8df3005e652da3781f51294baf90ritchie if (this.header != null) { 112930fb85f55cbd8df3005e652da3781f51294baf90ritchie for (String key : this.header.keySet()) { 113030fb85f55cbd8df3005e652da3781f51294baf90ritchie String value = this.header.get(key); 113130fb85f55cbd8df3005e652da3781f51294baf90ritchie pw.print(key + ": " + value + "\r\n"); 113230fb85f55cbd8df3005e652da3781f51294baf90ritchie } 113330fb85f55cbd8df3005e652da3781f51294baf90ritchie } 113430fb85f55cbd8df3005e652da3781f51294baf90ritchie 113530fb85f55cbd8df3005e652da3781f51294baf90ritchie sendConnectionHeaderIfNotAlreadyPresent(pw, this.header); 113630fb85f55cbd8df3005e652da3781f51294baf90ritchie 113730fb85f55cbd8df3005e652da3781f51294baf90ritchie if (this.requestMethod != Method.HEAD && this.chunkedTransfer) { 113830fb85f55cbd8df3005e652da3781f51294baf90ritchie sendAsChunked(outputStream, pw); 1139b814145cf66e94b7ce5511077ca0ebcff6e67268Paul Hawke } else { 114030fb85f55cbd8df3005e652da3781f51294baf90ritchie int pending = this.data != null ? this.data.available() : 0; 114130fb85f55cbd8df3005e652da3781f51294baf90ritchie pending = sendContentLengthHeaderIfNotAlreadyPresent(pw, this.header, pending); 114230fb85f55cbd8df3005e652da3781f51294baf90ritchie pw.print("\r\n"); 114330fb85f55cbd8df3005e652da3781f51294baf90ritchie pw.flush(); 114430fb85f55cbd8df3005e652da3781f51294baf90ritchie sendAsFixedLength(outputStream, pending); 1145269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke } 114630fb85f55cbd8df3005e652da3781f51294baf90ritchie outputStream.flush(); 114730fb85f55cbd8df3005e652da3781f51294baf90ritchie safeClose(this.data); 114830fb85f55cbd8df3005e652da3781f51294baf90ritchie } catch (IOException ioe) { 114930fb85f55cbd8df3005e652da3781f51294baf90ritchie NanoHTTPD.LOG.log(Level.SEVERE, "Could not send response to the client", ioe); 1150269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke } 1151269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke } 115293441a0455c683a17622ec2da6768c326e6d7a48Martin M Reed 115330fb85f55cbd8df3005e652da3781f51294baf90ritchie private void sendAsChunked(OutputStream outputStream, PrintWriter pw) throws IOException { 115430fb85f55cbd8df3005e652da3781f51294baf90ritchie pw.print("Transfer-Encoding: chunked\r\n"); 115530fb85f55cbd8df3005e652da3781f51294baf90ritchie pw.print("\r\n"); 115630fb85f55cbd8df3005e652da3781f51294baf90ritchie pw.flush(); 115730fb85f55cbd8df3005e652da3781f51294baf90ritchie int BUFFER_SIZE = 16 * 1024; 115830fb85f55cbd8df3005e652da3781f51294baf90ritchie byte[] CRLF = "\r\n".getBytes(); 115930fb85f55cbd8df3005e652da3781f51294baf90ritchie byte[] buff = new byte[BUFFER_SIZE]; 116030fb85f55cbd8df3005e652da3781f51294baf90ritchie int read; 116130fb85f55cbd8df3005e652da3781f51294baf90ritchie while ((read = this.data.read(buff)) > 0) { 116230fb85f55cbd8df3005e652da3781f51294baf90ritchie outputStream.write(String.format("%x\r\n", read).getBytes()); 116330fb85f55cbd8df3005e652da3781f51294baf90ritchie outputStream.write(buff, 0, read); 116430fb85f55cbd8df3005e652da3781f51294baf90ritchie outputStream.write(CRLF); 116530fb85f55cbd8df3005e652da3781f51294baf90ritchie } 116630fb85f55cbd8df3005e652da3781f51294baf90ritchie outputStream.write(String.format("0\r\n\r\n").getBytes()); 116793441a0455c683a17622ec2da6768c326e6d7a48Martin M Reed } 116893441a0455c683a17622ec2da6768c326e6d7a48Martin M Reed 116930fb85f55cbd8df3005e652da3781f51294baf90ritchie private void sendAsFixedLength(OutputStream outputStream, int pending) throws IOException { 117030fb85f55cbd8df3005e652da3781f51294baf90ritchie if (this.requestMethod != Method.HEAD && this.data != null) { 117130fb85f55cbd8df3005e652da3781f51294baf90ritchie int BUFFER_SIZE = 16 * 1024; 117230fb85f55cbd8df3005e652da3781f51294baf90ritchie byte[] buff = new byte[BUFFER_SIZE]; 117330fb85f55cbd8df3005e652da3781f51294baf90ritchie while (pending > 0) { 117430fb85f55cbd8df3005e652da3781f51294baf90ritchie int read = this.data.read(buff, 0, pending > BUFFER_SIZE ? BUFFER_SIZE : pending); 117530fb85f55cbd8df3005e652da3781f51294baf90ritchie if (read <= 0) { 117630fb85f55cbd8df3005e652da3781f51294baf90ritchie break; 117730fb85f55cbd8df3005e652da3781f51294baf90ritchie } 117830fb85f55cbd8df3005e652da3781f51294baf90ritchie outputStream.write(buff, 0, read); 117930fb85f55cbd8df3005e652da3781f51294baf90ritchie pending -= read; 118030fb85f55cbd8df3005e652da3781f51294baf90ritchie } 118130fb85f55cbd8df3005e652da3781f51294baf90ritchie } 1182fbe92e0eb77710addafab0d48e7be5e147f17d00Paul Hawke } 1183fbe92e0eb77710addafab0d48e7be5e147f17d00Paul Hawke 118430fb85f55cbd8df3005e652da3781f51294baf90ritchie protected void sendConnectionHeaderIfNotAlreadyPresent(PrintWriter pw, Map<String, String> header) { 118530fb85f55cbd8df3005e652da3781f51294baf90ritchie if (!headerAlreadySent(header, "connection")) { 118630fb85f55cbd8df3005e652da3781f51294baf90ritchie pw.print("Connection: keep-alive\r\n"); 118730fb85f55cbd8df3005e652da3781f51294baf90ritchie } 118830fb85f55cbd8df3005e652da3781f51294baf90ritchie } 118930fb85f55cbd8df3005e652da3781f51294baf90ritchie 119030fb85f55cbd8df3005e652da3781f51294baf90ritchie protected int sendContentLengthHeaderIfNotAlreadyPresent(PrintWriter pw, Map<String, String> header, int size) { 119130fb85f55cbd8df3005e652da3781f51294baf90ritchie for (String headerName : header.keySet()) { 119230fb85f55cbd8df3005e652da3781f51294baf90ritchie if (headerName.equalsIgnoreCase("content-length")) { 119330fb85f55cbd8df3005e652da3781f51294baf90ritchie try { 119430fb85f55cbd8df3005e652da3781f51294baf90ritchie return Integer.parseInt(header.get(headerName)); 119530fb85f55cbd8df3005e652da3781f51294baf90ritchie } catch (NumberFormatException ex) { 119630fb85f55cbd8df3005e652da3781f51294baf90ritchie return size; 119730fb85f55cbd8df3005e652da3781f51294baf90ritchie } 119830fb85f55cbd8df3005e652da3781f51294baf90ritchie } 119930fb85f55cbd8df3005e652da3781f51294baf90ritchie } 120030fb85f55cbd8df3005e652da3781f51294baf90ritchie 120130fb85f55cbd8df3005e652da3781f51294baf90ritchie pw.print("Content-Length: " + size + "\r\n"); 120230fb85f55cbd8df3005e652da3781f51294baf90ritchie return size; 120330fb85f55cbd8df3005e652da3781f51294baf90ritchie } 120430fb85f55cbd8df3005e652da3781f51294baf90ritchie 120530fb85f55cbd8df3005e652da3781f51294baf90ritchie public void setChunkedTransfer(boolean chunkedTransfer) { 120630fb85f55cbd8df3005e652da3781f51294baf90ritchie this.chunkedTransfer = chunkedTransfer; 120730fb85f55cbd8df3005e652da3781f51294baf90ritchie } 120830fb85f55cbd8df3005e652da3781f51294baf90ritchie 120930fb85f55cbd8df3005e652da3781f51294baf90ritchie public void setData(InputStream data) { 121030fb85f55cbd8df3005e652da3781f51294baf90ritchie this.data = data; 121130fb85f55cbd8df3005e652da3781f51294baf90ritchie } 121230fb85f55cbd8df3005e652da3781f51294baf90ritchie 121330fb85f55cbd8df3005e652da3781f51294baf90ritchie public void setMimeType(String mimeType) { 121430fb85f55cbd8df3005e652da3781f51294baf90ritchie this.mimeType = mimeType; 121530fb85f55cbd8df3005e652da3781f51294baf90ritchie } 121630fb85f55cbd8df3005e652da3781f51294baf90ritchie 121730fb85f55cbd8df3005e652da3781f51294baf90ritchie public void setRequestMethod(Method requestMethod) { 121830fb85f55cbd8df3005e652da3781f51294baf90ritchie this.requestMethod = requestMethod; 121930fb85f55cbd8df3005e652da3781f51294baf90ritchie } 122030fb85f55cbd8df3005e652da3781f51294baf90ritchie 122130fb85f55cbd8df3005e652da3781f51294baf90ritchie public void setStatus(IStatus status) { 122230fb85f55cbd8df3005e652da3781f51294baf90ritchie this.status = status; 122330fb85f55cbd8df3005e652da3781f51294baf90ritchie } 122430fb85f55cbd8df3005e652da3781f51294baf90ritchie } 122530fb85f55cbd8df3005e652da3781f51294baf90ritchie 122630fb85f55cbd8df3005e652da3781f51294baf90ritchie public static final class ResponseException extends Exception { 122730fb85f55cbd8df3005e652da3781f51294baf90ritchie 122830fb85f55cbd8df3005e652da3781f51294baf90ritchie private static final long serialVersionUID = 6569838532917408380L; 122930fb85f55cbd8df3005e652da3781f51294baf90ritchie 123030fb85f55cbd8df3005e652da3781f51294baf90ritchie private final Response.Status status; 123130fb85f55cbd8df3005e652da3781f51294baf90ritchie 123230fb85f55cbd8df3005e652da3781f51294baf90ritchie public ResponseException(Response.Status status, String message) { 123330fb85f55cbd8df3005e652da3781f51294baf90ritchie super(message); 123430fb85f55cbd8df3005e652da3781f51294baf90ritchie this.status = status; 123530fb85f55cbd8df3005e652da3781f51294baf90ritchie } 123630fb85f55cbd8df3005e652da3781f51294baf90ritchie 123730fb85f55cbd8df3005e652da3781f51294baf90ritchie public ResponseException(Response.Status status, String message, Exception e) { 123830fb85f55cbd8df3005e652da3781f51294baf90ritchie super(message, e); 123930fb85f55cbd8df3005e652da3781f51294baf90ritchie this.status = status; 124030fb85f55cbd8df3005e652da3781f51294baf90ritchie } 124130fb85f55cbd8df3005e652da3781f51294baf90ritchie 124230fb85f55cbd8df3005e652da3781f51294baf90ritchie public Response.Status getStatus() { 124330fb85f55cbd8df3005e652da3781f51294baf90ritchie return this.status; 124430fb85f55cbd8df3005e652da3781f51294baf90ritchie } 124530fb85f55cbd8df3005e652da3781f51294baf90ritchie } 124630fb85f55cbd8df3005e652da3781f51294baf90ritchie 124730fb85f55cbd8df3005e652da3781f51294baf90ritchie /** 124830fb85f55cbd8df3005e652da3781f51294baf90ritchie * A temp file. 124930fb85f55cbd8df3005e652da3781f51294baf90ritchie * <p/> 125030fb85f55cbd8df3005e652da3781f51294baf90ritchie * <p> 125130fb85f55cbd8df3005e652da3781f51294baf90ritchie * Temp files are responsible for managing the actual temporary storage and 125230fb85f55cbd8df3005e652da3781f51294baf90ritchie * cleaning themselves up when no longer needed. 125330fb85f55cbd8df3005e652da3781f51294baf90ritchie * </p> 125430fb85f55cbd8df3005e652da3781f51294baf90ritchie */ 125530fb85f55cbd8df3005e652da3781f51294baf90ritchie public interface TempFile { 125630fb85f55cbd8df3005e652da3781f51294baf90ritchie 125730fb85f55cbd8df3005e652da3781f51294baf90ritchie void delete() throws Exception; 125830fb85f55cbd8df3005e652da3781f51294baf90ritchie 125930fb85f55cbd8df3005e652da3781f51294baf90ritchie String getName(); 126030fb85f55cbd8df3005e652da3781f51294baf90ritchie 126130fb85f55cbd8df3005e652da3781f51294baf90ritchie OutputStream open() throws Exception; 126230fb85f55cbd8df3005e652da3781f51294baf90ritchie } 126330fb85f55cbd8df3005e652da3781f51294baf90ritchie 126430fb85f55cbd8df3005e652da3781f51294baf90ritchie /** 126530fb85f55cbd8df3005e652da3781f51294baf90ritchie * Temp file manager. 126630fb85f55cbd8df3005e652da3781f51294baf90ritchie * <p/> 126730fb85f55cbd8df3005e652da3781f51294baf90ritchie * <p> 126830fb85f55cbd8df3005e652da3781f51294baf90ritchie * Temp file managers are created 1-to-1 with incoming requests, to create 126930fb85f55cbd8df3005e652da3781f51294baf90ritchie * and cleanup temporary files created as a result of handling the request. 127030fb85f55cbd8df3005e652da3781f51294baf90ritchie * </p> 127130fb85f55cbd8df3005e652da3781f51294baf90ritchie */ 127230fb85f55cbd8df3005e652da3781f51294baf90ritchie public interface TempFileManager { 127330fb85f55cbd8df3005e652da3781f51294baf90ritchie 127430fb85f55cbd8df3005e652da3781f51294baf90ritchie void clear(); 127530fb85f55cbd8df3005e652da3781f51294baf90ritchie 127630fb85f55cbd8df3005e652da3781f51294baf90ritchie TempFile createTempFile() throws Exception; 127730fb85f55cbd8df3005e652da3781f51294baf90ritchie } 127830fb85f55cbd8df3005e652da3781f51294baf90ritchie 127930fb85f55cbd8df3005e652da3781f51294baf90ritchie /** 128030fb85f55cbd8df3005e652da3781f51294baf90ritchie * Factory to create temp file managers. 128130fb85f55cbd8df3005e652da3781f51294baf90ritchie */ 128230fb85f55cbd8df3005e652da3781f51294baf90ritchie public interface TempFileManagerFactory { 128330fb85f55cbd8df3005e652da3781f51294baf90ritchie 128430fb85f55cbd8df3005e652da3781f51294baf90ritchie TempFileManager create(); 128530fb85f55cbd8df3005e652da3781f51294baf90ritchie } 128630fb85f55cbd8df3005e652da3781f51294baf90ritchie 128730fb85f55cbd8df3005e652da3781f51294baf90ritchie /** 128830fb85f55cbd8df3005e652da3781f51294baf90ritchie * Maximum time to wait on Socket.getInputStream().read() (in milliseconds) 128930fb85f55cbd8df3005e652da3781f51294baf90ritchie * This is required as the Keep-Alive HTTP connections would otherwise block 129030fb85f55cbd8df3005e652da3781f51294baf90ritchie * the socket reading thread forever (or as long the browser is open). 129130fb85f55cbd8df3005e652da3781f51294baf90ritchie */ 129230fb85f55cbd8df3005e652da3781f51294baf90ritchie public static final int SOCKET_READ_TIMEOUT = 5000; 129330fb85f55cbd8df3005e652da3781f51294baf90ritchie 129430fb85f55cbd8df3005e652da3781f51294baf90ritchie /** 129530fb85f55cbd8df3005e652da3781f51294baf90ritchie * Common MIME type for dynamic content: plain text 129630fb85f55cbd8df3005e652da3781f51294baf90ritchie */ 129730fb85f55cbd8df3005e652da3781f51294baf90ritchie public static final String MIME_PLAINTEXT = "text/plain"; 129830fb85f55cbd8df3005e652da3781f51294baf90ritchie 129930fb85f55cbd8df3005e652da3781f51294baf90ritchie /** 130030fb85f55cbd8df3005e652da3781f51294baf90ritchie * Common MIME type for dynamic content: html 130130fb85f55cbd8df3005e652da3781f51294baf90ritchie */ 130230fb85f55cbd8df3005e652da3781f51294baf90ritchie public static final String MIME_HTML = "text/html"; 130330fb85f55cbd8df3005e652da3781f51294baf90ritchie 130430fb85f55cbd8df3005e652da3781f51294baf90ritchie /** 130530fb85f55cbd8df3005e652da3781f51294baf90ritchie * Pseudo-Parameter to use to store the actual query string in the 130630fb85f55cbd8df3005e652da3781f51294baf90ritchie * parameters map for later re-processing. 130730fb85f55cbd8df3005e652da3781f51294baf90ritchie */ 130830fb85f55cbd8df3005e652da3781f51294baf90ritchie private static final String QUERY_STRING_PARAMETER = "NanoHttpd.QUERY_STRING"; 130930fb85f55cbd8df3005e652da3781f51294baf90ritchie 131030fb85f55cbd8df3005e652da3781f51294baf90ritchie /** 131130fb85f55cbd8df3005e652da3781f51294baf90ritchie * logger to log to. 131230fb85f55cbd8df3005e652da3781f51294baf90ritchie */ 131330fb85f55cbd8df3005e652da3781f51294baf90ritchie private static Logger LOG = Logger.getLogger(NanoHTTPD.class.getName()); 131430fb85f55cbd8df3005e652da3781f51294baf90ritchie 131530fb85f55cbd8df3005e652da3781f51294baf90ritchie /** 131630fb85f55cbd8df3005e652da3781f51294baf90ritchie * Creates an SSLSocketFactory for HTTPS. Pass a loaded KeyStore and an 131730fb85f55cbd8df3005e652da3781f51294baf90ritchie * array of loaded KeyManagers. These objects must properly 131830fb85f55cbd8df3005e652da3781f51294baf90ritchie * loaded/initialized by the caller. 131930fb85f55cbd8df3005e652da3781f51294baf90ritchie */ 132030fb85f55cbd8df3005e652da3781f51294baf90ritchie public static SSLServerSocketFactory makeSSLSocketFactory(KeyStore loadedKeyStore, KeyManager[] keyManagers) throws IOException { 132130fb85f55cbd8df3005e652da3781f51294baf90ritchie SSLServerSocketFactory res = null; 132230fb85f55cbd8df3005e652da3781f51294baf90ritchie try { 132330fb85f55cbd8df3005e652da3781f51294baf90ritchie TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); 132430fb85f55cbd8df3005e652da3781f51294baf90ritchie trustManagerFactory.init(loadedKeyStore); 132530fb85f55cbd8df3005e652da3781f51294baf90ritchie SSLContext ctx = SSLContext.getInstance("TLS"); 132630fb85f55cbd8df3005e652da3781f51294baf90ritchie ctx.init(keyManagers, trustManagerFactory.getTrustManagers(), null); 132730fb85f55cbd8df3005e652da3781f51294baf90ritchie res = ctx.getServerSocketFactory(); 132830fb85f55cbd8df3005e652da3781f51294baf90ritchie } catch (Exception e) { 132930fb85f55cbd8df3005e652da3781f51294baf90ritchie throw new IOException(e.getMessage()); 133030fb85f55cbd8df3005e652da3781f51294baf90ritchie } 133130fb85f55cbd8df3005e652da3781f51294baf90ritchie return res; 133230fb85f55cbd8df3005e652da3781f51294baf90ritchie } 133330fb85f55cbd8df3005e652da3781f51294baf90ritchie 133430fb85f55cbd8df3005e652da3781f51294baf90ritchie /** 133530fb85f55cbd8df3005e652da3781f51294baf90ritchie * Creates an SSLSocketFactory for HTTPS. Pass a loaded KeyStore and a 133630fb85f55cbd8df3005e652da3781f51294baf90ritchie * loaded KeyManagerFactory. These objects must properly loaded/initialized 133730fb85f55cbd8df3005e652da3781f51294baf90ritchie * by the caller. 133830fb85f55cbd8df3005e652da3781f51294baf90ritchie */ 133930fb85f55cbd8df3005e652da3781f51294baf90ritchie public static SSLServerSocketFactory makeSSLSocketFactory(KeyStore loadedKeyStore, KeyManagerFactory loadedKeyFactory) throws IOException { 134030fb85f55cbd8df3005e652da3781f51294baf90ritchie SSLServerSocketFactory res = null; 134130fb85f55cbd8df3005e652da3781f51294baf90ritchie try { 134230fb85f55cbd8df3005e652da3781f51294baf90ritchie TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); 134330fb85f55cbd8df3005e652da3781f51294baf90ritchie trustManagerFactory.init(loadedKeyStore); 134430fb85f55cbd8df3005e652da3781f51294baf90ritchie SSLContext ctx = SSLContext.getInstance("TLS"); 134530fb85f55cbd8df3005e652da3781f51294baf90ritchie ctx.init(loadedKeyFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null); 134630fb85f55cbd8df3005e652da3781f51294baf90ritchie res = ctx.getServerSocketFactory(); 134730fb85f55cbd8df3005e652da3781f51294baf90ritchie } catch (Exception e) { 134830fb85f55cbd8df3005e652da3781f51294baf90ritchie throw new IOException(e.getMessage()); 134930fb85f55cbd8df3005e652da3781f51294baf90ritchie } 135030fb85f55cbd8df3005e652da3781f51294baf90ritchie return res; 135130fb85f55cbd8df3005e652da3781f51294baf90ritchie } 135230fb85f55cbd8df3005e652da3781f51294baf90ritchie 135330fb85f55cbd8df3005e652da3781f51294baf90ritchie /** 135430fb85f55cbd8df3005e652da3781f51294baf90ritchie * Creates an SSLSocketFactory for HTTPS. Pass a KeyStore resource with your 135530fb85f55cbd8df3005e652da3781f51294baf90ritchie * certificate and passphrase 135630fb85f55cbd8df3005e652da3781f51294baf90ritchie */ 135730fb85f55cbd8df3005e652da3781f51294baf90ritchie public static SSLServerSocketFactory makeSSLSocketFactory(String keyAndTrustStoreClasspathPath, char[] passphrase) throws IOException { 135830fb85f55cbd8df3005e652da3781f51294baf90ritchie SSLServerSocketFactory res = null; 135930fb85f55cbd8df3005e652da3781f51294baf90ritchie try { 136030fb85f55cbd8df3005e652da3781f51294baf90ritchie KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType()); 136130fb85f55cbd8df3005e652da3781f51294baf90ritchie InputStream keystoreStream = NanoHTTPD.class.getResourceAsStream(keyAndTrustStoreClasspathPath); 136230fb85f55cbd8df3005e652da3781f51294baf90ritchie keystore.load(keystoreStream, passphrase); 136330fb85f55cbd8df3005e652da3781f51294baf90ritchie TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); 136430fb85f55cbd8df3005e652da3781f51294baf90ritchie trustManagerFactory.init(keystore); 136530fb85f55cbd8df3005e652da3781f51294baf90ritchie KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); 136630fb85f55cbd8df3005e652da3781f51294baf90ritchie keyManagerFactory.init(keystore, passphrase); 136730fb85f55cbd8df3005e652da3781f51294baf90ritchie SSLContext ctx = SSLContext.getInstance("TLS"); 136830fb85f55cbd8df3005e652da3781f51294baf90ritchie ctx.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null); 136930fb85f55cbd8df3005e652da3781f51294baf90ritchie res = ctx.getServerSocketFactory(); 137030fb85f55cbd8df3005e652da3781f51294baf90ritchie } catch (Exception e) { 137130fb85f55cbd8df3005e652da3781f51294baf90ritchie throw new IOException(e.getMessage()); 137230fb85f55cbd8df3005e652da3781f51294baf90ritchie } 137330fb85f55cbd8df3005e652da3781f51294baf90ritchie return res; 137430fb85f55cbd8df3005e652da3781f51294baf90ritchie } 137530fb85f55cbd8df3005e652da3781f51294baf90ritchie 137630fb85f55cbd8df3005e652da3781f51294baf90ritchie private static final void safeClose(Closeable closeable) { 137730fb85f55cbd8df3005e652da3781f51294baf90ritchie if (closeable != null) { 137830fb85f55cbd8df3005e652da3781f51294baf90ritchie try { 137930fb85f55cbd8df3005e652da3781f51294baf90ritchie closeable.close(); 138030fb85f55cbd8df3005e652da3781f51294baf90ritchie } catch (IOException e) { 138130fb85f55cbd8df3005e652da3781f51294baf90ritchie NanoHTTPD.LOG.log(Level.SEVERE, "Could not close", e); 138230fb85f55cbd8df3005e652da3781f51294baf90ritchie } 138330fb85f55cbd8df3005e652da3781f51294baf90ritchie } 138430fb85f55cbd8df3005e652da3781f51294baf90ritchie } 138530fb85f55cbd8df3005e652da3781f51294baf90ritchie 138630fb85f55cbd8df3005e652da3781f51294baf90ritchie private final String hostname; 138730fb85f55cbd8df3005e652da3781f51294baf90ritchie 138830fb85f55cbd8df3005e652da3781f51294baf90ritchie private final int myPort; 138930fb85f55cbd8df3005e652da3781f51294baf90ritchie 139030fb85f55cbd8df3005e652da3781f51294baf90ritchie private ServerSocket myServerSocket; 139130fb85f55cbd8df3005e652da3781f51294baf90ritchie 139230fb85f55cbd8df3005e652da3781f51294baf90ritchie private final Set<Socket> openConnections = new HashSet<Socket>(); 139330fb85f55cbd8df3005e652da3781f51294baf90ritchie 139430fb85f55cbd8df3005e652da3781f51294baf90ritchie private SSLServerSocketFactory sslServerSocketFactory; 139530fb85f55cbd8df3005e652da3781f51294baf90ritchie 139630fb85f55cbd8df3005e652da3781f51294baf90ritchie private Thread myThread; 139730fb85f55cbd8df3005e652da3781f51294baf90ritchie 139830fb85f55cbd8df3005e652da3781f51294baf90ritchie /** 139930fb85f55cbd8df3005e652da3781f51294baf90ritchie * Pluggable strategy for asynchronously executing requests. 140030fb85f55cbd8df3005e652da3781f51294baf90ritchie */ 140130fb85f55cbd8df3005e652da3781f51294baf90ritchie private AsyncRunner asyncRunner; 140230fb85f55cbd8df3005e652da3781f51294baf90ritchie 140330fb85f55cbd8df3005e652da3781f51294baf90ritchie /** 140430fb85f55cbd8df3005e652da3781f51294baf90ritchie * Pluggable strategy for creating and cleaning up temporary files. 140530fb85f55cbd8df3005e652da3781f51294baf90ritchie */ 140630fb85f55cbd8df3005e652da3781f51294baf90ritchie private TempFileManagerFactory tempFileManagerFactory; 140730fb85f55cbd8df3005e652da3781f51294baf90ritchie 140830fb85f55cbd8df3005e652da3781f51294baf90ritchie /** 140930fb85f55cbd8df3005e652da3781f51294baf90ritchie * Constructs an HTTP server on given port. 141030fb85f55cbd8df3005e652da3781f51294baf90ritchie */ 141130fb85f55cbd8df3005e652da3781f51294baf90ritchie public NanoHTTPD(int port) { 141230fb85f55cbd8df3005e652da3781f51294baf90ritchie this(null, port); 141330fb85f55cbd8df3005e652da3781f51294baf90ritchie } 141430fb85f55cbd8df3005e652da3781f51294baf90ritchie 141530fb85f55cbd8df3005e652da3781f51294baf90ritchie // ------------------------------------------------------------------------------- 141630fb85f55cbd8df3005e652da3781f51294baf90ritchie // // 141730fb85f55cbd8df3005e652da3781f51294baf90ritchie // 141830fb85f55cbd8df3005e652da3781f51294baf90ritchie // Threading Strategy. 141930fb85f55cbd8df3005e652da3781f51294baf90ritchie // 142030fb85f55cbd8df3005e652da3781f51294baf90ritchie // ------------------------------------------------------------------------------- 142130fb85f55cbd8df3005e652da3781f51294baf90ritchie // // 142230fb85f55cbd8df3005e652da3781f51294baf90ritchie 142330fb85f55cbd8df3005e652da3781f51294baf90ritchie /** 142430fb85f55cbd8df3005e652da3781f51294baf90ritchie * Constructs an HTTP server on given hostname and port. 142530fb85f55cbd8df3005e652da3781f51294baf90ritchie */ 142630fb85f55cbd8df3005e652da3781f51294baf90ritchie public NanoHTTPD(String hostname, int port) { 142730fb85f55cbd8df3005e652da3781f51294baf90ritchie this.hostname = hostname; 142830fb85f55cbd8df3005e652da3781f51294baf90ritchie this.myPort = port; 142930fb85f55cbd8df3005e652da3781f51294baf90ritchie setTempFileManagerFactory(new DefaultTempFileManagerFactory()); 143030fb85f55cbd8df3005e652da3781f51294baf90ritchie setAsyncRunner(new DefaultAsyncRunner()); 143130fb85f55cbd8df3005e652da3781f51294baf90ritchie } 143230fb85f55cbd8df3005e652da3781f51294baf90ritchie 143330fb85f55cbd8df3005e652da3781f51294baf90ritchie // ------------------------------------------------------------------------------- 143430fb85f55cbd8df3005e652da3781f51294baf90ritchie // // 143530fb85f55cbd8df3005e652da3781f51294baf90ritchie // 143630fb85f55cbd8df3005e652da3781f51294baf90ritchie // Temp file handling strategy. 143730fb85f55cbd8df3005e652da3781f51294baf90ritchie // 143830fb85f55cbd8df3005e652da3781f51294baf90ritchie // ------------------------------------------------------------------------------- 143930fb85f55cbd8df3005e652da3781f51294baf90ritchie // // 144030fb85f55cbd8df3005e652da3781f51294baf90ritchie 144130fb85f55cbd8df3005e652da3781f51294baf90ritchie /** 144230fb85f55cbd8df3005e652da3781f51294baf90ritchie * Forcibly closes all connections that are open. 144330fb85f55cbd8df3005e652da3781f51294baf90ritchie */ 144430fb85f55cbd8df3005e652da3781f51294baf90ritchie public synchronized void closeAllConnections() { 144530fb85f55cbd8df3005e652da3781f51294baf90ritchie for (Socket socket : this.openConnections) { 144630fb85f55cbd8df3005e652da3781f51294baf90ritchie safeClose(socket); 144730fb85f55cbd8df3005e652da3781f51294baf90ritchie } 144830fb85f55cbd8df3005e652da3781f51294baf90ritchie } 144930fb85f55cbd8df3005e652da3781f51294baf90ritchie 145030fb85f55cbd8df3005e652da3781f51294baf90ritchie /** 145130fb85f55cbd8df3005e652da3781f51294baf90ritchie * Decode parameters from a URL, handing the case where a single parameter 145230fb85f55cbd8df3005e652da3781f51294baf90ritchie * name might have been supplied several times, by return lists of values. 145330fb85f55cbd8df3005e652da3781f51294baf90ritchie * In general these lists will contain a single element. 145430fb85f55cbd8df3005e652da3781f51294baf90ritchie * 145530fb85f55cbd8df3005e652da3781f51294baf90ritchie * @param parms 145630fb85f55cbd8df3005e652da3781f51294baf90ritchie * original <b>NanoHTTPD</b> parameters values, as passed to the 145730fb85f55cbd8df3005e652da3781f51294baf90ritchie * <code>serve()</code> method. 145830fb85f55cbd8df3005e652da3781f51294baf90ritchie * @return a map of <code>String</code> (parameter name) to 145930fb85f55cbd8df3005e652da3781f51294baf90ritchie * <code>List<String></code> (a list of the values supplied). 146030fb85f55cbd8df3005e652da3781f51294baf90ritchie */ 146130fb85f55cbd8df3005e652da3781f51294baf90ritchie protected Map<String, List<String>> decodeParameters(Map<String, String> parms) { 146230fb85f55cbd8df3005e652da3781f51294baf90ritchie return this.decodeParameters(parms.get(NanoHTTPD.QUERY_STRING_PARAMETER)); 146330fb85f55cbd8df3005e652da3781f51294baf90ritchie } 146430fb85f55cbd8df3005e652da3781f51294baf90ritchie 146530fb85f55cbd8df3005e652da3781f51294baf90ritchie /** 146630fb85f55cbd8df3005e652da3781f51294baf90ritchie * Decode parameters from a URL, handing the case where a single parameter 146730fb85f55cbd8df3005e652da3781f51294baf90ritchie * name might have been supplied several times, by return lists of values. 146830fb85f55cbd8df3005e652da3781f51294baf90ritchie * In general these lists will contain a single element. 146930fb85f55cbd8df3005e652da3781f51294baf90ritchie * 147030fb85f55cbd8df3005e652da3781f51294baf90ritchie * @param queryString 147130fb85f55cbd8df3005e652da3781f51294baf90ritchie * a query string pulled from the URL. 147230fb85f55cbd8df3005e652da3781f51294baf90ritchie * @return a map of <code>String</code> (parameter name) to 147330fb85f55cbd8df3005e652da3781f51294baf90ritchie * <code>List<String></code> (a list of the values supplied). 147430fb85f55cbd8df3005e652da3781f51294baf90ritchie */ 147530fb85f55cbd8df3005e652da3781f51294baf90ritchie protected Map<String, List<String>> decodeParameters(String queryString) { 147630fb85f55cbd8df3005e652da3781f51294baf90ritchie Map<String, List<String>> parms = new HashMap<String, List<String>>(); 147730fb85f55cbd8df3005e652da3781f51294baf90ritchie if (queryString != null) { 147830fb85f55cbd8df3005e652da3781f51294baf90ritchie StringTokenizer st = new StringTokenizer(queryString, "&"); 147930fb85f55cbd8df3005e652da3781f51294baf90ritchie while (st.hasMoreTokens()) { 148030fb85f55cbd8df3005e652da3781f51294baf90ritchie String e = st.nextToken(); 148130fb85f55cbd8df3005e652da3781f51294baf90ritchie int sep = e.indexOf('='); 148230fb85f55cbd8df3005e652da3781f51294baf90ritchie String propertyName = sep >= 0 ? decodePercent(e.substring(0, sep)).trim() : decodePercent(e).trim(); 148330fb85f55cbd8df3005e652da3781f51294baf90ritchie if (!parms.containsKey(propertyName)) { 148430fb85f55cbd8df3005e652da3781f51294baf90ritchie parms.put(propertyName, new ArrayList<String>()); 148530fb85f55cbd8df3005e652da3781f51294baf90ritchie } 148630fb85f55cbd8df3005e652da3781f51294baf90ritchie String propertyValue = sep >= 0 ? decodePercent(e.substring(sep + 1)) : null; 148730fb85f55cbd8df3005e652da3781f51294baf90ritchie if (propertyValue != null) { 148830fb85f55cbd8df3005e652da3781f51294baf90ritchie parms.get(propertyName).add(propertyValue); 148930fb85f55cbd8df3005e652da3781f51294baf90ritchie } 149030fb85f55cbd8df3005e652da3781f51294baf90ritchie } 149193441a0455c683a17622ec2da6768c326e6d7a48Martin M Reed } 149230fb85f55cbd8df3005e652da3781f51294baf90ritchie return parms; 149330fb85f55cbd8df3005e652da3781f51294baf90ritchie } 149493441a0455c683a17622ec2da6768c326e6d7a48Martin M Reed 149530fb85f55cbd8df3005e652da3781f51294baf90ritchie /** 149630fb85f55cbd8df3005e652da3781f51294baf90ritchie * Decode percent encoded <code>String</code> values. 149730fb85f55cbd8df3005e652da3781f51294baf90ritchie * 149830fb85f55cbd8df3005e652da3781f51294baf90ritchie * @param str 149930fb85f55cbd8df3005e652da3781f51294baf90ritchie * the percent encoded <code>String</code> 150030fb85f55cbd8df3005e652da3781f51294baf90ritchie * @return expanded form of the input, for example "foo%20bar" becomes 150130fb85f55cbd8df3005e652da3781f51294baf90ritchie * "foo bar" 150230fb85f55cbd8df3005e652da3781f51294baf90ritchie */ 150330fb85f55cbd8df3005e652da3781f51294baf90ritchie protected String decodePercent(String str) { 150430fb85f55cbd8df3005e652da3781f51294baf90ritchie String decoded = null; 150530fb85f55cbd8df3005e652da3781f51294baf90ritchie try { 150630fb85f55cbd8df3005e652da3781f51294baf90ritchie decoded = URLDecoder.decode(str, "UTF8"); 150730fb85f55cbd8df3005e652da3781f51294baf90ritchie } catch (UnsupportedEncodingException ignored) { 150830fb85f55cbd8df3005e652da3781f51294baf90ritchie NanoHTTPD.LOG.log(Level.WARNING, "Encoding not supported, ignored", ignored); 150993441a0455c683a17622ec2da6768c326e6d7a48Martin M Reed } 151030fb85f55cbd8df3005e652da3781f51294baf90ritchie return decoded; 151130fb85f55cbd8df3005e652da3781f51294baf90ritchie } 151293441a0455c683a17622ec2da6768c326e6d7a48Martin M Reed 151330fb85f55cbd8df3005e652da3781f51294baf90ritchie // ------------------------------------------------------------------------------- 151430fb85f55cbd8df3005e652da3781f51294baf90ritchie // // 15158cdc631dca8d05f64ea8da466ecfe759a2f6e429Martin M Reed 151630fb85f55cbd8df3005e652da3781f51294baf90ritchie public final int getListeningPort() { 151730fb85f55cbd8df3005e652da3781f51294baf90ritchie return this.myServerSocket == null ? -1 : this.myServerSocket.getLocalPort(); 151830fb85f55cbd8df3005e652da3781f51294baf90ritchie } 1519964393fd7e5ed49088882a126cf82507184467efMartin M Reed 152030fb85f55cbd8df3005e652da3781f51294baf90ritchie public final boolean isAlive() { 152130fb85f55cbd8df3005e652da3781f51294baf90ritchie return wasStarted() && !this.myServerSocket.isClosed() && this.myThread.isAlive(); 15228dd183554e56783ecf7cd6b8529cf807b9eb813cPaul Hawke } 1523964393fd7e5ed49088882a126cf82507184467efMartin M Reed 152430fb85f55cbd8df3005e652da3781f51294baf90ritchie /** 152530fb85f55cbd8df3005e652da3781f51294baf90ritchie * Call before start() to serve over HTTPS instead of HTTP 152630fb85f55cbd8df3005e652da3781f51294baf90ritchie */ 152730fb85f55cbd8df3005e652da3781f51294baf90ritchie public void makeSecure(SSLServerSocketFactory sslServerSocketFactory) { 152830fb85f55cbd8df3005e652da3781f51294baf90ritchie this.sslServerSocketFactory = sslServerSocketFactory; 152930fb85f55cbd8df3005e652da3781f51294baf90ritchie } 15309058464950a9734da0a7ff2dc47f3081bbb5117critchie 153130fb85f55cbd8df3005e652da3781f51294baf90ritchie /** 153230fb85f55cbd8df3005e652da3781f51294baf90ritchie * Registers that a new connection has been set up. 153330fb85f55cbd8df3005e652da3781f51294baf90ritchie * 153430fb85f55cbd8df3005e652da3781f51294baf90ritchie * @param socket 153530fb85f55cbd8df3005e652da3781f51294baf90ritchie * the {@link Socket} for the connection. 153630fb85f55cbd8df3005e652da3781f51294baf90ritchie */ 153730fb85f55cbd8df3005e652da3781f51294baf90ritchie public synchronized void registerConnection(Socket socket) { 153830fb85f55cbd8df3005e652da3781f51294baf90ritchie this.openConnections.add(socket); 153930fb85f55cbd8df3005e652da3781f51294baf90ritchie } 15405820f308631a825a05feddddd0aafb962f272b9bPaul Hawke 154130fb85f55cbd8df3005e652da3781f51294baf90ritchie /** 154230fb85f55cbd8df3005e652da3781f51294baf90ritchie * Override this to customize the server. 154330fb85f55cbd8df3005e652da3781f51294baf90ritchie * <p/> 154430fb85f55cbd8df3005e652da3781f51294baf90ritchie * <p/> 154530fb85f55cbd8df3005e652da3781f51294baf90ritchie * (By default, this returns a 404 "Not Found" plain text error response.) 154630fb85f55cbd8df3005e652da3781f51294baf90ritchie * 154730fb85f55cbd8df3005e652da3781f51294baf90ritchie * @param session 154830fb85f55cbd8df3005e652da3781f51294baf90ritchie * The HTTP session 154930fb85f55cbd8df3005e652da3781f51294baf90ritchie * @return HTTP response, see class Response for details 155030fb85f55cbd8df3005e652da3781f51294baf90ritchie */ 155130fb85f55cbd8df3005e652da3781f51294baf90ritchie public Response serve(IHTTPSession session) { 155230fb85f55cbd8df3005e652da3781f51294baf90ritchie Map<String, String> files = new HashMap<String, String>(); 155330fb85f55cbd8df3005e652da3781f51294baf90ritchie Method method = session.getMethod(); 155430fb85f55cbd8df3005e652da3781f51294baf90ritchie if (Method.PUT.equals(method) || Method.POST.equals(method)) { 155530fb85f55cbd8df3005e652da3781f51294baf90ritchie try { 155630fb85f55cbd8df3005e652da3781f51294baf90ritchie session.parseBody(files); 155730fb85f55cbd8df3005e652da3781f51294baf90ritchie } catch (IOException ioe) { 155830fb85f55cbd8df3005e652da3781f51294baf90ritchie return new Response(Response.Status.INTERNAL_ERROR, NanoHTTPD.MIME_PLAINTEXT, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage()); 155930fb85f55cbd8df3005e652da3781f51294baf90ritchie } catch (ResponseException re) { 156030fb85f55cbd8df3005e652da3781f51294baf90ritchie return new Response(re.getStatus(), NanoHTTPD.MIME_PLAINTEXT, re.getMessage()); 156130fb85f55cbd8df3005e652da3781f51294baf90ritchie } 15625820f308631a825a05feddddd0aafb962f272b9bPaul Hawke } 15635820f308631a825a05feddddd0aafb962f272b9bPaul Hawke 156430fb85f55cbd8df3005e652da3781f51294baf90ritchie Map<String, String> parms = session.getParms(); 156530fb85f55cbd8df3005e652da3781f51294baf90ritchie parms.put(NanoHTTPD.QUERY_STRING_PARAMETER, session.getQueryParameterString()); 156630fb85f55cbd8df3005e652da3781f51294baf90ritchie return serve(session.getUri(), method, session.getHeaders(), parms, files); 156730fb85f55cbd8df3005e652da3781f51294baf90ritchie } 15685820f308631a825a05feddddd0aafb962f272b9bPaul Hawke 156930fb85f55cbd8df3005e652da3781f51294baf90ritchie /** 157030fb85f55cbd8df3005e652da3781f51294baf90ritchie * Override this to customize the server. 157130fb85f55cbd8df3005e652da3781f51294baf90ritchie * <p/> 157230fb85f55cbd8df3005e652da3781f51294baf90ritchie * <p/> 157330fb85f55cbd8df3005e652da3781f51294baf90ritchie * (By default, this returns a 404 "Not Found" plain text error response.) 157430fb85f55cbd8df3005e652da3781f51294baf90ritchie * 157530fb85f55cbd8df3005e652da3781f51294baf90ritchie * @param uri 157630fb85f55cbd8df3005e652da3781f51294baf90ritchie * Percent-decoded URI without parameters, for example 157730fb85f55cbd8df3005e652da3781f51294baf90ritchie * "/index.cgi" 157830fb85f55cbd8df3005e652da3781f51294baf90ritchie * @param method 157930fb85f55cbd8df3005e652da3781f51294baf90ritchie * "GET", "POST" etc. 158030fb85f55cbd8df3005e652da3781f51294baf90ritchie * @param parms 158130fb85f55cbd8df3005e652da3781f51294baf90ritchie * Parsed, percent decoded parameters from URI and, in case of 158230fb85f55cbd8df3005e652da3781f51294baf90ritchie * POST, data. 158330fb85f55cbd8df3005e652da3781f51294baf90ritchie * @param headers 158430fb85f55cbd8df3005e652da3781f51294baf90ritchie * Header entries, percent decoded 158530fb85f55cbd8df3005e652da3781f51294baf90ritchie * @return HTTP response, see class Response for details 158630fb85f55cbd8df3005e652da3781f51294baf90ritchie */ 158730fb85f55cbd8df3005e652da3781f51294baf90ritchie @Deprecated 158830fb85f55cbd8df3005e652da3781f51294baf90ritchie public Response serve(String uri, Method method, Map<String, String> headers, Map<String, String> parms, Map<String, String> files) { 158930fb85f55cbd8df3005e652da3781f51294baf90ritchie return new Response(Response.Status.NOT_FOUND, NanoHTTPD.MIME_PLAINTEXT, "Not Found"); 159030fb85f55cbd8df3005e652da3781f51294baf90ritchie } 15915820f308631a825a05feddddd0aafb962f272b9bPaul Hawke 159230fb85f55cbd8df3005e652da3781f51294baf90ritchie /** 159330fb85f55cbd8df3005e652da3781f51294baf90ritchie * Pluggable strategy for asynchronously executing requests. 159430fb85f55cbd8df3005e652da3781f51294baf90ritchie * 159530fb85f55cbd8df3005e652da3781f51294baf90ritchie * @param asyncRunner 159630fb85f55cbd8df3005e652da3781f51294baf90ritchie * new strategy for handling threads. 159730fb85f55cbd8df3005e652da3781f51294baf90ritchie */ 159830fb85f55cbd8df3005e652da3781f51294baf90ritchie public void setAsyncRunner(AsyncRunner asyncRunner) { 159930fb85f55cbd8df3005e652da3781f51294baf90ritchie this.asyncRunner = asyncRunner; 160030fb85f55cbd8df3005e652da3781f51294baf90ritchie } 16015820f308631a825a05feddddd0aafb962f272b9bPaul Hawke 160230fb85f55cbd8df3005e652da3781f51294baf90ritchie /** 160330fb85f55cbd8df3005e652da3781f51294baf90ritchie * Pluggable strategy for creating and cleaning up temporary files. 160430fb85f55cbd8df3005e652da3781f51294baf90ritchie * 160530fb85f55cbd8df3005e652da3781f51294baf90ritchie * @param tempFileManagerFactory 160630fb85f55cbd8df3005e652da3781f51294baf90ritchie * new strategy for handling temp files. 160730fb85f55cbd8df3005e652da3781f51294baf90ritchie */ 160830fb85f55cbd8df3005e652da3781f51294baf90ritchie public void setTempFileManagerFactory(TempFileManagerFactory tempFileManagerFactory) { 160930fb85f55cbd8df3005e652da3781f51294baf90ritchie this.tempFileManagerFactory = tempFileManagerFactory; 16105820f308631a825a05feddddd0aafb962f272b9bPaul Hawke } 16115820f308631a825a05feddddd0aafb962f272b9bPaul Hawke 16128dd183554e56783ecf7cd6b8529cf807b9eb813cPaul Hawke /** 161330fb85f55cbd8df3005e652da3781f51294baf90ritchie * Start the server. 16149058464950a9734da0a7ff2dc47f3081bbb5117critchie * 161530fb85f55cbd8df3005e652da3781f51294baf90ritchie * @throws IOException 161630fb85f55cbd8df3005e652da3781f51294baf90ritchie * if the socket is in use. 16178dd183554e56783ecf7cd6b8529cf807b9eb813cPaul Hawke */ 161830fb85f55cbd8df3005e652da3781f51294baf90ritchie public void start() throws IOException { 161930fb85f55cbd8df3005e652da3781f51294baf90ritchie if (this.sslServerSocketFactory != null) { 162030fb85f55cbd8df3005e652da3781f51294baf90ritchie SSLServerSocket ss = (SSLServerSocket) this.sslServerSocketFactory.createServerSocket(); 162130fb85f55cbd8df3005e652da3781f51294baf90ritchie ss.setNeedClientAuth(false); 162230fb85f55cbd8df3005e652da3781f51294baf90ritchie this.myServerSocket = ss; 162330fb85f55cbd8df3005e652da3781f51294baf90ritchie } else { 162430fb85f55cbd8df3005e652da3781f51294baf90ritchie this.myServerSocket = new ServerSocket(); 162530fb85f55cbd8df3005e652da3781f51294baf90ritchie } 162630fb85f55cbd8df3005e652da3781f51294baf90ritchie this.myServerSocket.setReuseAddress(true); 162730fb85f55cbd8df3005e652da3781f51294baf90ritchie this.myServerSocket.bind(this.hostname != null ? new InetSocketAddress(this.hostname, this.myPort) : new InetSocketAddress(this.myPort)); 16289058464950a9734da0a7ff2dc47f3081bbb5117critchie 162930fb85f55cbd8df3005e652da3781f51294baf90ritchie this.myThread = new Thread(new Runnable() { 16309058464950a9734da0a7ff2dc47f3081bbb5117critchie 163130fb85f55cbd8df3005e652da3781f51294baf90ritchie @Override 163230fb85f55cbd8df3005e652da3781f51294baf90ritchie public void run() { 163330fb85f55cbd8df3005e652da3781f51294baf90ritchie do { 163430fb85f55cbd8df3005e652da3781f51294baf90ritchie try { 163530fb85f55cbd8df3005e652da3781f51294baf90ritchie final Socket finalAccept = NanoHTTPD.this.myServerSocket.accept(); 163630fb85f55cbd8df3005e652da3781f51294baf90ritchie registerConnection(finalAccept); 163730fb85f55cbd8df3005e652da3781f51294baf90ritchie finalAccept.setSoTimeout(NanoHTTPD.SOCKET_READ_TIMEOUT); 163830fb85f55cbd8df3005e652da3781f51294baf90ritchie final InputStream inputStream = finalAccept.getInputStream(); 163930fb85f55cbd8df3005e652da3781f51294baf90ritchie NanoHTTPD.this.asyncRunner.exec(new Runnable() { 16408dd183554e56783ecf7cd6b8529cf807b9eb813cPaul Hawke 164130fb85f55cbd8df3005e652da3781f51294baf90ritchie @Override 164230fb85f55cbd8df3005e652da3781f51294baf90ritchie public void run() { 164330fb85f55cbd8df3005e652da3781f51294baf90ritchie OutputStream outputStream = null; 164430fb85f55cbd8df3005e652da3781f51294baf90ritchie try { 164530fb85f55cbd8df3005e652da3781f51294baf90ritchie outputStream = finalAccept.getOutputStream(); 164630fb85f55cbd8df3005e652da3781f51294baf90ritchie TempFileManager tempFileManager = NanoHTTPD.this.tempFileManagerFactory.create(); 164730fb85f55cbd8df3005e652da3781f51294baf90ritchie HTTPSession session = new HTTPSession(tempFileManager, inputStream, outputStream, finalAccept.getInetAddress()); 164830fb85f55cbd8df3005e652da3781f51294baf90ritchie while (!finalAccept.isClosed()) { 164930fb85f55cbd8df3005e652da3781f51294baf90ritchie session.execute(); 165030fb85f55cbd8df3005e652da3781f51294baf90ritchie } 165130fb85f55cbd8df3005e652da3781f51294baf90ritchie } catch (Exception e) { 165230fb85f55cbd8df3005e652da3781f51294baf90ritchie // When the socket is closed by the client, 165330fb85f55cbd8df3005e652da3781f51294baf90ritchie // we throw our own SocketException 165430fb85f55cbd8df3005e652da3781f51294baf90ritchie // to break the "keep alive" loop above. If 165530fb85f55cbd8df3005e652da3781f51294baf90ritchie // the exception was anything other 165630fb85f55cbd8df3005e652da3781f51294baf90ritchie // than the expected SocketException OR a 165730fb85f55cbd8df3005e652da3781f51294baf90ritchie // SocketTimeoutException, print the 165830fb85f55cbd8df3005e652da3781f51294baf90ritchie // stacktrace 165930fb85f55cbd8df3005e652da3781f51294baf90ritchie if (!(e instanceof SocketException && "NanoHttpd Shutdown".equals(e.getMessage())) && !(e instanceof SocketTimeoutException)) { 166030fb85f55cbd8df3005e652da3781f51294baf90ritchie NanoHTTPD.LOG.log(Level.FINE, "Communication with the client broken", e); 166130fb85f55cbd8df3005e652da3781f51294baf90ritchie } 166230fb85f55cbd8df3005e652da3781f51294baf90ritchie } finally { 166330fb85f55cbd8df3005e652da3781f51294baf90ritchie safeClose(outputStream); 166430fb85f55cbd8df3005e652da3781f51294baf90ritchie safeClose(inputStream); 166530fb85f55cbd8df3005e652da3781f51294baf90ritchie safeClose(finalAccept); 166630fb85f55cbd8df3005e652da3781f51294baf90ritchie unRegisterConnection(finalAccept); 166730fb85f55cbd8df3005e652da3781f51294baf90ritchie } 166830fb85f55cbd8df3005e652da3781f51294baf90ritchie } 166930fb85f55cbd8df3005e652da3781f51294baf90ritchie }); 167030fb85f55cbd8df3005e652da3781f51294baf90ritchie } catch (IOException e) { 167130fb85f55cbd8df3005e652da3781f51294baf90ritchie NanoHTTPD.LOG.log(Level.FINE, "Communication with the client broken", e); 16728dd183554e56783ecf7cd6b8529cf807b9eb813cPaul Hawke } 167330fb85f55cbd8df3005e652da3781f51294baf90ritchie } while (!NanoHTTPD.this.myServerSocket.isClosed()); 16748dd183554e56783ecf7cd6b8529cf807b9eb813cPaul Hawke } 167530fb85f55cbd8df3005e652da3781f51294baf90ritchie }); 167630fb85f55cbd8df3005e652da3781f51294baf90ritchie this.myThread.setDaemon(true); 167730fb85f55cbd8df3005e652da3781f51294baf90ritchie this.myThread.setName("NanoHttpd Main Listener"); 167830fb85f55cbd8df3005e652da3781f51294baf90ritchie this.myThread.start(); 167930fb85f55cbd8df3005e652da3781f51294baf90ritchie } 16805820f308631a825a05feddddd0aafb962f272b9bPaul Hawke 168130fb85f55cbd8df3005e652da3781f51294baf90ritchie /** 168230fb85f55cbd8df3005e652da3781f51294baf90ritchie * Stop the server. 168330fb85f55cbd8df3005e652da3781f51294baf90ritchie */ 168430fb85f55cbd8df3005e652da3781f51294baf90ritchie public void stop() { 168530fb85f55cbd8df3005e652da3781f51294baf90ritchie try { 168630fb85f55cbd8df3005e652da3781f51294baf90ritchie safeClose(this.myServerSocket); 168730fb85f55cbd8df3005e652da3781f51294baf90ritchie closeAllConnections(); 168830fb85f55cbd8df3005e652da3781f51294baf90ritchie if (this.myThread != null) { 168930fb85f55cbd8df3005e652da3781f51294baf90ritchie this.myThread.join(); 169030fb85f55cbd8df3005e652da3781f51294baf90ritchie } 169130fb85f55cbd8df3005e652da3781f51294baf90ritchie } catch (Exception e) { 169230fb85f55cbd8df3005e652da3781f51294baf90ritchie NanoHTTPD.LOG.log(Level.SEVERE, "Could not stop all connections", e); 1693964393fd7e5ed49088882a126cf82507184467efMartin M Reed } 169430fb85f55cbd8df3005e652da3781f51294baf90ritchie } 1695964393fd7e5ed49088882a126cf82507184467efMartin M Reed 169630fb85f55cbd8df3005e652da3781f51294baf90ritchie /** 169730fb85f55cbd8df3005e652da3781f51294baf90ritchie * Registers that a connection has been closed 169830fb85f55cbd8df3005e652da3781f51294baf90ritchie * 169930fb85f55cbd8df3005e652da3781f51294baf90ritchie * @param socket 170030fb85f55cbd8df3005e652da3781f51294baf90ritchie * the {@link Socket} for the connection. 170130fb85f55cbd8df3005e652da3781f51294baf90ritchie */ 170230fb85f55cbd8df3005e652da3781f51294baf90ritchie public synchronized void unRegisterConnection(Socket socket) { 170330fb85f55cbd8df3005e652da3781f51294baf90ritchie this.openConnections.remove(socket); 170430fb85f55cbd8df3005e652da3781f51294baf90ritchie } 17059cd5d3b4438667394ab98895c75b4e1a2f8e76a0Martin M Reed 170630fb85f55cbd8df3005e652da3781f51294baf90ritchie public final boolean wasStarted() { 170730fb85f55cbd8df3005e652da3781f51294baf90ritchie return this.myServerSocket != null && this.myThread != null; 17087e423dde0fe863fadbd363de8a94746a30fca0eeMartin M Reed } 1709269baff79590de5abd70e8e05bf46547e4a28ee6Micah Hainline and Paul Hawke} 1710