
org.glassfish.grizzly.http.CompressionConfig Maven / Gradle / Ivy
The newest version!
/*
* 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.
*/
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 {@link CompressionMode} enum in all the Grizzly core methods, that expect CompressionModeI.
*
* @see CompressionMode
*/
public interface CompressionModeI {
}
public enum CompressionMode implements CompressionModeI {
OFF, ON, FORCE;
/**
* 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 compressibleMimeTypes = new ArraySet<>(String.class);
// the user-agents, for which the payload will never be compressed
private final ArraySet 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.
*/
@SuppressWarnings("IncompleteCopyConstructor")
public CompressionConfig(final CompressionConfig compression) {
set(compression);
}
public CompressionConfig(final CompressionMode compressionMode, final int compressionMinSize, final Set compressibleMimeTypes,
final Set noCompressionUserAgents) {
this(compressionMode, compressionMinSize, compressibleMimeTypes, noCompressionUserAgents, false);
}
public CompressionConfig(final CompressionMode compressionMode, final int compressionMinSize, final Set compressibleMimeTypes,
final Set 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.
*/
public void set(final CompressionConfig compression) {
compressionMode = compression.compressionMode;
compressionMinSize = compression.compressionMinSize;
setCompressibleMimeTypes(compression.compressibleMimeTypes);
setNoCompressionUserAgents(compression.noCompressionUserAgents);
decompressionEnabled = compression.isDecompressionEnabled();
}
/**
* Returns the {@link CompressionMode}.
*/
public CompressionMode getCompressionMode() {
return 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.
*/
public int getCompressionMinSize() {
return compressionMinSize;
}
/**
* 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 {@link #getCompressibleMimeTypes()}
*/
@Deprecated
public Set 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 {@link #setCompressibleMimeTypes(Set)}
*/
@Deprecated
public void setCompressableMimeTypes(final Set 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 {@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
*/
public Set 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
*/
public void setCompressibleMimeTypes(final Set 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
*/
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.
*/
public Set 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.
*/
public void setNoCompressionUserAgents(final Set 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.
*/
public void setNoCompressionUserAgents(final String... noCompressionUserAgents) {
this.noCompressionUserAgents.clear();
if (noCompressionUserAgents.length > 0) {
this.noCompressionUserAgents.addAll(noCompressionUserAgents);
}
}
/**
* @return true
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
*/
public void setDecompressionEnabled(boolean decompressionEnabled) {
this.decompressionEnabled = decompressionEnabled;
}
/**
* Returns true if a client, based on its {@link HttpRequestPacket}, could be responded with compressed data,
* or false otherwise.
*
* @param compressionConfig {@link CompressionConfig}
* @param request client-side {@link HttpRequestPacket}
* @param aliases compression algorithm aliases (to match with Accept-Encoding header)
* @return true if a client, based on its {@link HttpRequestPacket}, could be responded with compressed data,
* or false 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 {@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.
*/
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;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy