From a7132ee08e5a37e94903c85e920dfd8604120d00 Mon Sep 17 00:00:00 2001 From: Trustin Lee Date: Fri, 16 Oct 2009 06:10:25 +0000 Subject: [PATCH] Relates issue: NETTY-80 Compression codec * Initial implementation of jzlib based zlib compression handler --- NOTICE.txt | 8 ++ license/LICENSE.jzlib.txt | 26 ++++ pom.xml | 9 ++ .../codec/compression/ZlibDecoder.java | 105 +++++++++++++++++ .../codec/compression/ZlibEncoder.java | 111 ++++++++++++++++++ .../codec/compression/package-info.java | 26 ++++ 6 files changed, 285 insertions(+) create mode 100644 license/LICENSE.jzlib.txt create mode 100644 src/main/java/org/jboss/netty/handler/codec/compression/ZlibDecoder.java create mode 100644 src/main/java/org/jboss/netty/handler/codec/compression/ZlibEncoder.java create mode 100644 src/main/java/org/jboss/netty/handler/codec/compression/package-info.java diff --git a/NOTICE.txt b/NOTICE.txt index 5851a936fb..292e7cf3a6 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -57,6 +57,14 @@ interchange format, which can be obtained at: * HOMEPAGE: * http://code.google.com/p/protobuf/ +This product optionally depends on 'JZlib', a re-implementation of zlib in pure +Java, which can be obtained at: + + * LICENSE: + * license/LICENSE.jzlib.txt (BSD Style License) + * HOMEPAGE: + * http://www.jcraft.com/jzlib/ + This product optionally depends on 'SLF4J', a simple logging facade for Java, which can be obtained at: diff --git a/license/LICENSE.jzlib.txt b/license/LICENSE.jzlib.txt new file mode 100644 index 0000000000..29ad562af0 --- /dev/null +++ b/license/LICENSE.jzlib.txt @@ -0,0 +1,26 @@ +Copyright (c) 2000,2001,2002,2003,2004 ymnk, JCraft,Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, +INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/pom.xml b/pom.xml index aa4d72b754..241ecac6eb 100644 --- a/pom.xml +++ b/pom.xml @@ -77,6 +77,15 @@ true + + + com.jcraft + jzlib + 1.0.7 + compile + true + + javax.servlet diff --git a/src/main/java/org/jboss/netty/handler/codec/compression/ZlibDecoder.java b/src/main/java/org/jboss/netty/handler/codec/compression/ZlibDecoder.java new file mode 100644 index 0000000000..2bd995f256 --- /dev/null +++ b/src/main/java/org/jboss/netty/handler/codec/compression/ZlibDecoder.java @@ -0,0 +1,105 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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.jboss.netty.handler.codec.compression; + +import org.jboss.netty.buffer.ChannelBuffer; +import org.jboss.netty.buffer.ChannelBuffers; +import org.jboss.netty.channel.Channel; +import org.jboss.netty.channel.ChannelHandlerContext; +import org.jboss.netty.channel.ChannelPipelineCoverage; +import org.jboss.netty.handler.codec.oneone.OneToOneDecoder; + +import com.jcraft.jzlib.JZlib; +import com.jcraft.jzlib.ZStream; +import com.jcraft.jzlib.ZStreamException; + +/** + * Decompresses a {@link ChannelBuffer} using the deflate algorithm. + * + * @author The Netty Project (netty-dev@lists.jboss.org) + * @author Trustin Lee (tlee@redhat.com) + * @version $Rev$, $Date$ + */ +@ChannelPipelineCoverage("one") +public class ZlibDecoder extends OneToOneDecoder { + + private final ZStream z = new ZStream(); + + // TODO Auto-detect wrappers (zlib, gzip, nowrapper as a fallback) + + /** + * Creates a new instance. + */ + public ZlibDecoder() { + z.inflateInit(); + } + + @Override + protected Object decode(ChannelHandlerContext ctx, Channel channel, Object msg) throws Exception { + if (!(msg instanceof ChannelBuffer)) { + return msg; + } + + try { + // Configure input. + ChannelBuffer compressed = (ChannelBuffer) msg; + byte[] in = new byte[compressed.readableBytes()]; + compressed.readBytes(in); + z.next_in = in; + z.next_in_index = 0; + z.avail_in = in.length; + + // Configure output. + byte[] out = new byte[in.length << 1]; + ChannelBuffer decompressed = ChannelBuffers.dynamicBuffer( + compressed.order(), out.length, + ctx.getChannel().getConfig().getBufferFactory()); + z.next_out = out; + z.next_out_index = 0; + z.avail_out = out.length; + + do { + // Decompress 'in' into 'out' + int resultCode = z.inflate(JZlib.Z_SYNC_FLUSH); + switch (resultCode) { + case JZlib.Z_OK: + case JZlib.Z_BUF_ERROR: + decompressed.writeBytes(out, 0, z.next_out_index); + z.next_out_index = 0; + z.avail_out = out.length; + break; + default: + throw new ZStreamException( + "decompression failure (" + resultCode + ")" + + (z.msg != null? ": " + z.msg : "")); + } + } while (z.avail_in > 0); + + if (decompressed.writerIndex() != 0) { // readerIndex is always 0 + return decompressed; + } else { + return ChannelBuffers.EMPTY_BUFFER; + } + } finally { + // Deference the external references explicitly to tell the VM that + // the allocated byte arrays are temporary so that the call stack + // can be utilized. + // I'm not sure if the modern VMs do this optimization though. + z.next_in = null; + z.next_out = null; + } + } +} diff --git a/src/main/java/org/jboss/netty/handler/codec/compression/ZlibEncoder.java b/src/main/java/org/jboss/netty/handler/codec/compression/ZlibEncoder.java new file mode 100644 index 0000000000..db2f22f358 --- /dev/null +++ b/src/main/java/org/jboss/netty/handler/codec/compression/ZlibEncoder.java @@ -0,0 +1,111 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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.jboss.netty.handler.codec.compression; + +import org.jboss.netty.buffer.ChannelBuffer; +import org.jboss.netty.buffer.ChannelBuffers; +import org.jboss.netty.channel.Channel; +import org.jboss.netty.channel.ChannelHandlerContext; +import org.jboss.netty.channel.ChannelPipelineCoverage; +import org.jboss.netty.handler.codec.oneone.OneToOneEncoder; + +import com.jcraft.jzlib.JZlib; +import com.jcraft.jzlib.ZStream; +import com.jcraft.jzlib.ZStreamException; + +/** + * Compresses a {@link ChannelBuffer} using the deflate algorithm. + * + * @author The Netty Project (netty-dev@lists.jboss.org) + * @author Trustin Lee (tlee@redhat.com) + * @version $Rev$, $Date$ + */ +@ChannelPipelineCoverage("one") +public class ZlibEncoder extends OneToOneEncoder { + + private final ZStream z = new ZStream(); + + // TODO 'do not compress' once option + // TODO support three wrappers - zlib (default), gzip (unsupported by jzlib, but easy to implement), nowrap + + /** + * Creates a new GZip encoder with the default compression level + * ({@link JZlib#Z_DEFAULT_COMPRESSION}). + */ + public ZlibEncoder() { + this(JZlib.Z_DEFAULT_COMPRESSION); + } + + /** + * Creates a new GZip encoder with the specified {@code compressionLevel}. + * + * @param compressionLevel + * the compression level, as specified in {@link JZlib}. + * The common values are + * {@link JZlib#Z_BEST_COMPRESSION}, + * {@link JZlib#Z_BEST_SPEED}, + * {@link JZlib#Z_DEFAULT_COMPRESSION}, and + * {@link JZlib#Z_NO_COMPRESSION}. + */ + public ZlibEncoder(int compressionLevel) { + z.deflateInit(compressionLevel, false); // Default: ZLIB format + } + + @Override + protected Object encode(ChannelHandlerContext ctx, Channel channel, Object msg) throws Exception { + if (!(msg instanceof ChannelBuffer)) { + return msg; + } + + try { + // Configure input. + ChannelBuffer uncompressed = (ChannelBuffer) msg; + byte[] in = new byte[uncompressed.readableBytes()]; + uncompressed.readBytes(in); + z.next_in = in; + z.next_in_index = 0; + z.avail_in = in.length; + + // Configure output. + byte[] out = new byte[(int) Math.ceil(in.length * 1.001) + 12]; + z.next_out = out; + z.next_out_index = 0; + z.avail_out = out.length; + + // Note that Z_PARTIAL_FLUSH has been deprecated. + int resultCode = z.deflate(JZlib.Z_SYNC_FLUSH); + if (resultCode != JZlib.Z_OK) { + throw new ZStreamException( + "compression failure (" + resultCode + ")" + + (z.msg != null? ": " + z.msg : "")); + } + + if (z.next_out_index != 0) { + return ctx.getChannel().getConfig().getBufferFactory().getBuffer( + uncompressed.order(), out, 0, z.next_out_index); + } else { + return ChannelBuffers.EMPTY_BUFFER; + } + } finally { + // Deference the external references explicitly to tell the VM that + // the allocated byte arrays are temporary so that the call stack + // can be utilized. + // I'm not sure if the modern VMs do this optimization though. + z.next_in = null; + z.next_out = null; + } + } +} diff --git a/src/main/java/org/jboss/netty/handler/codec/compression/package-info.java b/src/main/java/org/jboss/netty/handler/codec/compression/package-info.java new file mode 100644 index 0000000000..7400e415a7 --- /dev/null +++ b/src/main/java/org/jboss/netty/handler/codec/compression/package-info.java @@ -0,0 +1,26 @@ +/* + * Copyright 2009 Red Hat, Inc. + * + * Red Hat 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. + */ + +/** + * Encoder and decoder which compresses and decompresses {@link ChannelBuffer}s + * in a compression format such as zlib, + * gzip, and + * bzip2. + * + * @apiviz.exclude \.codec\.(?!compression)[a-z0-9]+\. + */ +package org.jboss.netty.handler.codec.compression; +// TODO Implement bzip2 handlers