|
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. |
0 commit comments