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 ch.boye.httpclientandroidlib.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 ch.boye.httpclientandroidlib.annotation.NotThreadSafe; 38 import ch.boye.httpclientandroidlib.io.BufferInfo; 39 import ch.boye.httpclientandroidlib.io.HttpTransportMetrics; 40 import ch.boye.httpclientandroidlib.io.SessionOutputBuffer; 41 import ch.boye.httpclientandroidlib.protocol.HTTP; 42 import ch.boye.httpclientandroidlib.util.Args; 43 import ch.boye.httpclientandroidlib.util.Asserts; 44 import ch.boye.httpclientandroidlib.util.ByteArrayBuffer; 45 import ch.boye.httpclientandroidlib.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 */ SessionOutputBufferImpl( final HttpTransportMetricsImpl metrics, final int buffersize, final int fragementSizeHint, final CharsetEncoder charencoder)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 SessionOutputBufferImpl( final HttpTransportMetricsImpl metrics, final int buffersize)95 public SessionOutputBufferImpl( 96 final HttpTransportMetricsImpl metrics, 97 final int buffersize) { 98 this(metrics, buffersize, buffersize, null); 99 } 100 bind(final OutputStream outstream)101 public void bind(final OutputStream outstream) { 102 this.outstream = outstream; 103 } 104 isBound()105 public boolean isBound() { 106 return this.outstream != null; 107 } 108 capacity()109 public int capacity() { 110 return this.buffer.capacity(); 111 } 112 length()113 public int length() { 114 return this.buffer.length(); 115 } 116 available()117 public int available() { 118 return capacity() - length(); 119 } 120 streamWrite(final byte[] b, final int off, final int len)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 flushStream()126 private void flushStream() throws IOException { 127 if (this.outstream != null) { 128 this.outstream.flush(); 129 } 130 } 131 flushBuffer()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 flush()141 public void flush() throws IOException { 142 flushBuffer(); 143 flushStream(); 144 } 145 write(final byte[] b, final int off, final int len)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 write(final byte[] b)171 public void write(final byte[] b) throws IOException { 172 if (b == null) { 173 return; 174 } 175 write(b, 0, b.length); 176 } 177 write(final int b)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 */ writeLine(final String s)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 */ writeLine(final CharArrayBuffer charbuffer)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 writeEncoded(final CharBuffer cbuf)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 handleEncodingResult(final CoderResult result)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 getMetrics()279 public HttpTransportMetrics getMetrics() { 280 return this.metrics; 281 } 282 283 } 284