/*
 * Copyright (c) 2013, 2020 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0, which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the
 * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
 * version 2 with the GNU Classpath Exception, which is available at
 * https://www.gnu.org/software/classpath/license.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
 */

package org.glassfish.grizzly.http;

import java.util.Arrays;
import java.util.Collections;
import java.util.Set;

import org.glassfish.grizzly.http.util.DataChunk;
import org.glassfish.grizzly.http.util.Header;
import org.glassfish.grizzly.http.util.HttpUtils;
import org.glassfish.grizzly.utils.ArraySet;

Compression configuration class.
/** * Compression configuration class. */
public final class CompressionConfig {
Common CompressionMode interface. It is a temporary solution used to deprecate CompressionLevel enum in http-server module. This interface is intended to be used in Grizzly core methods only. Please don't use this interface directly in your code. Please use CompressionMode enum in all the Grizzly core methods, that expect CompressionModeI.
See Also:
/** * Common CompressionMode interface. It is a temporary solution used to deprecate CompressionLevel enum in http-server * module. * * This interface is intended to be used in Grizzly core methods only. Please don't use this interface directly in your * code. Please use {@link CompressionMode} enum in all the Grizzly core methods, that expect <tt>CompressionModeI</tt>. * * @see CompressionMode */
public interface CompressionModeI { } public enum CompressionMode implements CompressionModeI { OFF, ON, FORCE;
Returns the CompressionMode based on the string representation.
/** * Returns the {@link CompressionMode} based on the string representation. */
public static CompressionMode fromString(final String mode) { if ("on".equalsIgnoreCase(mode)) { return CompressionMode.ON; } else if ("force".equalsIgnoreCase(mode)) { return CompressionMode.FORCE; } else if ("off".equalsIgnoreCase(mode)) { return CompressionMode.OFF; } throw new IllegalArgumentException("Compression mode is not recognized. Supported modes: " + Arrays.toString(CompressionMode.values())); } } // compression mode private CompressionMode compressionMode; // the min size of the entities, which will be compressed private int compressionMinSize; // mime types of the enitties, which will be compressed private final ArraySet<String> compressibleMimeTypes = new ArraySet<>(String.class); // the user-agents, for which the payload will never be compressed private final ArraySet<String> noCompressionUserAgents = new ArraySet<>(String.class); // Allow decompression of incoming data private boolean decompressionEnabled; public CompressionConfig() { compressionMode = CompressionMode.OFF; }
The copy constructor. The newly constructed CompressionConfig object will have the same settings as the source one, but actual values will be independent, so changes to one CompressionConfig object will not affect the other one.
/** * The copy constructor. The newly constructed CompressionConfig object will have the same settings as the source one, * but actual values will be independent, so changes to one CompressionConfig object will not affect the other one. */
@SuppressWarnings("IncompleteCopyConstructor") public CompressionConfig(final CompressionConfig compression) { set(compression); } public CompressionConfig(final CompressionMode compressionMode, final int compressionMinSize, final Set<String> compressibleMimeTypes, final Set<String> noCompressionUserAgents) { this(compressionMode, compressionMinSize, compressibleMimeTypes, noCompressionUserAgents, false); } public CompressionConfig(final CompressionMode compressionMode, final int compressionMinSize, final Set<String> compressibleMimeTypes, final Set<String> noCompressionUserAgents, final boolean decompressionEnabled) { setCompressionMode(compressionMode); setCompressionMinSize(compressionMinSize); setCompressibleMimeTypes(compressibleMimeTypes); setNoCompressionUserAgents(noCompressionUserAgents); setDecompressionEnabled(decompressionEnabled); }
Copies the source CompressionConfig object value into this object. As the result this CompressionConfig object will have the same settings as the source one, but actual values will be independent, so changes to one CompressionConfig object will not affect the other one.
/** * Copies the source CompressionConfig object value into this object. As the result this CompressionConfig object will * have the same settings as the source one, but actual values will be independent, so changes to one CompressionConfig * object will not affect the other one. */
public void set(final CompressionConfig compression) { compressionMode = compression.compressionMode; compressionMinSize = compression.compressionMinSize; setCompressibleMimeTypes(compression.compressibleMimeTypes); setNoCompressionUserAgents(compression.noCompressionUserAgents); decompressionEnabled = compression.isDecompressionEnabled(); }
Returns the CompressionMode.
/** * Returns the {@link CompressionMode}. */
public CompressionMode getCompressionMode() { return compressionMode; }
Sets the CompressionMode.
/** * Sets the {@link CompressionMode}. */
public void setCompressionMode(final CompressionMode mode) { this.compressionMode = mode != null ? mode : CompressionMode.OFF; }
Returns the minimum size of an entity, which will be compressed.
/** * Returns the minimum size of an entity, which will be compressed. */
public int getCompressionMinSize() { return compressionMinSize; }
Sets the minimum size of an entity, which will be compressed.
/** * Sets the minimum size of an entity, which will be compressed. */
public void setCompressionMinSize(int compressionMinSize) { this.compressionMinSize = compressionMinSize; }
Returns the read-only set of the mime-types, which are allowed to be compressed. Empty set means *all* mime-types are allowed to be compressed.
Deprecated:Use getCompressibleMimeTypes()
/** * Returns the read-only set of the mime-types, which are allowed to be compressed. Empty set means *all* mime-types are * allowed to be compressed. * * @deprecated Use {@link #getCompressibleMimeTypes()} */
@Deprecated public Set<String> getCompressableMimeTypes() { return getCompressibleMimeTypes(); }
Sets the set of the mime-types, which are allowed to be compressed. Empty set means *all* mime-types are allowed to be compressed. Please note that CompressionConfig object will copy the source Set content, so further changes made on the source Set will not affect CompressionConfig object state.
Deprecated:Use setCompressibleMimeTypes(Set<String>)
/** * Sets the set of the mime-types, which are allowed to be compressed. Empty set means *all* mime-types are allowed to * be compressed. * * Please note that CompressionConfig object will copy the source Set content, so further changes made on the source Set * will not affect CompressionConfig object state. * * @deprecated Use {@link #setCompressibleMimeTypes(Set)} */
@Deprecated public void setCompressableMimeTypes(final Set<String> compressibleMimeTypes) { setCompressibleMimeTypes(compressibleMimeTypes); }
Sets the set of the mime-types, which are allowed to be compressed. Empty set means *all* mime-types are allowed to be compressed. Please note that CompressionConfig object will copy the source Set content, so further changes made on the source Set will not affect CompressionConfig object state.
Deprecated:Use setCompressibleMimeTypes(String...)
/** * Sets the set of the mime-types, which are allowed to be compressed. Empty set means *all* mime-types are allowed to * be compressed. * * Please note that CompressionConfig object will copy the source Set content, so further changes made on the source Set * will not affect CompressionConfig object state. * * @deprecated Use {@link #setCompressibleMimeTypes(String...)} */
@Deprecated public void setCompressableMimeTypes(final String... compressibleMimeTypes) { setCompressibleMimeTypes(compressibleMimeTypes); }
Returns the read-only set of the mime-types, which are allowed to be compressed. Empty set means *all* mime-types are allowed to be compressed.
Since:2.3.29
/** * Returns the read-only set of the mime-types, which are allowed to be compressed. Empty set means *all* mime-types are * allowed to be compressed. * * @since 2.3.29 */
public Set<String> getCompressibleMimeTypes() { return Collections.unmodifiableSet(compressibleMimeTypes); }
Sets the set of the mime-types, which are allowed to be compressed. Empty set means *all* mime-types are allowed to be compressed. Please note that CompressionConfig object will copy the source Set content, so further changes made on the source Set will not affect CompressionConfig object state.
Since:2.3.29
/** * Sets the set of the mime-types, which are allowed to be compressed. Empty set means *all* mime-types are allowed to * be compressed. * * Please note that CompressionConfig object will copy the source Set content, so further changes made on the source Set * will not affect CompressionConfig object state. * * @since 2.3.29 */
public void setCompressibleMimeTypes(final Set<String> compressibleMimeTypes) { this.compressibleMimeTypes.clear(); if (compressibleMimeTypes != null && !compressibleMimeTypes.isEmpty()) { this.compressibleMimeTypes.addAll(compressibleMimeTypes); } }
Sets the set of the mime-types, which are allowed to be compressed. Empty set means *all* mime-types are allowed to be compressed. Please note that CompressionConfig object will copy the source Set content, so further changes made on the source Set will not affect CompressionConfig object state.
Since:2.3.29
/** * Sets the set of the mime-types, which are allowed to be compressed. Empty set means *all* mime-types are allowed to * be compressed. * * Please note that CompressionConfig object will copy the source Set content, so further changes made on the source Set * will not affect CompressionConfig object state. * * @since 2.3.29 */
public void setCompressibleMimeTypes(final String... compressibleMimeTypes) { this.compressibleMimeTypes.clear(); if (compressibleMimeTypes.length > 0) { this.compressibleMimeTypes.addAll(compressibleMimeTypes); } }
Returns the read-only set of the user-agents, which will be always responded with uncompressed are. Empty set means that compressed data could be sent to *all* user-agents.
/** * Returns the read-only set of the user-agents, which will be always responded with uncompressed are. * * Empty set means that compressed data could be sent to *all* user-agents. */
public Set<String> getNoCompressionUserAgents() { return Collections.unmodifiableSet(noCompressionUserAgents); }
Sets the set of the user-agents, which will be always responded with uncompressed data. Empty set means that compressed data could be sent to *all* user-agents. Please note that CompressionConfig object will copy the source Set content, so further changes made on the source Set will not affect CompressionConfig object state.
/** * Sets the set of the user-agents, which will be always responded with uncompressed data. Empty set means that * compressed data could be sent to *all* user-agents. * * Please note that CompressionConfig object will copy the source Set content, so further changes made on the source Set * will not affect CompressionConfig object state. */
public void setNoCompressionUserAgents(final Set<String> noCompressionUserAgents) { this.noCompressionUserAgents.clear(); if (noCompressionUserAgents != null && !noCompressionUserAgents.isEmpty()) { this.noCompressionUserAgents.addAll(noCompressionUserAgents); } }
Sets the set of the user-agents, which will be always responded with uncompressed data. Empty set means that compressed data could be sent to *all* user-agents. Please note that CompressionConfig object will copy the source Set content, so further changes made on the source Set will not affect CompressionConfig object state.
/** * Sets the set of the user-agents, which will be always responded with uncompressed data. Empty set means that * compressed data could be sent to *all* user-agents. * * Please note that CompressionConfig object will copy the source Set content, so further changes made on the source Set * will not affect CompressionConfig object state. */
public void setNoCompressionUserAgents(final String... noCompressionUserAgents) { this.noCompressionUserAgents.clear(); if (noCompressionUserAgents.length > 0) { this.noCompressionUserAgents.addAll(noCompressionUserAgents); } }
Returns:true if decompression of incoming data is enabled.
Since:2.3.29
/** * @return <code>true</code> if decompression of incoming data is enabled. * * @since 2.3.29 */
public boolean isDecompressionEnabled() { return decompressionEnabled; }
If set to true content-encoding header for incoming data will be respected and data will be decompressed and the decompressed stream will be passed on.
Since:2.3.29
/** * If set to <code>true</code> content-encoding header for incoming data will be respected and data will be decompressed * and the decompressed stream will be passed on. * * @since 2.3.29 */
public void setDecompressionEnabled(boolean decompressionEnabled) { this.decompressionEnabled = decompressionEnabled; }
Returns true if a client, based on its HttpRequestPacket, could be responded with compressed data, or false otherwise.
Params:
Returns:true if a client, based on its HttpRequestPacket, could be responded with compressed data, or false otherwise
/** * Returns <tt>true</tt> if a client, based on its {@link HttpRequestPacket}, could be responded with compressed data, * or <tt>false</tt> otherwise. * * @param compressionConfig {@link CompressionConfig} * @param request client-side {@link HttpRequestPacket} * @param aliases compression algorithm aliases (to match with Accept-Encoding header) * @return <tt>true</tt> if a client, based on its {@link HttpRequestPacket}, could be responded with compressed data, * or <tt>false</tt> otherwise */
public static boolean isClientSupportCompression(final CompressionConfig compressionConfig, final HttpRequestPacket request, final String[] aliases) { final CompressionMode mode = compressionConfig.getCompressionMode(); switch (mode) { case OFF: return false; default: // Compress only since HTTP 1.1 if (org.glassfish.grizzly.http.Protocol.HTTP_1_1 != request.getProtocol()) { return false; } if (!isClientSupportContentEncoding(request, aliases)) { return false; } // If force mode, always compress (test purposes only) return mode == CompressionMode.FORCE || compressionConfig.checkUserAgent(request); } }
Returns true if based on this configuration user-agent, specified in the HttpRequestPacket, can receive compressed data.
/** * Returns <tt>true</tt> if based on this configuration user-agent, specified in the {@link HttpRequestPacket}, can * receive compressed data. */
public boolean checkUserAgent(final HttpRequestPacket request) { // Check for incompatible Browser if (!noCompressionUserAgents.isEmpty()) { final DataChunk userAgentValueDC = request.getHeaders().getValue(Header.UserAgent); if (userAgentValueDC != null && indexOf(noCompressionUserAgents.getArray(), userAgentValueDC) != -1) { return false; } } return true; }
Returns true if the resource with the given content-type could be compressed, or false otherwise.
/** * Returns <tt>true</tt> if the resource with the given content-type could be compressed, or <tt>false</tt> otherwise. */
public boolean checkMimeType(final String contentType) { return compressibleMimeTypes.isEmpty() || indexOfStartsWith(compressibleMimeTypes.getArray(), contentType) != -1; } private static boolean isClientSupportContentEncoding(HttpRequestPacket request, final String[] aliases) { // Check if browser support gzip encoding final DataChunk acceptEncodingDC = request.getHeaders().getValue(Header.AcceptEncoding); if (acceptEncodingDC == null) { return false; } String alias = null; int idx = -1; for (int i = 0, len = aliases.length; i < len; i++) { alias = aliases[i]; idx = acceptEncodingDC.indexOf(alias, 0); if (idx != -1) { break; } } if (idx == -1) { return false; } assert alias != null; // we only care about q=0/q=0.0. If present, the user-agent // doesn't support this particular compression. int qvalueStart = acceptEncodingDC.indexOf(';', idx + alias.length()); if (qvalueStart != -1) { qvalueStart = acceptEncodingDC.indexOf('=', qvalueStart); final int commaIdx = acceptEncodingDC.indexOf(',', qvalueStart); final int qvalueEnd = commaIdx != -1 ? commaIdx : acceptEncodingDC.getLength(); if (HttpUtils.convertQValueToFloat(acceptEncodingDC, qvalueStart + 1, qvalueEnd) == 0.0f) { return false; } } return true; } private static int indexOf(String[] aliases, DataChunk dc) { if (dc == null || dc.isNull()) { return -1; } for (int i = 0; i < aliases.length; i++) { final String alias = aliases[i]; if (dc.indexOf(alias, 0) != -1) { return i; } } return -1; } private static int indexOfStartsWith(String[] aliases, String s) { if (s == null || s.length() == 0) { return -1; } for (int i = 0; i < aliases.length; i++) { final String alias = aliases[i]; if (s.startsWith(alias)) { return i; } } return -1; } }