Libraryless. Click here for Pure Java version (8664L/56K).
1 | // from https://android.googlesource.com/platform/external/nanohttpd/+/42ff2a9/websocket/src/main/java/fi/iki/elonen/ |
2 | |
3 | import java.nio.charset.*; |
4 | import java.nio.*; |
5 | |
6 | sbool webSocket_debug; |
7 | |
8 | sclass WebSocketException extends IOException {
|
9 | private WebSocketFrame.CloseCode code; |
10 | private String reason; |
11 | public WebSocketException(Exception cause) {
|
12 | this(WebSocketFrame.CloseCode.InternalServerError, cause.toString(), cause); |
13 | } |
14 | public WebSocketException(WebSocketFrame.CloseCode code, String reason) {
|
15 | this(code, reason, null); |
16 | } |
17 | public WebSocketException(WebSocketFrame.CloseCode code, String reason, Exception cause) {
|
18 | super(code + ": " + reason, cause); |
19 | this.code = code; |
20 | this.reason = reason; |
21 | } |
22 | public WebSocketFrame.CloseCode getCode() {
|
23 | return code; |
24 | } |
25 | public String getReason() {
|
26 | return reason; |
27 | } |
28 | } |
29 | |
30 | sclass WebSocketFrame {
|
31 | private OpCode opCode; |
32 | private boolean fin; |
33 | private byte[] maskingKey; |
34 | private byte[] payload; |
35 | private transient int _payloadLength; |
36 | private transient String _payloadString; |
37 | private WebSocketFrame(OpCode opCode, boolean fin) {
|
38 | setOpCode(opCode); |
39 | setFin(fin); |
40 | } |
41 | public WebSocketFrame(OpCode opCode, boolean fin, byte[] payload, byte[] maskingKey) {
|
42 | this(opCode, fin); |
43 | setMaskingKey(maskingKey); |
44 | setBinaryPayload(payload); |
45 | } |
46 | public WebSocketFrame(OpCode opCode, boolean fin, byte[] payload) {
|
47 | this(opCode, fin, payload, null); |
48 | } |
49 | public WebSocketFrame(OpCode opCode, boolean fin, String payload, byte[] maskingKey) throws CharacterCodingException {
|
50 | this(opCode, fin); |
51 | setMaskingKey(maskingKey); |
52 | setTextPayload(payload); |
53 | } |
54 | public WebSocketFrame(OpCode opCode, boolean fin, String payload) throws CharacterCodingException {
|
55 | this(opCode, fin, payload, null); |
56 | } |
57 | public WebSocketFrame(WebSocketFrame clone) {
|
58 | setOpCode(clone.getOpCode()); |
59 | setFin(clone.isFin()); |
60 | setBinaryPayload(clone.getBinaryPayload()); |
61 | setMaskingKey(clone.getMaskingKey()); |
62 | } |
63 | public WebSocketFrame(OpCode opCode, List<WebSocketFrame> fragments) throws WebSocketException {
|
64 | setOpCode(opCode); |
65 | setFin(true); |
66 | long _payloadLength = 0; |
67 | for (WebSocketFrame inter : fragments) {
|
68 | _payloadLength += inter.getBinaryPayload().length; |
69 | } |
70 | if (webSocket_debug) |
71 | print("Payload length with " + nFragments(fragments) + ": " + _payloadLength);
|
72 | if (_payloadLength < 0 || _payloadLength > Integer.MAX_VALUE) {
|
73 | throw new WebSocketException(WebSocketFrame.CloseCode.MessageTooBig, "Max frame length has been exceeded."); |
74 | } |
75 | this._payloadLength = (int) _payloadLength; |
76 | byte[] payload = new byte[this._payloadLength]; |
77 | int offset = 0; |
78 | for (WebSocketFrame inter : fragments) {
|
79 | System.arraycopy(inter.getBinaryPayload(), 0, payload, offset, inter.getBinaryPayload().length); |
80 | offset += inter.getBinaryPayload().length; |
81 | } |
82 | setBinaryPayload(payload); |
83 | } |
84 | // --------------------------------GETTERS--------------------------------- |
85 | public OpCode getOpCode() {
|
86 | return opCode; |
87 | } |
88 | public void setOpCode(OpCode opcode) {
|
89 | this.opCode = opcode; |
90 | } |
91 | public boolean isFin() {
|
92 | return fin; |
93 | } |
94 | public void setFin(boolean fin) {
|
95 | this.fin = fin; |
96 | } |
97 | public boolean isMasked() {
|
98 | return maskingKey != null && maskingKey.length == 4; |
99 | } |
100 | public byte[] getMaskingKey() {
|
101 | return maskingKey; |
102 | } |
103 | public void setMaskingKey(byte[] maskingKey) {
|
104 | if (maskingKey != null && maskingKey.length != 4) {
|
105 | throw new IllegalArgumentException("MaskingKey " + Arrays.toString(maskingKey) + " hasn't length 4");
|
106 | } |
107 | this.maskingKey = maskingKey; |
108 | } |
109 | public void setUnmasked() {
|
110 | setMaskingKey(null); |
111 | } |
112 | public byte[] getBinaryPayload() {
|
113 | return payload; |
114 | } |
115 | public void setBinaryPayload(byte[] payload) {
|
116 | this.payload = payload; |
117 | this._payloadLength = payload.length; |
118 | this._payloadString = null; |
119 | } |
120 | public String getTextPayload() {
|
121 | if (_payloadString == null) {
|
122 | try {
|
123 | _payloadString = binary2Text(getBinaryPayload()); |
124 | } catch (CharacterCodingException e) {
|
125 | throw new RuntimeException("Undetected CharacterCodingException", e);
|
126 | } |
127 | } |
128 | return _payloadString; |
129 | } |
130 | public void setTextPayload(String payload) throws CharacterCodingException {
|
131 | this.payload = text2Binary(payload); |
132 | //this._payloadLength = payload.length(); // buggy! |
133 | this._payloadLength = this.payload.length; |
134 | if (webSocket_debug) print("payload length: " + _payloadLength + ", string length: " + l(payload)); // XXX
|
135 | this._payloadString = payload; |
136 | } |
137 | // --------------------------------SERIALIZATION--------------------------- |
138 | public static WebSocketFrame read(InputStream in) throws IOException {
|
139 | byte head = (byte) checkedRead(in.read()); |
140 | boolean fin = ((head & 0x80) != 0); |
141 | OpCode opCode = OpCode.find((byte) (head & 0x0F)); |
142 | if ((head & 0x70) != 0) {
|
143 | throw new WebSocketException(WebSocketFrame.CloseCode.ProtocolError, "The reserved bits (" + Integer.toBinaryString(head & 0x70) + ") must be 0.");
|
144 | } |
145 | if (opCode == null) {
|
146 | throw new WebSocketException(WebSocketFrame.CloseCode.ProtocolError, "Received frame with reserved/unknown opcode " + (head & 0x0F) + "."); |
147 | } else if (opCode.isControlFrame() && !fin) {
|
148 | throw new WebSocketException(WebSocketFrame.CloseCode.ProtocolError, "Fragmented control frame."); |
149 | } |
150 | WebSocketFrame frame = new WebSocketFrame(opCode, fin); |
151 | frame.readPayloadInfo(in); |
152 | frame.readPayload(in); |
153 | if (frame.getOpCode() == WebSocketFrame.OpCode.Close) {
|
154 | return new WebSocketFrame.CloseFrame(frame); |
155 | } else {
|
156 | return frame; |
157 | } |
158 | } |
159 | private static int checkedRead(int read) throws IOException {
|
160 | if (read < 0) {
|
161 | throw new EOFException(); |
162 | } |
163 | //System.out.println(Integer.toBinaryString(read) + "/" + read + "/" + Integer.toHexString(read)); |
164 | return read; |
165 | } |
166 | private void readPayloadInfo(InputStream in) throws IOException {
|
167 | byte b = (byte) checkedRead(in.read()); |
168 | boolean masked = ((b & 0x80) != 0); |
169 | _payloadLength = (byte) (0x7F & b); |
170 | if (_payloadLength == 126) {
|
171 | // checkedRead must return int for this to work |
172 | _payloadLength = (checkedRead(in.read()) << 8 | checkedRead(in.read())) & 0xFFFF; |
173 | if (_payloadLength < 126) {
|
174 | throw new WebSocketException(WebSocketFrame.CloseCode.ProtocolError, "Invalid data frame 2byte length. (not using minimal length encoding)"); |
175 | } |
176 | } else if (_payloadLength == 127) {
|
177 | long _payloadLength = ((long) checkedRead(in.read())) << 56 | |
178 | ((long) checkedRead(in.read())) << 48 | |
179 | ((long) checkedRead(in.read())) << 40 | |
180 | ((long) checkedRead(in.read())) << 32 | |
181 | checkedRead(in.read()) << 24 | checkedRead(in.read()) << 16 | checkedRead(in.read()) << 8 | checkedRead(in.read()); |
182 | if (_payloadLength < 65536) {
|
183 | throw new WebSocketException(WebSocketFrame.CloseCode.ProtocolError, "Invalid data frame 4byte length. (not using minimal length encoding)"); |
184 | } |
185 | if (_payloadLength < 0 || _payloadLength > Integer.MAX_VALUE) {
|
186 | throw new WebSocketException(WebSocketFrame.CloseCode.MessageTooBig, "Max frame length has been exceeded."); |
187 | } |
188 | this._payloadLength = (int) _payloadLength; |
189 | } |
190 | if (opCode.isControlFrame()) {
|
191 | if (_payloadLength > 125) {
|
192 | throw new WebSocketException(WebSocketFrame.CloseCode.ProtocolError, "Control frame with payload length > 125 bytes."); |
193 | } |
194 | if (opCode == OpCode.Close && _payloadLength == 1) {
|
195 | throw new WebSocketException(WebSocketFrame.CloseCode.ProtocolError, "Received close frame with payload len 1."); |
196 | } |
197 | } |
198 | if (masked) {
|
199 | maskingKey = new byte[4]; |
200 | int read = 0; |
201 | while (read < maskingKey.length) {
|
202 | read += checkedRead(in.read(maskingKey, read, maskingKey.length - read)); |
203 | } |
204 | } |
205 | } |
206 | private void readPayload(InputStream in) throws IOException {
|
207 | payload = new byte[_payloadLength]; |
208 | int read = 0; |
209 | while (read < _payloadLength) {
|
210 | read += checkedRead(in.read(payload, read, _payloadLength - read)); |
211 | } |
212 | if (isMasked()) {
|
213 | for (int i = 0; i < payload.length; i++) {
|
214 | payload[i] ^= maskingKey[i % 4]; |
215 | } |
216 | } |
217 | //Test for Unicode errors |
218 | if (getOpCode() == WebSocketFrame.OpCode.Text) {
|
219 | _payloadString = binary2Text(getBinaryPayload()); |
220 | } |
221 | } |
222 | public void write(OutputStream out) throws IOException {
|
223 | byte header = 0; |
224 | if (fin) {
|
225 | header |= 0x80; |
226 | } |
227 | header |= opCode.getValue() & 0x0F; |
228 | out.write(header); |
229 | _payloadLength = getBinaryPayload().length; |
230 | if (_payloadLength <= 125) {
|
231 | if (webSocket_debug) |
232 | print("Sending short payload: " + _payloadLength);
|
233 | out.write(isMasked() ? 0x80 | (byte) _payloadLength : (byte) _payloadLength); |
234 | } else if (_payloadLength <= 0xFFFF) {
|
235 | out.write(isMasked() ? 0xFE : 126); |
236 | out.write(_payloadLength >>> 8); |
237 | out.write(_payloadLength); |
238 | } else {
|
239 | out.write(isMasked() ? 0xFF : 127); |
240 | out.write(_payloadLength >>> 56 & 0); //integer only contains 31 bit |
241 | out.write(_payloadLength >>> 48 & 0); |
242 | out.write(_payloadLength >>> 40 & 0); |
243 | out.write(_payloadLength >>> 32 & 0); |
244 | out.write(_payloadLength >>> 24); |
245 | out.write(_payloadLength >>> 16); |
246 | out.write(_payloadLength >>> 8); |
247 | out.write(_payloadLength); |
248 | } |
249 | if (isMasked()) {
|
250 | out.write(maskingKey); |
251 | for (int i = 0; i < _payloadLength; i++) {
|
252 | out.write(getBinaryPayload()[i] ^ maskingKey[i % 4]); |
253 | } |
254 | } else {
|
255 | out.write(getBinaryPayload()); |
256 | } |
257 | out.flush(); |
258 | } |
259 | // --------------------------------ENCODING-------------------------------- |
260 | public static final Charset TEXT_CHARSET = Charset.forName("UTF-8");
|
261 | public static String binary2Text(byte[] payload) throws CharacterCodingException {
|
262 | ret new S(payload, TEXT_CHARSET); |
263 | } |
264 | public static String binary2Text(byte[] payload, int offset, int length) throws CharacterCodingException {
|
265 | ret new S(payload, offset, length, TEXT_CHARSET); |
266 | } |
267 | public static byte[] text2Binary(String payload) throws CharacterCodingException {
|
268 | ret payload.getBytes(TEXT_CHARSET); |
269 | } |
270 | @Override |
271 | public String toString() {
|
272 | final StringBuilder sb = new StringBuilder("WS[");
|
273 | sb.append(getOpCode()); |
274 | sb.append(", ").append(isFin() ? "fin" : "inter");
|
275 | sb.append(", ").append(isMasked() ? "masked" : "unmasked");
|
276 | sb.append(", ").append(payloadToString());
|
277 | sb.append(']');
|
278 | return sb.toString(); |
279 | } |
280 | protected String payloadToString() {
|
281 | if (payload == null) return "null"; |
282 | else {
|
283 | final StringBuilder sb = new StringBuilder(); |
284 | sb.append('[').append(payload.length).append("b] ");
|
285 | if (getOpCode() == WebSocketFrame.OpCode.Text) {
|
286 | String text = getTextPayload(); |
287 | if (text.length() > 100) |
288 | sb.append(text.substring(0, 100)).append("...");
|
289 | else |
290 | sb.append(text); |
291 | } else {
|
292 | sb.append("0x");
|
293 | for (int i = 0; i < Math.min(payload.length, 50); ++i) |
294 | sb.append(Integer.toHexString((int) payload[i] & 0xFF)); |
295 | if (payload.length > 50) |
296 | sb.append("...");
|
297 | } |
298 | return sb.toString(); |
299 | } |
300 | } |
301 | // --------------------------------CONSTANTS------------------------------- |
302 | public static enum OpCode {
|
303 | Continuation(0), Text(1), Binary(2), Close(8), Ping(9), Pong(10); |
304 | private final byte code; |
305 | private OpCode(int code) {
|
306 | this.code = (byte) code; |
307 | } |
308 | public byte getValue() {
|
309 | return code; |
310 | } |
311 | public boolean isControlFrame() {
|
312 | return this == Close || this == Ping || this == Pong; |
313 | } |
314 | public static OpCode find(byte value) {
|
315 | for (OpCode opcode : values()) {
|
316 | if (opcode.getValue() == value) {
|
317 | return opcode; |
318 | } |
319 | } |
320 | return null; |
321 | } |
322 | } |
323 | |
324 | public static enum CloseCode {
|
325 | NormalClosure(1000), GoingAway(1001), ProtocolError(1002), UnsupportedData(1003), NoStatusRcvd(1005), |
326 | AbnormalClosure(1006), InvalidFramePayloadData(1007), PolicyViolation(1008), MessageTooBig(1009), |
327 | MandatoryExt(1010), InternalServerError(1011), TLSHandshake(1015); |
328 | private final int code; |
329 | private CloseCode(int code) {
|
330 | this.code = code; |
331 | } |
332 | public int getValue() {
|
333 | return code; |
334 | } |
335 | public static WebSocketFrame.CloseCode find(int value) {
|
336 | for (WebSocketFrame.CloseCode code : values()) {
|
337 | if (code.getValue() == value) {
|
338 | return code; |
339 | } |
340 | } |
341 | return null; |
342 | } |
343 | } |
344 | // ------------------------------------------------------------------------ |
345 | public static class CloseFrame extends WebSocketFrame {
|
346 | private CloseCode _closeCode; |
347 | private String _closeReason; |
348 | private CloseFrame(WebSocketFrame wrap) throws CharacterCodingException {
|
349 | super(wrap); |
350 | assert wrap.getOpCode() == OpCode.Close; |
351 | if (wrap.getBinaryPayload().length >= 2) {
|
352 | _closeCode = CloseCode.find((wrap.getBinaryPayload()[0] & 0xFF) << 8 | |
353 | (wrap.getBinaryPayload()[1] & 0xFF)); |
354 | _closeReason = binary2Text(getBinaryPayload(), 2, getBinaryPayload().length - 2); |
355 | } |
356 | } |
357 | public CloseFrame(CloseCode code, String closeReason) throws CharacterCodingException {
|
358 | super(OpCode.Close, true, generatePayload(code, closeReason)); |
359 | } |
360 | private static byte[] generatePayload(CloseCode code, String closeReason) throws CharacterCodingException {
|
361 | if (code != null) {
|
362 | byte[] reasonBytes = text2Binary(closeReason); |
363 | byte[] payload = new byte[reasonBytes.length + 2]; |
364 | payload[0] = (byte) ((code.getValue() >> 8) & 0xFF); |
365 | payload[1] = (byte) ((code.getValue()) & 0xFF); |
366 | System.arraycopy(reasonBytes, 0, payload, 2, reasonBytes.length); |
367 | return payload; |
368 | } else {
|
369 | return new byte[0]; |
370 | } |
371 | } |
372 | protected String payloadToString() {
|
373 | return (_closeCode != null ? _closeCode : "UnknownCloseCode[" + _closeCode + "]") + (_closeReason != null && !_closeReason.isEmpty() ? ": " + _closeReason : ""); |
374 | } |
375 | public CloseCode getCloseCode() {
|
376 | return _closeCode; |
377 | } |
378 | public String getCloseReason() {
|
379 | return _closeReason; |
380 | } |
381 | } |
382 | } |
383 | |
384 | sclass WebSocket implements AutoCloseable {
|
385 | volatile Object userObject; |
386 | |
387 | protected final InputStream in; |
388 | protected /*final*/ OutputStream out; |
389 | protected WebSocketFrame.OpCode continuousOpCode = null; |
390 | protected List<WebSocketFrame> continuousFrames = new LinkedList<WebSocketFrame>(); |
391 | protected State state = State.UNCONNECTED; |
392 | public static enum State {
|
393 | UNCONNECTED, CONNECTING, OPEN, CLOSING, CLOSED |
394 | } |
395 | protected final NanoHTTPD.IHTTPSession handshakeRequest; |
396 | //protected final NanoHTTPD.Response handshakeResponse = new NanoHTTPD.Response(NanoHTTPD.Status.SWITCH_PROTOCOL, null, (InputStream) null) {
|
397 | protected final NanoHTTPD.Response handshakeResponse = new NanoHTTPD.Response(NanoHTTPD.Status.SWITCH_PROTOCOL, null, (InputStream) null, -1) {
|
398 | @Override |
399 | protected void send(OutputStream out) {
|
400 | WebSocket.this.out = out; |
401 | state = State.CONNECTING; |
402 | super.send(out); |
403 | state = State.OPEN; |
404 | onOpen(); |
405 | readWebsocket(); |
406 | } |
407 | }; |
408 | public WebSocket(NanoHTTPD.IHTTPSession handshakeRequest) {
|
409 | this.handshakeRequest = handshakeRequest; |
410 | this.in = handshakeRequest.getInputStream(); |
411 | handshakeResponse.addHeader(NanoWebSocketServer.HEADER_UPGRADE, NanoWebSocketServer.HEADER_UPGRADE_VALUE); |
412 | handshakeResponse.addHeader(NanoWebSocketServer.HEADER_CONNECTION, NanoWebSocketServer.HEADER_CONNECTION_VALUE); |
413 | } |
414 | // --------------------------------IO-------------------------------------- |
415 | protected void readWebsocket() {
|
416 | try {
|
417 | while (state == State.OPEN) {
|
418 | handleWebsocketFrame(WebSocketFrame.read(in)); |
419 | } |
420 | } catch (CharacterCodingException e) {
|
421 | onException(e); |
422 | doClose(WebSocketFrame.CloseCode.InvalidFramePayloadData, e.toString(), false); |
423 | } catch (IOException e) {
|
424 | onException(e); |
425 | if (e instanceof WebSocketException) {
|
426 | doClose(((WebSocketException) e).getCode(), ((WebSocketException) e).getReason(), false); |
427 | } |
428 | } finally {
|
429 | doClose(WebSocketFrame.CloseCode.InternalServerError, "Handler terminated without closing the connection.", false); |
430 | } |
431 | } |
432 | protected void handleWebsocketFrame(WebSocketFrame frame) throws IOException {
|
433 | if (frame.getOpCode() == WebSocketFrame.OpCode.Close) {
|
434 | handleCloseFrame(frame); |
435 | } else if (frame.getOpCode() == WebSocketFrame.OpCode.Ping) {
|
436 | sendFrame(new WebSocketFrame(WebSocketFrame.OpCode.Pong, true, frame.getBinaryPayload())); |
437 | } else if (frame.getOpCode() == WebSocketFrame.OpCode.Pong) {
|
438 | onPong(frame); |
439 | } else if (!frame.isFin() || frame.getOpCode() == WebSocketFrame.OpCode.Continuation) {
|
440 | handleFrameFragment(frame); |
441 | } else if (continuousOpCode != null) {
|
442 | throw new WebSocketException(WebSocketFrame.CloseCode.ProtocolError, "Continuous frame sequence not completed."); |
443 | } else if (frame.getOpCode() == WebSocketFrame.OpCode.Text || frame.getOpCode() == WebSocketFrame.OpCode.Binary) {
|
444 | onMessage(frame); |
445 | } else {
|
446 | throw new WebSocketException(WebSocketFrame.CloseCode.ProtocolError, "Non control or continuous frame expected."); |
447 | } |
448 | } |
449 | protected void handleCloseFrame(WebSocketFrame frame) throws IOException {
|
450 | WebSocketFrame.CloseCode code = WebSocketFrame.CloseCode.NormalClosure; |
451 | String reason = ""; |
452 | if (frame instanceof WebSocketFrame.CloseFrame) {
|
453 | code = ((WebSocketFrame.CloseFrame) frame).getCloseCode(); |
454 | reason = ((WebSocketFrame.CloseFrame) frame).getCloseReason(); |
455 | } |
456 | if (state == State.CLOSING) {
|
457 | //Answer for my requested close |
458 | doClose(code, reason, false); |
459 | } else {
|
460 | //Answer close request from other endpoint and close self |
461 | State oldState = state; |
462 | state = State.CLOSING; |
463 | if (oldState == State.OPEN) try {
|
464 | sendFrame(new WebSocketFrame.CloseFrame(code, reason)); |
465 | } catch {}
|
466 | doClose(code, reason, true); |
467 | } |
468 | } |
469 | protected void handleFrameFragment(WebSocketFrame frame) throws IOException {
|
470 | if (frame.getOpCode() != WebSocketFrame.OpCode.Continuation) {
|
471 | //First |
472 | if (continuousOpCode != null) {
|
473 | throw new WebSocketException(WebSocketFrame.CloseCode.ProtocolError, "Previous continuous frame sequence not completed."); |
474 | } |
475 | continuousOpCode = frame.getOpCode(); |
476 | continuousFrames.clear(); |
477 | continuousFrames.add(frame); |
478 | } else if (frame.isFin()) {
|
479 | //Last |
480 | if (continuousOpCode == null) {
|
481 | throw new WebSocketException(WebSocketFrame.CloseCode.ProtocolError, "Continuous frame sequence was not started."); |
482 | } |
483 | onMessage(new WebSocketFrame(continuousOpCode, continuousFrames)); |
484 | continuousOpCode = null; |
485 | continuousFrames.clear(); |
486 | } else if (continuousOpCode == null) {
|
487 | //Unexpected |
488 | throw new WebSocketException(WebSocketFrame.CloseCode.ProtocolError, "Continuous frame sequence was not started."); |
489 | } else {
|
490 | //Intermediate |
491 | continuousFrames.add(frame); |
492 | } |
493 | } |
494 | public synchronized void sendFrame(WebSocketFrame frame) throws IOException {
|
495 | frame.write(out); |
496 | } |
497 | // --------------------------------Close----------------------------------- |
498 | |
499 | // deliberate closing by server, e.g. because of module reload |
500 | public void close() {
|
501 | doClose(WebSocketFrame.CloseCode.GoingAway, "Internal closing", false); |
502 | } |
503 | |
504 | protected void doClose(WebSocketFrame.CloseCode code, String reason, boolean initiatedByRemote) {
|
505 | if (state == State.CLOSED) {
|
506 | return; |
507 | } |
508 | if (in != null) {
|
509 | try {
|
510 | in.close(); |
511 | } catch (IOException e) {
|
512 | e.printStackTrace(); |
513 | } |
514 | } |
515 | if (out != null) {
|
516 | try {
|
517 | out.close(); |
518 | } catch (IOException e) {
|
519 | e.printStackTrace(); |
520 | } |
521 | } |
522 | state = State.CLOSED; |
523 | onClose(code, reason, initiatedByRemote); |
524 | } |
525 | // --------------------------------Listener-------------------------------- |
526 | protected void onPong(WebSocketFrame pongFrame) { print("WebSocket pong"); }
|
527 | swappable void onMessage(WebSocketFrame messageFrame) { print("WebSocket msg: " + messageFrame.getTextPayload()); }
|
528 | swappable void onOpen() {}
|
529 | protected void onClose(WebSocketFrame.CloseCode code, String reason, boolean initiatedByRemote) { print("WebSocket close"); onClose(); }
|
530 | swappable void onClose() {}
|
531 | protected void onException(IOException e) {
|
532 | printExceptionShort(e); //printStackTrace(e); |
533 | } |
534 | // --------------------------------Public Facade--------------------------- |
535 | public void ping(byte[] payload) throws IOException {
|
536 | sendFrame(new WebSocketFrame(WebSocketFrame.OpCode.Ping, true, payload)); |
537 | } |
538 | public void send(byte[] payload) throws IOException {
|
539 | sendFrame(new WebSocketFrame(WebSocketFrame.OpCode.Binary, true, payload)); |
540 | } |
541 | public void send(String payload) throws IOException {
|
542 | sendFrame(new WebSocketFrame(WebSocketFrame.OpCode.Text, true, payload)); |
543 | } |
544 | public void close(WebSocketFrame.CloseCode code, String reason) throws IOException {
|
545 | State oldState = state; |
546 | state = State.CLOSING; |
547 | if (oldState == State.OPEN) {
|
548 | sendFrame(new WebSocketFrame.CloseFrame(code, reason)); |
549 | } else {
|
550 | doClose(code, reason, false); |
551 | } |
552 | } |
553 | // --------------------------------Getters--------------------------------- |
554 | public NanoHTTPD.IHTTPSession getHandshakeRequest() {
|
555 | return handshakeRequest; |
556 | } |
557 | public NanoHTTPD.Response getHandshakeResponse() {
|
558 | return handshakeResponse; |
559 | } |
560 | |
561 | // convenience methods |
562 | S getUri() { ret getHandshakeRequest().getUri(); }
|
563 | SS getParms() { ret getHandshakeRequest().getParms(); }
|
564 | O getUserObject() { ret userObject; }
|
565 | void setUserObject(O o) { userObject = o; }
|
566 | } |
567 | |
568 | sinterface WebSocketFactory {
|
569 | WebSocket openWebSocket(NanoHTTPD.IHTTPSession handshake); |
570 | } |
571 | |
572 | sclass NanoWebSocketServer extends NanoHTTPD implements WebSocketFactory {
|
573 | public static final String HEADER_UPGRADE = "upgrade"; |
574 | public static final String HEADER_UPGRADE_VALUE = "websocket"; |
575 | public static final String HEADER_CONNECTION = "connection"; |
576 | public static final String HEADER_CONNECTION_VALUE = "Upgrade"; |
577 | public static final String HEADER_WEBSOCKET_VERSION = "sec-websocket-version"; |
578 | public static final String HEADER_WEBSOCKET_VERSION_VALUE = "13"; |
579 | public static final String HEADER_WEBSOCKET_KEY = "sec-websocket-key"; |
580 | public static final String HEADER_WEBSOCKET_ACCEPT = "sec-websocket-accept"; |
581 | public static final String HEADER_WEBSOCKET_PROTOCOL = "sec-websocket-protocol"; |
582 | public final static String WEBSOCKET_KEY_MAGIC = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; |
583 | |
584 | WebSocketFactory webSocketFactory; |
585 | |
586 | public NanoWebSocketServer(int port) {
|
587 | super(port); |
588 | webSocketFactory = null; |
589 | } |
590 | |
591 | public NanoWebSocketServer(String hostname, int port) {
|
592 | super(hostname, port); |
593 | webSocketFactory = null; |
594 | } |
595 | |
596 | public NanoWebSocketServer(int port, WebSocketFactory webSocketFactory) {
|
597 | super(port); |
598 | this.webSocketFactory = webSocketFactory; |
599 | } |
600 | |
601 | public NanoWebSocketServer(String hostname, int port,WebSocketFactory webSocketFactory) {
|
602 | super(hostname, port); |
603 | this.webSocketFactory = webSocketFactory; |
604 | } |
605 | |
606 | @Override |
607 | public Response serve_2(IHTTPSession session) {
|
608 | SS headers = session.getHeaders(); |
609 | |
610 | if (isWebsocketRequested(session)) {
|
611 | if (!HEADER_UPGRADE_VALUE.equalsIgnoreCase(headers.get(HEADER_UPGRADE)) |
612 | || !isWebSocketConnectionHeader(session.getHeaders())) {
|
613 | return newFixedLengthResponse(Status.BAD_REQUEST, NanoHTTPD.MIME_PLAINTEXT, "Invalid Websocket handshake"); |
614 | } |
615 | if (!HEADER_WEBSOCKET_VERSION_VALUE.equalsIgnoreCase(headers.get(HEADER_WEBSOCKET_VERSION))) {
|
616 | return newFixedLengthResponse(Status.BAD_REQUEST, NanoHTTPD.MIME_PLAINTEXT, "Invalid Websocket-Version " + headers.get(HEADER_WEBSOCKET_VERSION)); |
617 | } |
618 | if (!headers.containsKey(HEADER_WEBSOCKET_KEY)) {
|
619 | return newFixedLengthResponse(Status.BAD_REQUEST, NanoHTTPD.MIME_PLAINTEXT, "Missing Websocket-Key"); |
620 | } |
621 | WebSocket webSocket = openWebSocket(session); |
622 | try {
|
623 | webSocket.getHandshakeResponse().addHeader(HEADER_WEBSOCKET_ACCEPT, makeAcceptKey(headers.get(HEADER_WEBSOCKET_KEY))); |
624 | } catch (NoSuchAlgorithmException e) {
|
625 | return newFixedLengthResponse(Status.INTERNAL_ERROR, NanoHTTPD.MIME_PLAINTEXT, "The SHA-1 Algorithm required for websockets is not available on the server."); |
626 | } |
627 | if (headers.containsKey(HEADER_WEBSOCKET_PROTOCOL)) {
|
628 | webSocket.getHandshakeResponse().addHeader(HEADER_WEBSOCKET_PROTOCOL, headers.get(HEADER_WEBSOCKET_PROTOCOL).split(",")[0]);
|
629 | } |
630 | return webSocket.getHandshakeResponse(); |
631 | } else {
|
632 | return super.serve_2(session); |
633 | } |
634 | } |
635 | |
636 | public WebSocket openWebSocket(IHTTPSession handshake) {
|
637 | if (webSocketFactory == null) {
|
638 | throw new Error("You must either override this method or supply a WebSocketFactory in the constructor");
|
639 | } |
640 | return webSocketFactory.openWebSocket(handshake); |
641 | } |
642 | |
643 | protected boolean isWebsocketRequested(IHTTPSession session) {
|
644 | Map<String, String> headers = session.getHeaders(); |
645 | String upgrade = headers.get(HEADER_UPGRADE); |
646 | boolean isCorrectConnection = isWebSocketConnectionHeader(headers); |
647 | boolean isUpgrade = HEADER_UPGRADE_VALUE.equalsIgnoreCase(upgrade); |
648 | return (isUpgrade && isCorrectConnection); |
649 | } |
650 | |
651 | private boolean isWebSocketConnectionHeader(Map<String, String> headers) {
|
652 | String connection = headers.get(HEADER_CONNECTION); |
653 | return (connection != null && connection.toLowerCase().contains(HEADER_CONNECTION_VALUE.toLowerCase())); |
654 | } |
655 | |
656 | public static String makeAcceptKey(String key) throws NoSuchAlgorithmException {
|
657 | MessageDigest md = MessageDigest.getInstance("SHA-1");
|
658 | String text = key + WEBSOCKET_KEY_MAGIC; |
659 | md.update(text.getBytes(), 0, text.length()); |
660 | byte[] sha1hash = md.digest(); |
661 | return base64encode(sha1hash); |
662 | } |
663 | } |
download show line numbers debug dex old transpilations
Travelled to 14 computer(s): aoiabmzegqzx, bhatertpkbcr, cbybwowwnfue, cfunsshuasjs, gwrvuhgaqvyk, ishqpsrjomds, lpdgvwnxivlt, mqqgnosmbjvj, pyentgdyhuwx, pzhvpgtvlbxg, tslmcundralx, tvejysmllsmz, vouqrxazstgt, xrpafgyirdlv
No comments. add comment
| Snippet ID: | #1009196 |
| Snippet name: | NanoWebSocketServer |
| Eternal ID of this version: | #1009196/47 |
| Text MD5: | e49cbdba83be65ff71a980e7ff88371e |
| Transpilation MD5: | f90833c5bfe7c6d69f53ed3329ff3995 |
| Author: | stefan |
| Category: | javax / networking |
| Type: | JavaX fragment (include) |
| Public (visible to everyone): | Yes |
| Archived (hidden from active list): | No |
| Created/modified: | 2021-10-03 21:27:10 |
| Source code size: | 29611 bytes / 663 lines |
| Pitched / IR pitched: | No / No |
| Views / Downloads: | 1099 / 2069 |
| Version history: | 46 change(s) |
| Referenced in: | [show references] |