1、什么是Handler
先来看看javadoc中对Handler的描述
/* ------------------------------------------------------------ */
/** A Jetty Server Handler.
*
* A Handler instance is required by a {@link Server} to handle incoming
* HTTP requests. A Handler may: <ul>
* <li>Completely generate the HTTP Response</li>
* <li>Examine/modify the request and call another Handler (see {@link HandlerWrapper}).
* <li>Pass the request to one or more other Handlers (see {@link HandlerCollection}).
* </ul>
*
* Handlers are passed the servlet API request and response object, but are
* not Servlets. The servlet container is implemented by handlers for
* context, security, session and servlet that modify the request object
* before passing it to the next stage of handling.
*
*/
public interface Handler extends LifeCycle, Destroyable
也就是说,当一个HTTP请求到来的时候,需要Handler进行处理,而且必须要有一个Handler否则消息无法处理。Handler主要有以下几个功能:
1)产生HTTP Response
2)检查和修改request,并且调用其他Handler。之前已经介绍过,HandlerWrapper可以将多个Handler连接起来使用。
3)将request传递给其他的一个或者多个Handler。
现在知道了Handler的大致功能,接下来看看Jetty中实现了哪些Handler可以供开发者使用。
2、现有的Handler
在来回顾下之前的两篇文章中已经提到的Handler,
1)DefaultHandler,用于返回默认的错误页面404
2)ContextHandler,可以设置web应用的context
3)WebAppContext,可以设置web应用的context,用于加载和处理已有的web工程。
4)ServletContextHandler,可以设置web的context,同时可以添加servlet用于处理http request。
5)ResourceHandler,用于加载本地资源并且显示
之外还有HandlerWrapper,HandlerCollection,ContextHandlerCollection,HandlerList。
3、Handler调用路径
首先可以确认的是Server中的handle是调用Handler的入口,那么我们以这个函数为线索一步一步找出Handler是如何被调用的。
1)看一下handle函数是如何被调用的
2)查看handleRequest是如何被调用的
3)查看handle是如何被调用的
4)查看handle是如何被调用的
可以看出是在一个线程中被调用的,那么找出这个线程,
5)查看_handler是如何被调用的
6)查看dispatch是如何被调用的
7)查看schedule是如何被调用的
8)查看doSelect是如何被调用的
是在一个线程中被调用,查看这个线程
而这个函数所在的类是SelectorManager,在上一篇文章中讲到server启动时,会从_beans中读取各个bean,然后调用bean的start()函数。而其中有一个bean的类型
是SelectChannelConnector$ConnectorSelectorManager,我们来看一个这个类的定义,
private final class ConnectorSelectorManager extends SelectorManager
/* ------------------------------------------------------------ */
/**
* The Selector Manager manages and number of SelectSets to allow
* NIO scheduling to scale to large numbers of connections.
* <p>
*/
public abstract class SelectorManager extends AbstractLifeCycle implements Dumpable
服务器启动 -> 步骤8 -> 步骤7 ->步骤6 -> 步骤5 ...... -> 步骤1.
4、Handler调用详细步骤
首先看看doStart()函数的工作流程,
protected void doStart() throws Exception {
// 初始化_selectSet,SelectSet是对Selector的封装
_selectSet = new SelectSet[_selectSets];
for (int i = 0; i < _selectSet.length; i++)
_selectSet[i] = new SelectSet(i);
super.doStart();
// start a thread to Select
// 根据设置的selector数目_selectSets,启动_selectSets个线程
for (int i = 0; i < getSelectSets(); i++) {
// 匿名类中需要使用final
final int id = i;
// 使用dispatch函数分发线程,并且获取返回值
boolean selecting = dispatch(new Runnable() {
public void run() {
String name = Thread.currentThread().getName();
int priority = Thread.currentThread().getPriority();
try {
SelectSet[] sets = _selectSet;
if (sets == null)
return;
// 获取第i个selector
SelectSet set = sets[id];
Thread.currentThread().setName(name + " Selector" + id);
if (getSelectorPriorityDelta() != 0)
Thread.currentThread().setPriority(
Thread.currentThread().getPriority()
+ getSelectorPriorityDelta());
LOG.debug("Starting {} on {}", Thread.currentThread(),
this);
// 线程主内容:selector不停的doSelect()
while (isRunning()) {
try {
set.doSelect();
} catch (IOException e) {
LOG.ignore(e);
} catch (Exception e) {
LOG.warn(e);
}
}
} finally {
LOG.debug("Stopped {} on {}", Thread.currentThread(),
this);
Thread.currentThread().setName(name);
if (getSelectorPriorityDelta() != 0)
Thread.currentThread().setPriority(priority);
}
}
});
if (!selecting)
throw new IllegalStateException("!Selecting");
}
}
主要是根据配置的selector的数目_selectSets,初始化_selectSets个selector,然后针对每个selector启动一个线程,用于监听事件。这些线程中都有一个循环,方便selector不停地doSelect。
然后进入doSelect函数,
public void doSelect() throws IOException {
try {
_selecting = Thread.currentThread();
final Selector selector = _selector;
// Stopped concurrently ?
if (selector == null)
return;
// Make any key changes required
Object change;
int changes = _changes.size();
while (changes-- > 0 && (change = _changes.poll()) != null) {
Channel ch = null;
SelectionKey key = null;
try {
if (change instanceof EndPoint) {
// Update the operations for a key.
SelectChannelEndPoint endpoint = (SelectChannelEndPoint) change;
ch = endpoint.getChannel();
endpoint.doUpdateKey();
} else if (change instanceof ChannelAndAttachment) {
// finish accepting/connecting this connection
final ChannelAndAttachment asc = (ChannelAndAttachment) change;
final SelectableChannel channel = asc._channel;
ch = channel;
final Object att = asc._attachment;
if ((channel instanceof SocketChannel)
&& ((SocketChannel) channel).isConnected()) {
key = channel.register(selector,
SelectionKey.OP_READ, att);
SelectChannelEndPoint endpoint = createEndPoint(
(SocketChannel) channel, key);
key.attach(endpoint);
endpoint.schedule();
} else if (channel.isOpen()) {
key = channel.register(selector,
SelectionKey.OP_CONNECT, att);
}
} else if (change instanceof SocketChannel) {
// Newly registered channel
final SocketChannel channel = (SocketChannel) change;
ch = channel;
key = channel.register(selector,
SelectionKey.OP_READ, null);
SelectChannelEndPoint endpoint = createEndPoint(
channel, key);
key.attach(endpoint);
endpoint.schedule();
} else if (change instanceof ChangeTask) {
((Runnable) change).run();
} else if (change instanceof Runnable) {
dispatch((Runnable) change);
} else
throw new IllegalArgumentException(
change.toString());
} catch (CancelledKeyException e) {
LOG.ignore(e);
} catch (Throwable e) {
if (isRunning())
LOG.warn(e);
else
LOG.debug(e);
try {
if (ch != null)
ch.close();
} catch (IOException e2) {
LOG.debug(e2);
}
}
}
// Do and instant select to see if any connections can be
// handled.
int selected = selector.selectNow();
long now = System.currentTimeMillis();
// if no immediate things to do
if (selected == 0 && selector.selectedKeys().isEmpty()) {
// If we are in pausing mode
if (_pausing) {
try {
Thread.sleep(__BUSY_PAUSE); // pause to reduce
// impact of busy loop
} catch (InterruptedException e) {
LOG.ignore(e);
}
now = System.currentTimeMillis();
}
// workout how long to wait in select
_timeout.setNow(now);
long to_next_timeout = _timeout.getTimeToNext();
long wait = _changes.size() == 0 ? __IDLE_TICK : 0L;
if (wait > 0 && to_next_timeout >= 0
&& wait > to_next_timeout)
wait = to_next_timeout;
// If we should wait with a select
if (wait > 0) {
long before = now;
selector.select(wait);
now = System.currentTimeMillis();
_timeout.setNow(now);
// If we are monitoring for busy selector
// and this select did not wait more than 1ms
if (__MONITOR_PERIOD > 0 && now - before <= 1) {
// count this as a busy select and if there have
// been too many this monitor cycle
if (++_busySelects > __MAX_SELECTS) {
// Start injecting pauses
_pausing = true;
// if this is the first pause
if (!_paused) {
// Log and dump some status
_paused = true;
LOG.warn(
"Selector {} is too busy, pausing!",
this);
}
}
}
}
}
// have we been destroyed while sleeping
if (_selector == null || !selector.isOpen())
return;
// Look for things to do
for (SelectionKey key : selector.selectedKeys()) {
SocketChannel channel = null;
try {
if (!key.isValid()) {
key.cancel();
SelectChannelEndPoint endpoint = (SelectChannelEndPoint) key
.attachment();
if (endpoint != null)
endpoint.doUpdateKey();
continue;
}
Object att = key.attachment();
if (att instanceof SelectChannelEndPoint) {
if (key.isReadable() || key.isWritable())
((SelectChannelEndPoint) att).schedule();
} else if (key.isConnectable()) {
// Complete a connection of a registered channel
channel = (SocketChannel) key.channel();
boolean connected = false;
try {
connected = channel.finishConnect();
} catch (Exception e) {
connectionFailed(channel, e, att);
} finally {
if (connected) {
key.interestOps(SelectionKey.OP_READ);
SelectChannelEndPoint endpoint = createEndPoint(
channel, key);
key.attach(endpoint);
endpoint.schedule();
} else {
key.cancel();
}
}
} else {
// Wrap readable registered channel in an endpoint
channel = (SocketChannel) key.channel();
SelectChannelEndPoint endpoint = createEndPoint(
channel, key);
key.attach(endpoint);
if (key.isReadable())
endpoint.schedule();
}
key = null;
} catch (CancelledKeyException e) {
LOG.ignore(e);
} catch (Exception e) {
if (isRunning())
LOG.warn(e);
else
LOG.ignore(e);
try {
if (channel != null)
channel.close();
} catch (IOException e2) {
LOG.debug(e2);
}
if (key != null
&& !(key.channel() instanceof ServerSocketChannel)
&& key.isValid())
key.cancel();
}
}
// Everything always handled
selector.selectedKeys().clear();
now = System.currentTimeMillis();
_timeout.setNow(now);
Task task = _timeout.expired();
while (task != null) {
if (task instanceof Runnable)
dispatch((Runnable) task);
task = _timeout.expired();
}
// Idle tick
if (now - _idleTick > __IDLE_TICK) {
_idleTick = now;
final long idle_now = ((_lowResourcesConnections > 0 && selector
.keys().size() > _lowResourcesConnections)) ? (now
+ _maxIdleTime - _lowResourcesMaxIdleTime) : now;
dispatch(new Runnable() {
public void run() {
for (SelectChannelEndPoint endp : _endPoints
.keySet()) {
endp.checkIdleTimestamp(idle_now);
}
}
public String toString() {
return "Idle-" + super.toString();
}
});
}
// Reset busy select monitor counts
if (__MONITOR_PERIOD > 0 && now > _monitorNext) {
_busySelects = 0;
_pausing = false;
_monitorNext = now + __MONITOR_PERIOD;
}
} catch (ClosedSelectorException e) {
if (isRunning())
LOG.warn(e);
else
LOG.ignore(e);
} catch (CancelledKeyException e) {
LOG.ignore(e);
} finally {
_selecting = null;
}
}
doSelect()函数十分的长,这里暂时不做解释,只需要知道它是从channel中获取selectkey,然后调用SelectChannelEndPoint的schedule方法进行处理。关于连接会在之后的文章中详细介绍。
可以看到多处如下代码:
endpoint.schedule()
进入这个函数, /* ------------------------------------------------------------ */
/** Called by selectSet to schedule handling
*
*/
public void schedule()
{
synchronized (this)
{
// If there is no key, then do nothing
if (_key == null || !_key.isValid())
{
_readBlocked=false;
_writeBlocked=false;
this.notifyAll();
return;
}
// If there are threads dispatched reading and writing
if (_readBlocked || _writeBlocked)
{
// assert _dispatched;
if (_readBlocked && _key.isReadable())
_readBlocked=false;
if (_writeBlocked && _key.isWritable())
_writeBlocked=false;
// wake them up is as good as a dispatched.
this.notifyAll();
// we are not interested in further selecting
_key.interestOps(0);
if (_state<STATE_DISPATCHED)
updateKey();
return;
}
// Remove writeable op
if ((_key.readyOps() & SelectionKey.OP_WRITE) == SelectionKey.OP_WRITE && (_key.interestOps() & SelectionKey.OP_WRITE) == SelectionKey.OP_WRITE)
{
// Remove writeable op
_interestOps = _key.interestOps() & ~SelectionKey.OP_WRITE;
_key.interestOps(_interestOps);
_writable = true; // Once writable is in ops, only removed with dispatch.
}
// If dispatched, then deregister interest
if (_state>=STATE_DISPATCHED)
_key.interestOps(0);
else
{
// other wise do the dispatch
// 在这里会进入到调用handler的地方
dispatch();
if (_state>=STATE_DISPATCHED && !_selectSet.getManager().isDeferringInterestedOps0())
{
_key.interestOps(0);
}
}
}
}
点击进入dispatch()函数,
/* ------------------------------------------------------------ */
public void dispatch()
{
synchronized(this)
{
if (_state<=STATE_UNDISPATCHED)
{
if (_onIdle)
_state = STATE_NEEDS_DISPATCH;
else
{
_state = STATE_DISPATCHED;
boolean dispatched = _manager.dispatch(_handler);
if(!dispatched)
{
_state = STATE_NEEDS_DISPATCH;
LOG.warn("Dispatched Failed! "+this+" to "+_manager);
updateKey();
}
}
}
}
}
_manager.dispatch(_handler)就是调用_handler的真正地方。而_handler就是在之前多次提到的存放到server中的handler。
在之后涉及到的就是jetty种的线程池的工作原理,这里暂时不做介绍。