ContextHandler是ScopedHandler的直接子类,继承关系比较简单就不贴类图了。
ContextHandler从名字的直观含义上来看是上下文Handler,在servlet规范中每个web应用都有一个上下文(context)的含义,其实就是对应到这里的ContextHandler了。当web容器收到用户请求之后,会根据请求里面的url先判断属于请求时发给哪个web应用的,但并不会将请求直接交给Servlet进行处理而是交给这个应用的ContextHandler。这是因为可能应用中配置的Servlet并不只有一个,还需要在一个地方根据请求的url再次转发给合适的Servlet;另外在将请求交给Servlet之前还需要进行一些判断和处理,这些工作都是在ContextHandler中完成的。另外ContextHandler还以内部类的形式实现了ServletContext。
下面先看下ContextHandler中的一些属性:
//ServletContext,Servlet规范定义的接口,这里的内部类Context是其具体实现
protected Context _scontext;
private final AttributesMap _attributes;
//对应web.xml中配置的初始化参数
private final Map<String, String> _initParams;
private ClassLoader _classLoader;
//应用程序路径,可以在jetty.xml配置文件中配置,如果同时在jetty下部署了多个应用,则会默认以应用名称作为前缀
private String _contextPath = "/";
private String _contextPathEncoded = "/";
//Context对应的名称
private String _displayName;
private Resource _baseResource;
//文件类型
private MimeTypes _mimeTypes;
private Map<String, String> _localeEncodingMap;
private String[] _welcomeFiles;
//负责错误处理返回的Hanler,会返回一个默认的错误页面
private ErrorHandler _errorHandler;
//允许目标是这些主机的请求通过,如果不配置,则表示不对请求中的目标主机做限制
private String[] _vhosts;
private Logger _logger;
//是否允许请求的url就是_contextPath的值,比如"/manager"(针对部署了多个应用的情况,contextPath需要根据加上每个应用名称做前缀)
//这种情况下要想请求应用程序根路径应该是"/manager/"而不是"/manager".如果只部署了一个应用,contextPath可以设为"/",这种也就无所谓了.
private boolean _allowNullPathInfo;
private int _maxFormKeys = Integer.getInteger("org.eclipse.jetty.server.Request.maxFormKeys",-1).intValue();
private int _maxFormContentSize = Integer.getInteger("org.eclipse.jetty.server.Request.maxFormContentSize",-1).intValue();
//是否对传入的url进行路径压缩,路径压缩指的是将url中的多个“/"替换成一个"/"
private boolean _compactPath = false;
private boolean _usingSecurityManager = System.getSecurityManager()!=null;
//一些监听器的配置,对应到web.xml中配置的各种listener
private final List<EventListener> _eventListeners=new CopyOnWriteArrayList<>();
private final List<EventListener> _programmaticListeners=new CopyOnWriteArrayList<>();
private final List<ServletContextListener> _servletContextListeners=new CopyOnWriteArrayList<>();
private final List<ServletContextListener> _destroySerletContextListeners=new ArrayList<>();
private final List<ServletContextAttributeListener> _servletContextAttributeListeners=new CopyOnWriteArrayList<>();
private final List<ServletRequestListener> _servletRequestListeners=new CopyOnWriteArrayList<>();
private final List<ServletRequestAttributeListener> _servletRequestAttributeListeners=new CopyOnWriteArrayList<>();
private final List<ContextScopeListener> _contextListeners = new CopyOnWriteArrayList<>();
private final List<EventListener> _durableListeners = new CopyOnWriteArrayList<>();
private Map<String, Object> _managedAttributes;
//受保护的路径,比如WEB-INF/就是受保护的路径,应用程序是不能够访问的
private String[] _protectedTargets;
private final CopyOnWriteArrayList<AliasCheck> _aliasChecks = new CopyOnWriteArrayList<ContextHandler.AliasCheck>();
//服务器状态,注意SHUTDOWN状态表示的是服务器将要关闭,不再接受新的请求但是已接收的请求会处理完成之后再关闭服务器,
//增加这个状态可以实现服务器的平滑关闭
public enum Availability { UNAVAILABLE,STARTING,AVAILABLE,SHUTDOWN,};
private volatile Availability _availability = Availability.UNAVAILABLE;
注释中说明了大部分属性的含义。protected Context _scontext; 是一个ServletContext,这里的Context是ContextHandler中实现了ServletContext接口的一个内部类。并且通过一个ThreadLocal静态变量将_scontext声明成了可全局共享的。
//ThreadLocal变量,用来保证在当前线程中可以随时取到ServletContext对象
private static final ThreadLocal<Context> __context = new ThreadLocal<Context>();
ContextHandler实现了CycleLife和Handler接口,所以下面再看下启动过程和请求处理过程。
先是启动过程,ContextHandler重写了doStart()方法。
protected void doStart() throws Exception
{
//设置启动状态
_availability = Availability.STARTING;
if (_contextPath == null)
throw new IllegalStateException("Null contextPath");
if (_logger == null)
{
_logger = Log.getLogger(ContextHandler.class.getName() + getLogNameSuffix());
}
ClassLoader old_classloader = null;
Thread current_thread = null;
Context old_context = null;
//设置全局属性
_attributes.setAttribute("org.eclipse.jetty.server.Executor",getServer().getThreadPool());
if (_mimeTypes == null)
_mimeTypes = new MimeTypes();
try
{
// Set the classloader, context and enter scope
//这里的ClassLoader一般是子类定义的
if (_classLoader != null)
{
current_thread = Thread.currentThread();
old_classloader = current_thread.getContextClassLoader();
current_thread.setContextClassLoader(_classLoader);
}
old_context = __context.get();
__context.set(_scontext);
enterScope(null, getState());
// defers the calling of super.doStart()
startContext();
_availability = Availability.AVAILABLE;
LOG.info("Started {}", this);
}
finally
{
if (_availability==Availability.STARTING)
_availability=Availability.UNAVAILABLE;
exitScope(null);
__context.set(old_context);
// reset the classloader
if (_classLoader != null && current_thread!=null)
current_thread.setContextClassLoader(old_classloader);
}
}
这里面先是给当前线程重新设置了ClassLoader和ServletContext,并且将旧的值放置到了局部变量中存储。另外在启动中加了一步startContext(),下面是其源码:
protected void startContext() throws Exception
{
//增加一个Attributes事件监听器
String managedAttributes = _initParams.get(MANAGED_ATTRIBUTES);
if (managedAttributes != null)
addEventListener(new ManagedAttributeListener(this,StringUtil.csvSplit(managedAttributes)));
super.doStart();
// Call context listeners
//将容器初始化成功事件通知给监听器
_destroySerletContextListeners.clear();
if (!_servletContextListeners.isEmpty())
{
ServletContextEvent event = new ServletContextEvent(_scontext);
for (ServletContextListener listener:_servletContextListeners)
{
callContextInitialized(listener, event);
_destroySerletContextListeners.add(listener);
}
}
}
可以看到逻辑并不复杂,只是增加了一些监听器的处理流程。
再来看下请求处理流程。ContextHandler继承自ScopedHandler,请求处理主要是在doScope()和doHandle()两个方法中进行的,ContextHandler对这两个方法都进行了重写。先看下doScope()方法:
public void doScope(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
if (LOG.isDebugEnabled())
LOG.debug("scope {}|{}|{} @ {}",baseRequest.getContextPath(),baseRequest.getServletPath(),baseRequest.getPathInfo(),this);
Context old_context = null;
String old_context_path = null;
String old_servlet_path = null;
String old_path_info = null;
ClassLoader old_classloader = null;
Thread current_thread = null;
String pathInfo = target;
//请求类型
DispatcherType dispatch = baseRequest.getDispatcherType();
//请求对应的ServeltContext,对于新连接建立请求来说一般都为null
old_context = baseRequest.getContext();
// Are we already in this context?
//里面主要进行请求合法性检查,包括请求路径和请求中的主机名的检查,并重新设置当前线程的ClassLoader
if (old_context != _scontext)
{
// check the target.
if (DispatcherType.REQUEST.equals(dispatch) ||
DispatcherType.ASYNC.equals(dispatch) ||
DispatcherType.ERROR.equals(dispatch) && baseRequest.getHttpChannelState().isAsync())
{
if (_compactPath)//对请求进行压缩处理,比如"///"压缩成“/”
target = URIUtil.compactPath(target);
if (!checkContext(target,baseRequest,response))
return;
//先是对请求url的处理
if (target.length() > _contextPath.length())
{
//如果contextPath长度大于1,则意味着前缀一般是应用的名称,在请求的url中要把这部分去掉
if (_contextPath.length() > 1)
target = target.substring(_contextPath.length());
pathInfo = target;
}
else if (_contextPath.length() == 1)
{
target = URIUtil.SLASH;
pathInfo = URIUtil.SLASH;
}
else
{
target = URIUtil.SLASH;
pathInfo = null;
}
}
// Set the classloader
if (_classLoader != null)
{
current_thread = Thread.currentThread();
old_classloader = current_thread.getContextClassLoader();
current_thread.setContextClassLoader(_classLoader);
}
}
try
{
old_context_path = baseRequest.getContextPath();
old_servlet_path = baseRequest.getServletPath();
old_path_info = baseRequest.getPathInfo();
// Update the paths
//更新请求的ServletContext
baseRequest.setContext(_scontext);
__context.set(_scontext);
if (!DispatcherType.INCLUDE.equals(dispatch) && target.startsWith("/"))
{
if (_contextPath.length() == 1)
baseRequest.setContextPath("");
else
baseRequest.setContextPath(_contextPathEncoded);
baseRequest.setServletPath(null);
baseRequest.setPathInfo(pathInfo);
}
if (old_context != _scontext)
enterScope(baseRequest,dispatch);
if (LOG.isDebugEnabled())
LOG.debug("context={}|{}|{} @ {}",baseRequest.getContextPath(),baseRequest.getServletPath(), baseRequest.getPathInfo(),this);
//继续scope链的调用
nextScope(target,baseRequest,request,response);
}
finally
{
if (old_context != _scontext)
{
exitScope(baseRequest);
// reset the classloader
if (_classLoader != null && current_thread!=null)
{
current_thread.setContextClassLoader(old_classloader);
}
// reset the context and servlet path.
//重设ServletContext和servletPath(处理从其它servlet转发过来的请求???)
baseRequest.setContext(old_context);
__context.set(old_context);
baseRequest.setContextPath(old_context_path);
baseRequest.setServletPath(old_servlet_path);
baseRequest.setPathInfo(old_path_info);
}
}
}
可以看到主要是对请求中的url进行一些处理,包括路径压缩和前缀的处理,并且通过请求的url判断(包括URL与ContextPath路径比较,请求中目标主机与该ContextHandler中的虚拟主机列表匹配)该请求是否应该放到当前的ContextHandler进行处理。另外就是重设了请求中ServletContext、servletPath等信息,当请求处理完成返回的时候又会将旧的值填充进去。
然后再看下doHandle()中的流程:
public void doHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
final DispatcherType dispatch = baseRequest.getDispatcherType();
final boolean new_context = baseRequest.takeNewContext(); //第一次获取请求中的ServletContext 则为true
try
{
if (new_context) //请求初始化工作,主要是为请求添加ServletRequestAttributeListener监听器,并将请求开始处理这个事件通知给监听器
requestInitialized(baseRequest,request);
switch(dispatch)
{
case REQUEST: //重复请求
if (isProtectedTarget(target))//如果请求的是受保护的资源,则直接返回错误
{
response.sendError(HttpServletResponse.SC_NOT_FOUND);
baseRequest.setHandled(true);
return;
}
break;
case ERROR://错误请求,直接返回错误页面
// If this is already a dispatch to an error page, proceed normally
if (Boolean.TRUE.equals(baseRequest.getAttribute(Dispatcher.__ERROR_DISPATCH)))
break;
// We can just call doError here. If there is no error page, then one will
// be generated. If there is an error page, then a RequestDispatcher will be
// used to route the request through appropriate filters etc.
doError(target,baseRequest,request,response);
return;
default:
break;
}
//继续doHandle()或doScope()处理
nextHandle(target,baseRequest,request,response);
}
finally
{
if (new_context)
requestDestroyed(baseRequest,request);
}
}
可以看到逻辑还是比较简单的,主要注意这里对请求的路径有个限制,如果请求的是服务器上受保护的路径(默认是WEB-INF和META-INF),那么会直接返回404错误。
启动和请求处理流程分析完成之后,ContextHandler就大致分析完了。总的来说,主要分为两大部分:第一部分就是启动时候的初始化,ContextHandler初始化主要是重设了下servletContxt和当前线程的ClassLoader和对监听器的处理包括将容器初始化成功的事件通知给注册的ServletContextListener监听器。第二部分是请求处理,先是通过请求的url和请求中的目标主机名称判断该请求是否需要当前的ContextHandler进行处理,如果需要的话会将请求转发给ServletHandler等具体处理请求的Handler进行处理,另外还会给请求增加一些Listener。
ContextHandler对于请求的处理可以说是基本完备的,但是对于启动过程的处理过于简单了,因为在WebApplication启动的时候,面对的是一个war包(或者是war包解压之后的一堆文件),ContextHandler的启动的时候是需要对这个war包进行处理的,包括解压war包、处理里面的包含的依赖jar包文件、处理里面应用自定义的web.xml文件等。其实这些处理都是在ContextHandler的子类WebAppContext中进行的,下一篇文章就可以看到这些重要的初始化流程。
本文详细分析了Jetty的ContextHandler,它是ScopedHandler的子类,实现ServletContext并处理web应用上下文。在启动过程中,ContextHandler设置线程的ClassLoader和ServletContext,并处理监听器。在请求处理中,它负责URL路径处理,决定请求是否应由当前ContextHandler处理,同时防止对服务器受保护路径的访问。ContextHandler的初始化和请求处理是其两大核心功能,而更复杂的web应用初始化则在WebAppContext中完成。
3364

被折叠的 条评论
为什么被折叠?



