1 package fi.iki.elonen; 2 3 import fi.iki.elonen.WebSocketFrame.CloseCode; 4 import fi.iki.elonen.WebSocketFrame.CloseFrame; 5 import fi.iki.elonen.WebSocketFrame.OpCode; 6 7 import java.io.IOException; 8 import java.io.InputStream; 9 import java.io.OutputStream; 10 import java.nio.charset.CharacterCodingException; 11 import java.util.LinkedList; 12 import java.util.List; 13 14 public abstract class WebSocket { 15 public static enum State { 16 UNCONNECTED, CONNECTING, OPEN, CLOSING, CLOSED 17 } 18 19 protected InputStream in; 20 21 protected OutputStream out; 22 23 protected WebSocketFrame.OpCode continuousOpCode = null; 24 25 protected List<WebSocketFrame> continuousFrames = new LinkedList<WebSocketFrame>(); 26 27 protected State state = State.UNCONNECTED; 28 29 protected final NanoHTTPD.IHTTPSession handshakeRequest; 30 31 protected final NanoHTTPD.Response handshakeResponse = new NanoHTTPD.Response( 32 NanoHTTPD.Response.Status.SWITCH_PROTOCOL, null, (InputStream) null) { 33 @Override 34 protected void send(OutputStream out) { 35 WebSocket.this.out = out; 36 state = State.CONNECTING; 37 super.send(out); 38 state = State.OPEN; 39 readWebsocket(); 40 } 41 }; 42 43 public WebSocket(NanoHTTPD.IHTTPSession handshakeRequest) { 44 this.handshakeRequest = handshakeRequest; 45 this.in = handshakeRequest.getInputStream(); 46 47 handshakeResponse.addHeader(WebSocketResponseHandler.HEADER_UPGRADE, 48 WebSocketResponseHandler.HEADER_UPGRADE_VALUE); 49 handshakeResponse.addHeader(WebSocketResponseHandler.HEADER_CONNECTION, 50 WebSocketResponseHandler.HEADER_CONNECTION_VALUE); 51 } 52 53 public NanoHTTPD.IHTTPSession getHandshakeRequest() { 54 return handshakeRequest; 55 } 56 57 public NanoHTTPD.Response getHandshakeResponse() { 58 return handshakeResponse; 59 } 60 61 // --------------------------------IO-------------------------------------- 62 63 protected void readWebsocket() { 64 try { 65 while (state == State.OPEN) { 66 handleWebsocketFrame(WebSocketFrame.read(in)); 67 } 68 } catch (CharacterCodingException e) { 69 onException(e); 70 doClose(CloseCode.InvalidFramePayloadData, e.toString(), false); 71 } catch (IOException e) { 72 onException(e); 73 if (e instanceof WebSocketException) { 74 doClose(((WebSocketException) e).getCode(), ((WebSocketException) e).getReason(), false); 75 } 76 } finally { 77 doClose(CloseCode.InternalServerError, "Handler terminated without closing the connection.", false); 78 } 79 } 80 81 protected void handleWebsocketFrame(WebSocketFrame frame) throws IOException { 82 if (frame.getOpCode() == OpCode.Close) { 83 handleCloseFrame(frame); 84 } else if (frame.getOpCode() == OpCode.Ping) { 85 sendFrame(new WebSocketFrame(OpCode.Pong, true, frame.getBinaryPayload())); 86 } else if (frame.getOpCode() == OpCode.Pong) { 87 onPong(frame); 88 } else if (!frame.isFin() || frame.getOpCode() == OpCode.Continuation) { 89 handleFrameFragment(frame); 90 } else if (continuousOpCode != null) { 91 throw new WebSocketException(CloseCode.ProtocolError, "Continuous frame sequence not completed."); 92 } else if (frame.getOpCode() == OpCode.Text || frame.getOpCode() == OpCode.Binary) { 93 onMessage(frame); 94 } else { 95 throw new WebSocketException(CloseCode.ProtocolError, "Non control or continuous frame expected."); 96 } 97 } 98 99 protected void handleCloseFrame(WebSocketFrame frame) throws IOException { 100 CloseCode code = CloseCode.NormalClosure; 101 String reason = ""; 102 if (frame instanceof CloseFrame) { 103 code = ((CloseFrame) frame).getCloseCode(); 104 reason = ((CloseFrame) frame).getCloseReason(); 105 } 106 if (state == State.CLOSING) { 107 //Answer for my requested close 108 doClose(code, reason, false); 109 } else { 110 //Answer close request from other endpoint and close self 111 State oldState = state; 112 state = State.CLOSING; 113 if (oldState == State.OPEN) { 114 sendFrame(new CloseFrame(code, reason)); 115 } 116 doClose(code, reason, true); 117 } 118 } 119 120 protected void handleFrameFragment(WebSocketFrame frame) throws IOException { 121 if (frame.getOpCode() != OpCode.Continuation) { 122 //First 123 if (continuousOpCode != null) { 124 throw new WebSocketException(CloseCode.ProtocolError, "Previous continuous frame sequence not completed."); 125 } 126 continuousOpCode = frame.getOpCode(); 127 continuousFrames.clear(); 128 continuousFrames.add(frame); 129 } else if (frame.isFin()) { 130 //Last 131 if (continuousOpCode == null) { 132 throw new WebSocketException(CloseCode.ProtocolError, "Continuous frame sequence was not started."); 133 } 134 onMessage(new WebSocketFrame(continuousOpCode, continuousFrames)); 135 continuousOpCode = null; 136 continuousFrames.clear(); 137 } else if (continuousOpCode == null) { 138 //Unexpected 139 throw new WebSocketException(CloseCode.ProtocolError, "Continuous frame sequence was not started."); 140 } else { 141 //Intermediate 142 continuousFrames.add(frame); 143 } 144 } 145 146 public synchronized void sendFrame(WebSocketFrame frame) throws IOException { 147 frame.write(out); 148 } 149 150 // --------------------------------Close----------------------------------- 151 152 protected void doClose(CloseCode code, String reason, boolean initiatedByRemote) { 153 if (state == State.CLOSED) { 154 return; 155 } 156 if (in != null) { 157 try { 158 in.close(); 159 } catch (IOException e) { 160 e.printStackTrace(); 161 } 162 } 163 if (out != null) { 164 try { 165 out.close(); 166 } catch (IOException e) { 167 e.printStackTrace(); 168 } 169 } 170 state = State.CLOSED; 171 onClose(code, reason, initiatedByRemote); 172 } 173 174 // --------------------------------Listener-------------------------------- 175 176 protected abstract void onPong(WebSocketFrame pongFrame); 177 178 protected abstract void onMessage(WebSocketFrame messageFrame); 179 180 protected abstract void onClose(CloseCode code, String reason, boolean initiatedByRemote); 181 182 protected abstract void onException(IOException e); 183 184 // --------------------------------Public Facade--------------------------- 185 186 public void ping(byte[] payload) throws IOException { 187 sendFrame(new WebSocketFrame(OpCode.Ping, true, payload)); 188 } 189 190 public void send(byte[] payload) throws IOException { 191 sendFrame(new WebSocketFrame(OpCode.Binary, true, payload)); 192 } 193 194 public void send(String payload) throws IOException { 195 sendFrame(new WebSocketFrame(OpCode.Text, true, payload)); 196 } 197 198 public void close(CloseCode code, String reason) throws IOException { 199 State oldState = state; 200 state = State.CLOSING; 201 if (oldState == State.OPEN) { 202 sendFrame(new CloseFrame(code, reason)); 203 } else { 204 doClose(code, reason, false); 205 } 206 } 207 } 208