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.nio.reactor; 029 030import java.io.IOException; 031import java.nio.ByteBuffer; 032import java.nio.CharBuffer; 033import java.nio.channels.ReadableByteChannel; 034import java.nio.channels.WritableByteChannel; 035import java.nio.charset.CharacterCodingException; 036import java.nio.charset.Charset; 037import java.nio.charset.CharsetEncoder; 038import java.nio.charset.CoderResult; 039import java.nio.charset.CodingErrorAction; 040 041import org.apache.http.nio.reactor.SessionOutputBuffer; 042import org.apache.http.nio.util.ByteBufferAllocator; 043import org.apache.http.nio.util.ExpandableBuffer; 044import org.apache.http.nio.util.HeapByteBufferAllocator; 045import org.apache.http.params.CoreProtocolPNames; 046import org.apache.http.params.HttpParams; 047import org.apache.http.protocol.HTTP; 048import org.apache.http.util.Args; 049import org.apache.http.util.CharArrayBuffer; 050import org.apache.http.util.CharsetUtils; 051 052/** 053 * Default implementation of {@link SessionOutputBuffer} based on 054 * the {@link ExpandableBuffer} class. 055 * 056 * @since 4.0 057 */ 058@SuppressWarnings("deprecation") 059public class SessionOutputBufferImpl extends ExpandableBuffer implements SessionOutputBuffer { 060 061 private static final byte[] CRLF = new byte[] {HTTP.CR, HTTP.LF}; 062 063 private final CharsetEncoder charencoder; 064 private final int lineBuffersize; 065 066 private CharBuffer charbuffer; 067 068 /** 069 * Creates SessionOutputBufferImpl instance. 070 * 071 * @param buffersize input buffer size 072 * @param lineBuffersize buffer size for line operations. Has effect only if 073 * {@code charencoder} is not {@code null}. 074 * @param charencoder charencoder to be used for encoding HTTP protocol elements. 075 * If {@code null} simple type cast will be used for char to byte conversion. 076 * @param allocator memory allocator. 077 * If {@code null} {@link HeapByteBufferAllocator#INSTANCE} will be used. 078 * 079 * @since 4.3 080 */ 081 public SessionOutputBufferImpl( 082 final int buffersize, 083 final int lineBuffersize, 084 final CharsetEncoder charencoder, 085 final ByteBufferAllocator allocator) { 086 super(buffersize, allocator != null ? allocator : HeapByteBufferAllocator.INSTANCE); 087 this.lineBuffersize = Args.positive(lineBuffersize, "Line buffer size"); 088 this.charencoder = charencoder; 089 } 090 091 /** 092 * @deprecated (4.3) use 093 * {@link SessionOutputBufferImpl#SessionOutputBufferImpl(int, int, CharsetEncoder, 094 * ByteBufferAllocator)} 095 */ 096 @Deprecated 097 public SessionOutputBufferImpl( 098 final int buffersize, 099 final int lineBuffersize, 100 final ByteBufferAllocator allocator, 101 final HttpParams params) { 102 super(buffersize, allocator); 103 this.lineBuffersize = Args.positive(lineBuffersize, "Line buffer size"); 104 final String charsetName = (String) params.getParameter(CoreProtocolPNames.HTTP_ELEMENT_CHARSET); 105 final Charset charset = CharsetUtils.lookup(charsetName); 106 if (charset != null) { 107 this.charencoder = charset.newEncoder(); 108 final CodingErrorAction a1 = (CodingErrorAction) params.getParameter( 109 CoreProtocolPNames.HTTP_MALFORMED_INPUT_ACTION); 110 this.charencoder.onMalformedInput(a1 != null ? a1 : CodingErrorAction.REPORT); 111 final CodingErrorAction a2 = (CodingErrorAction) params.getParameter( 112 CoreProtocolPNames.HTTP_UNMAPPABLE_INPUT_ACTION); 113 this.charencoder.onUnmappableCharacter(a2 != null? a2 : CodingErrorAction.REPORT); 114 } else { 115 this.charencoder = null; 116 } 117 } 118 119 /** 120 * @deprecated (4.3) use 121 * {@link SessionOutputBufferImpl#SessionOutputBufferImpl(int, int, Charset)} 122 */ 123 @Deprecated 124 public SessionOutputBufferImpl( 125 final int buffersize, 126 final int linebuffersize, 127 final HttpParams params) { 128 this(buffersize, linebuffersize, HeapByteBufferAllocator.INSTANCE, params); 129 } 130 131 /** 132 * @since 4.3 133 */ 134 public SessionOutputBufferImpl(final int buffersize) { 135 this(buffersize, 256, null, HeapByteBufferAllocator.INSTANCE); 136 } 137 138 /** 139 * @since 4.3 140 */ 141 public SessionOutputBufferImpl( 142 final int buffersize, 143 final int linebuffersize, 144 final Charset charset) { 145 this(buffersize, linebuffersize, 146 charset != null ? charset.newEncoder() : null, HeapByteBufferAllocator.INSTANCE); 147 } 148 149 /** 150 * @since 4.3 151 */ 152 public SessionOutputBufferImpl( 153 final int buffersize, 154 final int linebuffersize) { 155 this(buffersize, linebuffersize, null, HeapByteBufferAllocator.INSTANCE); 156 } 157 158 public void reset(final HttpParams params) { 159 clear(); 160 } 161 162 @Override 163 public int flush(final WritableByteChannel channel) throws IOException { 164 Args.notNull(channel, "Channel"); 165 setOutputMode(); 166 return channel.write(this.buffer); 167 } 168 169 @Override 170 public void write(final ByteBuffer src) { 171 if (src == null) { 172 return; 173 } 174 setInputMode(); 175 final int requiredCapacity = this.buffer.position() + src.remaining(); 176 ensureCapacity(requiredCapacity); 177 this.buffer.put(src); 178 } 179 180 @Override 181 public void write(final ReadableByteChannel src) throws IOException { 182 if (src == null) { 183 return; 184 } 185 setInputMode(); 186 src.read(this.buffer); 187 } 188 189 private void write(final byte[] b) { 190 if (b == null) { 191 return; 192 } 193 setInputMode(); 194 final int off = 0; 195 final int len = b.length; 196 final int requiredCapacity = this.buffer.position() + len; 197 ensureCapacity(requiredCapacity); 198 this.buffer.put(b, off, len); 199 } 200 201 private void writeCRLF() { 202 write(CRLF); 203 } 204 205 @Override 206 public void writeLine(final CharArrayBuffer linebuffer) throws CharacterCodingException { 207 if (linebuffer == null) { 208 return; 209 } 210 setInputMode(); 211 // Do not bother if the buffer is empty 212 if (linebuffer.length() > 0 ) { 213 if (this.charencoder == null) { 214 final int requiredCapacity = this.buffer.position() + linebuffer.length(); 215 ensureCapacity(requiredCapacity); 216 if (this.buffer.hasArray()) { 217 final byte[] b = this.buffer.array(); 218 final int len = linebuffer.length(); 219 final int off = this.buffer.position(); 220 for (int i = 0; i < len; i++) { 221 b[off + i] = (byte) linebuffer.charAt(i); 222 } 223 this.buffer.position(off + len); 224 } else { 225 for (int i = 0; i < linebuffer.length(); i++) { 226 this.buffer.put((byte) linebuffer.charAt(i)); 227 } 228 } 229 } else { 230 if (this.charbuffer == null) { 231 this.charbuffer = CharBuffer.allocate(this.lineBuffersize); 232 } 233 this.charencoder.reset(); 234 // transfer the string in small chunks 235 int remaining = linebuffer.length(); 236 int offset = 0; 237 while (remaining > 0) { 238 int l = this.charbuffer.remaining(); 239 boolean eol = false; 240 if (remaining <= l) { 241 l = remaining; 242 // terminate the encoding process 243 eol = true; 244 } 245 this.charbuffer.put(linebuffer.buffer(), offset, l); 246 this.charbuffer.flip(); 247 248 boolean retry = true; 249 while (retry) { 250 final CoderResult result = this.charencoder.encode(this.charbuffer, this.buffer, eol); 251 if (result.isError()) { 252 result.throwException(); 253 } 254 if (result.isOverflow()) { 255 expand(); 256 } 257 retry = !result.isUnderflow(); 258 } 259 this.charbuffer.compact(); 260 offset += l; 261 remaining -= l; 262 } 263 // flush the encoder 264 boolean retry = true; 265 while (retry) { 266 final CoderResult result = this.charencoder.flush(this.buffer); 267 if (result.isError()) { 268 result.throwException(); 269 } 270 if (result.isOverflow()) { 271 expand(); 272 } 273 retry = !result.isUnderflow(); 274 } 275 } 276 } 277 writeCRLF(); 278 } 279 280 @Override 281 public void writeLine(final String s) throws IOException { 282 if (s == null) { 283 return; 284 } 285 if (s.length() > 0) { 286 final CharArrayBuffer tmp = new CharArrayBuffer(s.length()); 287 tmp.append(s); 288 writeLine(tmp); 289 } else { 290 write(CRLF); 291 } 292 } 293 294}