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  
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   * Implementation of AsyncContext interface that holds the state of request-response cycle.
39   *
40   * <table>
41   * <tr><th>STATE</th><th colspan=6>ACTION</th></tr>
42   * <tr><th></th>                           <th>handling()</th>  <th>startAsync()</th><th>unhandle()</th>  <th>dispatch()</th>   <th>complete()</th>      <th>completed()</th></tr>
43   * <tr><th align=right>IDLE:</th>          <td>DISPATCHED</td>  <td></td>            <td></td>            <td></td>             <td>COMPLETECALLED??</td><td></td></tr>
44   * <tr><th align=right>DISPATCHED:</th>    <td></td>            <td>ASYNCSTARTED</td><td>COMPLETING</td>  <td></td>             <td></td>                <td></td></tr>
45   * <tr><th align=right>ASYNCSTARTED:</th>  <td></td>            <td></td>            <td>ASYNCWAIT</td>   <td>REDISPATCHING</td><td>COMPLETECALLED</td>  <td></td></tr>
46   * <tr><th align=right>REDISPATCHING:</th> <td></td>            <td></td>            <td>REDISPATCHED</td><td></td>             <td></td>                <td></td></tr>
47   * <tr><th align=right>ASYNCWAIT:</th>     <td></td>            <td></td>            <td></td>            <td>REDISPATCH</td>   <td>COMPLETECALLED</td>  <td></td></tr>
48   * <tr><th align=right>REDISPATCH:</th>    <td>REDISPATCHED</td><td></td>            <td></td>            <td></td>             <td></td>                <td></td></tr>
49   * <tr><th align=right>REDISPATCHED:</th>  <td></td>            <td>ASYNCSTARTED</td><td>COMPLETING</td>  <td></td>             <td></td>                <td></td></tr>
50   * <tr><th align=right>COMPLETECALLED:</th><td>COMPLETING</td>  <td></td>            <td>COMPLETING</td>  <td></td>             <td></td>                <td></td></tr>
51   * <tr><th align=right>COMPLETING:</th>    <td>COMPLETING</td>  <td></td>            <td></td>            <td></td>             <td></td>                <td>COMPLETED</td></tr>
52   * <tr><th align=right>COMPLETED:</th>     <td></td>            <td></td>            <td></td>            <td></td>             <td></td>                <td></td></tr>
53   * </table>
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,          // Idle request
64          DISPATCHED,    // Request dispatched to filter/servlet
65          ASYNCSTARTED,  // Suspend called, but not yet returned to container
66          REDISPATCHING, // resumed while dispatched
67          ASYNCWAIT,     // Suspended and parked
68          REDISPATCH,    // Has been scheduled
69          REDISPATCHED,  // Request redispatched to filter/servlet
70          COMPLETECALLED,// complete called
71          COMPLETING,    // Request is completable
72          COMPLETED      // Request is complete
73      }
74  
75      public enum Next
76      {
77          CONTINUE,       // Continue handling the channel        
78          WAIT,           // Wait for further events 
79          COMPLETE        // Complete the channel
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      * @return Next handling of the request should proceed
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      * Signal that the HttpConnection has finished handling the request.
267      * For blocking connectors, this call may block if the request has
268      * been suspended (startAsync called).
269      * @return next actions
270      * be handled again (eg because of a resume that happened before unhandle was called)
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         // just like resume, except don't set _dispatched=true;
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:  // Suspend called, but not yet returned to container
583                 case REDISPATCHING: // resumed while dispatched
584                 case COMPLETECALLED:   // complete called
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 }