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.hc.core5.http.impl.nio;
29  
30  import java.io.IOException;
31  import java.nio.ByteBuffer;
32  import java.nio.CharBuffer;
33  import java.nio.channels.ReadableByteChannel;
34  import java.nio.channels.WritableByteChannel;
35  import java.nio.charset.CharacterCodingException;
36  import java.nio.charset.Charset;
37  import java.nio.charset.CharsetEncoder;
38  import java.nio.charset.CoderResult;
39  
40  import org.apache.hc.core5.http.Chars;
41  import org.apache.hc.core5.http.nio.SessionOutputBuffer;
42  import org.apache.hc.core5.util.Args;
43  import org.apache.hc.core5.util.CharArrayBuffer;
44  
45  /**
46   * Default implementation of {@link SessionOutputBuffer} based on
47   * the {@link ExpandableBuffer} class.
48   *
49   * @since 4.0
50   */
51  public class SessionOutputBufferImpl extends ExpandableBuffer implements SessionOutputBuffer {
52  
53      private static final byte[] CRLF = new byte[] {Chars.CR, Chars.LF};
54  
55      private final CharsetEncoder charencoder;
56      private final int lineBuffersize;
57  
58      private CharBuffer charbuffer;
59  
60      /**
61       *  Creates SessionOutputBufferImpl instance.
62       *
63       * @param buffersize input buffer size
64       * @param lineBuffersize buffer size for line operations. Has effect only if
65       *   {@code charencoder} is not {@code null}.
66       * @param charencoder charencoder to be used for encoding HTTP protocol elements.
67       *   If {@code null} simple type cast will be used for char to byte conversion.
68       *
69       * @since 4.3
70       */
71      public SessionOutputBufferImpl(
72              final int buffersize,
73              final int lineBuffersize,
74              final CharsetEncoder charencoder) {
75          super(buffersize);
76          this.lineBuffersize = Args.positive(lineBuffersize, "Line buffer size");
77          this.charencoder = charencoder;
78      }
79  
80      /**
81       * @since 4.3
82       */
83      public SessionOutputBufferImpl(
84              final int buffersize,
85              final int linebuffersize,
86              final Charset charset) {
87          this(buffersize, linebuffersize, charset != null ? charset.newEncoder() : null);
88      }
89  
90      /**
91       * @since 4.3
92       */
93      public SessionOutputBufferImpl(
94              final int buffersize,
95              final int linebuffersize) {
96          this(buffersize, linebuffersize, (CharsetEncoder) null);
97      }
98  
99      /**
100      * @since 4.3
101      */
102     public SessionOutputBufferImpl(final int buffersize) {
103         this(buffersize, 256);
104     }
105 
106     @Override
107     public int flush(final WritableByteChannel channel) throws IOException {
108         Args.notNull(channel, "Channel");
109         setOutputMode();
110         return channel.write(buffer());
111     }
112 
113     @Override
114     public void write(final ByteBuffer src) {
115         if (src == null) {
116             return;
117         }
118         setInputMode();
119         final int requiredCapacity = buffer().position() + src.remaining();
120         ensureCapacity(requiredCapacity);
121         buffer().put(src);
122     }
123 
124     @Override
125     public void write(final ReadableByteChannel src) throws IOException {
126         if (src == null) {
127             return;
128         }
129         setInputMode();
130         src.read(buffer());
131     }
132 
133     private void write(final byte[] b) {
134         if (b == null) {
135             return;
136         }
137         setInputMode();
138         final int off = 0;
139         final int len = b.length;
140         final int requiredCapacity = buffer().position() + len;
141         ensureCapacity(requiredCapacity);
142         buffer().put(b, off, len);
143     }
144 
145     private void writeCRLF() {
146         write(CRLF);
147     }
148 
149     @Override
150     public void writeLine(final CharArrayBuffer linebuffer) throws CharacterCodingException {
151         if (linebuffer == null) {
152             return;
153         }
154         setInputMode();
155         // Do not bother if the buffer is empty
156         if (linebuffer.length() > 0 ) {
157             if (this.charencoder == null) {
158                 final int requiredCapacity = buffer().position() + linebuffer.length();
159                 ensureCapacity(requiredCapacity);
160                 if (buffer().hasArray()) {
161                     final byte[] b = buffer().array();
162                     final int len = linebuffer.length();
163                     final int off = buffer().position();
164                     for (int i = 0; i < len; i++) {
165                         b[off + i]  = (byte) linebuffer.charAt(i);
166                     }
167                     buffer().position(off + len);
168                 } else {
169                     for (int i = 0; i < linebuffer.length(); i++) {
170                         buffer().put((byte) linebuffer.charAt(i));
171                     }
172                 }
173             } else {
174                 if (this.charbuffer == null) {
175                     this.charbuffer = CharBuffer.allocate(this.lineBuffersize);
176                 }
177                 this.charencoder.reset();
178                 // transfer the string in small chunks
179                 int remaining = linebuffer.length();
180                 int offset = 0;
181                 while (remaining > 0) {
182                     int l = this.charbuffer.remaining();
183                     boolean eol = false;
184                     if (remaining <= l) {
185                         l = remaining;
186                         // terminate the encoding process
187                         eol = true;
188                     }
189                     this.charbuffer.put(linebuffer.array(), offset, l);
190                     this.charbuffer.flip();
191 
192                     boolean retry = true;
193                     while (retry) {
194                         final CoderResult result = this.charencoder.encode(this.charbuffer, buffer(), eol);
195                         if (result.isError()) {
196                             result.throwException();
197                         }
198                         if (result.isOverflow()) {
199                             expand();
200                         }
201                         retry = !result.isUnderflow();
202                     }
203                     this.charbuffer.compact();
204                     offset += l;
205                     remaining -= l;
206                 }
207                 // flush the encoder
208                 boolean retry = true;
209                 while (retry) {
210                     final CoderResult result = this.charencoder.flush(buffer());
211                     if (result.isError()) {
212                         result.throwException();
213                     }
214                     if (result.isOverflow()) {
215                         expand();
216                     }
217                     retry = !result.isUnderflow();
218                 }
219             }
220         }
221         writeCRLF();
222     }
223 
224 }