org.eclipse.jetty.server.HttpChannelState Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jetty-server Show documentation
Show all versions of jetty-server Show documentation
The core jetty server artifact.
//
// ========================================================================
// Copyright (c) 1995-2013 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 javax.servlet.AsyncEvent;
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.Scheduler;
/**
* Implementation of AsyncContext interface that holds the state of request-response cycle.
*
*
* STATE ACTION
* handling() startAsync() unhandle() dispatch() complete() completed()
* IDLE: DISPATCHED COMPLETECALLED??
* DISPATCHED: ASYNCSTARTED COMPLETING
* ASYNCSTARTED: ASYNCWAIT REDISPATCHING COMPLETECALLED
* REDISPATCHING: REDISPATCHED
* ASYNCWAIT: REDISPATCH COMPLETECALLED
* REDISPATCH: REDISPATCHED
* REDISPATCHED: ASYNCSTARTED COMPLETING
* COMPLETECALLED: COMPLETING COMPLETING
* COMPLETING: COMPLETING COMPLETED
* COMPLETED:
*
*/
public class HttpChannelState
{
private static final Logger LOG = Log.getLogger(HttpChannelState.class);
private final static long DEFAULT_TIMEOUT=30000L;
public enum State
{
IDLE, // Idle request
DISPATCHED, // Request dispatched to filter/servlet
ASYNCSTARTED, // Suspend called, but not yet returned to container
REDISPATCHING, // resumed while dispatched
ASYNCWAIT, // Suspended and parked
REDISPATCH, // Has been scheduled
REDISPATCHED, // Request redispatched to filter/servlet
COMPLETECALLED,// complete called
COMPLETING, // Request is completable
COMPLETED // Request is complete
}
public enum Next
{
CONTINUE, // Continue handling the channel
WAIT, // Wait for further events
COMPLETE // Complete the channel
}
private final HttpChannel> _channel;
private List _lastAsyncListeners;
private List _asyncListeners;
private State _state;
private boolean _initial;
private boolean _dispatched;
private boolean _expired;
private volatile boolean _responseWrapped;
private long _timeoutMs=DEFAULT_TIMEOUT;
private AsyncContextEvent _event;
protected HttpChannelState(HttpChannel> channel)
{
_channel=channel;
_state=State.IDLE;
_initial=true;
}
public State getState()
{
synchronized(this)
{
return _state;
}
}
public void addListener(AsyncListener listener)
{
synchronized(this)
{
if (_asyncListeners==null)
_asyncListeners=new ArrayList<>();
_asyncListeners.add(listener);
}
}
public void setTimeout(long ms)
{
synchronized(this)
{
_timeoutMs=ms;
}
}
public long getTimeout()
{
synchronized(this)
{
return _timeoutMs;
}
}
public AsyncContextEvent getAsyncContextEvent()
{
synchronized(this)
{
return _event;
}
}
@Override
public String toString()
{
synchronized (this)
{
return super.toString()+"@"+getStatusString();
}
}
public String getStatusString()
{
synchronized (this)
{
return _state+
(_initial?",initial":"")+
(_dispatched?",resumed":"")+
(_expired?",expired":"");
}
}
/**
* @return Next handling of the request should proceed
*/
protected Next handling()
{
synchronized (this)
{
switch(_state)
{
case IDLE:
_initial=true;
_state=State.DISPATCHED;
if (_lastAsyncListeners!=null)
_lastAsyncListeners.clear();
if (_asyncListeners!=null)
_asyncListeners.clear();
else
{
_asyncListeners=_lastAsyncListeners;
_lastAsyncListeners=null;
}
break;
case COMPLETECALLED:
_state=State.COMPLETING;
return Next.COMPLETE;
case COMPLETING:
return Next.COMPLETE;
case ASYNCWAIT:
return Next.WAIT;
case COMPLETED:
return Next.WAIT;
case REDISPATCH:
_state=State.REDISPATCHED;
break;
default:
throw new IllegalStateException(this.getStatusString());
}
_responseWrapped=false;
return Next.CONTINUE;
}
}
public void startAsync(AsyncContextEvent event)
{
synchronized (this)
{
switch(_state)
{
case DISPATCHED:
case REDISPATCHED:
_dispatched=false;
_expired=false;
_responseWrapped=event.getSuppliedResponse()!=_channel.getResponse();
_responseWrapped=false;
_event=event;
_state=State.ASYNCSTARTED;
List listeners=_lastAsyncListeners;
_lastAsyncListeners=_asyncListeners;
if (listeners!=null)
listeners.clear();
_asyncListeners=listeners;
break;
default:
throw new IllegalStateException(this.getStatusString());
}
}
if (_lastAsyncListeners!=null)
{
for (AsyncListener listener : _lastAsyncListeners)
{
try
{
listener.onStartAsync(_event);
}
catch(Exception e)
{
LOG.warn(e);
}
}
}
}
protected void error(Throwable th)
{
synchronized (this)
{
if (_event!=null)
_event.setThrowable(th);
}
}
/**
* 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 Next unhandle()
{
synchronized (this)
{
switch(_state)
{
case REDISPATCHED:
case DISPATCHED:
_state=State.COMPLETING;
return Next.COMPLETE;
case IDLE:
throw new IllegalStateException(this.getStatusString());
case ASYNCSTARTED:
_initial=false;
_state=State.ASYNCWAIT;
scheduleTimeout();
return Next.WAIT;
case REDISPATCHING:
_initial=false;
_state=State.REDISPATCHED;
return Next.CONTINUE;
case COMPLETECALLED:
_initial=false;
_state=State.COMPLETING;
return Next.COMPLETE;
default:
throw new IllegalStateException(this.getStatusString());
}
}
}
public void dispatch(ServletContext context, String path)
{
boolean dispatch;
synchronized (this)
{
switch(_state)
{
case ASYNCSTARTED:
_state=State.REDISPATCHING;
_event.setDispatchTarget(context,path);
_dispatched=true;
return;
case ASYNCWAIT:
dispatch=!_expired;
_state=State.REDISPATCH;
_event.setDispatchTarget(context,path);
_dispatched=true;
break;
default:
throw new IllegalStateException(this.getStatusString());
}
}
if (dispatch)
{
cancelTimeout();
scheduleDispatch();
}
}
public boolean isDispatched()
{
synchronized (this)
{
return _dispatched;
}
}
protected void expired()
{
final List aListeners;
AsyncContextEvent event;
synchronized (this)
{
switch(_state)
{
case ASYNCSTARTED:
case ASYNCWAIT:
_expired=true;
event=_event;
aListeners=_asyncListeners;
break;
default:
return;
}
}
if (aListeners!=null)
{
for (AsyncListener listener : aListeners)
{
try
{
listener.onTimeout(event);
}
catch(Exception e)
{
LOG.debug(e);
event.setThrowable(e);
_channel.getRequest().setAttribute(RequestDispatcher.ERROR_EXCEPTION,e);
break;
}
}
}
synchronized (this)
{
switch(_state)
{
case ASYNCSTARTED:
case ASYNCWAIT:
_state=State.REDISPATCH;
break;
default:
_expired=false;
break;
}
}
scheduleDispatch();
}
public void complete()
{
// just like resume, except don't set _dispatched=true;
boolean handle;
synchronized (this)
{
switch(_state)
{
case DISPATCHED:
case REDISPATCHED:
throw new IllegalStateException(this.getStatusString());
case IDLE:
case ASYNCSTARTED:
_state=State.COMPLETECALLED;
return;
case ASYNCWAIT:
_state=State.COMPLETECALLED;
handle=!_expired;
break;
default:
throw new IllegalStateException(this.getStatusString());
}
}
if (handle)
{
cancelTimeout();
ContextHandler handler=getContextHandler();
if (handler!=null)
handler.handle(_channel);
else
_channel.handle();
}
}
protected void completed()
{
final List aListeners;
final AsyncContextEvent event;
synchronized (this)
{
switch(_state)
{
case COMPLETING:
_state=State.COMPLETED;
aListeners=_asyncListeners;
event=_event;
break;
default:
throw new IllegalStateException(this.getStatusString());
}
}
if (aListeners!=null)
{
for (AsyncListener listener : aListeners)
{
try
{
if (event!=null && event.getThrowable()!=null)
{
event.getSuppliedRequest().setAttribute(RequestDispatcher.ERROR_EXCEPTION,event.getThrowable());
event.getSuppliedRequest().setAttribute(RequestDispatcher.ERROR_MESSAGE,event.getThrowable().getMessage());
listener.onError(event);
}
else
listener.onComplete(event);
}
catch(Exception e)
{
LOG.warn(e);
}
}
}
if (event!=null)
event.completed();
}
protected void recycle()
{
synchronized (this)
{
switch(_state)
{
case DISPATCHED:
case REDISPATCHED:
throw new IllegalStateException(getStatusString());
default:
_state=State.IDLE;
}
_initial = true;
_dispatched=false;
_expired=false;
_responseWrapped=false;
cancelTimeout();
_timeoutMs=DEFAULT_TIMEOUT;
_event=null;
}
}
protected void scheduleDispatch()
{
_channel.execute(_channel);
}
protected void scheduleTimeout()
{
Scheduler scheduler = _channel.getScheduler();
if (scheduler!=null && _timeoutMs>0)
_event.setTimeoutTask(scheduler.schedule(new AsyncTimeout(),_timeoutMs,TimeUnit.MILLISECONDS));
}
protected void cancelTimeout()
{
AsyncContextEvent event=_event;
if (event!=null)
event.cancelTimeoutTask();
}
public boolean isExpired()
{
synchronized (this)
{
return _expired;
}
}
public boolean isInitial()
{
synchronized(this)
{
return _initial;
}
}
public boolean isSuspended()
{
synchronized(this)
{
switch(_state)
{
case ASYNCSTARTED:
case REDISPATCHING:
case COMPLETECALLED:
case ASYNCWAIT:
return true;
default:
return false;
}
}
}
boolean isCompleting()
{
synchronized (this)
{
return _state==State.COMPLETING;
}
}
boolean isCompleted()
{
synchronized (this)
{
return _state == State.COMPLETED;
}
}
public boolean isAsyncStarted()
{
synchronized (this)
{
switch(_state)
{
case ASYNCSTARTED: // Suspend called, but not yet returned to container
case REDISPATCHING: // resumed while dispatched
case COMPLETECALLED: // complete called
case ASYNCWAIT:
return true;
default:
return false;
}
}
}
public boolean isAsync()
{
synchronized (this)
{
switch(_state)
{
case ASYNCSTARTED:
case REDISPATCHING:
case ASYNCWAIT:
case REDISPATCHED:
case REDISPATCH:
case COMPLETECALLED:
return true;
default:
return false;
}
}
}
public Request getBaseRequest()
{
return _channel.getRequest();
}
public HttpChannel> getHttpChannel()
{
return _channel;
}
public ContextHandler getContextHandler()
{
final AsyncContextEvent event=_event;
if (event!=null)
{
Context context=((Context)event.getServletContext());
if (context!=null)
return context.getContextHandler();
}
return null;
}
public ServletResponse getServletResponse()
{
if (_responseWrapped && _event!=null && _event.getSuppliedResponse()!=null)
return _event.getSuppliedResponse();
return _channel.getResponse();
}
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);
}
public class AsyncTimeout implements Runnable
{
@Override
public void run()
{
HttpChannelState.this.expired();
}
}
}