(4.2.47.2)NanoHttpd手机服务器

一、概述

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请求
        */
        @Override
        public 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参数的例子

二、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;//socketServer
    private 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>() {

            @Override
            public Response handle(IHTTPSession input) {
                return NanoHTTPD.this.serve(input);
            }
        };
    }

3.1.1 DefaultServerSocketFactory 服务SocketServer工厂

/**
 * SocketServer工厂,用于生产ServerSocket
 */
public class DefaultServerSocketFactory implements IFactoryThrowing<ServerSocket, IOException> {

    @Override
    public 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;
    }

    @Override
    public 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> {

    @Override
    public 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>();
    }

    @Override
    public 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();
    }

    @Override
    public 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);
    }

    @Override
    public void delete() throws Exception {
        NanoHTTPD.safeClose(this.fstream);
        if (!this.file.delete()) {
            throw new Exception("could not delete temporary file: " + this.file.getAbsolutePath());
        }
    }

    @Override
    public String getName() {
        return this.file.getAbsolutePath();
    }

    @Override
    public 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;
    }

    @Override
    public void closeAll() {
        // copy of the list for concurrency
        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;
        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>() {

            @Override
            public Response handle(IHTTPSession input) {
                return NanoHTTPD.this.serve(input);
            }
        };

  @Deprecated
    protected 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工厂生成一个 ServerSocket
        this.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传入异步任务运行器中执行
    @Override
    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);
                }
                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);
    }

    @Override
    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());
            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);
        }
    }
}

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>() {

            @Override
            public 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;  
                }  
            }  
        }       

参考文献

猜你喜欢

转载自blog.csdn.net/fei20121106/article/details/80098821