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

问题描述

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

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

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

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

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

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

对于我的下一个魔术,我将使 HTTPS 工作 C-;

1 个答案:

答案 0 :(得分:0)

即使在虚拟内存和 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 进行 snit-fit,进入 HTTPSession 并用返回替换这个 throw

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

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

解决方法

即使在虚拟内存和 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 进行 snit-fit,进入 HTTPSession 并用返回替换这个 throw

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

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

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...