Package io.netty.handler.codec.http2

Source Code of io.netty.handler.codec.http2.DelegatingDecompressorFrameListener

/*
* Copyright 2014 The Netty Project
*
* The Netty Project 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 io.netty.handler.codec.http2;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.handler.codec.AsciiString;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.handler.codec.compression.ZlibCodecFactory;
import io.netty.handler.codec.compression.ZlibWrapper;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaderValues;

/**
* A HTTP2 frame listener that will decompress data frames according to the {@code content-encoding} header for each
* stream.
*/
public class DelegatingDecompressorFrameListener extends Http2FrameListenerDecorator {

    /**
     * {@code "x-deflate"}
     */
    private static final AsciiString X_DEFLATE = new AsciiString("x-deflate");
    /**
     * {@code "x-gzip"}
     */
    private static final AsciiString X_GZIP = new AsciiString("x-gzip");

    private static final Http2ConnectionAdapter CLEAN_UP_LISTENER = new Http2ConnectionAdapter() {
        @Override
        public void streamRemoved(Http2Stream stream) {
            final EmbeddedChannel decompressor = stream.decompressor();
            if (decompressor != null) {
                cleanup(stream, decompressor);
            }
        }
    };

    private final Http2Connection connection;
    private final boolean strict;

    public DelegatingDecompressorFrameListener(Http2Connection connection, Http2FrameListener listener) {
        this(connection, listener, true);
    }

    public DelegatingDecompressorFrameListener(Http2Connection connection, Http2FrameListener listener,
                    boolean strict) {
        super(listener);
        this.connection = connection;
        this.strict = strict;

        connection.addListener(CLEAN_UP_LISTENER);
    }

    @Override
    public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding, boolean endOfStream)
                    throws Http2Exception {
        final Http2Stream stream = connection.stream(streamId);
        final EmbeddedChannel decompressor = stream == null ? null : stream.decompressor();
        if (decompressor == null) {
            listener.onDataRead(ctx, streamId, data, padding, endOfStream);
            return;
        }

        try {
            // call retain here as it will call release after its written to the channel
            decompressor.writeInbound(data.retain());
            ByteBuf buf = nextReadableBuf(decompressor);
            if (buf == null) {
                if (endOfStream) {
                    listener.onDataRead(ctx, streamId, Unpooled.EMPTY_BUFFER, padding, true);
                }
                // END_STREAM is not set and the data could not be decoded yet.
                // The assumption has to be there will be more data frames to complete the decode.
                // We don't have enough information here to know if this is an error.
            } else {
                for (;;) {
                    final ByteBuf nextBuf = nextReadableBuf(decompressor);
                    if (nextBuf == null) {
                        listener.onDataRead(ctx, streamId, buf, padding, endOfStream);
                        break;
                    }

                    listener.onDataRead(ctx, streamId, buf, padding, false);
                    buf = nextBuf;
                }
            }
        } finally {
            if (endOfStream) {
                cleanup(stream, decompressor);
            }
        }
    }

    @Override
    public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, int padding,
                    boolean endStream) throws Http2Exception {
        initDecompressor(streamId, headers, endStream);
        listener.onHeadersRead(ctx, streamId, headers, padding, endStream);
    }

    @Override
    public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, int streamDependency,
                    short weight, boolean exclusive, int padding, boolean endStream) throws Http2Exception {
        initDecompressor(streamId, headers, endStream);
        listener.onHeadersRead(ctx, streamId, headers, streamDependency, weight, exclusive, padding, endStream);
    }

    /**
     * Returns a new {@link EmbeddedChannel} that decodes the HTTP2 message content encoded in the specified
     * {@code contentEncoding}.
     *
     * @param contentEncoding the value of the {@code content-encoding} header
     * @return a new {@link ByteToMessageDecoder} if the specified encoding is supported. {@code null} otherwise
     *         (alternatively, you can throw a {@link Http2Exception} to block unknown encoding).
     * @throws Http2Exception If the specified encoding is not not supported and warrants an exception
     */
    protected EmbeddedChannel newContentDecompressor(AsciiString contentEncoding) throws Http2Exception {
        if (HttpHeaderValues.GZIP.equalsIgnoreCase(contentEncoding) ||
            X_GZIP.equalsIgnoreCase(contentEncoding)) {
            return new EmbeddedChannel(ZlibCodecFactory.newZlibDecoder(ZlibWrapper.GZIP));
        }
        if (HttpHeaderValues.DEFLATE.equalsIgnoreCase(contentEncoding) ||
            X_DEFLATE.equalsIgnoreCase(contentEncoding)) {
            final ZlibWrapper wrapper = strict ? ZlibWrapper.ZLIB : ZlibWrapper.ZLIB_OR_NONE;
            // To be strict, 'deflate' means ZLIB, but some servers were not implemented correctly.
            return new EmbeddedChannel(ZlibCodecFactory.newZlibDecoder(wrapper));
        }
        // 'identity' or unsupported
        return null;
    }

    /**
     * Returns the expected content encoding of the decoded content. This getMethod returns {@code "identity"} by
     * default, which is the case for most decompressors.
     *
     * @param contentEncoding the value of the {@code content-encoding} header
     * @return the expected content encoding of the new content.
     * @throws Http2Exception if the {@code contentEncoding} is not supported and warrants an exception
     */
    protected AsciiString getTargetContentEncoding(@SuppressWarnings("UnusedParameters") AsciiString contentEncoding)
                    throws Http2Exception {
        return HttpHeaderValues.IDENTITY;
    }

    /**
     * Checks if a new decompressor object is needed for the stream identified by {@code streamId}.
     * This method will modify the {@code content-encoding} header contained in {@code headers}.
     *
     * @param streamId The identifier for the headers inside {@code headers}
     * @param headers Object representing headers which have been read
     * @param endOfStream Indicates if the stream has ended
     * @throws Http2Exception If the {@code content-encoding} is not supported
     */
    private void initDecompressor(int streamId, Http2Headers headers, boolean endOfStream) throws Http2Exception {
        final Http2Stream stream = connection.stream(streamId);
        if (stream == null) {
            return;
        }

        EmbeddedChannel decompressor = stream.decompressor();
        if (decompressor == null) {
            if (!endOfStream) {
                // Determine the content encoding.
                AsciiString contentEncoding = headers.get(HttpHeaderNames.CONTENT_ENCODING);
                if (contentEncoding == null) {
                    contentEncoding = HttpHeaderValues.IDENTITY;
                }
                decompressor = newContentDecompressor(contentEncoding);
                if (decompressor != null) {
                    stream.decompressor(decompressor);
                    // Decode the content and remove or replace the existing headers
                    // so that the message looks like a decoded message.
                    AsciiString targetContentEncoding = getTargetContentEncoding(contentEncoding);
                    if (HttpHeaderValues.IDENTITY.equalsIgnoreCase(targetContentEncoding)) {
                        headers.remove(HttpHeaderNames.CONTENT_ENCODING);
                    } else {
                        headers.set(HttpHeaderNames.CONTENT_ENCODING, targetContentEncoding);
                    }
                }
            }
        } else if (endOfStream) {
            cleanup(stream, decompressor);
        }
        if (decompressor != null) {
            // The content length will be for the compressed data. Since we will decompress the data
            // this content-length will not be correct. Instead of queuing messages or delaying sending
            // header frames...just remove the content-length header
            headers.remove(HttpHeaderNames.CONTENT_LENGTH);
        }
    }

    /**
     * Release remaining content from the {@link EmbeddedChannel} and remove the decompressor
     * from the {@link Http2Stream}.
     *
     * @param stream The stream for which {@code decompressor} is the decompressor for
     * @param decompressor The decompressor for {@code stream}
     */
    private static void cleanup(Http2Stream stream, EmbeddedChannel decompressor) {
        if (decompressor.finish()) {
            for (;;) {
                final ByteBuf buf = decompressor.readInbound();
                if (buf == null) {
                    break;
                }
                buf.release();
            }
        }
        stream.decompressor(null);
    }

    /**
     * Read the next decompressed {@link ByteBuf} from the {@link EmbeddedChannel}
     * or {@code null} if one does not exist.
     *
     * @param decompressor The channel to read from
     * @return The next decoded {@link ByteBuf} from the {@link EmbeddedChannel} or {@code null} if one does not exist
     */
    private static ByteBuf nextReadableBuf(EmbeddedChannel decompressor) {
        for (;;) {
            final ByteBuf buf = decompressor.readInbound();
            if (buf == null) {
                return null;
            }
            if (!buf.isReadable()) {
                buf.release();
                continue;
            }
            return buf;
        }
    }
}
TOP

Related Classes of io.netty.handler.codec.http2.DelegatingDecompressorFrameListener

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.