(4.2.47.2)NanoHttpd手机服务器
- 一、概述
- 二、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响应
- 3.1 持有变量与构造函数
- 参考文献
一、概述
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源码整体结构分析 中的原生写法
- 在线程或后台任务(非UI线程)中,实例化socket并设置监听端口;
开启 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手机服务器相关推荐
- 云手机服务器系统,云手机主板 私有云手机系统 私有云手机平台 云手机服务器...
SPECIFICATION FOR MT6771 Customer : Product model: Z750 Module Customer model: Version Designed by C ...
- 亚信科技虚拟手机服务器地址,亚信云手机服务器地址是什么
亚信云手机服务器地址是什么 内容精选 换一换 华为云帮助中心,为用户提供产品简介.价格说明.购买指南.用户指南.API参考.最佳实践.常见问题.视频帮助等技术文档,帮助您快速上手使用华为云服务. 华为 ...
- 如何修改手机服务器的ip地址,怎么修改手机服务器ip地址
怎么修改手机服务器ip地址 内容精选 换一换 云堡垒机的认证方式是系统全局可选择设置,即系统所有用户都可选择认证方式,包括本地认证.多因子认证(手机令牌.手机短信.USBKey.动态令牌).远程认证( ...
- Android之解决PC浏览器上传表单文件到手机服务器read数据错误导致有时候下载到手机的文件打开文字错乱问题
1 .问题 手机客户端app通过java socket写了一个服务器,然后PC端通过浏览器访问服务端显示上传页面(这些资源都在手机端),然后其中有一个功能是PC端上传文件到手机服务器,然后把这个文件下 ...
- 无限流量手机怎样改服务器,无限流量手机服务器
无限流量手机服务器 内容精选 换一换 云手机作为一种新型服务,依靠华为云的服务器底座.创新技术及周边服务的天然优势,让移动应用不但可以在物理手机运行,还可以在云端智能运行.相比常见的手机模拟方案,云手 ...
- 手机服务器共享信息,手机云共享服务器
手机云共享服务器 内容精选 换一换 云手机(Cloud Phone,简称CPH),是基于华为云裸金属服务器虚拟出的带有原生安卓操作系统,同时具有虚拟手机功能的云服务器.简单来说,云手机=云服务器+An ...
- 手机服务器异常修复,手机服务器异常
手机服务器异常 内容精选 换一换 正常返回值说明200 OKGET和PUT操作正常返回.201 CreatedPOST操作正常返回.异常返回值说明400 Bad Request请求参数错误403 Fo ...
- 灵活使用手机之-手机服务器和客户端
灵活使用手机之-手机服务器和客户端 2020-12-15 近期玩手机少了,更多的精力用来学习未来必将大放光明的linux系统,一个redhat系列,一个ubuntu系列,把自己搞的晕头晕脑,虽然了解的 ...
- 用AndServer做安卓手机服务器
用AndServer搭建手机服务器以及跨域问题解决 AndServer官方文档地址 AndServer简介 什么情况需要用? AndServer官方文档地址 https://yanzhenjie.co ...
最新文章
- 2018全球100个最有价值的科技品牌 18个中国品牌上榜
- asp.net 获取插入记录的ID
- Linux下硬盘安装linux
- win11安装chocolatey软件包管理工具(win版)
- EPUB.js 解决图片裁剪问题(缩放问题)
- vim 设置标签等操作
- mysql 自动备份_如何将mysql备份自动存储到minio
- 面试系列-Java面试总则
- TF ckpt转pb脚本
- 三角网导线平差实例_三角网间接平差示例
- python垃圾邮件过滤_手把手教你用 Python 和 Scikit-Learn 实现垃圾邮件过滤
- c++11新特性std::is_trivial
- 直接下载:Windows 10正式版官方原版镜像!
- 经验分享:SecureCRT远程登录树莓派开发板
- 学python还是matlab_先学matlab还是python-女性时尚流行美容健康娱乐mv-ida网
- Google工具包Guava——聊聊代码校验Preconditions
- Cygwin环境使用第三方ARMGCC编译eCos系统
- 快递e栈控制台版实现心得
- VMware ,centos7能ping通外面,但是外面ping不通里面,且使用工具远程连接不上
- Flink系列之Flink流式计算引擎基础理论