| // |
| // ======================================================================== |
| // Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd. |
| // ------------------------------------------------------------------------ |
| // All rights reserved. This program and the accompanying materials |
| // are made available under the terms of the Eclipse Public License v1.0 |
| // and Apache License v2.0 which accompanies this distribution. |
| // |
| // The Eclipse Public License is available at |
| // http://www.eclipse.org/legal/epl-v10.html |
| // |
| // The Apache License v2.0 is available at |
| // http://www.opensource.org/licenses/apache2.0.php |
| // |
| // You may elect to redistribute this code under either of these licenses. |
| // ======================================================================== |
| // |
| |
| package org.eclipse.jetty.server; |
| |
| import static javax.servlet.RequestDispatcher.ERROR_EXCEPTION; |
| import static javax.servlet.RequestDispatcher.ERROR_MESSAGE; |
| import static javax.servlet.RequestDispatcher.ERROR_STATUS_CODE; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.concurrent.TimeUnit; |
| import java.util.concurrent.atomic.AtomicReference; |
| |
| import javax.servlet.AsyncListener; |
| import javax.servlet.RequestDispatcher; |
| import javax.servlet.ServletContext; |
| import javax.servlet.ServletResponse; |
| import javax.servlet.UnavailableException; |
| |
| import org.eclipse.jetty.http.BadMessageException; |
| import org.eclipse.jetty.http.HttpStatus; |
| import org.eclipse.jetty.server.handler.ContextHandler; |
| import org.eclipse.jetty.server.handler.ContextHandler.Context; |
| import org.eclipse.jetty.util.log.Log; |
| import org.eclipse.jetty.util.log.Logger; |
| import org.eclipse.jetty.util.thread.Locker; |
| import org.eclipse.jetty.util.thread.Scheduler; |
| |
| /** |
| * Implementation of AsyncContext interface that holds the state of request-response cycle. |
| */ |
| public class HttpChannelState |
| { |
| private static final Logger LOG = Log.getLogger(HttpChannelState.class); |
| |
| private final static long DEFAULT_TIMEOUT=Long.getLong("org.eclipse.jetty.server.HttpChannelState.DEFAULT_TIMEOUT",30000L); |
| |
| /** |
| * The state of the HttpChannel,used to control the overall lifecycle. |
| */ |
| public enum State |
| { |
| IDLE, // Idle request |
| DISPATCHED, // Request dispatched to filter/servlet |
| THROWN, // Exception thrown while DISPATCHED |
| ASYNC_WAIT, // Suspended and waiting |
| ASYNC_WOKEN, // Dispatch to handle from ASYNC_WAIT |
| ASYNC_IO, // Dispatched for async IO |
| COMPLETING, // Response is completable |
| COMPLETED, // Response is completed |
| UPGRADED // Request upgraded the connection |
| } |
| |
| /** |
| * The actions to take as the channel moves from state to state. |
| */ |
| public enum Action |
| { |
| DISPATCH, // handle a normal request dispatch |
| ASYNC_DISPATCH, // handle an async request dispatch |
| ERROR_DISPATCH, // handle a normal error |
| WRITE_CALLBACK, // handle an IO write callback |
| READ_CALLBACK, // handle an IO read callback |
| COMPLETE, // Complete the response |
| TERMINATED, // No further actions |
| WAIT, // Wait for further events |
| } |
| |
| /** |
| * The state of the servlet async API. |
| */ |
| public enum Async |
| { |
| STARTED, // AsyncContext.startAsync() has been called |
| DISPATCH, // AsyncContext.dispatch() has been called |
| COMPLETE, // AsyncContext.complete() has been called |
| EXPIRING, // AsyncContext timeout just happened |
| EXPIRED, // AsyncContext timeout has been processed |
| ERRORING, // An error just happened |
| ERRORED // The error has been processed |
| } |
| |
| private final boolean DEBUG=LOG.isDebugEnabled(); |
| private final Locker _locker=new Locker(); |
| private final HttpChannel _channel; |
| |
| private List<AsyncListener> _asyncListeners; |
| private State _state; |
| private Async _async; |
| private boolean _initial; |
| private boolean _asyncReadPossible; |
| private boolean _asyncReadUnready; |
| private boolean _asyncWrite; // TODO refactor same as read |
| private long _timeoutMs=DEFAULT_TIMEOUT; |
| private AsyncContextEvent _event; |
| |
| protected HttpChannelState(HttpChannel channel) |
| { |
| _channel=channel; |
| _state=State.IDLE; |
| _async=null; |
| _initial=true; |
| } |
| |
| public State getState() |
| { |
| try(Locker.Lock lock= _locker.lock()) |
| { |
| return _state; |
| } |
| } |
| |
| public void addListener(AsyncListener listener) |
| { |
| try(Locker.Lock lock= _locker.lock()) |
| { |
| if (_asyncListeners==null) |
| _asyncListeners=new ArrayList<>(); |
| _asyncListeners.add(listener); |
| } |
| } |
| |
| public void setTimeout(long ms) |
| { |
| try(Locker.Lock lock= _locker.lock()) |
| { |
| _timeoutMs=ms; |
| } |
| } |
| |
| public long getTimeout() |
| { |
| try(Locker.Lock lock= _locker.lock()) |
| { |
| return _timeoutMs; |
| } |
| } |
| |
| public AsyncContextEvent getAsyncContextEvent() |
| { |
| try(Locker.Lock lock= _locker.lock()) |
| { |
| return _event; |
| } |
| } |
| |
| @Override |
| public String toString() |
| { |
| try(Locker.Lock lock= _locker.lock()) |
| { |
| return toStringLocked(); |
| } |
| } |
| |
| public String toStringLocked() |
| { |
| return String.format("%s@%x{s=%s a=%s i=%b r=%s w=%b}",getClass().getSimpleName(),hashCode(),_state,_async,_initial, |
| _asyncReadPossible?(_asyncReadUnready?"PU":"P!U"):(_asyncReadUnready?"!PU":"!P!U"), |
| _asyncWrite); |
| } |
| |
| |
| private String getStatusStringLocked() |
| { |
| return String.format("s=%s i=%b a=%s",_state,_initial,_async); |
| } |
| |
| public String getStatusString() |
| { |
| try(Locker.Lock lock= _locker.lock()) |
| { |
| return getStatusStringLocked(); |
| } |
| } |
| |
| /** |
| * @return Next handling of the request should proceed |
| */ |
| protected Action handling() |
| { |
| try(Locker.Lock lock= _locker.lock()) |
| { |
| if(DEBUG) |
| LOG.debug("handling {}",toStringLocked()); |
| |
| switch(_state) |
| { |
| case IDLE: |
| _initial=true; |
| _state=State.DISPATCHED; |
| return Action.DISPATCH; |
| |
| case COMPLETING: |
| return Action.COMPLETE; |
| |
| case COMPLETED: |
| return Action.TERMINATED; |
| |
| case ASYNC_WOKEN: |
| if (_asyncReadPossible) |
| { |
| _state=State.ASYNC_IO; |
| _asyncReadUnready=false; |
| return Action.READ_CALLBACK; |
| } |
| |
| if (_asyncWrite) |
| { |
| _state=State.ASYNC_IO; |
| _asyncWrite=false; |
| return Action.WRITE_CALLBACK; |
| } |
| |
| if (_async!=null) |
| { |
| Async async=_async; |
| switch(async) |
| { |
| case COMPLETE: |
| _state=State.COMPLETING; |
| return Action.COMPLETE; |
| case DISPATCH: |
| _state=State.DISPATCHED; |
| _async=null; |
| return Action.ASYNC_DISPATCH; |
| case EXPIRED: |
| case ERRORED: |
| _state=State.DISPATCHED; |
| _async=null; |
| return Action.ERROR_DISPATCH; |
| case STARTED: |
| case EXPIRING: |
| case ERRORING: |
| return Action.WAIT; |
| |
| default: |
| throw new IllegalStateException(getStatusStringLocked()); |
| } |
| } |
| |
| return Action.WAIT; |
| |
| case ASYNC_IO: |
| case ASYNC_WAIT: |
| case DISPATCHED: |
| case UPGRADED: |
| default: |
| throw new IllegalStateException(getStatusStringLocked()); |
| |
| } |
| } |
| } |
| |
| public void startAsync(AsyncContextEvent event) |
| { |
| final List<AsyncListener> lastAsyncListeners; |
| |
| try(Locker.Lock lock= _locker.lock()) |
| { |
| if(DEBUG) |
| LOG.debug("startAsync {}",toStringLocked()); |
| |
| if (_state!=State.DISPATCHED || _async!=null) |
| throw new IllegalStateException(this.getStatusStringLocked()); |
| |
| _async=Async.STARTED; |
| _event=event; |
| lastAsyncListeners=_asyncListeners; |
| _asyncListeners=null; |
| } |
| |
| if (lastAsyncListeners!=null) |
| { |
| Runnable callback=new Runnable() |
| { |
| @Override |
| public void run() |
| { |
| for (AsyncListener listener : lastAsyncListeners) |
| { |
| try |
| { |
| listener.onStartAsync(event); |
| } |
| catch(Exception e) |
| { |
| // TODO Async Dispatch Error |
| LOG.warn(e); |
| } |
| } |
| } |
| @Override |
| public String toString() |
| { |
| return "startAsync"; |
| } |
| }; |
| |
| runInContext(event,callback); |
| } |
| } |
| |
| |
| /** |
| * Signal that the HttpConnection has finished handling the request. |
| * For blocking connectors,this call may block if the request has |
| * been suspended (startAsync called). |
| * @return next actions |
| * be handled again (eg because of a resume that happened before unhandle was called) |
| */ |
| protected Action unhandle() |
| { |
| Action action; |
| AsyncContextEvent schedule_event=null; |
| boolean read_interested=false; |
| |
| try(Locker.Lock lock= _locker.lock()) |
| { |
| if(DEBUG) |
| LOG.debug("unhandle {}",toStringLocked()); |
| |
| switch(_state) |
| { |
| case COMPLETING: |
| case COMPLETED: |
| return Action.TERMINATED; |
| |
| case THROWN: |
| _state=State.DISPATCHED; |
| return Action.ERROR_DISPATCH; |
| |
| case DISPATCHED: |
| case ASYNC_IO: |
| break; |
| |
| default: |
| throw new IllegalStateException(this.getStatusStringLocked()); |
| } |
| |
| if (_async!=null) |
| { |
| _initial=false; |
| switch(_async) |
| { |
| case COMPLETE: |
| _state=State.COMPLETING; |
| _async=null; |
| action=Action.COMPLETE; |
| break; |
| |
| case DISPATCH: |
| _state=State.DISPATCHED; |
| _async=null; |
| action=Action.ASYNC_DISPATCH; |
| break; |
| |
| case STARTED: |
| if (_asyncReadUnready && _asyncReadPossible) |
| { |
| _state=State.ASYNC_IO; |
| _asyncReadUnready=false; |
| action = Action.READ_CALLBACK; |
| } |
| else if (_asyncWrite) // TODO refactor same as read |
| { |
| _asyncWrite=false; |
| _state=State.ASYNC_IO; |
| action=Action.WRITE_CALLBACK; |
| } |
| else |
| { |
| schedule_event=_event; |
| read_interested=_asyncReadUnready; |
| _state=State.ASYNC_WAIT; |
| action=Action.WAIT; |
| } |
| break; |
| |
| case EXPIRING: |
| // onTimeout callbacks still being called, so just WAIT |
| _state=State.ASYNC_WAIT; |
| action=Action.WAIT; |
| break; |
| |
| case EXPIRED: |
| // onTimeout handling is complete, but did not dispatch as |
| // we were handling. So do the error dispatch here |
| _state=State.DISPATCHED; |
| _async=null; |
| action=Action.ERROR_DISPATCH; |
| break; |
| |
| case ERRORED: |
| _state=State.DISPATCHED; |
| _async=null; |
| action=Action.ERROR_DISPATCH; |
| break; |
| |
| default: |
| throw new IllegalStateException(this.getStatusStringLocked()); |
| } |
| } |
| else |
| { |
| _state=State.COMPLETING; |
| action=Action.COMPLETE; |
| } |
| } |
| |
| if (schedule_event!=null) |
| scheduleTimeout(schedule_event); |
| if (read_interested) |
| _channel.asyncReadFillInterested(); |
| return action; |
| } |
| |
| public void dispatch(ServletContext context, String path) |
| { |
| boolean dispatch=false; |
| AsyncContextEvent event; |
| try(Locker.Lock lock= _locker.lock()) |
| { |
| if(DEBUG) |
| LOG.debug("dispatch {} -> {}",toStringLocked(),path); |
| |
| boolean started=false; |
| event=_event; |
| switch(_async) |
| { |
| case STARTED: |
| started=true; |
| break; |
| case EXPIRING: |
| case ERRORING: |
| case ERRORED: |
| break; |
| default: |
| throw new IllegalStateException(this.getStatusStringLocked()); |
| } |
| _async=Async.DISPATCH; |
| |
| if (context!=null) |
| _event.setDispatchContext(context); |
| if (path!=null) |
| _event.setDispatchPath(path); |
| |
| if (started) |
| { |
| switch(_state) |
| { |
| case DISPATCHED: |
| case ASYNC_IO: |
| case ASYNC_WOKEN: |
| break; |
| case ASYNC_WAIT: |
| _state=State.ASYNC_WOKEN; |
| dispatch=true; |
| break; |
| default: |
| LOG.warn("async dispatched when complete {}",this); |
| break; |
| } |
| } |
| } |
| |
| cancelTimeout(event); |
| if (dispatch) |
| scheduleDispatch(); |
| } |
| |
| protected void onTimeout() |
| { |
| final List<AsyncListener> listeners; |
| AsyncContextEvent event; |
| try(Locker.Lock lock= _locker.lock()) |
| { |
| if(DEBUG) |
| LOG.debug("onTimeout {}",toStringLocked()); |
| |
| if (_async!=Async.STARTED) |
| return; |
| _async=Async.EXPIRING; |
| event=_event; |
| listeners=_asyncListeners; |
| |
| } |
| |
| final AtomicReference<Throwable> error=new AtomicReference<Throwable>(); |
| if (listeners!=null) |
| { |
| Runnable task=new Runnable() |
| { |
| @Override |
| public void run() |
| { |
| for (AsyncListener listener : listeners) |
| { |
| try |
| { |
| listener.onTimeout(event); |
| } |
| catch(Throwable x) |
| { |
| LOG.debug("Exception while invoking listener " + listener,x); |
| if (error.get()==null) |
| error.set(x); |
| else |
| error.get().addSuppressed(x); |
| } |
| } |
| } |
| @Override |
| public String toString() |
| { |
| return "onTimeout"; |
| } |
| }; |
| |
| runInContext(event,task); |
| } |
| |
| Throwable th=error.get(); |
| boolean dispatch=false; |
| try(Locker.Lock lock= _locker.lock()) |
| { |
| switch(_async) |
| { |
| case EXPIRING: |
| _async=th==null ? Async.EXPIRED : Async.ERRORING; |
| break; |
| |
| case COMPLETE: |
| case DISPATCH: |
| if (th!=null) |
| { |
| LOG.ignore(th); |
| th=null; |
| } |
| break; |
| |
| default: |
| throw new IllegalStateException(); |
| } |
| |
| if (_state==State.ASYNC_WAIT) |
| { |
| _state=State.ASYNC_WOKEN; |
| dispatch=true; |
| } |
| } |
| |
| if (th!=null) |
| { |
| if (LOG.isDebugEnabled()) |
| LOG.debug("Error after async timeout {}",this,th); |
| onError(th); |
| } |
| |
| if (dispatch) |
| { |
| if (LOG.isDebugEnabled()) |
| LOG.debug("Dispatch after async timeout {}",this); |
| scheduleDispatch(); |
| } |
| } |
| |
| public void complete() |
| { |
| |
| // just like resume, except don't set _dispatched=true; |
| boolean handle=false; |
| AsyncContextEvent event; |
| try(Locker.Lock lock= _locker.lock()) |
| { |
| if(DEBUG) |
| LOG.debug("complete {}",toStringLocked()); |
| |
| boolean started=false; |
| event=_event; |
| |
| switch(_async) |
| { |
| case STARTED: |
| started=true; |
| break; |
| case EXPIRING: |
| case ERRORING: |
| case ERRORED: |
| break; |
| case COMPLETE: |
| return; |
| default: |
| throw new IllegalStateException(this.getStatusStringLocked()); |
| } |
| _async=Async.COMPLETE; |
| |
| if (started && _state==State.ASYNC_WAIT) |
| { |
| handle=true; |
| _state=State.ASYNC_WOKEN; |
| } |
| } |
| |
| cancelTimeout(event); |
| if (handle) |
| runInContext(event,_channel); |
| } |
| |
| public void errorComplete() |
| { |
| try(Locker.Lock lock= _locker.lock()) |
| { |
| if(DEBUG) |
| LOG.debug("error complete {}",toStringLocked()); |
| |
| _async=Async.COMPLETE; |
| _event.setDispatchContext(null); |
| _event.setDispatchPath(null); |
| } |
| |
| cancelTimeout(); |
| } |
| |
| protected void onError(Throwable failure) |
| { |
| final List<AsyncListener> listeners; |
| final AsyncContextEvent event; |
| final Request baseRequest = _channel.getRequest(); |
| |
| int code=HttpStatus.INTERNAL_SERVER_ERROR_500; |
| String reason=null; |
| if (failure instanceof BadMessageException) |
| { |
| BadMessageException bme = (BadMessageException)failure; |
| code = bme.getCode(); |
| reason = bme.getReason(); |
| } |
| else if (failure instanceof UnavailableException) |
| { |
| if (((UnavailableException)failure).isPermanent()) |
| code = HttpStatus.NOT_FOUND_404; |
| else |
| code = HttpStatus.SERVICE_UNAVAILABLE_503; |
| } |
| |
| try(Locker.Lock lock= _locker.lock()) |
| { |
| if(DEBUG) |
| LOG.debug("onError {} {}",toStringLocked(),failure); |
| |
| // Set error on request. |
| if(_event!=null) |
| { |
| if (_event.getThrowable()!=null) |
| throw new IllegalStateException("Error already set",_event.getThrowable()); |
| _event.addThrowable(failure); |
| _event.getSuppliedRequest().setAttribute(ERROR_STATUS_CODE,code); |
| _event.getSuppliedRequest().setAttribute(ERROR_EXCEPTION,failure); |
| _event.getSuppliedRequest().setAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE,failure==null?null:failure.getClass()); |
| |
| _event.getSuppliedRequest().setAttribute(ERROR_MESSAGE,reason!=null?reason:null); |
| } |
| else |
| { |
| Throwable error = (Throwable)baseRequest.getAttribute(ERROR_EXCEPTION); |
| if (error!=null) |
| throw new IllegalStateException("Error already set",error); |
| baseRequest.setAttribute(ERROR_STATUS_CODE,code); |
| baseRequest.setAttribute(ERROR_EXCEPTION,failure); |
| baseRequest.setAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE,failure==null?null:failure.getClass()); |
| baseRequest.setAttribute(ERROR_MESSAGE,reason!=null?reason:null); |
| } |
| |
| // Are we blocking? |
| if (_async==null) |
| { |
| // Only called from within HttpChannel Handling, so much be dispatched, let's stay dispatched! |
| if (_state==State.DISPATCHED) |
| { |
| _state=State.THROWN; |
| return; |
| } |
| throw new IllegalStateException(this.getStatusStringLocked()); |
| } |
| |
| // We are Async |
| _async=Async.ERRORING; |
| listeners=_asyncListeners; |
| event=_event; |
| } |
| |
| if(listeners!=null) |
| { |
| Runnable task=new Runnable() |
| { |
| @Override |
| public void run() |
| { |
| for (AsyncListener listener : listeners) |
| { |
| try |
| { |
| listener.onError(event); |
| } |
| catch (Throwable x) |
| { |
| LOG.info("Exception while invoking listener " + listener,x); |
| } |
| } |
| } |
| |
| @Override |
| public String toString() |
| { |
| return "onError"; |
| } |
| }; |
| runInContext(event,task); |
| } |
| |
| boolean dispatch=false; |
| try(Locker.Lock lock= _locker.lock()) |
| { |
| switch(_async) |
| { |
| case ERRORING: |
| { |
| // Still in this state ? The listeners did not invoke API methods |
| // and the container must provide a default error dispatch. |
| _async=Async.ERRORED; |
| break; |
| } |
| case DISPATCH: |
| case COMPLETE: |
| { |
| // The listeners called dispatch() or complete(). |
| break; |
| } |
| default: |
| { |
| throw new IllegalStateException(toString()); |
| } |
| } |
| |
| if(_state==State.ASYNC_WAIT) |
| { |
| _state=State.ASYNC_WOKEN; |
| dispatch=true; |
| } |
| } |
| |
| if(dispatch) |
| { |
| if(LOG.isDebugEnabled()) |
| LOG.debug("Dispatch after error {}",this); |
| scheduleDispatch(); |
| } |
| } |
| |
| protected void onComplete() |
| { |
| final List<AsyncListener> aListeners; |
| final AsyncContextEvent event; |
| |
| try(Locker.Lock lock= _locker.lock()) |
| { |
| if(DEBUG) |
| LOG.debug("onComplete {}",toStringLocked()); |
| |
| switch(_state) |
| { |
| case COMPLETING: |
| aListeners=_asyncListeners; |
| event=_event; |
| _state=State.COMPLETED; |
| _async=null; |
| break; |
| |
| default: |
| throw new IllegalStateException(this.getStatusStringLocked()); |
| } |
| } |
| |
| if (event!=null) |
| { |
| if (aListeners!=null) |
| { |
| Runnable callback = new Runnable() |
| { |
| @Override |
| public void run() |
| { |
| for (AsyncListener listener : aListeners) |
| { |
| try |
| { |
| listener.onComplete(event); |
| } |
| catch(Exception e) |
| { |
| LOG.warn("Exception while invoking listener " + listener,e); |
| } |
| } |
| } |
| @Override |
| public String toString() |
| { |
| return "onComplete"; |
| } |
| }; |
| |
| runInContext(event,callback); |
| } |
| event.completed(); |
| } |
| } |
| |
| protected void recycle() |
| { |
| cancelTimeout(); |
| try(Locker.Lock lock= _locker.lock()) |
| { |
| if(DEBUG) |
| LOG.debug("recycle {}",toStringLocked()); |
| |
| switch(_state) |
| { |
| case DISPATCHED: |
| case ASYNC_IO: |
| throw new IllegalStateException(getStatusStringLocked()); |
| case UPGRADED: |
| return; |
| default: |
| break; |
| } |
| _asyncListeners=null; |
| _state=State.IDLE; |
| _async=null; |
| _initial=true; |
| _asyncReadPossible=_asyncReadUnready=false; |
| _asyncWrite=false; |
| _timeoutMs=DEFAULT_TIMEOUT; |
| _event=null; |
| } |
| } |
| |
| public void upgrade() |
| { |
| cancelTimeout(); |
| try(Locker.Lock lock= _locker.lock()) |
| { |
| if(DEBUG) |
| LOG.debug("upgrade {}",toStringLocked()); |
| |
| switch(_state) |
| { |
| case IDLE: |
| case COMPLETED: |
| break; |
| default: |
| throw new IllegalStateException(getStatusStringLocked()); |
| } |
| _asyncListeners=null; |
| _state=State.UPGRADED; |
| _async=null; |
| _initial=true; |
| _asyncReadPossible=_asyncReadUnready=false; |
| _asyncWrite=false; |
| _timeoutMs=DEFAULT_TIMEOUT; |
| _event=null; |
| } |
| } |
| |
| protected void scheduleDispatch() |
| { |
| _channel.execute(_channel); |
| } |
| |
| protected void scheduleTimeout(AsyncContextEvent event) |
| { |
| Scheduler scheduler = _channel.getScheduler(); |
| if (scheduler!=null && _timeoutMs>0) |
| event.setTimeoutTask(scheduler.schedule(event,_timeoutMs,TimeUnit.MILLISECONDS)); |
| } |
| |
| protected void cancelTimeout() |
| { |
| final AsyncContextEvent event; |
| try(Locker.Lock lock= _locker.lock()) |
| { |
| event=_event; |
| } |
| cancelTimeout(event); |
| } |
| |
| protected void cancelTimeout(AsyncContextEvent event) |
| { |
| if (event!=null) |
| event.cancelTimeoutTask(); |
| } |
| |
| public boolean isIdle() |
| { |
| try(Locker.Lock lock= _locker.lock()) |
| { |
| return _state==State.IDLE; |
| } |
| } |
| |
| public boolean isExpired() |
| { |
| try(Locker.Lock lock= _locker.lock()) |
| { |
| return _async==Async.EXPIRED; |
| } |
| } |
| |
| public boolean isInitial() |
| { |
| try(Locker.Lock lock= _locker.lock()) |
| { |
| return _initial; |
| } |
| } |
| |
| public boolean isSuspended() |
| { |
| try(Locker.Lock lock= _locker.lock()) |
| { |
| return _state==State.ASYNC_WAIT || _state==State.DISPATCHED && _async==Async.STARTED; |
| } |
| } |
| |
| boolean isCompleting() |
| { |
| try(Locker.Lock lock= _locker.lock()) |
| { |
| return _state==State.COMPLETING; |
| } |
| } |
| |
| boolean isCompleted() |
| { |
| try(Locker.Lock lock= _locker.lock()) |
| { |
| return _state == State.COMPLETED; |
| } |
| } |
| |
| public boolean isAsyncStarted() |
| { |
| try(Locker.Lock lock= _locker.lock()) |
| { |
| if (_state==State.DISPATCHED) |
| return _async!=null; |
| return _async==Async.STARTED || _async==Async.EXPIRING; |
| } |
| } |
| |
| public boolean isAsync() |
| { |
| try(Locker.Lock lock= _locker.lock()) |
| { |
| return !_initial || _async!=null; |
| } |
| } |
| |
| public Request getBaseRequest() |
| { |
| return _channel.getRequest(); |
| } |
| |
| public HttpChannel getHttpChannel() |
| { |
| return _channel; |
| } |
| |
| public ContextHandler getContextHandler() |
| { |
| final AsyncContextEvent event; |
| try(Locker.Lock lock= _locker.lock()) |
| { |
| event=_event; |
| } |
| return getContextHandler(event); |
| } |
| |
| ContextHandler getContextHandler(AsyncContextEvent event) |
| { |
| if (event!=null) |
| { |
| Context context=((Context)event.getServletContext()); |
| if (context!=null) |
| return context.getContextHandler(); |
| } |
| return null; |
| } |
| |
| public ServletResponse getServletResponse() |
| { |
| final AsyncContextEvent event; |
| try(Locker.Lock lock= _locker.lock()) |
| { |
| event=_event; |
| } |
| return getServletResponse(event); |
| } |
| |
| public ServletResponse getServletResponse(AsyncContextEvent event) |
| { |
| if (event!=null && event.getSuppliedResponse()!=null) |
| return event.getSuppliedResponse(); |
| return _channel.getResponse(); |
| } |
| |
| void runInContext(AsyncContextEvent event,Runnable runnable) |
| { |
| ContextHandler contextHandler = getContextHandler(event); |
| if (contextHandler==null) |
| runnable.run(); |
| else |
| contextHandler.handle(_channel.getRequest(),runnable); |
| } |
| |
| public Object getAttribute(String name) |
| { |
| return _channel.getRequest().getAttribute(name); |
| } |
| |
| public void removeAttribute(String name) |
| { |
| _channel.getRequest().removeAttribute(name); |
| } |
| |
| public void setAttribute(String name, Object attribute) |
| { |
| _channel.getRequest().setAttribute(name,attribute); |
| } |
| |
| |
| /* ------------------------------------------------------------ */ |
| /** Called to signal async read isReady() has returned false. |
| * This indicates that there is no content available to be consumed |
| * and that once the channel enteres the ASYNC_WAIT state it will |
| * register for read interest by calling {@link HttpChannel#asyncReadFillInterested()} |
| * either from this method or from a subsequent call to {@link #unhandle()}. |
| */ |
| public void onReadUnready() |
| { |
| boolean interested=false; |
| try(Locker.Lock lock= _locker.lock()) |
| { |
| if(DEBUG) |
| LOG.debug("onReadUnready {}",toStringLocked()); |
| |
| // We were already unready, this is not a state change, so do nothing |
| if (!_asyncReadUnready) |
| { |
| _asyncReadUnready=true; |
| _asyncReadPossible=false; // Assumes this has been checked in isReady() with lock held |
| if (_state==State.ASYNC_WAIT) |
| interested=true; |
| } |
| } |
| |
| if (interested) |
| _channel.asyncReadFillInterested(); |
| } |
| |
| /* ------------------------------------------------------------ */ |
| /** Called to signal that content is now available to read. |
| * If the channel is in ASYNC_WAIT state and unready (ie isReady() has |
| * returned false), then the state is changed to ASYNC_WOKEN and true |
| * is returned. |
| * @return True IFF the channel was unready and in ASYNC_WAIT state |
| */ |
| public boolean onReadPossible() |
| { |
| boolean woken=false; |
| try(Locker.Lock lock= _locker.lock()) |
| { |
| if(DEBUG) |
| LOG.debug("onReadPossible {}",toStringLocked()); |
| |
| _asyncReadPossible=true; |
| if (_state==State.ASYNC_WAIT && _asyncReadUnready) |
| { |
| woken=true; |
| _state=State.ASYNC_WOKEN; |
| } |
| } |
| return woken; |
| } |
| |
| /* ------------------------------------------------------------ */ |
| /** Called to signal that the channel is ready for a callback. |
| * This is similar to calling {@link #onReadUnready()} followed by |
| * {@link #onReadPossible()}, except that as content is already |
| * available, read interest is never set. |
| * @return true if woken |
| */ |
| public boolean onReadReady() |
| { |
| boolean woken=false; |
| try(Locker.Lock lock= _locker.lock()) |
| { |
| if(DEBUG) |
| LOG.debug("onReadReady {}",toStringLocked()); |
| |
| _asyncReadUnready=true; |
| _asyncReadPossible=true; |
| if (_state==State.ASYNC_WAIT) |
| { |
| woken=true; |
| _state=State.ASYNC_WOKEN; |
| } |
| } |
| return woken; |
| } |
| |
| public boolean isReadPossible() |
| { |
| try(Locker.Lock lock= _locker.lock()) |
| { |
| return _asyncReadPossible; |
| } |
| } |
| |
| public boolean onWritePossible() |
| { |
| boolean handle=false; |
| |
| try(Locker.Lock lock= _locker.lock()) |
| { |
| if(DEBUG) |
| LOG.debug("onWritePossible {}",toStringLocked()); |
| |
| _asyncWrite=true; |
| if (_state==State.ASYNC_WAIT) |
| { |
| _state=State.ASYNC_WOKEN; |
| handle=true; |
| } |
| } |
| |
| return handle; |
| } |
| |
| } |