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.io;
29  
30  import java.io.IOException;
31  import java.io.OutputStream;
32  import java.nio.ByteBuffer;
33  import java.nio.CharBuffer;
34  import java.nio.charset.CharsetEncoder;
35  import java.nio.charset.CoderResult;
36  
37  import org.apache.http.annotation.NotThreadSafe;
38  import org.apache.http.io.BufferInfo;
39  import org.apache.http.io.HttpTransportMetrics;
40  import org.apache.http.io.SessionOutputBuffer;
41  import org.apache.http.protocol.HTTP;
42  import org.apache.http.util.Args;
43  import org.apache.http.util.Asserts;
44  import org.apache.http.util.ByteArrayBuffer;
45  import org.apache.http.util.CharArrayBuffer;
46  
47  /**
48   * Abstract base class for session output buffers that stream data to
49   * an arbitrary {@link OutputStream}. This class buffers small chunks of
50   * output data in an internal byte array for optimal output performance.
51   * </p>
52   * {@link #writeLine(CharArrayBuffer)} and {@link #writeLine(String)} methods
53   * of this class use CR-LF as a line delimiter.
54   *
55   * @since 4.3
56   */
57  @NotThreadSafe
58  public class SessionOutputBufferImpl implements SessionOutputBuffer, BufferInfo {
59  
60      private static final byte[] CRLF = new byte[] {HTTP.CR, HTTP.LF};
61  
62      private final HttpTransportMetricsImpl metrics;
63      private final ByteArrayBuffer buffer;
64      private final int fragementSizeHint;
65      private final CharsetEncoder encoder;
66  
67      private OutputStream outstream;
68      private ByteBuffer bbuf;
69  
70      /**
71       * Creates new instance of SessionOutputBufferImpl.
72       *
73       * @param metrics HTTP transport metrics.
74       * @param buffersize buffer size. Must be a positive number.
75       * @param fragementSizeHint fragment size hint defining a minimal size of a fragment
76       *   that should be written out directly to the socket bypassing the session buffer.
77       *   Value <code>0</code> disables fragment buffering.
78       * @param charencoder charencoder to be used for encoding HTTP protocol elements.
79       *   If <code>null</code> simple type cast will be used for char to byte conversion.
80       */
81      public SessionOutputBufferImpl(
82              final HttpTransportMetricsImpl metrics,
83              final int buffersize,
84              final int fragementSizeHint,
85              final CharsetEncoder charencoder) {
86          super();
87          Args.positive(buffersize, "Buffer size");
88          Args.notNull(metrics, "HTTP transport metrcis");
89          this.metrics = metrics;
90          this.buffer = new ByteArrayBuffer(buffersize);
91          this.fragementSizeHint = fragementSizeHint >= 0 ? fragementSizeHint : 0;
92          this.encoder = charencoder;
93      }
94  
95      public SessionOutputBufferImpl(
96              final HttpTransportMetricsImpl metrics,
97              final int buffersize) {
98          this(metrics, buffersize, buffersize, null);
99      }
100 
101     public void bind(final OutputStream outstream) {
102         this.outstream = outstream;
103     }
104 
105     public boolean isBound() {
106         return this.outstream != null;
107     }
108 
109     @Override
110     public int capacity() {
111         return this.buffer.capacity();
112     }
113 
114     @Override
115     public int length() {
116         return this.buffer.length();
117     }
118 
119     @Override
120     public int available() {
121         return capacity() - length();
122     }
123 
124     private void streamWrite(final byte[] b, final int off, final int len) throws IOException {
125         Asserts.notNull(outstream, "Output stream");
126         this.outstream.write(b, off, len);
127     }
128 
129     private void flushStream() throws IOException {
130         if (this.outstream != null) {
131             this.outstream.flush();
132         }
133     }
134 
135     private void flushBuffer() throws IOException {
136         final int len = this.buffer.length();
137         if (len > 0) {
138             streamWrite(this.buffer.buffer(), 0, len);
139             this.buffer.clear();
140             this.metrics.incrementBytesTransferred(len);
141         }
142     }
143 
144     @Override
145     public void flush() throws IOException {
146         flushBuffer();
147         flushStream();
148     }
149 
150     @Override
151     public void write(final byte[] b, final int off, final int len) throws IOException {
152         if (b == null) {
153             return;
154         }
155         // Do not want to buffer large-ish chunks
156         // if the byte array is larger then MIN_CHUNK_LIMIT
157         // write it directly to the output stream
158         if (len > this.fragementSizeHint || len > this.buffer.capacity()) {
159             // flush the buffer
160             flushBuffer();
161             // write directly to the out stream
162             streamWrite(b, off, len);
163             this.metrics.incrementBytesTransferred(len);
164         } else {
165             // Do not let the buffer grow unnecessarily
166             final int freecapacity = this.buffer.capacity() - this.buffer.length();
167             if (len > freecapacity) {
168                 // flush the buffer
169                 flushBuffer();
170             }
171             // buffer
172             this.buffer.append(b, off, len);
173         }
174     }
175 
176     @Override
177     public void write(final byte[] b) throws IOException {
178         if (b == null) {
179             return;
180         }
181         write(b, 0, b.length);
182     }
183 
184     @Override
185     public void write(final int b) throws IOException {
186         if (this.fragementSizeHint > 0) {
187             if (this.buffer.isFull()) {
188                 flushBuffer();
189             }
190             this.buffer.append(b);
191         } else {
192             flushBuffer();
193             this.outstream.write(b);
194         }
195     }
196 
197     /**
198      * Writes characters from the specified string followed by a line delimiter
199      * to this session buffer.
200      * <p>
201      * This method uses CR-LF as a line delimiter.
202      *
203      * @param      s   the line.
204      * @exception  IOException  if an I/O error occurs.
205      */
206     @Override
207     public void writeLine(final String s) throws IOException {
208         if (s == null) {
209             return;
210         }
211         if (s.length() > 0) {
212             if (this.encoder == null) {
213                 for (int i = 0; i < s.length(); i++) {
214                     write(s.charAt(i));
215                 }
216             } else {
217                 final CharBuffer cbuf = CharBuffer.wrap(s);
218                 writeEncoded(cbuf);
219             }
220         }
221         write(CRLF);
222     }
223 
224     /**
225      * Writes characters from the specified char array followed by a line
226      * delimiter to this session buffer.
227      * <p>
228      * This method uses CR-LF as a line delimiter.
229      *
230      * @param      charbuffer the buffer containing chars of the line.
231      * @exception  IOException  if an I/O error occurs.
232      */
233     @Override
234     public void writeLine(final CharArrayBuffer charbuffer) throws IOException {
235         if (charbuffer == null) {
236             return;
237         }
238         if (this.encoder == null) {
239             int off = 0;
240             int remaining = charbuffer.length();
241             while (remaining > 0) {
242                 int chunk = this.buffer.capacity() - this.buffer.length();
243                 chunk = Math.min(chunk, remaining);
244                 if (chunk > 0) {
245                     this.buffer.append(charbuffer, off, chunk);
246                 }
247                 if (this.buffer.isFull()) {
248                     flushBuffer();
249                 }
250                 off += chunk;
251                 remaining -= chunk;
252             }
253         } else {
254             final CharBuffer cbuf = CharBuffer.wrap(charbuffer.buffer(), 0, charbuffer.length());
255             writeEncoded(cbuf);
256         }
257         write(CRLF);
258     }
259 
260     private void writeEncoded(final CharBuffer cbuf) throws IOException {
261         if (!cbuf.hasRemaining()) {
262             return;
263         }
264         if (this.bbuf == null) {
265             this.bbuf = ByteBuffer.allocate(1024);
266         }
267         this.encoder.reset();
268         while (cbuf.hasRemaining()) {
269             final CoderResult result = this.encoder.encode(cbuf, this.bbuf, true);
270             handleEncodingResult(result);
271         }
272         final CoderResult result = this.encoder.flush(this.bbuf);
273         handleEncodingResult(result);
274         this.bbuf.clear();
275     }
276 
277     private void handleEncodingResult(final CoderResult result) throws IOException {
278         if (result.isError()) {
279             result.throwException();
280         }
281         this.bbuf.flip();
282         while (this.bbuf.hasRemaining()) {
283             write(this.bbuf.get());
284         }
285         this.bbuf.compact();
286     }
287 
288     @Override
289     public HttpTransportMetrics getMetrics() {
290         return this.metrics;
291     }
292 
293 }