Skip to content

Commit 7b94861

Browse files
committed
Merge branch 'release/0.9.0'
2 parents aa3a737 + 788c036 commit 7b94861

File tree

9 files changed

+2095
-1
lines changed

9 files changed

+2095
-1
lines changed

README.md

Lines changed: 183 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,183 @@
1-
# netty-websocket-http1
1+
# netty-websocket-http1
2+
3+
Alternative Netty implementation of [RFC6455](https://tools.ietf.org/html/rfc6455) - the WebSocket protocol.
4+
5+
Its advantage is significant per-core throughput improvement (1.8 - 2x) for small frames in comparison to netty's out-of-the-box
6+
websocket codecs, and minimal heap allocations on frame path. Library is compatible with
7+
[netty-websocket-http2](https://github.com/jauntsdn/netty-websocket-http2).
8+
9+
### use case & scope
10+
11+
* Intended for efficiently encoded, dense binary data: no extensions (compression) support / outbound text frames / inbound
12+
utf8 validation.
13+
14+
* Library assumes small frames - many have payload <= 125 bytes, most are < 1500, maximum supported is 65k (65535 bytes).
15+
16+
* Just codec - fragments, pings, close frames are decoded & validated only. It is responsibility of user code
17+
to handle frames according to protocol (reassemble frame fragments, perform graceful close,
18+
respond to pings).
19+
20+
* Dedicated decoder for case of exchanging tiny messages over TLS connection:
21+
only non-masked frames with <= 125 bytes of payload for minimal per-webSocket state (memory) overhead.
22+
23+
* No per-frame heap allocations in websocket frameFactory / decoder.
24+
25+
* Single-threaded (transport IO event-loop) callbacks / frame factory API -
26+
in practice user code has its own message types to carry data, external means (e.g. mpsc / spsc queues) may be used to
27+
properly publish messages on eventloop thread.
28+
29+
### performance
30+
31+
Per-core throughput [this codec perf-test](https://github.com/jauntsdn/netty-websocket-http1/tree/develop/netty-websocket-http1-perftest/src/main/java/com/jauntsdn/netty/handler/codec/http/websocketx/perftest),
32+
[netty built-in codec perf-test](https://github.com/jauntsdn/netty-websocket-http1/tree/netty-codec/netty-builtin-websocket-perftest/src/main/java/io/netty/handler/codec/http/websocketx/perftest)
33+
comparison with netty's out-of-the-box websocket handlers:
34+
non-masked frames with 8, 64, 125, 1000 bytes of payload over encrypted/non-encrypted connection.
35+
36+
```
37+
./gradlew clean build installDist
38+
./perf_server_run.sh
39+
./perf_client_run.sh
40+
```
41+
42+
* non-encrypted
43+
44+
| payload size | this codec, million messages | netty's codec, million messages |
45+
| :--- | :---: | :---: |
46+
| 8 | 2.45 | 1.35 |
47+
| 64 | 2.35 | 1.25 |
48+
| 125 | 2.3 | 1.15 |
49+
| 1000 | 1.15 | 0.64 |
50+
51+
* encrypted
52+
53+
| payload size | this codec, million messages | netty's codec, million messages |
54+
| :--- | :---: | :---: |
55+
| 8 | 2.8 | 1.45 |
56+
| 64 | 2.3 | 1.2 |
57+
| 125 | 1.9 | 1.1 |
58+
| 1000 | 0.52| 0.35 |
59+
60+
### websocket-http2
61+
62+
Library may be combined with [jauntsdn/websocket-http2](https://github.com/jauntsdn/netty-websocket-http2) using [http1 codec](https://github.com/jauntsdn/netty-websocket-http2/blob/develop/netty-websocket-http2/src/main/java/com/jauntsdn/netty/handler/codec/http2/websocketx/Http1WebSocketCodec.java)
63+
64+
for significantly improved per-core throughput [this codec perf-test](), [netty built-in codec perf-test]():
65+
for 8, 125, 1000 byte payload frames over encrypted connection results are as follows:
66+
67+
| payload size | this codec, million msgs | netty's codec, million msgs |
68+
| :---: | :---: | :---: |
69+
| 8 | 0.93 | 0.56 |
70+
| 125 | 0.74 | 0.464 |
71+
| 1000 | 0.275 | 0.211 |
72+
73+
### frameFactory / callbacks API
74+
75+
[WebSocketFrameFactory](https://github.com/jauntsdn/netty-websocket-http1/blob/develop/netty-websocket-http1/src/main/java/com/jauntsdn/netty/handler/codec/http/websocketx/WebSocketFrameFactory.java)
76+
to create outbound frames. It is library user responsibility to mask outbound frame once payload is written
77+
`ByteBuf WebSocketFrameFactory.mask(ByteBuf)`
78+
79+
```java
80+
public interface WebSocketFrameFactory {
81+
82+
ByteBuf createBinaryFrame(ByteBufAllocator allocator, int binaryDataSize);
83+
// create*Frame are omitted for control frames, created in similar fashion
84+
85+
ByteBuf mask(ByteBuf frame);
86+
}
87+
```
88+
89+
[WebSocketFrameListener](https://github.com/jauntsdn/netty-websocket-http1/blob/develop/netty-websocket-http1/src/main/java/com/jauntsdn/netty/handler/codec/http/websocketx/WebSocketFrameListener.java)
90+
to receive inbound frames
91+
92+
```java
93+
public interface WebSocketFrameListener {
94+
95+
void onChannelRead(
96+
ChannelHandlerContext ctx, boolean finalFragment, int rsv, int opcode, ByteBuf payload);
97+
98+
// netty handler callbacks are omitted for brevity
99+
100+
// lifecycle
101+
default void onOpen(ChannelHandlerContext ctx) {}
102+
103+
default void onClose(ChannelHandlerContext ctx) {}
104+
}
105+
```
106+
107+
[WebSocketCallbacksHandler](https://github.com/jauntsdn/netty-websocket-http1/blob/develop/netty-websocket-http1/src/main/java/com/jauntsdn/netty/handler/codec/http/websocketx/WebSocketCallbacksHandler.java)
108+
to exchange `WebSocketFrameListener` for `WebSocketFrameFactory` on successful websocket handshake
109+
110+
```java
111+
public interface WebSocketCallbacksHandler {
112+
113+
WebSocketFrameListener exchange(
114+
ChannelHandlerContext ctx, WebSocketFrameFactory webSocketFrameFactory);
115+
}
116+
```
117+
118+
### configuration
119+
120+
#### default messages decoder
121+
122+
Always expects masked frames, allows mask mismatch [test example](https://github.com/jauntsdn/netty-websocket-http1/blob/bc942b19958c1486ef7414bee9c69ef36a55bfa5/netty-websocket-http1-test/src/test/java/com/jauntsdn/netty/handler/codec/http/websocketx/WebSocketHandshakeTest.java#L121)
123+
124+
server: `allowMaskMismatch: true, maxFramePayloadLength: 65_535`
125+
126+
client: `allowMaskMismatch: true, maxFramePayloadLength: 65_535`
127+
128+
#### small messages decoder
129+
130+
Always expects non-masking frames, disallows mask mismatch [test example](https://github.com/jauntsdn/netty-websocket-http1/blob/bc942b19958c1486ef7414bee9c69ef36a55bfa5/netty-websocket-http1-test/src/test/java/com/jauntsdn/netty/handler/codec/http/websocketx/WebSocketHandshakeTest.java#L140):
131+
132+
server: `expectMasked: false, allowMaskMismatch: false, maxFramePayloadLength: 125`
133+
134+
client: `mask: false, allowMaskMismatch: false, maxFramePayloadLength: 125`
135+
136+
### testing
137+
138+
* WebSocket frames [integration test](https://github.com/jauntsdn/netty-websocket-http1/blob/develop/netty-websocket-http1-test/src/test/java/com/jauntsdn/netty/handler/codec/http/websocketx/WebSocketCodecTest.java):
139+
control & data frames of all allowed sizes, jauntsdn/netty-websocket-http1 client, netty websocket server.
140+
141+
* WebSocket frames long-running [soak test](https://github.com/jauntsdn/netty-websocket-http1/tree/develop/netty-websocket-http1-soaktest/src/main/java/com/jauntsdn/netty/handler/codec/http/websocketx/soaktest):
142+
exercising all logic paths of codec with 100m of randomized frames over multiple connections: netty websocket client, jauntsdn/netty-websocket-http1 server.
143+
144+
* [Perf test](https://github.com/jauntsdn/netty-websocket-http1/tree/develop/netty-websocket-http1-perftest/src/main/java/com/jauntsdn/netty/handler/codec/http/websocketx/perftest):
145+
per-core throughput of jauntsdn/netty-websocket-http1 client & server.
146+
147+
### examples
148+
149+
`netty-websocket-http1-perftest` may serve as API showcase for both [client](https://github.com/jauntsdn/netty-websocket-http1/blob/develop/netty-websocket-http1-perftest/src/main/java/com/jauntsdn/netty/handler/codec/http/websocketx/perftest/client/Main.java)
150+
and [server](https://github.com/jauntsdn/netty-websocket-http1/blob/develop/netty-websocket-http1-perftest/src/main/java/com/jauntsdn/netty/handler/codec/http/websocketx/perftest/server/Main.java):
151+
152+
### build & binaries
153+
154+
```
155+
./gradlew
156+
```
157+
158+
Releases are published on MavenCentral
159+
```groovy
160+
repositories {
161+
mavenCentral()
162+
}
163+
164+
dependencies {
165+
implementation "com.jauntsdn.netty:netty-websocket-http1:<VERSION>"
166+
}
167+
```
168+
169+
## LICENSE
170+
171+
Copyright 2022-Present Maksym Ostroverkhov.
172+
173+
Licensed under the Apache License, Version 2.0 (the "License");
174+
you may not use this file except in compliance with the License.
175+
You may obtain a copy of the License at
176+
177+
http://www.apache.org/licenses/LICENSE-2.0
178+
179+
Unless required by applicable law or agreed to in writing, software
180+
distributed under the License is distributed on an "AS IS" BASIS,
181+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
182+
See the License for the specific language governing permissions and
183+
limitations under the License.
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright 2022 - present Maksym Ostroverkhov.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
plugins {
18+
id "application"
19+
}
20+
21+
description = "Perf test for netty based implementation of rfc6455 - the websocket protocol"
22+
23+
dependencies {
24+
implementation project(":netty-websocket-http1-test")
25+
implementation "org.hdrhistogram:HdrHistogram"
26+
implementation "org.slf4j:slf4j-api"
27+
28+
if (osdetector.os == "linux") {
29+
runtimeOnly "io.netty:netty-transport-native-epoll::${osdetector.classifier}"
30+
} else if (osdetector.os == "osx") {
31+
runtimeOnly "io.netty:netty-transport-native-kqueue::${osdetector.classifier}"
32+
}
33+
runtimeOnly "io.netty:netty-tcnative-boringssl-static::${osdetector.classifier}"
34+
runtimeOnly "ch.qos.logback:logback-classic"
35+
}
36+
37+
task runServer(type: JavaExec) {
38+
classpath = sourceSets.main.runtimeClasspath
39+
mainClass = "com.jauntsdn.netty.handler.codec.http.websocketx.perftest.server.Main"
40+
}
41+
42+
task runClient(type: JavaExec) {
43+
classpath = sourceSets.main.runtimeClasspath
44+
mainClass = "com.jauntsdn.netty.handler.codec.http.websocketx.perftest.client.Main"
45+
}
46+
47+
task serverScripts(type: CreateStartScripts) {
48+
mainClass = "com.jauntsdn.netty.handler.codec.http.websocketx.perftest.server.Main"
49+
applicationName = "${project.name}-server"
50+
classpath = startScripts.classpath
51+
outputDir = startScripts.outputDir
52+
}
53+
54+
task clientScripts(type: CreateStartScripts) {
55+
mainClass = "com.jauntsdn.netty.handler.codec.http.websocketx.perftest.client.Main"
56+
applicationName = "${project.name}-client"
57+
classpath = startScripts.classpath
58+
outputDir = startScripts.outputDir
59+
}
60+
61+
startScripts.dependsOn serverScripts
62+
startScripts.dependsOn clientScripts
63+
64+
tasks.named("startScripts") {
65+
enabled = false
66+
}

0 commit comments

Comments
 (0)