3

I'm trying to send a compressed HTML file through a java socket but the browser displays an empty HTML file.

The thing is, when I try to send the uncompressed HTML everything works find (yes I do modify the HTTP headers accordingly).

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

Basically, headers contains the HTTP headers, and body the HTML file. The rest seems self explanatory. What could cause such a behavior?

EDIT: The header is generated as such:

    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";
Gogol31
  • 33
  • 1
  • 4
  • Please show actual headers, both with and without compression enabled. --- Also, why are you using chunked transfer when you know the full size. Just give the length in the header, so you don't have to do that extra chunking stuff. – Andreas May 02 '19 at 19:34
  • @Andreas Added the headers. Using chunked transfer is part of the assignment. – Gogol31 May 02 '19 at 19:41

2 Answers2

6

The problem is that a GZIPOutputStream isn't complete until the finish() method has been called.

It is automatically called when you close() the stream.

Since you are calling byteStream.toByteArray() before that has happened, you're not getting complete data.

Also, you don't need to call flush() since that is also automatically done when you call close(). And closing the GZIPOutputStream automatically closes the underlying stream(s) (i.e. the ByteArrayOutputStream).

So, you code should be:

ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
GZIPOutputStream zipStream = new GZIPOutputStream(byteStream);
zipStream.write(body.getBytes(this.charset));
zipStream.close();
byteBody = byteStream.toByteArray();
Andreas
  • 154,647
  • 11
  • 152
  • 247
-1

A simple test class should show you where the problem is:

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

This leads to the following output:

baos before gzip flush: 10
baos after gzip flush: 10
baos after gzip close: 29

You're closing your GZIPOutputStream after building you body data, so the browser receives incomplete GZIP-data and therefor can't decompress it.

Lothar
  • 5,323
  • 1
  • 11
  • 27
  • You have to call `close()` (or `finish()`) on the `GZIPOutputStream`. Since `close()` on a `ByteArrayOutputStream` is a no-op, nothing gets truncated. – Andreas May 02 '19 at 19:33
  • @Lothar I would thus need to wrap my BufferedOutputStream with GZIPOutputStream to make it work or am I missing something? This would be a problem because I plan to send the HTML file in many chunks and I need to specify their size (or am I missing something there too?). – Gogol31 May 02 '19 at 19:33
  • @Andreas That's what I'm doing in my test class. So I'm not sure what your point is. – Lothar May 02 '19 at 19:39
  • @Gogol31 You can write a part of the body data to the `GZIPOutputStream` and call the `ByteArrayOutputStream`'s `toByteArray`-method to get a block of data. Put the CTE-length-info in front and send it onto the wire. Then you call `reset` on your `ByteArrayOutputStream` and repeat until you reach the end of your body data. – Lothar May 02 '19 at 19:43
  • @Lothar Well it does not seem to work in the particular case where I write the whole body data, that is what I did in the post but with taking what you wrote in your first post into account. – Gogol31 May 02 '19 at 19:54