001/* 002 * ==================================================================== 003 * Licensed to the Apache Software Foundation (ASF) under one 004 * or more contributor license agreements. See the NOTICE file 005 * distributed with this work for additional information 006 * regarding copyright ownership. The ASF licenses this file 007 * to you under the Apache License, Version 2.0 (the 008 * "License"); you may not use this file except in compliance 009 * with the License. You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, 014 * software distributed under the License is distributed on an 015 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 016 * KIND, either express or implied. See the License for the 017 * specific language governing permissions and limitations 018 * under the License. 019 * ==================================================================== 020 * 021 * This software consists of voluntary contributions made by many 022 * individuals on behalf of the Apache Software Foundation. For more 023 * information on the Apache Software Foundation, please see 024 * <http://www.apache.org/>. 025 * 026 */ 027package org.apache.http.client.protocol; 028 029import java.io.IOException; 030import java.io.InputStream; 031import java.util.Locale; 032import java.util.zip.GZIPInputStream; 033 034import org.apache.http.Header; 035import org.apache.http.HeaderElement; 036import org.apache.http.HttpEntity; 037import org.apache.http.HttpException; 038import org.apache.http.HttpResponse; 039import org.apache.http.HttpResponseInterceptor; 040import org.apache.http.annotation.Contract; 041import org.apache.http.annotation.ThreadingBehavior; 042import org.apache.http.client.config.RequestConfig; 043import org.apache.http.client.entity.DecompressingEntity; 044import org.apache.http.client.entity.DeflateInputStream; 045import org.apache.http.client.entity.InputStreamFactory; 046import org.apache.http.config.Lookup; 047import org.apache.http.config.RegistryBuilder; 048import org.apache.http.protocol.HttpContext; 049 050/** 051 * {@link HttpResponseInterceptor} responsible for processing Content-Encoding 052 * responses. 053 * <p> 054 * Instances of this class are stateless and immutable, therefore threadsafe. 055 * 056 * @since 4.1 057 * 058 */ 059@Contract(threading = ThreadingBehavior.IMMUTABLE_CONDITIONAL) 060public class ResponseContentEncoding implements HttpResponseInterceptor { 061 062 public static final String UNCOMPRESSED = "http.client.response.uncompressed"; 063 064 private final static InputStreamFactory GZIP = new InputStreamFactory() { 065 066 @Override 067 public InputStream create(final InputStream instream) throws IOException { 068 return new GZIPInputStream(instream); 069 } 070 }; 071 072 private final static InputStreamFactory DEFLATE = new InputStreamFactory() { 073 074 @Override 075 public InputStream create(final InputStream instream) throws IOException { 076 return new DeflateInputStream(instream); 077 } 078 079 }; 080 081 private final Lookup<InputStreamFactory> decoderRegistry; 082 private final boolean ignoreUnknown; 083 084 /** 085 * @since 4.5 086 */ 087 public ResponseContentEncoding(final Lookup<InputStreamFactory> decoderRegistry, final boolean ignoreUnknown) { 088 this.decoderRegistry = decoderRegistry != null ? decoderRegistry : 089 RegistryBuilder.<InputStreamFactory>create() 090 .register("gzip", GZIP) 091 .register("x-gzip", GZIP) 092 .register("deflate", DEFLATE) 093 .build(); 094 this.ignoreUnknown = ignoreUnknown; 095 } 096 097 /** 098 * @since 4.5 099 */ 100 public ResponseContentEncoding(final boolean ignoreUnknown) { 101 this(null, ignoreUnknown); 102 } 103 104 /** 105 * @since 4.4 106 */ 107 public ResponseContentEncoding(final Lookup<InputStreamFactory> decoderRegistry) { 108 this(decoderRegistry, true); 109 } 110 111 /** 112 * Handles {@code gzip} and {@code deflate} compressed entities by using the following 113 * decoders: 114 * <ul> 115 * <li>gzip - see {@link GZIPInputStream}</li> 116 * <li>deflate - see {@link DeflateInputStream}</li> 117 * </ul> 118 */ 119 public ResponseContentEncoding() { 120 this(null); 121 } 122 123 @Override 124 public void process( 125 final HttpResponse response, 126 final HttpContext context) throws HttpException, IOException { 127 final HttpEntity entity = response.getEntity(); 128 129 final HttpClientContext clientContext = HttpClientContext.adapt(context); 130 final RequestConfig requestConfig = clientContext.getRequestConfig(); 131 // entity can be null in case of 304 Not Modified, 204 No Content or similar 132 // check for zero length entity. 133 if (requestConfig.isContentCompressionEnabled() && entity != null && entity.getContentLength() != 0) { 134 final Header ceheader = entity.getContentEncoding(); 135 if (ceheader != null) { 136 final HeaderElement[] codecs = ceheader.getElements(); 137 for (final HeaderElement codec : codecs) { 138 final String codecname = codec.getName().toLowerCase(Locale.ROOT); 139 final InputStreamFactory decoderFactory = decoderRegistry.lookup(codecname); 140 if (decoderFactory != null) { 141 response.setEntity(new DecompressingEntity(response.getEntity(), decoderFactory)); 142 response.removeHeaders("Content-Length"); 143 response.removeHeaders("Content-Encoding"); 144 response.removeHeaders("Content-MD5"); 145 } else { 146 if (!"identity".equals(codecname) && !ignoreUnknown) { 147 throw new HttpException("Unsupported Content-Encoding: " + codec.getName()); 148 } 149 } 150 } 151 } 152 } 153 } 154 155}