1 // ========================================================================
2 // Copyright (c) 2007-2009 Mort Bay Consulting Pty. Ltd.
3 // ------------------------------------------------------------------------
4 // All rights reserved. This program and the accompanying materials
5 // are made available under the terms of the Eclipse Public License v1.0
6 // and Apache License v2.0 which accompanies this distribution.
7 // The Eclipse Public License is available at
8 // http://www.eclipse.org/legal/epl-v10.html
9 // The Apache License v2.0 is available at
10 // http://www.opensource.org/licenses/apache2.0.php
11 // You may elect to redistribute this code under either of these licenses.
12 // ========================================================================
13
14 package org.eclipse.jetty.server;
15
16 import javax.servlet.ServletContext;
17 import javax.servlet.ServletRequest;
18 import javax.servlet.ServletResponse;
19 import javax.servlet.ServletResponseWrapper;
20
21 import org.eclipse.jetty.continuation.ContinuationThrowable;
22 import org.eclipse.jetty.continuation.ContinuationListener;
23 import org.eclipse.jetty.continuation.Continuation;
24 import org.eclipse.jetty.io.AsyncEndPoint;
25 import org.eclipse.jetty.io.EndPoint;
26 import org.eclipse.jetty.server.handler.ContextHandler;
27 import org.eclipse.jetty.server.handler.ContextHandler.Context;
28 import org.eclipse.jetty.util.LazyList;
29 import org.eclipse.jetty.util.log.Log;
30 import org.eclipse.jetty.util.thread.Timeout;
31
32 /* ------------------------------------------------------------ */
33 /** Implementation of Continuation and AsyncContext interfaces.
34 *
35 */
36 public class AsyncContinuation implements AsyncContext, Continuation
37 {
38 private final static ContinuationThrowable __exception = new ContinuationThrowable();
39
40 // STATES:
41 // handling() suspend() unhandle() resume() complete() doComplete()
42 // startAsync() dispatch()
43 // IDLE DISPATCHED
44 // DISPATCHED ASYNCSTARTED UNCOMPLETED
45 // ASYNCSTARTED ASYNCWAIT REDISPATCHING COMPLETING
46 // REDISPATCHING REDISPATCHED
47 // ASYNCWAIT REDISPATCH COMPLETING
48 // REDISPATCH REDISPATCHED
49 // REDISPATCHED ASYNCSTARTED UNCOMPLETED
50 // COMPLETING UNCOMPLETED UNCOMPLETED
51 // UNCOMPLETED COMPLETED
52 // COMPLETED
53 private static final int __IDLE=0; // Idle request
54 private static final int __DISPATCHED=1; // Request dispatched to filter/servlet
55 private static final int __ASYNCSTARTED=2; // Suspend called, but not yet returned to container
56 private static final int __REDISPATCHING=3;// resumed while dispatched
57 private static final int __ASYNCWAIT=4; // Suspended and parked
58 private static final int __REDISPATCH=5; // Has been scheduled
59 private static final int __REDISPATCHED=6; // Request redispatched to filter/servlet
60 private static final int __COMPLETING=7; // complete while dispatched
61 private static final int __UNCOMPLETED=8; // Request is completable
62 private static final int __COMPLETED=9; // Request is complete
63
64
65 /* ------------------------------------------------------------ */
66 protected HttpConnection _connection;
67 private Object _listeners;
68
69 /* ------------------------------------------------------------ */
70 private int _state;
71 private boolean _initial;
72 private boolean _resumed;
73 private boolean _expired;
74 private volatile boolean _responseWrapped;
75 private long _timeoutMs;
76 private AsyncEventState _event;
77
78 // private StringBuilder _history = new StringBuilder();
79
80 /* ------------------------------------------------------------ */
81 protected AsyncContinuation()
82 {
83 _state=__IDLE;
84 _initial=true;
85 // _history.append(super.toString());
86 // _history.append('\n');
87 }
88
89 /* ------------------------------------------------------------ */
90 protected void setConnection(final HttpConnection connection)
91 {
92 synchronized(this)
93 {
94 _connection=connection;
95 // _history.append(connection.toString());
96 // _history.append('\n');
97 }
98 }
99
100 /* ------------------------------------------------------------ */
101 public void addContinuationListener(ContinuationListener listener)
102 {
103 synchronized(this)
104 {
105 _listeners=LazyList.add(_listeners,listener);
106 // _history.append('L');
107 }
108 }
109
110 /* ------------------------------------------------------------ */
111 public void setAsyncTimeout(long ms)
112 {
113 synchronized(this)
114 {
115 _timeoutMs=ms;
116 // _history.append('T');
117 // _history.append(ms);
118 }
119 }
120
121 /* ------------------------------------------------------------ */
122 public long getAsyncTimeout()
123 {
124 synchronized(this)
125 {
126 return _timeoutMs;
127 }
128 }
129
130 /* ------------------------------------------------------------ */
131 public AsyncEventState getAsyncEventState()
132 {
133 synchronized(this)
134 {
135 return _event;
136 }
137 }
138
139 /* ------------------------------------------------------------ */
140 /**
141 * @see org.eclipse.jetty.continuation.Continuation#keepWrappers()
142 */
143
144 /* ------------------------------------------------------------ */
145 /**
146 * @see org.eclipse.jetty.continuation.Continuation#isResponseWrapped()
147 */
148 public boolean isResponseWrapped()
149 {
150 return _responseWrapped;
151 }
152
153 /* ------------------------------------------------------------ */
154 /* (non-Javadoc)
155 * @see javax.servlet.ServletRequest#isInitial()
156 */
157 public boolean isInitial()
158 {
159 synchronized(this)
160 {
161 return _initial;
162 }
163 }
164
165 /* ------------------------------------------------------------ */
166 /* (non-Javadoc)
167 * @see javax.servlet.ServletRequest#isSuspended()
168 */
169 public boolean isSuspended()
170 {
171 synchronized(this)
172 {
173 switch(_state)
174 {
175 case __ASYNCSTARTED:
176 case __REDISPATCHING:
177 case __COMPLETING:
178 case __ASYNCWAIT:
179 return true;
180
181 default:
182 return false;
183 }
184 }
185 }
186
187 /* ------------------------------------------------------------ */
188 public String toString()
189 {
190 synchronized (this)
191 {
192 return super.toString()+"@"+getStatusString();
193 }
194 }
195
196 /* ------------------------------------------------------------ */
197 public String getStatusString()
198 {
199 synchronized (this)
200 {
201 return
202 ((_state==__IDLE)?"IDLE":
203 (_state==__DISPATCHED)?"DISPATCHED":
204 (_state==__ASYNCSTARTED)?"ASYNCSTARTED":
205 (_state==__ASYNCWAIT)?"ASYNCWAIT":
206 (_state==__REDISPATCHING)?"REDISPATCHING":
207 (_state==__REDISPATCH)?"REDISPATCH":
208 (_state==__REDISPATCHED)?"REDISPATCHED":
209 (_state==__COMPLETING)?"COMPLETING":
210 (_state==__UNCOMPLETED)?"UNCOMPLETED":
211 (_state==__COMPLETED)?"COMPLETE":
212 ("UNKNOWN?"+_state))+
213 (_initial?",initial":"")+
214 (_resumed?",resumed":"")+
215 (_expired?",expired":"");
216 }
217 }
218
219 /* ------------------------------------------------------------ */
220 /* (non-Javadoc)
221 * @see javax.servlet.ServletRequest#resume()
222 */
223 protected boolean handling()
224 {
225 synchronized (this)
226 {
227 // _history.append('H');
228 // _history.append(_connection.getRequest().getUri().toString());
229 // _history.append(':');
230 _responseWrapped=false;
231
232 switch(_state)
233 {
234 case __DISPATCHED:
235 case __REDISPATCHED:
236 case __COMPLETED:
237 throw new IllegalStateException(this.getStatusString());
238
239 case __IDLE:
240 _initial=true;
241 _state=__DISPATCHED;
242 return true;
243
244 case __ASYNCSTARTED:
245 case __REDISPATCHING:
246 throw new IllegalStateException(this.getStatusString());
247
248 case __COMPLETING:
249 _state=__UNCOMPLETED;
250 return false;
251
252 case __ASYNCWAIT:
253 return false;
254
255 case __REDISPATCH:
256 _state=__REDISPATCHED;
257 return true;
258
259 default:
260 throw new IllegalStateException(""+_state);
261 }
262 }
263 }
264
265 /* ------------------------------------------------------------ */
266 /* (non-Javadoc)
267 * @see javax.servlet.ServletRequest#suspend(long)
268 */
269 protected void suspend(final ServletContext context,
270 final ServletRequest request,
271 final ServletResponse response)
272 {
273 synchronized (this)
274 {
275 // _history.append('S');
276 _resumed=false;
277 _expired=false;
278
279 // TODO move this to callers
280 if (_event==null || request!=_event.getRequest() || response != _event.getResponse() || context != _event.getServletContext())
281 _event=new AsyncEventState(context,request,response);
282 else
283 {
284 _event._dispatchContext=null;
285 _event._path=null;
286 }
287
288 switch(_state)
289 {
290 case __DISPATCHED:
291 case __REDISPATCHED:
292 _state=__ASYNCSTARTED;
293 return;
294
295 case __IDLE:
296 throw new IllegalStateException(this.getStatusString());
297
298 case __ASYNCSTARTED:
299 case __REDISPATCHING:
300 return;
301
302 case __COMPLETING:
303 case __ASYNCWAIT:
304 case __REDISPATCH:
305 case __COMPLETED:
306 throw new IllegalStateException(this.getStatusString());
307
308 default:
309 throw new IllegalStateException(""+_state);
310 }
311 }
312 }
313
314 /* ------------------------------------------------------------ */
315 /**
316 * Signal that the HttpConnection has finished handling the request.
317 * For blocking connectors, this call may block if the request has
318 * been suspended (startAsync called).
319 * @return true if handling is complete, false if the request should
320 * be handled again (eg because of a resume that happened before unhandle was called)
321 */
322 protected boolean unhandle()
323 {
324 synchronized (this)
325 {
326 // _history.append('U');
327 switch(_state)
328 {
329 case __REDISPATCHED:
330 case __DISPATCHED:
331 _state=__UNCOMPLETED;
332 return true;
333
334 case __IDLE:
335 throw new IllegalStateException(this.getStatusString());
336
337 case __ASYNCSTARTED:
338 _initial=false;
339 _state=__ASYNCWAIT;
340 scheduleTimeout(); // could block and change state.
341 if (_state==__ASYNCWAIT)
342 return true;
343 else if (_state==__COMPLETING)
344 {
345 _state=__UNCOMPLETED;
346 return true;
347 }
348 _initial=false;
349 _state=__REDISPATCHED;
350 return false;
351
352 case __REDISPATCHING:
353 _initial=false;
354 _state=__REDISPATCHED;
355 return false;
356
357 case __COMPLETING:
358 _initial=false;
359 _state=__UNCOMPLETED;
360 return true;
361
362 case __ASYNCWAIT:
363 case __REDISPATCH:
364 default:
365 throw new IllegalStateException(this.getStatusString());
366 }
367 }
368 }
369
370 /* ------------------------------------------------------------ */
371 public void dispatch()
372 {
373 boolean dispatch=false;
374 synchronized (this)
375 {
376 // _history.append('D');
377 switch(_state)
378 {
379 case __REDISPATCHED:
380 case __DISPATCHED:
381 case __IDLE:
382 case __REDISPATCHING:
383 case __COMPLETING:
384 case __COMPLETED:
385 case __UNCOMPLETED:
386 return;
387
388 case __ASYNCSTARTED:
389 _state=__REDISPATCHING;
390 _resumed=true;
391 return;
392
393 case __ASYNCWAIT:
394 dispatch=!_expired;
395 _state=__REDISPATCH;
396 _resumed=true;
397 break;
398
399 case __REDISPATCH:
400 return;
401
402 default:
403 throw new IllegalStateException(this.getStatusString());
404 }
405 }
406
407 if (dispatch)
408 {
409 cancelTimeout();
410 scheduleDispatch();
411 }
412 }
413
414 /* ------------------------------------------------------------ */
415 protected void expired()
416 {
417 Object listeners=null;
418 synchronized (this)
419 {
420 // _history.append('E');
421 switch(_state)
422 {
423 case __ASYNCSTARTED:
424 case __ASYNCWAIT:
425 listeners=_listeners;
426 break;
427 default:
428 return;
429 }
430 _expired=true;
431 }
432
433 if (listeners!=null)
434 {
435 for(int i=0;i<LazyList.size(listeners);i++)
436 {
437 try
438 {
439 // synchronized (this)
440 // {
441 // _history.append('l');
442 // _history.append(i);
443 // }
444 ContinuationListener listener=((ContinuationListener)LazyList.get(listeners,i));
445 listener.onTimeout(this);
446 }
447 catch(Exception e)
448 {
449 Log.warn(e);
450 }
451 }
452 }
453
454 synchronized (this)
455 {
456 // _history.append('e');
457 switch(_state)
458 {
459 case __ASYNCSTARTED:
460 case __ASYNCWAIT:
461 dispatch();
462 }
463 }
464
465 scheduleDispatch();
466 }
467
468 /* ------------------------------------------------------------ */
469 /* (non-Javadoc)
470 * @see javax.servlet.ServletRequest#complete()
471 */
472 public void complete()
473 {
474 // just like resume, except don't set _resumed=true;
475 boolean dispatch=false;
476 synchronized (this)
477 {
478 // _history.append('C');
479 switch(_state)
480 {
481 case __IDLE:
482 case __COMPLETED:
483 case __REDISPATCHING:
484 case __COMPLETING:
485 case __REDISPATCH:
486 return;
487
488 case __DISPATCHED:
489 case __REDISPATCHED:
490 throw new IllegalStateException(this.getStatusString());
491
492 case __ASYNCSTARTED:
493 _state=__COMPLETING;
494 return;
495
496 case __ASYNCWAIT:
497 _state=__COMPLETING;
498 dispatch=!_expired;
499 break;
500
501 default:
502 throw new IllegalStateException(this.getStatusString());
503 }
504 }
505
506 if (dispatch)
507 {
508 cancelTimeout();
509 scheduleDispatch();
510 }
511 }
512
513
514 /* ------------------------------------------------------------ */
515 /* (non-Javadoc)
516 * @see javax.servlet.ServletRequest#complete()
517 */
518 protected void doComplete()
519 {
520 Object listeners=null;
521 synchronized (this)
522 {
523 // _history.append("c");
524 switch(_state)
525 {
526 case __UNCOMPLETED:
527 _state=__COMPLETED;
528 listeners=_listeners;
529 break;
530
531 default:
532 throw new IllegalStateException(this.getStatusString());
533 }
534 }
535
536 if (listeners!=null)
537 {
538 for(int i=0;i<LazyList.size(listeners);i++)
539 {
540 try
541 {
542 // synchronized (this)
543 // {
544 // _history.append('l');
545 // _history.append(i);
546 // }
547 ((ContinuationListener)LazyList.get(listeners,i)).onComplete(this);
548 }
549 catch(Exception e)
550 {
551 Log.warn(e);
552 }
553 }
554 }
555 }
556
557 /* ------------------------------------------------------------ */
558 protected void recycle()
559 {
560 synchronized (this)
561 {
562 // _history.append("r\n");
563 switch(_state)
564 {
565 case __DISPATCHED:
566 case __REDISPATCHED:
567 throw new IllegalStateException(getStatusString());
568 default:
569 _state=__IDLE;
570 }
571 _initial = true;
572 _resumed=false;
573 _expired=false;
574 _responseWrapped=false;
575 cancelTimeout();
576 _timeoutMs=60000L; // TODO configure
577 _listeners=null;
578 }
579 }
580
581 /* ------------------------------------------------------------ */
582 public void cancel()
583 {
584 synchronized (this)
585 {
586 // _history.append("X");
587 cancelTimeout();
588 _listeners=null;
589 }
590 }
591
592 /* ------------------------------------------------------------ */
593 protected void scheduleDispatch()
594 {
595 EndPoint endp=_connection.getEndPoint();
596 if (!endp.isBlocking())
597 {
598 ((AsyncEndPoint)endp).dispatch();
599 }
600 }
601
602 /* ------------------------------------------------------------ */
603 protected void scheduleTimeout()
604 {
605 EndPoint endp=_connection.getEndPoint();
606 if (endp.isBlocking())
607 {
608 synchronized(this)
609 {
610 long expire_at = System.currentTimeMillis()+_timeoutMs;
611 long wait=_timeoutMs;
612 while (_timeoutMs>0 && wait>0)
613 {
614 try
615 {
616 this.wait(wait);
617 }
618 catch (InterruptedException e)
619 {
620 Log.ignore(e);
621 }
622 wait=expire_at-System.currentTimeMillis();
623 }
624
625 if (_timeoutMs>0 && wait<=0)
626 expired();
627 }
628 }
629 else
630 _connection.scheduleTimeout(_event._timeout,_timeoutMs);
631 }
632
633 /* ------------------------------------------------------------ */
634 protected void cancelTimeout()
635 {
636 EndPoint endp=_connection.getEndPoint();
637 if (endp.isBlocking())
638 {
639 synchronized(this)
640 {
641 _timeoutMs=0;
642 this.notifyAll();
643 }
644 }
645 else
646 {
647 final AsyncEventState event=_event;
648 if (event!=null)
649 _connection.cancelTimeout(event._timeout);
650 }
651 }
652
653 /* ------------------------------------------------------------ */
654 public boolean isCompleting()
655 {
656 synchronized (this)
657 {
658 return _state==__COMPLETING;
659 }
660 }
661
662 /* ------------------------------------------------------------ */
663 boolean isUncompleted()
664 {
665 synchronized (this)
666 {
667 return _state==__UNCOMPLETED;
668 }
669 }
670
671 /* ------------------------------------------------------------ */
672 public boolean isComplete()
673 {
674 synchronized (this)
675 {
676 return _state==__COMPLETED;
677 }
678 }
679
680
681 /* ------------------------------------------------------------ */
682 public boolean isAsyncStarted()
683 {
684 synchronized (this)
685 {
686 switch(_state)
687 {
688 case __ASYNCSTARTED:
689 case __REDISPATCHING:
690 case __REDISPATCH:
691 case __ASYNCWAIT:
692 return true;
693
694 default:
695 return false;
696 }
697 }
698 }
699
700
701 /* ------------------------------------------------------------ */
702 public boolean isAsync()
703 {
704 synchronized (this)
705 {
706 switch(_state)
707 {
708 case __IDLE:
709 case __DISPATCHED:
710 return false;
711
712 default:
713 return true;
714 }
715 }
716 }
717
718 /* ------------------------------------------------------------ */
719 public void dispatch(ServletContext context, String path)
720 {
721 _event._dispatchContext=context;
722 _event._path=path;
723 dispatch();
724 }
725
726 /* ------------------------------------------------------------ */
727 public void dispatch(String path)
728 {
729 _event._path=path;
730 dispatch();
731 }
732
733 /* ------------------------------------------------------------ */
734 public ServletRequest getRequest()
735 {
736 if (_event!=null)
737 return _event.getRequest();
738 return _connection.getRequest();
739 }
740
741 /* ------------------------------------------------------------ */
742 public ServletResponse getResponse()
743 {
744 if (_event!=null)
745 return _event.getResponse();
746 return _connection.getResponse();
747 }
748
749 /* ------------------------------------------------------------ */
750 public void start(Runnable run)
751 {
752 final AsyncEventState event=_event;
753 if (event!=null)
754 ((Context)event.getServletContext()).getContextHandler().handle(run);
755 }
756
757 /* ------------------------------------------------------------ */
758 public boolean hasOriginalRequestAndResponse()
759 {
760 synchronized (this)
761 {
762 return (_event!=null && _event.getRequest()==_connection._request && _event.getResponse()==_connection._response);
763 }
764 }
765
766 /* ------------------------------------------------------------ */
767 public ContextHandler getContextHandler()
768 {
769 final AsyncEventState event=_event;
770 if (event!=null)
771 return ((Context)event.getServletContext()).getContextHandler();
772 return null;
773 }
774
775
776 /* ------------------------------------------------------------ */
777 /**
778 * @see Continuation#isResumed()
779 */
780 public boolean isResumed()
781 {
782 synchronized (this)
783 {
784 return _resumed;
785 }
786 }
787 /* ------------------------------------------------------------ */
788 /**
789 * @see Continuation#isExpired()
790 */
791 public boolean isExpired()
792 {
793 synchronized (this)
794 {
795 return _expired;
796 }
797 }
798
799 /* ------------------------------------------------------------ */
800 /**
801 * @see Continuation#resume()
802 */
803 public void resume()
804 {
805 dispatch();
806 }
807
808 /* ------------------------------------------------------------ */
809 /**
810 * @see Continuation#suspend(long)
811 */
812 public void setTimeout(long timeoutMs)
813 {
814 setAsyncTimeout(timeoutMs);
815 }
816
817 /* ------------------------------------------------------------ */
818 /**
819 * @see Continuation#suspend()
820 */
821 public void suspend(ServletResponse response)
822 {
823 if (response instanceof ServletResponseWrapper)
824 {
825 _responseWrapped=true;
826 AsyncContinuation.this.suspend(_connection.getRequest().getServletContext(),_connection.getRequest(),response);
827 }
828 else
829 {
830 _responseWrapped=false;
831 AsyncContinuation.this.suspend(_connection.getRequest().getServletContext(),_connection.getRequest(),_connection.getResponse());
832 }
833 }
834
835 /* ------------------------------------------------------------ */
836 /**
837 * @see Continuation#suspend()
838 */
839 public void suspend()
840 {
841 // TODO simplify? move event creation to suspend(args)
842 _responseWrapped=false;
843 AsyncContinuation.this.suspend(_connection.getRequest().getServletContext(),_connection.getRequest(),_connection.getResponse());
844 }
845
846 /* ------------------------------------------------------------ */
847 /**
848 * @see org.eclipse.jetty.continuation.Continuation#getServletResponse()
849 */
850 public ServletResponse getServletResponse()
851 {
852 if (_responseWrapped && _event!=null && _event.getResponse()!=null)
853 return _event.getResponse();
854 return _connection.getResponse();
855 }
856
857 /* ------------------------------------------------------------ */
858 /**
859 * @see org.eclipse.jetty.continuation.Continuation#getAttribute(java.lang.String)
860 */
861 public Object getAttribute(String name)
862 {
863 return _connection.getRequest().getAttribute(name);
864 }
865
866 /* ------------------------------------------------------------ */
867 /**
868 * @see org.eclipse.jetty.continuation.Continuation#removeAttribute(java.lang.String)
869 */
870 public void removeAttribute(String name)
871 {
872 _connection.getRequest().removeAttribute(name);
873 }
874
875 /* ------------------------------------------------------------ */
876 /**
877 * @see org.eclipse.jetty.continuation.Continuation#setAttribute(java.lang.String, java.lang.Object)
878 */
879 public void setAttribute(String name, Object attribute)
880 {
881 _connection.getRequest().setAttribute(name,attribute);
882 }
883
884 /* ------------------------------------------------------------ */
885 /**
886 * @see org.eclipse.jetty.continuation.Continuation#undispatch()
887 */
888 public void undispatch()
889 {
890 if (isSuspended())
891 {
892 if (Log.isDebugEnabled())
893 throw new ContinuationThrowable();
894 else
895 throw __exception;
896 }
897 throw new IllegalStateException("!suspended");
898 }
899
900 /* ------------------------------------------------------------ */
901 /* ------------------------------------------------------------ */
902 public class AsyncEventState
903 {
904 private final ServletContext _suspendedContext;
905 private final ServletRequest _request;
906 private final ServletResponse _response;
907
908 ServletContext _dispatchContext;
909
910 String _path;
911 final Timeout.Task _timeout = new Timeout.Task()
912 {
913 public void expired()
914 {
915 AsyncContinuation.this.expired();
916 }
917 };
918
919 public AsyncEventState(ServletContext context, ServletRequest request, ServletResponse response)
920 {
921 _suspendedContext=context;
922 _request=request;
923 _response=response;
924 }
925
926 public ServletContext getSuspendedContext()
927 {
928 return _suspendedContext;
929 }
930
931 public ServletContext getDispatchContext()
932 {
933 return _dispatchContext;
934 }
935
936 public ServletContext getServletContext()
937 {
938 return _dispatchContext==null?_suspendedContext:_dispatchContext;
939 }
940
941 public ServletRequest getRequest()
942 {
943 return _request;
944 }
945
946 public ServletResponse getResponse()
947 {
948 return _response;
949 }
950
951 public String getPath()
952 {
953 return _path;
954 }
955 }
956
957 public String getHistory()
958 {
959 // synchronized (this)
960 // {
961 // return _history.toString();
962 // }
963 return null;
964 }
965 }