| // |
| // ======================================================================== |
| // Copyright (c) 1995-2017 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 java.util.ArrayList; |
| import java.util.List; |
| import java.util.concurrent.TimeUnit; |
| import java.util.concurrent.TimeoutException; |
| |
| import javax.servlet.AsyncListener; |
| import javax.servlet.RequestDispatcher; |
| import javax.servlet.ServletContext; |
| import javax.servlet.ServletResponse; |
| |
| 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 dispatched state of the HttpChannel, used to control the overall lifecycle |
| */ |
| public enum State |
| { |
| IDLE, // Idle request |
| DISPATCHED, // Request dispatched to filter/servlet |
| 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 |
| ASYNC_ERROR, // handle an async 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. This can lead or follow the |
| * channel dispatch state and also includes reasons such as expired, |
| * dispatched or completed. |
| */ |
| public enum Async |
| { |
| NOT_ASYNC, |
| STARTED, // AsyncContext.startAsync() has been called |
| DISPATCH, // |
| 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 |
| } |
| |
| public enum Interest |
| { |
| NONE(false), |
| NEEDED(true), |
| REGISTERED(true); |
| |
| final boolean _interested; |
| boolean isInterested() { return _interested;} |
| |
| Interest(boolean interest) |
| { |
| _interested = interest; |
| } |
| } |
| |
| 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 Interest _asyncRead=Interest.NONE; |
| private boolean _asyncWritePossible; |
| private long _timeoutMs=DEFAULT_TIMEOUT; |
| private AsyncContextEvent _event; |
| |
| protected HttpChannelState(HttpChannel channel) |
| { |
| _channel=channel; |
| _state=State.IDLE; |
| _async=Async.NOT_ASYNC; |
| _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 String.format("%s@%x{s=%s a=%s i=%b r=%s/%s w=%b}", |
| getClass().getSimpleName(), |
| hashCode(), |
| _state, |
| _async, |
| _initial, |
| _asyncRead, |
| _asyncReadPossible, |
| _asyncWritePossible); |
| } |
| } |
| |
| 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() |
| { |
| if(DEBUG) |
| LOG.debug("{} handling {}",this,_state); |
| try(Locker.Lock lock= _locker.lock()) |
| { |
| switch(_state) |
| { |
| case IDLE: |
| _initial=true; |
| _state=State.DISPATCHED; |
| return Action.DISPATCH; |
| |
| case COMPLETING: |
| case COMPLETED: |
| return Action.TERMINATED; |
| |
| case ASYNC_WOKEN: |
| if (_asyncRead.isInterested() && _asyncReadPossible) |
| { |
| _state=State.ASYNC_IO; |
| _asyncRead=Interest.NONE; |
| return Action.READ_CALLBACK; |
| } |
| |
| if (_asyncWritePossible) |
| { |
| _state=State.ASYNC_IO; |
| _asyncWritePossible=false; |
| return Action.WRITE_CALLBACK; |
| } |
| |
| switch(_async) |
| { |
| case COMPLETE: |
| _state=State.COMPLETING; |
| return Action.COMPLETE; |
| case DISPATCH: |
| _state=State.DISPATCHED; |
| _async=Async.NOT_ASYNC; |
| return Action.ASYNC_DISPATCH; |
| case EXPIRING: |
| break; |
| case EXPIRED: |
| _state=State.DISPATCHED; |
| _async=Async.NOT_ASYNC; |
| return Action.ERROR_DISPATCH; |
| case STARTED: |
| return Action.WAIT; |
| case ERRORING: |
| _state=State.DISPATCHED; |
| return Action.ASYNC_ERROR; |
| case NOT_ASYNC: |
| break; |
| 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 (_state!=State.DISPATCHED || _async!=Async.NOT_ASYNC) |
| 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(Throwable e) |
| { |
| // TODO Async Dispatch Error |
| LOG.warn(e); |
| } |
| } |
| } |
| @Override |
| public String toString() |
| { |
| return "startAsync"; |
| } |
| }; |
| |
| runInContext(event,callback); |
| } |
| } |
| |
| protected void error(Throwable th) |
| { |
| try(Locker.Lock lock= _locker.lock()) |
| { |
| if (_event!=null) |
| _event.addThrowable(th); |
| _async=Async.ERRORING; |
| } |
| } |
| |
| public void asyncError(Throwable failure) |
| { |
| AsyncContextEvent event = null; |
| try (Locker.Lock lock= _locker.lock()) |
| { |
| switch (_state) |
| { |
| case IDLE: |
| case DISPATCHED: |
| case COMPLETING: |
| case COMPLETED: |
| case UPGRADED: |
| case ASYNC_IO: |
| case ASYNC_WOKEN: |
| { |
| break; |
| } |
| case ASYNC_WAIT: |
| { |
| _event.addThrowable(failure); |
| _state=State.ASYNC_WOKEN; |
| _async=Async.ERRORING; |
| event=_event; |
| break; |
| } |
| default: |
| { |
| throw new IllegalStateException(getStatusStringLocked()); |
| } |
| } |
| } |
| |
| if (event != null) |
| { |
| cancelTimeout(event); |
| runInContext(event, _channel); |
| } |
| } |
| |
| /** |
| * 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; |
| boolean read_interested=false; |
| |
| if(DEBUG) |
| LOG.debug("{} unhandle {}",this,_state); |
| |
| try(Locker.Lock lock= _locker.lock()) |
| { |
| switch(_state) |
| { |
| case COMPLETING: |
| case COMPLETED: |
| return Action.TERMINATED; |
| |
| case DISPATCHED: |
| case ASYNC_IO: |
| break; |
| |
| default: |
| throw new IllegalStateException(this.getStatusStringLocked()); |
| } |
| |
| _initial=false; |
| switch(_async) |
| { |
| case COMPLETE: |
| _state=State.COMPLETING; |
| _async=Async.NOT_ASYNC; |
| action=Action.COMPLETE; |
| break; |
| |
| case DISPATCH: |
| _state=State.DISPATCHED; |
| _async=Async.NOT_ASYNC; |
| action=Action.ASYNC_DISPATCH; |
| break; |
| |
| case EXPIRED: |
| _state=State.DISPATCHED; |
| _async=Async.NOT_ASYNC; |
| action=Action.ERROR_DISPATCH; |
| break; |
| |
| case STARTED: |
| if (_asyncRead.isInterested() && _asyncReadPossible) |
| { |
| _state=State.ASYNC_IO; |
| _asyncRead=Interest.NONE; |
| action=Action.READ_CALLBACK; |
| } |
| else if (_asyncWritePossible) |
| { |
| _state=State.ASYNC_IO; |
| _asyncWritePossible=false; |
| action=Action.WRITE_CALLBACK; |
| } |
| else |
| { |
| _state=State.ASYNC_WAIT; |
| action=Action.WAIT; |
| if (_asyncRead==Interest.NEEDED) |
| { |
| _asyncRead=Interest.REGISTERED; |
| read_interested=true; |
| } |
| Scheduler scheduler=_channel.getScheduler(); |
| if (scheduler!=null && _timeoutMs>0) |
| _event.setTimeoutTask(scheduler.schedule(_event,_timeoutMs,TimeUnit.MILLISECONDS)); |
| } |
| break; |
| |
| case EXPIRING: |
| _state=State.ASYNC_WAIT; |
| action=Action.WAIT; |
| break; |
| |
| case ERRORING: |
| _state=State.DISPATCHED; |
| action=Action.ASYNC_ERROR; |
| break; |
| |
| case ERRORED: |
| _state=State.DISPATCHED; |
| action=Action.ERROR_DISPATCH; |
| _async=Async.NOT_ASYNC; |
| break; |
| |
| case NOT_ASYNC: |
| _state=State.COMPLETING; |
| action=Action.COMPLETE; |
| break; |
| |
| default: |
| _state=State.COMPLETING; |
| action=Action.COMPLETE; |
| break; |
| } |
| } |
| |
| if (read_interested) |
| _channel.asyncReadFillInterested(); |
| |
| return action; |
| } |
| |
| public void dispatch(ServletContext context, String path) |
| { |
| boolean dispatch=false; |
| AsyncContextEvent event=null; |
| try(Locker.Lock lock= _locker.lock()) |
| { |
| boolean started=false; |
| event=_event; |
| switch(_async) |
| { |
| case STARTED: |
| started=true; |
| break; |
| case EXPIRING: |
| 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 (_async!=Async.STARTED) |
| return; |
| _async=Async.EXPIRING; |
| event=_event; |
| listeners=_asyncListeners; |
| |
| } |
| |
| if (LOG.isDebugEnabled()) |
| LOG.debug("Async timeout {}",this); |
| |
| if (listeners!=null) |
| { |
| Runnable callback=new Runnable() |
| { |
| @Override |
| public void run() |
| { |
| for (AsyncListener listener : listeners) |
| { |
| try |
| { |
| listener.onTimeout(event); |
| } |
| catch(Throwable e) |
| { |
| LOG.debug(e); |
| event.addThrowable(e); |
| _channel.getRequest().setAttribute(RequestDispatcher.ERROR_EXCEPTION,event.getThrowable()); |
| break; |
| } |
| } |
| } |
| @Override |
| public String toString() |
| { |
| return "onTimeout"; |
| } |
| }; |
| |
| runInContext(event,callback); |
| } |
| |
| boolean dispatch=false; |
| try(Locker.Lock lock= _locker.lock()) |
| { |
| switch(_async) |
| { |
| case EXPIRING: |
| if (event.getThrowable()==null) |
| { |
| _async=Async.EXPIRED; |
| _event.addThrowable(new TimeoutException("Async API violation")); |
| } |
| else |
| { |
| _async=Async.ERRORING; |
| } |
| break; |
| |
| case COMPLETE: |
| case DISPATCH: |
| break; |
| |
| default: |
| throw new IllegalStateException(); |
| } |
| |
| if (_state==State.ASYNC_WAIT) |
| { |
| _state=State.ASYNC_WOKEN; |
| dispatch=true; |
| } |
| } |
| |
| 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=null; |
| try(Locker.Lock lock= _locker.lock()) |
| { |
| boolean started=false; |
| event=_event; |
| |
| switch(_async) |
| { |
| case STARTED: |
| started=true; |
| break; |
| case EXPIRING: |
| case ERRORED: |
| break; |
| 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()) |
| { |
| _async=Async.COMPLETE; |
| _event.setDispatchContext(null); |
| _event.setDispatchPath(null); |
| } |
| |
| cancelTimeout(); |
| } |
| |
| protected void onError() |
| { |
| final List<AsyncListener> aListeners; |
| final AsyncContextEvent event; |
| |
| try(Locker.Lock lock= _locker.lock()) |
| { |
| if (_state!=State.DISPATCHED/* || _async!=Async.ERRORING*/) |
| throw new IllegalStateException(this.getStatusStringLocked()); |
| |
| aListeners=_asyncListeners; |
| event=_event; |
| _async=Async.ERRORED; |
| } |
| |
| if (event!=null && aListeners!=null) |
| { |
| event.getSuppliedRequest().setAttribute(RequestDispatcher.ERROR_EXCEPTION,event.getThrowable()); |
| event.getSuppliedRequest().setAttribute(RequestDispatcher.ERROR_MESSAGE,event.getThrowable().getMessage()); |
| for (AsyncListener listener : aListeners) |
| { |
| try |
| { |
| listener.onError(event); |
| } |
| catch(Throwable x) |
| { |
| LOG.info("Exception while invoking listener " + listener, x); |
| } |
| } |
| } |
| } |
| |
| |
| protected void onComplete() |
| { |
| final List<AsyncListener> aListeners; |
| final AsyncContextEvent event; |
| |
| try(Locker.Lock lock= _locker.lock()) |
| { |
| switch(_state) |
| { |
| case COMPLETING: |
| aListeners=_asyncListeners; |
| event=_event; |
| _state=State.COMPLETED; |
| _async=Async.NOT_ASYNC; |
| 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(Throwable e) |
| { |
| LOG.warn(e); |
| } |
| } |
| } |
| @Override |
| public String toString() |
| { |
| return "onComplete"; |
| } |
| }; |
| |
| runInContext(event,callback); |
| } |
| event.completed(); |
| } |
| } |
| |
| protected void recycle() |
| { |
| cancelTimeout(); |
| try(Locker.Lock lock= _locker.lock()) |
| { |
| switch(_state) |
| { |
| case DISPATCHED: |
| case ASYNC_IO: |
| throw new IllegalStateException(getStatusStringLocked()); |
| case UPGRADED: |
| return; |
| default: |
| break; |
| } |
| _asyncListeners=null; |
| _state=State.IDLE; |
| _async=Async.NOT_ASYNC; |
| _initial=true; |
| _asyncReadPossible=false; |
| _asyncRead=Interest.NONE; |
| _asyncWritePossible=false; |
| _timeoutMs=DEFAULT_TIMEOUT; |
| _event=null; |
| } |
| } |
| |
| public void upgrade() |
| { |
| cancelTimeout(); |
| try(Locker.Lock lock= _locker.lock()) |
| { |
| switch(_state) |
| { |
| case IDLE: |
| case COMPLETED: |
| break; |
| default: |
| throw new IllegalStateException(getStatusStringLocked()); |
| } |
| _asyncListeners=null; |
| _state=State.UPGRADED; |
| _async=Async.NOT_ASYNC; |
| _initial=true; |
| _asyncReadPossible=false; |
| _asyncRead=Interest.NONE; |
| _asyncWritePossible=false; |
| _timeoutMs=DEFAULT_TIMEOUT; |
| _event=null; |
| } |
| } |
| |
| protected void scheduleDispatch() |
| { |
| _channel.execute(_channel); |
| } |
| |
| 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!=Async.NOT_ASYNC; |
| return _async==Async.STARTED || _async==Async.EXPIRING; |
| } |
| } |
| |
| public boolean isAsync() |
| { |
| try(Locker.Lock lock= _locker.lock()) |
| { |
| return !_initial || _async!=Async.NOT_ASYNC; |
| } |
| } |
| |
| 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()) |
| { |
| // We were already unready, this is not a state change, so do nothing |
| if (_asyncRead!=Interest.REGISTERED) |
| { |
| _asyncReadPossible=false; // Assumes this has been checked in isReady() with lock held |
| if (_state==State.ASYNC_WAIT) |
| { |
| interested=true; |
| _asyncRead=Interest.REGISTERED; |
| } |
| else |
| _asyncRead=Interest.NEEDED; |
| } |
| } |
| |
| 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()) |
| { |
| _asyncReadPossible=true; |
| if (_state==State.ASYNC_WAIT && _asyncRead.isInterested()) |
| { |
| 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()) |
| { |
| _asyncRead=Interest.REGISTERED; |
| _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()) |
| { |
| _asyncWritePossible=true; |
| if (_state==State.ASYNC_WAIT) |
| { |
| _state=State.ASYNC_WOKEN; |
| handle=true; |
| } |
| } |
| |
| return handle; |
| } |
| |
| } |