文章

HTTP服务器(八)

HTTP服务器(八)

本文基于 Dubbo 2.6.1 版本,望知悉。

1. 概述

本文,我们来分享 Dubbo 的 HTTP 服务器,在 dubbo-remoting-http 模块中实现,使用在 http://rest://hessian://webservice://协议上。

dubbo-remoting-http 只提供 Server 部分,不同于前面分享的 Dubbo 的 NIO 服务器( dubbo-remoting-api ),提供 Client 和 Server 。代码结构如下:

代码结构

代码结构

  • API 层:
    • 最外层:API 定义。
    • support 包: 公用实现。
  • 实现层:
    • jetty 包:基于内嵌的 Jetty 实现,版本为 6.x 。
    • tomcat 包:基于内嵌的 Tomcat 实现,版本为 8.x 。
    • servlet 包:基于外部的 Servlet Bridge Server 实现。简单的说,使用 war 包,部署在 Tomcat 、Jetty 等 Servlet 容器。

《Dubbo 用户指南 —— http://》 文档中,分享了具体的配置方式。这块的文档,写的比较简略,如果看不太明白的胖友,可以看看 《Dubbo 组成原理 - http服务消费端如何调用》 作为补充。

另外,文档中推荐使用 Servlet Bridge Server 的部署方式,可能是文档写的比较早,现在主流是的 Fat Jar 的方式,所以实际使用时,jetty 或 tomcat 方式更为适合。

当然,能够方便的通过配置的方式,切换具体的 HTTP 服务的拓展,依托于 Dubbo SPI 的机制。

2. 原理

dubbo-remoting-http 模块,类图如下:

类图

代码结构

  • HttpBinder ,负责创建对应的 HttpServer 对象。
  • 不同的 Protocol ,实现各自的 HttpHandler 类。并且,暴露服务时,启动 HttpServer 的同时,创建对应的 HttpHandler 对象,以 port 为键,注册到 DispatcherServlet 上。
  • DispatcherServlet ,核心,调度请求,到对应的 HttpHandler 中。

整体流程如下:

流程

流程

3. API

3.1 HttpServer

com.alibaba.dubbo.remoting.http.HttpServer ,实现 Resetable 接口,HTTP 服务器接口。方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 处理器
HttpHandler getHttpHandler();

// 【属性相关】
URL getUrl();
InetSocketAddress getLocalAddress();

// 【状态相关】
boolean isBound();

void close();
void close(int timeout);
boolean isClosed();

3.1.1 AbstractHttpServer

com.alibaba.dubbo.remoting.http.AbstractHttpServer ,实现 HttpServer 接口,HTTP 服务器抽象类。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
public abstract class AbstractHttpServer implements HttpServer {

    /**
     * URL 对象
     */
    private final URL url;
    /**
     * 处理器
     */
    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;
    }

    @Override
    public HttpHandler getHttpHandler() {
        return handler;
    }

    @Override
    public URL getUrl() {
        return url;
    }

    @Override
    public void reset(URL url) {
    }

    @Override
    public boolean isBound() {
        return true;
    }

    @Override
    public InetSocketAddress getLocalAddress() {
        return url.toInetSocketAddress();
    }

    @Override
    public void close() {
        closed = true;
    }

    @Override
    public void close(int timeout) {
        close();
    }

    @Override
    public boolean isClosed() {
        return closed;
    }

}

3.2 HttpHandler

com.alibaba.dubbo.remoting.http.HttpHandler ,HTTP 处理器接口。方法如下:

1
2
3
4
5
6
7
8
9
10
11
/**
 * invoke.
 *
 * 处理器请求
 *
 * @param request  request. 请求
 * @param response response. 响应
 * @throws IOException 当 IO 发生异常
 * @throws ServletException 当 Servlet 发生异常
 */
void handle(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException;

3.3 HttpBinder

com.alibaba.dubbo.remoting.http.HttpBinder ,HTTP 绑定器接口。方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
@SPI("jetty")
public interface HttpBinder {

    /**
     * bind the server.
     *
     * @param url server url.
     * @return server.
     */
    @Adaptive({Constants.SERVER_KEY})
    HttpServer bind(URL url, HttpHandler handler);

}

  • @SPI(“jetty”) 拓展点注解,Dubbo SPI,默认为 “jetty” ,即未配置情况下,使用 Jetty Server 。
  • @Adaptive({Constants.SERVER_KEY}) 注解,基于 Dubbo SPI Adaptive 机制,加载对应的 Server 实现,使用 URL.server 属性。

3.4 DispatcherServlet

com.alibaba.dubbo.remoting.http.serlvet.DispatcherServlet ,实现 javax.servlet.http.HttpServlet 接口,服务请求调度 Servlet。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
public class DispatcherServlet extends HttpServlet {

    /**
     * 处理器集合
     *
     * key:服务器端口
     */
    private static final Map<Integer, HttpHandler> handlers = new ConcurrentHashMap<Integer, HttpHandler>();
    /**
     * 单例
     */
    private static DispatcherServlet INSTANCE;

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

    public static DispatcherServlet getInstance() {
        return INSTANCE;
    }

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

    /**
     * 移除处理器
     *
     * @param port 服务器端口
     */
    public static void removeHttpHandler(int port) {
        handlers.remove(port);
    }

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

}

  • handlers 静态属性,处理器集合。
    • #addHttpHandler(port, HttpHandler) 方法,注册处理器。
    • #removeHttpHandler(port) 方法,取消处理器。
  • #service(request, response) 实现方法,调度请求。
    • 第 45 行:基于端口,获得处理器。
    • 第 46 至 48 行:处理器不存在,返回 500 报错。
    • 第 49 至 52 行:调用 HttpHandler#handle(request, response) 方法,处理请求,从而调度到 Service 的对应的方法。

3.5 ServletManager

com.alibaba.dubbo.remoting.http.serlvet.ServletManager ,Servlet 管理器,负责管理 ServletContext ,目前仅有 dubbo-rpc-rest 模块,需要使用到这个类。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
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 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 ServletContext 对象
     */
    public ServletContext getServletContext(int port) {
        return contextMap.get(port);
    }

}

  • EXTERNAL_SERVER_PORT 静态属性,外部服务器端口,用于 servlet 的服务器端口。
  • contextMap 静态属性,ServletContext 集合。
    • #addServletContext(port, ServletContext) 方法,添加。
    • #removeServletContext(port) 方法,移除。
    • #getServletContext(port) 方法,查询。

4. Tomcat 实现

4.1 TomcatHttpServer

com.alibaba.dubbo.remoting.http.tomcat.TomcatHttpServer ,实现 AbstractHttpServer 抽象类,基于 Tomcat 的 HTTP 服务器实现类。

4.1.1 构造方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/**
 * 内嵌的 Tomcat 对象
 */
private final Tomcat tomcat;
/**
 * URL 对象
 */
private final URL url;

public TomcatHttpServer(URL url, final HttpHandler handler) {
    super(url, handler);
    this.url = url;

    // 注册 HttpHandler 到 DispatcherServlet 中
    DispatcherServlet.addHttpHandler(url.getPort(), handler);

    // 创建内嵌的 Tomcat 对象
    String baseDir = new File(System.getProperty("java.io.tmpdir")).getAbsolutePath();
    tomcat = new Tomcat();
    tomcat.setBaseDir(baseDir);
    tomcat.setPort(url.getPort());
    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))); // 最大连接池
    tomcat.getConnector().setProperty("URIEncoding", "UTF-8"); // 编码为 UTF-8
    tomcat.getConnector().setProperty("connectionTimeout", "60000"); // 连接超时,60 秒
    tomcat.getConnector().setProperty("maxKeepAliveRequests", "-1");
    tomcat.getConnector().setProtocol("org.apache.coyote.http11.Http11NioProtocol");

    // 添加 DispatcherServlet 到 Tomcat 中
    Context context = tomcat.addContext("/", baseDir);
    Tomcat.addServlet(context, "dispatcher", new DispatcherServlet());
    context.addServletMapping("/*", "dispatcher");

    // 添加 ServletContext 对象,到 ServletManager 中
    ServletManager.getInstance().addServletContext(url.getPort(), context.getServletContext());

    // 启动 Tomcat
    try {
        tomcat.start();
    } catch (LifecycleException e) {
        throw new IllegalStateException("Failed to start tomcat server at " + url.getAddress(), e);
    }
}

  • 第 15 行:调用 DispatcherServlet#addHttpHandler(port, handler) 方法,注册 HttpHandler 到 DispatcherServlet 中。
  • 第 17 至 29 行:创建内嵌的 Tomcat 对象。
  • 第 31 至 34 行:创建并添加 DispatcherServlet 对象,到 Tomcat 中。
  • 第 37 行:调用 ServletManager#addServletContext(port, ServletContext) 方法,添加 DispatcherServlet 对象,到 ServletManager 中。
  • 第 39 至 44 行:调用 Tomcat#start() 方法,启动 Tomcat 。

4.1.2 关闭

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Override
public void close() {
    // 标记关闭
    super.close();

    // 移除 ServletContext 对象
    ServletManager.getInstance().removeServletContext(url.getPort());

    // 关闭 Tomcat
    try {
        tomcat.stop();
    } catch (Exception e) {
        logger.warn(e.getMessage(), e);
    }
}

  • 缺少,调用 DispacherServlet#remove(port) 方法,将 HttpHandler 对象,移除出 DispatcherServlet 。

4.2 TomcatHttpBinder

com.alibaba.dubbo.remoting.http.tomcat.TomcatHttpBinder ,TomcatHttpServer 绑定器实现类。代码如下:

1
2
3
4
5
6
7
8
public class TomcatHttpBinder implements HttpBinder {

    @Override
    public HttpServer bind(URL url, HttpHandler handler) {
        return new TomcatHttpServer(url, handler);
    }

}

5. Jetty 实现

jetty 和 tomcat 包的实现,差不多,主要差异在 Tomcat 和 Jetty 的 API 不同。

所以,下面我们就贴贴代码啦,当然,还是有中文详细注释的。

5.1 JettyHttpServer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
public class JettyHttpServer extends AbstractHttpServer {

    private static final Logger logger = LoggerFactory.getLogger(JettyHttpServer.class);

    /**
     * 内嵌的 Jetty 服务器
     */
    private Server server;
    /**
     * URL 对象
     */
    private URL url;

    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);

        // 注册 HttpHandler 到 DispatcherServlet 中
        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);

        // 创建 Jetty Connector 对象
        SelectChannelConnector connector = new SelectChannelConnector();
        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);

        // 添加 ServletContext 对象,到 ServletManager 中
        // 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);
        ServletManager.getInstance().addServletContext(url.getParameter(Constants.BIND_PORT_KEY, url.getPort()), context.getServletContext());

        // 启动 Jetty
        try {
            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);
        }
    }

    @Override
    public void close() {
        // 标记关闭
        super.close();

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

        // 关闭 Jetty
        if (server != null) {
            try {
                server.stop();
            } catch (Exception e) {
                logger.warn(e.getMessage(), e);
            }
        }
    }
}

5.2 JettyHttpBinder

1
2
3
4
5
6
7
8
public class JettyHttpBinder implements HttpBinder {

    @Override
    public HttpServer bind(URL url, HttpHandler handler) {
        return new JettyHttpServer(url, handler);
    }

}

6. Servlet Bridge 实现

6.1 ServletHttpServer

com.alibaba.dubbo.remoting.http.servlet.ServletHttpServer ,实现 AbstractHttpServer 抽象类, 基于 Servlet 的服务器实现类。代码如下:

1
2
3
4
5
6
7
8
9
10
public class ServletHttpServer extends AbstractHttpServer {

    public ServletHttpServer(URL url, HttpHandler handler) {
        super(url, handler);

        // 注册 HttpHandler 到 DispatcherServlet 中
        DispatcherServlet.addHttpHandler(url.getParameter(Constants.BIND_PORT_KEY, 8080), handler);
    }

}

  • 注意,在配置的端口,和外部的 Servlet 容器的端口,需要保持一致
  • 需要配置 DispatcherServlet 到 web.xml 中。通过这样的方式,让外部的 Servlet 容器,可以进行转发。

6.2 ServletHttpBinder

1
2
3
4
5
6
7
8
public class ServletHttpBinder implements HttpBinder {

    @Adaptive()
    public HttpServer bind(URL url, HttpHandler handler) {
        return new ServletHttpServer(url, handler);
    }

}

6.3 BootstrapListener

com.alibaba.dubbo.remoting.http.servlet.BootstrapListener ,实现 ServletContextListener 接口, 启动监听器。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class BootstrapListener implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        ServletManager.getInstance().addServletContext(ServletManager.EXTERNAL_SERVER_PORT, servletContextEvent.getServletContext());
    }

    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) {
        ServletManager.getInstance().removeServletContext(ServletManager.EXTERNAL_SERVER_PORT);
    }

}

  • 需要配置 BootstrapListener 到 web.xml 中。通过这样的方式,让外部的 ServletContext 对象,添加到 ServletManager 中。

666. 彩蛋

又开阔了下思路,美滋滋。另外,艿艿配置了 http:// 协议的例子,使用 Tomcat 内嵌服务器。地址如下:

本文由作者按照 CC BY 4.0 进行授权