高性能非阻塞 Web 服务器 Undertow

Undertow 简介

Undertow是一个用java编写的、灵活的、高性能的Web服务器,提供基于NIO的阻塞和非阻塞API。

Undertow的架构是组合式的,可以通过组合各种小型的目的单一的处理程序来构建Web服务器。所以可以很灵活地的选择完整的Java EE servlet 3.1容器或初级非阻塞程序处理。

Undertow的设计是可以完全可嵌入的,具有简单易用的编译接口。Undertow的生命周期完全由嵌入的应用程序控制。

Undertow是JBoss赞助的一个Web服务器,是Wildfly应用程序服务器中的默认Web服务器。

Undertow的特点:

  • 非常轻量级,Undertow核心瓶子在1Mb以下。它在运行时也是轻量级的,有一个简单的嵌入式服务器使用少于4Mb的堆空间。
  • 支持HTTP升级,允许多个协议通过HTTP端口进行多路复用。
  • 提供对Web套接字的全面支持,包括JSR-356支持。
  • 提供对Servlet 3.1的支持,包括对嵌入式servlet的支持。还可以在同一部署中混合Servlet和本机Undertow非阻塞处理程序。
  • 可以嵌入在应用程序中或独立运行,只需几行代码。
  • 通过将处理程序链接在一起来配置Undertow服务器。它可以对各种功能进行配置,方便灵活。

内核

通常情况下有两种方法来引导Undertow。 第一种也是最简单的是使用API即io.undertow.Undertow。第二种方式是直接使用XNIO和Undertow侦听器类来组装服务器。第二种方法需要更多的代码,但是给出更多的灵活性。大多数情况下,通过API构建就行了。

重点需要理解,Undertow中没有任何容器的概念。Undertow应用程序是由多个处理程序组合而来的,它通过嵌入的方式来管理所有这些处理程序的生命周期。这是一个专门的设计决定,以便给予嵌入应用更多的控制权。当然这种设计会产生一个问题,如果你有处理程序,需要在服务器停止时清理一下它使用的资源。

代码示例,一个使用Async IO的简单Hello World服务器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class HelloWorldServer {
public static void main(final String[] args) {
Undertow server = Undertow.builder()
.addHttpListener(8080, "localhost")
.setHandler(new 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();
}
}

上面的示例启动一个简单的服务器,它向所有请求返回Hello World。服务器将监听端口8080上的本地主机地址,直到调用server.stop()方法。当请求到达时,它们将由处理程序链中的第一个(也是唯一的)处理程序处理,在这种情况下,只需设置一个标题并写入一些内容。

手动配置一个服务器

如果不想使用构建器API,则需要执行以下几个步骤来创建服务器:

  • 创建XNIO Worker。 此工作程序管理服务器的IO和工作线程。
  • 创建XNIO SSL实例(可选,仅在使用HTTPS时需要)
  • 创建相关Undertow侦听器类的实例
  • 使用XNIO打开服务器套接字并设置其接受侦听器

HTTP,HTTPS和AJP侦听器的代码如下所示:

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
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);
if (listener.type == ListenerType.AJP) {
AjpOpenListener openListener = new AjpOpenListener(buffers, serverOptions, 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();
} else if (listener.type == ListenerType.HTTP) {
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();
} else if (listener.type == ListenerType.HTTPS){
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);
XnioSsl xnioSsl;
if(listener.sslContext != null) {
xnioSsl = new JsseXnioSsl(xnio, OptionMap.create(Options.USE_DIRECT_BUFFERS, true), listener.sslContext);
} else {
xnioSsl = xnio.getSslProvider(listener.keyManagers, listener.trustManagers, OptionMap.create(Options.USE_DIRECT_BUFFERS, true));
}
AcceptingChannel <SslConnection> sslServer = xnioSsl.createSslConnectionServer(worker, new InetSocketAddress(Inet4Address.getByName(listener.host), listener.port), (ChannelListener) acceptListener, socketOptions);
sslServer.resumeAccepts();
}

如你所见,上述代码内容不少,而不仅仅是使用构建器,但它提供了一些灵活性:

  • 完全控制所有选项
  • 能够为每个侦听器使用不同的缓冲池和工作程序
  • XnioWorker实例可以在不同的服务器实例之间共享
  • 缓冲池可以在不同的服务器实例之间共享
  • 监听器可以给予不同的根处理程序

在大多数情况下,这个级别的控制不是必需的,最好是简单地使用构建器API:io.undertow.Undertow。

创建Servlet的部署

有关如何创建Servlet部署的一个简单示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
DeploymentInfo servletBuilder = Servlets.deployment()
.setClassLoader(ServletServer.class.getClassLoader())
.setContextPath("/myapp")
.setDeploymentName("test.war")
.addServlets(
Servlets.servlet("MessageServlet", MessageServlet.class)
.addInitParam("message", "Hello World")
.addMapping("/*"),
Servlets.servlet("MyServlet", MessageServlet.class)
.addInitParam("message", "MyServlet")
.addMapping("/myservlet"));
DeploymentManager manager = Servlets.defaultContainer().addDeployment(servletBuilder);
manager.deploy();
PathHandler path = Handlers.path(Handlers.redirect("/myapp"))
.addPrefixPath("/myapp", manager.start());
Undertow server = Undertow.builder()
.addHttpListener(8080, "localhost")
.setHandler(path)
.build();
server.start();

基本过程是创建一个DeploymentInfo结构(通过使用io.undertow.servlets.Servlets中的方法),将需要的Servlet和其他信息添加到此结构,然后将其部署到Servlet容器。

部署之后,就可以在DeploymentManager上调用start()方法,该方法返回一个HttpHandler,然后可以安装在Undertow服务器处理程序链接中。

DeploymentInfo结构有很多数据,并且大部分数据直接对应于web.xml中的数据。

JSP

JSP可以通过使用Jastow项目在Undertow中使用,Jastow项目是Apache Jasper的一个分支,用于Undertow。

Jasper通过Servlet提供了所有的功能,因此可以通过将JSP servlet添加到*.jsp映射来将其添加到标准Undertow servlet部署中。

JSP还需要一些额外的上下文参数,Jastow提供了一个帮助类来设置它们。

下面显示了如何设置JSP部署的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
final PathHandler servletPath = new PathHandler();
final ServletContainer container = ServletContainer.Factory.newInstance();
DeploymentInfo builder = new DeploymentInfo()
.setClassLoader(SimpleJspTestCase.class.getClassLoader())
.setContextPath("/servletContext")
.setClassIntrospecter(TestClassIntrospector.INSTANCE)
.setDeploymentName("servletContext.war")
.setResourceManager(new TestResourceLoader(SimpleJspTestCase.class))
.addServlet(JspServletBuilder.createServlet("Default Jsp Servlet", "*.jsp"));
JspServletBuilder.setupDeployment(builder, new HashMap<String, JspPropertyGroup>(), new HashMap<String, TagLibraryInfo>(), new MyInstanceManager());
DeploymentManager manager = container.addDeployment(builder);
manager.deploy();
servletPath.addPrefixPath(builder.getContextPath(), manager.start());

这里JSP标签是使用Jasper InstanceManager接口的实例创建的。如果你不需要注入标签,那么这个接口可以直接使用反射创建一个新的实例。

Undertow.js

Undertow.js是一个独立的项目,使得使用Undertow编写服务器端Javascript变得很容易。 它支持以下:

  • Java EE integration, including dependency injection
  • REST
  • Templates
  • Declarative security
  • Filters
  • Websockets
  • Hot reload
  • JDBC

首先,您需要在应用程序中包含最新的Undertow.js。如果你使用Wildfly 10就不用了,因为Wildfly 10提供了这个功能。如果你使用maven,你可以在pom.xml中包含以下内容:

1
2
3
4
5
<dependency>
        <groupId> io.undertow.js </ groupId>
        <artifactId> undertow-js </ artifactId>
        <version> 1.0.0.Alpha3 </ version>
</ dependency>

您也可以从链接:http://mvnrepository.com/artifact/io.undertow.js/undertow-js下载jars。

创建一个文件WEB-INF/undertow-scripts.conf。在此文件中,列出服务器端JavaScript文件,每行一个,这些文件将按指定的顺序执行。

即使服务器JavaScript文件位于Web上下文中,也不允许直接访问他们。如果用户请求服务器端JS文件,则将返回404。

我们现在可以创建一个简单的端点。创建一个javascript文件,将其添加到undertow-scripts.conf并添加以下内容:

1
2
3
4
5
6
$ undertow
    .onGet(“/hello”,
        {headers:{“content-type”:“text/plain”}},
        [function$ exchange{
            return“Hello World”;
        }])

访问部署中的 http://localhost:8080/hello 路径现在应该返回Hello World响应。

更多内容可以访问官网:

http://undertow.io/undertow-docs/undertow-docs-1.3.0/index.html

posted @ 2019-08-08 13:56  星朝  阅读(2457)  评论(0编辑  收藏  举报