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