Android端搭建web服务器

来源:互联网 发布:实时汇率查询软件 编辑:程序博客网 时间:2024/05/17 03:43


在Android假设服务器,其最终的结果就是根据预先设定好的端口和Url访问到你预先放好的资源。

步骤简单的概括几部就是


1,实现NanoHttpd类

2,设置端口和路径

3,访问实验。


先把源码放上:http://download.csdn.net/detail/u012455330/9132489


package webserve;import java.io.BufferedReader;import java.io.BufferedWriter;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.Closeable;import java.io.DataOutput;import java.io.DataOutputStream;import java.io.File;import java.io.FileOutputStream;import java.io.FilterOutputStream;import java.io.IOException;import java.io.InputStream;import java.io.InputStreamReader;import java.io.OutputStream;import java.io.OutputStreamWriter;import java.io.PrintWriter;import java.io.PushbackInputStream;import java.io.RandomAccessFile;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.ByteBuffer;import java.nio.channels.FileChannel;import java.nio.charset.Charset;import java.security.KeyStore;import java.text.SimpleDateFormat;import java.util.ArrayList;import java.util.Calendar;import java.util.Collections;import java.util.Date;import java.util.HashMap;import java.util.Iterator;import java.util.List;import java.util.Locale;import java.util.Map;import java.util.StringTokenizer;import java.util.TimeZone;import java.util.logging.Level;import java.util.logging.Logger;import java.util.regex.Matcher;import java.util.regex.Pattern;import java.util.zip.GZIPOutputStream;import javax.net.ssl.KeyManager;import javax.net.ssl.KeyManagerFactory;import javax.net.ssl.SSLContext;import javax.net.ssl.SSLServerSocket;import javax.net.ssl.SSLServerSocketFactory;import javax.net.ssl.TrustManagerFactory;import webserve.NanoHTTPD.Response.IStatus;import webserve.NanoHTTPD.Response.Status;/** * 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 {/** * Pluggable strategy for asynchronously executing requests. */public interface AsyncRunner {void closeAll();void closed(ClientHandler clientHandler);void exec(ClientHandler code);}/** * The runnable that will be used for every new client connection. */public class ClientHandler implements Runnable {private final InputStream inputStream;private final Socket acceptSocket;private ClientHandler(InputStream inputStream, Socket acceptSocket) {this.inputStream = inputStream;this.acceptSocket = acceptSocket;}public void close() {safeClose(this.inputStream);safeClose(this.acceptSocket);}@Overridepublic void run() {OutputStream outputStream = null;try {outputStream = this.acceptSocket.getOutputStream();TempFileManager tempFileManager = NanoHTTPD.this.tempFileManagerFactory.create();HTTPSession session = new HTTPSession(tempFileManager, this.inputStream, outputStream,this.acceptSocket.getInetAddress());while (!this.acceptSocket.isClosed()) {session.execute();}} catch (Exception e) {// When the socket is closed by the client,// we throw our own SocketException// to break the "keep alive" loop above. If// the exception was anything other// than the expected SocketException OR a// SocketTimeoutException, print the// stacktraceif (!(e instanceof SocketException && "NanoHttpd Shutdown".equals(e.getMessage()))&& !(e instanceof SocketTimeoutException)) {NanoHTTPD.LOG.log(Level.FINE, "Communication with the client broken", e);}} finally {safeClose(outputStream);safeClose(this.inputStream);safeClose(this.acceptSocket);NanoHTTPD.this.asyncRunner.closed(this);}}}public static class Cookie {public static String getHTTPTime(int days) {Calendar calendar = Calendar.getInstance();SimpleDateFormat dateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));calendar.add(Calendar.DAY_OF_MONTH, days);return dateFormat.format(calendar.getTime());}private final String n, v, e;public Cookie(String name, String value) {this(name, value, 30);}public Cookie(String name, String value, int numDays) {this.n = name;this.v = value;this.e = getHTTPTime(numDays);}public Cookie(String name, String value, String expires) {this.n = name;this.v = value;this.e = expires;}public String getHTTPHeader() {String fmt = "%s=%s; expires=%s";return String.format(fmt, this.n, this.v, this.e);}}/** * Provides rudimentary support for cookies. Doesn't support 'path', * 'secure' nor 'httpOnly'. Feel free to improve it and/or add unsupported * features. *  * @author LordFokas */public class CookieHandler implements Iterable<String> {private final HashMap<String, String> cookies = new HashMap<String, String>();private final ArrayList<Cookie> queue = new ArrayList<Cookie>();public CookieHandler(Map<String, String> httpHeaders) {String raw = httpHeaders.get("cookie");if (raw != null) {String[] tokens = raw.split(";");for (String token : tokens) {String[] data = token.trim().split("=");if (data.length == 2) {this.cookies.put(data[0], data[1]);}}}}/** * Set a cookie with an expiration date from a month ago, effectively * deleting it on the client side. *  * @param name *            The cookie name. */public void delete(String name) {set(name, "-delete-", -30);}@Overridepublic Iterator<String> iterator() {return this.cookies.keySet().iterator();}/** * Read a cookie from the HTTP Headers. *  * @param name *            The cookie's name. * @return The cookie's value if it exists, null otherwise. */public String read(String name) {return this.cookies.get(name);}public void set(Cookie cookie) {this.queue.add(cookie);}/** * Sets a cookie. *  * @param name *            The cookie's name. * @param value *            The cookie's value. * @param expires *            How many days until the cookie expires. */public void set(String name, String value, int expires) {this.queue.add(new Cookie(name, value, Cookie.getHTTPTime(expires)));}/** * Internally used by the webserver to add all queued cookies into the * Response's HTTP Headers. *  * @param response *            The Response object to which headers the queued cookies *            will be added. */public void unloadQueue(Response response) {for (Cookie cookie : this.queue) {response.addHeader("Set-Cookie", cookie.getHTTPHeader());}}}/** * Default threading strategy for NanoHTTPD. * <p/> * <p> * By default, the server spawns a new Thread for every incoming request. * These are set to <i>daemon</i> status, and named according to the request * number. The name is useful when profiling the application. * </p> */public static class DefaultAsyncRunner implements AsyncRunner {private long requestCount;private final List<ClientHandler> running = Collections.synchronizedList(new ArrayList<NanoHTTPD.ClientHandler>());/** * @return a list with currently running clients. */public List<ClientHandler> getRunning() {return running;}@Overridepublic void closeAll() {// copy of the list for concurrencyfor (ClientHandler clientHandler : new ArrayList<ClientHandler>(this.running)) {clientHandler.close();}}@Overridepublic void closed(ClientHandler clientHandler) {this.running.remove(clientHandler);}@Overridepublic void exec(ClientHandler clientHandler) {++this.requestCount;Thread t = new Thread(clientHandler);t.setDaemon(true);t.setName("NanoHttpd Request Processor (#" + this.requestCount + ")");this.running.add(clientHandler);t.start();}}/** * Default strategy for creating and cleaning up temporary files. * <p/> * <p> * By default, files are created by <code>File.createTempFile()</code> in * the directory specified. * </p> */public static class DefaultTempFile implements TempFile {private final File file;private final OutputStream fstream;public DefaultTempFile(String tempdir) throws IOException {this.file = File.createTempFile("NanoHTTPD-", "", new File(tempdir));this.fstream = new FileOutputStream(this.file);}@Overridepublic void delete() throws Exception {safeClose(this.fstream);if (!this.file.delete()) {throw new Exception("could not delete temporary file");}}@Overridepublic String getName() {return this.file.getAbsolutePath();}@Overridepublic OutputStream open() throws Exception {return this.fstream;}}/** * Default strategy for creating and cleaning up temporary files. * <p/> * <p> * This class stores its files in the standard location (that is, wherever * <code>java.io.tmpdir</code> points to). Files are added to an internal * list, and deleted when no longer needed (that is, when * <code>clear()</code> is invoked at the end of processing a request). * </p> */public static class DefaultTempFileManager implements TempFileManager {private final String tmpdir;private final List<TempFile> tempFiles;public DefaultTempFileManager() {this.tmpdir = System.getProperty("java.io.tmpdir");this.tempFiles = new ArrayList<TempFile>();}@Overridepublic void clear() {for (TempFile file : this.tempFiles) {try {file.delete();} catch (Exception ignored) {NanoHTTPD.LOG.log(Level.WARNING, "could not delete file ", ignored);}}this.tempFiles.clear();}@Overridepublic TempFile createTempFile(String filename_hint) throws Exception {DefaultTempFile tempFile = new DefaultTempFile(this.tmpdir);this.tempFiles.add(tempFile);return tempFile;}}/** * Default strategy for creating and cleaning up temporary files. */private class DefaultTempFileManagerFactory implements TempFileManagerFactory {@Overridepublic TempFileManager create() {return new DefaultTempFileManager();}}private static final String CONTENT_DISPOSITION_REGEX = "([ |\t]*Content-Disposition[ |\t]*:)(.*)";private static final Pattern CONTENT_DISPOSITION_PATTERN = Pattern.compile(CONTENT_DISPOSITION_REGEX,Pattern.CASE_INSENSITIVE);private static final String CONTENT_TYPE_REGEX = "([ |\t]*content-type[ |\t]*:)(.*)";private static final Pattern CONTENT_TYPE_PATTERN = Pattern.compile(CONTENT_TYPE_REGEX, Pattern.CASE_INSENSITIVE);private static final String CONTENT_DISPOSITION_ATTRIBUTE_REGEX = "[ |\t]*([a-zA-Z]*)[ |\t]*=[ |\t]*['|\"]([^\"^']*)['|\"]";private static final Pattern CONTENT_DISPOSITION_ATTRIBUTE_PATTERN = Pattern.compile(CONTENT_DISPOSITION_ATTRIBUTE_REGEX);protected class HTTPSession implements IHTTPSession {private static final int REQUEST_BUFFER_LEN = 512;private static final int MEMORY_STORE_LIMIT = 1024;public static final int BUFSIZE = 8192;private final TempFileManager tempFileManager;private final OutputStream outputStream;private final PushbackInputStream inputStream;private int splitbyte;private int rlen;private String uri;private Method method;private Map<String, String> parms;private Map<String, String> headers;private CookieHandler cookies;private String queryParameterString;private String remoteIp;private String protocolVersion;public HTTPSession(TempFileManager tempFileManager, InputStream inputStream, OutputStream outputStream) {this.tempFileManager = tempFileManager;this.inputStream = new PushbackInputStream(inputStream, HTTPSession.BUFSIZE);this.outputStream = outputStream;}public HTTPSession(TempFileManager tempFileManager, InputStream inputStream, OutputStream outputStream,InetAddress inetAddress) {this.tempFileManager = tempFileManager;this.inputStream = new PushbackInputStream(inputStream, HTTPSession.BUFSIZE);this.outputStream = outputStream;this.remoteIp = inetAddress.isLoopbackAddress() || inetAddress.isAnyLocalAddress() ? "127.0.0.1": inetAddress.getHostAddress().toString();this.headers = new HashMap<String, String>();}/** * 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 lineString 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 URIint 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.// NOTE: this now forces header names lower case since they are// case insensitive and vary by client.if (st.hasMoreTokens()) {protocolVersion = st.nextToken();} else {protocolVersion = "HTTP/1.1";NanoHTTPD.LOG.log(Level.FINE, "no protocol version specified, strange. Assuming HTTP/1.1.");}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);}}/** * Decodes the Multipart Body data and put it into Key/Value pairs. */private void decodeMultipartFormData(String boundary, ByteBuffer fbuf, Map<String, String> parms,Map<String, String> files) throws ResponseException {try {int[] boundary_idxs = getBoundaryPositions(fbuf, boundary.getBytes());if (boundary_idxs.length < 2) {throw new ResponseException(Response.Status.BAD_REQUEST,"BAD REQUEST: Content type is multipart/form-data but contains less than two boundary strings.");}final int MAX_HEADER_SIZE = 1024;byte[] part_header_buff = new byte[MAX_HEADER_SIZE];for (int bi = 0; bi < boundary_idxs.length - 1; bi++) {fbuf.position(boundary_idxs[bi]);int len = (fbuf.remaining() < MAX_HEADER_SIZE) ? fbuf.remaining() : MAX_HEADER_SIZE;fbuf.get(part_header_buff, 0, len);ByteArrayInputStream bais = new ByteArrayInputStream(part_header_buff, 0, len);BufferedReader in = new BufferedReader(new InputStreamReader(bais, Charset.forName("US-ASCII")));// First line is boundary stringString mpline = in.readLine();if (!mpline.contains(boundary)) {throw new ResponseException(Response.Status.BAD_REQUEST,"BAD REQUEST: Content type is multipart/form-data but chunk does not start with boundary.");}String part_name = null, file_name = null, content_type = null;// Parse the reset of the header linesmpline = in.readLine();while (mpline != null && mpline.trim().length() > 0) {Matcher matcher = CONTENT_DISPOSITION_PATTERN.matcher(mpline);if (matcher.matches()) {String attributeString = matcher.group(2);matcher = CONTENT_DISPOSITION_ATTRIBUTE_PATTERN.matcher(attributeString);while (matcher.find()) {String key = matcher.group(1);if (key.equalsIgnoreCase("name")) {part_name = matcher.group(2);} else if (key.equalsIgnoreCase("filename")) {file_name = matcher.group(2);}}}matcher = CONTENT_TYPE_PATTERN.matcher(mpline);if (matcher.matches()) {content_type = matcher.group(2).trim();}mpline = in.readLine();}// Read the part dataint part_header_len = len - (int) in.skip(MAX_HEADER_SIZE);if (part_header_len >= len - 4) {throw new ResponseException(Response.Status.INTERNAL_ERROR,"Multipart header size exceeds MAX_HEADER_SIZE.");}int part_data_start = boundary_idxs[bi] + part_header_len;int part_data_end = boundary_idxs[bi + 1] - 4;fbuf.position(part_data_start);if (content_type == null) {// Read the part into a stringbyte[] data_bytes = new byte[part_data_end - part_data_start];fbuf.get(data_bytes);parms.put(part_name, new String(data_bytes));} else {// Read it into a fileString path = saveTmpFile(fbuf, part_data_start, part_data_end - part_data_start, file_name);if (!files.containsKey(part_name)) {files.put(part_name, path);} else {int count = 2;while (files.containsKey(part_name + count)) {count++;}files.put(part_name + count, path);}parms.put(part_name, file_name);}}} catch (ResponseException re) {throw re;} catch (Exception e) {throw new ResponseException(Response.Status.INTERNAL_ERROR, e.toString());}}/** * 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) {this.queryParameterString = "";return;}this.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(), "");}}}@Overridepublic void execute() throws IOException {Response r = null;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[HTTPSession.BUFSIZE];this.splitbyte = 0;this.rlen = 0;int read = -1;try {read = this.inputStream.read(buf, 0, HTTPSession.BUFSIZE);} catch (Exception e) {safeClose(this.inputStream);safeClose(this.outputStream);throw new SocketException("NanoHttpd Shutdown");}if (read == -1) {// socket was been closedsafeClose(this.inputStream);safeClose(this.outputStream);throw new SocketException("NanoHttpd Shutdown");}while (read > 0) {this.rlen += read;this.splitbyte = findHeaderEnd(buf, this.rlen);if (this.splitbyte > 0) {break;}read = this.inputStream.read(buf, this.rlen, HTTPSession.BUFSIZE - this.rlen);}if (this.splitbyte < this.rlen) {this.inputStream.unread(buf, this.splitbyte, this.rlen - this.splitbyte);}this.parms = new HashMap<String, String>();if (null == this.headers) {this.headers = new HashMap<String, String>();} else {this.headers.clear();}if (null != this.remoteIp) {this.headers.put("remote-addr", this.remoteIp);this.headers.put("http-client-ip", this.remoteIp);}// Create a BufferedReader for parsing the header.BufferedReader hin = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(buf, 0, this.rlen)));// Decode the header into parms and header java propertiesMap<String, String> pre = new HashMap<String, String>();decodeHeader(hin, pre, this.parms, this.headers);this.method = Method.lookup(pre.get("method"));if (this.method == null) {throw new ResponseException(Response.Status.BAD_REQUEST, "BAD REQUEST: Syntax error.");}this.uri = pre.get("uri");this.cookies = new CookieHandler(this.headers);String connection = this.headers.get("connection");boolean keepAlive = protocolVersion.equals("HTTP/1.1")&& (connection == null || !connection.matches("(?i).*close.*"));// Ok, now do the serve()// TODO: long body_size = getBodySize();// TODO: long pos_before_serve = this.inputStream.totalRead()// (requires implementaion for totalRead())r = serve(this);// TODO: this.inputStream.skip(body_size -// (this.inputStream.totalRead() - pos_before_serve))if (r == null) {throw new ResponseException(Response.Status.INTERNAL_ERROR,"SERVER INTERNAL ERROR: Serve() returned a null response.");} else {String acceptEncoding = this.headers.get("accept-encoding");this.cookies.unloadQueue(r);r.setRequestMethod(this.method);r.setGzipEncoding(useGzipWhenAccepted(r) && acceptEncoding != null && acceptEncoding.contains("gzip"));r.setKeepAlive(keepAlive);r.send(this.outputStream);}if (!keepAlive || "close".equalsIgnoreCase(r.getHeader("connection"))) {throw new SocketException("NanoHttpd Shutdown");}} catch (SocketException e) {// throw it out to close socket object (finalAccept)throw e;} catch (SocketTimeoutException ste) {// treat socket timeouts the same way we treat socket exceptions// i.e. close the stream & finalAccept object by throwing the// exception up the call stack.throw ste;} catch (IOException ioe) {Response resp = newFixedLengthResponse(Response.Status.INTERNAL_ERROR, NanoHTTPD.MIME_PLAINTEXT,"SERVER INTERNAL ERROR: IOException: " + ioe.getMessage());resp.send(this.outputStream);safeClose(this.outputStream);} catch (ResponseException re) {Response resp = newFixedLengthResponse(re.getStatus(), NanoHTTPD.MIME_PLAINTEXT, re.getMessage());resp.send(this.outputStream);safeClose(this.outputStream);} finally {safeClose(r);this.tempFileManager.clear();}}/** * 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;}/** * Find the byte positions where multipart boundaries start. This reads * a large block at a time and uses a temporary buffer to optimize * (memory mapped) file access. */private int[] getBoundaryPositions(ByteBuffer b, byte[] boundary) {int[] res = new int[0];if (b.remaining() < boundary.length) {return res;}int search_window_pos = 0;byte[] search_window = new byte[4 * 1024 + boundary.length];int first_fill = (b.remaining() < search_window.length) ? b.remaining() : search_window.length;b.get(search_window, 0, first_fill);int new_bytes = first_fill - boundary.length;do {// Search the search_windowfor (int j = 0; j < new_bytes; j++) {for (int i = 0; i < boundary.length; i++) {if (search_window[j + i] != boundary[i])break;if (i == boundary.length - 1) {// Match found, add it to resultsint[] new_res = new int[res.length + 1];System.arraycopy(res, 0, new_res, 0, res.length);new_res[res.length] = search_window_pos + j;res = new_res;}}}search_window_pos += new_bytes;// Copy the end of the buffer to the startSystem.arraycopy(search_window, search_window.length - boundary.length, search_window, 0,boundary.length);// Refill search_windownew_bytes = search_window.length - boundary.length;new_bytes = (b.remaining() < new_bytes) ? b.remaining() : new_bytes;b.get(search_window, boundary.length, new_bytes);} while (new_bytes > 0);return res;}@Overridepublic CookieHandler getCookies() {return this.cookies;}@Overridepublic final Map<String, String> getHeaders() {return this.headers;}@Overridepublic final InputStream getInputStream() {return this.inputStream;}@Overridepublic final Method getMethod() {return this.method;}@Overridepublic final Map<String, String> getParms() {return this.parms;}@Overridepublic String getQueryParameterString() {return this.queryParameterString;}private RandomAccessFile getTmpBucket() {try {TempFile tempFile = this.tempFileManager.createTempFile(null);return new RandomAccessFile(tempFile.getName(), "rw");} catch (Exception e) {throw new Error(e); // we won't recover, so throw an error}}@Overridepublic final String getUri() {return this.uri;}/** * Deduce body length in bytes. Either from "content-length" header or * read bytes. */public long getBodySize() {if (this.headers.containsKey("content-length")) {return Integer.parseInt(this.headers.get("content-length"));} else if (this.splitbyte < this.rlen) {return this.rlen - this.splitbyte;}return 0;}@Overridepublic void parseBody(Map<String, String> files) throws IOException, ResponseException {RandomAccessFile randomAccessFile = null;try {long size = getBodySize();ByteArrayOutputStream baos = null;DataOutput request_data_output = null;// Store the request in memory or a file, depending on sizeif (size < MEMORY_STORE_LIMIT) {baos = new ByteArrayOutputStream();request_data_output = new DataOutputStream(baos);} else {randomAccessFile = getTmpBucket();request_data_output = randomAccessFile;}// Read all the body and write it to request_data_outputbyte[] buf = new byte[REQUEST_BUFFER_LEN];while (this.rlen >= 0 && size > 0) {this.rlen = this.inputStream.read(buf, 0, (int) Math.min(size, REQUEST_BUFFER_LEN));size -= this.rlen;if (this.rlen > 0) {request_data_output.write(buf, 0, this.rlen);}}ByteBuffer fbuf = null;if (baos != null) {fbuf = ByteBuffer.wrap(baos.toByteArray(), 0, baos.size());} else {fbuf = randomAccessFile.getChannel().map(FileChannel.MapMode.READ_ONLY, 0,randomAccessFile.length());randomAccessFile.seek(0);}// If the method is POST, there may be parameters// in data section, too, read it:if (Method.POST.equals(this.method)) {String contentType = "";String contentTypeHeader = this.headers.get("content-type");StringTokenizer st = null;if (contentTypeHeader != null) {st = new StringTokenizer(contentTypeHeader, ",; ");if (st.hasMoreTokens()) {contentType = st.nextToken();}}if ("multipart/form-data".equalsIgnoreCase(contentType)) {// Handle multipart/form-dataif (!st.hasMoreTokens()) {throw new ResponseException(Response.Status.BAD_REQUEST,"BAD REQUEST: Content type is multipart/form-data but boundary missing. Usage: GET /example/file.html");}String boundaryStartString = "boundary=";int boundaryContentStart = contentTypeHeader.indexOf(boundaryStartString)+ boundaryStartString.length();String boundary = contentTypeHeader.substring(boundaryContentStart, contentTypeHeader.length());if (boundary.startsWith("\"") && boundary.endsWith("\"")) {boundary = boundary.substring(1, boundary.length() - 1);}decodeMultipartFormData(boundary, fbuf, this.parms, files);} else {byte[] postBytes = new byte[fbuf.remaining()];fbuf.get(postBytes);String postLine = new String(postBytes).trim();// Handle application/x-www-form-urlencodedif ("application/x-www-form-urlencoded".equalsIgnoreCase(contentType)) {decodeParms(postLine, this.parms);} else if (postLine.length() != 0) {// Special case for raw POST data => create a// special files entry "postData" with raw content// datafiles.put("postData", postLine);}}} else if (Method.PUT.equals(this.method)) {files.put("content", saveTmpFile(fbuf, 0, fbuf.limit(), null));}} finally {safeClose(randomAccessFile);}}/** * Retrieves the content of a sent file and saves it to a temporary * file. The full path to the saved file is returned. */private String saveTmpFile(ByteBuffer b, int offset, int len, String filename_hint) {String path = "";if (len > 0) {FileOutputStream fileOutputStream = null;try {TempFile tempFile = this.tempFileManager.createTempFile(filename_hint);ByteBuffer src = b.duplicate();fileOutputStream = new FileOutputStream(tempFile.getName());FileChannel dest = fileOutputStream.getChannel();src.position(offset).limit(offset + len);dest.write(src.slice());path = tempFile.getName();} catch (Exception e) { // Catch exception if anythrow new Error(e); // we won't recover, so throw an error} finally {safeClose(fileOutputStream);}}return path;}}/** * Handles one session, i.e. parses the HTTP request and returns the * response. */public interface IHTTPSession {void execute() throws IOException;CookieHandler getCookies();Map<String, String> getHeaders();InputStream getInputStream();Method getMethod();Map<String, String> getParms();String getQueryParameterString();/** * @return the path part of the URL. */String getUri();/** * 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;}/** * 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, TRACE, CONNECT, PATCH;static Method lookup(String method) {for (Method m : Method.values()) {if (m.toString().equalsIgnoreCase(method)) {return m;}}return null;}}/** * HTTP response. Return one of these from serve(). */public static class Response implements Closeable {public interface IStatus {String getDescription();int getRequestStatus();}/** * 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"), NOT_ACCEPTABLE(406,"Not Acceptable"), REQUEST_TIMEOUT(408,"Request Timeout"), CONFLICT(409,"Conflict"), RANGE_NOT_SATISFIABLE(416,"Requested Range Not Satisfiable"), INTERNAL_ERROR(500,"Internal Server Error"), NOT_IMPLEMENTED(501,"Not Implemented"), UNSUPPORTED_HTTP_VERSION(505,"HTTP Version Not Supported");private final int requestStatus;private final String description;Status(int requestStatus, String description) {this.requestStatus = requestStatus;this.description = description;}@Overridepublic String getDescription() {return "" + this.requestStatus + " " + this.description;}@Overridepublic int getRequestStatus() {return this.requestStatus;}}/** * Output stream that will automatically send every write to the wrapped * OutputStream according to chunked transfer: * http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6.1 */private static class ChunkedOutputStream extends FilterOutputStream {public ChunkedOutputStream(OutputStream out) {super(out);}@Overridepublic void write(int b) throws IOException {byte[] data = { (byte) b };write(data, 0, 1);}@Overridepublic void write(byte[] b) throws IOException {write(b, 0, b.length);}@Overridepublic void write(byte[] b, int off, int len) throws IOException {if (len == 0)return;out.write(String.format("%x\r\n", len).getBytes());out.write(b, off, len);out.write("\r\n".getBytes());}public void finish() throws IOException {out.write("0\r\n\r\n".getBytes());}}/** * 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;private long contentLength;/** * Headers for the HTTP response. Use addHeader() to add lines. */private final Map<String, String> header = new HashMap<String, String>();/** * The request method that spawned this response. */private Method requestMethod;/** * Use chunkedTransfer */private boolean chunkedTransfer;private boolean encodeAsGzip;private boolean keepAlive;/** * Creates a fixed length response if totalBytes>=0, otherwise chunked. */public Response(IStatus status, String mimeType, InputStream data, long totalBytes) {this.status = status;this.mimeType = mimeType;if (data == null) {this.data = new ByteArrayInputStream(new byte[0]);this.contentLength = 0L;} else {this.data = data;this.contentLength = totalBytes;}this.chunkedTransfer = this.contentLength < 0;keepAlive = true;}@Overridepublic void close() throws IOException {if (this.data != null) {this.data.close();}}/** * Adds given line to the header. */public void addHeader(String name, String value) {this.header.put(name, value);}public InputStream getData() {return this.data;}public String getHeader(String name) {for (String headerName : header.keySet()) {if (headerName.equalsIgnoreCase(name)) {return header.get(headerName);}}return null;}public String getMimeType() {return this.mimeType;}public Method getRequestMethod() {return this.requestMethod;}public IStatus getStatus() {return this.status;}public void setGzipEncoding(boolean encodeAsGzip) {this.encodeAsGzip = encodeAsGzip;}public void setKeepAlive(boolean useKeepAlive) {this.keepAlive = useKeepAlive;}private static boolean headerAlreadySent(Map<String, String> header, String name) {boolean alreadySent = false;for (String headerName : header.keySet()) {alreadySent |= headerName.equalsIgnoreCase(name);}return alreadySent;}/** * Sends given response to the socket. */protected void send(OutputStream outputStream) {String mime = this.mimeType;SimpleDateFormat gmtFrmt = new SimpleDateFormat("E, d MMM yyyy HH:mm:ss 'GMT'", Locale.US);gmtFrmt.setTimeZone(TimeZone.getTimeZone("GMT"));try {if (this.status == null) {throw new Error("sendResponse(): Status can't be null.");}PrintWriter pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(outputStream, "UTF-8")),false);pw.print("HTTP/1.1 " + this.status.getDescription() + " \r\n");if (mime != null) {pw.print("Content-Type: " + mime + "\r\n");}if (this.header == null || this.header.get("Date") == null) {pw.print("Date: " + gmtFrmt.format(new Date()) + "\r\n");}if (this.header != null) {for (String key : this.header.keySet()) {String value = this.header.get(key);pw.print(key + ": " + value + "\r\n");}}if (!headerAlreadySent(header, "connection")) {pw.print("Connection: " + (this.keepAlive ? "keep-alive" : "close") + "\r\n");}if (headerAlreadySent(this.header, "content-length")) {encodeAsGzip = false;}if (encodeAsGzip) {pw.print("Content-Encoding: gzip\r\n");setChunkedTransfer(true);}long pending = this.data != null ? this.contentLength : 0;if (this.requestMethod != Method.HEAD && this.chunkedTransfer) {pw.print("Transfer-Encoding: chunked\r\n");} else if (!encodeAsGzip) {pending = sendContentLengthHeaderIfNotAlreadyPresent(pw, this.header, pending);}pw.print("\r\n");pw.flush();sendBodyWithCorrectTransferAndEncoding(outputStream, pending);outputStream.flush();safeClose(this.data);} catch (IOException ioe) {NanoHTTPD.LOG.log(Level.SEVERE, "Could not send response to the client", ioe);}}private void sendBodyWithCorrectTransferAndEncoding(OutputStream outputStream, long pending)throws IOException {if (this.requestMethod != Method.HEAD && this.chunkedTransfer) {ChunkedOutputStream chunkedOutputStream = new ChunkedOutputStream(outputStream);sendBodyWithCorrectEncoding(chunkedOutputStream, -1);chunkedOutputStream.finish();} else {sendBodyWithCorrectEncoding(outputStream, pending);}}private void sendBodyWithCorrectEncoding(OutputStream outputStream, long pending) throws IOException {if (encodeAsGzip) {GZIPOutputStream gzipOutputStream = new GZIPOutputStream(outputStream);sendBody(gzipOutputStream, -1);gzipOutputStream.finish();} else {sendBody(outputStream, pending);}}/** * Sends the body to the specified OutputStream. The pending parameter * limits the maximum amounts of bytes sent unless it is -1, in which * case everything is sent. *  * @param outputStream *            the OutputStream to send data to * @param pending *            -1 to send everything, otherwise sets a max limit to the *            number of bytes sent * @throws IOException *             if something goes wrong while sending the data. */private void sendBody(OutputStream outputStream, long pending) throws IOException {long BUFFER_SIZE = 16 * 1024;byte[] buff = new byte[(int) BUFFER_SIZE];boolean sendEverything = pending == -1;while (pending > 0 || sendEverything) {long bytesToRead = sendEverything ? BUFFER_SIZE : Math.min(pending, BUFFER_SIZE);int read = this.data.read(buff, 0, (int) bytesToRead);if (read <= 0) {break;}outputStream.write(buff, 0, read);if (!sendEverything) {pending -= read;}}}protected static long sendContentLengthHeaderIfNotAlreadyPresent(PrintWriter pw, Map<String, String> header,long size) {for (String headerName : header.keySet()) {if (headerName.equalsIgnoreCase("content-length")) {try {return Long.parseLong(header.get(headerName));} catch (NumberFormatException ex) {return size;}}}pw.print("Content-Length: " + size + "\r\n");return size;}public void setChunkedTransfer(boolean chunkedTransfer) {this.chunkedTransfer = chunkedTransfer;}public void setData(InputStream data) {this.data = data;}public void setMimeType(String mimeType) {this.mimeType = mimeType;}public void setRequestMethod(Method requestMethod) {this.requestMethod = requestMethod;}public void setStatus(IStatus status) {this.status = status;}}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 this.status;}}/** * The runnable that will be used for the main listening thread. */public class ServerRunnable implements Runnable {private final int timeout;private IOException bindException;private boolean hasBinded = false;private ServerRunnable(int timeout) {this.timeout = timeout;}@Overridepublic void run() {try {myServerSocket.bind(hostname != null ? new InetSocketAddress(hostname, myPort) : new InetSocketAddress(myPort));hasBinded = true;} catch (IOException e) {this.bindException = e;return;}do {try {final Socket finalAccept = NanoHTTPD.this.myServerSocket.accept();if (this.timeout > 0) {finalAccept.setSoTimeout(this.timeout);}final InputStream inputStream = finalAccept.getInputStream();NanoHTTPD.this.asyncRunner.exec(createClientHandler(finalAccept, inputStream));} catch (IOException e) {NanoHTTPD.LOG.log(Level.FINE, "Communication with the client broken", e);}} while (!NanoHTTPD.this.myServerSocket.isClosed());}}/** * A temp file. * <p/> * <p> * Temp files are responsible for managing the actual temporary storage and * cleaning themselves up when no longer needed. * </p> */public interface TempFile {void delete() throws Exception;String getName();OutputStream open() throws Exception;}/** * Temp file manager. * <p/> * <p> * Temp file managers are created 1-to-1 with incoming requests, to create * and cleanup temporary files created as a result of handling the request. * </p> */public interface TempFileManager {void clear();TempFile createTempFile(String filename_hint) throws Exception;}/** * Factory to create temp file managers. */public interface TempFileManagerFactory {TempFileManager create();}/** * 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 = 5000;/** * Common MIME type for dynamic content: plain text */public static final String MIME_PLAINTEXT = "text/plain";/** * Common MIME type for dynamic content: html */public static final String MIME_HTML = "text/html";/** * Pseudo-Parameter to use to store the actual query string in the * parameters map for later re-processing. */private static final String QUERY_STRING_PARAMETER = "NanoHttpd.QUERY_STRING";/** * logger to log to. */private static final Logger LOG = Logger.getLogger(NanoHTTPD.class.getName());/** * Creates an SSLSocketFactory for HTTPS. Pass a loaded KeyStore and an * array of loaded KeyManagers. These objects must properly * loaded/initialized by the caller. */public static SSLServerSocketFactory makeSSLSocketFactory(KeyStore loadedKeyStore, KeyManager[] keyManagers)throws IOException {SSLServerSocketFactory res = null;try {TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());trustManagerFactory.init(loadedKeyStore);SSLContext ctx = SSLContext.getInstance("TLS");ctx.init(keyManagers, trustManagerFactory.getTrustManagers(), null);res = ctx.getServerSocketFactory();} catch (Exception e) {throw new IOException(e.getMessage());}return res;}/** * Creates an SSLSocketFactory for HTTPS. Pass a loaded KeyStore and a * loaded KeyManagerFactory. These objects must properly loaded/initialized * by the caller. */public static SSLServerSocketFactory makeSSLSocketFactory(KeyStore loadedKeyStore,KeyManagerFactory loadedKeyFactory) throws IOException {try {return makeSSLSocketFactory(loadedKeyStore, loadedKeyFactory.getKeyManagers());} catch (Exception e) {throw new IOException(e.getMessage());}}/** * Creates an SSLSocketFactory for HTTPS. Pass a KeyStore resource with your * certificate and passphrase */public static SSLServerSocketFactory makeSSLSocketFactory(String keyAndTrustStoreClasspathPath, char[] passphrase)throws IOException {try {KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());InputStream keystoreStream = NanoHTTPD.class.getResourceAsStream(keyAndTrustStoreClasspathPath);keystore.load(keystoreStream, passphrase);KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());keyManagerFactory.init(keystore, passphrase);return makeSSLSocketFactory(keystore, keyManagerFactory);} catch (Exception e) {throw new IOException(e.getMessage());}}private static final void safeClose(Object closeable) {try {if (closeable != null) {if (closeable instanceof Closeable) {((Closeable) closeable).close();} else if (closeable instanceof Socket) {((Socket) closeable).close();} else if (closeable instanceof ServerSocket) {((ServerSocket) closeable).close();} else {throw new IllegalArgumentException("Unknown object to close");}}} catch (IOException e) {NanoHTTPD.LOG.log(Level.SEVERE, "Could not close", e);}}private final String hostname;private final int myPort;private volatile ServerSocket myServerSocket;private SSLServerSocketFactory sslServerSocketFactory;private Thread myThread;/** * Pluggable strategy for asynchronously executing requests. */protected AsyncRunner asyncRunner;/** * Pluggable strategy for creating and cleaning up temporary files. */private TempFileManagerFactory tempFileManagerFactory;/** * Constructs an HTTP server on given port. */public NanoHTTPD(int port) {this(null, port);}// -------------------------------------------------------------------------------// ////// Threading Strategy.//// -------------------------------------------------------------------------------// ///** * Constructs an HTTP server on given hostname and port. */public NanoHTTPD(String hostname, int port) {this.hostname = hostname;this.myPort = port;setTempFileManagerFactory(new DefaultTempFileManagerFactory());setAsyncRunner(new DefaultAsyncRunner());}/** * Forcibly closes all connections that are open. */public synchronized void closeAllConnections() {stop();}/** * create a instance of the client handler, subclasses can return a subclass * of the ClientHandler. *  * @param finalAccept *            the socket the cleint is connected to * @param inputStream *            the input stream * @return the client handler */protected ClientHandler createClientHandler(final Socket finalAccept, final InputStream inputStream) {return new ClientHandler(inputStream, finalAccept);}/** * Instantiate the server runnable, can be overwritten by subclasses to * provide a subclass of the ServerRunnable. *  * @param timeout *            the socet timeout to use. * @return the server runnable. */protected ServerRunnable createServerRunnable(final int timeout) {return new ServerRunnable(timeout);}/** * Decode parameters from a URL, handing the case where a single parameter * name might have been supplied several times, by return lists of values. * In general these lists will contain a single element. *  * @param parms *            original <b>NanoHTTPD</b> parameters values, as passed to the *            <code>serve()</code> method. * @return a map of <code>String</code> (parameter name) to *         <code>List<String></code> (a list of the values supplied). */protected static Map<String, List<String>> decodeParameters(Map<String, String> parms) {return decodeParameters(parms.get(NanoHTTPD.QUERY_STRING_PARAMETER));}// -------------------------------------------------------------------------------// ///** * Decode parameters from a URL, handing the case where a single parameter * name might have been supplied several times, by return lists of values. * In general these lists will contain a single element. *  * @param queryString *            a query string pulled from the URL. * @return a map of <code>String</code> (parameter name) to *         <code>List<String></code> (a list of the values supplied). */protected static 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;}/** * 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 static String decodePercent(String str) {String decoded = null;try {decoded = URLDecoder.decode(str, "UTF8");} catch (UnsupportedEncodingException ignored) {NanoHTTPD.LOG.log(Level.WARNING, "Encoding not supported, ignored", ignored);}return decoded;}/** * @return true if the gzip compression should be used if the client *         accespts it. Default this option is on for text content and off *         for everything. Override this for custom semantics. */protected boolean useGzipWhenAccepted(Response r) {return r.getMimeType() != null && r.getMimeType().toLowerCase().contains("text/");}public final int getListeningPort() {return this.myServerSocket == null ? -1 : this.myServerSocket.getLocalPort();}public final boolean isAlive() {return wasStarted() && !this.myServerSocket.isClosed() && this.myThread.isAlive();}/** * Call before start() to serve over HTTPS instead of HTTP */public void makeSecure(SSLServerSocketFactory sslServerSocketFactory) {this.sslServerSocketFactory = sslServerSocketFactory;}/** * Create a response with unknown length (using HTTP 1.1 chunking). */public static Response newChunkedResponse(IStatus status, String mimeType, InputStream data) {return new Response(status, mimeType, data, -1);}/** * Create a response with known length. */public static Response newFixedLengthResponse(IStatus status, String mimeType, InputStream data, long totalBytes) {return new Response(status, mimeType, data, totalBytes);}/** * Create a text response with known length. */public static Response newFixedLengthResponse(IStatus status, String mimeType, String txt) {if (txt == null) {return newFixedLengthResponse(status, mimeType, new ByteArrayInputStream(new byte[0]), 0);} else {byte[] bytes;try {bytes = txt.getBytes("UTF-8");} catch (UnsupportedEncodingException e) {NanoHTTPD.LOG.log(Level.SEVERE, "encoding problem, responding nothing", e);bytes = new byte[0];}return newFixedLengthResponse(status, mimeType, new ByteArrayInputStream(bytes), bytes.length);}}/** * Create a text response with known length. */public static Response newFixedLengthResponse(String msg) {return newFixedLengthResponse(Status.OK, NanoHTTPD.MIME_HTML, msg);}/** * 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 newFixedLengthResponse(Response.Status.INTERNAL_ERROR, NanoHTTPD.MIME_PLAINTEXT,"SERVER INTERNAL ERROR: IOException: " + ioe.getMessage());} catch (ResponseException re) {return newFixedLengthResponse(re.getStatus(), NanoHTTPD.MIME_PLAINTEXT, re.getMessage());}}Map<String, String> parms = session.getParms();parms.put(NanoHTTPD.QUERY_STRING_PARAMETER, session.getQueryParameterString());return serve(session.getUri(), method, session.getHeaders(), parms, files);}/** * 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 */@Deprecatedpublic Response serve(String uri, Method method, Map<String, String> headers, Map<String, String> parms,Map<String, String> files) {return newFixedLengthResponse(Response.Status.NOT_FOUND, NanoHTTPD.MIME_PLAINTEXT, "Not Found");}/** * Pluggable strategy for asynchronously executing requests. *  * @param asyncRunner *            new strategy for handling threads. */public void setAsyncRunner(AsyncRunner asyncRunner) {this.asyncRunner = asyncRunner;}/** * Pluggable strategy for creating and cleaning up temporary files. *  * @param tempFileManagerFactory *            new strategy for handling temp files. */public void setTempFileManagerFactory(TempFileManagerFactory tempFileManagerFactory) {this.tempFileManagerFactory = tempFileManagerFactory;}/** * Start the server. *  * @throws IOException *             if the socket is in use. */public void start() throws IOException {start(NanoHTTPD.SOCKET_READ_TIMEOUT);}/** * Start the server. *  * @param timeout *            timeout to use for socket connections. * @param daemon *            start the thread daemon or not. * @throws IOException *             if the socket is in use. */public void start(final int timeout, boolean daemon) throws IOException {if (this.sslServerSocketFactory != null) {SSLServerSocket ss = (SSLServerSocket) this.sslServerSocketFactory.createServerSocket();ss.setNeedClientAuth(false);this.myServerSocket = ss;} else {this.myServerSocket = new ServerSocket();}this.myServerSocket.setReuseAddress(true);ServerRunnable serverRunnable = createServerRunnable(timeout);this.myThread = new Thread(serverRunnable);this.myThread.setDaemon(daemon);this.myThread.setName("NanoHttpd Main Listener");this.myThread.start();while (!serverRunnable.hasBinded && serverRunnable.bindException == null) {try {Thread.sleep(10L);} catch (Throwable e) {// on android this may not be allowed, that's why we// catch throwable the wait should be very short because we are// just waiting for the bind of the socket}}if (serverRunnable.bindException != null) {throw serverRunnable.bindException;}}/** * Starts the server (in setDaemon(true) mode). */public void start(final int timeout) throws IOException {start(timeout, true);}/** * Stop the server. */public void stop() {try {safeClose(this.myServerSocket);this.asyncRunner.closeAll();if (this.myThread != null) {this.myThread.join();}} catch (Exception e) {NanoHTTPD.LOG.log(Level.SEVERE, "Could not stop all connections", e);}}public final boolean wasStarted() {return this.myServerSocket != null && this.myThread != null;}}

NanoHttpd是Github上的一个开源项目,号称只用一个java文件就能创建一个http server。

本文着重如何在Android机器上将本地的文件作为web项目,通过url访问。


创建服务器时要给,需要提供的时“web项目的路径”也就是一个文件夹(字符串路径),端口号

String[] args = { "--host", "localhost", "--port", "9090", "--dir", CyclopsApplication.PLACE_DOWNLOAD };SimpleWebServer.main(args);

具体:

package webseverutil;import java.io.File;import java.io.PipedInputStream;import java.io.PipedOutputStream;import webserve.SimpleWebServer;import android.os.Environment;import android.util.Log;import util.CyclopsApplication;public class TestHttpServer extends AbstractTestHttpServer {private static PipedOutputStream stdIn;private static Thread serverStartThread;private static String CacheDirStr;public static String getSDPath() {File sdDir = null;boolean sdCardExist = Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED); // 判断sd卡是否存在Log.d("drummor", "外存是否存在" + sdCardExist);if (sdCardExist) {sdDir = Environment.getExternalStorageDirectory();// 获取跟目录}return sdDir.toString();}public static void makeCacheDir() {File CacheDir = new File(Environment.getExternalStorageDirectory().toString() + "/" + "/zhiwu" + "/webview/myzip");if (!CacheDir.exists()) {Log.d("drummor", "创建成功否????" + CacheDir.mkdir());}CacheDirStr = CacheDir.getAbsolutePath();Log.d("drummor", "看下这个路径" + CacheDirStr);}// @BeforeClasspublic static void setUp() throws Exception {stdIn = new PipedOutputStream();System.setIn(new PipedInputStream(stdIn));serverStartThread = new Thread(new Runnable() {@Overridepublic void run() {makeCacheDir();String[] args = { "--host", "localhost", "--port", "9090", "--dir", CyclopsApplication.PLACE_DOWNLOAD };SimpleWebServer.main(args);}});serverStartThread.start();// give the server some tine to start.Thread.sleep(100);}}


以下的类可以直接放在自己的项目中:

package webserve;/* * #%L * NanoHttpd-Webserver * %% * 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.File;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.FilenameFilter;import java.io.IOException;import java.io.UnsupportedEncodingException;import java.net.URLEncoder;import java.util.ArrayList;import java.util.Arrays;import java.util.Collections;import java.util.HashMap;import java.util.Iterator;import java.util.List;import java.util.Map;import java.util.ServiceLoader;import java.util.StringTokenizer;import webserve.ServerRunner;import android.util.Log;import webserve.NanoHTTPD.Response.IStatus;public class SimpleWebServer extends NanoHTTPD {/** * Common mime type for dynamic content: binary */public static final String MIME_DEFAULT_BINARY = "application/octet-stream";/** * Default Index file names. */@SuppressWarnings("serial")public static final List<String> INDEX_FILE_NAMES = new ArrayList<String>() {{add("index.html");add("index.htm");}};/** * Hashtable mapping (String)FILENAME_EXTENSION -> (String)MIME_TYPE */@SuppressWarnings("serial")private static final Map<String, String> MIME_TYPES = new HashMap<String, String>() {{put("css", "text/css");put("htm", "text/html");put("html", "text/html");put("xml", "text/xml");put("java", "text/x-java-source, text/java");put("md", "text/plain");put("txt", "text/plain");put("asc", "text/plain");put("gif", "image/gif");put("jpg", "image/jpeg");put("jpeg", "image/jpeg");put("png", "image/png");put("svg", "image/svg+xml");put("mp3", "audio/mpeg");put("m3u", "audio/mpeg-url");put("mp4", "video/mp4");put("ogv", "video/ogg");put("flv", "video/x-flv");put("mov", "video/quicktime");put("swf", "application/x-shockwave-flash");put("js", "application/javascript");put("pdf", "application/pdf");put("doc", "application/msword");put("ogg", "application/x-ogg");put("zip", "application/octet-stream");put("exe", "application/octet-stream");put("class", "application/octet-stream");put("m3u8", "application/vnd.apple.mpegurl");put("ts", " video/mp2t");}};// 停止掉public void stop() {};/** * The distribution licence */private static final String LICENCE = "Copyright (c) 2012-2013 by Paul S. Hawke, 2001,2005-2013 by Jarno Elonen, 2010 by Konstantinos Togias\n"+ "\n" + "Redistribution and use in source and binary forms, with or without\n"+ "modification, are permitted provided that the following conditions\n" + "are met:\n" + "\n"+ "Redistributions of source code must retain the above copyright notice,\n"+ "this list of conditions and the following disclaimer. Redistributions in\n"+ "binary form must reproduce the above copyright notice, this list of\n"+ "conditions and the following disclaimer in the documentation and/or other\n"+ "materials provided with the distribution. The name of the author may not\n"+ "be used to endorse or promote products derived from this software without\n"+ "specific prior written permission. \n" + " \n"+ "THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR\n"+ "IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES\n"+ "OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.\n"+ "IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,\n"+ "INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT\n"+ "NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n"+ "DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n"+ "THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n"+ "(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n"+ "OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.";private static Map<String, WebServerPlugin> mimeTypeHandlers = new HashMap<String, WebServerPlugin>();/** * Starts as a standalone file server and waits for Enter. */public static void main(String[] args) {// Defaultsint port = 8080;String host = null; // bind to all interfaces by defaultList<File> rootDirs = new ArrayList<File>();boolean quiet = false;String cors = null;/** * 临时文件 */Map<String, String> options = new HashMap<String, String>();// Parse command-line, with short and long versions of the options.for (int i = 0; i < args.length; ++i) {if (args[i].equalsIgnoreCase("-h") || args[i].equalsIgnoreCase("--host")) {host = args[i + 1];} else if (args[i].equalsIgnoreCase("-p") || args[i].equalsIgnoreCase("--port")) {port = Integer.parseInt(args[i + 1]);} else if (args[i].equalsIgnoreCase("-q") || args[i].equalsIgnoreCase("--quiet")) {quiet = true;} else if (args[i].equalsIgnoreCase("-d") || args[i].equalsIgnoreCase("--dir")) {/* * 创建目录并加到rootDirs */File file = new File(args[i + 1]);rootDirs.add(file.getAbsoluteFile());Log.d("drummor", file.exists() + "是否存在>");Log.d("drummor", file.getAbsoluteFile().toString());} else if (args[i].startsWith("--cors")) {cors = "*";int equalIdx = args[i].indexOf('=');if (equalIdx > 0) {cors = args[i].substring(equalIdx + 1);}} else if (args[i].equalsIgnoreCase("--licence")) {System.out.println(SimpleWebServer.LICENCE + "\n");} else if (args[i].startsWith("-X:")) {int dot = args[i].indexOf('=');if (dot > 0) {String name = args[i].substring(0, dot);String value = args[i].substring(dot + 1, args[i].length());options.put(name, value);}}}if (rootDirs.isEmpty()) {rootDirs.add(new File(".").getAbsoluteFile());}options.put("host", host);options.put("port", "" + port);options.put("quiet", String.valueOf(quiet));StringBuilder sb = new StringBuilder();for (File dir : rootDirs) {if (sb.length() > 0) {sb.append(":");}try {sb.append(dir.getCanonicalPath());} catch (IOException ignored) {}}options.put("home", sb.toString());ServiceLoader<WebServerPluginInfo> serviceLoader = ServiceLoader.load(WebServerPluginInfo.class);for (WebServerPluginInfo info : serviceLoader) {String[] mimeTypes = info.getMimeTypes();for (String mime : mimeTypes) {String[] indexFiles = info.getIndexFilesForMimeType(mime);if (!quiet) {System.out.print("# Found plugin for Mime type: \"" + mime + "\"");if (indexFiles != null) {System.out.print(" (serving index files: ");for (String indexFile : indexFiles) {System.out.print(indexFile + " ");}}System.out.println(").");}registerPluginForMimeType(indexFiles, mime, info.getWebServerPlugin(mime), options);}}ServerRunner.executeInstance(new SimpleWebServer(host, port, rootDirs, quiet, cors));}/** *  * @param indexFiles * @param mimeType * @param plugin * @param commandLineOptions */protected static void registerPluginForMimeType(String[] indexFiles, String mimeType, WebServerPlugin plugin,Map<String, String> commandLineOptions) {if (mimeType == null || plugin == null) {return;}if (indexFiles != null) {for (String filename : indexFiles) {int dot = filename.lastIndexOf('.');if (dot >= 0) {String extension = filename.substring(dot + 1).toLowerCase();SimpleWebServer.MIME_TYPES.put(extension, mimeType);}}SimpleWebServer.INDEX_FILE_NAMES.addAll(Arrays.asList(indexFiles));}SimpleWebServer.mimeTypeHandlers.put(mimeType, plugin);plugin.initialize(commandLineOptions);}private final boolean quiet;private final String cors;protected List<File> rootDirs;public SimpleWebServer(String host, int port, File wwwroot, boolean quiet, String cors) {this(host, port, Collections.singletonList(wwwroot), quiet, cors);}public SimpleWebServer(String host, int port, File wwwroot, boolean quiet) {this(host, port, Collections.singletonList(wwwroot), quiet, null);}public SimpleWebServer(String host, int port, List<File> wwwroots, boolean quiet) {this(host, port, wwwroots, quiet, null);}public SimpleWebServer(String host, int port, List<File> wwwroots, boolean quiet, String cors) {super(host, port);this.quiet = quiet;this.cors = cors;this.rootDirs = new ArrayList<File>(wwwroots);init();}private boolean canServeUri(String uri, File homeDir) {boolean canServeUri;File f = new File(homeDir, uri);canServeUri = f.exists();if (!canServeUri) {String mimeTypeForFile = getMimeTypeForFile(uri);WebServerPlugin plugin = SimpleWebServer.mimeTypeHandlers.get(mimeTypeForFile);if (plugin != null) {canServeUri = plugin.canServeUri(uri, homeDir);}}return canServeUri;}/** * URL-encodes everything between "/"-characters. Encodes spaces as '%20' * instead of '+'. */private String encodeUri(String uri) {String newUri = "";StringTokenizer st = new StringTokenizer(uri, "/ ", true);while (st.hasMoreTokens()) {String tok = st.nextToken();if (tok.equals("/")) {newUri += "/";} else if (tok.equals(" ")) {newUri += "%20";} else {try {newUri += URLEncoder.encode(tok, "UTF-8");} catch (UnsupportedEncodingException ignored) {}}}return newUri;}private String findIndexFileInDirectory(File directory) {for (String fileName : SimpleWebServer.INDEX_FILE_NAMES) {File indexFile = new File(directory, fileName);if (indexFile.isFile()) {return fileName;}}return null;}protected Response getForbiddenResponse(String s) {return newFixedLengthResponse(Response.Status.FORBIDDEN, NanoHTTPD.MIME_PLAINTEXT, "FORBIDDEN: " + s);}protected Response getInternalErrorResponse(String s) {return newFixedLengthResponse(Response.Status.INTERNAL_ERROR, NanoHTTPD.MIME_PLAINTEXT, "INTERNAL ERROR: " + s);}// Get MIME type from file name extension, if possibleprivate String getMimeTypeForFile(String uri) {int dot = uri.lastIndexOf('.');String mime = null;if (dot >= 0) {mime = SimpleWebServer.MIME_TYPES.get(uri.substring(dot + 1).toLowerCase());}return mime == null ? SimpleWebServer.MIME_DEFAULT_BINARY : mime;}protected Response getNotFoundResponse() {return newFixedLengthResponse(Response.Status.NOT_FOUND, NanoHTTPD.MIME_PLAINTEXT,"Error 404, file not found.");}/** * Used to initialize and customize the server. */public void init() {}protected String listDirectory(String uri, File f) {String heading = "Directory " + uri;StringBuilder msg = new StringBuilder("<html><head><title>" + heading + "</title><style><!--\n"+ "span.dirname { font-weight: bold; }\n" + "span.filesize { font-size: 75%; }\n" + "// -->\n"+ "</style>" + "</head><body><h1>" + heading + "</h1>");String up = null;if (uri.length() > 1) {String u = uri.substring(0, uri.length() - 1);int slash = u.lastIndexOf('/');if (slash >= 0 && slash < u.length()) {up = uri.substring(0, slash + 1);}}List<String> files = Arrays.asList(f.list(new FilenameFilter() {@Overridepublic boolean accept(File dir, String name) {return new File(dir, name).isFile();}}));Collections.sort(files);List<String> directories = Arrays.asList(f.list(new FilenameFilter() {@Overridepublic boolean accept(File dir, String name) {return new File(dir, name).isDirectory();}}));Collections.sort(directories);if (up != null || directories.size() + files.size() > 0) {msg.append("<ul>");if (up != null || directories.size() > 0) {msg.append("<section class=\"directories\">");if (up != null) {msg.append("<li><a rel=\"directory\" href=\"").append(up).append("\"><span class=\"dirname\">..</span></a></b></li>");}for (String directory : directories) {String dir = directory + "/";msg.append("<li><a rel=\"directory\" href=\"").append(encodeUri(uri + dir)).append("\"><span class=\"dirname\">").append(dir).append("</span></a></b></li>");}msg.append("</section>");}if (files.size() > 0) {msg.append("<section class=\"files\">");for (String file : files) {msg.append("<li><a href=\"").append(encodeUri(uri + file)).append("\"><span class=\"filename\">").append(file).append("</span></a>");File curFile = new File(f, file);long len = curFile.length();msg.append(" <span class=\"filesize\">(");if (len < 1024) {msg.append(len).append(" bytes");} else if (len < 1024 * 1024) {msg.append(len / 1024).append(".").append(len % 1024 / 10 % 100).append(" KB");} else {msg.append(len / (1024 * 1024)).append(".").append(len % (1024 * 1024) / 10000 % 100).append(" MB");}msg.append(")</span></li>");}msg.append("</section>");}msg.append("</ul>");}msg.append("</body></html>");return msg.toString();}public static Response newFixedLengthResponse(IStatus status, String mimeType, String message) {Response response = NanoHTTPD.newFixedLengthResponse(status, mimeType, message);response.addHeader("Accept-Ranges", "bytes");return response;}private Response respond(Map<String, String> headers, IHTTPSession session, String uri) {// First let's handle CORS OPTION queryResponse r;if (cors != null && Method.OPTIONS.equals(session.getMethod())) {r = new NanoHTTPD.Response(Response.Status.OK, MIME_PLAINTEXT, null, 0);} else {r = defaultRespond(headers, session, uri);}if (cors != null) {r = addCORSHeaders(headers, r, cors);}return r;}private Response defaultRespond(Map<String, String> headers, IHTTPSession session, String uri) {// Remove URL argumentsuri = uri.trim().replace(File.separatorChar, '/');if (uri.indexOf('?') >= 0) {uri = uri.substring(0, uri.indexOf('?'));}// Prohibit getting out of current directoryif (uri.contains("../")) {return getForbiddenResponse("Won't serve ../ for security reasons.");}boolean canServeUri = false;File homeDir = null;for (int i = 0; !canServeUri && i < this.rootDirs.size(); i++) {homeDir = this.rootDirs.get(i);canServeUri = canServeUri(uri, homeDir);}if (!canServeUri) {return getNotFoundResponse();}// Browsers get confused without '/' after the directory, send a// redirect.File f = new File(homeDir, uri);if (f.isDirectory() && !uri.endsWith("/")) {uri += "/";Response res = newFixedLengthResponse(Response.Status.REDIRECT, NanoHTTPD.MIME_HTML,"<html><body>Redirected: <a href=\"" + uri + "\">" + uri + "</a></body></html>");res.addHeader("Location", uri);return res;}if (f.isDirectory()) {// First look for index files (index.html, index.htm, etc) and if// none found, list the directory if readable.String indexFile = findIndexFileInDirectory(f);if (indexFile == null) {if (f.canRead()) {// No index file, list the directory if it is readablereturn newFixedLengthResponse(Response.Status.OK, NanoHTTPD.MIME_HTML, listDirectory(uri, f));} else {return getForbiddenResponse("No directory listing.");}} else {return respond(headers, session, uri + indexFile);}}String mimeTypeForFile = getMimeTypeForFile(uri);WebServerPlugin plugin = SimpleWebServer.mimeTypeHandlers.get(mimeTypeForFile);Response response = null;if (plugin != null && plugin.canServeUri(uri, homeDir)) {response = plugin.serveFile(uri, headers, session, f, mimeTypeForFile);if (response != null && response instanceof InternalRewrite) {InternalRewrite rewrite = (InternalRewrite) response;return respond(rewrite.getHeaders(), session, rewrite.getUri());}} else {response = serveFile(uri, headers, f, mimeTypeForFile);}return response != null ? response : getNotFoundResponse();}@Overridepublic Response serve(IHTTPSession session) {Map<String, String> header = session.getHeaders();Map<String, String> parms = session.getParms();String uri = session.getUri();if (!this.quiet) {System.out.println(session.getMethod() + " '" + uri + "' ");Iterator<String> e = header.keySet().iterator();while (e.hasNext()) {String value = e.next();System.out.println("  HDR: '" + value + "' = '" + header.get(value) + "'");}e = parms.keySet().iterator();while (e.hasNext()) {String value = e.next();System.out.println("  PRM: '" + value + "' = '" + parms.get(value) + "'");}}for (File homeDir : this.rootDirs) {// Make sure we won't die of an exception laterif (!homeDir.isDirectory()) {return getInternalErrorResponse("given path is not a directory (" + homeDir + ").");}}return respond(Collections.unmodifiableMap(header), session, uri);}/** * Serves file from homeDir and its' subdirectories (only). Uses only URI, * ignores all headers and HTTP parameters. */Response serveFile(String uri, Map<String, String> header, File file, String mime) {Response res;try {// Calculate etagString etag = Integer.toHexString((file.getAbsolutePath() + file.lastModified() + "" + file.length()).hashCode());// Support (simple) skipping:long startFrom = 0;long endAt = -1;String range = header.get("range");if (range != null) {if (range.startsWith("bytes=")) {range = range.substring("bytes=".length());int minus = range.indexOf('-');try {if (minus > 0) {startFrom = Long.parseLong(range.substring(0, minus));endAt = Long.parseLong(range.substring(minus + 1));}} catch (NumberFormatException ignored) {}}}// get if-range header. If present, it must match etag or else we// should ignore the range requestString ifRange = header.get("if-range");boolean headerIfRangeMissingOrMatching = (ifRange == null || etag.equals(ifRange));String ifNoneMatch = header.get("if-none-match");boolean headerIfNoneMatchPresentAndMatching = ifNoneMatch != null&& (ifNoneMatch.equals("*") || ifNoneMatch.equals(etag));// Change return code and add Content-Range header when skipping is// requestedlong fileLen = file.length();if (headerIfRangeMissingOrMatching && range != null && startFrom >= 0 && startFrom < fileLen) {// range request that matches current etag// and the startFrom of the range is satisfiableif (headerIfNoneMatchPresentAndMatching) {// range request that matches current etag// and the startFrom of the range is satisfiable// would return range from file// respond with not-modifiedres = newFixedLengthResponse(Response.Status.NOT_MODIFIED, mime, "");res.addHeader("ETag", etag);} else {if (endAt < 0) {endAt = fileLen - 1;}long newLen = endAt - startFrom + 1;if (newLen < 0) {newLen = 0;}FileInputStream fis = new FileInputStream(file);fis.skip(startFrom);res = newFixedLengthResponse(Response.Status.PARTIAL_CONTENT, mime, fis, newLen);res.addHeader("Accept-Ranges", "bytes");res.addHeader("Content-Length", "" + newLen);res.addHeader("Content-Range", "bytes " + startFrom + "-" + endAt + "/" + fileLen);res.addHeader("ETag", etag);}} else {if (headerIfRangeMissingOrMatching && range != null && startFrom >= fileLen) {// return the size of the file// 4xx responses are not trumped by if-none-matchres = newFixedLengthResponse(Response.Status.RANGE_NOT_SATISFIABLE, NanoHTTPD.MIME_PLAINTEXT, "");res.addHeader("Content-Range", "bytes */" + fileLen);res.addHeader("ETag", etag);} else if (range == null && headerIfNoneMatchPresentAndMatching) {// full-file-fetch request// would return entire file// respond with not-modifiedres = newFixedLengthResponse(Response.Status.NOT_MODIFIED, mime, "");res.addHeader("ETag", etag);} else if (!headerIfRangeMissingOrMatching && headerIfNoneMatchPresentAndMatching) {// range request that doesn't match current etag// would return entire (different) file// respond with not-modifiedres = newFixedLengthResponse(Response.Status.NOT_MODIFIED, mime, "");res.addHeader("ETag", etag);} else {// supply the fileres = newFixedFileResponse(file, mime);res.addHeader("Content-Length", "" + fileLen);res.addHeader("ETag", etag);}}} catch (IOException ioe) {res = getForbiddenResponse("Reading file failed.");}return res;}private Response newFixedFileResponse(File file, String mime) throws FileNotFoundException {Response res;res = newFixedLengthResponse(Response.Status.OK, mime, new FileInputStream(file), (int) file.length());res.addHeader("Accept-Ranges", "bytes");return res;}protected Response addCORSHeaders(Map<String, String> queryHeaders, Response resp, String cors) {resp.addHeader("Access-Control-Allow-Origin", cors);resp.addHeader("Access-Control-Allow-Headers", calculateAllowHeaders(queryHeaders));resp.addHeader("Access-Control-Allow-Credentials", "true");resp.addHeader("Access-Control-Allow-Methods", ALLOWED_METHODS);resp.addHeader("Access-Control-Max-Age", "" + MAX_AGE);return resp;}private String calculateAllowHeaders(Map<String, String> queryHeaders) {// here we should use the given asked headers// but NanoHttpd uses a Map whereas it is possible for requester to send// several time the same header// let's just use default values for this versionreturn System.getProperty(ACCESS_CONTROL_ALLOW_HEADER_PROPERTY_NAME, DEFAULT_ALLOWED_HEADERS);}private final static String ALLOWED_METHODS = "GET, POST, PUT, DELETE, OPTIONS, HEAD";private final static int MAX_AGE = 42 * 60 * 60;// explicitly relax visibility to package for tests purposespublic final static String DEFAULT_ALLOWED_HEADERS = "origin,accept,content-type";public final static String ACCESS_CONTROL_ALLOW_HEADER_PROPERTY_NAME = "AccessControlAllowHeader";}



package webserve;/* * #%L * NanoHttpd-Webserver * %% * 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.ByteArrayInputStream;import java.util.Map;import webserve.NanoHTTPD.Response;/** * @author Paul S. Hawke (paul.hawke@gmail.com) On: 9/15/13 at 2:52 PM */public class InternalRewrite extends Response {private final String uri;private final Map<String, String> headers;public InternalRewrite(Map<String, String> headers, String uri) {super(Status.OK, NanoHTTPD.MIME_HTML, new ByteArrayInputStream(new byte[0]), 0);this.headers = headers;this.uri = uri;}public Map<String, String> getHeaders() {return this.headers;}public String getUri() {return this.uri;}}

package webserve;import java.io.IOException;import java.util.logging.Level;import java.util.logging.Logger;import webserve.NanoHTTPD;public class ServerRunner {/** * logger to log to. */private static final Logger LOG = Logger.getLogger(ServerRunner.class.getName());public static void executeInstance(NanoHTTPD server) {try {server.start(NanoHTTPD.SOCKET_READ_TIMEOUT, false);} catch (IOException ioe) {System.err.println("Couldn't start server:\n" + ioe);System.exit(-1);}System.out.println("Server started, Hit Enter to stop.\n");try {System.in.read();} catch (Throwable ignored) {}server.stop();System.out.println("Server stopped.\n");}public static <T extends NanoHTTPD> void run(Class<T> serverClass) {try {executeInstance(serverClass.newInstance());} catch (Exception e) {ServerRunner.LOG.log(Level.SEVERE, "Cound nor create server", e);}}}



package webserve;/* * #%L * NanoHttpd-Webserver * %% * 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.File;import java.util.Map;import webserve.NanoHTTPD.IHTTPSession;/** * @author Paul S. Hawke (paul.hawke@gmail.com) On: 9/14/13 at 8:09 AM */public interface WebServerPlugin {    boolean canServeUri(String uri, File rootDir);    void initialize(Map<String, String> commandLineOptions);    NanoHTTPD.Response serveFile(String uri, Map<String, String> headers, IHTTPSession session, File file, String mimeType);}


package webserve;/* * #%L * NanoHttpd-Webserver * %% * 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% *//** * @author Paul S. Hawke (paul.hawke@gmail.com) On: 9/14/13 at 8:09 AM */public interface WebServerPluginInfo {String[] getIndexFilesForMimeType(String mime);String[] getMimeTypes();WebServerPlugin getWebServerPlugin(String mimeType);}

0 0
原创粉丝点击