• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
WebSocket(NanoHTTPD.IHTTPSession handshakeRequest)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 
getHandshakeRequest()53     public NanoHTTPD.IHTTPSession getHandshakeRequest() {
54         return handshakeRequest;
55     }
56 
getHandshakeResponse()57     public NanoHTTPD.Response getHandshakeResponse() {
58         return handshakeResponse;
59     }
60 
61     // --------------------------------IO--------------------------------------
62 
readWebsocket()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 
handleWebsocketFrame(WebSocketFrame frame)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 
handleCloseFrame(WebSocketFrame frame)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 
handleFrameFragment(WebSocketFrame frame)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 
sendFrame(WebSocketFrame frame)146     public synchronized void sendFrame(WebSocketFrame frame) throws IOException {
147         frame.write(out);
148     }
149 
150     // --------------------------------Close-----------------------------------
151 
doClose(CloseCode code, String reason, boolean initiatedByRemote)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 
onPong(WebSocketFrame pongFrame)176     protected abstract void onPong(WebSocketFrame pongFrame);
177 
onMessage(WebSocketFrame messageFrame)178     protected abstract void onMessage(WebSocketFrame messageFrame);
179 
onClose(CloseCode code, String reason, boolean initiatedByRemote)180     protected abstract void onClose(CloseCode code, String reason, boolean initiatedByRemote);
181 
onException(IOException e)182     protected abstract void onException(IOException e);
183 
184     // --------------------------------Public Facade---------------------------
185 
ping(byte[] payload)186     public void ping(byte[] payload) throws IOException {
187         sendFrame(new WebSocketFrame(OpCode.Ping, true, payload));
188     }
189 
send(byte[] payload)190     public void send(byte[] payload) throws IOException {
191         sendFrame(new WebSocketFrame(OpCode.Binary, true, payload));
192     }
193 
send(String payload)194     public void send(String payload) throws IOException {
195         sendFrame(new WebSocketFrame(OpCode.Text, true, payload));
196     }
197 
close(CloseCode code, String reason)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