高性能非阻塞 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() { 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