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