From 146a0fb8b0e90f9196e569152f649baf60d6cc8f Mon Sep 17 00:00:00 2001
From: Joaquín Reñé <jrene@curisit.net>
Date: Tue, 07 Oct 2025 14:52:57 +0000
Subject: [PATCH] #4410 - Comments on classes
---
securis/src/main/java/net/curisit/securis/utils/GZipServletResponseWrapper.java | 175 +++++++++++++++++++++++++++++++++++++++++++++++++++++-----
1 files changed, 160 insertions(+), 15 deletions(-)
diff --git a/securis/src/main/java/net/curisit/securis/utils/GZipServletResponseWrapper.java b/securis/src/main/java/net/curisit/securis/utils/GZipServletResponseWrapper.java
index 82ba7f6..e252ebb 100644
--- a/securis/src/main/java/net/curisit/securis/utils/GZipServletResponseWrapper.java
+++ b/securis/src/main/java/net/curisit/securis/utils/GZipServletResponseWrapper.java
@@ -1,3 +1,6 @@
+/*
+ * Copyright @ 2013 CurisTEC, S.A.S. All Rights Reserved.
+ */
package net.curisit.securis.utils;
import java.io.IOException;
@@ -10,38 +13,92 @@
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletResponseWrapper;
+/**
+ * GZipServletResponseWrapper
+ * <p>
+ * {@link HttpServletResponseWrapper} that transparently compresses the response
+ * body using GZIP. Intended for use in filters/servlets where the caller wants
+ * to wrap the original response and write compressed bytes to the client.
+ * <p>
+ * How it works:
+ * <ul>
+ * <li>Overrides {@link #getOutputStream()} and {@link #getWriter()} to route output through a {@link GZIPOutputStream}.</li>
+ * <li>Ensures mutual exclusivity between OutputStream and Writer access per Servlet API requirements.</li>
+ * <li>Ignores {@link #setContentLength(int)} since compressed size differs from uncompressed.</li>
+ * </ul>
+ *
+ * Usage:
+ * <pre>
+ * GZipServletResponseWrapper gz = new GZipServletResponseWrapper(resp);
+ * chain.doFilter(request, gz);
+ * gz.close(); // important: finish compression and flush buffers
+ * </pre>
+ *
+ * Thread-safety:
+ * <p>
+ * Instances are per-request and not shared.
+ *
+ * Limitations:
+ * <p>
+ * Caller is responsible for setting "Content-Encoding: gzip" and for avoiding
+ * double-compression scenarios.
+ *
+ * @author JRA
+ * Last reviewed by JRA on Oct 6, 2025.
+ */
public class GZipServletResponseWrapper extends HttpServletResponseWrapper {
private GZIPServletOutputStream gzipOutputStream = null;
private PrintWriter printWriter = null;
+ // ---------------------------------------------------------------------
+ // Constructors
+ // ---------------------------------------------------------------------
+
+ /**
+ * GZipServletResponseWrapper
+ * <p>
+ * Wraps the given response. Actual GZIP streams are lazily created on first write.
+ *
+ * @param response the original {@link HttpServletResponse} to wrap
+ * @throws IOException if the underlying response streams cannot be accessed
+ */
public GZipServletResponseWrapper(HttpServletResponse response) throws IOException {
super(response);
}
- public void close() throws IOException {
+ // ---------------------------------------------------------------------
+ // Lifecycle
+ // ---------------------------------------------------------------------
- //PrintWriter.close does not throw exceptions.
- //Hence no try-catch block.
+ /**
+ * close<p>
+ * Closes any open writer or output stream and finalizes the GZIP stream.
+ * Must be called once all response content has been written.
+ *
+ * @throws IOException if closing the underlying streams fails
+ */
+ public void close() throws IOException {
+ // PrintWriter.close does not throw exceptions. Hence no try-catch block.
if (this.printWriter != null) {
this.printWriter.close();
}
-
if (this.gzipOutputStream != null) {
this.gzipOutputStream.close();
}
}
/**
- * Flush OutputStream or PrintWriter
+ * flushBuffer<p>
+ * Flushes the writer and the GZIP output stream, then delegates to the wrapped response.
+ * If multiple exceptions occur, the first encountered is thrown (typical servlet practice).
*
- * @throws IOException
+ * @throws IOException if flushing any of the streams fails
*/
-
@Override
public void flushBuffer() throws IOException {
- //PrintWriter.flush() does not throw exception
+ // PrintWriter.flush() does not throw exception
if (this.printWriter != null) {
this.printWriter.flush();
}
@@ -62,12 +119,23 @@
exception2 = e;
}
- if (exception1 != null)
- throw exception1;
- if (exception2 != null)
- throw exception2;
+ if (exception1 != null) throw exception1;
+ if (exception2 != null) throw exception2;
}
+ // ---------------------------------------------------------------------
+ // Output acquisition
+ // ---------------------------------------------------------------------
+
+ /**
+ * getOutputStream<p>
+ * Returns a {@link ServletOutputStream} that writes compressed data.
+ * Mutually exclusive with {@link #getWriter()} as per Servlet API.
+ *
+ * @return compressed {@link ServletOutputStream}
+ * @throws IOException if the underlying output stream cannot be obtained
+ * @throws IllegalStateException if the writer has been already acquired
+ */
@Override
public ServletOutputStream getOutputStream() throws IOException {
if (this.printWriter != null) {
@@ -79,6 +147,15 @@
return this.gzipOutputStream;
}
+ /**
+ * getWriter<p>
+ * Returns a {@link PrintWriter} that writes compressed data (UTF-8 by default, inherited from response).
+ * Mutually exclusive with {@link #getOutputStream()} as per Servlet API.
+ *
+ * @return compressed {@link PrintWriter}
+ * @throws IOException if streams cannot be allocated
+ * @throws IllegalStateException if the output stream has been already acquired
+ */
@Override
public PrintWriter getWriter() throws IOException {
if (this.printWriter == null && this.gzipOutputStream != null) {
@@ -91,50 +168,118 @@
return this.printWriter;
}
+ /**
+ * setContentLength<p>
+ * No-op. The content length of the zipped content is not known a priori and
+ * will not match the uncompressed length; therefore we do not set it here.
+ *
+ * @param len ignored
+ */
@Override
public void setContentLength(int len) {
- //ignore, since content length of zipped content
- //does not match content length of unzipped content.
+ // ignore, since content length of zipped content does not match content length of unzipped content.
}
+ // ---------------------------------------------------------------------
+ // Inner compressed stream
+ // ---------------------------------------------------------------------
+
+ /**
+ * GZIPServletOutputStream
+ * <p>
+ * Decorates the original {@link ServletOutputStream} with a {@link GZIPOutputStream}.
+ * Delegates readiness and listener to the underlying (container) stream.
+ *
+ * @author JRA
+ * Last reviewed by JRA on Oct 5, 2025.
+ */
private static class GZIPServletOutputStream extends ServletOutputStream {
private final ServletOutputStream servletOutputStream;
private final GZIPOutputStream gzipStream;
+ /**
+ * GZIPServletOutputStream<p>
+ * Creates a new compressed stream wrapper.
+ *
+ * @param servletOutputStream underlying (container-provided) output stream
+ * @throws IOException if the GZIP stream cannot be created
+ */
public GZIPServletOutputStream(ServletOutputStream servletOutputStream) throws IOException {
this.servletOutputStream = servletOutputStream;
this.gzipStream = new GZIPOutputStream(servletOutputStream);
}
+ /**
+ * isReady<p>
+ * Check if the output stream is ready
+ * {@inheritDoc}
+ *
+ * @return isReady
+ */
@Override
public boolean isReady() {
return this.servletOutputStream.isReady();
}
+ /**
+ * setWriteListener<p>
+ * Set the write listener for the output stream
+ * {@inheritDoc}
+ *
+ * @param writeListener
+ */
@Override
public void setWriteListener(WriteListener writeListener) {
this.servletOutputStream.setWriteListener(writeListener);
}
+ /**
+ * write<p>
+ * Write on the gzip stream
+ * {@inheritDoc}
+ *
+ * @param b
+ * @throws IOException
+ */
@Override
public void write(int b) throws IOException {
this.gzipStream.write(b);
}
+ /**
+ * close<p>
+ * Close the gzip stream
+ * {@inheritDoc}
+ *
+ * @throws IOException
+ */
@Override
public void close() throws IOException {
this.gzipStream.close();
}
+ /**
+ * flush<p>
+ * Flush the gzip stream
+ * {@inheritDoc}
+ *
+ * @throws IOException
+ */
@Override
public void flush() throws IOException {
this.gzipStream.flush();
}
+ /**
+ * finish<p>
+ * Explicitly finishes writing of the GZIP stream, without closing the underlying stream.
+ * Not used by the wrapper but available for completeness.
+ *
+ * @throws IOException if finishing fails
+ */
@SuppressWarnings("unused")
public void finish() throws IOException {
this.gzipStream.finish();
}
}
-
}
--
Gitblit v1.3.2