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

org.eclipse.jetty.compression.server.CompressionConfig Maven / Gradle / Ivy

//
// ========================================================================
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//

package org.eclipse.jetty.compression.server;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Stream;

import org.eclipse.jetty.http.HttpException;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.http.QuotedQualityCSV;
import org.eclipse.jetty.http.pathmap.PathSpecSet;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.util.AsciiLowerCaseSet;
import org.eclipse.jetty.util.IncludeExclude;
import org.eclipse.jetty.util.IncludeExcludeSet;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.component.AbstractLifeCycle;

/**
 * 

Configuration for a specific compression behavior per matching path from the {@link CompressionHandler}.

*

Configuration is split between compression (of responses) and decompression (of requests).

*/ @ManagedObject("Compression Configuration") public class CompressionConfig extends AbstractLifeCycle { /** * Set of {@code Accept-Encoding} encodings that are supported for compressing Response content. */ private final IncludeExcludeSet compressEncodings; /** * Set of {@code Content-Encoding} encodings that are supported for decompressing Request content. */ private final IncludeExcludeSet decompressEncodings; /** * Set of HTTP Methods that are supported for compressing Response content. */ private final IncludeExcludeSet compressMethods; /** * Set of HTTP Methods that are supported for decompressing Request content. */ private final IncludeExcludeSet decompressMethods; /** * Mime-Types that support decompressing of Request content. */ private final IncludeExcludeSet compressMimeTypes; /** * Mime-Types that support compressing Response content. */ private final IncludeExcludeSet decompressMimeTypes; /** * Set of paths that support compressing Response content. */ private final IncludeExcludeSet compressPaths; /** * Set of paths that support decompressing Request content. */ private final IncludeExcludeSet decompressPaths; /** * Optional preferred order of encoders for compressing Response content. */ private final List preferredCompressEncodings; private CompressionConfig(Builder builder) { this.preferredCompressEncodings = Collections.unmodifiableList(builder.compressPreferredEncodings); this.compressEncodings = builder.compressEncodings.asImmutable(); this.decompressEncodings = builder.decompressEncodings.asImmutable(); this.compressMethods = builder.compressMethods.asImmutable(); this.decompressMethods = builder.decompressMethods.asImmutable(); this.compressMimeTypes = builder.compressMimeTypes.asImmutable(); this.decompressMimeTypes = builder.decompressMimeTypes.asImmutable(); this.compressPaths = builder.compressPaths.asImmutable(); this.decompressPaths = builder.decompressPaths.asImmutable(); } /** * @return a new {@link Builder} to configure a {@code CompressionConfig} instance */ public static Builder builder() { return new Builder(); } /** * @return the encodings that disable response compression * @see #getCompressIncludeEncodings() */ @ManagedAttribute("Encodings that disable response compression") public Set getCompressExcludeEncodings() { return compressEncodings.getExcluded(); } /** * @return the HTTP methods that disable response compression * @see #getCompressIncludeMethods() */ @ManagedAttribute("HTTP methods that disable response compression") public Set getCompressExcludeMethods() { return compressMethods.getExcluded(); } /** * @return the MIME types that disable response compression * @see #getCompressIncludeMimeTypes() */ @ManagedAttribute("MIME types that disable response compression") public Set getCompressExcludeMimeTypes() { return compressMimeTypes.getExcluded(); } /** * @return the path specs that exclude response compression * @see #getCompressIncludePaths() */ @ManagedAttribute("Path specs that exclude response compression") public Set getCompressExcludePaths() { return compressPaths.getExcluded(); } /** * @return the encodings that enable response compression * @see #getCompressExcludeEncodings() */ @ManagedAttribute("Encodings that enable response compression") public Set getCompressIncludeEncodings() { return compressEncodings.getIncluded(); } /** * @return HTTP methods that enable response compression * @see #getCompressExcludeMethods() */ @ManagedAttribute("HTTP methods that enable response compression") public Set getCompressIncludeMethods() { return compressMethods.getIncluded(); } /** * @return the MIME types that enable response compression * @see #getCompressExcludeMimeTypes() */ @ManagedAttribute("MIME types that enable response compression") public Set getCompressIncludeMimeTypes() { return compressMimeTypes.getIncluded(); } /** * @return the path specs that enable response compression * @see #getCompressExcludePaths() */ @ManagedAttribute("Path specs that enable response compression") public Set getCompressIncludePaths() { return compressPaths.getIncluded(); } /** * @return the encodings for response compression in preferred order */ @ManagedAttribute("Encodings for response compression in preferred order") public List getCompressPreferredEncodings() { return preferredCompressEncodings; } String getCompressionEncoding(Set supportedEncodings, Request request, List requestAcceptEncoding, String pathInContext) { if (requestAcceptEncoding.isEmpty()) return null; if (!isCompressMethodSupported(request.getMethod())) return null; // MIME types checks are performed later in CompressionResponse. if (!compressPaths.test(pathInContext)) return null; List matches = new ArrayList<>(); QuotedQualityCSV.QualityValue star = null; QuotedQualityCSV.QualityValue identity = null; for (QuotedQualityCSV.QualityValue qualityValue : requestAcceptEncoding) { String value = qualityValue.getValue(); if ("*".equals(value)) { star = qualityValue; continue; } if ("identity".equalsIgnoreCase(value)) { identity = qualityValue; continue; } if (!qualityValue.isAcceptable()) continue; if (!supportedEncodings.contains(value)) continue; if (compressEncodings.test(value)) matches.add(value); } List preferred = getCompressPreferredEncodings(); if (matches.isEmpty()) { // Try a default encoding if possible. if (star != null && star.isAcceptable()) { String candidate; if (preferred.isEmpty()) candidate = supportedEncodings.stream().findFirst().orElse(null); else candidate = preferred.stream().filter(supportedEncodings::contains).findFirst().orElse(null); if (!compressEncodings.test(candidate)) candidate = null; if (candidate != null) return candidate; } // The only option left is identity, if acceptable. if (identity != null && !identity.isAcceptable()) throw new HttpException.RuntimeException(HttpStatus.UNSUPPORTED_MEDIA_TYPE_415); // Identity is acceptable. return null; } // Only one match. if (matches.size() == 1) return matches.get(0); // Multiple matches, return most preferred, if any. return preferred.stream() .filter(matches::contains) .findFirst() .orElse(matches.get(0)); } /** * @return the HTTP methods that disable request decompression * @see #getDecompressIncludeMethods() */ @ManagedAttribute("HTTP methods that disable request decompression") public Set getDecompressExcludeMethods() { return decompressMethods.getExcluded(); } /** * @return the path specs that disable request decompression * @see #getDecompressIncludePaths() */ @ManagedAttribute("Path specs that disable request decompression") public Set getDecompressExcludePaths() { return decompressPaths.getExcluded(); } /** * @return the HTTP methods that enable request decompression * @see #getDecompressExcludeMethods() */ @ManagedAttribute("HTTP methods that enable request decompression") public Set getDecompressIncludeMethods() { return decompressMethods.getIncluded(); } /** * @return the path specs that enable request decompression * @see #getDecompressExcludePaths() */ @ManagedAttribute("Path specs that enable request decompression") public Set getDecompressIncludePaths() { return decompressPaths.getIncluded(); } String getDecompressionEncoding(Set supportedEncodings, Request request, String requestContentEncoding, String pathInContext) { if (requestContentEncoding == null) return null; if (!supportedEncodings.contains(requestContentEncoding)) return null; if (!decompressEncodings.test(requestContentEncoding)) return null; if (!isDecompressMethodSupported(request.getMethod())) return null; String contentType = request.getHeaders().get(HttpHeader.CONTENT_TYPE); if (!isDecompressMimeTypeSupported(contentType)) return null; if (!decompressPaths.test(pathInContext)) return null; return requestContentEncoding; } public boolean isCompressMethodSupported(String method) { return compressMethods.test(method); } public boolean isCompressMimeTypeSupported(String mimeType) { return compressMimeTypes.test(mimeType); } public boolean isDecompressMethodSupported(String method) { return decompressMethods.test(method); } public boolean isDecompressMimeTypeSupported(String mimeType) { return decompressMimeTypes.test(mimeType); } /** *

The builder of {@link CompressionConfig} immutable instances.

*

Notes about PathSpec strings

*

There are 2 syntaxes supported, Servlet {@code url-pattern} based, * and regex based.

*
    *
  • If the spec starts with {@code '^'} the spec is assumed to be * a regex based path spec and will match with normal Java regex rules.
  • *
  • If the spec starts with {@code '/'} then spec is assumed to be * a Servlet url-pattern rules path spec for either an exact match * or prefix based match.
  • *
  • If the spec starts with {@code '*.'} then spec is assumed to be * a Servlet url-pattern rules path spec for a suffix based match.
  • *
  • All other syntaxes are unsupported
  • *
*

For all properties, exclusion takes precedence over inclusion, * as defined by {@link IncludeExcludeSet}.

*/ public static class Builder { private final IncludeExclude decompressEncodings = new IncludeExclude<>(); private final IncludeExclude compressEncodings = new IncludeExclude<>(); private final IncludeExclude decompressMethods = new IncludeExclude<>(); private final IncludeExclude compressMethods = new IncludeExclude<>(); private final IncludeExclude decompressPaths = new IncludeExclude<>(PathSpecSet.class); private final IncludeExclude compressPaths = new IncludeExclude<>(PathSpecSet.class); private final IncludeExclude compressMimeTypes = new IncludeExclude<>(AsciiLowerCaseSet.class); private final IncludeExclude decompressMimeTypes = new IncludeExclude<>(AsciiLowerCaseSet.class); private final List compressPreferredEncodings = new ArrayList<>(); private Builder() { // Use the static builder() method instead. } /** * @param encoding the encoding to exclude for response compression. * @return this builder */ public Builder compressExcludeEncoding(String encoding) { this.compressEncodings.exclude(encoding); return this; } /** * @param method the HTTP method to exclude for response compression * @return this builder */ public Builder compressExcludeMethod(String method) { this.compressMethods.exclude(method); return this; } /** * @param mimetype the MIME type to exclude for response compression * @return this builder */ public Builder compressExcludeMimeType(String mimetype) { this.compressMimeTypes.exclude(mimetype); return this; } /** *

A path spec to exclude for response compression.

*

The path spec is matched against {@link Request#getPathInContext(Request)}.

* * @param pathSpecString the path spec to exclude for response compression. * @return this builder * @see #compressIncludePath(String) */ public Builder compressExcludePath(String pathSpecString) { this.compressPaths.exclude(pathSpecString); return this; } /** * @param encoding the encoding to include for response compression. * @return this builder */ public Builder compressIncludeEncoding(String encoding) { this.compressEncodings.include(encoding); return this; } /** * @param method the HTTP method to include for response compression * @return this builder */ public Builder compressIncludeMethod(String method) { this.compressMethods.include(method); return this; } /** * @param mimetype the MIME type to include for response compression * @return this builder */ public Builder compressIncludeMimeType(String mimetype) { this.compressMimeTypes.include(mimetype); return this; } /** *

A path spec to include for response compression.

*

The path spec is matched against {@link Request#getPathInContext(Request)}.

* * @param pathSpecString the path spec to include for response compression. * @return this builder * @see #compressExcludePath(String) */ public Builder compressIncludePath(String pathSpecString) { this.compressPaths.include(pathSpecString); return this; } /** *

Specifies a list of encodings for response compression in preferred order.

*

This list is only used when {@link CompressionHandler} computes more * than one candidate content encoding for response compression. * This happens only when {@code Accept-Encoding} specifies more than * one encoding, and they are all supported by the server, or when * {@code Accept-Encoding} specifies the token {@code *} and the server * supports more than one encoding.

* * @param encodings a list of encodings for response compression in preferred order * @return this builder */ public Builder compressPreferredEncodings(List encodings) { this.compressPreferredEncodings.clear(); if (encodings != null) this.compressPreferredEncodings.addAll(encodings); return this; } /** * @param encoding the encoding to exclude for request decompression * @return this builder */ public Builder decompressExcludeEncoding(String encoding) { this.decompressEncodings.exclude(encoding); return this; } /** * @param method the HTTP method to exclude for request decompression * @return this builder */ public Builder decompressExcludeMethod(String method) { this.decompressMethods.exclude(method); return this; } /** * @param mimetype the MIME type to exclude for request decompression * @return this builder */ public Builder decompressExcludeMimeType(String mimetype) { this.decompressMimeTypes.exclude(mimetype); return this; } /** *

A path spec to exclude for request decompression.

*

The path spec is matched against {@link Request#getPathInContext(Request)}.

* * @param pathSpecString the path spec to exclude for request decompression * @return this builder * @see #decompressIncludePath(String) */ public Builder decompressExcludePath(String pathSpecString) { this.decompressPaths.exclude(pathSpecString); return this; } /** * @param encoding the encoding to include for request decompression * @return this builder */ public Builder decompressIncludeEncoding(String encoding) { this.decompressEncodings.include(encoding); return this; } /** * @param method the HTTP method to include for request decompression * @return this builder */ public Builder decompressIncludeMethod(String method) { this.decompressMethods.include(method); return this; } /** * @param mimetype the MIME type to include for request decompression * @return this builder */ public Builder decompressIncludeMimeType(String mimetype) { this.decompressMimeTypes.include(mimetype); return this; } /** *

A path spec to include for request decompression.

*

The path spec is matched against {@link Request#getPathInContext(Request)}.

* * @param pathSpecString the path spec to include for request decompression * @return this builder * @see #decompressExcludePath(String) */ public Builder decompressIncludePath(String pathSpecString) { this.decompressPaths.include(pathSpecString); return this; } /** *

Configures this {@code Builder} with the default configuration.

*

Additional configuration may be specified using the {@code Builder} * methods, possibly overriding the defaults.

* * @return this builder */ public Builder defaults() { for (String type : MimeTypes.DEFAULTS.getMimeMap().values()) { if ("image/svg+xml".equals(type)) { compressExcludeMimeType(type); decompressExcludeMimeType(type); compressExcludePath("*.svgz"); decompressExcludePath("*.svgz"); } else if (type.startsWith("image/") || type.startsWith("audio/") || type.startsWith("video/")) { compressExcludeMimeType(type); decompressExcludeMimeType(type); } } Stream.of("application/compress", "application/zip", "application/gzip", "application/x-bzip2", "application/brotli", "application/x-br", "application/x-xz", "application/x-rar-compressed", "application/vnd.bzip3", "application/zstd", // It is possible to use SSE with CompressionHandler, but only if you use // `gzip` encoding with syncFlush to true which will impact performance. "text/event-stream" ).forEach((type) -> { compressExcludeMimeType(type); decompressExcludeMimeType(type); }); decompressIncludeMethod(HttpMethod.POST.asString()); compressIncludeMethod(HttpMethod.GET.asString()); compressIncludeMethod(HttpMethod.POST.asString()); return this; } /** * @return a new {@link CompressionConfig} instance configured with this {@code Builder}. */ public CompressionConfig build() { return new CompressionConfig(this); } // TODO: compression specific config (eg: compression level, strategy, etc) // TODO: dictionary support // TODO: Add configuration for decompression body size limit (to help with decompression bombs) // See: apache httpd mod_deflate DeflateInflateLimitRequestBody config // TODO: Add configuration for decompression ration burst / limit (to help with decompression bombs) // See: apache httpd mod_deflate DeflateInflateRatioBurst and DeflateInflateRatioLimit configs } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy