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

org.glassfish.jersey.server.filter.EncodingFilter Maven / Gradle / Ivy

Go to download

A bundle project producing JAX-RS RI bundles. The primary artifact is an "all-in-one" OSGi-fied JAX-RS RI bundle (jaxrs-ri.jar). Attached to that are two compressed JAX-RS RI archives. The first archive (jaxrs-ri.zip) consists of binary RI bits and contains the API jar (under "api" directory), RI libraries (under "lib" directory) as well as all external RI dependencies (under "ext" directory). The secondary archive (jaxrs-ri-src.zip) contains buildable JAX-RS RI source bundle and contains the API jar (under "api" directory), RI sources (under "src" directory) as well as all external RI dependencies (under "ext" directory). The second archive also contains "build.xml" ANT script that builds the RI sources. To build the JAX-RS RI simply unzip the archive, cd to the created jaxrs-ri directory and invoke "ant" from the command line.

There is a newer version: 3.1.6
Show newest version
/*
 * Copyright (c) 2012, 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.jersey.server.filter;

import java.io.IOException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.logging.Level;
import java.util.logging.Logger;

import jakarta.ws.rs.NotAcceptableException;
import jakarta.ws.rs.Priorities;
import jakarta.ws.rs.container.ContainerRequestContext;
import jakarta.ws.rs.container.ContainerResponseContext;
import jakarta.ws.rs.container.ContainerResponseFilter;
import jakarta.ws.rs.core.HttpHeaders;

import jakarta.annotation.Priority;
import jakarta.inject.Inject;

import org.glassfish.jersey.internal.inject.InjectionManager;
import org.glassfish.jersey.message.internal.HttpHeaderReader;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.spi.ContentEncoder;

/**
 * Container filter that supports encoding-based content negotiation. The filter examines what
 * content encodings are supported by the container (by looking up all the
 * {@link org.glassfish.jersey.spi.ContentEncoder encoders}) and decides what encoding should be chosen
 * based on the encodings listed in the Accept-Encoding request header and their associated quality values.
 * If none of the acceptable encodings is supported and identity encoding is explicitly forbidden by the client,
 * the filter generates {@link jakarta.ws.rs.core.Response.Status#NOT_ACCEPTABLE} response.
 * 

* The filter also ensures Accept-Encoding is added to the Vary header, for proper interaction with web caches. *

* * @author Martin Matula */ @Priority(Priorities.HEADER_DECORATOR) public final class EncodingFilter implements ContainerResponseFilter { // name for the identity encoding private static final String IDENTITY_ENCODING = "identity"; @Inject private InjectionManager injectionManager; // sorted set to keep the order same for different invocations of the app private volatile SortedSet supportedEncodings = null; /** * Enables this filter along with the provided {@link org.glassfish.jersey.spi.ContentEncoder encoders} * for the supplied {@link ResourceConfig}. * * @param rc Resource config this filter should be enabled for. * @param encoders content encoders. */ @SafeVarargs public static void enableFor(ResourceConfig rc, Class... encoders) { rc.registerClasses(encoders).registerClasses(EncodingFilter.class); } @Override public void filter(ContainerRequestContext request, ContainerResponseContext response) throws IOException { if (!response.hasEntity()) { return; } // add Accept-Encoding to Vary header List varyHeader = response.getStringHeaders().get(HttpHeaders.VARY); if (varyHeader == null || !varyHeader.contains(HttpHeaders.ACCEPT_ENCODING)) { response.getHeaders().add(HttpHeaders.VARY, HttpHeaders.ACCEPT_ENCODING); } // if Content-Encoding is already set, don't do anything if (response.getHeaders().getFirst(HttpHeaders.CONTENT_ENCODING) != null) { return; } // retrieve the list of accepted encodings List acceptEncoding = request.getHeaders().get(HttpHeaders.ACCEPT_ENCODING); // if empty, don't do anything if (acceptEncoding == null || acceptEncoding.isEmpty()) { return; } // convert encodings from String to Encoding objects List encodings = new ArrayList<>(); for (String input : acceptEncoding) { if (input.isEmpty()) { continue; } String[] tokens = input.split(","); for (String token : tokens) { try { ContentEncoding encoding = ContentEncoding.fromString(token); encodings.add(encoding); } catch (ParseException e) { // ignore the encoding that could not parse // but log the exception Logger.getLogger(EncodingFilter.class.getName()).log(Level.WARNING, e.getLocalizedMessage(), e); } } } // sort based on quality parameter Collections.sort(encodings); // make sure IDENTITY_ENCODING is at the end (since it accepted if not explicitly forbidden // in the Accept-Content header by assigning q=0 encodings.add(new ContentEncoding(IDENTITY_ENCODING, -1)); // get a copy of supported encoding (we'll be modifying this set, hence the copy) SortedSet acceptedEncodings = new TreeSet<>(getSupportedEncodings()); // indicates that we can pick any of the encodings that remained in the acceptedEncodings set boolean anyRemaining = false; // final resulting value of the Content-Encoding header to be set String contentEncoding = null; // iterate through the accepted encodings, starting with the highest quality one for (ContentEncoding encoding : encodings) { if (encoding.q == 0) { // ok, we are down at 0 quality if ("*".equals(encoding.name)) { // no other encoding is acceptable break; } // all the 0 quality encodings need to be removed from the accepted ones (these are explicitly // forbidden by the client) acceptedEncodings.remove(encoding.name); } else { if ("*".equals(encoding.name)) { // any remaining encoding (after filtering out q=0) will be acceptable anyRemaining = true; } else { if (acceptedEncodings.contains(encoding.name)) { // found an acceptable one -> we are done contentEncoding = encoding.name; break; } } } } if (contentEncoding == null) { // haven't found any explicit acceptable encoding, let's see if we can just pick any of the remaining ones // (if there are any left) if (anyRemaining && !acceptedEncodings.isEmpty()) { contentEncoding = acceptedEncodings.first(); } else { // no acceptable encoding can be sent -> return NOT ACCEPTABLE status code back to the client throw new NotAcceptableException(); } } // finally set the header - but no need to set for identity encoding if (!IDENTITY_ENCODING.equals(contentEncoding)) { response.getHeaders().putSingle(HttpHeaders.CONTENT_ENCODING, contentEncoding); } } // representation of a single Content-Encoding header value private static class ContentEncoding implements Comparable { public final String name; public final int q; public ContentEncoding(String encoding, int q) { this.name = encoding; this.q = q; } public static ContentEncoding fromString(String input) throws ParseException { HttpHeaderReader reader = HttpHeaderReader.newInstance(input); // Skip any white space reader.hasNext(); return new ContentEncoding(reader.nextToken().toString(), HttpHeaderReader.readQualityFactorParameter(reader)); } @Override public int hashCode() { return 41 * name.hashCode() + q; } @Override public boolean equals(Object obj) { return obj == this || obj != null && (obj instanceof ContentEncoding) && name.equals(((ContentEncoding) obj).name) && q == ((ContentEncoding) obj).q; } @Override public int compareTo(ContentEncoding o) { // higher q goes first (i.e. descending order) return Integer.compare(o.q, q); } } /** * Returns a (lexically) sorted set of supported encodings. * @return sorted set of supported encodings. */ SortedSet getSupportedEncodings() { // no need for synchronization - in case of a race condition, the property // may be set twice, but it does not break anything if (supportedEncodings == null) { SortedSet se = new TreeSet<>(); List encoders = injectionManager.getAllInstances(ContentEncoder.class); for (ContentEncoder encoder : encoders) { se.addAll(encoder.getSupportedEncodings()); } se.add(IDENTITY_ENCODING); supportedEncodings = se; } return supportedEncodings; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy