Skip to content

Commit 78e83b1

Browse files
committed
optimize huffman coding
1 parent 68d7bf8 commit 78e83b1

File tree

15 files changed

+264
-111
lines changed

15 files changed

+264
-111
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package robaho.net.httpserver;
2+
3+
/** small set designed for efficient negative contains */
4+
public class BloomSet {
5+
private final int bloomHash;
6+
private OpenAddressMap<String,Boolean> values;
7+
private BloomSet(String... values) {
8+
this.values = new OpenAddressMap<>(values.length*2);
9+
int bloomHash = 0;
10+
for(var v : values) {
11+
bloomHash = bloomHash | v.hashCode();
12+
this.values.put(v,true);
13+
}
14+
this.bloomHash = bloomHash;
15+
}
16+
public static BloomSet of(String... values) {
17+
return new BloomSet(values);
18+
}
19+
public boolean contains(String value) {
20+
return (bloomHash & value.hashCode()) == value.hashCode() && Boolean.TRUE.equals(values.get(value));
21+
}
22+
public Iterable<String> values() {
23+
return values.keys();
24+
}
25+
}

src/main/java/robaho/net/httpserver/OpenAddressMap.java

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ private static class Entry<K,V> {
2020
private int used;
2121
private Entry[] entries;
2222

23+
private static int hash(int hash) {
24+
return hash;
25+
// return hash ^ (hash>>>16);
26+
}
27+
2328
public OpenAddressMap(int capacity) {
2429
// round up to next power of 2
2530
capacity--;
@@ -40,7 +45,7 @@ public V put(K key, V value) {
4045
resize();
4146
}
4247

43-
int index = key.hashCode() & mask;
48+
int index = hash(key.hashCode()) & mask;
4449
int start = index;
4550
int sentinel = -1;
4651
Entry entry;
@@ -58,7 +63,7 @@ public V put(K key, V value) {
5863
index = (index + 1) & mask;
5964
if (index == start) {
6065
resize();
61-
index = key.hashCode() & mask;
66+
index = hash(key.hashCode()) & mask;
6267
start = index;
6368
}
6469
}
@@ -83,7 +88,7 @@ private void resize() {
8388
}
8489

8590
public V get(K key) {
86-
int index = key.hashCode() & mask;
91+
int index = hash(key.hashCode()) & mask;
8792
int start = index;
8893
Entry entry;
8994
while ((entry = entries[index]) != null) {
@@ -115,4 +120,27 @@ public void forEach(BiConsumer<K,V> action) {
115120
}
116121
}
117122
}
123+
public Iterable<K> keys() {
124+
return () -> new KeyIterator();
125+
}
126+
127+
private class KeyIterator implements java.util.Iterator<K> {
128+
private int index = 0;
129+
130+
@Override
131+
public boolean hasNext() {
132+
while (index < entries.length) {
133+
if (entries[index] != null && entries[index].value != null) {
134+
return true;
135+
}
136+
index++;
137+
}
138+
return false;
139+
}
140+
141+
@Override
142+
public K next() {
143+
return (K) entries[index++].key;
144+
}
145+
}
118146
}

src/main/java/robaho/net/httpserver/OptimizedHeaders.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,11 @@
1212

1313
import com.sun.net.httpserver.Headers;
1414

15+
import robaho.net.httpserver.http2.hpack.HPackContext;
16+
1517
public class OptimizedHeaders extends Headers {
18+
private static final BloomSet commonKeys = BloomSet.of(HPackContext.getStaticHeaderNames().stream().map(s -> (Character.toUpperCase(s.charAt(0))+s.substring(1))).toArray(String[]::new));
19+
1620
private final OpenAddressMap<String,Object> map;
1721
public OptimizedHeaders() {
1822
super();
@@ -61,22 +65,24 @@ public String getFirst(String key) {
6165
* First {@code char} upper case, rest lower case.
6266
* key is presumed to be {@code ASCII}.
6367
*/
64-
private String normalize(String key) {
68+
private static String normalize(String key) {
6569
int len = key.length();
6670
if(len==0) return key;
6771

72+
if(commonKeys.contains(key)) return key;
73+
6874
int i=0;
6975

7076
for(;i<len;i++) {
7177
char c = key.charAt(i);
7278
if (c == '\r' || c == '\n')
7379
throw new IllegalArgumentException("illegal character in key");
7480
if(i==0) {
75-
if (Character.isLowerCase(c)) {
81+
if (c >= 'a' && c <= 'z') {
7682
break;
7783
}
7884
} else {
79-
if (Character.isUpperCase(c)) {
85+
if (c >= 'A' && c <= 'Z') {
8086
break;
8187
}
8288
}

src/main/java/robaho/net/httpserver/ServerImpl.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -511,7 +511,7 @@ public void run() {
511511
http2.sendMySettings();
512512
http2.handle();
513513
} catch (HTTP2Exception ex) {
514-
logger.log(Level.WARNING, "ServerImpl http2 protocol exception "+http2, ex.getMessage());
514+
logger.log(Level.WARNING, "ServerImpl http2 protocol exception "+http2,ex);
515515
} catch (EOFException | SocketException ex) {
516516
logger.log(Level.DEBUG, "end of stream "+http2);
517517
} catch (Exception ex) {

src/main/java/robaho/net/httpserver/http2/HTTP2Connection.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import robaho.net.httpserver.http2.frame.ContinuationFrame;
3131
import robaho.net.httpserver.http2.frame.DataFrame;
3232
import robaho.net.httpserver.http2.frame.FrameFlag;
33+
import robaho.net.httpserver.http2.frame.FrameFlag.FlagSet;
3334
import robaho.net.httpserver.http2.frame.FrameHeader;
3435
import robaho.net.httpserver.http2.frame.FrameSerializer;
3536
import robaho.net.httpserver.http2.frame.FrameType;
@@ -373,7 +374,7 @@ public void updateRemoteSettings(SettingsFrame remoteSettingFrame) throws HTTP2E
373374

374375
public void sendSettingsAck() throws IOException {
375376
try {
376-
byte[] frame = FrameHeader.encode(0, FrameType.SETTINGS, EnumSet.of(FrameFlag.ACK), 0);
377+
byte[] frame = FrameHeader.encode(0, FrameType.SETTINGS, FlagSet.of(FrameFlag.ACK), 0);
377378
HTTP2Connection.this.writeFrame(frame);
378379
} finally {
379380
logger.log(Level.TRACE, () -> "sent Settings Ack");

src/main/java/robaho/net/httpserver/http2/HTTP2Stream.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import robaho.net.httpserver.http2.frame.BaseFrame;
2525
import robaho.net.httpserver.http2.frame.DataFrame;
2626
import robaho.net.httpserver.http2.frame.FrameFlag;
27+
import robaho.net.httpserver.http2.frame.FrameFlag.FlagSet;
2728
import robaho.net.httpserver.http2.frame.FrameHeader;
2829
import robaho.net.httpserver.http2.frame.FrameType;
2930
import robaho.net.httpserver.http2.frame.ResetStreamFrame;
@@ -210,7 +211,7 @@ public InetSocketAddress getRemoteAddress() {
210211
}
211212

212213
class Http2OutputStream extends OutputStream {
213-
private static final EnumSet<FrameFlag> END_STREAM = EnumSet.of(FrameFlag.END_STREAM);
214+
private static final FlagSet END_STREAM = FlagSet.of(FrameFlag.END_STREAM);
214215

215216
private final int streamId;
216217
private final int max_frame_size;

src/main/java/robaho/net/httpserver/http2/Utils.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,30 @@ public static byte[] combineByteArrays(List<byte[]> blocks) {
8080
offset += block.length;
8181
}
8282

83+
return combined;
84+
}
85+
public static byte[] combineByteArrays(List<byte[]> array1,List<byte[]> array2) {
86+
int totalLength = 0;
87+
for (byte[] block : array1) {
88+
totalLength += block.length;
89+
}
90+
for (byte[] block : array2) {
91+
totalLength += block.length;
92+
}
93+
if(totalLength==0) return EMPTY;
94+
95+
byte[] combined = new byte[totalLength];
96+
97+
int offset = 0;
98+
for (byte[] block : array1) {
99+
System.arraycopy(block, 0, combined, offset, block.length);
100+
offset += block.length;
101+
}
102+
for (byte[] block : array2) {
103+
System.arraycopy(block, 0, combined, offset, block.length);
104+
offset += block.length;
105+
}
106+
83107
return combined;
84108
}
85109
}
Lines changed: 64 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,88 +1,87 @@
11
package robaho.net.httpserver.http2.frame;
22

3-
import java.util.Collections;
4-
import java.util.EnumSet;
5-
import java.util.Set;
6-
73
import robaho.net.httpserver.http2.HTTP2Exception;
84

95
/**
106
* An enumeration to define all the Flags that can be attached to a frame
117
*/
128
public enum FrameFlag {
139

14-
END_STREAM((byte)0x1),
15-
ACK((byte)0x1),
16-
END_HEADERS((byte)0x4),
17-
PADDED((byte)0x8),
18-
PRIORITY((byte)0x20);
19-
20-
private final byte value;
21-
22-
FrameFlag(byte value) {
23-
this.value = value;
24-
}
10+
END_STREAM((byte) 0x1),
11+
ACK((byte) 0x1),
12+
END_HEADERS((byte) 0x4),
13+
PADDED((byte) 0x8),
14+
PRIORITY((byte) 0x20);
2515

16+
private final byte value;
17+
private static final byte MASK = (byte) (END_STREAM.value | END_HEADERS.value | PADDED.value | PRIORITY.value);
2618
private static final FrameFlag[] _values = FrameFlag.values();
2719

28-
public static final Set<FrameFlag> NONE = Collections.unmodifiableSet(EnumSet.noneOf(FrameFlag.class));
20+
FrameFlag(byte value) {
21+
this.value = value;
22+
}
2923

30-
public byte getValue() {
31-
return value;
32-
}
24+
public static final FlagSet NONE = new FlagSet(0,false);
3325

34-
public static Set<FrameFlag> getEnumSet(byte value, FrameType type) throws HTTP2Exception {
35-
if(value==0) {
26+
public byte getValue() {
27+
return value;
28+
}
29+
30+
public static FlagSet getEnumSet(byte value, FrameType type) throws HTTP2Exception {
31+
if (value == 0) {
3632
return NONE;
3733
}
34+
return new FlagSet(value & MASK, type == FrameType.SETTINGS || type == FrameType.PING);
35+
}
3836

39-
// Empty EnumSet
40-
EnumSet<FrameFlag> result = EnumSet.noneOf(FrameFlag.class);
37+
public static class FlagSet {
4138

42-
// Check if the first bit is set
43-
if((value & 1) == 1)
44-
{
45-
// for SETTING and PING frames the first bit indicates whether the frame is ACK
46-
if(type == FrameType.SETTINGS || type == FrameType.PING)
47-
{
48-
result.add(FrameFlag.ACK);
49-
}
50-
else
51-
{
52-
result.add(FrameFlag.END_STREAM);
53-
}
54-
55-
// reset the first bit
56-
value = (byte)(value ^ 1);
57-
}
39+
private final int value;
40+
private final boolean isAck;
5841

59-
// For each flag in FrameFlag
60-
for (FrameFlag flag : _values) {
61-
// Check whether the flag bit is set
62-
if ((value & flag.value) != 0) {
63-
result.add(flag);
64-
65-
// reset the flag bit
66-
value = (byte)(value ^ flag.value);
67-
}
68-
}
69-
70-
if(value != 0) {
71-
// Unknown bit flag is set, according to the spec we should ignore it
72-
// throw new HTTP2Exception(HTTP2ErrorCode.CONNECT_ERROR, "Unknown bit flag is set: " + value);
42+
FlagSet(int value, boolean isAck) {
43+
this.value = value;
44+
this.isAck = isAck;
7345
}
7446

75-
return result;
76-
}
77-
78-
public static byte getValue(Set<FrameFlag> flags) {
79-
80-
byte result = 0;
81-
82-
for (FrameFlag flag : flags) {
83-
result = (byte) (result | flag.getValue());
84-
}
47+
public byte value() {
48+
return (byte) value;
49+
}
50+
public boolean contains(FrameFlag flag) {
51+
return (value & flag.value) == flag.value;
52+
}
53+
public static FlagSet of(FrameFlag... flags) {
54+
int value = 0;
55+
boolean isAck = false;
56+
for (FrameFlag flag : flags) {
57+
value |= flag.value;
58+
if (flag == ACK) {
59+
isAck = true;
60+
}
61+
}
62+
return new FlagSet(value, isAck);
63+
}
8564

86-
return result;
87-
}
65+
@Override
66+
public String toString() {
67+
StringBuilder sb = new StringBuilder("[");
68+
var tmp = this.value;
69+
70+
if ((tmp & 1) == 1) {
71+
sb.append(isAck ? "ACK" : "END_STREAM");
72+
// reset the first bit
73+
tmp = (byte) (tmp ^ 1);
74+
}
75+
for (FrameFlag flag : FrameFlag._values) {
76+
if ((tmp & flag.value) == flag.value) {
77+
if(!sb.isEmpty()) {
78+
sb.append(",");
79+
}
80+
sb.append(flag);
81+
}
82+
}
83+
sb.append("]");
84+
return sb.toString();
85+
}
86+
}
8887
}

0 commit comments

Comments
 (0)