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