dubbo源码解析(十四)远程通信——Http

29次阅读

共计 9934 个字符,预计需要花费 25 分钟才能阅读完成。

远程通讯——Http
目标:介绍基于 Http 的来实现的远程通信、介绍 dubbo-remoting-http 内的源码解析。
前言
本文我们讲解的是如何基于 Tomcat 或者 Jetty 实现 HTTP 服务器。Tomcat 和 Jetty 都是一种 servlet 引擎,Jetty 要比 Tomcat 的架构更简单一些。关于它们之间的比较,我觉得 google 一些更加方便,我就不多废话了。
下面是 dubbo-remoting-http 包下的类图:

可以看到这个包下只提供了服务端实现,并没有客户端实现。
源码分析
(一)HttpServer
public interface HttpServer extends Resetable {

/**
* get http handler.
* 获得 http 的处理类
* @return http handler.
*/
HttpHandler getHttpHandler();

/**
* get url.
* 获得 url
* @return url
*/
URL getUrl();

/**
* get local address.
* 获得本地服务器地址
* @return local address.
*/
InetSocketAddress getLocalAddress();

/**
* close the channel.
* 关闭通道
*/
void close();

/**
* Graceful close the channel.
* 优雅的关闭通道
*/
void close(int timeout);

/**
* is bound.
* 是否绑定
* @return bound
*/
boolean isBound();

/**
* is closed.
* 服务器是否关闭
* @return closed
*/
boolean isClosed();

}
该接口是 http 服务器的接口,定义了服务器相关的方法,都比较好理解。
(二)AbstractHttpServer
该类实现接口 HttpServer,是 http 服务器接口的抽象类。
/**
* url
*/
private final URL url;

/**
* http 服务器处理器
*/
private final HttpHandler handler;

/**
* 该服务器是否关闭
*/
private volatile boolean closed;

public AbstractHttpServer(URL url, HttpHandler handler) {
if (url == null) {
throw new IllegalArgumentException(“url == null”);
}
if (handler == null) {
throw new IllegalArgumentException(“handler == null”);
}
this.url = url;
this.handler = handler;
}
该类中有三个属性,关键是看在后面怎么用到,因为该类是抽象类,所以该类中的方法并没有实现具体的逻辑,我们继续往下看它的三个子类。
(三)TomcatHttpServer
该类是基于 Tomcat 来实现服务器的实现类,它继承了 AbstractHttpServer。
1. 属性
/**
* 内嵌的 tomcat 对象
*/
private final Tomcat tomcat;

/**
* url 对象
*/
private final URL url;
该类的两个属性,关键是内嵌的 tomcat。
2. 构造方法
public TomcatHttpServer(URL url, final HttpHandler handler) {
super(url, handler);

this.url = url;
// 添加处理器
DispatcherServlet.addHttpHandler(url.getPort(), handler);
// 获得 java.io.tmpdir 的绝对路径目录
String baseDir = new File(System.getProperty(“java.io.tmpdir”)).getAbsolutePath();
// 创建内嵌的 tomcat 对象
tomcat = new Tomcat();
// 设置根目录
tomcat.setBaseDir(baseDir);
// 设置端口号
tomcat.setPort(url.getPort());
// 给默认的 http 连接器。设置最大线程数
tomcat.getConnector().setProperty(
“maxThreads”, String.valueOf(url.getParameter(Constants.THREADS_KEY, Constants.DEFAULT_THREADS)));
// tomcat.getConnector().setProperty(
// “minSpareThreads”, String.valueOf(url.getParameter(Constants.THREADS_KEY, Constants.DEFAULT_THREADS)));

// 设置最大的连接数
tomcat.getConnector().setProperty(
“maxConnections”, String.valueOf(url.getParameter(Constants.ACCEPTS_KEY, -1)));

// 设置 URL 编码格式
tomcat.getConnector().setProperty(“URIEncoding”, “UTF-8”);
// 设置连接超时事件为 60s
tomcat.getConnector().setProperty(“connectionTimeout”, “60000”);

// 设置最大长连接个数为不限制个数
tomcat.getConnector().setProperty(“maxKeepAliveRequests”, “-1”);
// 设置将由连接器使用的 Coyote 协议。
tomcat.getConnector().setProtocol(“org.apache.coyote.http11.Http11NioProtocol”);

// 添加上下文
Context context = tomcat.addContext(“/”, baseDir);
// 添加 servlet,把 servlet 添加到 context
Tomcat.addServlet(context, “dispatcher”, new DispatcherServlet());
// 添加 servlet 映射
context.addServletMapping(“/*”, “dispatcher”);
// 添加 servlet 上下文
ServletManager.getInstance().addServletContext(url.getPort(), context.getServletContext());

try {
// 开启 tomcat
tomcat.start();
} catch (LifecycleException e) {
throw new IllegalStateException(“Failed to start tomcat server at ” + url.getAddress(), e);
}
}
该方法的构造函数中就启动了 tomcat,前面很多都是对 tomcat 的启动参数以及配置设置。如果有过 tomcat 配置经验的朋友应该看起来很简单。
3.close
@Override
public void close() {
super.close();

// 移除相关的 servlet 上下文
ServletManager.getInstance().removeServletContext(url.getPort());

try {
// 停止 tomcat
tomcat.stop();
} catch (Exception e) {
logger.warn(e.getMessage(), e);
}
}
该方法是关闭服务器的方法。调用了 tomcat.stop
(四)JettyHttpServer
该类是基于 Jetty 来实现服务器的实现类,它继承了 AbstractHttpServer。
1. 属性
/**
* 内嵌的 Jetty 服务器对象
*/
private Server server;

/**
* url 对象
*/
private URL url;
该类的两个属性,关键是内嵌的 sever,它是内嵌的 etty 服务器对象。
2. 构造方法
public JettyHttpServer(URL url, final HttpHandler handler) {
super(url, handler);
this.url = url;
// TODO we should leave this setting to slf4j
// we must disable the debug logging for production use
// 设置日志
Log.setLog(new StdErrLog());
// 禁用调试用的日志
Log.getLog().setDebugEnabled(false);

// 添加 http 服务器处理器
DispatcherServlet.addHttpHandler(url.getParameter(Constants.BIND_PORT_KEY, url.getPort()), handler);

// 获得线程数
int threads = url.getParameter(Constants.THREADS_KEY, Constants.DEFAULT_THREADS);
// 创建线程池
QueuedThreadPool threadPool = new QueuedThreadPool();
// 设置线程池配置
threadPool.setDaemon(true);
threadPool.setMaxThreads(threads);
threadPool.setMinThreads(threads);

// 创建选择 NIO 连接器
SelectChannelConnector connector = new SelectChannelConnector();

// 获得绑定的 ip
String bindIp = url.getParameter(Constants.BIND_IP_KEY, url.getHost());
if (!url.isAnyHost() && NetUtils.isValidLocalHost(bindIp)) {
// 设置主机地址
connector.setHost(bindIp);
}
// 设置端口号
connector.setPort(url.getParameter(Constants.BIND_PORT_KEY, url.getPort()));

// 创建 Jetty 服务器对象
server = new Server();
// 设置线程池
server.setThreadPool(threadPool);
// 设置连接器
server.addConnector(connector);

// 添加 DispatcherServlet 到 jetty
ServletHandler servletHandler = new ServletHandler();
ServletHolder servletHolder = servletHandler.addServletWithMapping(DispatcherServlet.class, “/*”);
servletHolder.setInitOrder(2);

// dubbo’s original impl can’t support the use of ServletContext
// server.addHandler(servletHandler);
// TODO Context.SESSIONS is the best option here?
Context context = new Context(server, “/”, Context.SESSIONS);
context.setServletHandler(servletHandler);
// 添加 ServletContext 对象,到 ServletManager 中
ServletManager.getInstance().addServletContext(url.getParameter(Constants.BIND_PORT_KEY, url.getPort()), context.getServletContext());

try {
// 启动 jetty 服务器
server.start();
} catch (Exception e) {
throw new IllegalStateException(“Failed to start jetty server on ” + url.getParameter(Constants.BIND_IP_KEY) + “:” + url.getParameter(Constants.BIND_PORT_KEY) + “, cause: ”
+ e.getMessage(), e);
}
}
可以看到它跟 TomcatHttpServer 中构造函数不同的是 API 的不同,不过思路差不多,先设置启动参数和配置,然后启动 jetty 服务器。
3.close
@Override
public void close() {
super.close();

// 移除 ServletContext 对象
ServletManager.getInstance().removeServletContext(url.getParameter(Constants.BIND_PORT_KEY, url.getPort()));

if (server != null) {
try {
// 停止服务器
server.stop();
} catch (Exception e) {
logger.warn(e.getMessage(), e);
}
}
}
该方法是关闭服务器的方法,调用的是 server 的 stop 方法。
(五)ServletHttpServer
该类继承了 AbstractHttpServer,是基于 Servlet 的服务器实现类。
public class ServletHttpServer extends AbstractHttpServer {

public ServletHttpServer(URL url, HttpHandler handler) {
super(url, handler);
// / 把 HttpHandler 到 DispatcherServlet 中,默认端口为 8080
DispatcherServlet.addHttpHandler(url.getParameter(Constants.BIND_PORT_KEY, 8080), handler);
}

}
该类就一个构造方法。就是把服务器处理器注册到 DispatcherServlet 上。
(六)HttpBinder
@SPI(“jetty”)
public interface HttpBinder {

/**
* bind the server.
* 绑定到服务器
* @param url server url.
* @return server.
*/
@Adaptive({Constants.SERVER_KEY})
HttpServer bind(URL url, HttpHandler handler);

}
该接口是 http 绑定器接口,其中就定义了一个方法就是绑定方法,并且返回服务器对象。该接口是一个可扩展接口,默认实现 JettyHttpBinder。它有三个实现类,请往下看。
(七)TomcatHttpBinder
public class TomcatHttpBinder implements HttpBinder {

@Override
public HttpServer bind(URL url, HttpHandler handler) {
// 创建一个 TomcatHttpServer
return new TomcatHttpServer(url, handler);
}

}
一眼就看出来,就是创建了个基于 Tomcat 实现的服务器 TomcatHttpServer 对象。具体的就往上看 TomcatHttpServer 里面的实现。
(八)JettyHttpBinder
public class JettyHttpBinder implements HttpBinder {

@Override
public HttpServer bind(URL url, HttpHandler handler) {
// 创建 JettyHttpServer 实例
return new JettyHttpServer(url, handler);
}

}
一眼就看出来,就是创建了个基于 Jetty 实现的服务器 JettyHttpServer 对象。具体的就往上看 JettyHttpServer 里面的实现。
(九)ServletHttpBinder
public class ServletHttpBinder implements HttpBinder {

@Override
@Adaptive()
public HttpServer bind(URL url, HttpHandler handler) {
// 创建 ServletHttpServer 对象
return new ServletHttpServer(url, handler);
}

}
创建了个基于 servlet 实现的服务器 ServletHttpServer 对象。并且方法上加入了 Adaptive,用到了 dubbo SPI 机制。
(十)BootstrapListener
public class BootstrapListener implements ServletContextListener {

@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
// context 创建的时候,把 ServletContext 添加到 ServletManager
ServletManager.getInstance().addServletContext(ServletManager.EXTERNAL_SERVER_PORT, servletContextEvent.getServletContext());
}

@Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
// context 销毁到时候,把 servletContextEvent 移除
ServletManager.getInstance().removeServletContext(ServletManager.EXTERNAL_SERVER_PORT);
}
}
该类实现了 ServletContextListener,是 启动监听器,当 context 创建和销毁的时候对 ServletContext 做处理。不过需要配置 BootstrapListener 到 web.xml,通过这样的方式,让外部的 ServletContext 对象,添加到 ServletManager 中。
(十一)DispatcherServlet
public class DispatcherServlet extends HttpServlet {

private static final long serialVersionUID = 5766349180380479888L;
/**
* http 服务器处理器
*/
private static final Map<Integer, HttpHandler> handlers = new ConcurrentHashMap<Integer, HttpHandler>();
/**
* 单例
*/
private static DispatcherServlet INSTANCE;

public DispatcherServlet() {
DispatcherServlet.INSTANCE = this;
}

/**
* 添加处理器
* @param port
* @param processor
*/
public static void addHttpHandler(int port, HttpHandler processor) {
handlers.put(port, processor);
}

public static void removeHttpHandler(int port) {
handlers.remove(port);
}

public static DispatcherServlet getInstance() {
return INSTANCE;
}

@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 获得处理器
HttpHandler handler = handlers.get(request.getLocalPort());
// 如果处理器不存在
if (handler == null) {// service not found.
// 返回 404
response.sendError(HttpServletResponse.SC_NOT_FOUND, “Service not found.”);
} else {
// 处理请求
handler.handle(request, response);
}
}

}
该类继承了 HttpServlet,是服务请求调度 servlet 类,主要是 service 方法,根据请求来调度不同的处理器去处理请求,如果没有该处理器,则报错 404
(十二)ServletManager
public class ServletManager {

/**
* 外部服务器端口,用于 `servlet` 的服务器端口
*/
public static final int EXTERNAL_SERVER_PORT = -1234;

/**
* 单例
*/
private static final ServletManager instance = new ServletManager();

/**
* ServletContext 集合
*/
private final Map<Integer, ServletContext> contextMap = new ConcurrentHashMap<Integer, ServletContext>();

public static ServletManager getInstance() {
return instance;
}

/**
* 添加 ServletContext
* @param port
* @param servletContext
*/
public void addServletContext(int port, ServletContext servletContext) {
contextMap.put(port, servletContext);
}

/**
* 移除 ServletContext
* @param port
*/
public void removeServletContext(int port) {
contextMap.remove(port);
}

/**
* 获得 ServletContext 对象
* @param port
* @return
*/
public ServletContext getServletContext(int port) {
return contextMap.get(port);
}
}
该类是 servlet 的管理器,管理着 ServletContext。
(十三)HttpHandler
public interface HttpHandler {

/**
* invoke.
* HTTP 请求处理
* @param request request.
* @param response response.
* @throws IOException
* @throws ServletException
*/
void handle(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException;

}
该接口是 HTTP 处理器接口,就定义了一个处理请求的方法。
后记
该部分相关的源码解析地址:https://github.com/CrazyHZM/i…

该文章讲解了内嵌 tomcat 和 jetty 的来实现的 http 服务器,关键需要对 tomcat 和 jetty 的配置有所了解。下一篇我会讲解基于 mina 实现远程通信部分。

正文完
 0