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: | 392 / 514 |
| Referenced in: | [show references] |