Android 视频投射之NanoHTTPD

号称用一个java文件实现Http服务器 有必要对其源码及例子进行分析

 public abstract class NanoHTTPD {

     //异步执行请求
public interface AsyncRunner { void closeAll(); void closed(ClientHandler clientHandler); void exec(ClientHandler code);
} //每一个新的连接创建一个线程
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);
} @Override
public 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) {
          
if (!(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);
}
} //提供对cookies的支持
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]);
}
}
}
} //设置cookies存放时间为一个月
public void delete(String name) {
set(name, "-delete-", -30);
} @Override
public Iterator<String> iterator() {
return this.cookies.keySet().iterator();
} //从HTTP Headers读取Cookie
public String read(String name) {
return this.cookies.get(name);
} public void set(Cookie cookie) {
this.queue.add(cookie);
} //设置Cookie
public void set(String name, String value, int expires) {
this.queue.add(new Cookie(name, value, Cookie.getHTTPTime(expires)));
} public void unloadQueue(Response response) {
for (Cookie cookie : this.queue) {
response.addHeader("Set-Cookie", cookie.getHTTPHeader());
}
}
} //每次进来的请求启动一个新的线程
public static class DefaultAsyncRunner implements AsyncRunner { private long requestCount; private final List<ClientHandler> running = Collections.synchronizedList(new ArrayList<NanoHTTPD.ClientHandler>()); //返回目前所有正在运行的客户端
public List<ClientHandler> getRunning() {
return running;
} @Override
public void closeAll() {
for (ClientHandler clientHandler : new ArrayList<ClientHandler>(this.running)) {
clientHandler.close();
}
} @Override
public void closed(ClientHandler clientHandler) {
this.running.remove(clientHandler);
} @Override
public 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();
}
} //默认创建临时文件的策略
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);
} @Override
public void delete() throws Exception {
safeClose(this.fstream);
if (!this.file.delete()) {
throw new Exception("could not delete temporary file");
}
} @Override
public String getName() {
return this.file.getAbsolutePath();
} @Override
public OutputStream open() throws Exception {
return this.fstream;
}
} //创建和清除临时文件的策略
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>();
} @Override
public 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();
} @Override
public TempFile createTempFile(String filename_hint) throws Exception {
DefaultTempFile tempFile = new DefaultTempFile(this.tmpdir);
this.tempFiles.add(tempFile);
return tempFile;
}
} private class DefaultTempFileManagerFactory implements TempFileManagerFactory { @Override
public 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 BufferedInputStream 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 BufferedInputStream(inputStream, HTTPSession.BUFSIZE);
this.outputStream = outputStream;
} public HTTPSession(TempFileManager tempFileManager, InputStream inputStream, OutputStream outputStream, InetAddress inetAddress) {
this.tempFileManager = tempFileManager;
this.inputStream = new BufferedInputStream(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>();
} //解析发送头
private void decodeHeader(BufferedReader in, Map<String, String> pre, Map<String, String> parms, Map<String, String> headers) throws ResponseException {
try {
// Read the request line
String inLine = in.readLine();
if (inLine == null) {
return;
} StringTokenizer st = new StringTokenizer(inLine);
if (!st.hasMoreTokens()) {
throw new ResponseException(Response.Status.BAD_REQUEST, "BAD REQUEST: Syntax error. Usage: GET /example/file.html");
} pre.put("method", st.nextToken()); if (!st.hasMoreTokens()) {
throw new ResponseException(Response.Status.BAD_REQUEST, "BAD REQUEST: Missing URI. Usage: GET /example/file.html");
} String uri = st.nextToken(); //从URI中解析参数
int qmi = uri.indexOf('?');
if (qmi >= 0) {
decodeParms(uri.substring(qmi + 1), parms);
uri = decodePercent(uri.substring(0, qmi));
} else {
uri = decodePercent(uri);
} if (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 string
String 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 lines
mpline = 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 data
int 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 string
byte[] 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 file
String 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(), "");
}
}
} @Override
public 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;
this.inputStream.mark(HTTPSession.BUFSIZE);
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 closed
safeClose(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.reset();
this.inputStream.skip(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 properties
Map<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_window
for (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 results
int[] 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 start
System.arraycopy(search_window, search_window.length - boundary.length, search_window, 0, boundary.length); // Refill search_window
new_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;
} @Override
public CookieHandler getCookies() {
return this.cookies;
} @Override
public final Map<String, String> getHeaders() {
return this.headers;
} @Override
public final InputStream getInputStream() {
return this.inputStream;
} @Override
public final Method getMethod() {
return this.method;
} @Override
public final Map<String, String> getParms() {
return this.parms;
} @Override
public 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
}
} @Override
public 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;
} @Override
public 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 size
if (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_output
byte[] 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-data
if (!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-urlencoded
if ("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
// data
files.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 any
throw 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;
} @Override
public String getDescription() {
return "" + this.requestStatus + " " + this.description;
} @Override
public 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);
} @Override
public void write(int b) throws IOException {
byte[] data = {
(byte) b
};
write(data, 0, 1);
} @Override
public void write(byte[] b) throws IOException {
write(b, 0, b.length);
} @Override
public 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.
*/
protected 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;
} @Override
public 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;
} @Override
public 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()); /**
* Hashtable mapping (String)FILENAME_EXTENSION -> (String)MIME_TYPE
*/
protected static Map<String, String> MIME_TYPES; public static Map<String, String> mimeTypes() {
if (MIME_TYPES == null) {
MIME_TYPES = new HashMap<String, String>();
loadMimeTypes(MIME_TYPES, "META-INF/nanohttpd/default-mimetypes.properties");
loadMimeTypes(MIME_TYPES, "META-INF/nanohttpd/mimetypes.properties");
if (MIME_TYPES.isEmpty()) {
LOG.log(Level.WARNING, "no mime types found in the classpath! please provide mimetypes.properties");
}
}
return MIME_TYPES;
} private static void loadMimeTypes(Map<String, String> result, String resourceName) {
try {
Enumeration<URL> resources = NanoHTTPD.class.getClassLoader().getResources(resourceName);
while (resources.hasMoreElements()) {
URL url = (URL) resources.nextElement();
Properties properties = new Properties();
InputStream stream = null;
try {
stream = url.openStream();
properties.load(url.openStream());
} catch (IOException e) {
LOG.log(Level.SEVERE, "could not load mimetypes from " + url, e);
} finally {
safeClose(stream);
}
result.putAll((Map) properties);
}
} catch (IOException e) {
LOG.log(Level.INFO, "no mime types available at " + resourceName);
}
}; /**
* 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());
}
} /**
* Get MIME type from file name extension, if possible
*
* @param uri
* the string representing a file
* @return the connected mime/type
*/
public static String getMimeTypeForFile(String uri) {
int dot = uri.lastIndexOf('.');
String mime = null;
if (dot >= 0) {
mime = mimeTypes().get(uri.substring(dot + 1).toLowerCase());
}
return mime == null ? "application/octet-stream" : mime;
} 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 String[] sslProtocols; 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&lt;String&gt;</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&lt;String&gt;</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, String[] sslProtocols) {
this.sslServerSocketFactory = sslServerSocketFactory;
this.sslProtocols = sslProtocols;
} /**
* 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
*/
@Deprecated
public 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();
if (this.sslProtocols != null) {
ss.setEnabledProtocols(this.sslProtocols);
} else {
ss.setEnabledProtocols(ss.getSupportedProtocols());
}
ss.setUseClientMode(false);
ss.setWantClientAuth(false);
ss.setNeedClientAuth(false);
ss.setSoTimeout(timeout);
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;
}
}

ServerRunner

 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);
}
}
}

第一个服务器程序

 public class HelloServer extends NanoHTTPD {

     /**
* logger to log to.
*/
private static final Logger LOG = Logger.getLogger(HelloServer.class.getName()); public static void main(String[] args) {
ServerRunner.run(HelloServer.class);
} public HelloServer() {
super(8080);
} @Override
public Response serve(IHTTPSession session) {
Method method = session.getMethod();
String uri = session.getUri();
HelloServer.LOG.info(method + " '" + uri + "' "); String msg = "<html><body><h1>Hello server</h1>\n";
Map<String, String> parms = session.getParms();
if (parms.get("username") == null) {
msg += "<form action='?' method='get'>\n" + " <p>Your name: <input type='text' name='username'></p>\n" + "</form>\n";
} else {
msg += "<p>Hello, " + parms.get("username") + "!</p>";
} msg += "</body></html>\n"; return newFixedLengthResponse(msg);
}
}

另外一个服务器程序

DebugServer

 public class DebugServer extends NanoHTTPD {

     public static void main(String[] args) {
ServerRunner.run(DebugServer.class);
} public DebugServer() {
super(8080);
} private void listItem(StringBuilder sb, Map.Entry<String, ? extends Object> entry) {
sb.append("<li><code><b>").append(entry.getKey()).append("</b> = ").append(entry.getValue()).append("</code></li>");
} @Override
public Response serve(IHTTPSession session) {
Map<String, List<String>> decodedQueryParameters = decodeParameters(session.getQueryParameterString()); StringBuilder sb = new StringBuilder();
sb.append("<html>");
sb.append("<head><title>Debug Server</title></head>");
sb.append("<body>");
sb.append("<h1>Debug Server</h1>"); sb.append("<p><blockquote><b>URI</b> = ").append(String.valueOf(session.getUri())).append("<br />"); sb.append("<b>Method</b> = ").append(String.valueOf(session.getMethod())).append("</blockquote></p>"); sb.append("<h3>Headers</h3><p><blockquote>").append(toString(session.getHeaders())).append("</blockquote></p>"); sb.append("<h3>Parms</h3><p><blockquote>").append(toString(session.getParms())).append("</blockquote></p>"); sb.append("<h3>Parms (multi values?)</h3><p><blockquote>").append(toString(decodedQueryParameters)).append("</blockquote></p>"); try {
Map<String, String> files = new HashMap<String, String>();
session.parseBody(files);
sb.append("<h3>Files</h3><p><blockquote>").append(toString(files)).append("</blockquote></p>");
} catch (Exception e) {
e.printStackTrace();
} sb.append("</body>");
sb.append("</html>");
return newFixedLengthResponse(sb.toString());
} private String toString(Map<String, ? extends Object> map) {
if (map.size() == 0) {
return "";
}
return unsortedList(map);
} private String unsortedList(Map<String, ? extends Object> map) {
StringBuilder sb = new StringBuilder();
sb.append("<ul>");
for (Map.Entry<String, ? extends Object> entry : map.entrySet()) {
listItem(sb, entry);
}
sb.append("</ul>");
return sb.toString();
}
}

项目代码见

https://github.com/huanyi0723/NanoHTTPDCode/

Android 视频投射之NanoHTTPD的更多相关文章

  1. android视频录制、另一部手机实时观看方案

    最近调研android视频录制.另一部手机实时观看,大致有以下几种思路. 1. android手机充当服务器,使用NanoHTTPD充当服务器,另一部手机或者pc通过输入http://手机的ip:80 ...

  2. Android 视频直播 SDK

    Android 视频直播 SDK接入说明 一.名词解释 分辨率:用于计算机视频处理的图像,以水平和垂直方向上所能显示的像素数来表示分辨率.常见视频分辨率的有1080P即1920x1080,720P即1 ...

  3. Android 视频编辑 SDK

    Android 视频编辑 SDK接入说明 一.名词解释 分辨率:用于计算机视频处理的图像,以水平和垂直方向上所能显示的像素数来表示分辨率.常见视频分辨率的有1080P即1920x1080,720P即1 ...

  4. Android视频录制从不入门到入门系列教程(一)————简介

    一.WHY Android SDK提供了MediaRecorder帮助开发者进行视频的录制,不过这个类很鸡肋,实际项目中应该很少用到它,最大的原因我觉得莫过于其输出的视频分辨率太有限了,满足不了项目的 ...

  5. Android视频录制从不入门到入门系列教程(三)————视频方向

    运行Android视频录制从不入门到入门系列教程(二)————显示视频图像中的Demo后,我们应该能发现视频的方向是错误的. 由于Android中,Camera给我们的视频图片的原始方向是下图这个样子 ...

  6. MediaCodec在Android视频硬解码组件的应用

    https://yq.aliyun.com/articles/632892 云栖社区> 博客列表> 正文 MediaCodec在Android视频硬解码组件的应用   cheenc 201 ...

  7. 最简单的基于FFmpeg的移动端样例:Android 视频解码器-单个库版

    ===================================================== 最简单的基于FFmpeg的移动端样例系列文章列表: 最简单的基于FFmpeg的移动端样例:A ...

  8. android视频处理相关资料

    <开源>基于Android的点对点视频通信/RTSP/RTP/H.264 http://blog.csdn.net/cazicaquw/article/details/8650543 历经 ...

  9. 利用FFmpeg玩转Android视频录制与压缩(二)<转>

    转载出处:http://blog.csdn.net/mabeijianxi/article/details/72983362 预热 时光荏苒,光阴如梭,离上一次吹牛逼已经过去了两三个月,身边很多人的女 ...

随机推荐

  1. ACM题目————士兵杀敌(三)

    [RMQ算法]:用于当数组过于庞大的时候,查询区间的最大(最小)值. 时间复杂度:O(nlogn),主要时间发费在预处理上,查询只要O(1). 描述 南将军统率着N个士兵,士兵分别编号为1~N,南将军 ...

  2. ajax 无刷新分页

    //ajax 无刷新分页1.前台要做的 滑动时 当前page+1,通过page ajax请求后台接口获取数据将数据进行拼装;2.后台要做的 做分页接口返回json数据前台判断触发请求条件: var p ...

  3. sql截断日志

    --收缩数据库 DBCC SHRINKDATABASE(fas) --截断事务日志: BACKUP LOG fas WITH NO_LOG 1.清空日志 DUMP TRANSACTION 库名 WIT ...

  4. 3G中的A-GPS移动定位技术

    位置业务(LBS,Location Based Service)是指移动网络通过特定的定位技术来获取移动终端的位置信息,从而为终端用户提供附加服务的一种增值业务,可广泛应用于紧急救援.导航追踪.运输调 ...

  5. SQL Server 索引和表体系结构(一)

    转自:http://www.cnblogs.com/chenmh/p/3780221.html 聚集索引 概述 关于索引和表体系结构的概念一直都是讨论比较多的话题,其中表的各种存储形式是讨论的重点,在 ...

  6. register_chrdev、register_chrdev_region以及alloc_chrdev_region之间的区别

    register_chrdev:Linux2.6.30之前所用,不用定义cdev:但 如果是register_chrdev 注册的话,这个时候,分配的次设备号,是从0~255,这样子的话,就分配的范围 ...

  7. AIM Tech Round 3 (Div. 2) A

    Description Kolya is going to make fresh orange juice. He has n oranges of sizes a1, a2, ..., an. Ko ...

  8. android 入门 001 (界面布局)

    学android 首先学会怎么布局界面,我开始是学.net的,因工作需要学习一下安卓,外行写的不好,请多多见谅指教 .这一篇文章然我们来学习一下四种布局吧! RelativeLayout(相对布局) ...

  9. 回车和换行在linux下和windows下

    今天,我总算搞清楚"回车"(carriage return)和"换行"(line feed)这两个概念的来历和区别了. 1.  在计算机还没有出现之前,有一种叫 ...

  10. SqlSever基础 datediff 计算两个时间相差多少年份

    镇场诗:---大梦谁觉,水月中建博客.百千磨难,才知世事无常.---今持佛语,技术无量愿学.愿尽所学,铸一良心博客.------------------------------------------ ...