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