1. 程式人生 > 程式設計 >Dubbo原始碼解析(十四)遠端通訊——Http

Dubbo原始碼解析(十四)遠端通訊——Http

遠端通訊——Http

目標:介紹基於Http的來實現的遠端通訊、介紹dubbo-remoting-http內的原始碼解析。

前言

本文我們講解的是如何基於Tomcat或者Jetty實現HTTP伺服器。Tomcat和Jetty都是一種servlet引擎,Jetty要比Tomcat的架構更簡單一些。關於它們之間的比較,我覺得google一些更加方便,我就不多廢話了。

下面是dubbo-remoting-http包下的類圖:

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實現遠端通訊部分。