• 一、概述
  • 二、Socket通信承载功能一览
  • 三、核心实现NanoHTTPD
    • 3.1 持有变量与构造函数

      • 3.1.1 DefaultServerSocketFactory 服务SocketServer工厂
      • 3.1.2 DefaultTempFileManagerFactory 临时文件的操作管理器的生成工厂
      • 3.1.3 DefaultAsyncRunner 异步任务运行器
      • 3.1.4 IHandler响应器
    • 3.2 核心start函数
    • 3.3 HttpSession
    • 3.4 Response响应
  • 参考文献

一、概述

NanoHttpd是使用Java实现的微型web server,是一个可嵌入应用程序的轻量级的HTTP Server

 package com.example;import java.io.IOException;import java.util.Map;import org.nanohttpd.NanoHTTPD;// NOTE: If you're using NanoHTTPD < 3.0.0 the namespace is different,//       instead of the above import use the following:// import fi.iki.elonen.NanoHTTPD;public class App extends NanoHTTPD {public static void main(String[] args) {try {new App();} catch (IOException ioe) {System.err.println("Couldn't start server:\n" + ioe);}}public App() throws IOException {super(8080);start(NanoHTTPD.SOCKET_READ_TIMEOUT, false);System.out.println("\nRunning! Point your browsers to http://localhost:8080/ \n");}/*** 接收并响应HTTP请求*/@Overridepublic Response serve(IHTTPSession session) {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>";}return newFixedLengthResponse(msg + "</body></html>\n");}}
  • 核心

    • 只有一个Java文件,提供HTTP 1.1支持。
    • 没有固定的配置文件,日志记录,授权等等(如果你需要它们,自己实现。但是错误传递给java.util.logging)。
    • 支持HTTPS(SSL)。
    • 对cookie的基本支持。
    • 支持GET和POST方法的参数解析。
    • 一些内置的HEAD,POST和DELETE请求支持。不过,您可以轻松实现/定制任何HTTP方法。
    • 支持文件上传。为小型上传使用内存,为大型文件使用临时文件。
    • 从不缓存任何东西。
    • 默认情况下,不会限制带宽,请求时间或同时连接。
    • 所有标题名称都被转换为小写字母,因此它们在浏览器/客户端之间不会改变。
    • 持久连接(连接“保持活动”)支持允许通过单个套接字连接提供多个请求。
  • WebSocket

    • 经过Firefox,Chrome和IE测试。
  • Webserver

    • 默认代码提供文件和显示(在控制台上打印)所有HTTP参数和标题。
    • 支持动态内容和文件服务。
    • 文件服务器支持目录列表index.html和index.htm。
    • 文件服务器支持部分内容(流媒体和继续下载)。
    • 文件服务器支持ETags。
    • 文件服务器为没有的目录执行301重定向技巧/。
    • 文件服务器还提供非常长的文件,无需内存开销。
    • 包含大多数常见MIME类型的内置列表。
    • 运行时扩展支持(服务特定MIME类型的扩展) - 服务于Markdown格式文件的示例扩展。只需在Web服务器类路径中包含扩展JAR即可加载扩展。
    • 简单的CORS支持通过–cors参数
      • 默认服务 Access-Control-Allow-Headers: origin,accept,content-type
      • Access-Control-Allow-Headers通过设置系统属性设置的可能性:AccessControlAllowHeader
      • 示例: -DAccessControlAllowHeader=origin,accept,content-type,Authorization
      • 可能的值:
      • –cors:激活CORS支持,Access-Control-Allow-Origin将被设置为*。
      • –cors=some_value:Access-Control-Allow-Origin将被设置为some_value。
  • CORS参数的例子

    • –cors=http://appOne.company.com
    • –cors=”http://appOne.company.com, http://appTwo.company.com“:请注意双引号,以便将这两个URL视为单个参数的一部分。

二、Socket通信承载功能一览

可以参看(4.2.46)AndroidGodEye源码整体结构分析 中的原生写法

  1. 在线程或后台任务(非UI线程)中,实例化socket并设置监听端口;
  2. 开启 while (mIsRunning) {…}死循环体,监听请求
    循环体内:
    2.1 Socket socket = mServerSocket.accept();响应一次请求
    2.2 mRequestHandler.handle(socket) 处理本次请求
    2.2.1 获取请求体中的Path
    2.2.2 根据 “后缀命名规则(/空,.html,.js等)“和“已支持Servlet列表(/login,/regiter…)”进行响应,前者返回本地文件数据流,后者返回java处理数据流
    2.2.2 根据请求体中的参数,进行对应响应
    2.2.2 html页面元素中的标签资源,会再次向服务器做请求,类似“后缀命名规则(/空,.html,.js等)逻辑
    2.2.3 返回“HTTP/1.0 200 OK”,“Content-Type: ”,“Content-Length:”头部,以及对应数据体
    2.3 socket.close() 结束本次请求


    【图1 processon】

NanoHTTPD项目目前由四部分组成:

  • /core - 由一(1)个Java文件组成的全功能的HTTP(s)服务器,可随时为您自己的项目定制/继承。

  • /samples - 关于如何定制NanoHTTPD的简单例子。看到HelloServer.java是一款热情招呼你的杀手级应用!

  • /nanolets - 独立的nano应用服务器,在/core的基础上,为实现者提供一个类似于系统的servlet。 响应URL返回JAVA运算数据

  • /webserver - 独立的文件服务器。运行和享受。一种流行的用途似乎是从Android设备上提供文件。 响应URL返回本地资源文件数据

  • /websocket - Websocket的实现,也在一个Java文件中,依赖于core。

  • /fileupload - 整合了apache常用文件上传库。

三、核心实现NanoHTTPD

新版本的代码可能和分析的不太一样,但是逻辑是相同的

3.1 持有变量与构造函数

    public final String hostname;public final int myPort;//端口private volatile ServerSocket myServerSocket;//socketServerprivate IFactoryThrowing<ServerSocket, IOException> serverSocketFactory = new DefaultServerSocketFactory();//socketServer工厂private IHandler<IHTTPSession, Response> httpHandler;//请求处理器protected List<IHandler<IHTTPSession, Response>> interceptors = new ArrayList<IHandler<IHTTPSession, Response>>(4);//拦截器protected IAsyncRunner asyncRunner;//异步任务运行器private IFactory<ITempFileManager> tempFileManagerFactory;//临时文件操作管理器的生成工厂public NanoHTTPD(int port) {this(null, port);}public NanoHTTPD(String hostname, int port) {this.hostname = hostname;this.myPort = port;setTempFileManagerFactory(new DefaultTempFileManagerFactory());setAsyncRunner(new DefaultAsyncRunner());// creates a default handler that redirects to deprecated serve();this.httpHandler = new IHandler<IHTTPSession, Response>() {@Overridepublic Response handle(IHTTPSession input) {return NanoHTTPD.this.serve(input);}};}

3.1.1 DefaultServerSocketFactory 服务SocketServer工厂

/*** SocketServer工厂,用于生产ServerSocket*/
public class DefaultServerSocketFactory implements IFactoryThrowing<ServerSocket, IOException> {@Overridepublic ServerSocket create() throws IOException {return new ServerSocket();}}/*** 工厂能力抽象: 一个“可以在生产实体期间抛出异常”的生成函数*/
public interface IFactoryThrowing<T, E extends Throwable> {T create() throws E;
}

nanohttpd下的sockets下还有一个SecureServerSocketFactory

/*** Creates a new SSLServerSocket*/
public class SecureServerSocketFactory implements IFactoryThrowing<ServerSocket, IOException> {private SSLServerSocketFactory sslServerSocketFactory;private String[] sslProtocols;public SecureServerSocketFactory(SSLServerSocketFactory sslServerSocketFactory, String[] sslProtocols) {this.sslServerSocketFactory = sslServerSocketFactory;this.sslProtocols = sslProtocols;}@Overridepublic ServerSocket create() throws IOException {SSLServerSocket ss = null;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);return ss;}}

3.1.2 DefaultTempFileManagerFactory 临时文件的操作管理器的生成工厂

nanohttpd下的tempfiles全部

  • 临时文件操作管理器的生成工厂
/*** 创建和清除临时文件的默认策略生成器*/
public class DefaultTempFileManagerFactory implements IFactory<ITempFileManager> {@Overridepublic ITempFileManager create() {return new DefaultTempFileManager();}
}/*** 工厂能力抽象*/
public interface IFactory<T> {T create();
}
  • 默认的临时文件操作管理器
public interface ITempFileManager {void clear();public ITempFile createTempFile(String filename_hint) throws Exception;
}/**
* 创建和清除临时文件的默认策略
* -  文件被存储在默认路径 java.io.tmpdir所指向位置
* - 临时文件的创建
* - 提供清除功能
*/
public class DefaultTempFileManager implements ITempFileManager {private final File tmpdir;private final List<ITempFile> tempFiles;public DefaultTempFileManager() {this.tmpdir = new File(System.getProperty("java.io.tmpdir"));if (!tmpdir.exists()) {tmpdir.mkdirs();}this.tempFiles = new ArrayList<ITempFile>();}@Overridepublic void clear() {for (ITempFile file : this.tempFiles) {try {file.delete();} catch (Exception ignored) {NanoHTTPD.LOG.log(Level.WARNING, "could not delete file ", ignored);}}this.tempFiles.clear();}@Overridepublic ITempFile createTempFile(String filename_hint) throws Exception {DefaultTempFile tempFile = new DefaultTempFile(this.tmpdir);this.tempFiles.add(tempFile);return tempFile;}
}
  • 临时文件:
public interface ITempFile {public void delete() throws Exception;public String getName();public OutputStream open() throws Exception;
}public class DefaultTempFile implements ITempFile {private final File file;private final OutputStream fstream;public DefaultTempFile(File tempdir) throws IOException {this.file = File.createTempFile("NanoHTTPD-", "", tempdir);this.fstream = new FileOutputStream(this.file);}@Overridepublic void delete() throws Exception {NanoHTTPD.safeClose(this.fstream);if (!this.file.delete()) {throw new Exception("could not delete temporary file: " + this.file.getAbsolutePath());}}@Overridepublic String getName() {return this.file.getAbsolutePath();}@Overridepublic OutputStream open() throws Exception {return this.fstream;}
}

3.1.3 DefaultAsyncRunner 异步任务运行器

默认的,当有新的Request来的时候,创新一个新的线程来响应;
这个线程被设置为守护线程和对应的命名

/**
* 为requests的响应过程提供异步过程
*/
public interface IAsyncRunner {void closeAll();void closed(ClientHandler clientHandler);void exec(ClientHandler code);
}public class DefaultAsyncRunner implements IAsyncRunner {protected long requestCount;private final List<ClientHandler> running = Collections.synchronizedList(new ArrayList<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;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;}
}

3.1.4 IHandler响应器

我们在使用NanoHTTPD时候会重写覆盖Response serve(IHTTPSession session)方法,用于真正的处理Request

这里给一个默认的实现,返回一个 NotFound异常Reponse

  // creates a default handler that redirects to deprecated serve();this.httpHandler = new IHandler<IHTTPSession, Response>() {@Overridepublic Response handle(IHTTPSession input) {return NanoHTTPD.this.serve(input);}};@Deprecatedprotected Response serve(IHTTPSession session) {return Response.newFixedLengthResponse(Status.NOT_FOUND, NanoHTTPD.MIME_PLAINTEXT, "Not Found");}

3.2 核心start函数

  • 【步骤1】使用服务SocketServer工厂生成一个 ServerSocket
  • 【步骤2】开启一个线程,并将当前NanoHTTPD传递过去
    public void start(final int timeout) throws IOException {start(timeout, true);}public void start(final int timeout, boolean daemon) throws IOException {//【步骤1】使用服务SocketServer工厂生成一个 ServerSocketthis.myServerSocket = this.getServerSocketFactory().create();this.myServerSocket.setReuseAddress(true);//【步骤2】开启一个线程,并将当前NanoHTTPD传递过去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.getBindException() == 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.getBindException() != null) {throw serverRunnable.getBindException();}}protected ServerRunnable createServerRunnable(final int timeout) {return new ServerRunnable(this, timeout);}
  • 【步骤3】异步线程的执行

    • 【步骤3.1】绑定端口和地址,重置绑定标示和绑定异常
    • 【步骤3.2】开启死循环,监听request: ServerSocket.accept()
    • 【步骤3.3】根据请求soket及其输入流构建处理器ClientHandler
    • 【步骤3.4】ClientHandler传入异步任务运行器中执行
    @Overridepublic 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);}final InputStream inputStream = finalAccept.getInputStream();httpd.asyncRunner.exec(httpd.createClientHandler(finalAccept, inputStream));} catch (IOException e) {NanoHTTPD.LOG.log(Level.FINE, "Communication with the client broken", e);}} while (!httpd.getMyServerSocket().isClosed());}
  • 【步骤4】分发请求

    • 【步骤4.1】获取socket的输出流
    • 【步骤4.2】获取 临时文件管理器
    • 【步骤4.3】NanoHTTPD、临时文件管理器ITempFileManager、输入流、输出流、ip端口,共同构建HttpSession
    • 【步骤4.3】socket未关闭时,死循环执行 session.execute();
public class ClientHandler implements Runnable {private final NanoHTTPD httpd;private final InputStream inputStream;private final Socket acceptSocket;public ClientHandler(NanoHTTPD httpd, InputStream inputStream, Socket acceptSocket) {this.httpd = httpd;this.inputStream = inputStream;this.acceptSocket = acceptSocket;}public void close() {NanoHTTPD.safeClose(this.inputStream);NanoHTTPD.safeClose(this.acceptSocket);}@Overridepublic 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());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.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);}}
}

3.3 HttpSession

HttpSession是java里Session概念的实现,简单来说一个Session就是一次httpClient->httpServer的连接,当连接close后session就结束了,如果没结束则session会一直存在。这点从这里的代码也能看到:如果socket不close或者exec没有抛出异常(异常有可能是client段断开连接)session会一直执行exec方法

一个HttpSession中存储了一次网络连接中server应该保存的信息,比如:URI,METHOD,PARAMS,HEADERS,COOKIES等

public interface IHTTPSession {void execute() throws IOException;void parseBody(Map<String, String> files) throws IOException, ResponseException;CookieHandler getCookies();Map<String, String> getHeaders();InputStream getInputStream();Method getMethod();Map<String, List<String>> getParameters();String getQueryParameterString();String getUri();String getRemoteIpAddress();String getRemoteHostName();
}

我们重点看下execute函数:

其实就是:解析Respons中的各类参数赋值到HttpSession中,把当前HttpSession传给自己复写的处理函数作为形参,调用并返回Response

  • 【步骤1】读取socket数据流的前8192个字节,因为http协议中头部最长为8192
  • 【步骤2】通过findHeaderEnd函数找到header数据的截止位置,并把位置保存到splitbyte内。
    byte[] buf = new byte[BUFSIZE];  splitbyte = 0;  rlen = 0;  {  int read = -1;  try {  read = inputStream.read(buf, 0, BUFSIZE);  } catch (Exception e) {  safeClose(inputStream);  safeClose(outputStream);  throw new SocketException("NanoHttpd Shutdown");  }  if (read == -1) {  // socket was been closed  safeClose(inputStream);  safeClose(outputStream);  throw new SocketException("NanoHttpd Shutdown");  }  while (read > 0) {  rlen += read;  splitbyte = findHeaderEnd(buf, rlen);  if (splitbyte > 0)  break;  read = inputStream.read(buf, rlen, BUFSIZE - rlen);  }  }  //涉及函数private int findHeaderEnd(final byte[] buf, int rlen) {  //Http协议规定header和body之间使用两个回车换行分割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;  } 
  • 【步骤3】使用unread函数将之前读出来的body pushback回去,这里使用了pushbackstream,用法比较巧妙,因为一旦读到了header的尾部就需要进入下面的逻辑来判断是否需要再读下去了,而不应该一直读,读到没有数据为止
  • 【步骤4】decodeHeader,将byte的header转换为java对象
if (splitbyte < rlen) {  inputStream.unread(buf, splitbyte, rlen - splitbyte);
}  parms = new HashMap<String, String>();
if(null == headers) {  headers = new HashMap<String, String>();
}  // Create a BufferedReader for parsing the header.
BufferedReader hin = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(buf, 0, rlen)));  // Decode the header into parms and header java properties
Map<String, String> pre = new HashMap<String, String>();
decodeHeader(hin, pre, parms, headers);  //涉及函数
//1.Http协议第一行是Method URI HTTP_VERSION
//2.后面每行都是KEY:VALUE格式的header
//3.uri需要经过URIDecode处理后才能使用
//4.uri中如果包含?则表示有param,httprequest的param一般表现为:/index.jsp?username=xiaoming&id=2
private void decodeHeader(BufferedReader in, Map<String, String> pre, Map<String, String> parms, Map<String, String> headers)  throws ResponseException {  try {  // Read the request line  String inLine = in.readLine();  if (inLine == null) {  return;  }  StringTokenizer st = new StringTokenizer(inLine);  if (!st.hasMoreTokens()) {  throw new ResponseException(Response.Status.BAD_REQUEST, "BAD REQUEST: Syntax error. Usage: GET /example/file.html");  }  pre.put("method", st.nextToken());  if (!st.hasMoreTokens()) {  throw new ResponseException(Response.Status.BAD_REQUEST, "BAD REQUEST: Missing URI. Usage: GET /example/file.html");  }  String uri = st.nextToken();  // Decode parameters from the URI  int qmi = uri.indexOf('?');  if (qmi >= 0) {  decodeParms(uri.substring(qmi + 1), parms);  uri = decodePercent(uri.substring(0, qmi));  } else {  uri = decodePercent(uri);  }  // If there's another token, it's protocol version,  // followed by HTTP headers. Ignore version but parse headers.  // NOTE: this now forces header names lowercase since they are  // case insensitive and vary by client.  if (st.hasMoreTokens()) {  String line = in.readLine();  while (line != null && line.trim().length() > 0) {  int p = line.indexOf(':');  if (p >= 0)  headers.put(line.substring(0, p).trim().toLowerCase(Locale.US), line.substring(p + 1).trim());  line = in.readLine();  }  }  pre.put("uri", uri);  } catch (IOException ioe) {  throw new ResponseException(Response.Status.INTERNAL_ERROR, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage(), ioe);  }
}  
  • 【步骤5】处理cookie
this.cookies = new CookieHandler(this.headers);
  • 【步骤6】响应处理,并生成Respons

    • 过滤器调用
    • 自己重写的处理函数响应
r = httpd.handle(this);//涉及NanoHTTPD的函数
public Response handle(IHTTPSession session) {for (IHandler<IHTTPSession, Response> interceptor : interceptors) {Response response = interceptor.handle(session);if (response != null)return response;}return httpHandler.handle(session);
}//涉及NanoHTTPD的变量
this.httpHandler = new IHandler<IHTTPSession, Response>() {@Overridepublic Response handle(IHTTPSession input) {return NanoHTTPD.this.serve(input);}};//涉及NanoHTTPD的函数
@Deprecated
protected Response serve(IHTTPSession session) {return Response.newFixedLengthResponse(Status.NOT_FOUND, NanoHTTPD.MIME_PLAINTEXT, "Not Found");
}
  • 【步骤7】Response返回,将返回数据写入输出流中
r.send(this.outputStream);

3.4 Response响应

发送response的步骤如下:
1.设置mimeType和Time等内容。

2.创建一个PrintWriter,按照HTTP协议依次开始写入内容

3.第一行是HTTP的返回码

4.然后是content-Type

5.然后是Date时间

6.之后是其他的HTTP Header

7.设置Keep-Alive的Header,Keep-Alive是Http1.1的新特性,作用是让客户端和服务器端之间保持一个长链接。

8.如果客户端指定了ChunkedEncoding则分块发送response,Chunked Encoding是Http1.1的又一新特性。一般在response的body比较大的时候使用,server端会首先发送response的HEADER,然后分块发送response的body,每个分块都由chunk length\r\n和chunk data\r\n组成,最后由一个0\r\n结束。

/** * Sends given response to the socket. */
protected void send(OutputStream outputStream) {  String mime = mimeType;  SimpleDateFormat gmtFrmt = new SimpleDateFormat("E, d MMM yyyy HH:mm:ss 'GMT'", Locale.US);  gmtFrmt.setTimeZone(TimeZone.getTimeZone("GMT"));  try {  if (status == null) {  throw new Error("sendResponse(): Status can't be null.");  }  PrintWriter pw = new PrintWriter(outputStream);  pw.print("HTTP/1.1 " + status.getDescription() + " \r\n");  if (mime != null) {  pw.print("Content-Type: " + mime + "\r\n");  }  if (header == null || header.get("Date") == null) {  pw.print("Date: " + gmtFrmt.format(new Date()) + "\r\n");  }  if (header != null) {  for (String key : header.keySet()) {  String value = header.get(key);  pw.print(key + ": " + value + "\r\n");  }  }  sendConnectionHeaderIfNotAlreadyPresent(pw, header);  if (requestMethod != Method.HEAD && chunkedTransfer) {  sendAsChunked(outputStream, pw);  } else {  int pending = data != null ? data.available() : 0;  sendContentLengthHeaderIfNotAlreadyPresent(pw, header, pending);  pw.print("\r\n");  pw.flush();  sendAsFixedLength(outputStream, pending);  }  outputStream.flush();  safeClose(data);  } catch (IOException ioe) {  // Couldn't write? No can do.  }
}  

9.如果没指定ChunkedEncoding则需要指定Content-Length来让客户端指定response的body的size,然后再一直写body直到写完为止

private void sendAsChunked(OutputStream outputStream, PrintWriter pw) throws IOException {  pw.print("Transfer-Encoding: chunked\r\n");  pw.print("\r\n");  pw.flush();  int BUFFER_SIZE = 16 * 1024;  byte[] CRLF = "\r\n".getBytes();  byte[] buff = new byte[BUFFER_SIZE];  int read;  while ((read = data.read(buff)) > 0) {  outputStream.write(String.format("%x\r\n", read).getBytes());  outputStream.write(buff, 0, read);  outputStream.write(CRLF);  }  outputStream.write(String.format("0\r\n\r\n").getBytes());  }  private void sendAsFixedLength(OutputStream outputStream, int pending) throws IOException {  if (requestMethod != Method.HEAD && data != null) {  int BUFFER_SIZE = 16 * 1024;  byte[] buff = new byte[BUFFER_SIZE];  while (pending > 0) {  int read = data.read(buff, 0, ((pending > BUFFER_SIZE) ? BUFFER_SIZE : pending));  if (read <= 0) {  break;  }  outputStream.write(buff, 0, read);  pending -= read;  }  }  }       

参考文献

  • NanoHttpd/nanohttpd
  • 使用NanoHttpd在Android上实现HttpServer
  • 如何开发自己的HttpServer-NanoHttpd源码解读

(4.2.47.2)NanoHttpd手机服务器相关推荐

  1. 云手机服务器系统,云手机主板 私有云手机系统 私有云手机平台 云手机服务器...

    SPECIFICATION FOR MT6771 Customer : Product model: Z750 Module Customer model: Version Designed by C ...

  2. 亚信科技虚拟手机服务器地址,亚信云手机服务器地址是什么

    亚信云手机服务器地址是什么 内容精选 换一换 华为云帮助中心,为用户提供产品简介.价格说明.购买指南.用户指南.API参考.最佳实践.常见问题.视频帮助等技术文档,帮助您快速上手使用华为云服务. 华为 ...

  3. 如何修改手机服务器的ip地址,怎么修改手机服务器ip地址

    怎么修改手机服务器ip地址 内容精选 换一换 云堡垒机的认证方式是系统全局可选择设置,即系统所有用户都可选择认证方式,包括本地认证.多因子认证(手机令牌.手机短信.USBKey.动态令牌).远程认证( ...

  4. Android之解决PC浏览器上传表单文件到手机服务器read数据错误导致有时候下载到手机的文件打开文字错乱问题

    1 .问题 手机客户端app通过java socket写了一个服务器,然后PC端通过浏览器访问服务端显示上传页面(这些资源都在手机端),然后其中有一个功能是PC端上传文件到手机服务器,然后把这个文件下 ...

  5. 无限流量手机怎样改服务器,无限流量手机服务器

    无限流量手机服务器 内容精选 换一换 云手机作为一种新型服务,依靠华为云的服务器底座.创新技术及周边服务的天然优势,让移动应用不但可以在物理手机运行,还可以在云端智能运行.相比常见的手机模拟方案,云手 ...

  6. 手机服务器共享信息,手机云共享服务器

    手机云共享服务器 内容精选 换一换 云手机(Cloud Phone,简称CPH),是基于华为云裸金属服务器虚拟出的带有原生安卓操作系统,同时具有虚拟手机功能的云服务器.简单来说,云手机=云服务器+An ...

  7. 手机服务器异常修复,手机服务器异常

    手机服务器异常 内容精选 换一换 正常返回值说明200 OKGET和PUT操作正常返回.201 CreatedPOST操作正常返回.异常返回值说明400 Bad Request请求参数错误403 Fo ...

  8. 灵活使用手机之-手机服务器和客户端

    灵活使用手机之-手机服务器和客户端 2020-12-15 近期玩手机少了,更多的精力用来学习未来必将大放光明的linux系统,一个redhat系列,一个ubuntu系列,把自己搞的晕头晕脑,虽然了解的 ...

  9. 用AndServer做安卓手机服务器

    用AndServer搭建手机服务器以及跨域问题解决 AndServer官方文档地址 AndServer简介 什么情况需要用? AndServer官方文档地址 https://yanzhenjie.co ...

最新文章

  1. 2018全球100个最有价值的科技品牌 18个中国品牌上榜
  2. asp.net 获取插入记录的ID
  3. Linux下硬盘安装linux
  4. win11安装chocolatey软件包管理工具(win版)
  5. EPUB.js 解决图片裁剪问题(缩放问题)
  6. vim 设置标签等操作
  7. mysql 自动备份_如何将mysql备份自动存储到minio
  8. 面试系列-Java面试总则
  9. TF ckpt转pb脚本
  10. 三角网导线平差实例_三角网间接平差示例
  11. python垃圾邮件过滤_手把手教你用 Python 和 Scikit-Learn 实现垃圾邮件过滤
  12. c++11新特性std::is_trivial
  13. 直接下载:Windows 10正式版官方原版镜像!
  14. 经验分享:SecureCRT远程登录树莓派开发板
  15. 学python还是matlab_先学matlab还是python-女性时尚流行美容健康娱乐mv-ida网
  16. Google工具包Guava——聊聊代码校验Preconditions
  17. Cygwin环境使用第三方ARMGCC编译eCos系统
  18. 快递e栈控制台版实现心得
  19. VMware ,centos7能ping通外面,但是外面ping不通里面,且使用工具远程连接不上
  20. Flink系列之Flink流式计算引擎基础理论

热门文章

  1. 推荐一款图片放大不失真的软件-PhotoZoom
  2. 个人QQ免签,实现QQ收款
  3. centos在线下载mysql出现404_Centos下载mysql遇到的问题及步骤(rpm)
  4. java将包含unicode的字符串转换成中文汉字
  5. uniapp实现计数器
  6. 安卓基本的POST请求工具类
  7. logins登录项目
  8. 2020年面试随想记录
  9. 带MPPT的同步降压电路的设计-开题报告
  10. 批量查询UPS快递物流的最简单方法