You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cxf.apache.org by dk...@apache.org on 2008/07/18 22:55:05 UTC
svn commit: r678035 - in /cxf/trunk:
common/common/src/main/java/org/apache/cxf/helpers/
rt/transports/http/src/main/java/org/apache/cxf/transport/http/gzip/
rt/transports/http/src/test/java/org/apache/cxf/transport/http/gzip/
Author: dkulp
Date: Fri Jul 18 13:55:05 2008
New Revision: 678035
URL: http://svn.apache.org/viewvc?rev=678035&view=rev
Log:
[CXF-1387] Patch from Ian Roberts applied. Adds gzip support for http.
Added:
cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/gzip/
cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/gzip/GZIPFeature.java (with props)
cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/gzip/GZIPInInterceptor.java (with props)
cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/gzip/GZIPOutInterceptor.java (with props)
cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/gzip/Messages.properties (with props)
cxf/trunk/rt/transports/http/src/test/java/org/apache/cxf/transport/http/gzip/
cxf/trunk/rt/transports/http/src/test/java/org/apache/cxf/transport/http/gzip/GZIPAcceptEncodingTest.java (with props)
Modified:
cxf/trunk/common/common/src/main/java/org/apache/cxf/helpers/HttpHeaderHelper.java
Modified: cxf/trunk/common/common/src/main/java/org/apache/cxf/helpers/HttpHeaderHelper.java
URL: http://svn.apache.org/viewvc/cxf/trunk/common/common/src/main/java/org/apache/cxf/helpers/HttpHeaderHelper.java?rev=678035&r1=678034&r2=678035&view=diff
==============================================================================
--- cxf/trunk/common/common/src/main/java/org/apache/cxf/helpers/HttpHeaderHelper.java (original)
+++ cxf/trunk/common/common/src/main/java/org/apache/cxf/helpers/HttpHeaderHelper.java Fri Jul 18 13:55:05 2008
@@ -28,8 +28,10 @@
import java.util.concurrent.ConcurrentHashMap;
public final class HttpHeaderHelper {
+ public static final String ACCEPT_ENCODING = "Accept-Encoding";
public static final String CONTENT_TYPE = "Content-Type";
public static final String CONTENT_ID = "Content-ID";
+ public static final String CONTENT_ENCODING = "Content-Encoding";
public static final String CONTENT_TRANSFER_ENCODING = "Content-Transfer-Encoding";
public static final String COOKIE = "Cookie";
public static final String TRANSFER_ENCODING = "Transfer-Encoding";
@@ -44,6 +46,8 @@
private static Map<String, String> encodings = new ConcurrentHashMap<String, String>();
static {
+ internalHeaders.put("Accept-Encoding", "accept-encoding");
+ internalHeaders.put("Content-Encoding", "content-encoding");
internalHeaders.put("Content-Type", "content-type");
internalHeaders.put("Content-ID", "content-id");
internalHeaders.put("Content-Transfer-Encoding", "content-transfer-encoding");
Added: cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/gzip/GZIPFeature.java
URL: http://svn.apache.org/viewvc/cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/gzip/GZIPFeature.java?rev=678035&view=auto
==============================================================================
--- cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/gzip/GZIPFeature.java (added)
+++ cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/gzip/GZIPFeature.java Fri Jul 18 13:55:05 2008
@@ -0,0 +1,84 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.cxf.transport.http.gzip;
+
+import org.apache.cxf.Bus;
+import org.apache.cxf.feature.AbstractFeature;
+import org.apache.cxf.interceptor.InterceptorProvider;
+
+/**
+ * This class is used to control GZIP compression of messages.
+ * Attaching this feature to an endpoint will allow the endpoint to handle
+ * compressed requests, and will cause outgoing responses to be compressed if
+ * the client indicates (via the Accept-Encoding header) that it can handle
+ * them.
+ * <pre>
+ * <![CDATA[
+ * <jaxws:endpoint ...>
+ * <jaxws:features>
+ * <bean class="org.apache.cxf.transport.http.gzip.GZIPFeature"/>
+ * </jaxws:features>
+ * </jaxws:endpoint>
+ * ]]>
+ * </pre>
+ * Attaching this feature to a client will cause outgoing request messages to
+ * be compressed and incoming compressed responses to be uncompressed. If you
+ * want the service to know that your client can accept compressed responses,
+ * you will also need to set the "AcceptEncoding" value in the relevant HTTP
+ * conduit client configuration.
+ * <pre>
+ * <![CDATA[
+ * <http-conf:conduit name="{http://apache.org/hello_world_soap_http}SoapPort.http-conduit">
+ * <http-conf:client AcceptEncoding="gzip" />
+ * </http-conf:conduit>
+ * ]]>
+ * </pre>
+ */
+public class GZIPFeature extends AbstractFeature {
+ private static final GZIPInInterceptor IN = new GZIPInInterceptor();
+ private static final GZIPOutInterceptor OUT = new GZIPOutInterceptor();
+
+ /**
+ * The compression threshold to pass to the outgoing interceptor.
+ */
+ int threshold = -1;
+
+ @Override
+ protected void initializeProvider(InterceptorProvider provider, Bus bus) {
+ provider.getInInterceptors().add(IN);
+ provider.getInFaultInterceptors().add(IN);
+ if (threshold == -1) {
+ provider.getOutInterceptors().add(OUT);
+ provider.getOutFaultInterceptors().add(OUT);
+ } else {
+ GZIPOutInterceptor out = new GZIPOutInterceptor();
+ out.setThreshold(threshold);
+ provider.getOutInterceptors().add(out);
+ provider.getOutFaultInterceptors().add(out);
+ }
+ }
+
+ public void setThreshold(int threshold) {
+ this.threshold = threshold;
+ }
+
+ public int getThreshold() {
+ return threshold;
+ }
+}
Propchange: cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/gzip/GZIPFeature.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/gzip/GZIPFeature.java
------------------------------------------------------------------------------
svn:keywords = Rev Date
Propchange: cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/gzip/GZIPFeature.java
------------------------------------------------------------------------------
svn:mime-type = text/plain
Added: cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/gzip/GZIPInInterceptor.java
URL: http://svn.apache.org/viewvc/cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/gzip/GZIPInInterceptor.java?rev=678035&view=auto
==============================================================================
--- cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/gzip/GZIPInInterceptor.java (added)
+++ cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/gzip/GZIPInInterceptor.java Fri Jul 18 13:55:05 2008
@@ -0,0 +1,126 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.cxf.transport.http.gzip;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+import java.util.Map;
+import java.util.ResourceBundle;
+import java.util.logging.Logger;
+import java.util.zip.GZIPInputStream;
+
+import org.apache.cxf.common.i18n.BundleUtils;
+import org.apache.cxf.common.logging.LogUtils;
+import org.apache.cxf.helpers.CastUtils;
+import org.apache.cxf.helpers.HttpHeaderHelper;
+import org.apache.cxf.interceptor.AttachmentInInterceptor;
+import org.apache.cxf.interceptor.Fault;
+import org.apache.cxf.message.Message;
+import org.apache.cxf.phase.AbstractPhaseInterceptor;
+import org.apache.cxf.phase.Phase;
+
+/**
+ * CXF interceptor that uncompresses those incoming messages that have "gzip"
+ * content-encoding. An instance of this class should be added as an in and
+ * inFault interceptor on clients that need to talk to a service that returns
+ * gzipped responses or on services that want to accept gzipped requests. For
+ * clients, you probably also want to use
+ * {@link org.apache.cxf.transports.http.configuration.HTTPClientPolicy#setAcceptEncoding}
+ * to let the server know you can handle compressed responses. To compress
+ * outgoing messages, see {@link GZIPOutInterceptor}. This class was originally
+ * based on one of the CXF samples (configuration_interceptor).
+ *
+ * @author Ian Roberts (i.roberts@dcs.shef.ac.uk)
+ */
+public class GZIPInInterceptor extends AbstractPhaseInterceptor<Message> {
+
+ /**
+ * Key under which we store the original input stream on the message, for
+ * use by the ending interceptor.
+ */
+ public static final String ORIGINAL_INPUT_STREAM_KEY = GZIPInInterceptor.class.getName()
+ + ".originalInputStream";
+
+ private static final ResourceBundle BUNDLE = BundleUtils.getBundle(GZIPInInterceptor.class);
+ private static final Logger LOG = LogUtils.getL7dLogger(GZIPInInterceptor.class);
+
+ /**
+ * Ending interceptor that restores the original input stream on the message
+ * when we have finished unzipping it.
+ */
+ private GZIPInEndingInterceptor ending = new GZIPInEndingInterceptor();
+
+ public GZIPInInterceptor() {
+ super(Phase.RECEIVE);
+ addBefore(AttachmentInInterceptor.class.getName());
+ }
+
+ public void handleMessage(Message message) throws Fault {
+ // check for Content-Encoding header - we are only interested in
+ // messages that say they are gzipped.
+ Map<String, List<String>> protocolHeaders = CastUtils.cast((Map<?, ?>)message
+ .get(Message.PROTOCOL_HEADERS));
+ if (protocolHeaders != null) {
+ List<String> contentEncoding = CastUtils.cast(HttpHeaderHelper
+ .getHeader(protocolHeaders, HttpHeaderHelper.CONTENT_ENCODING));
+ if (contentEncoding != null
+ && (contentEncoding.contains("gzip") || contentEncoding.contains("x-gzip"))) {
+ try {
+ LOG.fine("Uncompressing response");
+ // remember the original input stream, the ending
+ // interceptor
+ // will use it later
+ InputStream is = message.getContent(InputStream.class);
+ message.put(ORIGINAL_INPUT_STREAM_KEY, is);
+
+ // wrap an unzipping stream around the original one
+ GZIPInputStream zipInput = new GZIPInputStream(is);
+ message.setContent(InputStream.class, zipInput);
+
+ // remove content encoding header as we've now dealt with it
+ protocolHeaders.remove("Content-Encoding");
+
+ // add the ending interceptor
+ message.getInterceptorChain().add(ending);
+ } catch (IOException ex) {
+ throw new Fault(new org.apache.cxf.common.i18n.Message("COULD_NOT_UNZIP", BUNDLE), ex);
+ }
+ }
+ }
+ }
+
+ /**
+ * Ending interceptor to restore the original input stream after processing,
+ * so as not to interfere with streaming HTTP.
+ */
+ public class GZIPInEndingInterceptor extends AbstractPhaseInterceptor<Message> {
+ public GZIPInEndingInterceptor() {
+ super(Phase.POST_INVOKE);
+ }
+
+ /**
+ * Restores the original input stream for the message.
+ */
+ public void handleMessage(Message message) throws Fault {
+ InputStream originalIn = (InputStream)message.get(ORIGINAL_INPUT_STREAM_KEY);
+ message.setContent(InputStream.class, originalIn);
+ }
+ }
+}
Propchange: cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/gzip/GZIPInInterceptor.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/gzip/GZIPInInterceptor.java
------------------------------------------------------------------------------
svn:keywords = Rev Date
Propchange: cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/gzip/GZIPInInterceptor.java
------------------------------------------------------------------------------
svn:mime-type = text/plain
Added: cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/gzip/GZIPOutInterceptor.java
URL: http://svn.apache.org/viewvc/cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/gzip/GZIPOutInterceptor.java?rev=678035&view=auto
==============================================================================
--- cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/gzip/GZIPOutInterceptor.java (added)
+++ cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/gzip/GZIPOutInterceptor.java Fri Jul 18 13:55:05 2008
@@ -0,0 +1,317 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.cxf.transport.http.gzip;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.ResourceBundle;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.zip.GZIPOutputStream;
+
+import org.apache.cxf.common.i18n.BundleUtils;
+import org.apache.cxf.common.logging.LogUtils;
+import org.apache.cxf.helpers.CastUtils;
+import org.apache.cxf.helpers.HttpHeaderHelper;
+import org.apache.cxf.interceptor.Fault;
+import org.apache.cxf.interceptor.MessageSenderInterceptor;
+import org.apache.cxf.io.CachedOutputStream;
+import org.apache.cxf.message.Exchange;
+import org.apache.cxf.message.Message;
+import org.apache.cxf.phase.AbstractPhaseInterceptor;
+import org.apache.cxf.phase.Phase;
+
+/**
+ * CXF interceptor that compresses outgoing messages using gzip and sets the
+ * HTTP Content-Encoding header appropriately. An instance of this class should
+ * be added as an out interceptor on clients that need to talk to a service that
+ * accepts gzip-encoded requests or on a service that wants to be able to return
+ * compressed responses. In server mode, the interceptor only compresses
+ * responses if the client indicated (via an Accept-Encoding header on the
+ * request) that it can understand them. To handle gzip-encoded input messages,
+ * see {@link GZIPInInterceptor}. This interceptor supports a compression
+ * {@link #threshold} (default 1kB) - messages smaller than this threshold will
+ * not be compressed. To force compression of all messages, set the threshold to
+ * 0. This class was originally based on one of the CXF samples
+ * (configuration_interceptor).
+ *
+ * @author Ian Roberts (i.roberts@dcs.shef.ac.uk)
+ */
+public class GZIPOutInterceptor extends AbstractPhaseInterceptor<Message> {
+
+ /**
+ * Enum giving the possible values for whether we should gzip a particular
+ * message.
+ */
+ public static enum UseGzip {
+ NO, YES, FORCE
+ }
+
+ /**
+ * Key under which we store the original output stream on the message, for
+ * use by the ending interceptor.
+ */
+ public static final String ORIGINAL_OUTPUT_STREAM_KEY = GZIPOutInterceptor.class.getName()
+ + ".originalOutputStream";
+
+ /**
+ * Key under which we store an indication of whether compression is
+ * permitted or required, for use by the ending interceptor.
+ */
+ public static final String USE_GZIP_KEY = GZIPOutInterceptor.class.getName() + ".useGzip";
+
+ /**
+ * Key under which we store the name which should be used for the
+ * content-encoding of the outgoing message. Typically "gzip" but may be
+ * "x-gzip" if we are processing a response message and this is the name
+ * given by the client in Accept-Encoding.
+ */
+ public static final String GZIP_ENCODING_KEY = GZIPOutInterceptor.class.getName() + ".gzipEncoding";
+
+ private static final ResourceBundle BUNDLE = BundleUtils.getBundle(GZIPOutInterceptor.class);
+ private static final Logger LOG = LogUtils.getL7dLogger(GZIPOutInterceptor.class);
+
+ /**
+ * Ending interceptor that handles the compression process.
+ */
+ private GZIPOutEndingInterceptor ending = new GZIPOutEndingInterceptor();
+
+ /**
+ * Compression threshold in bytes - messages smaller than this will not be
+ * compressed.
+ */
+ private int threshold = 1024;
+
+ public GZIPOutInterceptor() {
+ super(Phase.PREPARE_SEND);
+ addAfter(MessageSenderInterceptor.class.getName());
+ }
+
+ public void setThreshold(int threshold) {
+ this.threshold = threshold;
+ }
+
+ public int getThreshold() {
+ return threshold;
+ }
+
+ public void handleMessage(Message message) throws Fault {
+ UseGzip use = gzipPermitted(message);
+ if (use != UseGzip.NO) {
+ // remember the original output stream, we will write compressed
+ // data to this later
+ OutputStream os = message.getContent(OutputStream.class);
+ message.put(ORIGINAL_OUTPUT_STREAM_KEY, os);
+
+ message.put(USE_GZIP_KEY, use);
+
+ // new stream to cache the message
+ CachedOutputStream cs = new CachedOutputStream();
+ message.setContent(OutputStream.class, cs);
+
+ // add the ending interceptor that does the work
+ message.getInterceptorChain().add(ending);
+ }
+ }
+
+ /**
+ * Checks whether we can, cannot or must use gzip compression on this output
+ * message. Gzip is always permitted if the message is a client request. If
+ * the message is a server response we check the Accept-Encoding header of
+ * the corresponding request message - with no Accept-Encoding we assume
+ * that gzip is not permitted. For the full gory details, see <a
+ * href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.3">section
+ * 14.3 of RFC 2616</a> (HTTP 1.1).
+ *
+ * @param message the outgoing message.
+ * @return whether to attempt gzip compression for this message.
+ * @throws Fault if the Accept-Encoding header does not allow any encoding
+ * that we can support (identity, gzip or x-gzip).
+ */
+ private UseGzip gzipPermitted(Message message) throws Fault {
+ UseGzip permitted = UseGzip.NO;
+ if (Boolean.TRUE.equals(message.get(Message.REQUESTOR_ROLE))) {
+ LOG.fine("Requestor role, so gzip enabled");
+ permitted = UseGzip.YES;
+ message.put(GZIP_ENCODING_KEY, "gzip");
+ } else {
+ LOG.fine("Response role, checking accept-encoding");
+ Exchange exchange = message.getExchange();
+ Message request = exchange.getInMessage();
+ Map<String, List<String>> requestHeaders = CastUtils.cast((Map<?, ?>)request
+ .get(Message.PROTOCOL_HEADERS));
+ if (requestHeaders != null) {
+ List<String> acceptEncodingHeader = CastUtils.cast(HttpHeaderHelper
+ .getHeader(requestHeaders, HttpHeaderHelper.ACCEPT_ENCODING));
+ if (acceptEncodingHeader != null) {
+ if (LOG.isLoggable(Level.FINE)) {
+ LOG.fine("Accept-Encoding header: " + acceptEncodingHeader);
+ }
+ // Accept-Encoding is a comma separated list of entries, so
+ // we split it into its component parts and build two
+ // lists, one with all the "q=0" encodings and the other
+ // with the rest (no q, or q=<non-zero>).
+ List<String> zeros = new ArrayList<String>(3);
+ List<String> nonZeros = new ArrayList<String>(3);
+
+ // regular expression that matches any encoding with a
+ // q-value of 0 (or 0.0, 0.00, etc.).
+ Pattern zeroQ = Pattern.compile(";\\s*q=0(?:\\.0+)?$");
+ for (String headerLine : acceptEncodingHeader) {
+ String[] encodings = headerLine.trim().split("[,\\s]*,\\s*");
+
+ for (String enc : encodings) {
+ Matcher m = zeroQ.matcher(enc);
+ if (m.find()) {
+ zeros.add(enc.substring(0, m.start()));
+ } else if (enc.indexOf(';') >= 0) {
+ nonZeros.add(enc.substring(0, enc.indexOf(';')));
+ } else {
+ nonZeros.add(enc);
+ }
+ }
+ }
+
+ // identity encoding is permitted if (a) it is not
+ // specifically disabled by an identity;q=0 and (b) if
+ // there is a *;q=0 then there is also an explicit
+ // identity[;q=<non-zero>]
+ //
+ // [x-]gzip is permitted if (a) there is an explicit
+ // [x-]gzip[;q=<non-zero>], or (b) there is a
+ // *[;q=<non-zero>] and no [x-]gzip;q=0 to disable it.
+ boolean identityEnabled = !zeros.contains("identity")
+ && (!zeros.contains("*") || nonZeros.contains("identity"));
+ boolean gzipEnabled = nonZeros.contains("gzip")
+ || (nonZeros.contains("*") && !zeros.contains("gzip"));
+ boolean xGzipEnabled = nonZeros.contains("x-gzip")
+ || (nonZeros.contains("*") && !zeros.contains("x-gzip"));
+
+ if (identityEnabled && !gzipEnabled && !xGzipEnabled) {
+ permitted = UseGzip.NO;
+ } else if (identityEnabled && gzipEnabled) {
+ permitted = UseGzip.YES;
+ message.put(GZIP_ENCODING_KEY, "gzip");
+ } else if (identityEnabled && xGzipEnabled) {
+ permitted = UseGzip.YES;
+ message.put(GZIP_ENCODING_KEY, "x-gzip");
+ } else if (!identityEnabled && gzipEnabled) {
+ permitted = UseGzip.FORCE;
+ message.put(GZIP_ENCODING_KEY, "gzip");
+ } else if (!identityEnabled && xGzipEnabled) {
+ permitted = UseGzip.FORCE;
+ message.put(GZIP_ENCODING_KEY, "x-gzip");
+ } else {
+ throw new Fault(new org.apache.cxf.common.i18n.Message("NO_SUPPORTED_ENCODING",
+ BUNDLE));
+ }
+ } else {
+ LOG.fine("No accept-encoding header");
+ }
+ }
+ }
+
+ if (LOG.isLoggable(Level.FINE)) {
+ LOG.fine("gzip permitted: " + permitted);
+ }
+ return permitted;
+ }
+
+ /**
+ * Ending interceptor to actually do the compression.
+ */
+ public class GZIPOutEndingInterceptor extends AbstractPhaseInterceptor<Message> {
+
+ public GZIPOutEndingInterceptor() {
+ super(Phase.PREPARE_SEND_ENDING);
+ addBefore(MessageSenderInterceptor.MessageSenderEndingInterceptor.class.getName());
+ }
+
+ /**
+ * Copies the message content to the real output stream, compressing it
+ * if we have to or if the message is larger than the threshold.
+ */
+ public void handleMessage(Message message) throws Fault {
+ try {
+ CachedOutputStream cs = (CachedOutputStream)message.getContent(OutputStream.class);
+ cs.flush();
+ OutputStream originalOutput = (OutputStream)message.get(ORIGINAL_OUTPUT_STREAM_KEY);
+ if (UseGzip.FORCE == message.get(USE_GZIP_KEY) || cs.size() > threshold) {
+ LOG.fine("Compressing message.");
+ // Set the Content-Encoding HTTP header
+ addHeader(message, "Content-Encoding", (String)message.get(GZIP_ENCODING_KEY));
+ // if this is a response message, add the Vary header
+ if (!Boolean.TRUE.equals(message.get(Message.REQUESTOR_ROLE))) {
+ addHeader(message, "Vary", "Accept-Encoding");
+ }
+
+ // gzip the result
+ GZIPOutputStream zipOutput = new GZIPOutputStream(originalOutput);
+ cs.writeCacheTo(zipOutput);
+ zipOutput.finish();
+ } else {
+ LOG.fine("Message is smaller than compression threshold, not compressing.");
+ cs.writeCacheTo(originalOutput);
+ }
+
+ cs.close();
+ originalOutput.flush();
+
+ message.setContent(OutputStream.class, originalOutput);
+ } catch (IOException ex) {
+ throw new Fault(new org.apache.cxf.common.i18n.Message("COULD_NOT_ZIP", BUNDLE), ex);
+ }
+ }
+
+ /**
+ * Adds a value to a header. If the given header name is not currently
+ * set in the message, an entry is created with the given single value.
+ * If the header is already set, the value is appended to the first
+ * element of the list, following a comma.
+ *
+ * @param message the message
+ * @param name the header to set
+ * @param value the value to add
+ */
+ private void addHeader(Message message, String name, String value) {
+ Map<String, List<String>> protocolHeaders = CastUtils.cast((Map<?, ?>)message
+ .get(Message.PROTOCOL_HEADERS));
+ if (protocolHeaders == null) {
+ protocolHeaders = new HashMap<String, List<String>>();
+ message.put(Message.PROTOCOL_HEADERS, protocolHeaders);
+ }
+ List<String> header = CastUtils.cast((List<?>)protocolHeaders.get(name));
+ if (header == null) {
+ header = new ArrayList<String>();
+ protocolHeaders.put(name, header);
+ }
+ if (header.size() == 0) {
+ header.add(value);
+ } else {
+ header.set(0, header.get(0) + "," + value);
+ }
+ }
+ }
+}
Propchange: cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/gzip/GZIPOutInterceptor.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/gzip/GZIPOutInterceptor.java
------------------------------------------------------------------------------
svn:keywords = Rev Date
Propchange: cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/gzip/GZIPOutInterceptor.java
------------------------------------------------------------------------------
svn:mime-type = text/plain
Added: cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/gzip/Messages.properties
URL: http://svn.apache.org/viewvc/cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/gzip/Messages.properties?rev=678035&view=auto
==============================================================================
--- cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/gzip/Messages.properties (added)
+++ cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/gzip/Messages.properties Fri Jul 18 13:55:05 2008
@@ -0,0 +1,23 @@
+#
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+#
+#
+COULD_NOT_UNZIP=Could not unzip compressed message.
+COULD_NOT_ZIP=Could not compress message.
+NO_SUPPORTED_ENCODING=No supported content encodings available.
Propchange: cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/gzip/Messages.properties
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/gzip/Messages.properties
------------------------------------------------------------------------------
svn:keywords = Rev Date
Propchange: cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/gzip/Messages.properties
------------------------------------------------------------------------------
svn:mime-type = text/plain
Added: cxf/trunk/rt/transports/http/src/test/java/org/apache/cxf/transport/http/gzip/GZIPAcceptEncodingTest.java
URL: http://svn.apache.org/viewvc/cxf/trunk/rt/transports/http/src/test/java/org/apache/cxf/transport/http/gzip/GZIPAcceptEncodingTest.java?rev=678035&view=auto
==============================================================================
--- cxf/trunk/rt/transports/http/src/test/java/org/apache/cxf/transport/http/gzip/GZIPAcceptEncodingTest.java (added)
+++ cxf/trunk/rt/transports/http/src/test/java/org/apache/cxf/transport/http/gzip/GZIPAcceptEncodingTest.java Fri Jul 18 13:55:05 2008
@@ -0,0 +1,132 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.cxf.transport.http.gzip;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.cxf.helpers.HttpHeaderHelper;
+import org.apache.cxf.interceptor.Fault;
+import org.apache.cxf.interceptor.InterceptorChain;
+import org.apache.cxf.message.Exchange;
+import org.apache.cxf.message.ExchangeImpl;
+import org.apache.cxf.message.Message;
+import org.apache.cxf.message.MessageImpl;
+import org.easymock.EasyMock;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.apache.cxf.transport.http.gzip.GZIPOutInterceptor.UseGzip.*;
+
+/**
+ * Test for the parsing of Accept-Encoding by the GZIPOutInterceptor. For
+ * Accept-Encoding values that enable gzip we expect an extra interceptor to be
+ * added to the out message, and the {@link GZIPOutInterceptor#USE_GZIP_KEY} to
+ * be set correctly. For Accept-Encoding values that do not enable gzip the
+ * interceptor should not be added.
+ */
+public class GZIPAcceptEncodingTest extends Assert {
+
+ private GZIPOutInterceptor interceptor;
+ private Message inMessage;
+ private Message outMessage;
+ private InterceptorChain outInterceptors;
+
+ @Before
+ public void setUp() throws Exception {
+ interceptor = new GZIPOutInterceptor();
+ inMessage = new MessageImpl();
+ outMessage = new MessageImpl();
+ Exchange exchange = new ExchangeImpl();
+ exchange.setInMessage(inMessage);
+ inMessage.setExchange(exchange);
+ exchange.setOutMessage(outMessage);
+ outMessage.setExchange(exchange);
+ outInterceptors = EasyMock.createMock(InterceptorChain.class);
+ outMessage.setInterceptorChain(outInterceptors);
+ }
+
+ @Test
+ public void testNoAcceptEncoding() throws Exception {
+ EasyMock.replay(outInterceptors);
+ interceptor.handleMessage(outMessage);
+ }
+
+ @Test
+ public void testAcceptGzip() throws Exception {
+ singleTest("gzip", true, YES, "gzip");
+ }
+
+ @Test
+ public void testAcceptXGzip() throws Exception {
+ singleTest("x-gzip, x-compress", true, YES, "x-gzip");
+ }
+
+ @Test
+ public void testAcceptStar() throws Exception {
+ singleTest("*", true, YES, "gzip");
+ }
+
+ @Test
+ public void testAcceptOnlyGzip() throws Exception {
+ singleTest("gzip, identity; q=0", true, FORCE, "gzip");
+ }
+
+ @Test
+ public void testOnlyIdentitySupported() throws Exception {
+ singleTest("deflate", false, null, null);
+ }
+
+ @Test
+ public void testGzipExplicitlyDisabled() throws Exception {
+ singleTest("gzip; q=0.00", false, null, null);
+ }
+
+ @Test(expected = Fault.class)
+ public void testNoValidEncodings() throws Exception {
+ EasyMock.replay();
+ setAcceptEncoding("*;q=0, deflate;q=0.5");
+ interceptor.handleMessage(outMessage);
+ }
+
+ private void singleTest(String encoding, boolean expectEndingInterceptor,
+ GZIPOutInterceptor.UseGzip expectedUseGzip, String expectedGzipEncoding)
+ throws Exception {
+ if (expectEndingInterceptor) {
+ outInterceptors.add(EasyMock.isA(GZIPOutInterceptor.GZIPOutEndingInterceptor.class));
+ }
+ EasyMock.replay(outInterceptors);
+ setAcceptEncoding(encoding);
+ interceptor.handleMessage(outMessage);
+ assertSame("Wrong value of " + GZIPOutInterceptor.USE_GZIP_KEY, expectedUseGzip, outMessage
+ .get(GZIPOutInterceptor.USE_GZIP_KEY));
+ assertEquals("Wrong value of " + GZIPOutInterceptor.GZIP_ENCODING_KEY, expectedGzipEncoding,
+ outMessage.get(GZIPOutInterceptor.GZIP_ENCODING_KEY));
+ }
+
+ private void setAcceptEncoding(String enc) {
+ Map<String, List<String>> protocolHeaders = new HashMap<String, List<String>>();
+ protocolHeaders.put(HttpHeaderHelper.getHeaderKey(HttpHeaderHelper.ACCEPT_ENCODING), Collections
+ .singletonList(enc));
+ inMessage.put(Message.PROTOCOL_HEADERS, protocolHeaders);
+ }
+}
Propchange: cxf/trunk/rt/transports/http/src/test/java/org/apache/cxf/transport/http/gzip/GZIPAcceptEncodingTest.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: cxf/trunk/rt/transports/http/src/test/java/org/apache/cxf/transport/http/gzip/GZIPAcceptEncodingTest.java
------------------------------------------------------------------------------
svn:keywords = Rev Date
Propchange: cxf/trunk/rt/transports/http/src/test/java/org/apache/cxf/transport/http/gzip/GZIPAcceptEncodingTest.java
------------------------------------------------------------------------------
svn:mime-type = text/plain