NanoHTTPD - 写入套接字而不是复制字符串来传递页面

NanoHTTPD - write to a socket instead of copy a string to deliver pages

这个问题不是关于 NanoHTTPD 如何传送流式内容,或者它如何在提供页面后保持 HTTP 套接字连接打开。

我非常负责任地生成 HTML,使用 HTML.java,通过传入一个将所有内容组装成字符串的 Writer。

然后我的代码复制该字符串并将其放入 newFixedLengthResponse(),后者将 HTML 发送给客户端。

这意味着,在我的 HTML 生成器写入 Writer stringStream 的整个过程中,一个真正的流——网络浏览器的套接字——是打开的,什么也不做。虽然我的 stringStream 做了太多 - 缓冲越来越多的内存...

我不能直接找到那个插座,然后把它放到我的 HTML 生成器中吗?这样,当我计算 html.div() 时,“

我知道大多数网络服务器不这样做,它们都在内存中缓冲大量字符串,而不是有效地将它们流出网络...

为了我的下一个魔术,我会让 HTTPS 工作 C-;

即使在虚拟内存和 TB RAM 时代,流也比字符串更高效。当我最初发布这个问题时,我不小心没有注意到 HTTPSession 对象已经有一个 outputStream 成员。所以第一步是升级它。将此添加到 IHTTPSession:

OutputStream getOutputStream();

现在将其添加到 HTTPSession:

public OutputStream getOutputStream() {
    return outputStream;
}

并将此方法添加到响应中:

public final void sender(@NonNull OutputStream outputStream, @NonNull Runnable run) {
    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(new BufferedWriter(new OutputStreamWriter(outputStream, new ContentType(mimeType).getEncoding())), false);
        pw.append("HTTP/1.1 ").append(status.getDescription()).append(" \r\n");
        if (mimeType != null) {
            printHeader(pw, "Content-Type", mimeType);
        }
        if (getHeader("date") == null) {
            printHeader(pw, "Date", gmtFrmt.format(new Date()));
        }
        for (Entry<String, String> entry : header.entrySet()) {
            printHeader(pw, entry.getKey(), entry.getValue());
        }
        for (String cookieHeader : cookieHeaders) {
            printHeader(pw, "Set-Cookie", cookieHeader);
        }
        if (getHeader("connection") == null) {
            printHeader(pw, "Connection", (keepAlive ? "keep-alive" : "close"));
        }
        long pending = data != null ? contentLength : 0;
        if (requestMethod != Method.HEAD && chunkedTransfer) {
            printHeader(pw, "Transfer-Encoding", "chunked");
        }
        pw.append("\r\n");
        pw.flush();

        run.run(); // <-- your streaming happens here

        outputStream.flush();
        NanoHTTPD.safeClose(data);
    } catch (IOException ioe) {
        NanoHTTPD.LOG.log(Level.SEVERE, "Could not send response to the client", ioe);
    }
}

请注意,我们可以剔除更多未使用的内容。例如,浏览器不应该知道 Content-Length;它只需要拉出页面并查看它会得到什么。

现在应用程序覆盖 .serve() 并使其看起来像这样:

    public final Response serve(IHTTPSession session) {
        OutputStream outputStream = session.getOutputStream();

        newFixedLengthResponse("").sender(outputStream, () -> {
            new OutputStreamWriter(outputStream).write("Yo, World!");
        } ;

        return null;
    }

最后,为了防止 NanoHTTPD 对 null 产生不满,进入 HTTPSession 并将此 throw 替换为 return:

//            throw new ResponseException(Status.INTERNAL_ERROR, "SERVER INTERNAL ERROR: Serve() returned a null response.");
            return;

进一步清理显然是可能的,但根本原则仍然是因为我的应用程序使用流式传输(具体来说,com.googlecode.jatl.HTML)来构建其页面,网络浏览器可以在我们的页面顶部绘制 我们仍在生成页面的底部。