View Javadoc
1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  
28  package org.apache.http.impl.nio.codecs;
29  
30  import java.io.IOException;
31  import java.nio.ByteBuffer;
32  import java.nio.channels.WritableByteChannel;
33  
34  import org.apache.http.impl.io.HttpTransportMetricsImpl;
35  import org.apache.http.io.BufferInfo;
36  import org.apache.http.nio.reactor.SessionOutputBuffer;
37  import org.apache.http.util.CharArrayBuffer;
38  
39  /**
40   * Implements chunked transfer coding. The content is sent in small chunks.
41   * Entities transferred using this decoder can be of unlimited length.
42   *
43   * @since 4.0
44   */
45  public class ChunkEncoder extends AbstractContentEncoder {
46  
47      private final int fragHint;
48      private final CharArrayBuffer lineBuffer;
49  
50      private final BufferInfo bufferinfo;
51  
52      /**
53       * @since 4.3
54       *
55       * @param channel underlying channel.
56       * @param buffer  session buffer.
57       * @param metrics transport metrics.
58       * @param fragementSizeHint fragment size hint defining an minimal size of a fragment
59       *   that should be written out directly to the channel bypassing the session buffer.
60       *   Value {@code 0} disables fragment buffering.
61       */
62      public ChunkEncoder(
63              final WritableByteChannel channel,
64              final SessionOutputBuffer buffer,
65              final HttpTransportMetricsImpl metrics,
66              final int fragementSizeHint) {
67          super(channel, buffer, metrics);
68          this.fragHint = fragementSizeHint > 0 ? fragementSizeHint : 0;
69          this.lineBuffer = new CharArrayBuffer(16);
70          if (buffer instanceof BufferInfo) {
71              this.bufferinfo = (BufferInfo) buffer;
72          } else {
73              this.bufferinfo = null;
74          }
75      }
76  
77      public ChunkEncoder(
78              final WritableByteChannel channel,
79              final SessionOutputBuffer buffer,
80              final HttpTransportMetricsImpl metrics) {
81          this(channel, buffer, metrics, 0);
82      }
83  
84      @Override
85      public int write(final ByteBuffer src) throws IOException {
86          if (src == null) {
87              return 0;
88          }
89          assertNotCompleted();
90  
91          int total = 0;
92          while (src.hasRemaining()) {
93              int chunk = src.remaining();
94              int avail;
95              if (this.bufferinfo != null) {
96                  avail = this.bufferinfo.available();
97              } else {
98                  avail = 4096;
99              }
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 }