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 */
027
028package org.apache.http.impl.nio.codecs;
029
030import java.io.IOException;
031import java.nio.ByteBuffer;
032import java.nio.channels.WritableByteChannel;
033
034import org.apache.http.impl.io.HttpTransportMetricsImpl;
035import org.apache.http.io.BufferInfo;
036import org.apache.http.nio.reactor.SessionOutputBuffer;
037import org.apache.http.util.CharArrayBuffer;
038
039/**
040 * Implements chunked transfer coding. The content is sent in small chunks.
041 * Entities transferred using this decoder can be of unlimited length.
042 *
043 * @since 4.0
044 */
045public class ChunkEncoder extends AbstractContentEncoder {
046
047    private final int fragHint;
048    private final CharArrayBuffer lineBuffer;
049
050    private final BufferInfo bufferinfo;
051
052    /**
053     * @since 4.3
054     *
055     * @param channel underlying channel.
056     * @param buffer  session buffer.
057     * @param metrics transport metrics.
058     * @param fragementSizeHint fragment size hint defining an minimal size of a fragment
059     *   that should be written out directly to the channel bypassing the session buffer.
060     *   Value {@code 0} disables fragment buffering.
061     */
062    public ChunkEncoder(
063            final WritableByteChannel channel,
064            final SessionOutputBuffer buffer,
065            final HttpTransportMetricsImpl metrics,
066            final int fragementSizeHint) {
067        super(channel, buffer, metrics);
068        this.fragHint = fragementSizeHint > 0 ? fragementSizeHint : 0;
069        this.lineBuffer = new CharArrayBuffer(16);
070        if (buffer instanceof BufferInfo) {
071            this.bufferinfo = (BufferInfo) buffer;
072        } else {
073            this.bufferinfo = null;
074        }
075    }
076
077    public ChunkEncoder(
078            final WritableByteChannel channel,
079            final SessionOutputBuffer buffer,
080            final HttpTransportMetricsImpl metrics) {
081        this(channel, buffer, metrics, 0);
082    }
083
084    @Override
085    public int write(final ByteBuffer src) throws IOException {
086        if (src == null) {
087            return 0;
088        }
089        assertNotCompleted();
090
091        int total = 0;
092        while (src.hasRemaining()) {
093            int chunk = src.remaining();
094            int avail;
095            if (this.bufferinfo != null) {
096                avail = this.bufferinfo.available();
097            } else {
098                avail = 4096;
099            }
100
101            // subtract the length of the longest chunk header
102            // 12345678\r\n
103            // <chunk-data>\r\n
104            avail -= 12;
105            if (avail > 0) {
106                if (avail < chunk) {
107                    // write no more than 'avail' bytes
108                    chunk = avail;
109                    this.lineBuffer.clear();
110                    this.lineBuffer.append(Integer.toHexString(chunk));
111                    this.buffer.writeLine(this.lineBuffer);
112                    final int oldlimit = src.limit();
113                    src.limit(src.position() + chunk);
114                    this.buffer.write(src);
115                    src.limit(oldlimit);
116                } else {
117                    // write all
118                    this.lineBuffer.clear();
119                    this.lineBuffer.append(Integer.toHexString(chunk));
120                    this.buffer.writeLine(this.lineBuffer);
121                    this.buffer.write(src);
122                }
123                this.lineBuffer.clear();
124                this.buffer.writeLine(this.lineBuffer);
125                total += chunk;
126            }
127            if (this.buffer.length() >= this.fragHint || src.hasRemaining()) {
128                final int bytesWritten = flushToChannel();
129                if (bytesWritten == 0) {
130                    break;
131                }
132            }
133        }
134        return total;
135    }
136
137    @Override
138    public void complete() throws IOException {
139        assertNotCompleted();
140        this.lineBuffer.clear();
141        this.lineBuffer.append("0");
142        this.buffer.writeLine(this.lineBuffer);
143        this.lineBuffer.clear();
144        this.buffer.writeLine(this.lineBuffer);
145        super.complete();
146    }
147
148    @Override
149    public String toString() {
150        final StringBuilder sb = new StringBuilder();
151        sb.append("[chunk-coded; completed: ");
152        sb.append(isCompleted());
153        sb.append("]");
154        return sb.toString();
155    }
156
157}