`
WildFly
  • 浏览: 63034 次
社区版块
存档分类
最新评论

Undertow服务器基础分析 - Undertow

阅读更多

Undertow是一个Web服务器,那么它就需要具备的现代Web服务器的基本特性,比如Servlet,JSP,文件服务器,代理服务器,安全认证等。undertow目前已经实现了绝大多数功能,并且因为wildfly通过了JavaEE7 TCK认证,所以可以说Undertow是一个通过Servlet 3.1认证的Web服务器和容器。这篇文章只分析Undertow的主干流程上的主要功能,即undertow-core和undertow-servlet。

在第一篇里介绍过,Undertow的一个设计目的就是为了嵌入当作web服务器使用。当前,很多Java和其他语言的开源项目,都内嵌一个小型的web server,来提供服务能力,可以是输出html,也可以是输出REST方式的json文本。支持HTTP(s)协议,对于很多应用程序已能够满足需要,所以很多框架如Netty支持到HTTP协议这一层,Undertow-core也是这样。

Servlet作为JavaEE重要规范,是目前Java端开发Web应用的首选技术,Undertow-servlet就是一个完备的Servlet容器。本文假设读者已经熟悉Servlet规范知识,并且对Websocket有所了解。

先看一个简单的内嵌WebServer的代码例子:

public class HelloWorldServer {

    public static void main(final String[] args) {
        Undertow server = Undertow.builder()
                .addHttpListener(8080, "localhost")
                .setHandler(new HttpHandler() { //设置HttpHandler回调方法
                    @Override
                    public void handleRequest(final HttpServerExchange exchange) throws Exception {
                        exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain");
                        exchange.getResponseSender().send("Hello World");
                    }
                }).build();
        server.start();
    }
}

 

和上篇XNIO相似,依然用了回调方式。在undertow里,最主要的接口就是HttpHandler,和XNIO中的ChannelListener概念相似。HttpHandler也是只有一个方法handleRequest,参数是HttpServerExchange。

这个程序内嵌一个Web服务器,打开本机的8080端口接受请求,当有浏览器连上之后,就发送一个纯文本"Hello World"。

HttpServerExchange携带所有的上下文状态信息,这个类也是目前undertow里面最长代码,有2000多行。同时包含了request和response的相关信息,可以通过getRequestHeaders()/getResponseHeaders()获取对应头部信息。

Undertow类是入口点,通过builder传入参数来构建Web容器。

再看一个稍微复杂些的构建例子:

Xnio xnio = Xnio.getInstance();

XnioWorker worker = xnio.createWorker(OptionMap.builder()
        .set(Options.WORKER_IO_THREADS, ioThreads)
        .set(Options.WORKER_TASK_CORE_THREADS, workerThreads)
        .set(Options.WORKER_TASK_MAX_THREADS, workerThreads)
        .set(Options.TCP_NODELAY, true)
        .getMap());

OptionMap socketOptions = OptionMap.builder()
        .set(Options.WORKER_IO_THREADS, ioThreads)
        .set(Options.TCP_NODELAY, true)
        .set(Options.REUSE_ADDRESSES, true)
        .getMap();

Pool<ByteBuffer> buffers = new ByteBufferSlicePool(BufferAllocator.DIRECT_BYTE_BUFFER_ALLOCATOR,bufferSize, bufferSize * buffersPerRegion);

    HttpOpenListener openListener = new HttpOpenListener(buffers, OptionMap.builder().set(UndertowOptions.BUFFER_PIPELINED_DATA, true).addAll(serverOptions).getMap(), bufferSize);
    openListener.setRootHandler(rootHandler);
    ChannelListener<AcceptingChannel<StreamConnection>> acceptListener = ChannelListeners.openListenerAdapter(openListener);
    AcceptingChannel<? extends StreamConnection> server = worker.createStreamConnectionServer(new InetSocketAddress(Inet4Address.getByName(listener.host), listener.port), acceptListener, socketOptions);
    server.resumeAccepts();

 
看到上一篇的Xnio知识全部用上。这是Undertow类入口方法构造的过程,参数全部通过OptionMap构造并传递给XnioWorker。构建一个缓冲池Pool<ByteBuffer>,用来分配服务器接收信息缓冲区。

有两个Listener:
HttpOpenListener继承于ChannelListener,作用是当有连接连入时打开端口。rootHandler是一个HttpHandler,也就是上面提过最重要的接口。
acceptListener用来侦听端口,并把openListener引用传入。

然后就可以通过AcceptingChannel来启动Web服务器了。当连接请求到来,首先acceptListener的handleEvent方法被调用:

    public void handleEvent(final AcceptingChannel<C> channel) {
        try {
            final C accepted = channel.accept();
            if (accepted != null) {
                invokeChannelListener(accepted, openListener);
            }
        } catch (IOException e) {
        }
    }
 
    public static <T extends Channel> boolean invokeChannelListener(T channel, ChannelListener<? super T> channelListener) {
        if (channelListener != null) try {
            // 进入openListener handleEvent方法
            channelListener.handleEvent(channel);
        } catch (Throwable t) {
            return false;
        }
        return true;
    }

 
接下来进入HttpOpenListener的handleEvent方法被调用:

    private final Pool<ByteBuffer> bufferPool;
    private final int bufferSize;
    private volatile HttpHandler rootHandler;
    private volatile OptionMap undertowOptions;
    private volatile HttpRequestParser parser;

    public void handleEvent(final StreamConnection channel) {
        HttpServerConnection connection = new HttpServerConnection(channel, bufferPool, rootHandler, undertowOptions, bufferSize);
        HttpReadListener readListener = new HttpReadListener(connection, parser);

        connection.setReadListener(readListener);
        readListener.newRequest();
        channel.getSourceChannel().setReadListener(readListener);
        readListener.handleEvent(channel.getSourceChannel());
    }

 
这里对传入的HttpHandler进行层层封装,HttpServerConnection是一个Http连接的概念抽象,而HttpReadListener则进一步包含了HttpRequestParser,用来解析HTTP请求内容。在readListener.newRequest()方法执行时,创建了上下文信息类HttpServerExchange。这些就绪后,进一步调用HttpReadListener.handleEvent。

处理请求数据的代码在HttpReadListener.handleEventWithNoRunningRequest方法中,整个流程很清晰,就是从缓冲池中拿到可用缓存区,从channel中读取信息,用parser进行解析,并存放到HttpServerExchange中,最后调度给最开始的HttpHandler。

    public static void executeRootHandler(final HttpHandler handler, final HttpServerExchange exchange) {
        try {
            exchange.setInCall(true);
            handler.handleRequest(exchange); //调用最初传入的HttpHandler
            exchange.setInCall(false);
            boolean resumed = exchange.runResumeReadWrite();
            if (exchange.isDispatched()) {
                if(resumed) {
                    throw new RuntimeException("resumed and dispatched");
                }
                final Runnable dispatchTask = exchange.getDispatchTask();
                Executor executor = exchange.getDispatchExecutor();
                exchange.unDispatch();
                if (dispatchTask != null) {
                    executor = executor == null ? exchange.getConnection().getWorker() : executor;
                    executor.execute(dispatchTask);
                }
            } else if(!resumed) {
                exchange.endExchange();
            }
        } catch (Throwable t) {
            exchange.setInCall(false);
            if (!exchange.isResponseStarted()) {
                exchange.setResponseCode(500);
            }
            UndertowLogger.REQUEST_LOGGER.errorf(t, "Blocking request failed %s", exchange);
            exchange.endExchange();
        }
    }

 
我们留意一下isDispatched分支是性能保障的关键,当这个调用可能阻塞时,则从线程池从获取可用线程,调配它来执行。这样就实现了异步操作,结果会放回exchange中。使用HttpServerExchange.dispatch()方法会把执行从IO线程转移到工作线程。另外需要注意exchange不是线程安全的。

如何装配一个支持Servlet的Web容器:

    DeploymentInfo servletBuilder = deployment()
            .setClassLoader(ServletServer.class.getClassLoader())
            .setContextPath(MYAPP)
            .setDeploymentName("test.war")
            .addServlets(
                    servlet("MessageServlet", MessageServlet.class)
                            .addInitParam("message", "Hello World")
                            .addMapping("/*"),
                    servlet("MyServlet", MessageServlet.class)
                            .addInitParam("message", "MyServlet")
                            .addMapping("/myservlet"));

    DeploymentManager manager = defaultContainer().addDeployment(servletBuilder);
    manager.deploy();

    HttpHandler servletHandler = manager.start();
    PathHandler path = Handlers.path(Handlers.redirect(MYAPP))
            .addPrefixPath(MYAPP, servletHandler);
    Undertow server = Undertow.builder()
            .addListener(8080, "localhost")
            .setHandler(path)
            .build();
    server.start();

 
Servlet元信息被保存到DeploymentInfo之中,这里的关键类是实现了DeploymentManager接口的DeploymentManagerImpl,在deploy方法中,将Servlet的各种信息,包括servlet,servletContext,listener,filter等都设置或者封装在对应的Handler之中。然后将其中的HttpHandler传入Undertow入口类中,完成了Servlet容器的构造。

Undertow设计之处就充分考虑了对于Websocket的支持,通过HTTP的Upgrage协议,返回101“继续”指令,Web服务器告诉浏览器要切换协议类型,随后进行Websocket的握手协商,进而完全在原有HTTP连接上同Websocket协议通信。Wildfly对于远程EJB调用,也是通过upgrade方式实现的。

目前undertow支持三种连接器:HTTP, HTTPS, 和AJP(apache httpd通信协议),预计很快会加入对SPDY的支持。

在io.undertow.server.handlers包中,有各种功能的Handler来完成不同的工作,Netty的Handler和Codec作用类似。

对于Undertow分析,还有更多的内容,比如Session, Security, Proxy等等,会逐步展开,学习其设计思路。

 

分享到:
评论

相关推荐

    undertow-jaxrs-cdi:带有 Undertow 的独立服务器 - JAX-RS - CDI

    undertow-jaxrs-cdi 带有 Undertow 的独立服务器 - JAX-RS - CDI 例子 跑步: de.mustnotbenamed.quickstart.undertowserver.example.Main 在浏览器中打开或

    undertow-jaxrs-test

    undertow-jaxrs-测试展示如何运行 JAX-RS 应用程序的小示例项目,该应用程序嵌入 Undertow 中,在main()方法中启动。入门只需构建项目: mvn clean install并启动服务器: java -jar target/server-test-1.0-...

    Spring Boot实现Undertow服务器同时支持HTTP2、HTTPS的方法

    考虑如何让Spring Boot应用程序同时支持HTTP和HTTPS两种协议。小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

    undertow-cors-filter:一个过滤器,用于在基于Underwow的服务器(Wildfly,JBOSS EAP)中正确处理CORS标头

    Undertow-cors-filter 一个过滤器,用于在基于Underwow的服务器(Wildfly,JBOSS EAP)中正确处理CORS标头Java EE的过滤器处理似乎有所疏忽,因为当容器配置了容器管理的授权并且尚未(尚未)通过身份验证的用户尝试...

    undertow,高性能无阻塞Web服务器.zip

    Undertow是一个基于非阻塞IO的Java Web服务器。它由几个不同的部分组成:

    spring-boot-undertow

    一分钟的使用寿命) 通过infinispan进行的jpa 2级缓存缓存包装器(用于在服务器集群中进行缓存,以减轻mysql的负载) 弹簧控制器(自定义Web操作) 与io的异步连接器。 百里香模板。 Reactjs示例启动服务器:mvn ...

    使用 Spring Boot 内嵌容器 Undertow创建服务器的方法

    Undertow是一个非常轻量并高性能的web server,它来自 JBoss。支持blocking和non-blocking两种NIO API。接下来通过本文给大家介绍使用Spring Boot 内嵌容器 Undertow创建服务器的方法,感兴趣的朋友一起看看吧

    embedded-lucee-undertow-factory:用于创建托管单个 Lucee 应用程序的嵌入式 undertow servlet 的 Java 库

    嵌入式 Lucee Undertow 服务器 Java 库提供了一个简单的接口,用于构建具有已部署 Lucee 应用程序的嵌入式 Undertow servlet 容器。 用法: import org.lucee // ... LuceeUndertowServer server = new ...

    springboot-demo-helloworld-undertow.rar

    同时也可以修改默认值来满足特定的需求提供了一些大型项目中常见的非功能性特性,如嵌入式服务器、安全、指标,健康检测、外部配置等SpringBoot不是对Spring功能上的增强,而是提供了一种快速使用Spring的方式# ...

    undertow:高性能无阻塞Web服务器

    Undertow是基于非阻塞IO的Java Web服务器。 它由几个不同的部分组成: 支持阻塞和非阻塞IO的核心HTTP服务器 Servlet 4.0实现 符合JSR-356的Web套接字实现 网站: : 问题: : 项目负责人:Flavia Rainone ...

    undertow-sample

    下流样品执行 mvn 全新安装mvn Eclipse:Eclipse安装redis服务器启动Redis:sudo service redis-server restart 运行 App.java 点击

    vaadin-with-plain-undertow

    这通常不是构建Web应用程序的方式,但可能是IoT解决方案中某些资源非常低的服务器的合适基础。 支持Vaadin加载项用法,但依赖于cdn.virit.in,因此用户必须始终具有Internet连接(实际服务器仍然没有)。

    shiro-lang-2.0.0-SNAPSHOT.jar

    对shiro框架在undertow服务器中cookie序列化失败的错误进行修正,主要问题出在shiro-lang包里的ClassUtil加载类的函数

    第一讲-大型互联网项目架构设计实践及架构优化思路.pdf

    3、服务器(tomcat 服务器,undertow 服务调优) 4、jvm 优化(详细讲解分析 jvm 优化方案) 5、数据库优化(连接池优化,数据服务端优化: SQL 语句优化,缓存) 6、多级缓存(堆内存缓存,分布式缓存,openresty +...

    基于 Java Web 项目的 SpringBoot 框架初始化模板,可基于此快速开发毕设等中小型项目

    1、使用 Undertow 服务器替换掉 Tomcat 服务器,无阻塞更适合高并发 2、SaToken 可配置分布式登录 & 认证 & 鉴权 3、AOP 逻辑处理示例 4、自定义注解处理示例 5、验证码分布式校验 6、全局请求拦截器 & 过滤器 7、...

    悟空CRM 9.0(JAVA版)

    将上述打包命令生成的 zip 文件上传到服务器并解压,将目录下的 72crm.sh/72crm.bat 放到解压后的目录下,运行即可 更换启动方式jetty和undertow时,需要更改Application.java中的启动文件 前端部署 安装node.js ...

    WildFly配置指南及管理员手册.pdf

    开发目标为兼容JavaEE7 的开源应用服务器产品,未来会成为 Redhat JBoss EAP 7(即红帽商业应用服务器)的基础版本,WildFly 最主要的特性之一就是新的高性能的称之为 Undertow 的网络服务器,它将取代之前的 JBoss 的...

    springboot基础pdf

    内嵌服务器:支持内嵌Tomcat、Jetty或Undertow等Web服务器,减少了部署的复杂性。 生产就绪特性:具备监控、度量和健康检查等功能,适合生产环境使用。 Spring Boot的开发工具: Spring Tool Suite (STS):这是...

    Spring Boot 2.0培训.pdf

    Tomcat 、Jetty 或者Undertow 等服务器,并且不需要传统的WAR 文件进行部署,也就是说搭建Spring Boot 项目并不需要单独下载Tomcat 等传统的服务器:同时提供通过Maven (或者Grandle )依赖的 starter ,这些...

    alfa-mark:在 clojure 和 common lisp 中对几个 webdev 工具进行基准测试

    服务器:http-kit、jetty9、undertow(通过 immutant2) 模板:selmer、hiccup、enlive、laser 数据库:couchdb (clutch)、couchbase (couchbase-clj)、mysql (korma)、redis (carmine) 常见 Lisp 设置:服务器 ...

Global site tag (gtag.js) - Google Analytics