]> WPIA git - gigi.git/blob - lib/jetty/org/eclipse/jetty/server/HttpChannelState.java
Importing upstream Jetty jetty-9.2.1.v20140609
[gigi.git] / lib / jetty / org / eclipse / jetty / server / HttpChannelState.java
1 //
2 //  ========================================================================
3 //  Copyright (c) 1995-2014 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 public class HttpChannelState
40 {
41     private static final Logger LOG = Log.getLogger(HttpChannelState.class);
42
43     private final static long DEFAULT_TIMEOUT=30000L;
44
45     /** The dispatched state of the HttpChannel, used to control the overall livecycle
46      */
47     public enum State
48     {
49         IDLE,             // Idle request
50         DISPATCHED,       // Request dispatched to filter/servlet
51         ASYNC_WAIT,       // Suspended and parked
52         ASYNC_WOKEN,      // A thread has been dispatch to handle from ASYNCWAIT
53         ASYNC_IO,         // Has been dispatched for async IO
54         COMPLETING,       // Request is completable
55         COMPLETED         // Request is complete
56     }
57
58     /**
59      * The actions to take as the channel moves from state to state.
60      */
61     public enum Action
62     {
63         REQUEST_DISPATCH, // handle a normal request dispatch  
64         ASYNC_DISPATCH,   // handle an async request dispatch
65         ASYNC_EXPIRED,    // handle an async timeout
66         WRITE_CALLBACK,   // handle an IO write callback
67         READ_CALLBACK,    // handle an IO read callback
68         WAIT,             // Wait for further events 
69         COMPLETE          // Complete the channel
70     }
71     
72     /**
73      * The state of the servlet async API.  This can lead or follow the 
74      * channel dispatch state and also includes reasons such as expired,
75      * dispatched or completed.
76      */
77     public enum Async
78     {
79         STARTED,
80         DISPATCH,
81         COMPLETE,
82         EXPIRING,
83         EXPIRED
84     }
85
86     private final boolean DEBUG=LOG.isDebugEnabled();
87     private final HttpChannel<?> _channel;
88
89     private List<AsyncListener> _asyncListeners;
90     private State _state;
91     private Async _async;
92     private boolean _initial;
93     private boolean _asyncRead;
94     private boolean _asyncWrite;
95     private long _timeoutMs=DEFAULT_TIMEOUT;
96     private AsyncContextEvent _event;
97
98     protected HttpChannelState(HttpChannel<?> channel)
99     {
100         _channel=channel;
101         _state=State.IDLE;
102         _async=null;
103         _initial=true;
104     }
105
106     public State getState()
107     {
108         synchronized(this)
109         {
110             return _state;
111         }
112     }
113
114     public void addListener(AsyncListener listener)
115     {
116         synchronized(this)
117         {
118             if (_asyncListeners==null)
119                 _asyncListeners=new ArrayList<>();
120             _asyncListeners.add(listener);
121         }
122     }
123
124     public void setTimeout(long ms)
125     {
126         synchronized(this)
127         {
128             _timeoutMs=ms;
129         }
130     }
131
132     public long getTimeout()
133     {
134         synchronized(this)
135         {
136             return _timeoutMs;
137         }
138     }
139
140     public AsyncContextEvent getAsyncContextEvent()
141     {
142         synchronized(this)
143         {
144             return _event;
145         }
146     }
147
148     @Override
149     public String toString()
150     {
151         synchronized (this)
152         {
153             return String.format("%s@%x{s=%s i=%b a=%s}",getClass().getSimpleName(),hashCode(),_state,_initial,_async);
154         }
155     }
156
157     public String getStatusString()
158     {
159         synchronized (this)
160         {
161             return String.format("s=%s i=%b a=%s",_state,_initial,_async);
162         }
163     }
164
165     /**
166      * @return Next handling of the request should proceed
167      */
168     protected Action handling()
169     {
170         synchronized (this)
171         {
172             if(DEBUG)
173                 LOG.debug("{} handling {}",this,_state);
174             switch(_state)
175             {
176                 case IDLE:
177                     _initial=true;
178                     _state=State.DISPATCHED;
179                     return Action.REQUEST_DISPATCH;
180
181                 case COMPLETING:
182                     return Action.COMPLETE;
183
184                 case COMPLETED:
185                     return Action.WAIT;
186
187                 case ASYNC_WOKEN:
188                     if (_asyncRead)
189                     {
190                         _state=State.ASYNC_IO;
191                         _asyncRead=false;
192                         return Action.READ_CALLBACK;
193                     }
194                     if (_asyncWrite)
195                     {
196                         _state=State.ASYNC_IO;
197                         _asyncWrite=false;
198                         return Action.WRITE_CALLBACK;
199                     }
200                     
201                     if (_async!=null)
202                     {
203                         Async async=_async;
204                         switch(async)
205                         {
206                             case COMPLETE:
207                                 _state=State.COMPLETING;
208                                 return Action.COMPLETE;
209                             case DISPATCH:
210                                 _state=State.DISPATCHED;
211                                 _async=null;
212                                 return Action.ASYNC_DISPATCH;
213                             case EXPIRING:
214                                 break;
215                             case EXPIRED:
216                                 _state=State.DISPATCHED;
217                                 _async=null;
218                                 return Action.ASYNC_EXPIRED;
219                             case STARTED:
220                                 // TODO
221                                 if (DEBUG)
222                                     LOG.debug("TODO Fix this double dispatch",new IllegalStateException(this
223                                             .getStatusString()));
224                                 return Action.WAIT;
225                         }
226                     }
227                     
228                     return Action.WAIT;
229
230                 default:
231                     throw new IllegalStateException(this.getStatusString());
232             }
233         }
234     }
235
236     public void startAsync(AsyncContextEvent event)
237     {
238         final List<AsyncListener> lastAsyncListeners;
239         
240         synchronized (this)
241         {
242             if (_state!=State.DISPATCHED || _async!=null)
243                 throw new IllegalStateException(this.getStatusString());
244             
245             _async=Async.STARTED;
246             _event=event;
247             lastAsyncListeners=_asyncListeners;
248             _asyncListeners=null;
249         }
250
251         if (lastAsyncListeners!=null)
252         {
253             for (AsyncListener listener : lastAsyncListeners)
254             {
255                 try
256                 {
257                     listener.onStartAsync(event);
258                 }
259                 catch(Exception e)
260                 {
261                     LOG.warn(e);
262                 }
263             }
264         }
265     }
266
267     protected void error(Throwable th)
268     {
269         synchronized (this)
270         {
271             if (_event!=null)
272                 _event.setThrowable(th);
273         }
274     }
275
276     /**
277      * Signal that the HttpConnection has finished handling the request.
278      * For blocking connectors, this call may block if the request has
279      * been suspended (startAsync called).
280      * @return next actions
281      * be handled again (eg because of a resume that happened before unhandle was called)
282      */
283     protected Action unhandle()
284     {
285         synchronized (this)
286         {
287             if(DEBUG)
288                 LOG.debug("{} unhandle {}",this,_state);
289             
290             switch(_state)
291             {
292                 case DISPATCHED:
293                 case ASYNC_IO:
294                     break;
295                 default:
296                     throw new IllegalStateException(this.getStatusString());
297             }
298
299             if (_asyncRead)
300             {
301                 _state=State.ASYNC_IO;
302                 _asyncRead=false;
303                 return Action.READ_CALLBACK;
304             }
305             
306             if (_asyncWrite)
307             {
308                 _asyncWrite=false;
309                 _state=State.ASYNC_IO;
310                 return Action.WRITE_CALLBACK;
311             }
312
313             if (_async!=null)
314             {
315                 _initial=false;
316                 switch(_async)
317                 {
318                     case COMPLETE:
319                         _state=State.COMPLETING;
320                         _async=null;
321                         return Action.COMPLETE;
322                     case DISPATCH:
323                         _state=State.DISPATCHED;
324                         _async=null;
325                         return Action.ASYNC_DISPATCH;
326                     case EXPIRED:
327                         _state=State.DISPATCHED;
328                         _async=null;
329                         return Action.ASYNC_EXPIRED;
330                     case EXPIRING:
331                     case STARTED:
332                         scheduleTimeout();
333                         _state=State.ASYNC_WAIT;
334                         return Action.WAIT;
335                 }
336             }
337             
338             _state=State.COMPLETING;
339             return Action.COMPLETE;
340         }
341     }
342
343     public void dispatch(ServletContext context, String path)
344     {
345         boolean dispatch;
346         synchronized (this)
347         {
348             if (_async!=Async.STARTED && _async!=Async.EXPIRING)
349                 throw new IllegalStateException("AsyncContext#dispath "+this.getStatusString());
350             _async=Async.DISPATCH;
351             
352             if (context!=null)
353                 _event.setDispatchContext(context);
354             if (path!=null)
355                 _event.setDispatchPath(path);
356            
357             switch(_state)
358             {
359                 case DISPATCHED:
360                 case ASYNC_IO:
361                     dispatch=false;
362                     break;
363                 case ASYNC_WAIT:
364                     _state=State.ASYNC_WOKEN;
365                     dispatch=true;
366                     break;
367                 case ASYNC_WOKEN:
368                     dispatch=false;
369                     break;
370                 default:
371                     LOG.warn("async dispatched when complete {}",this);
372                     dispatch=false;
373                     break;
374             }
375         }
376
377         cancelTimeout();
378         if (dispatch)
379             scheduleDispatch();
380     }
381
382     protected void expired()
383     {
384         final List<AsyncListener> aListeners;
385         AsyncContextEvent event;
386         synchronized (this)
387         {
388             if (_async!=Async.STARTED)
389                 return;
390             _async=Async.EXPIRING;
391             event=_event;
392             aListeners=_asyncListeners;
393         }
394
395         if (aListeners!=null)
396         {
397             for (AsyncListener listener : aListeners)
398             {
399                 try
400                 {
401                     listener.onTimeout(event);
402                 }
403                 catch(Exception e)
404                 {
405                     LOG.debug(e);
406                     event.setThrowable(e);
407                     _channel.getRequest().setAttribute(RequestDispatcher.ERROR_EXCEPTION,e);
408                     break;
409                 }
410             }
411         }
412         
413         boolean dispatch=false;
414         synchronized (this)
415         {
416             if (_async==Async.EXPIRING)
417             {
418                 _async=Async.EXPIRED;
419                 if (_state==State.ASYNC_WAIT)
420                 {
421                     _state=State.ASYNC_WOKEN;
422                     dispatch=true;
423                 }
424             }
425         }
426
427         if (dispatch)
428             scheduleDispatch();
429     }
430
431     public void complete()
432     {
433         // just like resume, except don't set _dispatched=true;
434         boolean handle=false;
435         synchronized (this)
436         {
437             if (_async!=Async.STARTED && _async!=Async.EXPIRING)
438                 throw new IllegalStateException(this.getStatusString());
439             _async=Async.COMPLETE;
440             if (_state==State.ASYNC_WAIT)
441             {
442                 handle=true;
443                 _state=State.ASYNC_WOKEN;
444             }
445         }
446
447         cancelTimeout();
448         if (handle)
449         {
450             ContextHandler handler=getContextHandler();
451             if (handler!=null)
452                 handler.handle(_channel);
453             else
454                 _channel.handle();
455         }
456     }
457
458     public void errorComplete()
459     {
460         synchronized (this)
461         {
462             _async=Async.COMPLETE;
463             _event.setDispatchContext(null);
464             _event.setDispatchPath(null);
465         }
466
467         cancelTimeout();
468     }
469
470     protected void completed()
471     {
472         final List<AsyncListener> aListeners;
473         final AsyncContextEvent event;
474         synchronized (this)
475         {
476             switch(_state)
477             {
478                 case COMPLETING:
479                     _state=State.COMPLETED;
480                     aListeners=_asyncListeners;
481                     event=_event;
482                     break;
483
484                 default:
485                     throw new IllegalStateException(this.getStatusString());
486             }
487         }
488
489         if (event!=null)
490         {
491             if (aListeners!=null)
492             {
493                 if (event.getThrowable()!=null)
494                 {
495                     event.getSuppliedRequest().setAttribute(RequestDispatcher.ERROR_EXCEPTION,event.getThrowable());
496                     event.getSuppliedRequest().setAttribute(RequestDispatcher.ERROR_MESSAGE,event.getThrowable().getMessage());
497                 }
498
499                 for (AsyncListener listener : aListeners)
500                 {
501                     try
502                     {
503                         if (event.getThrowable()!=null)
504                             listener.onError(event);
505                         else
506                             listener.onComplete(event);
507                     }
508                     catch(Exception e)
509                     {
510                         LOG.warn(e);
511                     }
512                 }
513             }
514
515             event.completed();
516         }
517     }
518
519     protected void recycle()
520     {
521         synchronized (this)
522         {
523             switch(_state)
524             {
525                 case DISPATCHED:
526                 case ASYNC_IO:
527                     throw new IllegalStateException(getStatusString());
528                 default:
529                     break;
530             }
531             _asyncListeners=null;
532             _state=State.IDLE;
533             _async=null;
534             _initial=true;
535             _asyncRead=false;
536             _asyncWrite=false;
537             _timeoutMs=DEFAULT_TIMEOUT;
538             cancelTimeout();
539             _event=null;
540         }
541     }
542
543     protected void scheduleDispatch()
544     {
545         _channel.execute(_channel);
546     }
547
548     protected void scheduleTimeout()
549     {
550         Scheduler scheduler = _channel.getScheduler();
551         if (scheduler!=null && _timeoutMs>0)
552             _event.setTimeoutTask(scheduler.schedule(_event,_timeoutMs,TimeUnit.MILLISECONDS));
553     }
554
555     protected void cancelTimeout()
556     {
557         final AsyncContextEvent event;
558         synchronized (this)
559         { 
560             event=_event;
561         }
562         if (event!=null)
563             event.cancelTimeoutTask();
564     }
565
566     public boolean isExpired()
567     {
568         synchronized (this)
569         {
570             return _async==Async.EXPIRED;
571         }
572     }
573
574     public boolean isInitial()
575     {
576         synchronized(this)
577         {
578             return _initial;
579         }
580     }
581
582     public boolean isSuspended()
583     {
584         synchronized(this)
585         {
586             return _state==State.ASYNC_WAIT || _state==State.DISPATCHED && _async==Async.STARTED;
587         }
588     }
589
590     boolean isCompleting()
591     {
592         synchronized (this)
593         {
594             return _state==State.COMPLETING;
595         }
596     }
597
598     boolean isCompleted()
599     {
600         synchronized (this)
601         {
602             return _state == State.COMPLETED;
603         }
604     }
605
606     public boolean isAsyncStarted()
607     {
608         synchronized (this)
609         {    
610             if (_state==State.DISPATCHED)
611                 return _async!=null;
612             return _async==Async.STARTED || _async==Async.EXPIRING;
613         }
614     }
615
616     public boolean isAsync()
617     {
618         synchronized (this)
619         {
620             return !_initial || _async!=null;
621         }
622     }
623
624     public Request getBaseRequest()
625     {
626         return _channel.getRequest();
627     }
628
629     public HttpChannel<?> getHttpChannel()
630     {
631         return _channel;
632     }
633
634     public ContextHandler getContextHandler()
635     {
636         final AsyncContextEvent event;
637         synchronized (this)
638         { 
639             event=_event;
640         }
641        
642         if (event!=null)
643         {
644             Context context=((Context)event.getServletContext());
645             if (context!=null)
646                 return context.getContextHandler();
647         }
648         return null;
649     }
650
651     public ServletResponse getServletResponse()
652     {
653         final AsyncContextEvent event;
654         synchronized (this)
655         { 
656             event=_event;
657         }
658         if (event!=null && event.getSuppliedResponse()!=null)
659             return event.getSuppliedResponse();
660         return _channel.getResponse();
661     }
662
663     public Object getAttribute(String name)
664     {
665         return _channel.getRequest().getAttribute(name);
666     }
667
668     public void removeAttribute(String name)
669     {
670         _channel.getRequest().removeAttribute(name);
671     }
672
673     public void setAttribute(String name, Object attribute)
674     {
675         _channel.getRequest().setAttribute(name,attribute);
676     }
677
678     public void onReadPossible()
679     {
680         boolean handle=false;
681
682         synchronized (this)
683         {
684             _asyncRead=true;
685             if (_state==State.ASYNC_WAIT)
686             {
687                 _state=State.ASYNC_WOKEN;
688                 handle=true;
689             }
690         }
691
692         if (handle)
693             _channel.execute(_channel);
694     }
695     
696     public void onWritePossible()
697     {
698         boolean handle=false;
699
700         synchronized (this)
701         {
702             _asyncWrite=true;
703             if (_state==State.ASYNC_WAIT)
704             {
705                 _state=State.ASYNC_WOKEN;
706                 handle=true;
707             }
708         }
709
710         if (handle)
711             _channel.execute(_channel);
712     }
713     
714 }