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.hc.core5.http.impl.nio;
29  
30  import java.io.IOException;
31  import java.nio.ByteBuffer;
32  import java.nio.channels.FileChannel;
33  import java.nio.channels.WritableByteChannel;
34  
35  import org.apache.hc.core5.http.impl.BasicHttpTransportMetrics;
36  import org.apache.hc.core5.http.nio.FileContentEncoder;
37  import org.apache.hc.core5.http.nio.SessionOutputBuffer;
38  import org.apache.hc.core5.util.Args;
39  
40  /**
41   * Content encoder that cuts off after a defined number of bytes. This class
42   * is used to send content of HTTP messages where the end of the content entity
43   * is determined by the value of the {@code Content-Length header}.
44   * Entities transferred using this stream can be maximum {@link Long#MAX_VALUE}
45   * long.
46   * <p>
47   * This decoder is optimized to transfer data directly from
48   * a {@link FileChannel} to the underlying I/O session's channel whenever
49   * possible avoiding intermediate buffering in the session buffer.
50   *
51   * @since 4.0
52   */
53  public class LengthDelimitedEncoder extends AbstractContentEncoder implements FileContentEncoder {
54  
55      private final long contentLength;
56      private final int fragHint;
57  
58      private long remaining;
59  
60      /**
61       * @since 4.3
62       *
63       * @param channel underlying channel.
64       * @param buffer  session buffer.
65       * @param metrics transport metrics.
66       * @param contentLength content length.
67       * @param chunkSizeHint fragment size hint defining an minimal size of a fragment
68       *   that should be written out directly to the channel bypassing the session buffer.
69       *   Value {@code 0} disables fragment buffering.
70       */
71      public LengthDelimitedEncoder(
72              final WritableByteChannel channel,
73              final SessionOutputBuffer buffer,
74              final BasicHttpTransportMetrics metrics,
75              final long contentLength,
76              final int chunkSizeHint) {
77          super(channel, buffer, metrics);
78          Args.notNegative(contentLength, "Content length");
79          this.contentLength = contentLength;
80          this.fragHint = chunkSizeHint > 0 ? chunkSizeHint : 0;
81          this.remaining = contentLength;
82      }
83  
84      public LengthDelimitedEncoder(
85              final WritableByteChannel channel,
86              final SessionOutputBuffer buffer,
87              final BasicHttpTransportMetrics metrics,
88              final long contentLength) {
89          this(channel, buffer, metrics, contentLength, 0);
90      }
91  
92      private int nextChunk(final ByteBuffer src) {
93          return (int) Math.min(Math.min(this.remaining, Integer.MAX_VALUE), src.remaining());
94      }
95  
96      @Override
97      public int write(final ByteBuffer src) throws IOException {
98          if (src == null) {
99              return 0;
100         }
101         assertNotCompleted();
102 
103         int total = 0;
104         while (src.hasRemaining() && this.remaining > 0) {
105             if (this.buffer.hasData() || this.fragHint > 0) {
106                 final int chunk = nextChunk(src);
107                 if (chunk <= this.fragHint) {
108                     final int capacity = this.fragHint - this.buffer.length();
109                     if (capacity > 0) {
110                         final int limit = Math.min(capacity, chunk);
111                         final int bytesWritten = writeToBuffer(src, limit);
112                         this.remaining -= bytesWritten;
113                         total += bytesWritten;
114                     }
115                 }
116             }
117             if (this.buffer.hasData()) {
118                 final int chunk = nextChunk(src);
119                 if (this.buffer.length() >= this.fragHint || chunk > 0) {
120                     final int bytesWritten = flushToChannel();
121                     if (bytesWritten == 0) {
122                         break;
123                     }
124                 }
125             }
126             if (!this.buffer.hasData()) {
127                 final int chunk = nextChunk(src);
128                 if (chunk > this.fragHint) {
129                     final int bytesWritten = writeToChannel(src, chunk);
130                     this.remaining -= bytesWritten;
131                     total += bytesWritten;
132                     if (bytesWritten == 0) {
133                         break;
134                     }
135                 }
136             }
137         }
138         if (this.remaining <= 0) {
139             super.complete(null);
140         }
141         return total;
142     }
143 
144     @Override
145     public long transfer(
146             final FileChannel src,
147             final long position,
148             final long count) throws IOException {
149 
150         if (src == null) {
151             return 0;
152         }
153         assertNotCompleted();
154 
155         flushToChannel();
156         if (this.buffer.hasData()) {
157             return 0;
158         }
159 
160         final long chunk = Math.min(this.remaining, count);
161         final long bytesWritten = src.transferTo(position, chunk, this.channel);
162         if (bytesWritten > 0) {
163             this.metrics.incrementBytesTransferred(bytesWritten);
164         }
165         this.remaining -= bytesWritten;
166         if (this.remaining <= 0) {
167             super.complete(null);
168         }
169         return bytesWritten;
170     }
171 
172     @Override
173     public String toString() {
174         final StringBuilder sb = new StringBuilder();
175         sb.append("[content length: ");
176         sb.append(this.contentLength);
177         sb.append("; pos: ");
178         sb.append(this.contentLength - this.remaining);
179         sb.append("; completed: ");
180         sb.append(isCompleted());
181         sb.append("]");
182         return sb.toString();
183     }
184 
185 }