Not logged in.  Login/Logout/Register | List snippets | | Create snippet | Upload image | Upload data

658
LINES

< > BotCompany Repo | #1029499 // NanoWebSocketServer [backup before fixing text encoding]

JavaX fragment (include) [tags: archive use-pretranspiled]

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  
}

Author comment

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