001/* 002 * ==================================================================== 003 * Licensed to the Apache Software Foundation (ASF) under one 004 * or more contributor license agreements. See the NOTICE file 005 * distributed with this work for additional information 006 * regarding copyright ownership. The ASF licenses this file 007 * to you under the Apache License, Version 2.0 (the 008 * "License"); you may not use this file except in compliance 009 * with the License. You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, 014 * software distributed under the License is distributed on an 015 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 016 * KIND, either express or implied. See the License for the 017 * specific language governing permissions and limitations 018 * under the License. 019 * ==================================================================== 020 * 021 * This software consists of voluntary contributions made by many 022 * individuals on behalf of the Apache Software Foundation. For more 023 * information on the Apache Software Foundation, please see 024 * <http://www.apache.org/>. 025 * 026 */ 027 028package org.apache.http.impl.io; 029 030import java.io.IOException; 031import java.io.OutputStream; 032import java.nio.ByteBuffer; 033import java.nio.CharBuffer; 034import java.nio.charset.CharsetEncoder; 035import java.nio.charset.CoderResult; 036 037import org.apache.http.io.BufferInfo; 038import org.apache.http.io.HttpTransportMetrics; 039import org.apache.http.io.SessionOutputBuffer; 040import org.apache.http.protocol.HTTP; 041import org.apache.http.util.Args; 042import org.apache.http.util.Asserts; 043import org.apache.http.util.ByteArrayBuffer; 044import org.apache.http.util.CharArrayBuffer; 045 046/** 047 * Abstract base class for session output buffers that stream data to 048 * an arbitrary {@link OutputStream}. This class buffers small chunks of 049 * output data in an internal byte array for optimal output performance. 050 * <p> 051 * {@link #writeLine(CharArrayBuffer)} and {@link #writeLine(String)} methods 052 * of this class use CR-LF as a line delimiter. 053 * 054 * @since 4.3 055 */ 056public class SessionOutputBufferImpl implements SessionOutputBuffer, BufferInfo { 057 058 private static final byte[] CRLF = new byte[] {HTTP.CR, HTTP.LF}; 059 060 private final HttpTransportMetricsImpl metrics; 061 private final ByteArrayBuffer buffer; 062 private final int fragementSizeHint; 063 private final CharsetEncoder encoder; 064 065 private OutputStream outstream; 066 private ByteBuffer bbuf; 067 068 /** 069 * Creates new instance of SessionOutputBufferImpl. 070 * 071 * @param metrics HTTP transport metrics. 072 * @param buffersize buffer size. Must be a positive number. 073 * @param fragementSizeHint fragment size hint defining a minimal size of a fragment 074 * that should be written out directly to the socket bypassing the session buffer. 075 * Value {@code 0} disables fragment buffering. 076 * @param charencoder charencoder to be used for encoding HTTP protocol elements. 077 * If {@code null} simple type cast will be used for char to byte conversion. 078 */ 079 public SessionOutputBufferImpl( 080 final HttpTransportMetricsImpl metrics, 081 final int buffersize, 082 final int fragementSizeHint, 083 final CharsetEncoder charencoder) { 084 super(); 085 Args.positive(buffersize, "Buffer size"); 086 Args.notNull(metrics, "HTTP transport metrcis"); 087 this.metrics = metrics; 088 this.buffer = new ByteArrayBuffer(buffersize); 089 this.fragementSizeHint = fragementSizeHint >= 0 ? fragementSizeHint : 0; 090 this.encoder = charencoder; 091 } 092 093 public SessionOutputBufferImpl( 094 final HttpTransportMetricsImpl metrics, 095 final int buffersize) { 096 this(metrics, buffersize, buffersize, null); 097 } 098 099 public void bind(final OutputStream outstream) { 100 this.outstream = outstream; 101 } 102 103 public boolean isBound() { 104 return this.outstream != null; 105 } 106 107 @Override 108 public int capacity() { 109 return this.buffer.capacity(); 110 } 111 112 @Override 113 public int length() { 114 return this.buffer.length(); 115 } 116 117 @Override 118 public int available() { 119 return capacity() - length(); 120 } 121 122 private void streamWrite(final byte[] b, final int off, final int len) throws IOException { 123 Asserts.notNull(outstream, "Output stream"); 124 this.outstream.write(b, off, len); 125 } 126 127 private void flushStream() throws IOException { 128 if (this.outstream != null) { 129 this.outstream.flush(); 130 } 131 } 132 133 private void flushBuffer() throws IOException { 134 final int len = this.buffer.length(); 135 if (len > 0) { 136 streamWrite(this.buffer.buffer(), 0, len); 137 this.buffer.clear(); 138 this.metrics.incrementBytesTransferred(len); 139 } 140 } 141 142 @Override 143 public void flush() throws IOException { 144 flushBuffer(); 145 flushStream(); 146 } 147 148 @Override 149 public void write(final byte[] b, final int off, final int len) throws IOException { 150 if (b == null) { 151 return; 152 } 153 // Do not want to buffer large-ish chunks 154 // if the byte array is larger then MIN_CHUNK_LIMIT 155 // write it directly to the output stream 156 if (len > this.fragementSizeHint || len > this.buffer.capacity()) { 157 // flush the buffer 158 flushBuffer(); 159 // write directly to the out stream 160 streamWrite(b, off, len); 161 this.metrics.incrementBytesTransferred(len); 162 } else { 163 // Do not let the buffer grow unnecessarily 164 final int freecapacity = this.buffer.capacity() - this.buffer.length(); 165 if (len > freecapacity) { 166 // flush the buffer 167 flushBuffer(); 168 } 169 // buffer 170 this.buffer.append(b, off, len); 171 } 172 } 173 174 @Override 175 public void write(final byte[] b) throws IOException { 176 if (b == null) { 177 return; 178 } 179 write(b, 0, b.length); 180 } 181 182 @Override 183 public void write(final int b) throws IOException { 184 if (this.fragementSizeHint > 0) { 185 if (this.buffer.isFull()) { 186 flushBuffer(); 187 } 188 this.buffer.append(b); 189 } else { 190 flushBuffer(); 191 this.outstream.write(b); 192 } 193 } 194 195 /** 196 * Writes characters from the specified string followed by a line delimiter 197 * to this session buffer. 198 * <p> 199 * This method uses CR-LF as a line delimiter. 200 * 201 * @param s the line. 202 * @throws IOException if an I/O error occurs. 203 */ 204 @Override 205 public void writeLine(final String s) throws IOException { 206 if (s == null) { 207 return; 208 } 209 if (s.length() > 0) { 210 if (this.encoder == null) { 211 for (int i = 0; i < s.length(); i++) { 212 write(s.charAt(i)); 213 } 214 } else { 215 final CharBuffer cbuf = CharBuffer.wrap(s); 216 writeEncoded(cbuf); 217 } 218 } 219 write(CRLF); 220 } 221 222 /** 223 * Writes characters from the specified char array followed by a line 224 * delimiter to this session buffer. 225 * <p> 226 * This method uses CR-LF as a line delimiter. 227 * 228 * @param charbuffer the buffer containing chars of the line. 229 * @throws IOException if an I/O error occurs. 230 */ 231 @Override 232 public void writeLine(final CharArrayBuffer charbuffer) throws IOException { 233 if (charbuffer == null) { 234 return; 235 } 236 if (this.encoder == null) { 237 int off = 0; 238 int remaining = charbuffer.length(); 239 while (remaining > 0) { 240 int chunk = this.buffer.capacity() - this.buffer.length(); 241 chunk = Math.min(chunk, remaining); 242 if (chunk > 0) { 243 this.buffer.append(charbuffer, off, chunk); 244 } 245 if (this.buffer.isFull()) { 246 flushBuffer(); 247 } 248 off += chunk; 249 remaining -= chunk; 250 } 251 } else { 252 final CharBuffer cbuf = CharBuffer.wrap(charbuffer.buffer(), 0, charbuffer.length()); 253 writeEncoded(cbuf); 254 } 255 write(CRLF); 256 } 257 258 private void writeEncoded(final CharBuffer cbuf) throws IOException { 259 if (!cbuf.hasRemaining()) { 260 return; 261 } 262 if (this.bbuf == null) { 263 this.bbuf = ByteBuffer.allocate(1024); 264 } 265 this.encoder.reset(); 266 while (cbuf.hasRemaining()) { 267 final CoderResult result = this.encoder.encode(cbuf, this.bbuf, true); 268 handleEncodingResult(result); 269 } 270 final CoderResult result = this.encoder.flush(this.bbuf); 271 handleEncodingResult(result); 272 this.bbuf.clear(); 273 } 274 275 private void handleEncodingResult(final CoderResult result) throws IOException { 276 if (result.isError()) { 277 result.throwException(); 278 } 279 this.bbuf.flip(); 280 while (this.bbuf.hasRemaining()) { 281 write(this.bbuf.get()); 282 } 283 this.bbuf.compact(); 284 } 285 286 @Override 287 public HttpTransportMetrics getMetrics() { 288 return this.metrics; 289 } 290 291}