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