使用GZIPOutputStream在Java中发送压缩块

3
我正在尝试通过Java套接字发送压缩的HTML文件,但浏览器显示为空的HTML文件。
问题在于,当我尝试发送未压缩的HTML时,一切正常(是的,我确实相应地修改了HTTP标头)。
最初的回答:我正在尝试使用GZIP压缩算法将HTML文件压缩,但是我忘记在HTTP响应标头中包括Content-Encoding: gzip。因此,浏览器无法正确解压缩文件并显示内容。
private void sendResponse(String headers, String body) throws IOException
{   
    BufferedOutputStream output = new BufferedOutputStream(
        this.SOCKET.getOutputStream());
    byte[] byteBody = null;

    // GZIP compression
    if(body != null && this.encoding.contains("gzip"))
    {
        ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
        GZIPOutputStream zipStream = new GZIPOutputStream(byteStream);
        zipStream.write(body.getBytes(this.charset));
        zipStream.flush();
        byteBody = byteStream.toByteArray();
        byteStream.flush();
        byteStream.close();
        zipStream.close();
    }
    else
        byteBody = body.getBytes(this.charset);

    // Sending response
    byte[] msg1 = (Integer.toHexString(byteBody.length) + "\r\n")
        .getBytes(this.charset);
    byte[] msg2 = byteBody;
    byte[] msg3 = ("\r\n" + "0").getBytes(this.charset);

    output.write(headers.getBytes(this.charset));
    output.write(msg1);
    output.write(msg2);
    output.write(msg3);
    output.flush();
    output.close();
}

基本上,headers 包含 HTTP 标头,而 body 则包含 HTML 文件。其余部分似乎不需要解释。可能是什么原因导致这种情况?
编辑:标头生成如下:
    headers = "HTTP/1.1 200 OK\r\n";
    headers += "Date: " + WebServer.getServerTime(Calendar.getInstance()) + "\r\n";
    headers += "Content-Type: text/html; charset=" + this.charset + "\r\n";
    headers += "Set-Cookie: sessionID=" + newCookie + "; Max-Age=600\r\n";
    headers += "Connection: close \r\n";
    if(this.encoding.contains("gzip"))
        headers += "Content-Encoding: gzip\r\n";
    headers += "Transfer-Encoding: chunked \r\n";
    headers += "\r\n";

请显示实际的标题,包括启用和禁用压缩的情况。此外,当您已知完整大小时,为什么要使用分块传输?只需在标题中给出长度即可,这样您就不必执行额外的分块操作。 - Andreas
@Andreas 已添加了头文件。使用分块传输是任务的一部分。 - Gogol31
2个回答

7
问题在于,只有在调用 finish() 方法后,GZIPOutputStream 才算完整。
当你关闭流时,它会自动调用 close() 方法。
由于你在此之前调用了 byteStream.toByteArray(),所以没有获取到完整的数据。
另外,你不需要调用 flush(),因为当你调用 close() 时,也会自动执行该操作。而关闭 GZIPOutputStream 会自动关闭底层流(即 ByteArrayOutputStream)。
所以,你的代码应该是这样的:
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
GZIPOutputStream zipStream = new GZIPOutputStream(byteStream);
zipStream.write(body.getBytes(this.charset));
zipStream.close();
byteBody = byteStream.toByteArray();

-1
一个简单的测试类应该能够告诉你问题出在哪里:
import java.io.ByteArrayOutputStream;
import java.util.zip.GZIPOutputStream;

public class GZipTest {

    public final static void main(String[] args) throws Exception {

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        GZIPOutputStream gzos = new GZIPOutputStream(baos);

        gzos.write("some data".getBytes());
        System.out.println("baos before gzip flush: " + baos.size());
        gzos.flush();
        System.out.println("baos after gzip flush: " + baos.size());
        gzos.close();
        System.out.println("baos after gzip close: " + baos.size());
    }
}

这将导致以下输出:
baos before gzip flush: 10
baos after gzip flush: 10
baos after gzip close: 29

你在构建完body数据后关闭了GZIPOutputStream,因此浏览器接收到不完整的GZIP数据,无法解压缩。


你必须在 GZIPOutputStream 上调用 close()(或 finish())。由于 ByteArrayOutputStream 上的 close() 是无操作的,因此不会截断任何内容。 - Andreas
@Lothar,因此我需要使用GZIPOutputStream包装我的BufferedOutputStream才能使其正常工作,还是我漏掉了什么?这将是一个问题,因为我计划将HTML文件分成许多块发送,并且我需要指定它们的大小(或者我在那方面也漏掉了什么吗?)。 - Gogol31
@Andreas 这就是我在我的测试类中正在做的事情。所以我不确定你的观点是什么。 - Lothar
@Gogol31 你可以将消息体的一部分写入 GZIPOutputStream,并调用 ByteArrayOutputStreamtoByteArray 方法获取数据块。在前面放置 CTE 长度信息并将其发送到网络上。然后,在 ByteArrayOutputStream 上调用 reset 并重复此过程,直到到达消息体数据的末尾。 - Lothar
@Lothar 嗯,它似乎在我写整个正文数据的特定情况下不起作用,这就是我在帖子中所做的,但考虑到你在第一篇帖子中写的内容。 - Gogol31

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接