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

發佈了86 篇原創文章 · 獲贊 56 · 訪問量 44萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章