All Downloads are FREE. Search and download functionalities are using the official Maven repository.

fi.iki.elonen.nanohttpd.NanoHTTPD Maven / Gradle / Ivy

package fi.iki.elonen.nanohttpd;
import java.io.*;
import java.util.*;
import java.net.*;

/**
 * A simple, tiny, nicely embeddable HTTP 1.0 server in Java
 *
 * 

NanoHTTPD version 1.02, * Copyright © 2001,2005 Jarno Elonen ([email protected], http://iki.fi/elonen/) * *

Features & limitations:

    * *
  • Only one Java file
  • *
  • Java 1.1 compatible
  • *
  • Released as open source, Modified BSD licence
  • *
  • No fixed config files, logging, authorization etc. (Implement yourself if you need them.)
  • *
  • Supports parameter parsing of GET and POST methods
  • *
  • Supports both dynamic content and file serving
  • *
  • Never caches anything
  • *
  • Doesn't limit bandwidth, request time or simultaneous connections
  • *
  • Default code serves files and shows all HTTP parameters and headers
  • *
  • File server supports directory listing, index.html and index.htm
  • *
  • File server does the 301 redirection trick for directories without '/'
  • *
  • File server supports simple skipping for files (continue download)
  • *
  • File server uses current directory as a web root
  • *
  • File server serves also very long files without memory overhead
  • *
  • Contains a built-in list of most common mime types
  • * *
* *

Ways to use:

    * *
  • Run as a standalone app, serves files from current directory and shows requests
  • *
  • Subclass serve() and embed to your own program
  • *
  • Call serveFile() from serve() with your own base directory
  • * *
* * See the end of the source file for distribution license * (Modified BSD licence) */ public class NanoHTTPD { // ================================================== // API parts // ================================================== /** * Override this to customize the server.

* * (By default, this delegates to serveFile() and allows directory listing.) * * @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 header Header entries, percent decoded * @return HTTP response, see class Response for details */ public Response serve( String uri, String method, Properties header, Properties parms ) { System.out.println( method + " '" + uri + "' " ); Enumeration e = header.propertyNames(); while ( e.hasMoreElements()) { String value = (String)e.nextElement(); System.out.println( " HDR: '" + value + "' = '" + header.getProperty( value ) + "'" ); } e = parms.propertyNames(); while ( e.hasMoreElements()) { String value = (String)e.nextElement(); System.out.println( " PRM: '" + value + "' = '" + parms.getProperty( value ) + "'" ); } return serveFile( uri, header, new File("."), true ); } /** * HTTP response. * Return one of these from serve(). */ public class Response { /** * Default constructor: response = HTTP_OK, data = mime = 'null' */ public Response() { this.status = HTTP_OK; } /** * Basic constructor. */ public Response( String 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( String status, String mimeType, String txt ) { this.status = status; this.mimeType = mimeType; this.data = new ByteArrayInputStream( txt.getBytes()); } /** * Adds given line to the header. */ public void addHeader( String name, String value ) { header.put( name, value ); } /** * HTTP status code after processing, e.g. "200 OK", HTTP_OK */ public String status; /** * MIME type of content, e.g. "text/html" */ public String mimeType; /** * Data of the response, may be null. */ public InputStream data; /** * Headers for the HTTP response. Use addHeader() * to add lines. */ public Properties header = new Properties(); } /** * Some HTTP response status codes */ public static final String HTTP_OK = "200 OK", HTTP_REDIRECT = "301 Moved Permanently", HTTP_FORBIDDEN = "403 Forbidden", HTTP_NOTFOUND = "404 Not Found", HTTP_BADREQUEST = "400 Bad Request", HTTP_INTERNALERROR = "500 Internal Server Error", HTTP_NOTIMPLEMENTED = "501 Not Implemented"; /** * Common mime types for dynamic content */ public static final String MIME_PLAINTEXT = "text/plain", MIME_HTML = "text/html", MIME_DEFAULT_BINARY = "application/octet-stream"; // ================================================== // Socket & server code // ================================================== /** * Starts a HTTP server to given port.

* Throws an IOException if the socket is already in use */ public NanoHTTPD( int port ) throws IOException { myTcpPort = port; final ServerSocket ss = new ServerSocket( myTcpPort ); Thread t = new Thread( new Runnable() { public void run() { try { while( true ) new HTTPSession( ss.accept()); } catch ( IOException ioe ) {} } }); t.setDaemon( true ); t.start(); } /** * Starts as a standalone file server and waits for Enter. */ public static void main( String[] args ) { System.out.println( "NanoHTTPD 1.02 (C) 2001,2005 Jarno Elonen\n" + "(Command line options: [port] [--licence])\n" ); // Show licence if requested int lopt = -1; for ( int i=0; i 0 && lopt != 0 ) port = Integer.parseInt( args[0] ); if ( args.length > 1 && args[1].toLowerCase().endsWith( "licence" )) System.out.println( LICENCE + "\n" ); NanoHTTPD nh = null; try { nh = new NanoHTTPD( port ); } catch( IOException ioe ) { System.err.println( "Couldn't start server:\n" + ioe ); System.exit( -1 ); } nh.myFileDir = new File(""); System.out.println( "Now serving files in port " + port + " from \"" + new File("").getAbsolutePath() + "\"" ); System.out.println( "Hit Enter to stop.\n" ); try { System.in.read(); } catch( Throwable t ) {}; } /** * Handles one session, i.e. parses the HTTP request * and returns the response. */ private class HTTPSession implements Runnable { public HTTPSession( Socket s ) { mySocket = s; Thread t = new Thread( this ); t.setDaemon( true ); t.start(); } public void run() { try { InputStream is = mySocket.getInputStream(); if ( is == null) return; BufferedReader in = new BufferedReader( new InputStreamReader( is )); // Read the request line StringTokenizer st = new StringTokenizer( in.readLine()); if ( !st.hasMoreTokens()) sendError( HTTP_BADREQUEST, "BAD REQUEST: Syntax error. Usage: GET /example/file.html" ); String method = st.nextToken(); if ( !st.hasMoreTokens()) sendError( HTTP_BADREQUEST, "BAD REQUEST: Missing URI. Usage: GET /example/file.html" ); String uri = decodePercent( st.nextToken()); // Decode parameters from the URI Properties parms = new Properties(); int qmi = uri.indexOf( '?' ); if ( qmi >= 0 ) { decodeParms( uri.substring( qmi+1 ), parms ); uri = decodePercent( uri.substring( 0, qmi )); } // If there's another token, it's protocol version, // followed by HTTP headers. Ignore version but parse headers. Properties header = new Properties(); if ( st.hasMoreTokens()) { String line = in.readLine(); while ( line.trim().length() > 0 ) { int p = line.indexOf( ':' ); header.put( line.substring(0,p).trim(), line.substring(p+1).trim()); line = in.readLine(); } } // If the method is POST, there may be parameters // in data section, too, read another line: if ( method.equalsIgnoreCase( "POST" )) decodeParms( in.readLine(), parms ); // Ok, now do the serve() Response r = serve( uri, method, header, parms ); if ( r == null ) sendError( HTTP_INTERNALERROR, "SERVER INTERNAL ERROR: Serve() returned a null response." ); else sendResponse( r.status, r.mimeType, r.header, r.data ); in.close(); } catch ( IOException ioe ) { try { sendError( HTTP_INTERNALERROR, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage()); } catch ( Throwable t ) {} } catch ( InterruptedException ie ) { // Thrown by sendError, ignore and exit the thread. } } /** * Decodes the percent encoding scheme.
* For example: "an+example%20string" -> "an example string" */ private String decodePercent( String str ) throws InterruptedException { try { StringBuffer sb = new StringBuffer(); for( int i=0; i= 0 ) uri = uri.substring(0, uri.indexOf( '?' )); // Prohibit getting out of current directory if ( uri.startsWith( ".." ) || uri.endsWith( ".." ) || uri.indexOf( "../" ) >= 0 ) return new Response( HTTP_FORBIDDEN, MIME_PLAINTEXT, "FORBIDDEN: Won't serve ../ for security reasons." ); File f = new File( homeDir, uri ); if ( !f.exists()) return new Response( HTTP_NOTFOUND, MIME_PLAINTEXT, "Error 404, file not found." ); // List the directory, if necessary if ( f.isDirectory()) { // Browsers get confused without '/' after the // directory, send a redirect. if ( !uri.endsWith( "/" )) { uri += "/"; Response r = new Response( HTTP_REDIRECT, MIME_HTML, "Redirected: " + uri + ""); r.addHeader( "Location", uri ); return r; } // First try index.html and index.htm if ( new File( f, "index.html" ).exists()) f = new File( homeDir, uri + "/index.html" ); else if ( new File( f, "index.htm" ).exists()) f = new File( homeDir, uri + "/index.htm" ); // No index file, list the directory else if ( allowDirectoryListing ) { String[] files = f.list(); String msg = "

Directory " + uri + "


"; if ( uri.length() > 1 ) { String u = uri.substring( 0, uri.length()-1 ); int slash = u.lastIndexOf( '/' ); if ( slash >= 0 && slash < u.length()) msg += "..
"; } for ( int i=0; i" + files[i] + ""; // Show file size if ( curFile.isFile()) { long len = curFile.length(); msg += "  ("; if ( len < 1024 ) msg += curFile.length() + " bytes"; else if ( len < 1024 * 1024 ) msg += curFile.length()/1024 + "." + (curFile.length()%1024/10%100) + " KB"; else msg += curFile.length()/(1024*1024) + "." + curFile.length()%(1024*1024)/10%100 + " MB"; msg += ")"; } msg += "
"; if ( dir ) msg += ""; } return new Response( HTTP_OK, MIME_HTML, msg ); } else { return new Response( HTTP_FORBIDDEN, MIME_PLAINTEXT, "FORBIDDEN: No directory listing." ); } } // Get MIME type from file name extension, if possible String mime = null; int dot = uri.lastIndexOf( '.' ); if ( dot >= 0 ) mime = (String)theMimeTypes.get( uri.substring( dot + 1 ).toLowerCase()); if ( mime == null ) mime = MIME_DEFAULT_BINARY; try { // Support (simple) skipping: long startFrom = 0; String range = header.getProperty( "Range" ); if ( range != null ) { if ( range.startsWith( "bytes=" )) { range = range.substring( "bytes=".length()); int minus = range.indexOf( '-' ); if ( minus > 0 ) range = range.substring( 0, minus ); try { startFrom = Long.parseLong( range ); } catch ( NumberFormatException nfe ) {} } } FileInputStream fis = new FileInputStream( f ); fis.skip( startFrom ); Response r = new Response( HTTP_OK, mime, fis ); r.addHeader( "Content-length", "" + (f.length() - startFrom)); r.addHeader( "Content-range", "" + startFrom + "-" + (f.length()-1) + "/" + f.length()); return r; } catch( IOException ioe ) { return new Response( HTTP_FORBIDDEN, MIME_PLAINTEXT, "FORBIDDEN: Reading file failed." ); } } /** * Hashtable mapping (String)FILENAME_EXTENSION -> (String)MIME_TYPE */ private static Hashtable theMimeTypes = new Hashtable(); static { StringTokenizer st = new StringTokenizer( "htm text/html "+ "html text/html "+ "txt text/plain "+ "asc text/plain "+ "gif image/gif "+ "jpg image/jpeg "+ "jpeg image/jpeg "+ "png image/png "+ "mp3 audio/mpeg "+ "m3u audio/mpeg-url " + "pdf application/pdf "+ "doc application/msword "+ "ogg application/x-ogg "+ "zip application/octet-stream "+ "exe application/octet-stream "+ "class application/octet-stream " ); while ( st.hasMoreTokens()) theMimeTypes.put( st.nextToken(), st.nextToken()); } /** * GMT date formatter */ private static java.text.SimpleDateFormat gmtFrmt; static { gmtFrmt = new java.text.SimpleDateFormat( "E, d MMM yyyy HH:mm:ss 'GMT'", Locale.US); gmtFrmt.setTimeZone(TimeZone.getTimeZone("GMT")); } /** * The distribution licence */ private static final String LICENCE = "Copyright (C) 2001,2005 by Jarno Elonen \n"+ "\n"+ "Redistribution and use in source and binary forms, with or without\n"+ "modification, are permitted provided that the following conditions\n"+ "are met:\n"+ "\n"+ "Redistributions of source code must retain the above copyright notice,\n"+ "this list of conditions and the following disclaimer. Redistributions in\n"+ "binary form must reproduce the above copyright notice, this list of\n"+ "conditions and the following disclaimer in the documentation and/or other\n"+ "materials provided with the distribution. The name of the author may not\n"+ "be used to endorse or promote products derived from this software without\n"+ "specific prior written permission. \n"+ " \n"+ "THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR\n"+ "IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES\n"+ "OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.\n"+ "IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,\n"+ "INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT\n"+ "NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n"+ "DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n"+ "THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n"+ "(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n"+ "OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."; }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy