View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
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  import javax.servlet.AsyncContext;
25  import javax.servlet.AsyncEvent;
26  import javax.servlet.AsyncListener;
27  import javax.servlet.RequestDispatcher;
28  import javax.servlet.ServletContext;
29  import javax.servlet.ServletException;
30  import javax.servlet.ServletRequest;
31  import javax.servlet.ServletResponse;
32  import javax.servlet.http.HttpServletRequest;
33  
34  import org.eclipse.jetty.server.handler.ContextHandler;
35  import org.eclipse.jetty.server.handler.ContextHandler.Context;
36  import org.eclipse.jetty.util.URIUtil;
37  import org.eclipse.jetty.util.log.Log;
38  import org.eclipse.jetty.util.log.Logger;
39  import org.eclipse.jetty.util.thread.Scheduler;
40  
41  /**
42   * Implementation of AsyncContext interface that holds the state of request-response cycle.
43   *
44   * <table>
45   * <tr><th>STATE</th><th colspan=6>ACTION</th></tr>
46   * <tr><th></th>                           <th>handling()</th>  <th>startAsync()</th><th>unhandle()</th>  <th>dispatch()</th>   <th>complete()</th>      <th>completed()</th></tr>
47   * <tr><th align=right>IDLE:</th>          <td>DISPATCHED</td>  <td></td>            <td></td>            <td></td>             <td>COMPLETECALLED??</td><td></td></tr>
48   * <tr><th align=right>DISPATCHED:</th>    <td></td>            <td>ASYNCSTARTED</td><td>COMPLETING</td>  <td></td>             <td></td>                <td></td></tr>
49   * <tr><th align=right>ASYNCSTARTED:</th>  <td></td>            <td></td>            <td>ASYNCWAIT</td>   <td>REDISPATCHING</td><td>COMPLETECALLED</td>  <td></td></tr>
50   * <tr><th align=right>REDISPATCHING:</th> <td></td>            <td></td>            <td>REDISPATCHED</td><td></td>             <td></td>                <td></td></tr>
51   * <tr><th align=right>ASYNCWAIT:</th>     <td></td>            <td></td>            <td></td>            <td>REDISPATCH</td>   <td>COMPLETECALLED</td>  <td></td></tr>
52   * <tr><th align=right>REDISPATCH:</th>    <td>REDISPATCHED</td><td></td>            <td></td>            <td></td>             <td></td>                <td></td></tr>
53   * <tr><th align=right>REDISPATCHED:</th>  <td></td>            <td>ASYNCSTARTED</td><td>COMPLETING</td>  <td></td>             <td></td>                <td></td></tr>
54   * <tr><th align=right>COMPLETECALLED:</th><td>COMPLETING</td>  <td></td>            <td>COMPLETING</td>  <td></td>             <td></td>                <td></td></tr>
55   * <tr><th align=right>COMPLETING:</th>    <td>COMPLETING</td>  <td></td>            <td></td>            <td></td>             <td></td>                <td>COMPLETED</td></tr>
56   * <tr><th align=right>COMPLETED:</th>     <td></td>            <td></td>            <td></td>            <td></td>             <td></td>                <td></td></tr>
57   * </table>
58   */
59  public class HttpChannelState implements AsyncContext
60  {
61      private static final Logger LOG = Log.getLogger(HttpChannelState.class);
62  
63      private final static long DEFAULT_TIMEOUT=30000L;
64  
65      public enum State
66      {
67          IDLE,          // Idle request
68          DISPATCHED,    // Request dispatched to filter/servlet
69          ASYNCSTARTED,  // Suspend called, but not yet returned to container
70          REDISPATCHING, // resumed while dispatched
71          ASYNCWAIT,     // Suspended and parked
72          REDISPATCH,    // Has been scheduled
73          REDISPATCHED,  // Request redispatched to filter/servlet
74          COMPLETECALLED,// complete called
75          COMPLETING,    // Request is completable
76          COMPLETED      // Request is complete
77      }
78  
79      private final HttpChannel<?> _channel;
80      private List<AsyncListener> _lastAsyncListeners;
81      private List<AsyncListener> _asyncListeners;
82  
83      private State _state;
84      private boolean _initial;
85      private boolean _dispatched;
86      private boolean _expired;
87      private volatile boolean _responseWrapped;
88      private long _timeoutMs=DEFAULT_TIMEOUT;
89      private AsyncEventState _event;
90  
91      protected HttpChannelState(HttpChannel<?> channel)
92      {
93          _channel=channel;
94          _state=State.IDLE;
95          _initial=true;
96      }
97  
98      public State getState()
99      {
100         synchronized(this)
101         {
102             return _state;
103         }
104     }
105 
106     @Override
107     public void addListener(AsyncListener listener)
108     {
109         synchronized(this)
110         {
111             if (_asyncListeners==null)
112                 _asyncListeners=new ArrayList<>();
113             _asyncListeners.add(listener);
114         }
115     }
116 
117     @Override
118     public void addListener(AsyncListener listener,ServletRequest request, ServletResponse response)
119     {
120         synchronized(this)
121         {
122             if (_asyncListeners==null)
123                 _asyncListeners=new ArrayList<>();
124             _asyncListeners.add(listener);
125         }
126     }
127 
128 
129     @Override
130     public void setTimeout(long ms)
131     {
132         synchronized(this)
133         {
134             _timeoutMs=ms;
135         }
136     }
137 
138     @Override
139     public long getTimeout()
140     {
141         synchronized(this)
142         {
143             return _timeoutMs;
144         }
145     }
146 
147     public AsyncEventState getAsyncEventState()
148     {
149         synchronized(this)
150         {
151             return _event;
152         }
153     }
154 
155     @Override
156     public String toString()
157     {
158         synchronized (this)
159         {
160             return super.toString()+"@"+getStatusString();
161         }
162     }
163 
164     public String getStatusString()
165     {
166         synchronized (this)
167         {
168             return _state+
169             (_initial?",initial":"")+
170             (_dispatched?",resumed":"")+
171             (_expired?",expired":"");
172         }
173     }
174 
175     /**
176      * @return true if the handling of the request should proceed
177      */
178     protected boolean handling()
179     {
180         synchronized (this)
181         {
182             switch(_state)
183             {
184                 case IDLE:
185                     _initial=true;
186                     _state=State.DISPATCHED;
187                     if (_lastAsyncListeners!=null)
188                         _lastAsyncListeners.clear();
189                     if (_asyncListeners!=null)
190                         _asyncListeners.clear();
191                     else
192                     {
193                         _asyncListeners=_lastAsyncListeners;
194                         _lastAsyncListeners=null;
195                     }
196                     break;
197 
198                 case COMPLETECALLED:
199                     _state=State.COMPLETING;
200                     return false;
201 
202                 case ASYNCWAIT:
203                 case COMPLETING:
204                 case COMPLETED:
205                     return false;
206 
207                 case REDISPATCH:
208                     _state=State.REDISPATCHED;
209                     break;
210 
211                 default:
212                     throw new IllegalStateException(this.getStatusString());
213             }
214 
215             _responseWrapped=false;
216             return true;
217 
218         }
219     }
220 
221     public void startAsync()
222     {
223         synchronized (this)
224         {
225             switch(_state)
226             {
227                 case DISPATCHED:
228                 case REDISPATCHED:
229                     _dispatched=false;
230                     _expired=false;
231                     _responseWrapped=false;
232                     _event=new AsyncEventState(_channel.getRequest().getServletContext(),_channel.getRequest(),_channel.getResponse());
233                     _state=State.ASYNCSTARTED;
234                     List<AsyncListener> listeners=_lastAsyncListeners;
235                     _lastAsyncListeners=_asyncListeners;
236                     _asyncListeners=listeners;
237                     if (_asyncListeners!=null)
238                         _asyncListeners.clear();
239                     break;
240 
241                 default:
242                     throw new IllegalStateException(this.getStatusString());
243             }
244         }
245 
246         if (_lastAsyncListeners!=null)
247         {
248             for (AsyncListener listener : _lastAsyncListeners)
249             {
250                 try
251                 {
252                     listener.onStartAsync(_event);
253                 }
254                 catch(Exception e)
255                 {
256                     LOG.warn(e);
257                 }
258             }
259         }
260     }
261 
262     public void startAsync(final ServletContext context,final ServletRequest request,final ServletResponse response)
263     {
264         synchronized (this)
265         {
266             switch(_state)
267             {
268                 case DISPATCHED:
269                 case REDISPATCHED:
270                     _dispatched=false;
271                     _expired=false;
272                     _responseWrapped=response!=_channel.getResponse();
273                     _event=new AsyncEventState(context,request,response);
274                     _event._pathInContext = (request instanceof HttpServletRequest)?URIUtil.addPaths(((HttpServletRequest)request).getServletPath(),((HttpServletRequest)request).getPathInfo()):null;
275                     _state=State.ASYNCSTARTED;
276                     List<AsyncListener> listeners=_lastAsyncListeners;
277                     _lastAsyncListeners=_asyncListeners;
278                     _asyncListeners=listeners;
279                     if (_asyncListeners!=null)
280                         _asyncListeners.clear();
281                     break;
282 
283                 default:
284                     throw new IllegalStateException(this.getStatusString());
285             }
286         }
287 
288         if (_lastAsyncListeners!=null)
289         {
290             for (AsyncListener listener : _lastAsyncListeners)
291             {
292                 try
293                 {
294                     listener.onStartAsync(_event);
295                 }
296                 catch(Exception e)
297                 {
298                     LOG.warn(e);
299                 }
300             }
301         }
302     }
303 
304     protected void error(Throwable th)
305     {
306         synchronized (this)
307         {
308             if (_event!=null)
309                 _event._cause=th;
310         }
311     }
312 
313     /**
314      * Signal that the HttpConnection has finished handling the request.
315      * For blocking connectors, this call may block if the request has
316      * been suspended (startAsync called).
317      * @return true if handling is complete, false if the request should
318      * be handled again (eg because of a resume that happened before unhandle was called)
319      */
320     protected boolean unhandle()
321     {
322         synchronized (this)
323         {
324             switch(_state)
325             {
326                 case REDISPATCHED:
327                 case DISPATCHED:
328                     _state=State.COMPLETING;
329                     return true;
330 
331                 case IDLE:
332                     throw new IllegalStateException(this.getStatusString());
333 
334                 case ASYNCSTARTED:
335                     _initial=false;
336                     _state=State.ASYNCWAIT;
337                     scheduleTimeout();
338                     if (_state==State.ASYNCWAIT)
339                         return true;
340                     else if (_state==State.COMPLETECALLED)
341                     {
342                         _state=State.COMPLETING;
343                         return true;
344                     }
345                     _initial=false;
346                     _state=State.REDISPATCHED;
347                     return false;
348 
349                 case REDISPATCHING:
350                     _initial=false;
351                     _state=State.REDISPATCHED;
352                     return false;
353 
354                 case COMPLETECALLED:
355                     _initial=false;
356                     _state=State.COMPLETING;
357                     return true;
358 
359                 default:
360                     throw new IllegalStateException(this.getStatusString());
361             }
362         }
363     }
364 
365     @Override
366     public void dispatch()
367     {
368         boolean dispatch;
369         synchronized (this)
370         {
371             switch(_state)
372             {
373                 case ASYNCSTARTED:
374                     _state=State.REDISPATCHING;
375                     _dispatched=true;
376                     return;
377 
378                 case ASYNCWAIT:
379                     dispatch=!_expired;
380                     _state=State.REDISPATCH;
381                     _dispatched=true;
382                     break;
383 
384                 case REDISPATCH:
385                     return;
386 
387                 default:
388                     throw new IllegalStateException(this.getStatusString());
389             }
390         }
391 
392         if (dispatch)
393         {
394             cancelTimeout();
395             scheduleDispatch();
396         }
397     }
398 
399     public boolean isDispatched()
400     {
401         synchronized (this)
402         {
403             return _dispatched;
404         }
405     }
406 
407     protected void expired()
408     {
409         final List<AsyncListener> aListeners;
410         synchronized (this)
411         {
412             switch(_state)
413             {
414                 case ASYNCSTARTED:
415                 case ASYNCWAIT:
416                     aListeners=_asyncListeners;
417                     break;
418                 default:
419                     return;
420             }
421             _expired=true;
422         }
423 
424         if (aListeners!=null)
425         {
426             for (AsyncListener listener : aListeners)
427             {
428                 try
429                 {
430                     listener.onTimeout(_event);
431                 }
432                 catch(Exception e)
433                 {
434                     LOG.warn(e);
435                 }
436             }
437         }
438 
439         boolean complete;
440         synchronized (this)
441         {
442             switch(_state)
443             {
444                 case ASYNCSTARTED:
445                 case ASYNCWAIT:
446                     complete = true;
447                     break;
448                 default:
449                     complete = false;
450                     break;
451             }
452         }
453         if (complete)
454             complete();
455 
456         scheduleDispatch();
457     }
458 
459     @Override
460     public void complete()
461     {
462         // just like resume, except don't set _dispatched=true;
463         boolean dispatch;
464         synchronized (this)
465         {
466             switch(_state)
467             {
468                 case DISPATCHED:
469                 case REDISPATCHED:
470                     throw new IllegalStateException(this.getStatusString());
471 
472                 case IDLE:
473                 case ASYNCSTARTED:
474                     _state=State.COMPLETECALLED;
475                     return;
476 
477                 case ASYNCWAIT:
478                     _state=State.COMPLETECALLED;
479                     dispatch=!_expired;
480                     break;
481 
482                 default:
483                     throw new IllegalStateException(this.getStatusString());
484             }
485         }
486 
487         if (dispatch)
488         {
489             cancelTimeout();
490             scheduleDispatch();
491         }
492     }
493 
494     @Override
495     public <T extends AsyncListener> T createListener(Class<T> clazz) throws ServletException
496     {
497         try
498         {
499             return clazz.newInstance();
500         }
501         catch(Exception e)
502         {
503             throw new ServletException(e);
504         }
505     }
506 
507     protected void completed()
508     {
509         final List<AsyncListener> aListeners;
510         synchronized (this)
511         {
512             switch(_state)
513             {
514                 case COMPLETING:
515                     _state=State.COMPLETED;
516                     aListeners=_asyncListeners;
517                     break;
518 
519                 default:
520                     throw new IllegalStateException(this.getStatusString());
521             }
522         }
523 
524         if (aListeners!=null)
525         {
526             for (AsyncListener listener : aListeners)
527             {
528                 try
529                 {
530                     if (_event!=null && _event._cause!=null)
531                     {
532                         _event.getSuppliedRequest().setAttribute(RequestDispatcher.ERROR_EXCEPTION,_event._cause);
533                         _event.getSuppliedRequest().setAttribute(RequestDispatcher.ERROR_MESSAGE,_event._cause.getMessage());
534                         listener.onError(_event);
535                     }
536                     else
537                         listener.onComplete(_event);
538                 }
539                 catch(Exception e)
540                 {
541                     LOG.warn(e);
542                 }
543             }
544         }
545     }
546 
547     protected void recycle()
548     {
549         synchronized (this)
550         {
551             switch(_state)
552             {
553                 case DISPATCHED:
554                 case REDISPATCHED:
555                     throw new IllegalStateException(getStatusString());
556                 default:
557                     _state=State.IDLE;
558             }
559             _initial = true;
560             _dispatched=false;
561             _expired=false;
562             _responseWrapped=false;
563             cancelTimeout();
564             _timeoutMs=DEFAULT_TIMEOUT;
565             _event=null;
566         }
567     }
568 
569     public void cancel()
570     {
571         synchronized (this)
572         {
573             cancelTimeout();
574         }
575     }
576 
577     protected void scheduleDispatch()
578     {
579         _channel.execute(_channel);
580     }
581 
582     protected void scheduleTimeout()
583     {
584         Scheduler scheduler = _channel.getScheduler();
585         if (scheduler!=null && _timeoutMs>0)
586             _event._timeout=scheduler.schedule(new AsyncTimeout(),_timeoutMs,TimeUnit.MILLISECONDS);
587     }
588 
589     protected void cancelTimeout()
590     {
591         AsyncEventState event=_event;
592         if (event!=null)
593         {
594             Scheduler.Task task=event._timeout;
595             if (task!=null)
596                 task.cancel();
597         }
598     }
599 
600     public boolean isInitial()
601     {
602         synchronized(this)
603         {
604             return _initial;
605         }
606     }
607 
608     public boolean isSuspended()
609     {
610         synchronized(this)
611         {
612             switch(_state)
613             {
614                 case ASYNCSTARTED:
615                 case REDISPATCHING:
616                 case COMPLETECALLED:
617                 case ASYNCWAIT:
618                     return true;
619 
620                 default:
621                     return false;
622             }
623         }
624     }
625 
626     boolean isCompleting()
627     {
628         synchronized (this)
629         {
630             return _state==State.COMPLETING;
631         }
632     }
633 
634     public boolean isAsync()
635     {
636         synchronized (this)
637         {
638             switch(_state)
639             {
640                 case ASYNCSTARTED:
641                 case REDISPATCHING:
642                 case ASYNCWAIT:
643                 case REDISPATCHED:
644                 case REDISPATCH:
645                 case COMPLETECALLED:
646                     return true;
647 
648                 default:
649                     return false;
650             }
651         }
652     }
653 
654     @Override
655     public void dispatch(ServletContext context, String path)
656     {
657         _event._dispatchContext=context;
658         _event._pathInContext=path;
659         dispatch();
660     }
661 
662     @Override
663     public void dispatch(String path)
664     {
665         _event._pathInContext=path;
666         dispatch();
667     }
668 
669     public Request getBaseRequest()
670     {
671         return _channel.getRequest();
672     }
673 
674     @Override
675     public ServletRequest getRequest()
676     {
677         if (_event!=null)
678             return _event.getSuppliedRequest();
679         return _channel.getRequest();
680     }
681 
682     @Override
683     public ServletResponse getResponse()
684     {
685         if (_responseWrapped && _event!=null && _event.getSuppliedResponse()!=null)
686             return _event.getSuppliedResponse();
687         return _channel.getResponse();
688     }
689 
690     @Override
691     public void start(final Runnable run)
692     {
693         final AsyncEventState event=_event;
694         if (event!=null)
695         {
696             _channel.execute(new Runnable()
697             {
698                 @Override
699                 public void run()
700                 {
701                     ((Context)event.getServletContext()).getContextHandler().handle(run);
702                 }
703             });
704         }
705     }
706 
707     @Override
708     public boolean hasOriginalRequestAndResponse()
709     {
710         synchronized (this)
711         {
712             return (_event!=null && _event.getSuppliedRequest()==_channel.getRequest() && _event.getSuppliedResponse()==_channel.getResponse());
713         }
714     }
715 
716     public ContextHandler getContextHandler()
717     {
718         final AsyncEventState event=_event;
719         if (event!=null)
720             return ((Context)event.getServletContext()).getContextHandler();
721         return null;
722     }
723 
724     public ServletResponse getServletResponse()
725     {
726         if (_responseWrapped && _event!=null && _event.getSuppliedResponse()!=null)
727             return _event.getSuppliedResponse();
728         return _channel.getResponse();
729     }
730 
731     public Object getAttribute(String name)
732     {
733         return _channel.getRequest().getAttribute(name);
734     }
735 
736     public void removeAttribute(String name)
737     {
738         _channel.getRequest().removeAttribute(name);
739     }
740 
741     public void setAttribute(String name, Object attribute)
742     {
743         _channel.getRequest().setAttribute(name,attribute);
744     }
745 
746     public class AsyncTimeout implements Runnable
747     {
748         @Override
749         public void run()
750         {
751             HttpChannelState.this.expired();
752         }
753     }
754 
755     public class AsyncEventState extends AsyncEvent
756     {
757         final private ServletContext _suspendedContext;
758         private String _pathInContext;
759         private Scheduler.Task _timeout;
760         private ServletContext _dispatchContext;
761         private Throwable _cause;
762 
763         public AsyncEventState(ServletContext context, ServletRequest request, ServletResponse response)
764         {
765             super(HttpChannelState.this, request,response);
766             _suspendedContext=context;
767 
768             // Get the base request So we can remember the initial paths
769             Request r=_channel.getRequest();
770 
771             // If we haven't been async dispatched before
772             if (r.getAttribute(AsyncContext.ASYNC_REQUEST_URI)==null)
773             {
774                 // We are setting these attributes during startAsync, when the spec implies that
775                 // they are only available after a call to AsyncContext.dispatch(...);
776 
777                 // have we been forwarded before?
778                 String uri=(String)r.getAttribute(RequestDispatcher.FORWARD_REQUEST_URI);
779                 if (uri!=null)
780                 {
781                     r.setAttribute(AsyncContext.ASYNC_REQUEST_URI,uri);
782                     r.setAttribute(AsyncContext.ASYNC_CONTEXT_PATH,r.getAttribute(RequestDispatcher.FORWARD_CONTEXT_PATH));
783                     r.setAttribute(AsyncContext.ASYNC_SERVLET_PATH,r.getAttribute(RequestDispatcher.FORWARD_SERVLET_PATH));
784                     r.setAttribute(AsyncContext.ASYNC_PATH_INFO,r.getAttribute(RequestDispatcher.FORWARD_PATH_INFO));
785                     r.setAttribute(AsyncContext.ASYNC_QUERY_STRING,r.getAttribute(RequestDispatcher.FORWARD_QUERY_STRING));
786                 }
787                 else
788                 {
789                     r.setAttribute(AsyncContext.ASYNC_REQUEST_URI,r.getRequestURI());
790                     r.setAttribute(AsyncContext.ASYNC_CONTEXT_PATH,r.getContextPath());
791                     r.setAttribute(AsyncContext.ASYNC_SERVLET_PATH,r.getServletPath());
792                     r.setAttribute(AsyncContext.ASYNC_PATH_INFO,r.getPathInfo());
793                     r.setAttribute(AsyncContext.ASYNC_QUERY_STRING,r.getQueryString());
794                 }
795             }
796         }
797 
798         public ServletContext getSuspendedContext()
799         {
800             return _suspendedContext;
801         }
802 
803         public ServletContext getDispatchContext()
804         {
805             return _dispatchContext;
806         }
807 
808         public ServletContext getServletContext()
809         {
810             return _dispatchContext==null?_suspendedContext:_dispatchContext;
811         }
812 
813         /**
814          * @return The path in the context
815          */
816         public String getPath()
817         {
818             return _pathInContext;
819         }
820     }
821 }