All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.omnifaces.servlet.GzipHttpServletResponse Maven / Gradle / Ivy

There is a newer version: 5.0-M2
Show newest version
/*
 * Copyright 2012 OmniFaces.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 */
package org.omnifaces.servlet;

import java.io.IOException;
import java.io.OutputStream;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.zip.GZIPOutputStream;

import javax.servlet.http.HttpServletResponse;

/**
 * This HTTP servlet response wrapper will GZIP the response when the given threshold has exceeded and the response
 * content type matches one of the given mimetypes.
 *
 * @author Bauke Scholtz
 * @since 1.1
 */
public class GzipHttpServletResponse extends HttpServletResponseOutputWrapper {

	// Constants ------------------------------------------------------------------------------------------------------

	private static final Pattern NO_TRANSFORM =
		Pattern.compile("((.*)[\\s,])?no-transform([\\s,](.*))?", Pattern.CASE_INSENSITIVE);

	// Properties -----------------------------------------------------------------------------------------------------

	private int threshold;
	private Set mimetypes;
	private long contentLength;
	private String vary;
	private boolean noGzip;
	private boolean closing;
	private GzipThresholdOutputStream output;

	// Constructors ---------------------------------------------------------------------------------------------------

	/**
	 * Construct a new GZIP HTTP servlet response based on the given wrapped response, threshold and mimetypes.
	 * @param wrapped The wrapped response.
	 * @param threshold The GZIP buffer threshold.
	 * @param mimetypes The mimetypes which needs to be compressed with GZIP.
	 */
	public GzipHttpServletResponse(HttpServletResponse wrapped, int threshold, Set mimetypes) {
		super(wrapped);
		this.threshold = threshold;
		this.mimetypes = mimetypes;
	}

	// Actions --------------------------------------------------------------------------------------------------------

	@Override
	public void setContentLength(int contentLength) {
		// Get hold of content length locally to avoid it from being set on responses which will actually be gzipped.
		this.contentLength = contentLength;
	}

	// @Override Servlet 3.1.
	public void setContentLengthLong(long contentLength) {
		// Get hold of content length locally to avoid it from being set on responses which will actually be gzipped.
		this.contentLength = contentLength;
	}

	@Override
	public void setHeader(String name, String value) {
		super.setHeader(name, value);

		if (name != null) {
			String lowerCasedName = name.toLowerCase();

			if ("vary".equals(lowerCasedName)) {
				vary = value;
			}
			else if ("content-range".equals(lowerCasedName)) {
				noGzip = (value != null);
			}
			else if ("cache-control".equals(lowerCasedName)) {
				noGzip = (value != null && NO_TRANSFORM.matcher(value).matches());
			}
		}
	}

	@Override
	public void addHeader(String name, String value) {
		super.addHeader(name, value);

		if (name != null && value != null) {
			String lowerCasedName = name.toLowerCase();

			if ("vary".equals(lowerCasedName)) {
				vary = ((vary != null) ? (vary + ",") : "") + value;
			}
			else if ("content-range".equals(lowerCasedName)) {
				noGzip = true;
			}
			else if ("cache-control".equals(lowerCasedName)) {
				noGzip = (noGzip || NO_TRANSFORM.matcher(value).matches());
			}
		}
	}

	@Override
	public void flushBuffer() throws IOException {
		if (isCommitted()) {
			super.flushBuffer();
		}
	}

	@Override
	public void reset() {
		super.reset();

		if (!isCommitted()) {
			contentLength = 0;
			vary = null;
			noGzip = false;

			if (output != null) {
				output.reset();
			}
		}
	}

	@Override
	public void close() throws IOException {
		closing = true;
		super.close();
		closing = false;
	}

	@Override
	protected OutputStream createOutputStream() {
		output = new GzipThresholdOutputStream(threshold);
		return output;
	}

	// Inner classes --------------------------------------------------------------------------------------------------

	/**
	 * This output stream will switch to GZIP compression when the given threshold is exceeded.
	 * 

* This is an inner class because it needs to be able to manipulate the response headers once the decision whether * to GZIP or not has been made. * * @author Bauke Scholtz */ private class GzipThresholdOutputStream extends OutputStream { // Constants -------------------------------------------------------------------------------------------------- private static final String ERROR_CLOSED = "Stream is already closed."; // Properties ------------------------------------------------------------------------------------------------- private byte[] thresholdBuffer; private int thresholdLength; private OutputStream output; private boolean closed; // Constructors ----------------------------------------------------------------------------------------------- public GzipThresholdOutputStream(int threshold) { thresholdBuffer = new byte[threshold]; } // Actions ---------------------------------------------------------------------------------------------------- @Override public void write(int b) throws IOException { write(new byte[] { (byte) b }, 0, 1); } @Override public void write(byte[] bytes) throws IOException { write(bytes, 0, bytes.length); } @Override public void write(byte[] bytes, int offset, int length) throws IOException { checkClosed(); if (length == 0) { return; } if (output == null) { if ((length - offset) <= (thresholdBuffer.length - thresholdLength)) { System.arraycopy(bytes, offset, thresholdBuffer, thresholdLength, length); thresholdLength += length; return; } else { // Threshold buffer has exceeded. Now use GZIP if possible. output = createGzipOutputStreamIfNecessary(true); output.write(thresholdBuffer, 0, thresholdLength); } } output.write(bytes, offset, length); } @Override public void flush() throws IOException { checkClosed(); if (output != null) { output.flush(); } } @Override public void close() throws IOException { if (closed) { return; } if (output == null) { // Threshold buffer hasn't exceeded. Use normal output stream. setContentLength(thresholdLength); output = createGzipOutputStreamIfNecessary(false); output.write(thresholdBuffer, 0, thresholdLength); } output.close(); closed = true; } public void reset() { thresholdLength = 0; output = null; } // Helpers ---------------------------------------------------------------------------------------------------- /** * Create GZIP output stream if necessary. That is, when the given gzip argument is * true, the current response does not have the Cache-Control: no-transform or * Content-Range headers, the current response is not committed, the content type is not * null and the content type matches one of the mimetypes. */ private OutputStream createGzipOutputStreamIfNecessary(boolean gzip) throws IOException { HttpServletResponse originalResponse = (HttpServletResponse) getResponse(); if (gzip && !noGzip && (closing || !isCommitted())) { String contentType = getContentType(); if (contentType != null && mimetypes.contains(contentType.split(";", 2)[0])) { addHeader("Content-Encoding", "gzip"); setHeader("Vary", ((vary != null && !vary.equals("*")) ? (vary + ",") : "") + "Accept-Encoding"); return new GZIPOutputStream(originalResponse.getOutputStream()); } } if (contentLength > 0) { originalResponse.setHeader("Content-Length", String.valueOf(contentLength)); } return originalResponse.getOutputStream(); } /** * Check if the current stream is closed and if so, then throw IO exception. * @throws IOException When the current stream is closed. */ private void checkClosed() throws IOException { if (closed) { throw new IOException(ERROR_CLOSED); } } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy