Libraryless. Click here for Pure Java version (5863L/43K).
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 = true; |
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 final CharsetDecoder TEXT_DECODER = TEXT_CHARSET.newDecoder(); |
262 | public static final CharsetEncoder TEXT_ENCODER = TEXT_CHARSET.newEncoder(); |
263 | public static String binary2Text(byte[] payload) throws CharacterCodingException { |
264 | return TEXT_DECODER.decode(ByteBuffer.wrap(payload)).toString(); |
265 | } |
266 | public static String binary2Text(byte[] payload, int offset, int length) throws CharacterCodingException { |
267 | return TEXT_DECODER.decode(ByteBuffer.wrap(payload, offset, length)).toString(); |
268 | } |
269 | public static byte[] text2Binary(String payload) throws CharacterCodingException { |
270 | return TEXT_ENCODER.encode(CharBuffer.wrap(payload)).array(); |
271 | } |
272 | @Override |
273 | public String toString() { |
274 | final StringBuilder sb = new StringBuilder("WS["); |
275 | sb.append(getOpCode()); |
276 | sb.append(", ").append(isFin() ? "fin" : "inter"); |
277 | sb.append(", ").append(isMasked() ? "masked" : "unmasked"); |
278 | sb.append(", ").append(payloadToString()); |
279 | sb.append(']'); |
280 | return sb.toString(); |
281 | } |
282 | protected String payloadToString() { |
283 | if (payload == null) return "null"; |
284 | else { |
285 | final StringBuilder sb = new StringBuilder(); |
286 | sb.append('[').append(payload.length).append("b] "); |
287 | if (getOpCode() == WebSocketFrame.OpCode.Text) { |
288 | String text = getTextPayload(); |
289 | if (text.length() > 100) |
290 | sb.append(text.substring(0, 100)).append("..."); |
291 | else |
292 | sb.append(text); |
293 | } else { |
294 | sb.append("0x"); |
295 | for (int i = 0; i < Math.min(payload.length, 50); ++i) |
296 | sb.append(Integer.toHexString((int) payload[i] & 0xFF)); |
297 | if (payload.length > 50) |
298 | sb.append("..."); |
299 | } |
300 | return sb.toString(); |
301 | } |
302 | } |
303 | // --------------------------------CONSTANTS------------------------------- |
304 | public static enum OpCode { |
305 | Continuation(0), Text(1), Binary(2), Close(8), Ping(9), Pong(10); |
306 | private final byte code; |
307 | private OpCode(int code) { |
308 | this.code = (byte) code; |
309 | } |
310 | public byte getValue() { |
311 | return code; |
312 | } |
313 | public boolean isControlFrame() { |
314 | return this == Close || this == Ping || this == Pong; |
315 | } |
316 | public static OpCode find(byte value) { |
317 | for (OpCode opcode : values()) { |
318 | if (opcode.getValue() == value) { |
319 | return opcode; |
320 | } |
321 | } |
322 | return null; |
323 | } |
324 | } |
325 | |
326 | public static enum CloseCode { |
327 | NormalClosure(1000), GoingAway(1001), ProtocolError(1002), UnsupportedData(1003), NoStatusRcvd(1005), |
328 | AbnormalClosure(1006), InvalidFramePayloadData(1007), PolicyViolation(1008), MessageTooBig(1009), |
329 | MandatoryExt(1010), InternalServerError(1011), TLSHandshake(1015); |
330 | private final int code; |
331 | private CloseCode(int code) { |
332 | this.code = code; |
333 | } |
334 | public int getValue() { |
335 | return code; |
336 | } |
337 | public static WebSocketFrame.CloseCode find(int value) { |
338 | for (WebSocketFrame.CloseCode code : values()) { |
339 | if (code.getValue() == value) { |
340 | return code; |
341 | } |
342 | } |
343 | return null; |
344 | } |
345 | } |
346 | // ------------------------------------------------------------------------ |
347 | public static class CloseFrame extends WebSocketFrame { |
348 | private CloseCode _closeCode; |
349 | private String _closeReason; |
350 | private CloseFrame(WebSocketFrame wrap) throws CharacterCodingException { |
351 | super(wrap); |
352 | assert wrap.getOpCode() == OpCode.Close; |
353 | if (wrap.getBinaryPayload().length >= 2) { |
354 | _closeCode = CloseCode.find((wrap.getBinaryPayload()[0] & 0xFF) << 8 | |
355 | (wrap.getBinaryPayload()[1] & 0xFF)); |
356 | _closeReason = binary2Text(getBinaryPayload(), 2, getBinaryPayload().length - 2); |
357 | } |
358 | } |
359 | public CloseFrame(CloseCode code, String closeReason) throws CharacterCodingException { |
360 | super(OpCode.Close, true, generatePayload(code, closeReason)); |
361 | } |
362 | private static byte[] generatePayload(CloseCode code, String closeReason) throws CharacterCodingException { |
363 | if (code != null) { |
364 | byte[] reasonBytes = text2Binary(closeReason); |
365 | byte[] payload = new byte[reasonBytes.length + 2]; |
366 | payload[0] = (byte) ((code.getValue() >> 8) & 0xFF); |
367 | payload[1] = (byte) ((code.getValue()) & 0xFF); |
368 | System.arraycopy(reasonBytes, 0, payload, 2, reasonBytes.length); |
369 | return payload; |
370 | } else { |
371 | return new byte[0]; |
372 | } |
373 | } |
374 | protected String payloadToString() { |
375 | return (_closeCode != null ? _closeCode : "UnknownCloseCode[" + _closeCode + "]") + (_closeReason != null && !_closeReason.isEmpty() ? ": " + _closeReason : ""); |
376 | } |
377 | public CloseCode getCloseCode() { |
378 | return _closeCode; |
379 | } |
380 | public String getCloseReason() { |
381 | return _closeReason; |
382 | } |
383 | } |
384 | } |
385 | |
386 | sclass WebSocket implements AutoCloseable { |
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) { printStackTrace(e); } |
532 | // --------------------------------Public Facade--------------------------- |
533 | public void ping(byte[] payload) throws IOException { |
534 | sendFrame(new WebSocketFrame(WebSocketFrame.OpCode.Ping, true, payload)); |
535 | } |
536 | public void send(byte[] payload) throws IOException { |
537 | sendFrame(new WebSocketFrame(WebSocketFrame.OpCode.Binary, true, payload)); |
538 | } |
539 | public void send(String payload) throws IOException { |
540 | sendFrame(new WebSocketFrame(WebSocketFrame.OpCode.Text, true, payload)); |
541 | } |
542 | public void close(WebSocketFrame.CloseCode code, String reason) throws IOException { |
543 | State oldState = state; |
544 | state = State.CLOSING; |
545 | if (oldState == State.OPEN) { |
546 | sendFrame(new WebSocketFrame.CloseFrame(code, reason)); |
547 | } else { |
548 | doClose(code, reason, false); |
549 | } |
550 | } |
551 | // --------------------------------Getters--------------------------------- |
552 | public NanoHTTPD.IHTTPSession getHandshakeRequest() { |
553 | return handshakeRequest; |
554 | } |
555 | public NanoHTTPD.Response getHandshakeResponse() { |
556 | return handshakeResponse; |
557 | } |
558 | |
559 | // convenience methods |
560 | S getUri() { ret getHandshakeRequest().getUri(); } |
561 | SS getParms() { ret getHandshakeRequest().getParms(); } |
562 | } |
563 | |
564 | sinterface WebSocketFactory { |
565 | WebSocket openWebSocket(NanoHTTPD.IHTTPSession handshake); |
566 | } |
567 | |
568 | sclass NanoWebSocketServer extends NanoHTTPD implements WebSocketFactory { |
569 | public static final String HEADER_UPGRADE = "upgrade"; |
570 | public static final String HEADER_UPGRADE_VALUE = "websocket"; |
571 | public static final String HEADER_CONNECTION = "connection"; |
572 | public static final String HEADER_CONNECTION_VALUE = "Upgrade"; |
573 | public static final String HEADER_WEBSOCKET_VERSION = "sec-websocket-version"; |
574 | public static final String HEADER_WEBSOCKET_VERSION_VALUE = "13"; |
575 | public static final String HEADER_WEBSOCKET_KEY = "sec-websocket-key"; |
576 | public static final String HEADER_WEBSOCKET_ACCEPT = "sec-websocket-accept"; |
577 | public static final String HEADER_WEBSOCKET_PROTOCOL = "sec-websocket-protocol"; |
578 | public final static String WEBSOCKET_KEY_MAGIC = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; |
579 | |
580 | WebSocketFactory webSocketFactory; |
581 | |
582 | public NanoWebSocketServer(int port) { |
583 | super(port); |
584 | webSocketFactory = null; |
585 | } |
586 | |
587 | public NanoWebSocketServer(String hostname, int port) { |
588 | super(hostname, port); |
589 | webSocketFactory = null; |
590 | } |
591 | |
592 | public NanoWebSocketServer(int port, WebSocketFactory webSocketFactory) { |
593 | super(port); |
594 | this.webSocketFactory = webSocketFactory; |
595 | } |
596 | |
597 | public NanoWebSocketServer(String hostname, int port,WebSocketFactory webSocketFactory) { |
598 | super(hostname, port); |
599 | this.webSocketFactory = webSocketFactory; |
600 | } |
601 | |
602 | @Override |
603 | public Response serve(final IHTTPSession session) { |
604 | Map<String, String> headers = session.getHeaders(); |
605 | if (isWebsocketRequested(session)) { |
606 | if (!HEADER_UPGRADE_VALUE.equalsIgnoreCase(headers.get(HEADER_UPGRADE)) |
607 | || !isWebSocketConnectionHeader(session.getHeaders())) { |
608 | return newFixedLengthResponse(Status.BAD_REQUEST, NanoHTTPD.MIME_PLAINTEXT, "Invalid Websocket handshake"); |
609 | } |
610 | if (!HEADER_WEBSOCKET_VERSION_VALUE.equalsIgnoreCase(headers.get(HEADER_WEBSOCKET_VERSION))) { |
611 | return newFixedLengthResponse(Status.BAD_REQUEST, NanoHTTPD.MIME_PLAINTEXT, "Invalid Websocket-Version " + headers.get(HEADER_WEBSOCKET_VERSION)); |
612 | } |
613 | if (!headers.containsKey(HEADER_WEBSOCKET_KEY)) { |
614 | return newFixedLengthResponse(Status.BAD_REQUEST, NanoHTTPD.MIME_PLAINTEXT, "Missing Websocket-Key"); |
615 | } |
616 | WebSocket webSocket = openWebSocket(session); |
617 | try { |
618 | webSocket.getHandshakeResponse().addHeader(HEADER_WEBSOCKET_ACCEPT, makeAcceptKey(headers.get(HEADER_WEBSOCKET_KEY))); |
619 | } catch (NoSuchAlgorithmException e) { |
620 | return newFixedLengthResponse(Status.INTERNAL_ERROR, NanoHTTPD.MIME_PLAINTEXT, "The SHA-1 Algorithm required for websockets is not available on the server."); |
621 | } |
622 | if (headers.containsKey(HEADER_WEBSOCKET_PROTOCOL)) { |
623 | webSocket.getHandshakeResponse().addHeader(HEADER_WEBSOCKET_PROTOCOL, headers.get(HEADER_WEBSOCKET_PROTOCOL).split(",")[0]); |
624 | } |
625 | return webSocket.getHandshakeResponse(); |
626 | } else { |
627 | return super.serve(session); |
628 | } |
629 | } |
630 | |
631 | public WebSocket openWebSocket(IHTTPSession handshake) { |
632 | if (webSocketFactory == null) { |
633 | throw new Error("You must either override this method or supply a WebSocketFactory in the constructor"); |
634 | } |
635 | return webSocketFactory.openWebSocket(handshake); |
636 | } |
637 | |
638 | protected boolean isWebsocketRequested(IHTTPSession session) { |
639 | Map<String, String> headers = session.getHeaders(); |
640 | String upgrade = headers.get(HEADER_UPGRADE); |
641 | boolean isCorrectConnection = isWebSocketConnectionHeader(headers); |
642 | boolean isUpgrade = HEADER_UPGRADE_VALUE.equalsIgnoreCase(upgrade); |
643 | return (isUpgrade && isCorrectConnection); |
644 | } |
645 | |
646 | private boolean isWebSocketConnectionHeader(Map<String, String> headers) { |
647 | String connection = headers.get(HEADER_CONNECTION); |
648 | return (connection != null && connection.toLowerCase().contains(HEADER_CONNECTION_VALUE.toLowerCase())); |
649 | } |
650 | |
651 | public static String makeAcceptKey(String key) throws NoSuchAlgorithmException { |
652 | MessageDigest md = MessageDigest.getInstance("SHA-1"); |
653 | String text = key + WEBSOCKET_KEY_MAGIC; |
654 | md.update(text.getBytes(), 0, text.length()); |
655 | byte[] sha1hash = md.digest(); |
656 | return base64encode(sha1hash); |
657 | } |
658 | } |
Began life as a copy of #1009196
download show line numbers debug dex old transpilations
Travelled to 7 computer(s): bhatertpkbcr, mqqgnosmbjvj, pyentgdyhuwx, pzhvpgtvlbxg, tvejysmllsmz, vouqrxazstgt, xrpafgyirdlv
No comments. add comment
Snippet ID: | #1029499 |
Snippet name: | NanoWebSocketServer [backup before fixing text encoding] |
Eternal ID of this version: | #1029499/1 |
Text MD5: | 6eb1cc7c8790c2dad03b948df0b7c8da |
Transpilation MD5: | 81dc5726d9d951725c71ded72721aa4b |
Author: | stefan |
Category: | javax / networking |
Type: | JavaX fragment (include) |
Public (visible to everyone): | Yes |
Archived (hidden from active list): | Yes |
Created/modified: | 2020-08-16 17:21:39 |
Source code size: | 29718 bytes / 658 lines |
Pitched / IR pitched: | No / No |
Views / Downloads: | 253 / 334 |
Referenced in: | [show references] |