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.EOFException;
22 import java.io.IOException;
23 import java.net.InetSocketAddress;
24 import java.net.SocketTimeoutException;
25 import java.nio.ByteBuffer;
26 import java.util.ArrayList;
27 import java.util.List;
28 import java.util.concurrent.Executor;
29 import java.util.concurrent.RejectedExecutionException;
30 import java.util.concurrent.atomic.AtomicBoolean;
31 import java.util.concurrent.atomic.AtomicLong;
32
33 import org.eclipse.jetty.io.AbstractConnection;
34 import org.eclipse.jetty.io.ByteBufferPool;
35 import org.eclipse.jetty.io.EndPoint;
36 import org.eclipse.jetty.util.BufferUtil;
37 import org.eclipse.jetty.util.component.ContainerLifeCycle;
38 import org.eclipse.jetty.util.component.Dumpable;
39 import org.eclipse.jetty.util.log.Log;
40 import org.eclipse.jetty.util.log.Logger;
41 import org.eclipse.jetty.util.thread.Scheduler;
42 import org.eclipse.jetty.websocket.api.BatchMode;
43 import org.eclipse.jetty.websocket.api.CloseException;
44 import org.eclipse.jetty.websocket.api.StatusCode;
45 import org.eclipse.jetty.websocket.api.SuspendToken;
46 import org.eclipse.jetty.websocket.api.WebSocketPolicy;
47 import org.eclipse.jetty.websocket.api.WriteCallback;
48 import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
49 import org.eclipse.jetty.websocket.api.extensions.Frame;
50 import org.eclipse.jetty.websocket.common.CloseInfo;
51 import org.eclipse.jetty.websocket.common.ConnectionState;
52 import org.eclipse.jetty.websocket.common.Generator;
53 import org.eclipse.jetty.websocket.common.LogicalConnection;
54 import org.eclipse.jetty.websocket.common.Parser;
55 import org.eclipse.jetty.websocket.common.WebSocketSession;
56 import org.eclipse.jetty.websocket.common.io.IOState.ConnectionStateListener;
57
58
59
60
61 public abstract class AbstractWebSocketConnection extends AbstractConnection implements LogicalConnection, ConnectionStateListener, Dumpable
62 {
63 private class Flusher extends FrameFlusher
64 {
65 private Flusher(ByteBufferPool bufferPool, Generator generator, EndPoint endpoint)
66 {
67 super(bufferPool,generator,endpoint,getPolicy().getMaxBinaryMessageBufferSize(),8);
68 }
69
70 @Override
71 protected void onFailure(Throwable x)
72 {
73 session.notifyError(x);
74
75 if (ioState.wasAbnormalClose())
76 {
77 LOG.ignore(x);
78 return;
79 }
80
81 if (LOG.isDebugEnabled())
82 LOG.debug("Write flush failure",x);
83 ioState.onWriteFailure(x);
84 }
85 }
86
87 public class OnDisconnectCallback implements WriteCallback
88 {
89 private final boolean outputOnly;
90
91 public OnDisconnectCallback(boolean outputOnly)
92 {
93 this.outputOnly = outputOnly;
94 }
95
96 @Override
97 public void writeFailed(Throwable x)
98 {
99 disconnect(outputOnly);
100 }
101
102 @Override
103 public void writeSuccess()
104 {
105 disconnect(outputOnly);
106 }
107 }
108
109 public class OnCloseLocalCallback implements WriteCallback
110 {
111 private final WriteCallback callback;
112 private final CloseInfo close;
113
114 public OnCloseLocalCallback(WriteCallback callback, CloseInfo close)
115 {
116 this.callback = callback;
117 this.close = close;
118 }
119
120 public OnCloseLocalCallback(CloseInfo close)
121 {
122 this(null,close);
123 }
124
125 @Override
126 public void writeFailed(Throwable x)
127 {
128 try
129 {
130 if (callback != null)
131 {
132 callback.writeFailed(x);
133 }
134 }
135 finally
136 {
137 onLocalClose();
138 }
139 }
140
141 @Override
142 public void writeSuccess()
143 {
144 try
145 {
146 if (callback != null)
147 {
148 callback.writeSuccess();
149 }
150 }
151 finally
152 {
153 onLocalClose();
154 }
155 }
156
157 private void onLocalClose()
158 {
159 if (LOG.isDebugEnabled())
160 LOG.debug("Local Close Confirmed {}",close);
161 if (close.isAbnormal())
162 {
163 ioState.onAbnormalClose(close);
164 }
165 else
166 {
167 ioState.onCloseLocal(close);
168 }
169 }
170 }
171
172 public static class Stats
173 {
174 private AtomicLong countFillInterestedEvents = new AtomicLong(0);
175 private AtomicLong countOnFillableEvents = new AtomicLong(0);
176 private AtomicLong countFillableErrors = new AtomicLong(0);
177
178 public long getFillableErrorCount()
179 {
180 return countFillableErrors.get();
181 }
182
183 public long getFillInterestedCount()
184 {
185 return countFillInterestedEvents.get();
186 }
187
188 public long getOnFillableCount()
189 {
190 return countOnFillableEvents.get();
191 }
192 }
193
194 private static enum ReadMode
195 {
196 PARSE,
197 DISCARD,
198 EOF
199 }
200
201 private static final Logger LOG = Log.getLogger(AbstractWebSocketConnection.class);
202
203
204
205
206 private static final int MIN_BUFFER_SIZE = Generator.MAX_HEADER_LENGTH;
207
208 private final ByteBufferPool bufferPool;
209 private final Scheduler scheduler;
210 private final Generator generator;
211 private final Parser parser;
212 private final WebSocketPolicy policy;
213 private final AtomicBoolean suspendToken;
214 private final FrameFlusher flusher;
215 private WebSocketSession session;
216 private List<ExtensionConfig> extensions;
217 private boolean isFilling;
218 private ByteBuffer buffer;
219 private ReadMode readMode = ReadMode.PARSE;
220 private IOState ioState;
221 private Stats stats = new Stats();
222
223 public AbstractWebSocketConnection(EndPoint endp, Executor executor, Scheduler scheduler, WebSocketPolicy policy, ByteBufferPool bufferPool)
224 {
225 super(endp,executor,EXECUTE_ONFILLABLE);
226 this.policy = policy;
227 this.bufferPool = bufferPool;
228 this.generator = new Generator(policy,bufferPool);
229 this.parser = new Parser(policy,bufferPool);
230 this.scheduler = scheduler;
231 this.extensions = new ArrayList<>();
232 this.suspendToken = new AtomicBoolean(false);
233 this.ioState = new IOState();
234 this.ioState.addListener(this);
235 this.flusher = new Flusher(bufferPool,generator,endp);
236 this.setInputBufferSize(policy.getInputBufferSize());
237 this.setMaxIdleTimeout(policy.getIdleTimeout());
238 }
239
240 @Override
241 public Executor getExecutor()
242 {
243 return super.getExecutor();
244 }
245
246 @Override
247 public void close()
248 {
249 close(StatusCode.NORMAL,null);
250 }
251
252
253
254
255
256
257
258
259
260
261
262
263
264 @Override
265 public void close(int statusCode, String reason)
266 {
267 if (LOG.isDebugEnabled())
268 LOG.debug("close({},{})",statusCode,reason);
269 CloseInfo close = new CloseInfo(statusCode,reason);
270 this.outgoingFrame(close.asFrame(),new OnCloseLocalCallback(close),BatchMode.OFF);
271 }
272
273 @Override
274 public void disconnect()
275 {
276 disconnect(false);
277 }
278
279 private void disconnect(boolean onlyOutput)
280 {
281 if (LOG.isDebugEnabled())
282 LOG.debug("{} disconnect({})",policy.getBehavior(),onlyOutput?"outputOnly":"both");
283
284 flusher.close();
285 EndPoint endPoint = getEndPoint();
286
287
288 if (LOG.isDebugEnabled())
289 LOG.debug("Shutting down output {}",endPoint);
290 endPoint.shutdownOutput();
291 if (!onlyOutput)
292 {
293 LOG.debug("Closing {}",endPoint);
294 endPoint.close();
295 }
296 }
297
298 protected void execute(Runnable task)
299 {
300 try
301 {
302 getExecutor().execute(task);
303 }
304 catch (RejectedExecutionException e)
305 {
306 if (LOG.isDebugEnabled())
307 LOG.debug("Job not dispatched: {}",task);
308 }
309 }
310
311 @Override
312 public void fillInterested()
313 {
314 stats.countFillInterestedEvents.incrementAndGet();
315 super.fillInterested();
316 }
317
318 @Override
319 public ByteBufferPool getBufferPool()
320 {
321 return bufferPool;
322 }
323
324
325
326
327
328
329
330
331 public List<ExtensionConfig> getExtensions()
332 {
333 return extensions;
334 }
335
336 public Generator getGenerator()
337 {
338 return generator;
339 }
340
341 @Override
342 public long getIdleTimeout()
343 {
344 return getEndPoint().getIdleTimeout();
345 }
346
347 @Override
348 public IOState getIOState()
349 {
350 return ioState;
351 }
352
353 @Override
354 public long getMaxIdleTimeout()
355 {
356 return getEndPoint().getIdleTimeout();
357 }
358
359 public Parser getParser()
360 {
361 return parser;
362 }
363
364 @Override
365 public WebSocketPolicy getPolicy()
366 {
367 return this.policy;
368 }
369
370 @Override
371 public InetSocketAddress getRemoteAddress()
372 {
373 return getEndPoint().getRemoteAddress();
374 }
375
376 public Scheduler getScheduler()
377 {
378 return scheduler;
379 }
380
381 @Override
382 public WebSocketSession getSession()
383 {
384 return session;
385 }
386
387 public Stats getStats()
388 {
389 return stats;
390 }
391
392 @Override
393 public boolean isOpen()
394 {
395 return getIOState().isOpen() && getEndPoint().isOpen();
396 }
397
398 @Override
399 public boolean isReading()
400 {
401 return isFilling;
402 }
403
404
405
406
407
408
409 @Override
410 public void onClose()
411 {
412 if (LOG.isDebugEnabled())
413 LOG.debug("{} onClose()",policy.getBehavior());
414 super.onClose();
415
416 flusher.close();
417 }
418
419 @Override
420 public void onConnectionStateChange(ConnectionState state)
421 {
422 if (LOG.isDebugEnabled())
423 LOG.debug("{} Connection State Change: {}",policy.getBehavior(),state);
424 switch (state)
425 {
426 case OPEN:
427 if (BufferUtil.isEmpty(buffer))
428 {
429 if (LOG.isDebugEnabled())
430 LOG.debug("fillInterested");
431 fillInterested();
432 }
433 else
434 onFillable();
435 break;
436 case CLOSED:
437 if (ioState.wasAbnormalClose())
438 {
439
440 CloseInfo abnormal = new CloseInfo(StatusCode.SHUTDOWN,"Abnormal Close - " + ioState.getCloseInfo().getReason());
441 outgoingFrame(abnormal.asFrame(),new OnDisconnectCallback(false),BatchMode.OFF);
442 }
443 else
444 {
445
446 this.disconnect(false);
447 }
448 break;
449 case CLOSING:
450
451 if (ioState.wasRemoteCloseInitiated())
452 {
453 CloseInfo close = ioState.getCloseInfo();
454
455 outgoingFrame(close.asFrame(),new OnCloseLocalCallback(new OnDisconnectCallback(true),close),BatchMode.OFF);
456 }
457 default:
458 break;
459 }
460 }
461
462 @Override
463 public void onFillable()
464 {
465 if (LOG.isDebugEnabled())
466 LOG.debug("{} onFillable()",policy.getBehavior());
467 stats.countOnFillableEvents.incrementAndGet();
468 if (buffer==null)
469 buffer = bufferPool.acquire(getInputBufferSize(),true);
470 try
471 {
472 isFilling = true;
473
474 if(readMode == ReadMode.PARSE)
475 {
476 readMode = readParse(buffer);
477 }
478 else
479 {
480 readMode = readDiscard(buffer);
481 }
482 }
483 finally
484 {
485 bufferPool.release(buffer);
486 buffer=null;
487 }
488
489 if ((readMode != ReadMode.EOF) && (suspendToken.get() == false))
490 {
491 fillInterested();
492 }
493 else
494 {
495 isFilling = false;
496 }
497 }
498
499
500
501 @Override
502 protected void onFillInterestedFailed(Throwable cause)
503 {
504 LOG.ignore(cause);
505 stats.countFillInterestedEvents.incrementAndGet();
506 super.onFillInterestedFailed(cause);
507 }
508
509 protected void prefill(ByteBuffer prefilled)
510 {
511 buffer=prefilled;
512 }
513
514 @Override
515 public void onOpen()
516 {
517 super.onOpen();
518 this.ioState.onOpened();
519 }
520
521
522
523
524 @Override
525 protected boolean onReadTimeout()
526 {
527 IOState state = getIOState();
528 ConnectionState cstate = state.getConnectionState();
529 if (LOG.isDebugEnabled())
530 LOG.debug("{} Read Timeout - {}",policy.getBehavior(),cstate);
531
532 if (cstate == ConnectionState.CLOSED)
533 {
534
535
536 return true;
537 }
538
539 try
540 {
541 session.notifyError(new SocketTimeoutException("Timeout on Read"));
542 }
543 finally
544 {
545
546 close(StatusCode.SHUTDOWN,"Idle Timeout");
547 }
548
549 return false;
550 }
551
552
553
554
555 @Override
556 public void outgoingFrame(Frame frame, WriteCallback callback, BatchMode batchMode)
557 {
558 if (LOG.isDebugEnabled())
559 {
560 LOG.debug("outgoingFrame({}, {})",frame,callback);
561 }
562
563 flusher.enqueue(frame,callback,batchMode);
564 }
565
566 private ReadMode readDiscard(ByteBuffer buffer)
567 {
568 EndPoint endPoint = getEndPoint();
569 try
570 {
571 while (true)
572 {
573 int filled = endPoint.fill(buffer);
574 if (filled == 0)
575 {
576 return ReadMode.DISCARD;
577 }
578 else if (filled < 0)
579 {
580 LOG.debug("read - EOF Reached (remote: {})",getRemoteAddress());
581 return ReadMode.EOF;
582 }
583 else
584 {
585 if (LOG.isDebugEnabled())
586 {
587 LOG.debug("Discarded {} bytes - {}",filled,BufferUtil.toDetailString(buffer));
588 }
589 }
590 }
591 }
592 catch (IOException e)
593 {
594 LOG.ignore(e);
595 return ReadMode.EOF;
596 }
597 catch (Throwable t)
598 {
599 LOG.ignore(t);
600 return ReadMode.DISCARD;
601 }
602 }
603
604 private ReadMode readParse(ByteBuffer buffer)
605 {
606 EndPoint endPoint = getEndPoint();
607 try
608 {
609 while (true)
610 {
611 int filled = endPoint.fill(buffer);
612 if (filled == 0)
613 {
614 return ReadMode.PARSE;
615 }
616 else if (filled < 0)
617 {
618 LOG.debug("read - EOF Reached (remote: {})",getRemoteAddress());
619 ioState.onReadFailure(new EOFException("Remote Read EOF"));
620 return ReadMode.EOF;
621 }
622 else
623 {
624 if (LOG.isDebugEnabled())
625 {
626 LOG.debug("Filled {} bytes - {}",filled,BufferUtil.toDetailString(buffer));
627 }
628 parser.parse(buffer);
629 }
630 }
631 }
632 catch (IOException e)
633 {
634 LOG.warn(e);
635 close(StatusCode.PROTOCOL,e.getMessage());
636 return ReadMode.DISCARD;
637 }
638 catch (CloseException e)
639 {
640 LOG.debug(e);
641 close(e.getStatusCode(),e.getMessage());
642 return ReadMode.DISCARD;
643 }
644 catch (Throwable t)
645 {
646 LOG.warn(t);
647 close(StatusCode.ABNORMAL,t.getMessage());
648
649 return ReadMode.DISCARD;
650 }
651 }
652
653 @Override
654 public void resume()
655 {
656 if (suspendToken.getAndSet(false))
657 {
658 fillInterested();
659 }
660 }
661
662
663
664
665
666
667
668
669
670 public void setExtensions(List<ExtensionConfig> extensions)
671 {
672 this.extensions = extensions;
673 }
674
675 @Override
676 public void setInputBufferSize(int inputBufferSize)
677 {
678 if (inputBufferSize < MIN_BUFFER_SIZE)
679 {
680 throw new IllegalArgumentException("Cannot have buffer size less than " + MIN_BUFFER_SIZE);
681 }
682 super.setInputBufferSize(inputBufferSize);
683 }
684
685 @Override
686 public void setMaxIdleTimeout(long ms)
687 {
688 getEndPoint().setIdleTimeout(ms);
689 }
690
691 @Override
692 public void setSession(WebSocketSession session)
693 {
694 this.session = session;
695 }
696
697 @Override
698 public SuspendToken suspend()
699 {
700 suspendToken.set(true);
701 return this;
702 }
703
704 @Override
705 public String dump()
706 {
707 return ContainerLifeCycle.dump(this);
708 }
709
710 @Override
711 public void dump(Appendable out, String indent) throws IOException
712 {
713 out.append(toString()).append(System.lineSeparator());
714 }
715
716 @Override
717 public String toString()
718 {
719 return String.format("%s{f=%s,g=%s,p=%s}",super.toString(),flusher,generator,parser);
720 }
721
722 }