All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.jetty.ee8.nested.HttpChannelState Maven / Gradle / Ivy

The newest version!
//
// ========================================================================
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.ee8.nested;

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.ServletContext;
import javax.servlet.ServletResponse;
import javax.servlet.UnavailableException;
import org.eclipse.jetty.http.HttpException;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.io.QuietException;
import org.eclipse.jetty.util.thread.AutoLock;
import org.eclipse.jetty.util.thread.Scheduler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static javax.servlet.RequestDispatcher.ERROR_EXCEPTION;
import static javax.servlet.RequestDispatcher.ERROR_EXCEPTION_TYPE;
import static javax.servlet.RequestDispatcher.ERROR_MESSAGE;
import static javax.servlet.RequestDispatcher.ERROR_REQUEST_URI;
import static javax.servlet.RequestDispatcher.ERROR_SERVLET_NAME;
import static javax.servlet.RequestDispatcher.ERROR_STATUS_CODE;

/**
 * Implementation of AsyncContext interface that holds the state of request-response cycle.
 */
public class HttpChannelState {

    private static final Logger LOG = LoggerFactory.getLogger(HttpChannelState.class);

    private static final long DEFAULT_TIMEOUT = Long.getLong("org.eclipse.jetty.server.HttpChannelState.DEFAULT_TIMEOUT", 30000L);

    /*
     * The state of the HttpChannel,used to control the overall lifecycle.
     * 
     *     IDLE <-----> HANDLING ----> WAITING
     *       |                 ^       /
     *       |                  \     /
     *       v                   \   v
     *    UPGRADED               WOKEN
     * 
*/ public enum State { // Idle request IDLE, // Request dispatched to filter/servlet or Async IO callback HANDLING, // Suspended and waiting WAITING, // Dispatch to handle from ASYNC_WAIT WOKEN, // Request upgraded the connection UPGRADED } /* * The state of the request processing lifecycle. *
     *       BLOCKING <----> COMPLETING ---> COMPLETED
     *       ^  |  ^            ^
     *      /   |   \           |
     *     |    |    DISPATCH   |
     *     |    |    ^  ^       |
     *     |    v   /   |       |
     *     |  ASYNC -------> COMPLETE
     *     |    |       |       ^
     *     |    v       |       |
     *     |  EXPIRE    |       |
     *      \   |      /        |
     *       \  v     /         |
     *       EXPIRING ----------+
     * 
*/ private enum RequestState { // Blocking request dispatched BLOCKING, // AsyncContext.startAsync() has been called ASYNC, // AsyncContext.dispatch() has been called DISPATCH, // AsyncContext timeout has happened EXPIRE, // AsyncListeners are being called EXPIRING, // AsyncContext.complete() has been called COMPLETE, // Request is being closed (maybe asynchronously) COMPLETING, // Response is completed COMPLETED } /* * The input readiness state, which works together with {@link HttpInput.State} */ private enum InputState { // No isReady; No data IDLE, // isReady()==false; No data UNREADY, // isReady() was false; data is available READY } /* * The output committed state, which works together with {@link HttpOutput.State} */ private enum OutputState { OPEN, COMMITTED, COMPLETED, ABORTED } /** * The actions to take as the channel moves from state to state. */ public enum Action { // handle a normal request dispatch DISPATCH, // handle an async request dispatch ASYNC_DISPATCH, // Generate an error page or error dispatch SEND_ERROR, // handle an async error ASYNC_ERROR, // call asyncContext onTimeout ASYNC_TIMEOUT, // handle an IO write callback WRITE_CALLBACK, // handle an IO read callback READ_CALLBACK, // Complete the response by closing output COMPLETE, // No further actions TERMINATED, // Wait for further events WAIT } private final AutoLock _lock = new AutoLock(); private final HttpChannel _channel; private List _asyncListeners; private State _state = State.IDLE; private RequestState _requestState = RequestState.BLOCKING; private OutputState _outputState = OutputState.OPEN; private InputState _inputState = InputState.IDLE; private boolean _initial = true; private boolean _sendError; private boolean _asyncWritePossible; private long _timeoutMs = DEFAULT_TIMEOUT; private AsyncContextEvent _event; private Thread _onTimeoutThread; private Throwable _failure; private boolean _failureListener; protected HttpChannelState(HttpChannel channel) { _channel = channel; } AutoLock lock() { return _lock.lock(); } boolean isLockHeldByCurrentThread() { return _lock.isHeldByCurrentThread(); } public State getState() { try (AutoLock l = lock()) { return _state; } } public boolean onIdleTimeout(TimeoutException timeout) { try (AutoLock l = lock()) { return _state == State.IDLE; } } public void addListener(AsyncListener listener) { try (AutoLock l = lock()) { if (_asyncListeners == null) _asyncListeners = new ArrayList<>(); _asyncListeners.add(listener); } } public boolean hasListener(AsyncListener listener) { try (AutoLock ignored = lock()) { if (_asyncListeners == null) return false; for (AsyncListener l : _asyncListeners) { if (l == listener) return true; if (l instanceof AsyncContextState.WrappedAsyncListener && ((AsyncContextState.WrappedAsyncListener) l).getListener() == listener) return true; } return false; } } public boolean isSendError() { try (AutoLock l = lock()) { return _sendError; } } public void setTimeout(long ms) { try (AutoLock l = lock()) { _timeoutMs = ms; } } public long getTimeout() { try (AutoLock l = lock()) { return _timeoutMs; } } public AsyncContextEvent getAsyncContextEvent() { try (AutoLock l = lock()) { return _event; } } @Override public String toString() { try (AutoLock l = lock()) { return toStringLocked(); } } private String toStringLocked() { return String.format("%s@%x{%s}", getClass().getSimpleName(), hashCode(), getStatusStringLocked()); } private String getStatusStringLocked() { return String.format("s=%s rs=%s os=%s is=%s awp=%b se=%b i=%b al=%d", _state, _requestState, _outputState, _inputState, _asyncWritePossible, _sendError, _initial, _asyncListeners == null ? 0 : _asyncListeners.size()); } public String getStatusString() { try (AutoLock l = lock()) { return getStatusStringLocked(); } } public boolean commitResponse() { try (AutoLock l = lock()) { switch(_outputState) { case OPEN: _outputState = OutputState.COMMITTED; return true; default: return false; } } } public boolean partialResponse() { try (AutoLock l = lock()) { switch(_outputState) { case COMMITTED: _outputState = OutputState.OPEN; return true; default: return false; } } } public Throwable completeResponse() { try (AutoLock l = lock()) { if (_outputState == OutputState.OPEN || _outputState == OutputState.COMMITTED) _outputState = OutputState.COMPLETED; return _failure; } } public boolean isResponseCommitted() { try (AutoLock l = lock()) { switch(_outputState) { case OPEN: return false; default: return true; } } } public boolean isResponseCompleted() { try (AutoLock l = lock()) { return _outputState == OutputState.COMPLETED; } } /** *

Aborts the {@link HttpChannel}, eventually * resulting in the completion of its state machine.

* * @param failure the cause of the abort * @return {@code null} when no abort happened because it was already aborted; * {@code false} when abort happened, but there is no need to call {@link HttpChannel#handle()}; * {@code true} when abort happened, and {@link HttpChannel#handle()} must be called. */ Boolean abort(Throwable failure) { boolean handle; try (AutoLock ignored = lock()) { boolean aborted = abortResponse(failure); if (LOG.isDebugEnabled()) LOG.debug("abort={} {}", aborted, this, failure); if (aborted) { handle = _state == State.WAITING; if (handle) _state = State.WOKEN; _requestState = RequestState.COMPLETED; return handle; } return null; } } public boolean abortResponse(Throwable failure) { try (AutoLock l = lock()) { switch(_outputState) { case COMPLETED: case ABORTED: return false; default: _outputState = OutputState.ABORTED; _failure = failure; return true; } } } /** * @return Next handling of the request should proceed */ public Action handling() { try (AutoLock l = lock()) { if (LOG.isDebugEnabled()) LOG.debug("handling {}", toStringLocked()); switch(_state) { case IDLE: if (_requestState != RequestState.BLOCKING) throw new IllegalStateException(getStatusStringLocked()); _initial = true; _state = State.HANDLING; return Action.DISPATCH; case WOKEN: if (_event != null && _event.getThrowable() != null && !_sendError) { _state = State.HANDLING; return Action.ASYNC_ERROR; } Action action = nextAction(true); if (LOG.isDebugEnabled()) LOG.debug("nextAction(true) {} {}", action, toStringLocked()); return action; default: throw new IllegalStateException(getStatusStringLocked()); } } } /** * 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() { try (AutoLock l = lock()) { if (LOG.isDebugEnabled()) LOG.debug("unhandle {}", toStringLocked()); if (_state != State.HANDLING) throw new IllegalStateException(this.getStatusStringLocked()); _initial = false; Action action = nextAction(false); if (LOG.isDebugEnabled()) LOG.debug("nextAction(false) {} {}", action, toStringLocked()); return action; } } private Action nextAction(boolean handling) { // Assume we can keep going, but exceptions are below _state = State.HANDLING; if (_sendError) { switch(_requestState) { case BLOCKING: case ASYNC: case COMPLETE: case DISPATCH: case COMPLETING: _requestState = RequestState.BLOCKING; _sendError = false; return Action.SEND_ERROR; default: break; } } switch(_requestState) { case BLOCKING: if (handling) throw new IllegalStateException(getStatusStringLocked()); _requestState = RequestState.COMPLETING; return Action.COMPLETE; case ASYNC: switch(_inputState) { case IDLE: case UNREADY: break; case READY: _inputState = InputState.IDLE; return Action.READ_CALLBACK; default: throw new IllegalStateException(getStatusStringLocked()); } if (_asyncWritePossible) { _asyncWritePossible = false; return Action.WRITE_CALLBACK; } Scheduler scheduler = _channel.getScheduler(); if (scheduler != null && _timeoutMs > 0 && !_event.hasTimeoutTask()) _event.setTimeoutTask(scheduler.schedule(_event, _timeoutMs, TimeUnit.MILLISECONDS)); _state = State.WAITING; return Action.WAIT; case DISPATCH: _requestState = RequestState.BLOCKING; return Action.ASYNC_DISPATCH; case EXPIRE: _requestState = RequestState.EXPIRING; return Action.ASYNC_TIMEOUT; case EXPIRING: if (handling) throw new IllegalStateException(getStatusStringLocked()); sendError(HttpStatus.INTERNAL_SERVER_ERROR_500, "AsyncContext timeout"); // handle sendError immediately _requestState = RequestState.BLOCKING; _sendError = false; return Action.SEND_ERROR; case COMPLETE: _requestState = RequestState.COMPLETING; return Action.COMPLETE; case COMPLETING: _state = State.WAITING; return Action.WAIT; case COMPLETED: _state = State.IDLE; return Action.TERMINATED; default: throw new IllegalStateException(getStatusStringLocked()); } } public void startAsync(AsyncContextEvent event) { final List lastAsyncListeners; try (AutoLock l = lock()) { if (LOG.isDebugEnabled()) LOG.debug("startAsync {}", toStringLocked()); if (_state != State.HANDLING || _requestState != RequestState.BLOCKING) throw new IllegalStateException(this.getStatusStringLocked()); if (!_failureListener) { _failureListener = true; getHttpChannel().getCoreRequest().addFailureListener(this::asyncError); } _requestState = RequestState.ASYNC; _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("Async dispatch error", e); } } } @Override public String toString() { return "startAsync"; } }; runInContext(event, callback); } } public void dispatch(ServletContext context, String path) { boolean dispatch = false; AsyncContextEvent event; try (AutoLock l = lock()) { if (LOG.isDebugEnabled()) LOG.debug("dispatch {} -> {}", toStringLocked(), path); switch(_requestState) { case ASYNC: break; case EXPIRING: if (Thread.currentThread() != _onTimeoutThread) throw new IllegalStateException(this.getStatusStringLocked()); break; default: throw new IllegalStateException(this.getStatusStringLocked()); } if (context != null) _event.setDispatchContext(context); if (path != null) _event.setDispatchPath(path); if (_requestState == RequestState.ASYNC && _state == State.WAITING) { _state = State.WOKEN; dispatch = true; } _requestState = RequestState.DISPATCH; event = _event; } cancelTimeout(event); if (dispatch) scheduleDispatch(); } protected void timeout() { boolean dispatch = false; try (AutoLock l = lock()) { if (LOG.isDebugEnabled()) LOG.debug("Timeout {}", toStringLocked()); if (_requestState != RequestState.ASYNC) return; _requestState = RequestState.EXPIRE; if (_state == State.WAITING) { _state = State.WOKEN; dispatch = true; } } if (dispatch) { if (LOG.isDebugEnabled()) LOG.debug("Dispatch after async timeout {}", this); scheduleDispatch(); } } protected void onTimeout() { final List listeners; AsyncContextEvent event; try (AutoLock l = lock()) { if (LOG.isDebugEnabled()) LOG.debug("onTimeout {}", toStringLocked()); if (_requestState != RequestState.EXPIRING || _state != State.HANDLING) throw new IllegalStateException(toStringLocked()); event = _event; listeners = _asyncListeners; _onTimeoutThread = Thread.currentThread(); } try { if (listeners != null) { Runnable task = new Runnable() { @Override public void run() { for (AsyncListener listener : listeners) { try { listener.onTimeout(event); } catch (Throwable x) { if (LOG.isDebugEnabled()) LOG.debug("{} while invoking onTimeout listener {}", x.toString(), listener, x); else LOG.warn("{} while invoking onTimeout listener {}", x.toString(), listener); } } } @Override public String toString() { return "onTimeout"; } }; runInContext(event, task); } } finally { try (AutoLock l = lock()) { _onTimeoutThread = null; } } } public void complete() { boolean handle = false; AsyncContextEvent event; try (AutoLock l = lock()) { if (LOG.isDebugEnabled()) LOG.debug("complete {}", toStringLocked()); event = _event; switch(_requestState) { case EXPIRING: if (Thread.currentThread() != _onTimeoutThread) throw new IllegalStateException(getStatusStringLocked()); _requestState = _sendError ? RequestState.BLOCKING : RequestState.COMPLETE; break; case ASYNC: _requestState = _sendError ? RequestState.BLOCKING : RequestState.COMPLETE; break; case COMPLETE: return; default: throw new IllegalStateException(getStatusStringLocked()); } if (_state == State.WAITING) { handle = true; _state = State.WOKEN; } } cancelTimeout(event); if (handle) runInContext(event, _channel); } public void asyncError(Throwable failure) { // This method is called when an failure occurs asynchronously to // normal handling. If the request is async, we arrange for the // exception to be thrown from the normal handling loop and then // actually handled by #thrownException AsyncContextEvent event = null; try (AutoLock l = lock()) { if (LOG.isDebugEnabled()) LOG.debug("asyncError {}", toStringLocked(), failure); if (_state == State.WAITING && _requestState == RequestState.ASYNC) { _state = State.WOKEN; _event.addThrowable(failure); event = _event; } else { if (!(failure instanceof QuietException)) LOG.warn(failure.toString()); if (LOG.isDebugEnabled()) LOG.debug("Async error", failure); } } if (event != null) { cancelTimeout(event); runInContext(event, _channel); } } protected boolean onError(Throwable th) { boolean committed = _channel.isCommitted(); final AsyncContextEvent asyncEvent; final List asyncListeners; try (AutoLock l = lock()) { if (LOG.isDebugEnabled()) LOG.debug("onError {}", getStatusStringLocked(), th); // This can only be called from within the handle loop if (_state != State.HANDLING) throw new IllegalStateException(getStatusStringLocked()); // If sendError has already been called, we can only handle one failure at a time! if (_sendError) { LOG.warn("onError not handled due to prior sendError() {}", getStatusStringLocked(), th); return true; } // Check async state to determine type of handling switch(_requestState) { case BLOCKING: { // Handle the exception with a sendError. if (committed) return true; sendError(th); return false; } // Dispatch has already been called but we ignore and handle exception below case DISPATCH: // Complete has already been called but we ignore and handle exception below case COMPLETE: case ASYNC: { if (_asyncListeners == null || _asyncListeners.isEmpty()) { if (committed) return true; sendError(th); return false; } asyncEvent = _event; asyncEvent.addThrowable(th); asyncListeners = _asyncListeners; break; } default: { LOG.warn("onError not handled due to invalid requestState {}", getStatusStringLocked(), th); return true; } } } // If we are async and have async listeners // call onError runInContext(asyncEvent, () -> { for (AsyncListener listener : asyncListeners) { try { listener.onError(asyncEvent); } catch (Throwable x) { if (LOG.isDebugEnabled()) LOG.debug("{} while invoking onError listener {}", x, listener, x); else LOG.warn("{} while invoking onError listener {}", x, listener); } } }); // check the actions of the listeners try (AutoLock l = lock()) { if (_requestState == RequestState.ASYNC && !_sendError) { // The listeners did not invoke API methods and the // container must provide a default error dispatch. if (committed) return true; sendError(th); return false; } else if (_requestState != RequestState.COMPLETE) { if (QuietException.isQuiet(th)) LOG.debug("unhandled in state {}", _requestState, th); else LOG.warn("unhandled in state {}", _requestState, th); } return committed; } } private void sendError(Throwable th) { // No sync as this is always called with lock held assert _lock.isHeldByCurrentThread(); // Determine the actual details of the exception final Request request = _channel.getRequest(); final int code; final String message; Throwable cause = _channel.unwrap(th, HttpException.class, UnavailableException.class); if (cause == null) { code = HttpStatus.INTERNAL_SERVER_ERROR_500; message = th.toString(); } else if (cause instanceof HttpException httpException) { code = httpException.getCode(); message = httpException.getReason(); } else if (cause instanceof UnavailableException) { message = cause.toString(); if (((UnavailableException) cause).isPermanent()) code = HttpStatus.NOT_FOUND_404; else code = HttpStatus.SERVICE_UNAVAILABLE_503; } else { code = HttpStatus.INTERNAL_SERVER_ERROR_500; message = null; } sendError(code, message); // No ISE, so good to modify request/state request.setAttribute(ERROR_EXCEPTION, th); request.setAttribute(ERROR_EXCEPTION_TYPE, th.getClass()); // Ensure any async lifecycle is ended! _requestState = RequestState.BLOCKING; } public void sendError(int code, String message) { // This method is called by Response.sendError to organise for an error page to be generated when it is possible: // + The response is reset and temporarily closed. // + The details of the error are saved as request attributes // + The _sendError boolean is set to true so that an ERROR_DISPATCH action will be generated: // - after unhandle for sync // - after both unhandle and complete for async final Request request = _channel.getRequest(); final Response response = _channel.getResponse(); if (message == null) message = HttpStatus.getMessage(code); try (AutoLock l = lock()) { if (LOG.isDebugEnabled()) LOG.debug("sendError {}", toStringLocked()); if (_outputState != OutputState.OPEN) throw new IllegalStateException(_outputState.toString()); switch(_state) { case HANDLING: case WOKEN: case WAITING: break; default: throw new IllegalStateException(getStatusStringLocked()); } response.setStatus(code); response.errorClose(); ServletContext errorContext = request.getLastContext(); request.setAttribute(ErrorHandler.ERROR_CONTEXT, errorContext); request.setAttribute(ERROR_REQUEST_URI, request.getRequestURI()); request.setAttribute(ERROR_SERVLET_NAME, request.getServletName()); request.setAttribute(ERROR_STATUS_CODE, code); request.setAttribute(ERROR_MESSAGE, message); _sendError = true; if (_event != null) { Throwable cause = (Throwable) request.getAttribute(ERROR_EXCEPTION); if (cause != null) _event.addThrowable(cause); } } } protected void completing() { try (AutoLock l = lock()) { if (LOG.isDebugEnabled()) LOG.debug("completing {}", toStringLocked()); switch(_requestState) { case COMPLETED: throw new IllegalStateException(getStatusStringLocked()); default: _requestState = RequestState.COMPLETING; } } } protected void completed(Throwable failure) { final List aListeners; final AsyncContextEvent event; boolean handle = false; try (AutoLock l = lock()) { if (LOG.isDebugEnabled()) LOG.debug("completed {}", toStringLocked()); if (_requestState != RequestState.COMPLETING) throw new IllegalStateException(this.getStatusStringLocked()); if (_event == null) { _requestState = RequestState.COMPLETED; aListeners = null; event = null; if (_state == State.WAITING) { _state = State.WOKEN; handle = true; } } else { aListeners = _asyncListeners; event = _event; } } // release any aggregate buffer from a closing flush _channel.getResponse().getHttpOutput().completed(failure); if (event != null) { cancelTimeout(event); if (aListeners != null) { runInContext(event, () -> { for (AsyncListener listener : aListeners) { try { listener.onComplete(event); } catch (Throwable x) { if (LOG.isDebugEnabled()) LOG.warn("{} while invoking onComplete listener {}", x.toString(), listener, x); else LOG.warn("{} while invoking onComplete listener {}", x.toString(), listener); } } }); } event.completed(); try (AutoLock l = lock()) { _requestState = RequestState.COMPLETED; if (_state == State.WAITING) { _state = State.WOKEN; handle = true; } } } if (handle) _channel.handle(); } protected void recycle() { cancelTimeout(); try (AutoLock l = lock()) { if (LOG.isDebugEnabled()) LOG.debug("recycle {}", toStringLocked()); switch(_state) { case HANDLING: throw new IllegalStateException(getStatusStringLocked()); case UPGRADED: return; default: break; } _asyncListeners = null; _state = State.IDLE; _requestState = RequestState.BLOCKING; _outputState = OutputState.OPEN; _initial = true; _inputState = InputState.IDLE; _asyncWritePossible = false; _timeoutMs = DEFAULT_TIMEOUT; _event = null; _failureListener = false; } } public void upgrade() { cancelTimeout(); try (AutoLock l = lock()) { if (LOG.isDebugEnabled()) LOG.debug("upgrade {}", toStringLocked()); switch(_state) { case IDLE: break; default: throw new IllegalStateException(getStatusStringLocked()); } _asyncListeners = null; _state = State.UPGRADED; _requestState = RequestState.BLOCKING; _initial = true; _inputState = InputState.IDLE; _asyncWritePossible = false; _timeoutMs = DEFAULT_TIMEOUT; _event = null; } } protected void scheduleDispatch() { _channel.execute(_channel); } protected void cancelTimeout() { cancelTimeout(getAsyncContextEvent()); } protected void cancelTimeout(AsyncContextEvent event) { if (event != null) event.cancelTimeoutTask(); } public boolean isIdle() { try (AutoLock l = lock()) { return _state == State.IDLE; } } public boolean isExpired() { try (AutoLock l = lock()) { // TODO review return _requestState == RequestState.EXPIRE || _requestState == RequestState.EXPIRING; } } public boolean isInitial() { try (AutoLock l = lock()) { return _initial; } } public boolean isSuspended() { try (AutoLock l = lock()) { return _state == State.WAITING || _state == State.HANDLING && _requestState == RequestState.ASYNC; } } boolean isCompleted() { try (AutoLock l = lock()) { return _requestState == RequestState.COMPLETED; } } public boolean isAsyncStarted() { try (AutoLock l = lock()) { if (_state == State.HANDLING) return _requestState != RequestState.BLOCKING; return _requestState == RequestState.ASYNC || _requestState == RequestState.EXPIRING; } } public boolean isAsync() { try (AutoLock l = lock()) { return !_initial || _requestState != RequestState.BLOCKING; } } public Request getBaseRequest() { return _channel.getRequest(); } public HttpChannel getHttpChannel() { return _channel; } public ContextHandler getContextHandler() { return getContextHandler(getAsyncContextEvent()); } ContextHandler getContextHandler(AsyncContextEvent event) { if (event != null) { ContextHandler.APIContext context = ((ContextHandler.APIContext) event.getServletContext()); if (context != null) return context.getContextHandler(); } return null; } public ServletResponse getServletResponse() { return getServletResponse(getAsyncContextEvent()); } 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 that the channel is ready for a callback. * * @return true if woken */ public boolean onReadReady() { boolean woken = false; try (AutoLock l = lock()) { if (LOG.isDebugEnabled()) LOG.debug("onReadReady {}", toStringLocked()); switch(_inputState) { case READY: _inputState = InputState.READY; break; case IDLE: case UNREADY: _inputState = InputState.READY; if (_state == State.WAITING) { woken = true; _state = State.WOKEN; } break; default: throw new IllegalStateException(toStringLocked()); } } return woken; } public boolean onReadEof() { boolean woken = false; try (AutoLock l = lock()) { if (LOG.isDebugEnabled()) LOG.debug("onReadEof {}", toStringLocked()); switch(_inputState) { case IDLE: case READY: case UNREADY: _inputState = InputState.READY; if (_state == State.WAITING) { woken = true; _state = State.WOKEN; } break; default: throw new IllegalStateException(toStringLocked()); } } return woken; } /** * Called to indicate that some content was produced and is * ready for consumption. */ public void onContentAdded() { try (AutoLock l = lock()) { if (LOG.isDebugEnabled()) LOG.debug("onContentAdded {}", toStringLocked()); switch(_inputState) { case IDLE: case UNREADY: case READY: _inputState = InputState.READY; break; default: throw new IllegalStateException(toStringLocked()); } } } /** * Called to indicate that the content is being consumed. */ public void onReadIdle() { try (AutoLock l = lock()) { if (LOG.isDebugEnabled()) LOG.debug("onReadIdle {}", toStringLocked()); switch(_inputState) { case UNREADY: case READY: case IDLE: _inputState = InputState.IDLE; break; default: throw new IllegalStateException(toStringLocked()); } } } /** * Called to indicate that no content is currently available, * more content has been demanded and may be available, but * that a handling thread may need to produce (fill/parse) it. */ public void onReadUnready() { try (AutoLock l = lock()) { if (LOG.isDebugEnabled()) LOG.debug("onReadUnready {}", toStringLocked()); switch(_inputState) { case IDLE: case UNREADY: case // READY->UNREADY is needed by AsyncServletIOTest.testStolenAsyncRead READY: _inputState = InputState.UNREADY; break; default: throw new IllegalStateException(toStringLocked()); } } } public boolean isInputUnready() { try (AutoLock l = lock()) { return _inputState == InputState.UNREADY; } } public boolean onWritePossible() { boolean wake = false; try (AutoLock l = lock()) { if (LOG.isDebugEnabled()) LOG.debug("onWritePossible {}", toStringLocked()); _asyncWritePossible = true; if (_state == State.WAITING) { _state = State.WOKEN; wake = true; } } return wake; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy