package fi.iki.elonen; /* * #%L * NanoHttpd-Websocket * %% * Copyright (C) 2012 - 2015 nanohttpd * %% * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * 3. Neither the name of the nanohttpd nor the names of its contributors * may be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. * #L% */ import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.charset.CharacterCodingException; import java.nio.charset.Charset; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import fi.iki.elonen.NanoWSD.WebSocketFrame.CloseCode; import fi.iki.elonen.NanoWSD.WebSocketFrame.CloseFrame; import fi.iki.elonen.NanoWSD.WebSocketFrame.OpCode; public abstract class NanoWSD extends NanoHTTPD { public static enum State { UNCONNECTED, CONNECTING, OPEN, CLOSING, CLOSED } public static abstract class WebSocket { private final InputStream in; private OutputStream out; private WebSocketFrame.OpCode continuousOpCode = null; private final List continuousFrames = new LinkedList(); private State state = State.UNCONNECTED; private final NanoHTTPD.IHTTPSession handshakeRequest; private final NanoHTTPD.Response handshakeResponse = new NanoHTTPD.Response(NanoHTTPD.Response.Status.SWITCH_PROTOCOL, null, (InputStream) null, 0) { @Override protected void send(OutputStream out) { WebSocket.this.out = out; WebSocket.this.state = State.CONNECTING; super.send(out); WebSocket.this.state = State.OPEN; WebSocket.this.onOpen(); readWebsocket(); } }; public WebSocket(NanoHTTPD.IHTTPSession handshakeRequest) { this.handshakeRequest = handshakeRequest; this.in = handshakeRequest.getInputStream(); this.handshakeResponse.addHeader(NanoWSD.HEADER_UPGRADE, NanoWSD.HEADER_UPGRADE_VALUE); this.handshakeResponse.addHeader(NanoWSD.HEADER_CONNECTION, NanoWSD.HEADER_CONNECTION_VALUE); } public boolean isOpen() { return state == State.OPEN; } protected abstract void onOpen(); protected abstract void onClose(CloseCode code, String reason, boolean initiatedByRemote); protected abstract void onMessage(WebSocketFrame message); protected abstract void onPong(WebSocketFrame pong); protected abstract void onException(IOException exception); /** * Debug method. Do not Override unless for debug purposes! * * @param frame * The received WebSocket Frame. */ protected void debugFrameReceived(WebSocketFrame frame) { } /** * Debug method. Do not Override unless for debug purposes!
* This method is called before actually sending the frame. * * @param frame * The sent WebSocket Frame. */ protected void debugFrameSent(WebSocketFrame frame) { } public void close(CloseCode code, String reason, boolean initiatedByRemote) throws IOException { State oldState = this.state; this.state = State.CLOSING; if (oldState == State.OPEN) { sendFrame(new CloseFrame(code, reason)); } else { doClose(code, reason, initiatedByRemote); } } private void doClose(CloseCode code, String reason, boolean initiatedByRemote) { if (this.state == State.CLOSED) { return; } if (this.in != null) { try { this.in.close(); } catch (IOException e) { NanoWSD.LOG.log(Level.FINE, "close failed", e); } } if (this.out != null) { try { this.out.close(); } catch (IOException e) { NanoWSD.LOG.log(Level.FINE, "close failed", e); } } this.state = State.CLOSED; onClose(code, reason, initiatedByRemote); } // --------------------------------IO-------------------------------------- public NanoHTTPD.IHTTPSession getHandshakeRequest() { return this.handshakeRequest; } public NanoHTTPD.Response getHandshakeResponse() { return this.handshakeResponse; } private void handleCloseFrame(WebSocketFrame frame) throws IOException { CloseCode code = CloseCode.NormalClosure; String reason = ""; if (frame instanceof CloseFrame) { code = ((CloseFrame) frame).getCloseCode(); reason = ((CloseFrame) frame).getCloseReason(); } if (this.state == State.CLOSING) { // Answer for my requested close doClose(code, reason, false); } else { close(code, reason, true); } } private void handleFrameFragment(WebSocketFrame frame) throws IOException { if (frame.getOpCode() != OpCode.Continuation) { // First if (this.continuousOpCode != null) { throw new WebSocketException(CloseCode.ProtocolError, "Previous continuous frame sequence not completed."); } this.continuousOpCode = frame.getOpCode(); this.continuousFrames.clear(); this.continuousFrames.add(frame); } else if (frame.isFin()) { // Last if (this.continuousOpCode == null) { throw new WebSocketException(CloseCode.ProtocolError, "Continuous frame sequence was not started."); } onMessage(new WebSocketFrame(this.continuousOpCode, this.continuousFrames)); this.continuousOpCode = null; this.continuousFrames.clear(); } else if (this.continuousOpCode == null) { // Unexpected throw new WebSocketException(CloseCode.ProtocolError, "Continuous frame sequence was not started."); } else { // Intermediate this.continuousFrames.add(frame); } } private void handleWebsocketFrame(WebSocketFrame frame) throws IOException { debugFrameReceived(frame); if (frame.getOpCode() == OpCode.Close) { handleCloseFrame(frame); } else if (frame.getOpCode() == OpCode.Ping) { sendFrame(new WebSocketFrame(OpCode.Pong, true, frame.getBinaryPayload())); } else if (frame.getOpCode() == OpCode.Pong) { onPong(frame); } else if (!frame.isFin() || frame.getOpCode() == OpCode.Continuation) { handleFrameFragment(frame); } else if (this.continuousOpCode != null) { throw new WebSocketException(CloseCode.ProtocolError, "Continuous frame sequence not completed."); } else if (frame.getOpCode() == OpCode.Text || frame.getOpCode() == OpCode.Binary) { onMessage(frame); } else { throw new WebSocketException(CloseCode.ProtocolError, "Non control or continuous frame expected."); } } // --------------------------------Close----------------------------------- public void ping(byte[] payload) throws IOException { sendFrame(new WebSocketFrame(OpCode.Ping, true, payload)); } // --------------------------------Public // Facade--------------------------- private void readWebsocket() { try { while (this.state == State.OPEN) { handleWebsocketFrame(WebSocketFrame.read(this.in)); } } catch (CharacterCodingException e) { onException(e); doClose(CloseCode.InvalidFramePayloadData, e.toString(), false); } catch (IOException e) { onException(e); if (e instanceof WebSocketException) { doClose(((WebSocketException) e).getCode(), ((WebSocketException) e).getReason(), false); } } finally { doClose(CloseCode.InternalServerError, "Handler terminated without closing the connection.", false); } } public void send(byte[] payload) throws IOException { sendFrame(new WebSocketFrame(OpCode.Binary, true, payload)); } public void send(String payload) throws IOException { sendFrame(new WebSocketFrame(OpCode.Text, true, payload)); } public synchronized void sendFrame(WebSocketFrame frame) throws IOException { debugFrameSent(frame); frame.write(this.out); } } public static class WebSocketException extends IOException { private static final long serialVersionUID = 1L; private final CloseCode code; private final String reason; public WebSocketException(CloseCode code, String reason) { this(code, reason, null); } public WebSocketException(CloseCode code, String reason, Exception cause) { super(code + ": " + reason, cause); this.code = code; this.reason = reason; } public WebSocketException(Exception cause) { this(CloseCode.InternalServerError, cause.toString(), cause); } public CloseCode getCode() { return this.code; } public String getReason() { return this.reason; } } public static class WebSocketFrame { public static enum CloseCode { NormalClosure(1000), GoingAway(1001), ProtocolError(1002), UnsupportedData(1003), NoStatusRcvd(1005), AbnormalClosure(1006), InvalidFramePayloadData(1007), PolicyViolation(1008), MessageTooBig(1009), MandatoryExt(1010), InternalServerError(1011), TLSHandshake(1015); public static CloseCode find(int value) { for (CloseCode code : values()) { if (code.getValue() == value) { return code; } } return null; } private final int code; private CloseCode(int code) { this.code = code; } public int getValue() { return this.code; } } public static class CloseFrame extends WebSocketFrame { private static byte[] generatePayload(CloseCode code, String closeReason) throws CharacterCodingException { if (code != null) { byte[] reasonBytes = text2Binary(closeReason); byte[] payload = new byte[reasonBytes.length + 2]; payload[0] = (byte) (code.getValue() >> 8 & 0xFF); payload[1] = (byte) (code.getValue() & 0xFF); System.arraycopy(reasonBytes, 0, payload, 2, reasonBytes.length); return payload; } else { return new byte[0]; } } private CloseCode _closeCode; private String _closeReason; public CloseFrame(CloseCode code, String closeReason) throws CharacterCodingException { super(OpCode.Close, true, generatePayload(code, closeReason)); } private CloseFrame(WebSocketFrame wrap) throws CharacterCodingException { super(wrap); assert wrap.getOpCode() == OpCode.Close; if (wrap.getBinaryPayload().length >= 2) { this._closeCode = CloseCode.find((wrap.getBinaryPayload()[0] & 0xFF) << 8 | wrap.getBinaryPayload()[1] & 0xFF); this._closeReason = binary2Text(getBinaryPayload(), 2, getBinaryPayload().length - 2); } } public CloseCode getCloseCode() { return this._closeCode; } public String getCloseReason() { return this._closeReason; } } public static enum OpCode { Continuation(0), Text(1), Binary(2), Close(8), Ping(9), Pong(10); public static OpCode find(byte value) { for (OpCode opcode : values()) { if (opcode.getValue() == value) { return opcode; } } return null; } private final byte code; private OpCode(int code) { this.code = (byte) code; } public byte getValue() { return this.code; } public boolean isControlFrame() { return this == Close || this == Ping || this == Pong; } } public static final Charset TEXT_CHARSET = Charset.forName("UTF-8"); public static String binary2Text(byte[] payload) throws CharacterCodingException { return new String(payload, WebSocketFrame.TEXT_CHARSET); } public static String binary2Text(byte[] payload, int offset, int length) throws CharacterCodingException { return new String(payload, offset, length, WebSocketFrame.TEXT_CHARSET); } private static int checkedRead(int read) throws IOException { if (read < 0) { throw new EOFException(); } return read; } public static WebSocketFrame read(InputStream in) throws IOException { byte head = (byte) checkedRead(in.read()); boolean fin = (head & 0x80) != 0; OpCode opCode = OpCode.find((byte) (head & 0x0F)); if ((head & 0x70) != 0) { throw new WebSocketException(CloseCode.ProtocolError, "The reserved bits (" + Integer.toBinaryString(head & 0x70) + ") must be 0."); } if (opCode == null) { throw new WebSocketException(CloseCode.ProtocolError, "Received frame with reserved/unknown opcode " + (head & 0x0F) + "."); } else if (opCode.isControlFrame() && !fin) { throw new WebSocketException(CloseCode.ProtocolError, "Fragmented control frame."); } WebSocketFrame frame = new WebSocketFrame(opCode, fin); frame.readPayloadInfo(in); frame.readPayload(in); if (frame.getOpCode() == OpCode.Close) { return new CloseFrame(frame); } else { return frame; } } public static byte[] text2Binary(String payload) throws CharacterCodingException { return payload.getBytes(WebSocketFrame.TEXT_CHARSET); } private OpCode opCode; private boolean fin; private byte[] maskingKey; private byte[] payload; // --------------------------------GETTERS--------------------------------- private transient int _payloadLength; private transient String _payloadString; private WebSocketFrame(OpCode opCode, boolean fin) { setOpCode(opCode); setFin(fin); } public WebSocketFrame(OpCode opCode, boolean fin, byte[] payload) { this(opCode, fin, payload, null); } public WebSocketFrame(OpCode opCode, boolean fin, byte[] payload, byte[] maskingKey) { this(opCode, fin); setMaskingKey(maskingKey); setBinaryPayload(payload); } public WebSocketFrame(OpCode opCode, boolean fin, String payload) throws CharacterCodingException { this(opCode, fin, payload, null); } public WebSocketFrame(OpCode opCode, boolean fin, String payload, byte[] maskingKey) throws CharacterCodingException { this(opCode, fin); setMaskingKey(maskingKey); setTextPayload(payload); } public WebSocketFrame(OpCode opCode, List fragments) throws WebSocketException { setOpCode(opCode); setFin(true); long _payloadLength = 0; for (WebSocketFrame inter : fragments) { _payloadLength += inter.getBinaryPayload().length; } if (_payloadLength < 0 || _payloadLength > Integer.MAX_VALUE) { throw new WebSocketException(CloseCode.MessageTooBig, "Max frame length has been exceeded."); } this._payloadLength = (int) _payloadLength; byte[] payload = new byte[this._payloadLength]; int offset = 0; for (WebSocketFrame inter : fragments) { System.arraycopy(inter.getBinaryPayload(), 0, payload, offset, inter.getBinaryPayload().length); offset += inter.getBinaryPayload().length; } setBinaryPayload(payload); } public WebSocketFrame(WebSocketFrame clone) { setOpCode(clone.getOpCode()); setFin(clone.isFin()); setBinaryPayload(clone.getBinaryPayload()); setMaskingKey(clone.getMaskingKey()); } public byte[] getBinaryPayload() { return this.payload; } public byte[] getMaskingKey() { return this.maskingKey; } public OpCode getOpCode() { return this.opCode; } // --------------------------------SERIALIZATION--------------------------- public String getTextPayload() { if (this._payloadString == null) { try { this._payloadString = binary2Text(getBinaryPayload()); } catch (CharacterCodingException e) { throw new RuntimeException("Undetected CharacterCodingException", e); } } return this._payloadString; } public boolean isFin() { return this.fin; } public boolean isMasked() { return this.maskingKey != null && this.maskingKey.length == 4; } private String payloadToString() { if (this.payload == null) { return "null"; } else { final StringBuilder sb = new StringBuilder(); sb.append('[').append(this.payload.length).append("b] "); if (getOpCode() == OpCode.Text) { String text = getTextPayload(); if (text.length() > 100) { sb.append(text.substring(0, 100)).append("..."); } else { sb.append(text); } } else { sb.append("0x"); for (int i = 0; i < Math.min(this.payload.length, 50); ++i) { sb.append(Integer.toHexString(this.payload[i] & 0xFF)); } if (this.payload.length > 50) { sb.append("..."); } } return sb.toString(); } } private void readPayload(InputStream in) throws IOException { this.payload = new byte[this._payloadLength]; int read = 0; while (read < this._payloadLength) { read += checkedRead(in.read(this.payload, read, this._payloadLength - read)); } if (isMasked()) { for (int i = 0; i < this.payload.length; i++) { this.payload[i] ^= this.maskingKey[i % 4]; } } // Test for Unicode errors if (getOpCode() == OpCode.Text) { this._payloadString = binary2Text(getBinaryPayload()); } } // --------------------------------ENCODING-------------------------------- private void readPayloadInfo(InputStream in) throws IOException { byte b = (byte) checkedRead(in.read()); boolean masked = (b & 0x80) != 0; this._payloadLength = (byte) (0x7F & b); if (this._payloadLength == 126) { // checkedRead must return int for this to work this._payloadLength = (checkedRead(in.read()) << 8 | checkedRead(in.read())) & 0xFFFF; if (this._payloadLength < 126) { throw new WebSocketException(CloseCode.ProtocolError, "Invalid data frame 2byte length. (not using minimal length encoding)"); } } else if (this._payloadLength == 127) { long _payloadLength = (long) checkedRead(in.read()) << 56 | (long) checkedRead(in.read()) << 48 | (long) checkedRead(in.read()) << 40 | (long) checkedRead(in.read()) << 32 | checkedRead(in.read()) << 24 | checkedRead(in.read()) << 16 | checkedRead(in.read()) << 8 | checkedRead(in.read()); if (_payloadLength < 65536) { throw new WebSocketException(CloseCode.ProtocolError, "Invalid data frame 4byte length. (not using minimal length encoding)"); } if (_payloadLength < 0 || _payloadLength > Integer.MAX_VALUE) { throw new WebSocketException(CloseCode.MessageTooBig, "Max frame length has been exceeded."); } this._payloadLength = (int) _payloadLength; } if (this.opCode.isControlFrame()) { if (this._payloadLength > 125) { throw new WebSocketException(CloseCode.ProtocolError, "Control frame with payload length > 125 bytes."); } if (this.opCode == OpCode.Close && this._payloadLength == 1) { throw new WebSocketException(CloseCode.ProtocolError, "Received close frame with payload len 1."); } } if (masked) { this.maskingKey = new byte[4]; int read = 0; while (read < this.maskingKey.length) { read += checkedRead(in.read(this.maskingKey, read, this.maskingKey.length - read)); } } } public void setBinaryPayload(byte[] payload) { this.payload = payload; this._payloadLength = payload.length; this._payloadString = null; } public void setFin(boolean fin) { this.fin = fin; } public void setMaskingKey(byte[] maskingKey) { if (maskingKey != null && maskingKey.length != 4) { throw new IllegalArgumentException("MaskingKey " + Arrays.toString(maskingKey) + " hasn't length 4"); } this.maskingKey = maskingKey; } public void setOpCode(OpCode opcode) { this.opCode = opcode; } public void setTextPayload(String payload) throws CharacterCodingException { this.payload = text2Binary(payload); this._payloadLength = payload.length(); this._payloadString = payload; } // --------------------------------CONSTANTS------------------------------- public void setUnmasked() { setMaskingKey(null); } @Override public String toString() { final StringBuilder sb = new StringBuilder("WS["); sb.append(getOpCode()); sb.append(", ").append(isFin() ? "fin" : "inter"); sb.append(", ").append(isMasked() ? "masked" : "unmasked"); sb.append(", ").append(payloadToString()); sb.append(']'); return sb.toString(); } // ------------------------------------------------------------------------ public void write(OutputStream out) throws IOException { byte header = 0; if (this.fin) { header |= 0x80; } header |= this.opCode.getValue() & 0x0F; out.write(header); this._payloadLength = getBinaryPayload().length; if (this._payloadLength <= 125) { out.write(isMasked() ? 0x80 | (byte) this._payloadLength : (byte) this._payloadLength); } else if (this._payloadLength <= 0xFFFF) { out.write(isMasked() ? 0xFE : 126); out.write(this._payloadLength >>> 8); out.write(this._payloadLength); } else { out.write(isMasked() ? 0xFF : 127); out.write(this._payloadLength >>> 56 & 0); // integer only // contains // 31 bit out.write(this._payloadLength >>> 48 & 0); out.write(this._payloadLength >>> 40 & 0); out.write(this._payloadLength >>> 32 & 0); out.write(this._payloadLength >>> 24); out.write(this._payloadLength >>> 16); out.write(this._payloadLength >>> 8); out.write(this._payloadLength); } if (isMasked()) { out.write(this.maskingKey); for (int i = 0; i < this._payloadLength; i++) { out.write(getBinaryPayload()[i] ^ this.maskingKey[i % 4]); } } else { out.write(getBinaryPayload()); } out.flush(); } } /** * logger to log to. */ private static final Logger LOG = Logger.getLogger(NanoWSD.class.getName()); public static final String HEADER_UPGRADE = "upgrade"; public static final String HEADER_UPGRADE_VALUE = "websocket"; public static final String HEADER_CONNECTION = "connection"; public static final String HEADER_CONNECTION_VALUE = "Upgrade"; public static final String HEADER_WEBSOCKET_VERSION = "sec-websocket-version"; public static final String HEADER_WEBSOCKET_VERSION_VALUE = "13"; public static final String HEADER_WEBSOCKET_KEY = "sec-websocket-key"; public static final String HEADER_WEBSOCKET_ACCEPT = "sec-websocket-accept"; public static final String HEADER_WEBSOCKET_PROTOCOL = "sec-websocket-protocol"; private final static String WEBSOCKET_KEY_MAGIC = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; private final static char[] ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray(); /** * Translates the specified byte array into Base64 string. *

* Android has android.util.Base64, sun has sun.misc.Base64Encoder, Java 8 * hast java.util.Base64, I have this from stackoverflow: * http://stackoverflow.com/a/4265472 *

* * @param buf * the byte array (not null) * @return the translated Base64 string (not null) */ private static String encodeBase64(byte[] buf) { int size = buf.length; char[] ar = new char[(size + 2) / 3 * 4]; int a = 0; int i = 0; while (i < size) { byte b0 = buf[i++]; byte b1 = i < size ? buf[i++] : 0; byte b2 = i < size ? buf[i++] : 0; int mask = 0x3F; ar[a++] = NanoWSD.ALPHABET[b0 >> 2 & mask]; ar[a++] = NanoWSD.ALPHABET[(b0 << 4 | (b1 & 0xFF) >> 4) & mask]; ar[a++] = NanoWSD.ALPHABET[(b1 << 2 | (b2 & 0xFF) >> 6) & mask]; ar[a++] = NanoWSD.ALPHABET[b2 & mask]; } switch (size % 3) { case 1: ar[--a] = '='; case 2: ar[--a] = '='; } return new String(ar); } public static String makeAcceptKey(String key) throws NoSuchAlgorithmException { MessageDigest md = MessageDigest.getInstance("SHA-1"); String text = key + NanoWSD.WEBSOCKET_KEY_MAGIC; md.update(text.getBytes(), 0, text.length()); byte[] sha1hash = md.digest(); return encodeBase64(sha1hash); } public NanoWSD(int port) { super(port); } public NanoWSD(String hostname, int port) { super(hostname, port); } private boolean isWebSocketConnectionHeader(Map headers) { String connection = headers.get(NanoWSD.HEADER_CONNECTION); return connection != null && connection.toLowerCase().contains(NanoWSD.HEADER_CONNECTION_VALUE.toLowerCase()); } protected boolean isWebsocketRequested(IHTTPSession session) { Map headers = session.getHeaders(); String upgrade = headers.get(NanoWSD.HEADER_UPGRADE); boolean isCorrectConnection = isWebSocketConnectionHeader(headers); boolean isUpgrade = NanoWSD.HEADER_UPGRADE_VALUE.equalsIgnoreCase(upgrade); return isUpgrade && isCorrectConnection; } // --------------------------------Listener-------------------------------- protected abstract WebSocket openWebSocket(IHTTPSession handshake); @Override public Response serve(final IHTTPSession session) { Map headers = session.getHeaders(); if (isWebsocketRequested(session)) { if (!NanoWSD.HEADER_WEBSOCKET_VERSION_VALUE.equalsIgnoreCase(headers.get(NanoWSD.HEADER_WEBSOCKET_VERSION))) { return newFixedLengthResponse(Response.Status.BAD_REQUEST, NanoHTTPD.MIME_PLAINTEXT, "Invalid Websocket-Version " + headers.get(NanoWSD.HEADER_WEBSOCKET_VERSION)); } if (!headers.containsKey(NanoWSD.HEADER_WEBSOCKET_KEY)) { return newFixedLengthResponse(Response.Status.BAD_REQUEST, NanoHTTPD.MIME_PLAINTEXT, "Missing Websocket-Key"); } WebSocket webSocket = openWebSocket(session); Response handshakeResponse = webSocket.getHandshakeResponse(); try { handshakeResponse.addHeader(NanoWSD.HEADER_WEBSOCKET_ACCEPT, makeAcceptKey(headers.get(NanoWSD.HEADER_WEBSOCKET_KEY))); } catch (NoSuchAlgorithmException e) { return newFixedLengthResponse(Response.Status.INTERNAL_ERROR, NanoHTTPD.MIME_PLAINTEXT, "The SHA-1 Algorithm required for websockets is not available on the server."); } if (headers.containsKey(NanoWSD.HEADER_WEBSOCKET_PROTOCOL)) { handshakeResponse.addHeader(NanoWSD.HEADER_WEBSOCKET_PROTOCOL, headers.get(NanoWSD.HEADER_WEBSOCKET_PROTOCOL).split(",")[0]); } return handshakeResponse; } else { return serveHttp(session); } } protected Response serveHttp(final IHTTPSession session) { return super.serve(session); } /** * not all websockets implementations accept gzip compression. */ @Override protected boolean useGzipWhenAccepted(Response r) { return false; } }