回到ServerRunnable的run函数
public void run() {
try {
httpd.getMyServerSocket().bind(httpd.hostname != null ? new InetSocketAddress(httpd.hostname, httpd
.myPort) : new InetSocketAddress(httpd.myPort));//绑定端口地址
hasBinded = true;
} catch (IOException e) {
this.bindException = e;
return;
}
do {
try {
final Socket finalAccept = httpd.getMyServerSocket().accept();//接受请求
if (this.timeout > 0) {
finalAccept.setSoTimeout(this.timeout);//连接状态timeout秒没有收到数据的话强制断开客户端
}
final InputStream inputStream = finalAccept.getInputStream();
httpd.asyncRunner.exec(httpd.createClientHandler(finalAccept, inputStream));
//首先以socket和inputStream参数创建一个ClientHandler,然后调用NanoHTTPD的asyncRunner执行这个handler
} catch (IOException e) {
NanoHTTPD.LOG.log(Level.FINE, "Communication with the client broken", e);
}
} while (!httpd.getMyServerSocket().isClosed());
}
然后会调用createClientHandler创建一个ClientHandler,ClientHandler是一个Runnable,然后会调用DefaultAsyncRunner的exec执行这个Runnable.
@Override
public void exec(ClientHandler clientHandler) {
++this.requestCount;
this.running.add(clientHandler);//添加到运行列表
createThread(clientHandler).start();//创建一个线程执行这个请求
}
protected Thread createThread(ClientHandler clientHandler) {
Thread t = new Thread(clientHandler);//创建一个线程
t.setDaemon(true);
t.setName("NanoHttpd Request Processor (#" + this.requestCount + ")");
return t;
}
public void run() {
OutputStream outputStream = null;
try {
outputStream = this.acceptSocket.getOutputStream();//获取输出流,用于发送响应
ITempFileManager tempFileManager = httpd.getTempFileManagerFactory().create();//临时文件管理
HTTPSession session = new HTTPSession(httpd, tempFileManager, this.inputStream, outputStream, this
.acceptSocket.getInetAddress());//创建一个HTTPSession
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
// stacktrace
if (!(e instanceof SocketException && "NanoHttpd Shutdown".equals(e.getMessage())) && !(e instanceof SocketTimeoutException)) {
NanoHTTPD.LOG.log(Level.SEVERE, "Communication with the client broken, or an bug in the handler code", e);
}
} finally {
NanoHTTPD.safeClose(outputStream);
NanoHTTPD.safeClose(this.inputStream);
NanoHTTPD.safeClose(this.acceptSocket);
httpd.asyncRunner.closed(this);
}
}
这里主要穿件了一个HTTPSession会话的意思,如果这个socket没有关闭,则一直执行这个会话
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);//记当前位置,并保证在mark以后最多可以读取readlimit字节数据,mark标记仍有效
try {
read = this.inputStream.read(buf, 0, HTTPSession.BUFSIZE);//读取请求数据
} catch (SSLException e) {
throw e;
} catch (IOException e) {
NanoHTTPD.safeClose(this.inputStream);
NanoHTTPD.safeClose(this.outputStream);
throw new SocketException("NanoHttpd Shutdown");
}
if (read == -1) {
// socket was been closed
NanoHTTPD.safeClose(this.inputStream);
NanoHTTPD.safeClose(this.outputStream);
throw new SocketException("NanoHttpd Shutdown");
}
while (read > 0) {
this.rlen += read;
this.splitbyte = findHeaderEnd(buf, this.rlen);//找到header结束位置
if (this.splitbyte > 0) {
break;
}
read = this.inputStream.read(buf, this.rlen, HTTPSession.BUFSIZE - this.rlen);//没有读到数据,则继续读
}
if (this.splitbyte < this.rlen) {//header头的数据小于当前读到的数据
this.inputStream.reset();//重设输入流
this.inputStream.skip(this.splitbyte);//忽略读到的头数据
}
this.parms = new HashMap<String, List<String>>();
if (null == this.headers) {
this.headers = new HashMap<String, String>();
} else {
this.headers.clear();
}
// Create a BufferedReader for parsing the header.创建一个BufferedReader,用来解析头
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);//解析头
if (null != this.remoteIp) {//添加ip
this.headers.put("remote-addr", this.remoteIp);
this.headers.put("http-client-ip", this.remoteIp);
}
this.method = Method.lookup(pre.get("method"));
if (this.method == null) {
throw new ResponseException(Status.BAD_REQUEST, "BAD REQUEST: Syntax error. HTTP verb " + pre.get("method") + " unhandled.");
}
this.uri = pre.get("uri");
this.cookies = new CookieHandler(this.headers);//处理Cookie
String connection = this.headers.get("connection");//是否保持连接
boolean keepAlive = "HTTP/1.1".equals(protocolVersion) && (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 implementation for totalRead())
r = httpd.serve(this);//调用server处理这个会话,返回响应
// TODO: this.inputStream.skip(body_size -
// (this.inputStream.totalRead() - pos_before_serve))
if (r == null) {
throw new ResponseException(Status.INTERNAL_ERROR, "SERVER INTERNAL ERROR: Serve() returned a null response.");
} else {
String acceptEncoding = this.headers.get("accept-encoding");//获取浏览器支持的编码类型
this.cookies.unloadQueue(r);//响应头添加cookie
r.setRequestMethod(this.method);//设置请求方法
r.setGzipEncoding(httpd.useGzipWhenAccepted(r) && acceptEncoding != null && acceptEncoding.contains("gzip"));
r.setKeepAlive(keepAlive);//保持连接
r.send(this.outputStream);//把响应通过outputStream发送出去
}
if (!keepAlive || r.isCloseConnection()) {//不需要保持连接
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 (SSLException ssle) {
Response resp = Response.newFixedLengthResponse(Status.INTERNAL_ERROR, NanoHTTPD.MIME_PLAINTEXT, "SSL PROTOCOL FAILURE: " + ssle.getMessage());
resp.send(this.outputStream);
NanoHTTPD.safeClose(this.outputStream);
} catch (IOException ioe) {
Response resp = Response.newFixedLengthResponse(Status.INTERNAL_ERROR, NanoHTTPD.MIME_PLAINTEXT, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage());
resp.send(this.outputStream);
NanoHTTPD.safeClose(this.outputStream);
} catch (ResponseException re) {
Response resp = Response.newFixedLengthResponse(re.getStatus(), NanoHTTPD.MIME_PLAINTEXT, re.getMessage());
resp.send(this.outputStream);
NanoHTTPD.safeClose(this.outputStream);
} finally {
NanoHTTPD.safeClose(r);//安全关闭响应
this.tempFileManager.clear();//清除临时文件
}
}
会话执行的代码比较长,主要分一下几部:
1、从socket读取数据
2、解析头,找到请求的协议版本,请求方法类型等
3、调用Server处理这个会话,并返回响应结果
4、发送响应结果
我们先看解析请求头
/**
* Decodes the sent headers and loads the data into Key/value pairs
*/
private void decodeHeader(BufferedReader in, Map<String, String> pre, Map<String, List<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);//构造一个用来解析str的StringTokenizer对象。java默认的分隔符是“空格”、“制表符(‘\t’)”、“换行符(‘\n’)”、“回车符(‘\r’)”
if (!st.hasMoreTokens()) {//返回是否还有分隔符
throw new ResponseException(Status.BAD_REQUEST, "BAD REQUEST: Syntax error. Usage: GET /example/file.html");
}
pre.put("method", st.nextToken());//方法
if (!st.hasMoreTokens()) {
throw new ResponseException(Status.BAD_REQUEST, "BAD REQUEST: Missing URI. Usage: GET /example/file.html");
}
String uri = st.nextToken();//请求路径
// Decode parameters from the URI是否有参数
int qmi = uri.indexOf('?');
if (qmi >= 0) {
decodeParms(uri.substring(qmi + 1), parms);//解析参数
uri = NanoHTTPD.decodePercent(uri.substring(0, qmi));
} else {
uri = NanoHTTPD.decodePercent(uri);//解密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().isEmpty()) {
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(Status.INTERNAL_ERROR, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage(), ioe);
}
}
然后是处理这个请求,对应到SimpleWebServer的serve函数
@Override
public Response serve(IHTTPSession session) {
Map<String, String> header = session.getHeaders();//获取header
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 later
if (!homeDir.isDirectory()) {//根目录是否是一个文件夹
return getInternalErrorResponse("given path is not a directory (" + homeDir + ").");
}
}
return respond(Collections.unmodifiableMap(header), session, uri);//调用respond,Collections.unmodifiableMap于返回指定映射的不可修改视图
}
private Response respond(Map<String, String> headers, IHTTPSession session, String uri) {
// First let's handle CORS OPTION query
Response r;
if (cors != null && Method.OPTIONS.equals(session.getMethod())) {//OPTIONS方法
r = Response.newFixedLengthResponse(Status.OK, MIME_PLAINTEXT, null, 0);
} else {
r = defaultRespond(headers, session, uri);
}
if (cors != null) {//跨域添加头
r = addCORSHeaders(headers, r, cors);
}
return r;
}
这里我们为get方法,进入defaultRespond
private Response defaultRespond(Map<String, String> headers, IHTTPSession session, String uri) {
// Remove URL arguments
uri = uri.trim().replace(File.separatorChar, '/');
if (uri.indexOf('?') >= 0) {
uri = uri.substring(0, uri.indexOf('?'));
}
// Prohibit getting out of current directory
if (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(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);//查找index.html, index.htm等文件
if (indexFile == null) {//index文件不存在
if (f.canRead()) {//可读
// No index file, list the directory if it is readable 没有index文件,则显示文件夹里面的文件
return newFixedLengthResponse(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();
}
protected String listDirectory(String uri, File f) {//构造返回的html(当前目录文件列表)
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() {
@Override
public boolean accept(File dir, String name) {
return new File(dir, name).isFile();
}
}));
Collections.sort(files);
List<String> directories = Arrays.asList(f.list(new FilenameFilter() {
@Override
public 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></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></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();
}
调用newFixedLengthResponse构造响应
public static Response newFixedLengthResponse(IStatus status, String mimeType, String message) {
Response response = Response.newFixedLengthResponse(status, mimeType, message);//构造一个response
response.addHeader("Accept-Ranges", "bytes");//表明服务器支持Range请求,以及服务器所支持的单位是字节
return response;
}
继续调用Response.newFixedLengthResponse创建一个已知长度文本 的响应消息
/**
* Create a text response with known length.
*/
public static Response newFixedLengthResponse(IStatus status, String mimeType, String txt) {
ContentType contentType = new ContentType(mimeType);
if (txt == null) {//内容为空
return newFixedLengthResponse(status, mimeType, new ByteArrayInputStream(new byte[0]), 0);
} else {
byte[] bytes;
try {
CharsetEncoder newEncoder = Charset.forName(contentType.getEncoding()).newEncoder();//创建一个CharsetEncoder
if (!newEncoder.canEncode(txt)) {//是否可以编码给定的字符串
contentType = contentType.tryUTF8();
}
bytes = txt.getBytes(contentType.getEncoding());//返回编码后的字符串
} catch (UnsupportedEncodingException e) {
NanoHTTPD.LOG.log(Level.SEVERE, "encoding problem, responding nothing", e);
bytes = new byte[0];
}
return newFixedLengthResponse(status, contentType.getContentTypeHeader(), new ByteArrayInputStream(bytes), bytes.length);
}
}
public static Response newFixedLengthResponse(IStatus status, String mimeType, InputStream data, long totalBytes) {
return new Response(status, mimeType, data, totalBytes);
}
这里会new 一个Response
/**
* Creates a fixed length response if totalBytes>=0, otherwise chunked.
*/
@SuppressWarnings({
"rawtypes",
"unchecked"
})
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;
this.keepAlive = true;
this.cookieHeaders = new ArrayList(10);
}
到这里终于把响应消息构造好了,接着就是发送了
调用Response的send
public void send(OutputStream outputStream) {//发送指定的响应到socket
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, new ContentType(this.mimeType).getEncoding())), false);
pw.append("HTTP/1.1 ").append(this.status.getDescription()).append(" \r\n");//添加http版本 响应状态
if (this.mimeType != null) {
printHeader(pw, "Content-Type", this.mimeType);
}
if (getHeader("date") == null) {
printHeader(pw, "Date", gmtFrmt.format(new Date()));
}
for (Entry<String, String> entry : this.header.entrySet()) {//头信息
printHeader(pw, entry.getKey(), entry.getValue());
}
for (String cookieHeader : this.cookieHeaders) {//cookie信息
printHeader(pw, "Set-Cookie", cookieHeader);
}
if (getHeader("connection") == null) {//Connection信息
printHeader(pw, "Connection", (this.keepAlive ? "keep-alive" : "close"));
}
if (getHeader("content-length") != null) {//内容长度
encodeAsGzip = false;
}
if (encodeAsGzip) {//是否使用gzip压缩
printHeader(pw, "Content-Encoding", "gzip");
setChunkedTransfer(true);
}
long pending = this.data != null ? this.contentLength : 0;
if (this.requestMethod != Method.HEAD && this.chunkedTransfer) {
printHeader(pw, "Transfer-Encoding", "chunked");//采用chunked编码方式来进行报文体的传输。chunked编码的基本方法是将大块数据分解成多块小数据,每块都可以自指定长度
} else if (!encodeAsGzip) {
pending = sendContentLengthHeaderIfNotAlreadyPresent(pw, pending);
}
pw.append("\r\n");
pw.flush();
sendBodyWithCorrectTransferAndEncoding(outputStream, pending);
outputStream.flush();
NanoHTTPD.safeClose(this.data);//安全关闭
} catch (IOException ioe) {
NanoHTTPD.LOG.log(Level.SEVERE, "Could not send response to the client", ioe);
}
}
这里主要构造响应头,调用sendBodyWithCorrectTransferAndEncoding进行发送
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);
}
}
是否是块传输,如果是的话ChunkedOutputStream包装outoutStream,它写数据的方式不一样,然后调用sendBodyWithCorrectEncoding发送
private void sendBodyWithCorrectEncoding(OutputStream outputStream, long pending) throws IOException {
if (encodeAsGzip) {//是否使用gzip压缩
GZIPOutputStream gzipOutputStream = new GZIPOutputStream(outputStream);//新建一个GZIPOutputStream的输出流
sendBody(gzipOutputStream, -1);
gzipOutputStream.finish();//关闭流
} else {
sendBody(outputStream, pending);
}
}
public GZIPOutputStream(OutputStream out) throws IOException {
this(out, 512, false);
}
public GZIPOutputStream(OutputStream out, int size, boolean syncFlush)
throws IOException
{
super(out, new Deflater(Deflater.DEFAULT_COMPRESSION, true),
size,
syncFlush);
usesDefaultDeflater = true;
writeHeader();
crc.reset();
}
/*
* Writes GZIP member header.
*/
private void writeHeader() throws IOException {
out.write(new byte[] {
(byte) GZIP_MAGIC, // Magic number (short)
(byte)(GZIP_MAGIC >> 8), // Magic number (short)
Deflater.DEFLATED, // Compression method (CM)
0, // Flags (FLG)
0, // Modification time MTIME (int)
0, // Modification time MTIME (int)
0, // Modification time MTIME (int)
0, // Modification time MTIME (int)
0, // Extra flags (XFLG)
0 // Operating system (OS)
});
}
会写一个gzip的头,这里的out的是我们前面构造的ChunkedOutputStream,看一下它的write
@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());//回车换行
}
//发送数据
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;
}
}
}
private Response defaultRespond(Map<String, String> headers, IHTTPSession session, String uri) {
// Remove URL arguments
uri = uri.trim().replace(File.separatorChar, '/');
if (uri.indexOf('?') >= 0) {
uri = uri.substring(0, uri.indexOf('?'));
}
// Prohibit getting out of current directory
if (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(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);//查找index.html, index.htm等文件
if (indexFile == null) {//index文件不存在
if (f.canRead()) {//可读
// No index file, list the directory if it is readable 没有index文件,则显示文件夹里面的文件
return newFixedLengthResponse(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();
}
先调用getMimeTypeForFile获取文件的类型(根据后缀),如果Web插件不为空则调用响应的插件处理,否则调用serveFile
这里假设没有插件,我们看看serveFile
Response serveFile(String uri, Map<String, String> header, File file, String mime) {
Response res;
try {
// Calculate etag 创建一个tag
String etag = Integer.toHexString((file.getAbsolutePath() + file.lastModified() + "" + file.length()).hashCode());
// Support (simple) skipping:
long startFrom = 0;
long endAt = -1;
String range = header.get("range");//头中是否有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 request
String ifRange = header.get("if-range"); //获取if-range
boolean headerIfRangeMissingOrMatching = (ifRange == null || etag.equals(ifRange));
String ifNoneMatch = header.get("if-none-match");//获取if-none-match
boolean headerIfNoneMatchPresentAndMatching = ifNoneMatch != null && ("*".equals(ifNoneMatch) || ifNoneMatch.equals(etag));
// Change return code and add Content-Range header when skipping is
// requested
long fileLen = file.length();
if (headerIfRangeMissingOrMatching && range != null && startFrom >= 0 && startFrom < fileLen) {
// range request that matches current etag
// and the startFrom of the range is satisfiable
if (headerIfNoneMatchPresentAndMatching) {
// range request that matches current etag
// and the startFrom of the range is satisfiable
// would return range from file
// respond with not-modified
res = newFixedLengthResponse(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 = Response.newFixedLengthResponse(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-match
res = newFixedLengthResponse(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-modified
res = newFixedLengthResponse(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-modified
res = newFixedLengthResponse(Status.NOT_MODIFIED, mime, "");
res.addHeader("ETag", etag);
} else {
// supply the file
res = 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 = Response.newFixedLengthResponse(Status.OK, mime, new FileInputStream(file), (int) file.length());
res.addHeader("Accept-Ranges", "bytes");
return res;
}