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 bytesWritten = writeToChannel(src, chunk);
132                     this.remaining -= bytesWritten;
133                     total += bytesWritten;
134                     if (bytesWritten == 0) {
135                         break;
136                     }
137                 }
138             }
139         }
140         if (this.remaining <= 0) {
141             super.complete();
142         }
143         return total;
144     }
145 
146     public long transfer(
147             final FileChannel src,
148             final long position,
149             final long count) throws IOException {
150 
151         if (src == null) {
152             return 0;
153         }
154         assertNotCompleted();
155 
156         flushToChannel();
157         if (this.buffer.hasData()) {
158             return 0;
159         }
160 
161         final long chunk = Math.min(this.remaining, count);
162         final long bytesWritten = src.transferTo(position, chunk, this.channel);
163         if (bytesWritten > 0) {
164             this.metrics.incrementBytesTransferred(bytesWritten);
165         }
166         this.remaining -= bytesWritten;
167         if (this.remaining <= 0) {
168             super.complete();
169         }
170         return bytesWritten;
171     }
172 
173     @Override
174     public String toString() {
175         final StringBuilder sb = new StringBuilder();
176         sb.append("[content length: ");
177         sb.append(this.contentLength);
178         sb.append("; pos: ");
179         sb.append(this.contentLength - this.remaining);
180         sb.append("; completed: ");
181         sb.append(isCompleted());
182         sb.append("]");
183         return sb.toString();
184     }
185 
186 }