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.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   * Implementation of AsyncContext interface that holds the state of request-response cycle.
38   *
39   * <table>
40   * <tr><th>STATE</th><th colspan=6>ACTION</th></tr>
41   * <tr><th></th>                           <th>handling()</th>  <th>startAsync()</th><th>unhandle()</th>  <th>dispatch()</th>   <th>complete()</th>      <th>completed()</th></tr>
42   * <tr><th align=right>IDLE:</th>          <td>DISPATCHED</td>  <td></td>            <td></td>            <td></td>             <td>COMPLETECALLED??</td><td></td></tr>
43   * <tr><th align=right>DISPATCHED:</th>    <td></td>            <td>ASYNCSTARTED</td><td>COMPLETING</td>  <td></td>             <td></td>                <td></td></tr>
44   * <tr><th align=right>ASYNCSTARTED:</th>  <td></td>            <td></td>            <td>ASYNCWAIT</td>   <td>REDISPATCHING</td><td>COMPLETECALLED</td>  <td></td></tr>
45   * <tr><th align=right>REDISPATCHING:</th> <td></td>            <td></td>            <td>REDISPATCHED</td><td></td>             <td></td>                <td></td></tr>
46   * <tr><th align=right>ASYNCWAIT:</th>     <td></td>            <td></td>            <td></td>            <td>REDISPATCH</td>   <td>COMPLETECALLED</td>  <td></td></tr>
47   * <tr><th align=right>REDISPATCH:</th>    <td>REDISPATCHED</td><td></td>            <td></td>            <td></td>             <td></td>                <td></td></tr>
48   * <tr><th align=right>REDISPATCHED:</th>  <td></td>            <td>ASYNCSTARTED</td><td>COMPLETING</td>  <td></td>             <td></td>                <td></td></tr>
49   * <tr><th align=right>COMPLETECALLED:</th><td>COMPLETING</td>  <td></td>            <td>COMPLETING</td>  <td></td>             <td></td>                <td></td></tr>
50   * <tr><th align=right>COMPLETING:</th>    <td>COMPLETING</td>  <td></td>            <td></td>            <td></td>             <td></td>                <td>COMPLETED</td></tr>
51   * <tr><th align=right>COMPLETED:</th>     <td></td>            <td></td>            <td></td>            <td></td>             <td></td>                <td></td></tr>
52   * </table>
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,          // Idle request
63          DISPATCHED,    // Request dispatched to filter/servlet
64          ASYNCWAIT,     // Suspended and parked
65          ASYNCIO,       // Has been dispatched for async IO
66          COMPLETING,    // Request is completable
67          COMPLETED      // Request is complete
68      }
69  
70      public enum Action
71      {
72          REQUEST_DISPATCH, // handle a normal request dispatch  
73          ASYNC_DISPATCH,   // handle an async request dispatch
74          ASYNC_EXPIRED,    // handle an async timeout
75          WRITE_CALLBACK,   // handle an IO write callback
76          READ_CALLBACK,    // handle an IO read callback
77          WAIT,             // Wait for further events 
78          COMPLETE          // Complete the channel
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      * @return Next handling of the request should proceed
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      * Signal that the HttpConnection has finished handling the request.
281      * For blocking connectors, this call may block if the request has
282      * been suspended (startAsync called).
283      * @return next actions
284      * be handled again (eg because of a resume that happened before unhandle was called)
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         // just like resume, except don't set _dispatched=true;
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 }