1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.jetty.websocket.common.io;
20
21 import java.io.IOException;
22 import java.util.List;
23 import java.util.concurrent.CopyOnWriteArrayList;
24
25 import org.eclipse.jetty.util.log.Log;
26 import org.eclipse.jetty.util.log.Logger;
27 import org.eclipse.jetty.websocket.api.StatusCode;
28 import org.eclipse.jetty.websocket.common.CloseInfo;
29 import org.eclipse.jetty.websocket.common.ConnectionState;
30
31
32
33
34
35
36
37
38
39 public class IOState
40 {
41
42
43
44 private static enum CloseHandshakeSource
45 {
46
47 NONE,
48
49 LOCAL,
50
51 REMOTE,
52
53 ABNORMAL;
54 }
55
56 public static interface ConnectionStateListener
57 {
58 public void onConnectionStateChange(ConnectionState state);
59 }
60
61 private static final Logger LOG = Log.getLogger(IOState.class);
62 private ConnectionState state;
63 private final List<ConnectionStateListener> listeners = new CopyOnWriteArrayList<>();
64
65 private boolean inputAvailable;
66 private boolean outputAvailable;
67 private CloseHandshakeSource closeHandshakeSource;
68 private CloseInfo closeInfo;
69 private boolean cleanClose;
70
71
72
73
74 public IOState()
75 {
76 this.state = ConnectionState.CONNECTING;
77 this.inputAvailable = false;
78 this.outputAvailable = false;
79 this.closeHandshakeSource = CloseHandshakeSource.NONE;
80 this.closeInfo = null;
81 this.cleanClose = false;
82 }
83
84 public void addListener(ConnectionStateListener listener)
85 {
86 listeners.add(listener);
87 }
88
89 public void assertInputOpen() throws IOException
90 {
91 if (!isInputAvailable())
92 {
93 throw new IOException("Connection input is closed");
94 }
95 }
96
97 public void assertOutputOpen() throws IOException
98 {
99 if (!isOutputAvailable())
100 {
101 throw new IOException("Connection output is closed");
102 }
103 }
104
105 public CloseInfo getCloseInfo()
106 {
107 return closeInfo;
108 }
109
110 public ConnectionState getConnectionState()
111 {
112 return state;
113 }
114
115 public boolean isClosed()
116 {
117 synchronized (state)
118 {
119 return (state == ConnectionState.CLOSED);
120 }
121 }
122
123 public boolean isInputAvailable()
124 {
125 return inputAvailable;
126 }
127
128 public boolean isOpen()
129 {
130 return (getConnectionState() != ConnectionState.CLOSED);
131 }
132
133 public boolean isOutputAvailable()
134 {
135 return outputAvailable;
136 }
137
138 private void notifyStateListeners(ConnectionState state)
139 {
140 for (ConnectionStateListener listener : listeners)
141 {
142 listener.onConnectionStateChange(state);
143 }
144 }
145
146
147
148
149
150
151 public void onAbnormalClose(CloseInfo close)
152 {
153 LOG.debug("onAbnormalClose({})",close);
154 ConnectionState event = null;
155 synchronized (this)
156 {
157 if (this.state == ConnectionState.CLOSED)
158 {
159
160 return;
161 }
162
163 if (this.state == ConnectionState.OPEN)
164 {
165 this.cleanClose = false;
166 }
167
168 this.state = ConnectionState.CLOSED;
169 if (closeInfo == null)
170 this.closeInfo = close;
171 this.inputAvailable = false;
172 this.outputAvailable = false;
173 this.closeHandshakeSource = CloseHandshakeSource.ABNORMAL;
174 event = this.state;
175 }
176 notifyStateListeners(event);
177 }
178
179
180
181
182 public void onCloseLocal(CloseInfo close)
183 {
184 ConnectionState event = null;
185 ConnectionState initialState = this.state;
186 LOG.debug("onCloseLocal({}) : {}",close,initialState);
187 if (initialState == ConnectionState.CLOSED)
188 {
189
190 LOG.debug("already closed");
191 return;
192 }
193
194 if (initialState == ConnectionState.CONNECTED)
195 {
196
197 LOG.debug("FastClose in CONNECTED detected");
198
199 onOpened();
200 }
201
202 synchronized (this)
203 {
204 if (closeInfo == null)
205 closeInfo = close;
206
207 boolean in = inputAvailable;
208 boolean out = outputAvailable;
209 if (closeHandshakeSource == CloseHandshakeSource.NONE)
210 {
211 closeHandshakeSource = CloseHandshakeSource.LOCAL;
212 }
213 out = false;
214 outputAvailable = false;
215
216 LOG.debug("onCloseLocal(), input={}, output={}",in,out);
217
218 if (!in && !out)
219 {
220 LOG.debug("Close Handshake satisfied, disconnecting");
221 cleanClose = true;
222 this.state = ConnectionState.CLOSED;
223 event = this.state;
224 }
225 else if (this.state == ConnectionState.OPEN)
226 {
227
228 this.state = ConnectionState.CLOSING;
229 event = this.state;
230 }
231 }
232
233
234 if (event != null)
235 {
236 LOG.debug("notifying state listeners: {}",event);
237 notifyStateListeners(event);
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257 }
258 }
259
260
261
262
263 public void onCloseRemote(CloseInfo close)
264 {
265 LOG.debug("onCloseRemote({})",close);
266 ConnectionState event = null;
267 synchronized (this)
268 {
269 if (this.state == ConnectionState.CLOSED)
270 {
271
272 return;
273 }
274
275 if (closeInfo == null)
276 closeInfo = close;
277
278 boolean in = inputAvailable;
279 boolean out = outputAvailable;
280 if (closeHandshakeSource == CloseHandshakeSource.NONE)
281 {
282 closeHandshakeSource = CloseHandshakeSource.REMOTE;
283 }
284 in = false;
285 inputAvailable = false;
286
287 LOG.debug("onCloseRemote(), input={}, output={}",in,out);
288
289 if (!in && !out)
290 {
291 LOG.debug("Close Handshake satisfied, disconnecting");
292 cleanClose = true;
293 state = ConnectionState.CLOSED;
294 event = this.state;
295 }
296 else if (this.state == ConnectionState.OPEN)
297 {
298
299 this.state = ConnectionState.CLOSING;
300 event = this.state;
301 }
302 }
303
304
305 if (event != null)
306 {
307 notifyStateListeners(event);
308 }
309 }
310
311
312
313
314
315
316 public void onConnected()
317 {
318 if (this.state != ConnectionState.CONNECTING)
319 {
320 LOG.debug("Unable to set to connected, not in CONNECTING state: {}",this.state);
321 return;
322 }
323
324 ConnectionState event = null;
325 synchronized (this)
326 {
327 this.state = ConnectionState.CONNECTED;
328 inputAvailable = false;
329 outputAvailable = true;
330 event = this.state;
331 }
332 notifyStateListeners(event);
333 }
334
335
336
337
338 public void onFailedUpgrade()
339 {
340 assert (this.state == ConnectionState.CONNECTING);
341 ConnectionState event = null;
342 synchronized (this)
343 {
344 this.state = ConnectionState.CLOSED;
345 cleanClose = false;
346 inputAvailable = false;
347 outputAvailable = false;
348 event = this.state;
349 }
350 notifyStateListeners(event);
351 }
352
353
354
355
356 public void onOpened()
357 {
358 if (this.state != ConnectionState.CONNECTED)
359 {
360 LOG.debug("Unable to open, not in CONNECTED state: {}",this.state);
361 return;
362 }
363
364 assert (this.state == ConnectionState.CONNECTED);
365
366 ConnectionState event = null;
367 synchronized (this)
368 {
369 this.state = ConnectionState.OPEN;
370 this.inputAvailable = true;
371 this.outputAvailable = true;
372 event = this.state;
373 }
374 notifyStateListeners(event);
375 }
376
377
378
379
380
381
382 public void onReadEOF()
383 {
384 ConnectionState event = null;
385 synchronized (this)
386 {
387 if (this.state == ConnectionState.CLOSED)
388 {
389
390 return;
391 }
392
393 CloseInfo close = new CloseInfo(StatusCode.NO_CLOSE,"Read EOF");
394
395 this.cleanClose = false;
396 this.state = ConnectionState.CLOSED;
397 if (closeInfo == null)
398 this.closeInfo = close;
399 this.inputAvailable = false;
400 this.outputAvailable = false;
401 this.closeHandshakeSource = CloseHandshakeSource.ABNORMAL;
402 event = this.state;
403 }
404 notifyStateListeners(event);
405 }
406
407 public boolean wasAbnormalClose()
408 {
409 return closeHandshakeSource == CloseHandshakeSource.ABNORMAL;
410 }
411
412 public boolean wasCleanClose()
413 {
414 return cleanClose;
415 }
416
417 public boolean wasLocalCloseInitiated()
418 {
419 return closeHandshakeSource == CloseHandshakeSource.LOCAL;
420 }
421
422 public boolean wasRemoteCloseInitiated()
423 {
424 return closeHandshakeSource == CloseHandshakeSource.REMOTE;
425 }
426 }