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