Skip to content

Commit 9b0097a

Browse files
committed
Port US-ASCII and UTF-8 ByteBuf decoders from Gatling, close AsyncHttpClient#1232
1 parent 3d2bb58 commit 9b0097a

File tree

5 files changed

+180
-158
lines changed

5 files changed

+180
-158
lines changed

client/src/main/java/org/asynchttpclient/netty/ws/NettyWebSocket.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,11 @@
2424
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
2525

2626
import java.net.SocketAddress;
27+
import java.nio.charset.CharacterCodingException;
2728
import java.util.Collection;
2829
import java.util.concurrent.ConcurrentLinkedQueue;
2930

31+
import org.asynchttpclient.util.ByteBufUtils;
3032
import org.asynchttpclient.ws.WebSocket;
3133
import org.asynchttpclient.ws.WebSocketByteListener;
3234
import org.asynchttpclient.ws.WebSocketCloseCodeReasonListener;
@@ -217,7 +219,11 @@ public void onBinaryFragment(BinaryWebSocketFrame frame) {
217219

218220
public void onTextFragment(TextWebSocketFrame frame) {
219221
if (interestedInTextMessages) {
220-
notifyTextListeners(frame.text());
222+
try {
223+
notifyTextListeners(ByteBufUtils.byteBuf2Utf8String(frame.content()));
224+
} catch (CharacterCodingException e) {
225+
throw new IllegalStateException(e);
226+
}
221227
}
222228
}
223229

client/src/main/java/org/asynchttpclient/util/ByteBufUtils.java

Lines changed: 12 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,10 @@
1616
import io.netty.buffer.ByteBuf;
1717

1818
import java.io.UTFDataFormatException;
19-
import java.nio.ByteBuffer;
20-
import java.nio.CharBuffer;
2119
import java.nio.charset.CharacterCodingException;
2220
import java.nio.charset.Charset;
23-
import java.nio.charset.CharsetDecoder;
24-
import java.nio.charset.CoderResult;
2521
import java.nio.charset.StandardCharsets;
22+
import java.util.Collections;
2623
import java.util.List;
2724

2825
public final class ByteBufUtils {
@@ -32,51 +29,23 @@ public final class ByteBufUtils {
3229
private ByteBufUtils() {
3330
}
3431

35-
public static String byteBuf2String(ByteBuf buf, Charset charset) throws UTFDataFormatException, IndexOutOfBoundsException, CharacterCodingException {
32+
public static String byteBuf2Utf8String(ByteBuf buf) throws CharacterCodingException {
33+
return Utf8ByteBufDecoder.getCachedDecoder().decode(Collections.singleton(buf));
34+
}
35+
36+
public static String byteBuf2UsAsciiString(ByteBuf buf) throws CharacterCodingException {
37+
return UsAsciiByteBufDecoder.getCachedDecoder().decode(Collections.singleton(buf));
38+
}
3639

37-
int byteLen = buf.readableBytes();
40+
public static String byteBuf2String(ByteBuf buf, Charset charset) throws UTFDataFormatException, IndexOutOfBoundsException, CharacterCodingException {
3841

3942
if (charset.equals(StandardCharsets.US_ASCII)) {
40-
return Utf8Reader.readUtf8(buf, byteLen);
43+
return byteBuf2UsAsciiString(buf);
4144
} else if (charset.equals(StandardCharsets.UTF_8)) {
42-
try {
43-
return Utf8Reader.readUtf8(buf.duplicate(), (int) (byteLen * 1.4));
44-
} catch (IndexOutOfBoundsException e) {
45-
// try again with 3 bytes per char
46-
return Utf8Reader.readUtf8(buf, byteLen * 3);
47-
}
45+
return byteBuf2Utf8String(buf);
4846
} else {
49-
return byteBuffersToString(buf.nioBuffers(), charset);
50-
}
51-
}
52-
53-
private static String byteBuffersToString(ByteBuffer[] bufs, Charset cs) throws CharacterCodingException {
54-
55-
CharsetDecoder cd = cs.newDecoder();
56-
int len = 0;
57-
for (ByteBuffer buf : bufs) {
58-
len += buf.remaining();
59-
}
60-
int en = (int) (len * (double) cd.maxCharsPerByte());
61-
char[] ca = new char[en];
62-
cd.reset();
63-
CharBuffer cb = CharBuffer.wrap(ca);
64-
65-
CoderResult cr = null;
66-
67-
for (int i = 0; i < bufs.length; i++) {
68-
69-
ByteBuffer buf = bufs[i];
70-
cr = cd.decode(buf, cb, i < bufs.length - 1);
71-
if (!cr.isUnderflow())
72-
cr.throwException();
47+
return buf.toString(charset);
7348
}
74-
75-
cr = cd.flush(cb);
76-
if (!cr.isUnderflow())
77-
cr.throwException();
78-
79-
return new String(ca, 0, cb.position());
8049
}
8150

8251
public static byte[] byteBuf2Bytes(ByteBuf buf) {
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Copyright (c) 2016 AsyncHttpClient Project. All rights reserved.
3+
*
4+
* This program is licensed to you under the Apache License Version 2.0,
5+
* and you may not use this file except in compliance with the Apache License Version 2.0.
6+
* You may obtain a copy of the Apache License Version 2.0 at
7+
* http://www.apache.org/licenses/LICENSE-2.0.
8+
*
9+
* Unless required by applicable law or agreed to in writing,
10+
* software distributed under the Apache License Version 2.0 is distributed on an
11+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
13+
*/
14+
package org.asynchttpclient.util;
15+
16+
import io.netty.buffer.ByteBuf;
17+
import io.netty.util.concurrent.FastThreadLocal;
18+
19+
public class UsAsciiByteBufDecoder {
20+
21+
private static final FastThreadLocal<UsAsciiByteBufDecoder> DECODERS = new FastThreadLocal<UsAsciiByteBufDecoder>() {
22+
protected UsAsciiByteBufDecoder initialValue() {
23+
return new UsAsciiByteBufDecoder();
24+
};
25+
};
26+
27+
public static UsAsciiByteBufDecoder getCachedDecoder() {
28+
UsAsciiByteBufDecoder cached = DECODERS.get();
29+
cached.reset();
30+
return cached;
31+
}
32+
33+
private StringBuilder sb = new StringBuilder();
34+
35+
public void reset() {
36+
sb.setLength(0);
37+
}
38+
39+
public String decode(Iterable<ByteBuf> bufs) {
40+
for (ByteBuf buf : bufs) {
41+
buf.forEachByte(b -> {
42+
sb.append((char) b);
43+
return true;
44+
});
45+
}
46+
return sb.toString();
47+
}
48+
}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
/*
2+
* Copyright (c) 2016 AsyncHttpClient Project. All rights reserved.
3+
*
4+
* This program is licensed to you under the Apache License Version 2.0,
5+
* and you may not use this file except in compliance with the Apache License Version 2.0.
6+
* You may obtain a copy of the Apache License Version 2.0 at
7+
* http://www.apache.org/licenses/LICENSE-2.0.
8+
*
9+
* Unless required by applicable law or agreed to in writing,
10+
* software distributed under the Apache License Version 2.0 is distributed on an
11+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
13+
*/
14+
package org.asynchttpclient.util;
15+
16+
import io.netty.buffer.ByteBuf;
17+
import io.netty.util.concurrent.FastThreadLocal;
18+
19+
import java.nio.charset.CharacterCodingException;
20+
21+
public class Utf8ByteBufDecoder {
22+
23+
private static final FastThreadLocal<Utf8ByteBufDecoder> DECODERS = new FastThreadLocal<Utf8ByteBufDecoder>() {
24+
protected Utf8ByteBufDecoder initialValue() {
25+
return new Utf8ByteBufDecoder();
26+
};
27+
};
28+
29+
public static Utf8ByteBufDecoder getCachedDecoder() {
30+
Utf8ByteBufDecoder cached = DECODERS.get();
31+
cached.reset();
32+
return cached;
33+
}
34+
35+
private static final byte[] TYPES = new byte[] {//
36+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,/**/
37+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,/**/
38+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,/**/
39+
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,/**/
40+
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,/**/
41+
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,/**/
42+
8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,/**/
43+
10, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, 11, 6, 6, 6, 5, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8 /**/
44+
};
45+
46+
private static final byte[] STATES = new byte[] {//
47+
0, 12, 24, 36, 60, 96, 84, 12, 12, 12, 48, 72, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,/**/
48+
12, 0, 12, 12, 12, 12, 12, 0, 12, 0, 12, 12, 12, 24, 12, 12, 12, 12, 12, 24, 12, 24, 12, 12,/**/
49+
12, 12, 12, 12, 12, 12, 12, 24, 12, 12, 12, 12, 12, 24, 12, 12, 12, 12, 12, 12, 12, 24, 12, 12,/**/
50+
12, 12, 12, 12, 12, 12, 12, 36, 12, 36, 12, 12, 12, 36, 12, 12, 12, 12, 12, 36, 12, 36, 12, 12,/**/
51+
12, 36, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12 //
52+
};
53+
54+
private static final int UTF8_ACCEPT = 0;
55+
private static final int UTF8_REJECT = 12;
56+
57+
private StringBuilder sb = new StringBuilder();
58+
private int state = UTF8_ACCEPT;
59+
private int codePoint = 0;
60+
61+
private void write(byte b) throws CharacterCodingException {
62+
int t = TYPES[b & 0xFF];
63+
64+
codePoint = state != UTF8_ACCEPT ? (b & 0x3f) | (codePoint << 6) : (0xff >> t) & b;
65+
state = STATES[state + t];
66+
67+
if (state == UTF8_ACCEPT) {
68+
if (codePoint < Character.MIN_HIGH_SURROGATE) {
69+
sb.append((char) codePoint);
70+
} else {
71+
appendCodePointChars();
72+
}
73+
} else if (state == UTF8_REJECT) {
74+
throw new CharacterCodingException();
75+
}
76+
}
77+
78+
private void appendCodePointChars() {
79+
if (Character.isBmpCodePoint(codePoint)) {
80+
sb.append((char) codePoint);
81+
82+
} else if (Character.isValidCodePoint(codePoint)) {
83+
char charIndexPlus1 = Character.lowSurrogate(codePoint);
84+
char charIndex = Character.highSurrogate(codePoint);
85+
sb.append(charIndex).append(charIndexPlus1);
86+
87+
} else {
88+
throw new IllegalArgumentException();
89+
}
90+
}
91+
92+
public void reset() {
93+
sb.setLength(0);
94+
state = UTF8_ACCEPT;
95+
codePoint = 0;
96+
}
97+
98+
public String decode(Iterable<ByteBuf> bufs) throws CharacterCodingException {
99+
100+
for (ByteBuf buf : bufs) {
101+
buf.forEachByte(value -> {
102+
write(value);
103+
return true;
104+
});
105+
}
106+
107+
if (state == UTF8_ACCEPT) {
108+
return sb.toString();
109+
} else {
110+
throw new CharacterCodingException();
111+
}
112+
}
113+
}

client/src/main/java/org/asynchttpclient/util/Utf8Reader.java

Lines changed: 0 additions & 114 deletions
This file was deleted.

0 commit comments

Comments
 (0)