Dubbo原始碼解析(十四)遠端通訊——Http
遠端通訊——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",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,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,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 處理器介面,就定義了一個處理請求的方法。
後記
該部分相關的原始碼解析地址:github.com/CrazyHZM/in…
該文章講解了內嵌tomcat和jetty的來實現的http伺服器,關鍵需要對tomcat和jetty的配置有所瞭解。下一篇我會講解基於mina實現遠端通訊部分。