blob: 338171323e8af36509438037d10973d76a301fe2 [file] [log] [blame]
/*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
*/
package org.apache.hc.core5.http.impl.nio;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.CodingErrorAction;
import java.nio.charset.StandardCharsets;
import org.apache.hc.core5.http.MessageConstraintException;
import org.apache.hc.core5.http.nio.SessionInputBuffer;
import org.apache.hc.core5.http.nio.SessionOutputBuffer;
import org.apache.hc.core5.util.CharArrayBuffer;
import org.junit.Assert;
import org.junit.Test;
/**
* Simple tests for {@link SessionInputBuffer} and {@link SessionOutputBuffer}.
*/
public class TestSessionInOutBuffers {
private static WritableByteChannel newChannel(final ByteArrayOutputStream outStream) {
return Channels.newChannel(outStream);
}
private static ReadableByteChannel newChannel(final byte[] bytes) {
return Channels.newChannel(new ByteArrayInputStream(bytes));
}
private static ReadableByteChannel newChannel(final String s, final Charset charset) {
return Channels.newChannel(new ByteArrayInputStream(s.getBytes(charset)));
}
private static ReadableByteChannel newChannel(final String s) {
return newChannel(s, StandardCharsets.US_ASCII);
}
@Test
public void testReadLineChunks() throws Exception {
final SessionInputBuffer inbuf = new SessionInputBufferImpl(16, 16);
ReadableByteChannel channel = newChannel("One\r\nTwo\r\nThree");
inbuf.fill(channel);
final CharArrayBuffer line = new CharArrayBuffer(64);
line.clear();
Assert.assertTrue(inbuf.readLine(line, false));
Assert.assertEquals("One", line.toString());
line.clear();
Assert.assertTrue(inbuf.readLine(line, false));
Assert.assertEquals("Two", line.toString());
line.clear();
Assert.assertFalse(inbuf.readLine(line, false));
channel = newChannel("\r\nFour");
inbuf.fill(channel);
line.clear();
Assert.assertTrue(inbuf.readLine(line, false));
Assert.assertEquals("Three", line.toString());
inbuf.fill(channel);
line.clear();
Assert.assertTrue(inbuf.readLine(line, true));
Assert.assertEquals("Four", line.toString());
line.clear();
Assert.assertFalse(inbuf.readLine(line, true));
}
@Test
public void testLineLimit() throws Exception {
final String s = "LoooooooooooooooooooooooooOOOOOOOOOOOOOOOOOOoooooooooooooooooooooong line\r\n";
final CharArrayBuffer line = new CharArrayBuffer(64);
final SessionInputBuffer inbuf1 = new SessionInputBufferImpl(128, 128);
final ReadableByteChannel channel1 = newChannel(s);
inbuf1.fill(channel1);
Assert.assertTrue(inbuf1.readLine(line, false));
line.clear();
final SessionInputBuffer inbuf2 = new SessionInputBufferImpl(128, 128, 10);
final ReadableByteChannel channel2 = newChannel(s);
inbuf2.fill(channel2);
try {
inbuf2.readLine(line, false);
Assert.fail("MessageConstraintException expected");
} catch (final MessageConstraintException ex) {
}
}
@Test
public void testLineLimitBufferFull() throws Exception {
final String s = "LoooooooooooooooooooooooooOOOOOOOOOOOOOOOOOOoooooooooooooooooooooong line\r\n";
final CharArrayBuffer line = new CharArrayBuffer(64);
final SessionInputBuffer inbuf1 = new SessionInputBufferImpl(32, 32);
final ReadableByteChannel channel1 = newChannel(s);
inbuf1.fill(channel1);
Assert.assertFalse(inbuf1.readLine(line, false));
line.clear();
final SessionInputBuffer inbuf2 = new SessionInputBufferImpl(32, 32,10);
final ReadableByteChannel channel2 = newChannel(s);
inbuf2.fill(channel2);
try {
inbuf2.readLine(line, false);
Assert.fail("MessageConstraintException expected");
} catch (final MessageConstraintException ex) {
}
}
@Test
public void testWriteLineChunks() throws Exception {
final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(16, 16);
final SessionInputBuffer inbuf = new SessionInputBufferImpl(16, 16, 0);
ReadableByteChannel inChannel = newChannel("One\r\nTwo\r\nThree");
inbuf.fill(inChannel);
final CharArrayBuffer line = new CharArrayBuffer(64);
line.clear();
Assert.assertTrue(inbuf.readLine(line, false));
Assert.assertEquals("One", line.toString());
outbuf.writeLine(line);
line.clear();
Assert.assertTrue(inbuf.readLine(line, false));
Assert.assertEquals("Two", line.toString());
outbuf.writeLine(line);
line.clear();
Assert.assertFalse(inbuf.readLine(line, false));
inChannel = newChannel("\r\nFour");
inbuf.fill(inChannel);
line.clear();
Assert.assertTrue(inbuf.readLine(line, false));
Assert.assertEquals("Three", line.toString());
outbuf.writeLine(line);
inbuf.fill(inChannel);
line.clear();
Assert.assertTrue(inbuf.readLine(line, true));
Assert.assertEquals("Four", line.toString());
outbuf.writeLine(line);
line.clear();
Assert.assertFalse(inbuf.readLine(line, true));
final ByteArrayOutputStream outStream = new ByteArrayOutputStream();
final WritableByteChannel outChannel = newChannel(outStream);
outbuf.flush(outChannel);
final String s = new String(outStream.toByteArray(), StandardCharsets.US_ASCII);
Assert.assertEquals("One\r\nTwo\r\nThree\r\nFour\r\n", s);
}
@Test
public void testBasicReadWriteLine() throws Exception {
final String[] teststrs = new String[5];
teststrs[0] = "Hello";
teststrs[1] = "This string should be much longer than the size of the line buffer " +
"which is only 16 bytes for this test";
final StringBuilder buffer = new StringBuilder();
for (int i = 0; i < 15; i++) {
buffer.append("123456789 ");
}
buffer.append("and stuff like that");
teststrs[2] = buffer.toString();
teststrs[3] = "";
teststrs[4] = "And goodbye";
final CharArrayBuffer chbuffer = new CharArrayBuffer(32);
final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(1024, 16);
for (final String teststr : teststrs) {
chbuffer.clear();
chbuffer.append(teststr);
outbuf.writeLine(chbuffer);
}
//this write operation should have no effect
outbuf.writeLine(null);
final ByteArrayOutputStream outStream = new ByteArrayOutputStream();
final WritableByteChannel outChannel = newChannel(outStream);
outbuf.flush(outChannel);
final ReadableByteChannel channel = newChannel(outStream.toByteArray());
final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 16, 0);
inbuf.fill(channel);
for (final String teststr : teststrs) {
chbuffer.clear();
inbuf.readLine(chbuffer, true);
Assert.assertEquals(teststr, chbuffer.toString());
}
chbuffer.clear();
Assert.assertFalse(inbuf.readLine(chbuffer, true));
chbuffer.clear();
Assert.assertFalse(inbuf.readLine(chbuffer, true));
}
@Test
public void testComplexReadWriteLine() throws Exception {
final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(1024, 16);
outbuf.write(ByteBuffer.wrap(new byte[] {'a', '\n'}));
outbuf.write(ByteBuffer.wrap(new byte[] {'\r', '\n'}));
outbuf.write(ByteBuffer.wrap(new byte[] {'\r', '\r', '\n'}));
outbuf.write(ByteBuffer.wrap(new byte[] {'\n'}));
final StringBuilder buffer = new StringBuilder();
for (int i = 0; i < 14; i++) {
buffer.append("a");
}
final String s1 = buffer.toString();
buffer.append("\r\n");
outbuf.write(ByteBuffer.wrap(buffer.toString().getBytes(StandardCharsets.US_ASCII)));
buffer.setLength(0);
for (int i = 0; i < 15; i++) {
buffer.append("a");
}
final String s2 = buffer.toString();
buffer.append("\r\n");
outbuf.write(ByteBuffer.wrap(buffer.toString().getBytes(StandardCharsets.US_ASCII)));
buffer.setLength(0);
for (int i = 0; i < 16; i++) {
buffer.append("a");
}
final String s3 = buffer.toString();
buffer.append("\r\n");
outbuf.write(ByteBuffer.wrap(buffer.toString().getBytes(StandardCharsets.US_ASCII)));
outbuf.write(ByteBuffer.wrap(new byte[] {'a'}));
final ByteArrayOutputStream outStream = new ByteArrayOutputStream();
final WritableByteChannel outChannel = newChannel(outStream);
outbuf.flush(outChannel);
final ReadableByteChannel channel = newChannel(outStream.toByteArray());
final SessionInputBuffer inbuf = new SessionInputBufferImpl(1024, 16, 0);
inbuf.fill(channel);
final CharArrayBuffer chbuffer = new CharArrayBuffer(32);
chbuffer.clear();
inbuf.readLine(chbuffer, true);
Assert.assertEquals("a", chbuffer.toString());
chbuffer.clear();
inbuf.readLine(chbuffer, true);
Assert.assertEquals("", chbuffer.toString());
chbuffer.clear();
inbuf.readLine(chbuffer, true);
Assert.assertEquals("\r", chbuffer.toString());
chbuffer.clear();
inbuf.readLine(chbuffer, true);
Assert.assertEquals("", chbuffer.toString());
chbuffer.clear();
inbuf.readLine(chbuffer, true);
Assert.assertEquals(s1, chbuffer.toString());
chbuffer.clear();
inbuf.readLine(chbuffer, true);
Assert.assertEquals(s2, chbuffer.toString());
chbuffer.clear();
inbuf.readLine(chbuffer, true);
Assert.assertEquals(s3, chbuffer.toString());
chbuffer.clear();
inbuf.readLine(chbuffer, true);
Assert.assertEquals("a", chbuffer.toString());
chbuffer.clear();
inbuf.readLine(chbuffer, true);
Assert.assertFalse(inbuf.readLine(chbuffer, true));
chbuffer.clear();
inbuf.readLine(chbuffer, true);
Assert.assertFalse(inbuf.readLine(chbuffer, true));
}
@Test
public void testReadOneByte() throws Exception {
// make the buffer larger than that of transmitter
final byte[] out = new byte[40];
for (int i = 0; i < out.length; i++) {
out[i] = (byte)('0' + i);
}
final ReadableByteChannel channel = newChannel(out);
final SessionInputBuffer inbuf = new SessionInputBufferImpl(16, 16, 0);
while (inbuf.fill(channel) > 0) {
}
final byte[] in = new byte[40];
for (int i = 0; i < in.length; i++) {
in[i] = (byte)inbuf.read();
}
for (int i = 0; i < out.length; i++) {
Assert.assertEquals(out[i], in[i]);
}
}
@Test
public void testReadByteBuffer() throws Exception {
final byte[] pattern = "0123456789ABCDEF".getBytes(StandardCharsets.US_ASCII);
final ReadableByteChannel channel = newChannel(pattern);
final SessionInputBuffer inbuf = new SessionInputBufferImpl(4096, 1024, 0);
while (inbuf.fill(channel) > 0) {
}
final ByteBuffer dst = ByteBuffer.allocate(10);
Assert.assertEquals(10, inbuf.read(dst));
dst.flip();
Assert.assertEquals(dst, ByteBuffer.wrap(pattern, 0, 10));
dst.clear();
Assert.assertEquals(6, inbuf.read(dst));
dst.flip();
Assert.assertEquals(dst, ByteBuffer.wrap(pattern, 10, 6));
}
@Test
public void testReadByteBufferWithMaxLen() throws Exception {
final byte[] pattern = "0123456789ABCDEF".getBytes(StandardCharsets.US_ASCII);
final ReadableByteChannel channel = newChannel(pattern);
final SessionInputBuffer inbuf = new SessionInputBufferImpl(4096, 1024, 0);
while (inbuf.fill(channel) > 0) {
}
final ByteBuffer dst = ByteBuffer.allocate(16);
Assert.assertEquals(10, inbuf.read(dst, 10));
dst.flip();
Assert.assertEquals(dst, ByteBuffer.wrap(pattern, 0, 10));
dst.clear();
Assert.assertEquals(3, inbuf.read(dst, 3));
dst.flip();
Assert.assertEquals(dst, ByteBuffer.wrap(pattern, 10, 3));
Assert.assertEquals(3, inbuf.read(dst, 20));
dst.flip();
Assert.assertEquals(dst, ByteBuffer.wrap(pattern, 13, 3));
}
@Test
public void testReadToChannel() throws Exception {
final byte[] pattern = "0123456789ABCDEF".getBytes(StandardCharsets.US_ASCII);
final ReadableByteChannel channel = newChannel(pattern);
final SessionInputBuffer inbuf = new SessionInputBufferImpl(4096, 1024, 0);
while (inbuf.fill(channel) > 0) {
}
final ByteArrayOutputStream outStream = new ByteArrayOutputStream();
final WritableByteChannel dst = newChannel(outStream);
Assert.assertEquals(16, inbuf.read(dst));
Assert.assertEquals(ByteBuffer.wrap(pattern), ByteBuffer.wrap(outStream.toByteArray()));
}
@Test
public void testReadToChannelWithMaxLen() throws Exception {
final byte[] pattern = "0123456789ABCDEF".getBytes(StandardCharsets.US_ASCII);
final ReadableByteChannel channel = newChannel(pattern);
final SessionInputBuffer inbuf = new SessionInputBufferImpl(4096, 1024, 0);
while (inbuf.fill(channel) > 0) {
}
final ByteArrayOutputStream outStream = new ByteArrayOutputStream();
final WritableByteChannel dst = newChannel(outStream);
Assert.assertEquals(10, inbuf.read(dst, 10));
Assert.assertEquals(3, inbuf.read(dst, 3));
Assert.assertEquals(3, inbuf.read(dst, 10));
Assert.assertEquals(ByteBuffer.wrap(pattern), ByteBuffer.wrap(outStream.toByteArray()));
}
@Test
public void testWriteByteBuffer() throws Exception {
final byte[] pattern = "0123456789ABCDEF0123456789ABCDEF".getBytes(StandardCharsets.US_ASCII);
final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(4096, 1024);
final ReadableByteChannel src = newChannel(pattern);
outbuf.write(src);
final ByteArrayOutputStream outStream = new ByteArrayOutputStream();
final WritableByteChannel channel = newChannel(outStream);
while (outbuf.flush(channel) > 0) {
}
Assert.assertEquals(ByteBuffer.wrap(pattern), ByteBuffer.wrap(outStream.toByteArray()));
}
@Test
public void testWriteFromChannel() throws Exception {
final byte[] pattern = "0123456789ABCDEF0123456789ABCDEF".getBytes(StandardCharsets.US_ASCII);
final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(4096, 1024);
outbuf.write(ByteBuffer.wrap(pattern, 0, 16));
outbuf.write(ByteBuffer.wrap(pattern, 16, 10));
outbuf.write(ByteBuffer.wrap(pattern, 26, 6));
final ByteArrayOutputStream outStream = new ByteArrayOutputStream();
final WritableByteChannel channel = newChannel(outStream);
while (outbuf.flush(channel) > 0) {
}
Assert.assertEquals(ByteBuffer.wrap(pattern), ByteBuffer.wrap(outStream.toByteArray()));
}
static final int SWISS_GERMAN_HELLO [] = {
0x47, 0x72, 0xFC, 0x65, 0x7A, 0x69, 0x5F, 0x7A, 0xE4, 0x6D, 0xE4
};
static final int RUSSIAN_HELLO [] = {
0x412, 0x441, 0x435, 0x43C, 0x5F, 0x43F, 0x440, 0x438,
0x432, 0x435, 0x442
};
private static String constructString(final int [] unicodeChars) {
final StringBuilder buffer = new StringBuilder();
if (unicodeChars != null) {
for (final int unicodeChar : unicodeChars) {
buffer.append((char)unicodeChar);
}
}
return buffer.toString();
}
@Test
public void testMultibyteCodedReadWriteLine() throws Exception {
final String s1 = constructString(SWISS_GERMAN_HELLO);
final String s2 = constructString(RUSSIAN_HELLO);
final String s3 = "Like hello and stuff";
final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(1024, 16,
StandardCharsets.UTF_8.newEncoder());
final CharArrayBuffer chbuffer = new CharArrayBuffer(32);
for (int i = 0; i < 10; i++) {
chbuffer.clear();
chbuffer.append(s1);
outbuf.writeLine(chbuffer);
chbuffer.clear();
chbuffer.append(s2);
outbuf.writeLine(chbuffer);
chbuffer.clear();
chbuffer.append(s3);
outbuf.writeLine(chbuffer);
}
final ByteArrayOutputStream outStream = new ByteArrayOutputStream();
final WritableByteChannel outChannel = newChannel(outStream);
outbuf.flush(outChannel);
final byte[] tmp = outStream.toByteArray();
final ReadableByteChannel channel = newChannel(tmp);
final SessionInputBuffer inbuf = new SessionInputBufferImpl(16, 16, 0,
StandardCharsets.UTF_8.newDecoder());
while (inbuf.fill(channel) > 0) {
}
for (int i = 0; i < 10; i++) {
chbuffer.clear();
inbuf.readLine(chbuffer, true);
Assert.assertEquals(s1, chbuffer.toString());
chbuffer.clear();
inbuf.readLine(chbuffer, true);
Assert.assertEquals(s2, chbuffer.toString());
chbuffer.clear();
inbuf.readLine(chbuffer, true);
Assert.assertEquals(s3, chbuffer.toString());
}
}
@Test
public void testInputMatchesBufferLength() throws Exception {
final String s1 = "abcde";
final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(1024, 5);
final CharArrayBuffer chbuffer = new CharArrayBuffer(16);
chbuffer.append(s1);
outbuf.writeLine(chbuffer);
}
@Test(expected=CharacterCodingException.class)
public void testMalformedInputActionReport() throws Exception {
final String s = constructString(SWISS_GERMAN_HELLO);
final byte[] tmp = s.getBytes(StandardCharsets.ISO_8859_1);
final CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder();
decoder.onMalformedInput(CodingErrorAction.REPORT);
decoder.onUnmappableCharacter(CodingErrorAction.IGNORE);
final SessionInputBuffer inbuf = new SessionInputBufferImpl(16, 16, 0, decoder);
final ReadableByteChannel channel = newChannel(tmp);
while (inbuf.fill(channel) > 0) {
}
final CharArrayBuffer chbuffer = new CharArrayBuffer(16);
inbuf.readLine(chbuffer, true);
}
@Test
public void testMalformedInputActionIgnore() throws Exception {
final String s = constructString(SWISS_GERMAN_HELLO);
final byte[] tmp = s.getBytes(StandardCharsets.ISO_8859_1);
final CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder();
decoder.onMalformedInput(CodingErrorAction.IGNORE);
decoder.onUnmappableCharacter(CodingErrorAction.IGNORE);
final SessionInputBuffer inbuf = new SessionInputBufferImpl(16, 16, 0, decoder);
final ReadableByteChannel channel = newChannel(tmp);
while (inbuf.fill(channel) > 0) {
}
final CharArrayBuffer chbuffer = new CharArrayBuffer(16);
inbuf.readLine(chbuffer, true);
Assert.assertEquals("Grezi_zm", chbuffer.toString());
}
@Test
public void testMalformedInputActionReplace() throws Exception {
final String s = constructString(SWISS_GERMAN_HELLO);
final byte[] tmp = s.getBytes(StandardCharsets.ISO_8859_1);
final CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder();
decoder.onMalformedInput(CodingErrorAction.REPLACE);
decoder.onUnmappableCharacter(CodingErrorAction.IGNORE);
final SessionInputBuffer inbuf = new SessionInputBufferImpl(16, 16, 0, decoder);
final ReadableByteChannel channel = newChannel(tmp);
while (inbuf.fill(channel) > 0) {
}
final CharArrayBuffer chbuffer = new CharArrayBuffer(16);
inbuf.readLine(chbuffer, true);
Assert.assertEquals("Gr\ufffdezi_z\ufffdm\ufffd", chbuffer.toString());
}
@Test(expected=CharacterCodingException.class)
public void testUnmappableInputActionReport() throws Exception {
final String s = "This text contains a circumflex \u0302!!!";
final CharsetEncoder encoder = StandardCharsets.ISO_8859_1.newEncoder();
encoder.onMalformedInput(CodingErrorAction.IGNORE);
encoder.onUnmappableCharacter(CodingErrorAction.REPORT);
final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(1024, 16, encoder);
final CharArrayBuffer chbuffer = new CharArrayBuffer(16);
chbuffer.append(s);
outbuf.writeLine(chbuffer);
}
@Test
public void testUnmappableInputActionIgnore() throws Exception {
final String s = "This text contains a circumflex \u0302!!!";
final CharsetEncoder encoder = StandardCharsets.ISO_8859_1.newEncoder();
encoder.onMalformedInput(CodingErrorAction.IGNORE);
encoder.onUnmappableCharacter(CodingErrorAction.IGNORE);
final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(1024, 16, encoder);
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
final WritableByteChannel channel = newChannel(baos);
final CharArrayBuffer chbuffer = new CharArrayBuffer(16);
chbuffer.append(s);
outbuf.writeLine(chbuffer);
outbuf.flush(channel);
final String result = new String(baos.toByteArray(), StandardCharsets.US_ASCII);
Assert.assertEquals("This text contains a circumflex !!!\r\n", result);
}
@Test
public void testUnmappableInputActionReplace() throws Exception {
final String s = "This text contains a circumflex \u0302 !!!";
final CharsetEncoder encoder = StandardCharsets.ISO_8859_1.newEncoder();
encoder.onMalformedInput(CodingErrorAction.IGNORE);
encoder.onUnmappableCharacter(CodingErrorAction.REPLACE);
final SessionOutputBuffer outbuf = new SessionOutputBufferImpl(1024, 16, encoder);
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
final WritableByteChannel channel = newChannel(baos);
final CharArrayBuffer chbuffer = new CharArrayBuffer(16);
chbuffer.append(s);
outbuf.writeLine(chbuffer);
outbuf.flush(channel);
final String result = new String(baos.toByteArray(), StandardCharsets.US_ASCII);
Assert.assertEquals("This text contains a circumflex ? !!!\r\n", result);
}
}