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