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 void bind(final OutputStream outstream) {
96          this.outstream = outstream;
97      }
98  
99      public boolean isBound() {
100         return this.outstream != null;
101     }
102 
103     public int capacity() {
104         return this.buffer.capacity();
105     }
106 
107     public int length() {
108         return this.buffer.length();
109     }
110 
111     public int available() {
112         return capacity() - length();
113     }
114 
115     private void streamWrite(final byte[] b, final int off, final int len) throws IOException {
116         Asserts.notNull(outstream, "Output stream");
117         this.outstream.write(b, off, len);
118     }
119 
120     private void flushStream() throws IOException {
121         if (this.outstream != null) {
122             this.outstream.flush();
123         }
124     }
125 
126     private void flushBuffer() throws IOException {
127         final int len = this.buffer.length();
128         if (len > 0) {
129             streamWrite(this.buffer.buffer(), 0, len);
130             this.buffer.clear();
131             this.metrics.incrementBytesTransferred(len);
132         }
133     }
134 
135     public void flush() throws IOException {
136         flushBuffer();
137         flushStream();
138     }
139 
140     public void write(final byte[] b, final int off, final int len) throws IOException {
141         if (b == null) {
142             return;
143         }
144         // Do not want to buffer large-ish chunks
145         // if the byte array is larger then MIN_CHUNK_LIMIT
146         // write it directly to the output stream
147         if (len > this.fragementSizeHint || len > this.buffer.capacity()) {
148             // flush the buffer
149             flushBuffer();
150             // write directly to the out stream
151             streamWrite(b, off, len);
152             this.metrics.incrementBytesTransferred(len);
153         } else {
154             // Do not let the buffer grow unnecessarily
155             final int freecapacity = this.buffer.capacity() - this.buffer.length();
156             if (len > freecapacity) {
157                 // flush the buffer
158                 flushBuffer();
159             }
160             // buffer
161             this.buffer.append(b, off, len);
162         }
163     }
164 
165     public void write(final byte[] b) throws IOException {
166         if (b == null) {
167             return;
168         }
169         write(b, 0, b.length);
170     }
171 
172     public void write(final int b) throws IOException {
173         if (this.fragementSizeHint > 0) {
174             if (this.buffer.isFull()) {
175                 flushBuffer();
176             }
177             this.buffer.append(b);
178         } else {
179             flushBuffer();
180             this.outstream.write(b);
181         }
182     }
183 
184     /**
185      * Writes characters from the specified string followed by a line delimiter
186      * to this session buffer.
187      * <p>
188      * This method uses CR-LF as a line delimiter.
189      *
190      * @param      s   the line.
191      * @exception  IOException  if an I/O error occurs.
192      */
193     public void writeLine(final String s) throws IOException {
194         if (s == null) {
195             return;
196         }
197         if (s.length() > 0) {
198             if (this.encoder == null) {
199                 for (int i = 0; i < s.length(); i++) {
200                     write(s.charAt(i));
201                 }
202             } else {
203                 final CharBuffer cbuf = CharBuffer.wrap(s);
204                 writeEncoded(cbuf);
205             }
206         }
207         write(CRLF);
208     }
209 
210     /**
211      * Writes characters from the specified char array followed by a line
212      * delimiter to this session buffer.
213      * <p>
214      * This method uses CR-LF as a line delimiter.
215      *
216      * @param      charbuffer the buffer containing chars of the line.
217      * @exception  IOException  if an I/O error occurs.
218      */
219     public void writeLine(final CharArrayBuffer charbuffer) throws IOException {
220         if (charbuffer == null) {
221             return;
222         }
223         if (this.encoder == null) {
224             int off = 0;
225             int remaining = charbuffer.length();
226             while (remaining > 0) {
227                 int chunk = this.buffer.capacity() - this.buffer.length();
228                 chunk = Math.min(chunk, remaining);
229                 if (chunk > 0) {
230                     this.buffer.append(charbuffer, off, chunk);
231                 }
232                 if (this.buffer.isFull()) {
233                     flushBuffer();
234                 }
235                 off += chunk;
236                 remaining -= chunk;
237             }
238         } else {
239             final CharBuffer cbuf = CharBuffer.wrap(charbuffer.buffer(), 0, charbuffer.length());
240             writeEncoded(cbuf);
241         }
242         write(CRLF);
243     }
244 
245     private void writeEncoded(final CharBuffer cbuf) throws IOException {
246         if (!cbuf.hasRemaining()) {
247             return;
248         }
249         if (this.bbuf == null) {
250             this.bbuf = ByteBuffer.allocate(1024);
251         }
252         this.encoder.reset();
253         while (cbuf.hasRemaining()) {
254             final CoderResult result = this.encoder.encode(cbuf, this.bbuf, true);
255             handleEncodingResult(result);
256         }
257         final CoderResult result = this.encoder.flush(this.bbuf);
258         handleEncodingResult(result);
259         this.bbuf.clear();
260     }
261 
262     private void handleEncodingResult(final CoderResult result) throws IOException {
263         if (result.isError()) {
264             result.throwException();
265         }
266         this.bbuf.flip();
267         while (this.bbuf.hasRemaining()) {
268             write(this.bbuf.get());
269         }
270         this.bbuf.compact();
271     }
272 
273     public HttpTransportMetrics getMetrics() {
274         return this.metrics;
275     }
276 
277 }