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