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