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