summaryrefslogblamecommitdiffstats
path: root/src/main/java/fi/iki/elonen/NanoHTTPD.java
blob: 19508c78ca5318d50b044aefb253a5c4e3b6502f (plain) (tree)




































                                                                                     
                                     




                                 
                                   







                                            

                                         
                           




                         
                           

                                 


                                                       

                                     



                                                  
















































                                                                                                     
 




                                                                                    
                                                            








                                                                 

                                                                          











                                                                                      

                                         

















                                                                
                                                                 



















                                                               

                                                                                                             
















                                                                                                     


                                                                                                                             




                                                                                                                                    
                                                                                                      
                                                                                                                 















































































                                                                                            

                                                                         
                                           

                                                                                   


























                                                                                                                 

                                                                                                            













                                                                                                       

                                                                              











                                                                   



                                                                                    


                   

                                                                                 








                                                                                                      



                                                                                    














                                                                                                      

                                                                                                                                   
































                                                                                                                     





                        

























                                                                                             
                              



                                                                     

                                                                                                         




                                                 
                                                     
                                                                  


































                                                                                   

                                                                                       










































                                                                                                                     




                                                                                                       


                                                      
                                                                                   

                                               
 












                                                                                           
 




                                                                                                     
 





                                                                                        
                                 
                         
 
                                                                              
 









                                                                                                            
 

                                                                                                       
                         
                                          

                 

                                                                                           










                                                                                                    


                                                        


                                    
                                                                                                                      

                                                                           





                                                                            




                                                                                            
                                                                     

                                                                          
                         
                                     

                 




                                                                                                            
                 



                                                                                               






                                                                                                 
                                                          










































































                                                                                                                               














                                                                                        














































                                                                                               

                                                                            



















































                                                                                                                 

                                                                                                                   


















                                                                                                       
                                                        


                                                                         







                                                                                                     

                                                                                                         
                                         


                                                                                                     


















                                                                                               

                                                                                                                              














                                                                                                                                 

                                                                                                                     









                                                                                    

                                                                                                            











                                                                                                             











                                                                                           

                                                              



                                                                                         
                                                                                           
                                                                   











                                                                                                   

                                         
                                                                                                                      

                                                                                              

                                                                                                                                     
                                        






                                                                                                                          
                                                              

                                                                           
                                         

                                                                                               


                                                                                                                    
                                                                                               









                                                                                                                                                  

                                                                                                                 









                                                                                   

                                                                                                                     




                                                                    

                                                                                                                    





















                                                                                               

                                                                                                                            





                                                                     

                                                                                                                 



                         

                                                                                        





                                                                       

                                                                                                                       









                                                                          

                                                                                        














                                                                               
                                                                                                                                        










































                                                                               
package fi.iki.elonen;

/*
 * #%L
 * NanoHttpd-Core
 * %%
 * Copyright (C) 2012 - 2015 nanohttpd
 * %%
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 * 
 * 1. Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 * 
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 * 
 * 3. Neither the name of the nanohttpd nor the names of its contributors
 * may be used to endorse or promote products derived from this software without
 * specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 * #L%
 */

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PushbackInputStream;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.URLDecoder;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.TimeUnit;

import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.openslx.util.GrowingThreadPoolExecutor;

/**
 * A simple, tiny, nicely embeddable HTTP server in Java
 * <p/>
 * <p/>
 * NanoHTTPD
 * <p>
 * Copyright (c) 2012-2013 by Paul S. Hawke, 2001,2005-2013 by Jarno Elonen, 2010 by Konstantinos
 * Togias
 * </p>
 * <p/>
 * <p/>
 * <b>Features + limitations: </b>
 * <ul>
 * <p/>
 * <li>Only one Java file</li>
 * <li>Java 5 compatible</li>
 * <li>Released as open source, Modified BSD licence</li>
 * <li>No fixed config files, logging, authorization etc. (Implement yourself if you need them.)</li>
 * <li>Supports parameter parsing of GET and POST methods (+ rudimentary PUT support in 1.25)</li>
 * <li>Supports both dynamic content and file serving</li>
 * <li>Supports file upload (since version 1.2, 2010)</li>
 * <li>Supports partial content (streaming)</li>
 * <li>Supports ETags</li>
 * <li>Never caches anything</li>
 * <li>Doesn't limit bandwidth, request time or simultaneous connections</li>
 * <li>Default code serves files and shows all HTTP parameters and headers</li>
 * <li>File server supports directory listing, index.html and index.htm</li>
 * <li>File server supports partial content (streaming)</li>
 * <li>File server supports ETags</li>
 * <li>File server does the 301 redirection trick for directories without '/'</li>
 * <li>File server supports simple skipping for files (continue download)</li>
 * <li>File server serves also very long files without memory overhead</li>
 * <li>Contains a built-in list of most common MIME types</li>
 * <li>All header names are converted to lower case so they don't vary between browsers/clients</li>
 * <p/>
 * </ul>
 * <p/>
 * <p/>
 * <b>How to use: </b>
 * <ul>
 * <p/>
 * <li>Subclass and implement serve() and embed to your own program</li>
 * <p/>
 * </ul>
 * <p/>
 * See the separate "LICENSE.md" file for the distribution license (Modified BSD licence)
 */
public abstract class NanoHTTPD implements Runnable
{

	/**
	 * Maximum time to wait on Socket.getInputStream().read() (in milliseconds)
	 * This is required as the Keep-Alive HTTP connections would otherwise
	 * block the socket reading thread forever (or as long the browser is open).
	 */
	public static final int SOCKET_READ_TIMEOUT = 10000;
	/**
	 * Common MIME type for dynamic content: plain text
	 */
	public static final String MIME_PLAINTEXT = "text/plain";
	/**
	 * Common MIME type for dynamic content: html
	 */
	public static final String MIME_HTML = "text/html";
	/**
	 * Pseudo-Parameter to use to store the actual query string in the
	 * parameters map for later
	 * re-processing.
	 */
	private static final String QUERY_STRING_PARAMETER = "NanoHttpd.QUERY_STRING";
	private final String hostname;
	private final int myPort;
	private ServerSocket myServerSocket;
	private Set<Socket> openConnections = new HashSet<Socket>();
	/**
	 * Pluggable strategy for asynchronously executing requests.
	 */
	private AsyncRunner asyncRunner;

	protected int maxRequestSize = 0;

	/**
	 * Constructs an HTTP server on given port.
	 */
	public NanoHTTPD( int port )
	{
		this( null, port );
	}

	/**
	 * Constructs an HTTP server on given hostname and port.
	 */
	public NanoHTTPD( String hostname, int port )
	{
		this.hostname = hostname;
		this.myPort = port;
		setAsyncRunner( new DefaultAsyncRunner() );
	}

	public static final void safeClose( Closeable closeable )
	{
		if ( closeable != null ) {
			try {
				closeable.close();
			} catch ( IOException e ) {
			}
		}
	}

	/**
	 * Start the server.
	 * 
	 * @throws IOException if the socket is in use.
	 */
	@Override
	public void run()
	{
		try {
			myServerSocket = new ServerSocket();
			myServerSocket.setReuseAddress( true );
			myServerSocket.bind( ( hostname != null ) ? new InetSocketAddress( hostname, myPort )
					: new InetSocketAddress( myPort ) );
		} catch ( Exception e ) {
			throw new RuntimeException( e );
		}

		do {
			try {
				final Socket finalAccept = myServerSocket.accept();
				registerConnection( finalAccept );
				finalAccept.setSoTimeout( SOCKET_READ_TIMEOUT );
				final InputStream inputStream = finalAccept.getInputStream();
				asyncRunner.exec( new Runnable() {
					@Override
					public void run()
					{
						OutputStream outputStream = null;
						try {
							outputStream = finalAccept.getOutputStream();
							HTTPSession session = new HTTPSession( inputStream, outputStream,
									finalAccept.getInetAddress() );
							while ( !finalAccept.isClosed() && !finalAccept.isInputShutdown() ) {
								session.execute();
							}
						} catch ( Exception e ) {
							// When the socket is closed by the client, we throw our own SocketException
							// to break the  "keep alive" loop above.
							if ( ! ( e instanceof SocketTimeoutException )
									&& ! ( e instanceof SocketException ) ) {
								e.printStackTrace();
							}
						} finally {
							safeClose( outputStream );
							safeClose( inputStream );
							safeClose( finalAccept );
							unRegisterConnection( finalAccept );
						}
					}
				} );
			} catch ( IOException e ) {
			}
		} while ( !myServerSocket.isClosed() );
	}

	/**
	 * Stop the server.
	 */
	public void stop()
	{
		try {
			safeClose( myServerSocket );
			closeAllConnections();
		} catch ( Exception e ) {
			e.printStackTrace();
		}
	}

	/**
	 * Registers that a new connection has been set up.
	 * 
	 * @param socket the {@link Socket} for the connection.
	 */
	public synchronized void registerConnection( Socket socket )
	{
		openConnections.add( socket );
	}

	/**
	 * Registers that a connection has been closed
	 * 
	 * @param socket
	 *           the {@link Socket} for the connection.
	 */
	public synchronized void unRegisterConnection( Socket socket )
	{
		openConnections.remove( socket );
	}

	/**
	 * Forcibly closes all connections that are open.
	 */
	public synchronized void closeAllConnections()
	{
		for ( Socket socket : openConnections ) {
			safeClose( socket );
		}
	}

	public final int getListeningPort()
	{
		return myServerSocket == null ? -1 : myServerSocket.getLocalPort();
	}

	public final boolean wasStarted()
	{
		return myServerSocket != null;
	}

	public final boolean isAlive()
	{
		return wasStarted() && !myServerSocket.isClosed();
	}

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

	/**
	 * Override this to customize the server.
	 * <p/>
	 * <p/>
	 * (By default, this returns a 404 "Not Found" plain text error response.)
	 * 
	 * @param session The HTTP session
	 * @return HTTP response, see class Response for details
	 */
	public Response serve( IHTTPSession session )
	{
		Map<String, String> files = new HashMap<String, String>();
		Method method = session.getMethod();
		if ( Method.PUT.equals( method ) || Method.POST.equals( method ) ) {
			try {
				session.parseBody( files );
			} catch ( IOException ioe ) {
				return new Response( Response.Status.INTERNAL_ERROR, MIME_PLAINTEXT,
						"SERVER INTERNAL ERROR: IOException: " + ioe.getMessage() );
			} catch ( ResponseException re ) {
				return new Response( re.getStatus(), MIME_PLAINTEXT, re.getMessage() );
			}
		}

		Map<String, String> parms = session.getParms();
		parms.put( QUERY_STRING_PARAMETER, session.getQueryParameterString() );
		return serve( session.getUri(), method, session.getHeaders(), parms, files );
	}

	/**
	 * Decode percent encoded <code>String</code> values.
	 * 
	 * @param str the percent encoded <code>String</code>
	 * @return expanded form of the input, for example "foo%20bar" becomes
	 *         "foo bar"
	 */
	protected String decodePercent( String str )
	{
		String decoded = null;
		try {
			decoded = URLDecoder.decode( str, "UTF8" );
		} catch ( UnsupportedEncodingException ignored ) {
		}
		return decoded;
	}

	/**
	 * Decode parameters from a URL, handing the case where a single parameter
	 * name might have been
	 * supplied several times, by return lists of values. In general these lists
	 * will contain a
	 * single
	 * element.
	 * 
	 * @param parms original <b>NanoHTTPD</b> parameters values, as passed to
	 *           the <code>serve()</code> method.
	 * @return a map of <code>String</code> (parameter name) to <code>List&lt;String&gt;</code> (a
	 *         list of the values supplied).
	 */
	protected Map<String, List<String>> decodeParameters( Map<String, String> parms )
	{
		return this.decodeParameters( parms.get( QUERY_STRING_PARAMETER ) );
	}

	/**
	 * Decode parameters from a URL, handing the case where a single parameter
	 * name might have been
	 * supplied several times, by return lists of values. In general these lists
	 * will contain a
	 * single
	 * element.
	 * 
	 * @param queryString a query string pulled from the URL.
	 * @return a map of <code>String</code> (parameter name) to <code>List&lt;String&gt;</code> (a
	 *         list of the values supplied).
	 */
	protected Map<String, List<String>> decodeParameters( String queryString )
	{
		Map<String, List<String>> parms = new HashMap<String, List<String>>();
		if ( queryString != null ) {
			StringTokenizer st = new StringTokenizer( queryString, "&" );
			while ( st.hasMoreTokens() ) {
				String e = st.nextToken();
				int sep = e.indexOf( '=' );
				String propertyName = ( sep >= 0 ) ? decodePercent( e.substring( 0, sep ) ).trim() : decodePercent(
						e ).trim();
				if ( !parms.containsKey( propertyName ) ) {
					parms.put( propertyName, new ArrayList<String>() );
				}
				String propertyValue = ( sep >= 0 ) ? decodePercent( e.substring( sep + 1 ) ) : null;
				if ( propertyValue != null ) {
					parms.get( propertyName ).add( propertyValue );
				}
			}
		}
		return parms;
	}

	// ------------------------------------------------------------------------------- //
	//
	// Threading Strategy.
	//
	// ------------------------------------------------------------------------------- //

	/**
	 * Pluggable strategy for asynchronously executing requests.
	 * 
	 * @param asyncRunner new strategy for handling threads.
	 */
	public void setAsyncRunner( AsyncRunner asyncRunner )
	{
		this.asyncRunner = asyncRunner;
	}

	/**
	 * HTTP Request methods, with the ability to decode a <code>String</code> back to its enum value.
	 */
	public enum Method
	{
		GET,
		PUT,
		POST,
		DELETE,
		HEAD,
		OPTIONS;

		static Method lookup( String method )
		{
			for ( Method m : Method.values() ) {
				if ( m.toString().equalsIgnoreCase( method ) ) {
					return m;
				}
			}
			return null;
		}
	}

	/**
	 * Pluggable strategy for asynchronously executing requests.
	 */
	public interface AsyncRunner
	{
		void exec( Runnable code );
	}

	// ------------------------------------------------------------------------------- //

	/**
	 * Default threading strategy for NanoHTTPD.
	 * <p/>
	 * <p>
	 * Uses a thread pool.
	 * </p>
	 */
	public static class DefaultAsyncRunner implements AsyncRunner
	{
		private ExecutorService pool = new GrowingThreadPoolExecutor( 2, 16, 1, TimeUnit.MINUTES,
				new ArrayBlockingQueue<Runnable>( 4 ) );

		@Override
		public void exec( Runnable code )
		{
			try {
				pool.execute( code );
			} catch ( RejectedExecutionException e ) {
			}
		}
	}

	/**
	 * HTTP response. Return one of these from serve().
	 */
	public static class Response
	{
		/**
		 * HTTP status code after processing, e.g. "200 OK", Status.OK
		 */
		private IStatus status;
		/**
		 * MIME type of content, e.g. "text/html"
		 */
		private String mimeType;
		/**
		 * Data of the response, may be null.
		 */
		private InputStream data;
		/**
		 * Headers for the HTTP response. Use addHeader() to add lines.
		 */
		private Map<String, String> header = new HashMap<String, String>();
		/**
		 * The request method that spawned this response.
		 */
		private Method requestMethod;
		/**
		 * Use chunkedTransfer
		 */
		private boolean chunkedTransfer;

		/**
		 * Default constructor: response = Status.OK, mime = MIME_HTML and your
		 * supplied message
		 */
		public Response( String msg )
		{
			this( Status.OK, MIME_HTML, msg );
		}

		/**
		 * Basic constructor.
		 */
		public Response( IStatus status, String mimeType, InputStream data )
		{
			this.status = status;
			this.mimeType = mimeType;
			this.data = data;
		}

		/**
		 * Convenience method that makes an InputStream out of given text.
		 */
		public Response( IStatus status, String mimeType, String txt )
		{
			this.status = status;
			this.mimeType = mimeType;
			try {
				this.data = txt != null ? new ByteArrayInputStream( txt.getBytes( "UTF-8" ) ) : null;
			} catch ( java.io.UnsupportedEncodingException uee ) {
				uee.printStackTrace();
			}
		}

		/**
		 * Adds given line to the header.
		 */
		public void addHeader( String name, String value )
		{
			header.put( name, value );
		}

		public String getHeader( String name )
		{
			return header.get( name );
		}

		private static final DateTimeFormatter headerDateFormatter = DateTimeFormat.forPattern(
				"E, d MMM yyyy HH:mm:ss 'GMT'" )
				.withLocale( Locale.US )
				.withZoneUTC();

		/**
		 * Sends given response to the socket.
		 */
		protected void send( OutputStream outputStream ) throws IOException
		{
			String mime = mimeType;

			StringBuilder sb = new StringBuilder();
			if ( status == null ) {
				throw new Error( "sendResponse(): Status can't be null." );
			}
			sb.append( "HTTP/1.1 " );
			sb.append( status.getDescription() );
			sb.append( " \r\n" );

			if ( mime != null ) {
				sb.append( "Content-Type: " );
				sb.append( mime );
				sb.append( "\r\n" );
			}

			if ( header == null || header.get( "Date" ) == null ) {
				sb.append( "Date: " );
				sb.append( headerDateFormatter.print( System.currentTimeMillis() ) );
				sb.append( "\r\n" );
			}

			if ( header != null ) {
				for ( Entry<String, String> item : header.entrySet() ) {
					sb.append( item.getKey() );
					sb.append( ": " );
					sb.append( item.getValue() );
					sb.append( "\r\n" );
				}
			}

			sendConnectionHeaderIfNotAlreadyPresent( sb, header );

			if ( requestMethod != Method.HEAD && chunkedTransfer ) {
				sendAsChunked( outputStream, sb );
			} else {
				int pending = data != null ? data.available() : 0;
				pending = sendContentLengthHeaderIfNotAlreadyPresent( sb, header, pending );
				sb.append( "\r\n" );
				outputStream.write( sb.toString().getBytes( StandardCharsets.UTF_8 ) );
				sb.setLength( 0 );
				sendAsFixedLength( outputStream, pending );
			}

			if ( sb.length() != 0 ) {
				outputStream.write( sb.toString().getBytes( StandardCharsets.UTF_8 ) );
			}
			safeClose( data );
		}

		protected int sendContentLengthHeaderIfNotAlreadyPresent( StringBuilder sb,
				Map<String, String> header, int size )
		{
			for ( String headerName : header.keySet() ) {
				if ( headerName.equalsIgnoreCase( "content-length" ) ) {
					try {
						return Integer.parseInt( header.get( headerName ) );
					} catch ( NumberFormatException ex ) {
						return size;
					}
				}
			}

			sb.append( "Content-Length: " );
			sb.append( size );
			sb.append( "\r\n" );
			return size;
		}

		protected void sendConnectionHeaderIfNotAlreadyPresent( StringBuilder sb, Map<String, String> header )
		{
			if ( !headerAlreadySent( header, "connection" ) ) {
				sb.append( "Connection: keep-alive\r\n" );
			}
			if ( !headerAlreadySent( header, "keep-alive" ) ) {
				sb.append( "Keep-Alive: timeout=" );
				sb.append( SOCKET_READ_TIMEOUT / 1000 - 1 );
				sb.append( "\r\n" );
			}
		}

		private boolean headerAlreadySent( Map<String, String> header, String name )
		{
			for ( String headerName : header.keySet() ) {
				if ( headerName.equalsIgnoreCase( name ) )
					return true;
			}
			return false;
		}

		private static final byte[] CRLF = "\r\n".getBytes();
		private static final byte[] CHUNKED_END = "0\r\n\r\n".getBytes();
		private static final int BUFFER_SIZE = 256 * 1024;

		private void sendAsChunked( OutputStream outputStream, StringBuilder sb ) throws IOException
		{
			sb.append( "Transfer-Encoding: chunked\r\n" );
			sb.append( "\r\n" );
			outputStream.write( sb.toString().getBytes( StandardCharsets.UTF_8 ) );
			sb.setLength( 0 );
			byte[] buff = new byte[ BUFFER_SIZE ];
			int read;
			while ( ( read = data.read( buff ) ) > 0 ) {
				outputStream.write( String.format( "%x\r\n", read ).getBytes() );
				outputStream.write( buff, 0, read );
				outputStream.write( CRLF );
			}
			outputStream.write( CHUNKED_END );
		}

		private void sendAsFixedLength( OutputStream outputStream, int pending ) throws IOException
		{
			if ( requestMethod != Method.HEAD && data != null ) {
				int BUFFER_SIZE = 16 * 1024;
				byte[] buff = new byte[ BUFFER_SIZE ];
				while ( pending > 0 ) {
					int read = data.read( buff, 0, ( ( pending > BUFFER_SIZE ) ? BUFFER_SIZE : pending ) );
					if ( read <= 0 ) {
						break;
					}
					outputStream.write( buff, 0, read );
					pending -= read;
				}
			}
		}

		public IStatus getStatus()
		{
			return status;
		}

		public void setStatus( IStatus status )
		{
			this.status = status;
		}

		public String getMimeType()
		{
			return mimeType;
		}

		public void setMimeType( String mimeType )
		{
			this.mimeType = mimeType;
		}

		public InputStream getData()
		{
			return data;
		}

		public void setData( InputStream data )
		{
			this.data = data;
		}

		public Method getRequestMethod()
		{
			return requestMethod;
		}

		public void setRequestMethod( Method requestMethod )
		{
			this.requestMethod = requestMethod;
		}

		public void setChunkedTransfer( boolean chunkedTransfer )
		{
			this.chunkedTransfer = chunkedTransfer;
		}

		public interface IStatus
		{
			int getRequestStatus();

			String getDescription();
		}

		/**
		 * Some HTTP response status codes
		 */
		public enum Status implements IStatus
		{
			SWITCH_PROTOCOL( 101, "Switching Protocols" ),
			OK( 200, "OK" ),
			CREATED( 201, "Created" ),
			ACCEPTED( 202, "Accepted" ),
			NO_CONTENT( 204, "No Content" ),
			PARTIAL_CONTENT( 206, "Partial Content" ),
			REDIRECT( 301, "Moved Permanently" ),
			NOT_MODIFIED( 304, "Not Modified" ),
			BAD_REQUEST( 400, "Bad Request" ),
			UNAUTHORIZED( 401, "Unauthorized" ),
			FORBIDDEN( 403, "Forbidden" ),
			NOT_FOUND( 404, "Not Found" ),
			METHOD_NOT_ALLOWED( 405, "Method Not Allowed" ),
			RANGE_NOT_SATISFIABLE( 416, "Requested Range Not Satisfiable" ),
			INTERNAL_ERROR( 500, "Internal Server Error" );
			private final int requestStatus;
			private final String description;

			Status( int requestStatus, String description )
			{
				this.requestStatus = requestStatus;
				this.description = description;
			}

			@Override
			public int getRequestStatus()
			{
				return this.requestStatus;
			}

			@Override
			public String getDescription()
			{
				return "" + this.requestStatus + " " + description;
			}
		}
	}

	public static final class ResponseException extends Exception
	{
		private static final long serialVersionUID = 6569838532917408380L;
		private final Response.Status status;

		public ResponseException( Response.Status status, String message )
		{
			super( message );
			this.status = status;
		}

		public ResponseException( Response.Status status, String message, Exception e )
		{
			super( message, e );
			this.status = status;
		}

		public Response.Status getStatus()
		{
			return status;
		}
	}

	/**
	 * Handles one session, i.e. parses the HTTP request and returns the
	 * response.
	 */
	public interface IHTTPSession
	{
		void execute() throws IOException;

		Map<String, String> getParms();

		Map<String, String> getHeaders();

		/**
		 * @return the path part of the URL.
		 */
		String getUri();

		String getQueryParameterString();

		Method getMethod();

		InputStream getInputStream();

		/**
		 * Adds the files in the request body to the files map.
		 * 
		 * @param files map to modify
		 */
		void parseBody( Map<String, String> files ) throws IOException, ResponseException;
	}

	protected class HTTPSession implements IHTTPSession
	{
		public static final int BUFSIZE = 8192;
		private final OutputStream outputStream;
		private PushbackInputStream inputStream;
		private int splitbyte;
		private int rlen;
		private String uri;
		private Method method;
		private Map<String, String> parms;
		private Map<String, String> headers;
		private String queryParameterString;
		private String remoteIp;

		public HTTPSession( InputStream inputStream, OutputStream outputStream )
		{
			this.inputStream = new PushbackInputStream( inputStream, BUFSIZE );
			this.outputStream = outputStream;
		}

		public HTTPSession( InputStream inputStream, OutputStream outputStream, InetAddress inetAddress )
		{
			this.inputStream = new PushbackInputStream( inputStream, BUFSIZE );
			this.outputStream = outputStream;
			remoteIp = inetAddress.isLoopbackAddress() || inetAddress.isAnyLocalAddress() ? "127.0.0.1"
					: inetAddress.getHostAddress().toString();
			headers = new HashMap<String, String>();
		}

		@Override
		public void execute() throws IOException
		{
			try {
				// Read the first 8192 bytes.
				// The full header should fit in here.
				// Apache's default header limit is 8KB.
				// Do NOT assume that a single read will get the entire header at once!
				byte[] buf = new byte[ BUFSIZE ];
				splitbyte = 0;
				rlen = 0;
				{
					int read = -1;
					try {
						read = inputStream.read( buf, 0, BUFSIZE );
					} catch ( Exception e ) {
						throw e;
					}
					if ( read == -1 ) {
						// socket was been closed
						throw new SocketException( "NanoHttpd Shutdown" );
					}
					while ( read > 0 ) {
						rlen += read;
						splitbyte = findHeaderEnd( buf, rlen );
						if ( splitbyte > 0 )
							break;
						read = inputStream.read( buf, rlen, BUFSIZE - rlen );
						if ( maxRequestSize != 0 && rlen > maxRequestSize )
							throw new SocketException( "Request too large" );
					}
					if ( splitbyte == 0 ) {
						throw new SocketException( "Connection was closed" );
					}
				}

				if ( splitbyte < rlen ) {
					inputStream.unread( buf, splitbyte, rlen - splitbyte );
				}

				parms = new HashMap<String, String>();
				if ( null == headers ) {
					headers = new HashMap<String, String>();
				} else {
					headers.clear();
				}

				if ( null != remoteIp ) {
					headers.put( "remote-addr", remoteIp );
					headers.put( "http-client-ip", remoteIp );
				}

				// Create a BufferedReader for parsing the header.
				BufferedReader hin = new BufferedReader( new InputStreamReader( new ByteArrayInputStream( buf,
						0, rlen ) ) );

				// Decode the header into parms and header java properties
				Map<String, String> pre = new HashMap<String, String>();
				decodeHeader( hin, pre, parms, headers );

				method = Method.lookup( pre.get( "method" ) );
				if ( method == null ) {
					throw new ResponseException( Response.Status.BAD_REQUEST, "BAD REQUEST: Syntax error." );
				}

				uri = pre.get( "uri" );

				// Ok, now do the serve()
				Response r = serve( this );
				if ( r == null ) {
					throw new ResponseException( Response.Status.INTERNAL_ERROR,
							"SERVER INTERNAL ERROR: Serve() returned a null response." );
				} else {
					r.setRequestMethod( method );
					r.send( outputStream );
				}
			} catch ( SocketException e ) {
				// throw it out to close socket object (finalAccept)
				throw e;
			} catch ( SocketTimeoutException ste ) {
				throw ste;
			} catch ( IOException ioe ) {
				Response r = new Response( Response.Status.INTERNAL_ERROR, MIME_PLAINTEXT,
						"SERVER INTERNAL ERROR: IOException: " + ioe.getMessage() );
				r.send( outputStream );
				safeClose( outputStream );
			} catch ( ResponseException re ) {
				Response r = new Response( re.getStatus(), MIME_PLAINTEXT, re.getMessage() );
				r.send( outputStream );
				safeClose( outputStream );
			}
		}

		@Override
		public void parseBody( Map<String, String> files ) throws IOException, ResponseException
		{
			long size;
			if ( headers.containsKey( "content-length" ) ) {
				size = Integer.parseInt( headers.get( "content-length" ) );
			} else if ( splitbyte < rlen ) {
				size = rlen - splitbyte;
			} else {
				size = 0;
			}

			// If the method is POST, there may be parameters
			// in data section, too, read it:
			if ( Method.POST.equals( method ) ) {
				String contentType = null;
				String contentEncoding = null;
				String contentTypeHeader = headers.get( "content-type" );

				StringTokenizer st = null;
				if ( contentTypeHeader != null ) {
					st = new StringTokenizer( contentTypeHeader, "," );
					if ( st.hasMoreTokens() ) {
						String part[] = st.nextToken().split( ";\\s*", 2 );
						contentType = part[0];
						if ( part.length == 2 ) {
							contentEncoding = part[1];
						}
					}
				}
				Charset cs = StandardCharsets.ISO_8859_1;
				if ( contentEncoding != null ) {
					try {
						cs = Charset.forName( contentEncoding );
					} catch ( Exception e ) {
					}
				}
				//LOGGER.debug("Content type is '" + contentType + "', encoding '" + cs.name() + "'");

				if ( "multipart/form-data".equalsIgnoreCase( contentType ) ) {
					throw new ResponseException( Response.Status.BAD_REQUEST,
							"BAD REQUEST: Content type is multipart/form-data, which is not supported" );
				} else {
					ByteArrayOutputStream baos = new ByteArrayOutputStream();
					byte pbuf[] = new byte[ 1000 ];
					while ( size > 0 ) {
						int ret = inputStream.read( pbuf, 0, (int)Math.min( size, pbuf.length ) );
						if ( ret <= 0 )
							break;
						if ( ret >= 2 && pbuf[ret - 1] == '\n' && pbuf[ret - 2] == '\r' )
							break;
						size -= ret;
						baos.write( pbuf, 0, ret );
					}
					String postLine = new String( baos.toByteArray(), cs );
					baos.close();
					// Handle application/x-www-form-urlencoded
					if ( "application/x-www-form-urlencoded".equalsIgnoreCase( contentType ) ) {
						decodeParms( postLine, parms );
					} else if ( files != null && postLine.length() != 0 ) {
						// Special case for raw POST data => create a special files entry "postData" with raw content data
						files.put( "postData", postLine );
					}
				}
			}
		}

		/**
		 * Decodes the sent headers and loads the data into Key/value pairs
		 */
		private void decodeHeader( BufferedReader in, Map<String, String> pre, Map<String, String> parms,
				Map<String, String> headers ) throws ResponseException
		{
			try {
				// Read the request line
				String inLine = in.readLine();
				if ( inLine == null ) {
					return;
				}

				StringTokenizer st = new StringTokenizer( inLine );
				if ( !st.hasMoreTokens() ) {
					throw new ResponseException( Response.Status.BAD_REQUEST,
							"BAD REQUEST: Syntax error. Usage: GET /example/file.html" );
				}

				pre.put( "method", st.nextToken() );

				if ( !st.hasMoreTokens() ) {
					throw new ResponseException( Response.Status.BAD_REQUEST,
							"BAD REQUEST: Missing URI. Usage: GET /example/file.html" );
				}

				String uri = st.nextToken();

				// Decode parameters from the URI
				int qmi = uri.indexOf( '?' );
				if ( qmi >= 0 ) {
					decodeParms( uri.substring( qmi + 1 ), parms );
					uri = decodePercent( uri.substring( 0, qmi ) );
				} else {
					uri = decodePercent( uri );
				}

				// If there's another token, its protocol version,
				// followed by HTTP headers. Ignore version but parse headers.
				// NOTE: this now forces header names lower case since they are
				// case insensitive and vary by client.
				if ( st.hasMoreTokens() ) {
					String line = in.readLine();
					while ( line != null && line.trim().length() > 0 ) {
						int p = line.indexOf( ':' );
						if ( p >= 0 )
							headers.put( line.substring( 0, p ).trim().toLowerCase( Locale.US ),
									line.substring( p + 1 ).trim() );
						line = in.readLine();
					}
				}

				pre.put( "uri", uri );
			} catch ( IOException ioe ) {
				throw new ResponseException( Response.Status.INTERNAL_ERROR,
						"SERVER INTERNAL ERROR: IOException: " + ioe.getMessage(), ioe );
			}
		}

		/**
		 * Find byte index separating header from body. It must be the last byte
		 * of the first two
		 * sequential new lines.
		 */
		private int findHeaderEnd( final byte[] buf, int rlen )
		{
			int splitbyte = 0;
			while ( splitbyte + 3 < rlen ) {
				if ( buf[splitbyte] == '\r' && buf[splitbyte + 1] == '\n' && buf[splitbyte + 2] == '\r'
						&& buf[splitbyte + 3] == '\n' ) {
					return splitbyte + 4;
				}
				splitbyte++;
			}
			return 0;
		}

		/**
		 * Decodes parameters in percent-encoded URI-format ( e.g.
		 * "name=Jack%20Daniels&pass=Single%20Malt" ) and
		 * adds them to given Map. NOTE: this doesn't support multiple identical
		 * keys due to the
		 * simplicity of Map.
		 */
		private void decodeParms( String parms, Map<String, String> p )
		{
			if ( parms == null ) {
				queryParameterString = "";
				return;
			}

			queryParameterString = parms;
			StringTokenizer st = new StringTokenizer( parms, "&" );
			while ( st.hasMoreTokens() ) {
				String e = st.nextToken();
				int sep = e.indexOf( '=' );
				if ( sep >= 0 ) {
					p.put( decodePercent( e.substring( 0, sep ) ).trim(), decodePercent( e.substring( sep + 1 ) ) );
				} else {
					p.put( decodePercent( e ).trim(), "" );
				}
			}
		}

		@Override
		public final Map<String, String> getParms()
		{
			return parms;
		}

		public String getQueryParameterString()
		{
			return queryParameterString;
		}

		@Override
		public final Map<String, String> getHeaders()
		{
			return headers;
		}

		@Override
		public final String getUri()
		{
			return uri;
		}

		@Override
		public final Method getMethod()
		{
			return method;
		}

		@Override
		public final InputStream getInputStream()
		{
			return inputStream;
		}
	}

}