1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.jetty.server;
20
21 import java.util.ArrayList;
22 import java.util.List;
23 import java.util.concurrent.TimeUnit;
24
25 import javax.servlet.AsyncListener;
26 import javax.servlet.RequestDispatcher;
27 import javax.servlet.ServletContext;
28 import javax.servlet.ServletResponse;
29
30 import org.eclipse.jetty.server.handler.ContextHandler;
31 import org.eclipse.jetty.server.handler.ContextHandler.Context;
32 import org.eclipse.jetty.util.log.Log;
33 import org.eclipse.jetty.util.log.Logger;
34 import org.eclipse.jetty.util.thread.Scheduler;
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54 public class HttpChannelState
55 {
56 private static final Logger LOG = Log.getLogger(HttpChannelState.class);
57
58 private final static long DEFAULT_TIMEOUT=30000L;
59
60 public enum State
61 {
62 IDLE,
63 DISPATCHED,
64 ASYNCWAIT,
65 ASYNCIO,
66 COMPLETING,
67 COMPLETED
68 }
69
70 public enum Action
71 {
72 REQUEST_DISPATCH,
73 ASYNC_DISPATCH,
74 ASYNC_EXPIRED,
75 WRITE_CALLBACK,
76 READ_CALLBACK,
77 WAIT,
78 COMPLETE
79 }
80
81 public enum Async
82 {
83 STARTED,
84 DISPATCH,
85 COMPLETE,
86 EXPIRING,
87 EXPIRED
88 }
89
90 private final boolean DEBUG=LOG.isDebugEnabled();
91 private final HttpChannel<?> _channel;
92
93 private List<AsyncListener> _asyncListeners;
94 private State _state;
95 private Async _async;
96 private boolean _initial;
97 private boolean _asyncRead;
98 private boolean _asyncWrite;
99 private long _timeoutMs=DEFAULT_TIMEOUT;
100 private AsyncContextEvent _event;
101
102 protected HttpChannelState(HttpChannel<?> channel)
103 {
104 _channel=channel;
105 _state=State.IDLE;
106 _async=null;
107 _initial=true;
108 }
109
110 public State getState()
111 {
112 synchronized(this)
113 {
114 return _state;
115 }
116 }
117
118 public void addListener(AsyncListener listener)
119 {
120 synchronized(this)
121 {
122 if (_asyncListeners==null)
123 _asyncListeners=new ArrayList<>();
124 _asyncListeners.add(listener);
125 }
126 }
127
128 public void setTimeout(long ms)
129 {
130 synchronized(this)
131 {
132 _timeoutMs=ms;
133 }
134 }
135
136 public long getTimeout()
137 {
138 synchronized(this)
139 {
140 return _timeoutMs;
141 }
142 }
143
144 public AsyncContextEvent getAsyncContextEvent()
145 {
146 synchronized(this)
147 {
148 return _event;
149 }
150 }
151
152 @Override
153 public String toString()
154 {
155 synchronized (this)
156 {
157 return String.format("%s@%x{s=%s i=%b a=%s}",getClass().getSimpleName(),hashCode(),_state,_initial,_async);
158 }
159 }
160
161 public String getStatusString()
162 {
163 synchronized (this)
164 {
165 return String.format("s=%s i=%b a=%s",_state,_initial,_async);
166 }
167 }
168
169
170
171
172 protected Action handling()
173 {
174 synchronized (this)
175 {
176 if(DEBUG)
177 LOG.debug("{} handling {}",this,_state);
178 switch(_state)
179 {
180 case IDLE:
181 _initial=true;
182 _state=State.DISPATCHED;
183 return Action.REQUEST_DISPATCH;
184
185 case COMPLETING:
186 return Action.COMPLETE;
187
188 case COMPLETED:
189 return Action.WAIT;
190
191 case ASYNCWAIT:
192 if (_asyncRead)
193 {
194 _state=State.ASYNCIO;
195 _asyncRead=false;
196 return Action.READ_CALLBACK;
197 }
198 if (_asyncWrite)
199 {
200 _state=State.ASYNCIO;
201 _asyncWrite=false;
202 return Action.WRITE_CALLBACK;
203 }
204
205 if (_async!=null)
206 {
207 Async async=_async;
208 switch(async)
209 {
210 case COMPLETE:
211 _state=State.COMPLETING;
212 return Action.COMPLETE;
213 case DISPATCH:
214 _state=State.DISPATCHED;
215 _async=null;
216 return Action.ASYNC_DISPATCH;
217 case EXPIRING:
218 break;
219 case EXPIRED:
220 _state=State.DISPATCHED;
221 _async=null;
222 return Action.ASYNC_EXPIRED;
223 case STARTED:
224 if (DEBUG)
225 LOG.debug("TODO Fix this double dispatch",new IllegalStateException(this
226 .getStatusString()));
227 return Action.WAIT;
228 }
229 }
230
231 return Action.WAIT;
232
233 default:
234 throw new IllegalStateException(this.getStatusString());
235 }
236 }
237 }
238
239 public void startAsync(AsyncContextEvent event)
240 {
241 final List<AsyncListener> lastAsyncListeners;
242
243 synchronized (this)
244 {
245 if (_state!=State.DISPATCHED || _async!=null)
246 throw new IllegalStateException(this.getStatusString());
247
248 _async=Async.STARTED;
249 _event=event;
250 lastAsyncListeners=_asyncListeners;
251 _asyncListeners=null;
252 }
253
254 if (lastAsyncListeners!=null)
255 {
256 for (AsyncListener listener : lastAsyncListeners)
257 {
258 try
259 {
260 listener.onStartAsync(event);
261 }
262 catch(Exception e)
263 {
264 LOG.warn(e);
265 }
266 }
267 }
268 }
269
270 protected void error(Throwable th)
271 {
272 synchronized (this)
273 {
274 if (_event!=null)
275 _event.setThrowable(th);
276 }
277 }
278
279
280
281
282
283
284
285
286 protected Action unhandle()
287 {
288 synchronized (this)
289 {
290 if(DEBUG)
291 LOG.debug("{} unhandle {}",this,_state);
292
293 switch(_state)
294 {
295 case DISPATCHED:
296 case ASYNCIO:
297 break;
298 default:
299 throw new IllegalStateException(this.getStatusString());
300 }
301
302 if (_asyncRead)
303 {
304 _state=State.ASYNCIO;
305 _asyncRead=false;
306 return Action.READ_CALLBACK;
307 }
308
309 if (_asyncWrite)
310 {
311 _asyncWrite=false;
312 _state=State.ASYNCIO;
313 return Action.WRITE_CALLBACK;
314 }
315
316 if (_async!=null)
317 {
318 _initial=false;
319 switch(_async)
320 {
321 case COMPLETE:
322 _state=State.COMPLETING;
323 _async=null;
324 return Action.COMPLETE;
325 case DISPATCH:
326 _state=State.DISPATCHED;
327 _async=null;
328 return Action.ASYNC_DISPATCH;
329 case EXPIRED:
330 _state=State.DISPATCHED;
331 _async=null;
332 return Action.ASYNC_EXPIRED;
333 case EXPIRING:
334 case STARTED:
335 scheduleTimeout();
336 _state=State.ASYNCWAIT;
337 return Action.WAIT;
338 }
339 }
340
341 _state=State.COMPLETING;
342 return Action.COMPLETE;
343 }
344 }
345
346 public void dispatch(ServletContext context, String path)
347 {
348 boolean dispatch;
349 synchronized (this)
350 {
351 if (_async!=Async.STARTED && _async!=Async.EXPIRING)
352 throw new IllegalStateException("AsyncContext#dispath "+this.getStatusString());
353 _async=Async.DISPATCH;
354 _event.setDispatchTarget(context,path);
355
356 switch(_state)
357 {
358 case DISPATCHED:
359 case ASYNCIO:
360 dispatch=false;
361 break;
362 default:
363 dispatch=true;
364 break;
365 }
366 }
367
368 cancelTimeout();
369 if (dispatch)
370 scheduleDispatch();
371 }
372
373 protected void expired()
374 {
375 final List<AsyncListener> aListeners;
376 AsyncContextEvent event;
377 synchronized (this)
378 {
379 if (_async!=Async.STARTED)
380 return;
381 _async=Async.EXPIRING;
382 event=_event;
383 aListeners=_asyncListeners;
384 }
385
386 if (aListeners!=null)
387 {
388 for (AsyncListener listener : aListeners)
389 {
390 try
391 {
392 listener.onTimeout(event);
393 }
394 catch(Exception e)
395 {
396 LOG.debug(e);
397 event.setThrowable(e);
398 _channel.getRequest().setAttribute(RequestDispatcher.ERROR_EXCEPTION,e);
399 break;
400 }
401 }
402 }
403
404 boolean dispatch=false;
405 synchronized (this)
406 {
407 if (_async==Async.EXPIRING)
408 {
409 _async=Async.EXPIRED;
410 if (_state==State.ASYNCWAIT)
411 dispatch=true;
412 }
413 }
414
415 if (dispatch)
416 scheduleDispatch();
417 }
418
419 public void complete()
420 {
421
422 boolean handle;
423 synchronized (this)
424 {
425 if (_async!=Async.STARTED && _async!=Async.EXPIRING)
426 throw new IllegalStateException(this.getStatusString());
427 _async=Async.COMPLETE;
428 handle=_state==State.ASYNCWAIT;
429 }
430
431 cancelTimeout();
432 if (handle)
433 {
434 ContextHandler handler=getContextHandler();
435 if (handler!=null)
436 handler.handle(_channel);
437 else
438 _channel.handle();
439 }
440 }
441
442 protected void completed()
443 {
444 final List<AsyncListener> aListeners;
445 final AsyncContextEvent event;
446 synchronized (this)
447 {
448 switch(_state)
449 {
450 case COMPLETING:
451 _state=State.COMPLETED;
452 aListeners=_asyncListeners;
453 event=_event;
454 break;
455
456 default:
457 throw new IllegalStateException(this.getStatusString());
458 }
459 }
460
461 if (event!=null)
462 {
463 if (aListeners!=null)
464 {
465 if (event.getThrowable()!=null)
466 {
467 event.getSuppliedRequest().setAttribute(RequestDispatcher.ERROR_EXCEPTION,event.getThrowable());
468 event.getSuppliedRequest().setAttribute(RequestDispatcher.ERROR_MESSAGE,event.getThrowable().getMessage());
469 }
470
471 for (AsyncListener listener : aListeners)
472 {
473 try
474 {
475 if (event.getThrowable()!=null)
476 listener.onError(event);
477 else
478 listener.onComplete(event);
479 }
480 catch(Exception e)
481 {
482 LOG.warn(e);
483 }
484 }
485 }
486
487 event.completed();
488 }
489 }
490
491 protected void recycle()
492 {
493 synchronized (this)
494 {
495 switch(_state)
496 {
497 case DISPATCHED:
498 case ASYNCIO:
499 throw new IllegalStateException(getStatusString());
500 default:
501 break;
502 }
503 _asyncListeners=null;
504 _state=State.IDLE;
505 _async=null;
506 _initial=true;
507 _asyncRead=false;
508 _asyncWrite=false;
509 _timeoutMs=DEFAULT_TIMEOUT;
510 cancelTimeout();
511 _event=null;
512 }
513 }
514
515 protected void scheduleDispatch()
516 {
517 _channel.execute(_channel);
518 }
519
520 protected void scheduleTimeout()
521 {
522 Scheduler scheduler = _channel.getScheduler();
523 if (scheduler!=null && _timeoutMs>0)
524 _event.setTimeoutTask(scheduler.schedule(new AsyncTimeout(),_timeoutMs,TimeUnit.MILLISECONDS));
525 }
526
527 protected void cancelTimeout()
528 {
529 final AsyncContextEvent event;
530 synchronized (this)
531 {
532 event=_event;
533 }
534 if (event!=null)
535 event.cancelTimeoutTask();
536 }
537
538 public boolean isExpired()
539 {
540 synchronized (this)
541 {
542 return _async==Async.EXPIRED;
543 }
544 }
545
546 public boolean isInitial()
547 {
548 synchronized(this)
549 {
550 return _initial;
551 }
552 }
553
554 public boolean isSuspended()
555 {
556 synchronized(this)
557 {
558 return _state==State.ASYNCWAIT || _state==State.DISPATCHED && _async==Async.STARTED;
559 }
560 }
561
562 boolean isCompleting()
563 {
564 synchronized (this)
565 {
566 return _state==State.COMPLETING;
567 }
568 }
569
570 boolean isCompleted()
571 {
572 synchronized (this)
573 {
574 return _state == State.COMPLETED;
575 }
576 }
577
578 public boolean isAsyncStarted()
579 {
580 synchronized (this)
581 {
582 if (_state==State.DISPATCHED)
583 return _async!=null;
584 return _async==Async.STARTED || _async==Async.EXPIRING;
585 }
586 }
587
588 public boolean isAsync()
589 {
590 synchronized (this)
591 {
592 return !_initial || _async!=null;
593 }
594 }
595
596 public Request getBaseRequest()
597 {
598 return _channel.getRequest();
599 }
600
601 public HttpChannel<?> getHttpChannel()
602 {
603 return _channel;
604 }
605
606 public ContextHandler getContextHandler()
607 {
608 final AsyncContextEvent event;
609 synchronized (this)
610 {
611 event=_event;
612 }
613
614 if (event!=null)
615 {
616 Context context=((Context)event.getServletContext());
617 if (context!=null)
618 return context.getContextHandler();
619 }
620 return null;
621 }
622
623 public ServletResponse getServletResponse()
624 {
625 final AsyncContextEvent event;
626 synchronized (this)
627 {
628 event=_event;
629 }
630 if (event!=null && event.getSuppliedResponse()!=null)
631 return event.getSuppliedResponse();
632 return _channel.getResponse();
633 }
634
635 public Object getAttribute(String name)
636 {
637 return _channel.getRequest().getAttribute(name);
638 }
639
640 public void removeAttribute(String name)
641 {
642 _channel.getRequest().removeAttribute(name);
643 }
644
645 public void setAttribute(String name, Object attribute)
646 {
647 _channel.getRequest().setAttribute(name,attribute);
648 }
649
650 public void onReadPossible()
651 {
652 boolean handle;
653
654 synchronized (this)
655 {
656 _asyncRead=true;
657 handle=_state==State.ASYNCWAIT;
658 }
659
660 if (handle)
661 _channel.execute(_channel);
662 }
663
664 public void onWritePossible()
665 {
666 boolean handle;
667
668 synchronized (this)
669 {
670 _asyncWrite=true;
671 handle=_state==State.ASYNCWAIT;
672 }
673
674 if (handle)
675 _channel.execute(_channel);
676 }
677
678 public class AsyncTimeout implements Runnable
679 {
680 @Override
681 public void run()
682 {
683 HttpChannelState.this.expired();
684 }
685 }
686
687 }