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     @Override
137     public int capacity() {
138         return this.buffer.capacity();
139     }
140 
141     /**
142      * @since 4.1
143      */
144     @Override
145     public int length() {
146         return this.buffer.length();
147     }
148 
149     /**
150      * @since 4.1
151      */
152     @Override
153     public int available() {
154         return capacity() - length();
155     }
156 
157     protected void flushBuffer() throws IOException {
158         final int len = this.buffer.length();
159         if (len > 0) {
160             this.outStream.write(this.buffer.buffer(), 0, len);
161             this.buffer.clear();
162             this.metrics.incrementBytesTransferred(len);
163         }
164     }
165 
166     @Override
167     public void flush() throws IOException {
168         flushBuffer();
169         this.outStream.flush();
170     }
171 
172     @Override
173     public void write(final byte[] b, final int off, final int len) throws IOException {
174         if (b == null) {
175             return;
176         }
177         // Do not want to buffer large-ish chunks
178         // if the byte array is larger then MIN_CHUNK_LIMIT
179         // write it directly to the output stream
180         if (len > this.minChunkLimit || len > this.buffer.capacity()) {
181             // flush the buffer
182             flushBuffer();
183             // write directly to the out stream
184             this.outStream.write(b, off, len);
185             this.metrics.incrementBytesTransferred(len);
186         } else {
187             // Do not let the buffer grow unnecessarily
188             final int freecapacity = this.buffer.capacity() - this.buffer.length();
189             if (len > freecapacity) {
190                 // flush the buffer
191                 flushBuffer();
192             }
193             // buffer
194             this.buffer.append(b, off, len);
195         }
196     }
197 
198     @Override
199     public void write(final byte[] b) throws IOException {
200         if (b == null) {
201             return;
202         }
203         write(b, 0, b.length);
204     }
205 
206     @Override
207     public void write(final int b) throws IOException {
208         if (this.buffer.isFull()) {
209             flushBuffer();
210         }
211         this.buffer.append(b);
212     }
213 
214     /**
215      * Writes characters from the specified string followed by a line delimiter
216      * to this session buffer.
217      * <p>
218      * This method uses CR-LF as a line delimiter.
219      *
220      * @param      s   the line.
221      * @throws  IOException  if an I/O error occurs.
222      */
223     @Override
224     public void writeLine(final String s) throws IOException {
225         if (s == null) {
226             return;
227         }
228         if (s.length() > 0) {
229             if (this.ascii) {
230                 for (int i = 0; i < s.length(); i++) {
231                     write(s.charAt(i));
232                 }
233             } else {
234                 final CharBuffer cbuf = CharBuffer.wrap(s);
235                 writeEncoded(cbuf);
236             }
237         }
238         write(CRLF);
239     }
240 
241     /**
242      * Writes characters from the specified char array followed by a line
243      * delimiter to this session buffer.
244      * <p>
245      * This method uses CR-LF as a line delimiter.
246      *
247      * @param      charbuffer the buffer containing chars of the line.
248      * @throws  IOException  if an I/O error occurs.
249      */
250     @Override
251     public void writeLine(final CharArrayBuffer charbuffer) throws IOException {
252         if (charbuffer == null) {
253             return;
254         }
255         if (this.ascii) {
256             int off = 0;
257             int remaining = charbuffer.length();
258             while (remaining > 0) {
259                 int chunk = this.buffer.capacity() - this.buffer.length();
260                 chunk = Math.min(chunk, remaining);
261                 if (chunk > 0) {
262                     this.buffer.append(charbuffer, off, chunk);
263                 }
264                 if (this.buffer.isFull()) {
265                     flushBuffer();
266                 }
267                 off += chunk;
268                 remaining -= chunk;
269             }
270         } else {
271             final CharBuffer cbuf = CharBuffer.wrap(charbuffer.buffer(), 0, charbuffer.length());
272             writeEncoded(cbuf);
273         }
274         write(CRLF);
275     }
276 
277     private void writeEncoded(final CharBuffer cbuf) throws IOException {
278         if (!cbuf.hasRemaining()) {
279             return;
280         }
281         if (this.encoder == null) {
282             this.encoder = this.charset.newEncoder();
283             this.encoder.onMalformedInput(this.onMalformedCharAction);
284             this.encoder.onUnmappableCharacter(this.onUnmappableCharAction);
285         }
286         if (this.bbuf == null) {
287             this.bbuf = ByteBuffer.allocate(1024);
288         }
289         this.encoder.reset();
290         while (cbuf.hasRemaining()) {
291             final CoderResult result = this.encoder.encode(cbuf, this.bbuf, true);
292             handleEncodingResult(result);
293         }
294         final CoderResult result = this.encoder.flush(this.bbuf);
295         handleEncodingResult(result);
296         this.bbuf.clear();
297     }
298 
299     private void handleEncodingResult(final CoderResult result) throws IOException {
300         if (result.isError()) {
301             result.throwException();
302         }
303         this.bbuf.flip();
304         while (this.bbuf.hasRemaining()) {
305             write(this.bbuf.get());
306         }
307         this.bbuf.compact();
308     }
309 
310     @Override
311     public HttpTransportMetrics getMetrics() {
312         return this.metrics;
313     }
314 
315 }