Jetty 嵌入使用

1. 簡介

Jetty 提供了一個Web服務器和javax.servlet容器,以及對HTTP/2,WebSocket,OSGi,JMX,JNDI,JAAS和許多其他集成的支持。這些組件是開源的,可用於商業用途和發行。

Jetty有一個口號:不要把應用部署到Jetty上,要把Jetty部署到你的應用裏。這句話的意思是把應用打成一個war包部署到Jetty上,不如將Jetty作爲應用的一個組件,它可以實例化並像POJO一樣。換種說法,在嵌入式模塊中運行Jetty意味着把HTTP模塊放到你的應用裏,這種方法比把你的應用放到一個HTTP服務器裏要好。

2. 簡單使用

添加 jetty-server 依賴

<dependency>
  <groupId>org.eclipse.jetty</groupId>
  <artifactId>jetty-server</artifactId>
  <version>9.4.19.v20190610</version>
</dependency>

繼承 AbstractHandler

public class HelloHandler extends AbstractHandler {
    @Override
    public void handle(String s, Request request, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws IOException, ServletException {
        // 設置類型,指定編碼utf8
        httpServletResponse.setContentType("text/html; charset=utf-8");
        // 設置響應狀態嗎
        httpServletResponse.setStatus(HttpServletResponse.SC_OK);
        // 寫響應數據
        httpServletResponse.getWriter().write("<h1>HelloHandler</h1>");
        // 標記請求已處理,handler鏈
        request.setHandled(true);
    }
}

創建Server並啓動

public class JettySimpleTest {
    public static void main(String[] args) throws Exception {
        // 創建服務器
        Server server = new Server(8001);
        // 設置handler
        server.setHandler(new HelloHandler());
        // 啓動應用服務並等待請求
        server.start();
        // 阻塞Jetty server的線程池,直到線程池停止
        server.join();
    }
}

訪問 localhost:8001
簡單使用

3. 嵌入 web 應用(WebAppContext)

WebAppContext是ServletContextHandler 的擴展,使用標準的web應用組件和web.xml,通過web.xml和註解配置servlet,filter和其它特性。

3.1 hello-war

這裏會創建三個項目,然後打成war包。

添加依賴

<dependency>
  <groupId>javax.servlet</groupId>
  <artifactId>javax.servlet-api</artifactId>
  <version>3.1.0</version>
  <scope>provided</scope>
</dependency>

Hello1Servlet

public class Hello1Servlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 設置響應內容類型
        resp.setContentType("text/html");
        // 實際的邏輯是在這裏
        PrintWriter out = resp.getWriter();
        out.println("<h1> hello-war-1 </h1>");
        out.println("<h2> HelloServlet </h2>");
    }
}

web.xml

<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                      http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">
  <display-name>Hello1Servlet</display-name>

  <servlet>
    <servlet-name>Hello1Servlet</servlet-name>
    <servlet-class>com.shpun.Hello1Servlet</servlet-class>
  </servlet>

  <servlet-mapping>
    <servlet-name>Hello1Servlet</servlet-name>
    <url-pattern>/hello1</url-pattern>
  </servlet-mapping>
</web-app>

index.jsp

<html>
<body>
<h2> hello-war-1 index.jsp </h2>
<h2>Hello World!</h2>
</body>
</html>

hello-war-1 項目結構
hello-war-1 項目結構
hello-war-2 和 hello-war-3 項目結構,和hello-war-1一樣。
hello-war-2 和 hello-war-3 項目結構

3.2 jetty-server

添加依賴

<dependency>
  <groupId>org.eclipse.jetty</groupId>
  <artifactId>jetty-server</artifactId>
  <version>9.4.19.v20190610</version>
</dependency>
<dependency>
  <groupId>org.eclipse.jetty</groupId>
  <artifactId>jetty-servlet</artifactId>
  <version>9.4.19.v20190610</version>
</dependency>
<dependency>
  <groupId>org.eclipse.jetty</groupId>
  <artifactId>jetty-webapp</artifactId>
  <version>9.4.19.v20190610</version>
</dependency>
<dependency>
  <groupId>org.eclipse.jetty</groupId>
  <artifactId>jetty-deploy</artifactId>
  <version>9.4.19.v20190610</version>
</dependency>
<dependency>
  <groupId>org.eclipse.jetty</groupId>
  <artifactId>jetty-annotations</artifactId>
  <version>9.4.19.v20190610</version>
</dependency>
<dependency>
  <groupId>org.eclipse.jetty</groupId>
  <artifactId>jetty-servlets</artifactId>
  <version>9.4.19.v20190610</version>
</dependency>
<dependency>
  <groupId>org.eclipse.jetty</groupId>
  <artifactId>apache-jsp</artifactId>
  <version>9.4.19.v20190610</version>
  <type>jar</type>
</dependency>

JettyServer,獲取war包地址,使用 WebAppContext 封裝。然後再添加到 Server 中。

public class JettyServer {

    private final Server server;

    public JettyServer() throws Exception {
        ClassLoader classLoader = getClass().getClassLoader();

        File tempDir = new File("E:\\IDEA_workspace\\jetty-test\\work\\jetty");

        // war包文件路徑
        File helloWar1 = new File("E:\\IDEA_workspace\\jetty-test\\hello-war-1\\target\\hello-war-1.war");
        File helloWar2 = new File("E:\\IDEA_workspace\\jetty-test\\hello-war-2\\target\\hello-war-2.war");
        File helloWar3 = new File("E:\\IDEA_workspace\\jetty-test\\hello-war-3\\target\\hello-war-3.war");

        // Web應用程序上下文處理程序。封裝工作目錄,war包地址,上下文,類加載器
        WebAppContext helloWar1WebAppContext = buildWebApContext(tempDir, helloWar1,"/hello1", classLoader);
        WebAppContext helloWar2WebAppContext = buildWebApContext(tempDir, helloWar2,"/hello2", classLoader);
        WebAppContext helloWar3WebAppContext = buildWebApContext(tempDir, helloWar3,"/hello3", classLoader);

        // 將三個 WebAppContext 添加到一個 HandlerCollection 中。
        // HandlerCollection 是一個包含多個處理器的集合,按照順序依次處理
        HandlerCollection handlerCollection = new HandlerCollection();
        handlerCollection.addHandler(helloWar1WebAppContext);
        handlerCollection.addHandler(helloWar2WebAppContext);
        handlerCollection.addHandler(helloWar3WebAppContext);

        // 將 HandlerCollection 壓縮
        GzipHandler gzipHandler = new GzipHandler();
        gzipHandler.setIncludedMethods("GET", "POST", "PUT", "DELETE");
        gzipHandler.setHandler(handlerCollection);

        // HandlerCollection會依次調用每一個Handler,即使請求已經被處理了
        ContextHandlerCollection contextHandlerCollection = new ContextHandlerCollection();
        contextHandlerCollection.addHandler(gzipHandler);

		// HandlerList順序執行handler
        HandlerList handlerList = new HandlerList();
        handlerList.addHandler(contextHandlerCollection);

        // 初始化 Server
        QueuedThreadPool threadPool = new QueuedThreadPool(30);
        threadPool.setName("Web Server");
        server = new Server(threadPool);
        
        server.setHandler(handlerList);

        // HTTP 配置,這是設置請求和返回頭的大小
        HttpConfiguration httpConfiguration = new HttpConfiguration();
        httpConfiguration.setRequestHeaderSize(16384);
        httpConfiguration.setResponseHeaderSize(16384);

        // 配置連接器
        // Connector是Jetty中可以直接接受客戶端連接的抽象,一個Connector監聽Jetty服務器的一個端口
        ServerConnector serverConnector = new ServerConnector(server, new HttpConnectionFactory(httpConfiguration));
        // 多網卡時使用
        //serverConnector.setHost("192.168.1.2");
        serverConnector.setPort(8001);
        // 設置連接的最大空閒時間
        serverConnector.setIdleTimeout(30000L * 2);

        server.addConnector(serverConnector);

        // 解析jsp方式1:啓用基於註釋的配置,以確保正確初始化了jsp容器
        Configuration.ClassList classList = Configuration.ClassList.setServerDefault(server);
        classList.addBefore(JettyWebXmlConfiguration.class.getName(), AnnotationConfiguration.class.getName());
    }

    private void start() throws Exception {
        server.start();
    }

    public static void main( String[] args ) throws Exception {
        JettyServer jettyServer = new JettyServer();
        jettyServer.start();
    }

    /**
     * 構建 WebAppContext
     * @param tempDir
     * @param war
     * @param contextPath
     * @param classLoader
     * @return
     * @throws IOException
     */
    private WebAppContext buildWebApContext(File tempDir, File war, String contextPath, ClassLoader classLoader) throws IOException {
        WebAppContext webappContext = new WebAppContext(war.getPath(), contextPath);
        webappContext.setContextPath(contextPath);
        webappContext.setDisplayName(contextPath);

        File workDir = new File(tempDir, war.getName());
        webappContext.setTempDirectory(workDir);
        webappContext.setClassLoader(new WebAppClassLoader(classLoader, webappContext));

        // 解析jsp方式2:
        //webappContext.addBean(new JspStarter(webappContext));
        //webappContext.addServlet(JettyJspServlet.class, "*.jsp");

        return webappContext;
    }
}

Jetty解析jsp目前發現有兩種方式
一種是在 Server 啓動前,添加

Configuration.ClassList classList = Configuration.ClassList.setServerDefault(server);
classList.addBefore(JettyWebXmlConfiguration.class.getName(), AnnotationConfiguration.class.getName());

一種是在創建 WebAppContext 時添加

webappContext.addBean(new JspStarter(webappContext));
webappContext.addServlet(JettyJspServlet.class, "*.jsp");

還需要 JspStarter

public class JspStarter extends AbstractLifeCycle implements ServletContextHandler.ServletContainerInitializerCaller {
    JettyJasperInitializer sci;
    ServletContextHandler context;

    public JspStarter(ServletContextHandler context) {
        this.sci = new JettyJasperInitializer();
        this.context = context;
        this.context.setAttribute("org.apache.tomcat.JarScanner", new StandardJarScanner());
    }

    @Override
    protected void doStart() throws Exception {
        ClassLoader old = Thread.currentThread().getContextClassLoader();
        Thread.currentThread().setContextClassLoader(context.getClassLoader());
        try {
            sci.onStartup(null, context.getServletContext());
            super.doStart();
        } finally {
            Thread.currentThread().setContextClassLoader(old);
        }
    }
}

測試,三個war包都可正常部署訪問
hello1
hello1/hello1
hello2/hello2
hello3/hello3

參考:
Jetty 官方文檔
Jetty 官方文檔 - 嵌入Jetty
Jetty篇教程 之Jetty 嵌入式服務器
idea下,Jetty採用main方法啓動web項目
內嵌式jetty服務器支持jsp
jetty介紹之handler
Jetty Connector學習筆記

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章