Jetty使用教程(四:21-22)—Jetty開發指南


二十一、嵌入式開發

21.1 Jetty嵌入式開發HelloWorld

  本章節將提供一些教程,通過Jetty API快速開發嵌入式代碼

21.1.1 下載Jetty的jar包

  Jetty目前已經把所有功能分解成小的jar包,用戶可以根據需要選擇合適的jar包,來實現需要的功能。通常建議用戶使用maven等管理工具來管理jar包,然而本教程使用一個包含所有功能的合集jar包來演示,用戶可以使用curl或者瀏覽器下載jetty-all.jar包。

jetty-all.jar下載地址:http://central.maven.org/maven2/org/eclipse/jetty/aggregate/jetty-all/9.3.11.v20160721/jetty-all-9.3.11.v20160721-uber.jar

注意:

  Maven中央倉庫已經開始拒絕使用wget命令獲得資源(因為一些工具的濫用),所以Maven中央倉庫建議使用curl命令來獲得jar包。

使用curl 命令如下(Windows用戶可以將上面的地址復制到瀏覽器來下載):

> mkdir Demo
> cd Demo > curl -o jetty-all-uber.jar http://central.maven.org/maven2/org/eclipse/jetty/aggregate/jetty-all/9.3.11.v20160721/jetty-all-9.3.11.v20160721-uber.jar

21.1.2 寫一個HelloWorld例子

   這個Jetty嵌入式教程章節包含很多通過Jetty API的例子,這個教程在main方法里運行一個Helloworld的handler來實現一個服務,可以自己寫或者復制以下代碼: 

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.AbstractHandler;

public class HelloWorld extends AbstractHandler {
    @Override
    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
            throws IOException, ServletException {
        // 聲明response的編碼和文件類型
        response.setContentType("text/html; charset=utf-8");

        // 聲明返回狀態碼
        response.setStatus(HttpServletResponse.SC_OK);

        // 請求的返回值
        response.getWriter().println("<h1>Hello World</h1>");

        // 通知Jettyrequest使用此處理器處理請求
        baseRequest.setHandled(true);
    }

    public static void main(String[] args) throws Exception {
        //創建一個應用服務監聽8080端口
        Server server = new Server(8080);
        server.setHandler(new HelloWorld());

        //啟動應用服務並等待請求
        server.start();
        server.join();
    }
}
View Code

21.1.3 編譯helloworld例子

  使用如下命令編譯生成class文件 

> mkdir classes
> javac -d classes -cp jetty-all-uber.jar HelloWorld.java

21.1.4 運行服務

  使用如下命令運行服務,啟動服務器。(建議使用開發工具進行學習,如eclipse)

> java -cp classes:jetty-all-uber.jar org.eclipse.jetty.embedded.HelloWorld

  運行成功后會輸出如下內容:

2016-09-02 11:44:48.851:INFO::main: Logging initialized @214ms
2016-09-02 11:44:48.915:INFO:oejs.Server:main: jetty-9.3.z-SNAPSHOT 2016-09-02 11:44:49.049:INFO:oejs.AbstractConnector:main: Started ServerConnector@5436b246{HTTP/1.1,[http/1.1]}{0.0.0.0:8080} 2016-09-02 11:44:49.050:INFO:oejs.Server:main: Started @417ms

   此時可以訪問http://localhost:8080 ,查看結果:

21.1.5 后續步驟

  為了更好的學習Jetty,可以按如下步驟進行:

  • 跟着例子學習嵌入式Jetty開發比直接看Jetty API要好
  • 把Jetty javadoc完全看一遍
  • 使用Maven來管理所有jar包和依賴

21.2 Jetty的嵌入式開發 

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

  這個教程將一步一步的教你如何通過簡單的Jetty服務運行多個web應用。大多數例子的源代碼都是Jetty項目的一部分。

21.2.1 概述

  本教程中嵌入一個Jetty的典型步驟如下:

  • 創建一個server實例
  • 新增/配置連接
  • 新增/配置處理程序,或者Contexts,或者Servlets
  • 啟動Server
  • 等待連接或者在當前線程上做一些其他的事

21.2.2 創建一個server實例

  下面的代碼實例化並運行了一個Jetty Server

import org.eclipse.jetty.server.Server;

/**
 * The simplest possible Jetty server.
 */
public class SimplestServer
{
    public static void main( String[] args ) throws Exception
    {
        Server server = new Server(8080);
        server.start();
        server.dumpStdErr();
        server.join();
    }
}
View Code

  在8080端口運行了一個HTTP服務,因為沒有處理程序,所以這不是一個有效的server,所有請求將會返回404錯誤信息。

21.2.3 使用處理器處理請求

  為了針對請求產生一個相應,Jetty要求用戶為服務創建一個處理請求的處理器,一個處理器可以做的工作有:

  • 檢測或修改一個請求
  • 一個完整的HTTP響應
  • 處理轉發(詳見:HandlerWrapper)
  • 處理轉發到多個其它處理器上(詳見:HandlerCollection)

21.2.3.1 處理器的HelloWorld

  接下來的代碼HelloHandler.java,表示一個簡單的處理程序 

//
//  ========================================================================
//  Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
//  ------------------------------------------------------------------------
//  All rights reserved. This program and the accompanying materials
//  are made available under the terms of the Eclipse Public License v1.0
//  and Apache License v2.0 which accompanies this distribution.
//
//      The Eclipse Public License is available at
//      http://www.eclipse.org/legal/epl-v10.html
//
//      The Apache License v2.0 is available at
//      http://www.opensource.org/licenses/apache2.0.php
//
//  You may elect to redistribute this code under either of these licenses.
//  ========================================================================
//

package org.eclipse.jetty.embedded;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.handler.AbstractHandler;

public class HelloHandler extends AbstractHandler
{
    final String greeting;
    final String body;

    public HelloHandler()
    {
        this("Hello World");
    }

    public HelloHandler( String greeting )
    {
        this(greeting, null);
    }

    public HelloHandler( String greeting, String body )
    {
        this.greeting = greeting;
        this.body = body;
    }

    public void handle( String target,
                        Request baseRequest,
                        HttpServletRequest request,
                        HttpServletResponse response ) throws IOException,
                                                      ServletException
    {
        response.setContentType("text/html; charset=utf-8");
        response.setStatus(HttpServletResponse.SC_OK);

        PrintWriter out = response.getWriter();

        out.println("<h1>" + greeting + "</h1>");
        if (body != null)
        {
            out.println(body);
        }

        baseRequest.setHandled(true);
    }
}
View Code

 

  傳入到處理程序方法handle的參數有:

  • target - 目標請求,可以是一個URI或者是一個轉發到這的處理器的名字
  • baseRequest  - Jetty自己的沒有被包裝的請求,一個可變的Jetty請求對象
  • request  - 被filter或者servlet包裝的請求,一個不可變的Jetty請求對象
  • response  - 響應,可能被filter或者servlet包裝過

  處理程序會設置狀態碼,content-type,並調用write向response輸出內容。

21.2.3.2 運行HelloWorldHandler

  為了讓處理器處理HTTP請求,你必須將處理程序添加到server實例上,接下來的代碼OneHandler.java展示了一個serve實例如何調用處理出現來處理請求: 

//
//  ========================================================================
//  Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
//  ------------------------------------------------------------------------
//  All rights reserved. This program and the accompanying materials
//  are made available under the terms of the Eclipse Public License v1.0
//  and Apache License v2.0 which accompanies this distribution.
//
//      The Eclipse Public License is available at
//      http://www.eclipse.org/legal/epl-v10.html
//
//      The Apache License v2.0 is available at
//      http://www.opensource.org/licenses/apache2.0.php
//
//  You may elect to redistribute this code under either of these licenses.
//  ========================================================================
//

package org.eclipse.jetty.embedded;

import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.Server;

public class OneHandler
{
    public static void main( String[] args ) throws Exception
    {
        Server server = new Server(8080);
        server.setHandler(new HelloHandler());

        server.start();
        server.join();
    }
}
View Code

 

  一個或多個處理器將處理Jetty所有的請求。一些處理器會轉發請求到其他處理器(例如: ContextHandlerCollection 會根據路徑選擇匹配的ContextHandler),另一些將根據邏輯判斷來生成相應的響應(例如:ServletHandler 將把request轉發到servlet),還有的處理器將處理和請求無關的操作(例如:RequestLogHandler 或者StatisticsHandler)。

  后面的章節將介紹如何在切面調用一個處理器,你可以到org.eclipse.jetty.server.handler 包下看看當前可用的處理器。

21.2.3.3 處理器的集合及封裝

  復雜的請求可以由多個處理器來完成,你可以通過不同的方式把它們結合起來,Jetty有幾個HandlerContainer接口的實現:

  HandlerCollection:

    一個包含多個處理器的集合,按照順序依次處理。這在響應請求的同時進行統計和日志記錄特別有用。

  HandlerList

    一個包含多個處理器的集合,按照順序處理,與HandlerCollection不同的是,當處理器出現異常或者響應被提交或者request.isHandled()方法返回true時,后續將不再被調用。一般用來匹配不同的主機,用來進行不同的處理。

  HandlerWrapper

    一個處理器的基類用來進行切面編程。例如,一個標准的web應用會由context,session,安全和servlet處理器構成。

  ContextHandlerCollection

    一個特殊的HandlerCollection,使用完整的URI前綴來選擇匹配的ContextHandler對請求進行處理。

21.2.3.4 處理器的作用域

  在Jetty中,很多標准的服務器會繼承HandlerWrappers,用來進行鏈式處理,比如從請求從ContextHandler 到SessionHandler ,再到SecurityHandler 最后到ServletHandler。然而,因為servlet規范的性質,外部處理器不能在沒有調用內部處理器的時候得知內部處理器的信息,例如:ContextHandler調用應用監聽請求的context時,它必須已經知道ServletHandler將要轉發請求到哪個servlet,以確保servletPath方法返回正確的值。

  ScopedHandler是HandlerWrapper 一個抽象實現類,用來提供鏈式調用時作用域的支持。例如:一個ServletHandler內嵌在一個ContextHandler中,方法嵌套調用的順序為:

  Server.handle(...)
    ContextHandler.doScope(...)
      ServletHandler.doScope(...)
        ContextHandler.doHandle(...)
          ServletHandler.doHandle(...)
            SomeServlet.service(...)

  因此ContextHandler處理請求時,它內嵌的ServletHandler已經建立了。

21.2.3.5 資源處理器

  下面這個FileServer例子,展示了你可以使用ResourceHandler 來處理當前工作路徑下的靜態資源。

import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.server.handler.HandlerList;
import org.eclipse.jetty.server.handler.ResourceHandler;
import org.eclipse.jetty.server.handler.gzip.GzipHandler;

public class FileServer {
    public static void main(String[] args) throws Exception {
        //創建一個基礎的Jetty服務監聽8080端口
        //若設置端口為0,則隨機一個可用的端口,端口信息可以從日志中獲取也可以寫測試方法獲取
        Server server = new Server(8080);

        //創建一個ResourceHandler,它處理請求的方式是提供一個資源文件
        //這是一個Jetty內置的處理器,所以它非常適合與其他處理器構成一個處理鏈
        ResourceHandler resource_handler = new ResourceHandler();
        //配置ResourceHandler,設置哪個文件應該被提供給請求方
        //這個例子里,配置的是當前路徑下的文件,但是實際上可以配置長任何jvm能訪問到的地方
        resource_handler.setDirectoriesListed(true);
        resource_handler.setWelcomeFiles(new String[] { "index.html" });
        resource_handler.setResourceBase(".");

        // 將resource_handler添加到GzipHandler中,然后將GzipHandler提供給Server
        GzipHandler gzip = new GzipHandler();
        server.setHandler(gzip);
        HandlerList handlers = new HandlerList();
        handlers.setHandlers(new Handler[] { resource_handler, new DefaultHandler() });
        gzip.setHandler(handlers);

        server.start();
        server.join();
    }
}
View Code

  請注意,例子中的HandlerList 包含ResourceHandler 和DefaultHandler,DefaultHandler 將會為不能匹配到資源的生成一個格式良好的404回應。

21.2.4 嵌入式的連接

  在前面的例子中,通過在Server實例通過傳入一個端口號,讓Server創建一個默認的連接來監聽指定的端口。然而,通常編寫嵌入式代碼希望顯式的配置一個或者多個連接器。

21.2.4.1 一個連接

  下面的例子, OneConnector.java,實例化、配置、新增一個HTTP連接到server上

import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;

/**
 * 有一個連接的Server
 */
public class OneConnector {
    public static void main(String[] args) throws Exception {
        Server server = new Server();

        // 創建一個HTTP的連接,配置監聽主機,端口,以及超時時間
        ServerConnector http = new ServerConnector(server);
        http.setHost("localhost");
        http.setPort(8080);
        http.setIdleTimeout(30000);

        // 將此連接添加到Server
        server.addConnector(http);

        // 設置一個處理器
        server.setHandler(new HelloHandler());

        // 啟動Server
        server.start();
        server.join();
    }
}

  在這個例子中,連接將處理http的請求,這個也是默認的ServerConnector連接類。

21.2.4.2 多個連接

  當配置多個連接(例如:HTTP和HTTPS),它們可能是共同分享HTTP設置的參數。為了顯式的配置ServerConnector 需要使用ConnectionFactory ,並提供一個常用的HTTP配置。

  下面這個 ManyConnectors例子,給一個Server配置了兩個ServerConnector ,http連接有一個HTTPConnectionFactory 實例,https連接有一個SslConnectionFactory 實例在HttpConnectionFactory里面。兩個HttpConnectionFactory 都是基於同一個HttpConfiguration實例,然而https使用包裝過的配置信息,因此SecureRequestCustomizer 可以被添加進去。

import java.io.File;
import java.io.FileNotFoundException;

import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.SecureRequestCustomizer;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.SslConnectionFactory;
import org.eclipse.jetty.util.ssl.SslContextFactory;

/**
 * 一個有多個連接的Jetty例子
 */
public class ManyConnectors {
    public static void main(String[] args) throws Exception {

        //這個例子會展示如何配置SSL,我們需要一個秘鑰庫,會在jetty.home下面找
        String jettyDistKeystore = "../../jetty-distribution/target/distribution/demo-base/etc/keystore";
        String keystorePath = System.getProperty("example.keystore", jettyDistKeystore);
        File keystoreFile = new File(keystorePath);
        if (!keystoreFile.exists()) {
            throw new FileNotFoundException(keystoreFile.getAbsolutePath());
        }

        //創建一個不指定端口的Server,隨后將直接配置連接和端口
        Server server = new Server();
        
        //HTTP配置
        //HttpConfiguration是一個配置http和https屬性的集合,默認的配置是http的
        //帶secured的ui配置https的,
        HttpConfiguration http_config = new HttpConfiguration();
        http_config.setSecureScheme("https");
        http_config.setSecurePort(8443);
        http_config.setOutputBufferSize(32768);
        
        //HTTP連接
        //第一個創建的連接是http連接,傳入剛才創建的配置信息,也可以重新設置新的配置,如端口,超時等
        ServerConnector http = new ServerConnector(server, new HttpConnectionFactory(http_config));
        http.setPort(8080);
        http.setIdleTimeout(30000);

        //使用SslContextFactory來創建http
        //SSL需要一個證書,所以我們配置一個工廠來獲得需要的東西
        SslContextFactory sslContextFactory = new SslContextFactory();
        sslContextFactory.setKeyStorePath(keystoreFile.getAbsolutePath());
        sslContextFactory.setKeyStorePassword("OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4");
        sslContextFactory.setKeyManagerPassword("OBF:1u2u1wml1z7s1z7a1wnl1u2g");

        //HTTPS的配置類
        HttpConfiguration https_config = new HttpConfiguration(http_config);
        SecureRequestCustomizer src = new SecureRequestCustomizer();
        src.setStsMaxAge(2000);
        src.setStsIncludeSubDomains(true);
        https_config.addCustomizer(src);

        //HTTPS連接
        //創建第二個連接,
        ServerConnector https = new ServerConnector(server,
                new SslConnectionFactory(sslContextFactory, HttpVersion.HTTP_1_1.asString()),
                new HttpConnectionFactory(https_config));
        https.setPort(8443);
        https.setIdleTimeout(500000);

        // 設置一個連接的集合
        server.setConnectors(new Connector[] { http, https });

        // 設置一個處理器
        server.setHandler(new HelloHandler());

        // 啟動服務
        server.start();
        server.join();
    }
}
View Code

21.2.5 嵌入式的Servlets

  Servlets是處理邏輯和HTTP請求的標准方式。Servlets 類似於Jetty的處理器,request對象不可變且不能被修改。在Jetty中servlet將有ServletHandler進行負責調用。它使用標准的路徑匹配一個請求到servlet,設置請求的路徑和請求內容,將請求傳遞到servlet,或者通過過濾器產生一個響應。

  下面這個MinimalServlets例子,創建一個ServletHandler 實例,並配置一個簡單的servlet

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler;

public class MinimalServlets {
    public static void main(String[] args) throws Exception {
        
        Server server = new Server(8080);
        //ServletHandler通過一個servlet創建了一個非常簡單的context處理器
        //這個處理器需要在Server上注冊
        ServletHandler handler = new ServletHandler();
        server.setHandler(handler);

        //傳入能匹配到這個servlet的路徑
        //提示:這是一個未經處理的servlet,沒有通過web.xml或@WebServlet注解或其他方式配置
        handler.addServletWithMapping(HelloServlet.class, "/*");

        server.start();
        server.join();
    }

    @SuppressWarnings("serial")
    public static class HelloServlet extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {
            response.setContentType("text/html");
            response.setStatus(HttpServletResponse.SC_OK);
            response.getWriter().println("<h1>Hello from HelloServlet</h1>");
        }
    }
}
View Code

21.2.6 嵌入式Context

  ContextHandler是一種ScopedHandler,只用來響應配匹配指定URI前綴的請求,

  • 一個Classloader 當在一個請求作用域里的時候處理當前線程的請求
  • 一個ServletContext有小屬性的集合
  • 通過ServletContext獲得的初始化參數的集合
  • 通過ServletContext 獲得的基礎資源的集合
  • 一個虛擬主機名稱的集合 

  下面這個OneContext例子,包含一個HelloHandler處理器,用來處理指定路徑下的請求

import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.ContextHandler;

public class OneContext {
    public static void main(String[] args) throws Exception {
        Server server = new Server(8080);

        //在/hello路徑上增加一個處理器
        ContextHandler context = new ContextHandler();
        context.setContextPath("/hello");
        context.setHandler(new HelloHandler());

        //可以通過http://localhost:8080/hello訪問
        server.setHandler(context);

        server.start();
        server.join();
    }
}

  當有很多有效的contexts 時,可以創建一個ContextHandlerCollection 集合存儲這些處理器,下面的這個ManyContexts例子展示多個context的使用:

import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;

public class ManyContexts
{
    public static void main( String[] args ) throws Exception
    {
        Server server = new Server(8080);

        ContextHandler context = new ContextHandler("/");
        context.setContextPath("/");
        context.setHandler(new HelloHandler("Root Hello"));

        ContextHandler contextFR = new ContextHandler("/fr");
        contextFR.setHandler(new HelloHandler("Bonjoir"));

        ContextHandler contextIT = new ContextHandler("/it");
        contextIT.setHandler(new HelloHandler("Bongiorno"));

        ContextHandler contextV = new ContextHandler("/");
        contextV.setVirtualHosts(new String[] { "127.0.0.2" });
        contextV.setHandler(new HelloHandler("Virtual Hello"));

        ContextHandlerCollection contexts = new ContextHandlerCollection();
        contexts.setHandlers(new Handler[] { context, contextFR, contextIT,
                contextV });

        server.setHandler(contexts);

        server.start();
        server.join();
    }
}
View Code

21.2.7 嵌入式ServletContexts

  ServletContextHandler是一種特殊的ContextHandler,它可以支持標准的sessions 和Servlets。下面例子的OneServletContext 實例化了一個 DefaultServlet為/tmp/ 和DumpServlet 提供靜態資源服務,DumpServlet 創建session並且應答請求信息

import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.DefaultServlet;
import org.eclipse.jetty.servlet.ServletContextHandler;

public class OneServletContext {
    public static void main(String[] args) throws Exception {
        Server server = new Server(8080);

        ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
        context.setContextPath("/");
        context.setResourceBase(System.getProperty("java.io.tmpdir"));
        server.setHandler(context);

        // 增加一個 dump servlet
        context.addServlet(DumpServlet.class, "/dump/*");
        // 增加一個默認的servlet
        context.addServlet(DefaultServlet.class, "/");

        server.start();
        server.join();
    }
}
View Code

21.2.8 嵌入一個web應用

  WebAppContext是ServletContextHandler 的擴展,使用標准的web應用組件和web.xml,通過web.xml和注解配置servlet,filter和其它特性。下面這個OneWebApp例子部署了一個簡單的web應用。Web應用程序可以使用容器提供的資源,在這種情況下需要一個LoginService並配置:

import java.io.File;
import java.lang.management.ManagementFactory;

import org.eclipse.jetty.jmx.MBeanContainer;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.AllowSymLinkAliasChecker;
import org.eclipse.jetty.webapp.WebAppContext;

public class OneWebApp {
    public static void main(String[] args) throws Exception {
        Server server = new Server(8080);

        // 設置 JMX
        MBeanContainer mbContainer = new MBeanContainer(ManagementFactory.getPlatformMBeanServer());
        server.addBean(mbContainer);

        //下面這個web應用是一個完整的web應用,在這個例子里設置/為根路徑,web應用所有的配置都是有效的,
        WebAppContext webapp = new WebAppContext();
        webapp.setContextPath("/");
        File warFile = new File("../../jetty-distribution/target/distribution/test/webapps/test/");
        webapp.setWar(warFile.getAbsolutePath());
        webapp.addAliasCheck(new AllowSymLinkAliasChecker());

        //將web應用設置到server里
        server.setHandler(webapp);

        server.start();
        server.join();
    }
}
View Code

21.2.9 使用Jetty XML配置

  通常配置Jetty實例是通過配置jetty.xml和其它關聯的配置文件,然而Jetty 配置信息都可以用簡單的代碼進行配置,下面的例子將從配置文件中獲得相關信息:

  • jetty.xml
  • jetty-jmx.xml
  • jetty-http.xml
  • jetty-https.xml
  • jetty-deploy.xml
  • jetty-stats.xml
  • jetty-requestlog.xml
  • jetty-lowresources.xml
  • test-realm.xml
import java.io.File;
import java.io.FileNotFoundException;
import java.lang.management.ManagementFactory;

import org.eclipse.jetty.deploy.DeploymentManager;
import org.eclipse.jetty.deploy.PropertiesConfigurationManager;
import org.eclipse.jetty.deploy.bindings.DebugListenerBinding;
import org.eclipse.jetty.deploy.providers.WebAppProvider;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.jmx.MBeanContainer;
import org.eclipse.jetty.rewrite.handler.CompactPathRule;
import org.eclipse.jetty.rewrite.handler.RewriteHandler;
import org.eclipse.jetty.security.HashLoginService;
import org.eclipse.jetty.server.ConnectorStatistics;
import org.eclipse.jetty.server.DebugListener;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.LowResourceMonitor;
import org.eclipse.jetty.server.NCSARequestLog;
import org.eclipse.jetty.server.SecureRequestCustomizer;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.SslConnectionFactory;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.server.handler.RequestLogHandler;
import org.eclipse.jetty.server.handler.StatisticsHandler;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
import org.eclipse.jetty.webapp.Configuration;

/**
 * Starts the Jetty Distribution's demo-base directory using entirely
 * embedded jetty techniques.
 */
public class LikeJettyXml
{
    public static void main( String[] args ) throws Exception
    {
        // Path to as-built jetty-distribution directory
        String jettyHomeBuild = "../../jetty-distribution/target/distribution";

        // Find jetty home and base directories
        String homePath = System.getProperty("jetty.home", jettyHomeBuild);
        File start_jar = new File(homePath,"start.jar");
        if (!start_jar.exists())
        {
            homePath = jettyHomeBuild = "jetty-distribution/target/distribution";
            start_jar = new File(homePath,"start.jar");
            if (!start_jar.exists())
                throw new FileNotFoundException(start_jar.toString());
        }

        File homeDir = new File(homePath);

        String basePath = System.getProperty("jetty.base", homeDir + "/demo-base");
        File baseDir = new File(basePath);
        if(!baseDir.exists())
        {
            throw new FileNotFoundException(baseDir.getAbsolutePath());
        }

        // Configure jetty.home and jetty.base system properties
        String jetty_home = homeDir.getAbsolutePath();
        String jetty_base = baseDir.getAbsolutePath();
        System.setProperty("jetty.home", jetty_home);
        System.setProperty("jetty.base", jetty_base);


        // === jetty.xml ===
        // Setup Threadpool
        QueuedThreadPool threadPool = new QueuedThreadPool();
        threadPool.setMaxThreads(500);

        // Server
        Server server = new Server(threadPool);

        // Scheduler
        server.addBean(new ScheduledExecutorScheduler());

        // HTTP Configuration
        HttpConfiguration http_config = new HttpConfiguration();
        http_config.setSecureScheme("https");
        http_config.setSecurePort(8443);
        http_config.setOutputBufferSize(32768);
        http_config.setRequestHeaderSize(8192);
        http_config.setResponseHeaderSize(8192);
        http_config.setSendServerVersion(true);
        http_config.setSendDateHeader(false);
        // httpConfig.addCustomizer(new ForwardedRequestCustomizer());

        // Handler Structure
        HandlerCollection handlers = new HandlerCollection();
        ContextHandlerCollection contexts = new ContextHandlerCollection();
        handlers.setHandlers(new Handler[] { contexts, new DefaultHandler() });
        server.setHandler(handlers);

        // Extra options
        server.setDumpAfterStart(false);
        server.setDumpBeforeStop(false);
        server.setStopAtShutdown(true);

        // === jetty-jmx.xml ===
        MBeanContainer mbContainer = new MBeanContainer(
                ManagementFactory.getPlatformMBeanServer());
        server.addBean(mbContainer);


        // === jetty-http.xml ===
        ServerConnector http = new ServerConnector(server,
                new HttpConnectionFactory(http_config));
        http.setPort(8080);
        http.setIdleTimeout(30000);
        server.addConnector(http);


        // === jetty-https.xml ===
        // SSL Context Factory
        SslContextFactory sslContextFactory = new SslContextFactory();
        sslContextFactory.setKeyStorePath(jetty_home + "/../../../jetty-server/src/test/config/etc/keystore");
        sslContextFactory.setKeyStorePassword("OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4");
        sslContextFactory.setKeyManagerPassword("OBF:1u2u1wml1z7s1z7a1wnl1u2g");
        sslContextFactory.setTrustStorePath(jetty_home + "/../../../jetty-server/src/test/config/etc/keystore");
        sslContextFactory.setTrustStorePassword("OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4");
        sslContextFactory.setExcludeCipherSuites("SSL_RSA_WITH_DES_CBC_SHA",
                "SSL_DHE_RSA_WITH_DES_CBC_SHA", "SSL_DHE_DSS_WITH_DES_CBC_SHA",
                "SSL_RSA_EXPORT_WITH_RC4_40_MD5",
                "SSL_RSA_EXPORT_WITH_DES40_CBC_SHA",
                "SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA",
                "SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA");

        // SSL HTTP Configuration
        HttpConfiguration https_config = new HttpConfiguration(http_config);
        https_config.addCustomizer(new SecureRequestCustomizer());

        // SSL Connector
        ServerConnector sslConnector = new ServerConnector(server,
            new SslConnectionFactory(sslContextFactory,HttpVersion.HTTP_1_1.asString()),
            new HttpConnectionFactory(https_config));
        sslConnector.setPort(8443);
        server.addConnector(sslConnector);


        // === jetty-deploy.xml ===
        DeploymentManager deployer = new DeploymentManager();
        DebugListener debug = new DebugListener(System.out,true,true,true);
        server.addBean(debug);
        deployer.addLifeCycleBinding(new DebugListenerBinding(debug));
        deployer.setContexts(contexts);
        deployer.setContextAttribute(
                "org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern",
                ".*/servlet-api-[^/]*\\.jar$");

        WebAppProvider webapp_provider = new WebAppProvider();
        webapp_provider.setMonitoredDirName(jetty_base + "/webapps");
        webapp_provider.setDefaultsDescriptor(jetty_home + "/etc/webdefault.xml");
        webapp_provider.setScanInterval(1);
        webapp_provider.setExtractWars(true);
        webapp_provider.setConfigurationManager(new PropertiesConfigurationManager());

        deployer.addAppProvider(webapp_provider);
        server.addBean(deployer);

        // === setup jetty plus ==
        Configuration.ClassList.setServerDefault(server).addAfter(
                "org.eclipse.jetty.webapp.FragmentConfiguration",
                "org.eclipse.jetty.plus.webapp.EnvConfiguration",
                "org.eclipse.jetty.plus.webapp.PlusConfiguration");

        // === jetty-stats.xml ===
        StatisticsHandler stats = new StatisticsHandler();
        stats.setHandler(server.getHandler());
        server.setHandler(stats);
        ConnectorStatistics.addToAllConnectors(server);

        // === Rewrite Handler
        RewriteHandler rewrite = new RewriteHandler();
        rewrite.setHandler(server.getHandler());
        server.setHandler(rewrite);

        // === jetty-requestlog.xml ===
        NCSARequestLog requestLog = new NCSARequestLog();
        requestLog.setFilename(jetty_home + "/logs/yyyy_mm_dd.request.log");
        requestLog.setFilenameDateFormat("yyyy_MM_dd");
        requestLog.setRetainDays(90);
        requestLog.setAppend(true);
        requestLog.setExtended(true);
        requestLog.setLogCookies(false);
        requestLog.setLogTimeZone("GMT");
        RequestLogHandler requestLogHandler = new RequestLogHandler();
        requestLogHandler.setRequestLog(requestLog);
        handlers.addHandler(requestLogHandler);


        // === jetty-lowresources.xml ===
        LowResourceMonitor lowResourcesMonitor=new LowResourceMonitor(server);
        lowResourcesMonitor.setPeriod(1000);
        lowResourcesMonitor.setLowResourcesIdleTimeout(200);
        lowResourcesMonitor.setMonitorThreads(true);
        lowResourcesMonitor.setMaxConnections(0);
        lowResourcesMonitor.setMaxMemory(0);
        lowResourcesMonitor.setMaxLowResourcesTime(5000);
        server.addBean(lowResourcesMonitor);


        // === test-realm.xml ===
        HashLoginService login = new HashLoginService();
        login.setName("Test Realm");
        login.setConfig(jetty_base + "/etc/realm.properties");
        login.setHotReload(false);
        server.addBean(login);

        // Start the server
        server.start();
        server.join();
    }
}
View Code

21.3 嵌入式的例子

  Jetty在被嵌入到各種應用程序方面有豐富的歷史,在本節,將帶着你通過幾個簡單的例子理解我們在git倉庫的embedded-jetty-examples項目。

 重要:

  這是文件是直接在git倉庫里面獲取的,如果行號不能正確的匹配git倉庫上的請通知我們。

21.3.1 文件服務器

  這個例子展示了如何創建一個簡單的文件服務器Jetty。是完全適合測試用於,需要一個實際的web服務器來獲取文件,它可以很容易地配置到src/test/resources目錄下。注意,這個沒有任何形式的緩存,在服務器或設置適當的響應標頭。通過簡單的幾行就可以提供一些文件:

import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.server.handler.HandlerList;
import org.eclipse.jetty.server.handler.ResourceHandler;
import org.eclipse.jetty.server.handler.gzip.GzipHandler;

/**
 * Simple Jetty FileServer.
 * This is a simple example of Jetty configured as a FileServer.
 */
public class FileServer
{
    public static void main(String[] args) throws Exception
    {
        // Create a basic Jetty server object that will listen on port 8080.  Note that if you set this to port 0
        // then a randomly available port will be assigned that you can either look in the logs for the port,
        // or programmatically obtain it for use in test cases.
        Server server = new Server(8080);

        // Create the ResourceHandler. It is the object that will actually handle the request for a given file. It is
        // a Jetty Handler object so it is suitable for chaining with other handlers as you will see in other examples.
        ResourceHandler resource_handler = new ResourceHandler();
        // Configure the ResourceHandler. Setting the resource base indicates where the files should be served out of.
        // In this example it is the current directory but it can be configured to anything that the jvm has access to.
        resource_handler.setDirectoriesListed(true);
        resource_handler.setWelcomeFiles(new String[]{ "index.html" });
        resource_handler.setResourceBase(".");

        // Add the ResourceHandler to the server.
        GzipHandler gzip = new GzipHandler();
        server.setHandler(gzip);
        HandlerList handlers = new HandlerList();
        handlers.setHandlers(new Handler[] { resource_handler, new DefaultHandler() });
        gzip.setHandler(handlers);

        // Start things up! By using the server.join() the server thread will join with the current thread.
        // See "http://docs.oracle.com/javase/1.5.0/docs/api/java/lang/Thread.html#join()" for more details.
        server.start();
        server.join();
    }
}
View Code

21.3.1.1 運行程序

  運行后通過訪問 http://localhost:8080/index.html,即可查看到想要的結果。

21.3.1.2 Maven坐標

  為了成功運行上面的例子,項目需要增加以下依賴:

<dependency>
  <groupId>org.eclipse.jetty</groupId>
  <artifactId>jetty-server</artifactId>
  <version>${project.version}</version>
</dependency>

21.3.2 多重文件提供

  這個例子展示了通過多重資源處理器提供資源,通過訪問同一個路徑會按順興調用ResourceHandlers 獲得資源:

import java.io.File;

import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.server.handler.ResourceHandler;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.util.resource.Resource;

public class SplitFileServer {
    public static void main(String[] args) throws Exception {
        Server server = new Server();
        ServerConnector connector = new ServerConnector(server);
        connector.setPort(8090);
        server.setConnectors(new Connector[] { connector });
        
        //創建一個資源處理器,設置"/"根路徑,
        ResourceHandler rh0 = new ResourceHandler();

        ContextHandler context0 = new ContextHandler();
        context0.setContextPath("/");
        File dir0 = MavenTestingUtils.getTestResourceDir("dir0");
        context0.setBaseResource(Resource.newResource(dir0));
        context0.setHandler(rh0);

        //創建另一個資源處理器,指定另一個文件夾
        ResourceHandler rh1 = new ResourceHandler();

        ContextHandler context1 = new ContextHandler();
        context1.setContextPath("/");
        File dir1 = MavenTestingUtils.getTestResourceDir("dir1");
        context1.setBaseResource(Resource.newResource(dir1));
        context1.setHandler(rh1);

        //創建一個ContextHandlerCollection集合來包含所有資源處理器,若都匹配則按順序來
        ContextHandlerCollection contexts = new ContextHandlerCollection();
        contexts.setHandlers(new Handler[] { context0, context1 });

        server.setHandler(contexts);

        server.start();

        // 輸出server狀態
        System.out.println(server.dump());
        server.join();
    }
}
View Code

21.3.2.1 運行程序

  啟動程序后訪問http://localhost:8090/index.html ,若第一個匹配路徑下沒有找到相應的資源,則在第二個路徑下繼續尋找,以此類推。

21.3.2.2 Maven坐標

  為了能正確的運行需要添加以下依賴

<dependency>
  <groupId>org.eclipse.jetty</groupId>
  <artifactId>jetty-server</artifactId>
  <version>${project.version}</version>
</dependency>
<dependency>
  <groupId>org.eclipse.jetty.toolchain</groupId>
  <artifactId>jetty-test-helper</artifactId>
  <version>2.2</version>
</dependency>

21.3.3 多重連接

  這個例子展示了如何配置多重連接,主要是同時配置http和https兩種,兩種配置都使用了HelloHandler (此例子上面已有不再翻譯):

//
//  ========================================================================
//  Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
//  ------------------------------------------------------------------------
//  All rights reserved. This program and the accompanying materials
//  are made available under the terms of the Eclipse Public License v1.0
//  and Apache License v2.0 which accompanies this distribution.
//
//      The Eclipse Public License is available at
//      http://www.eclipse.org/legal/epl-v10.html
//
//      The Apache License v2.0 is available at
//      http://www.opensource.org/licenses/apache2.0.php
//
//  You may elect to redistribute this code under either of these licenses.
//  ========================================================================
//

package org.eclipse.jetty.embedded;

import java.io.File;
import java.io.FileNotFoundException;

import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.SecureRequestCustomizer;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.SslConnectionFactory;
import org.eclipse.jetty.util.ssl.SslContextFactory;

/**
 * A Jetty server with multiple connectors.
 */
public class ManyConnectors
{
    public static void main( String[] args ) throws Exception
    {
        // Since this example shows off SSL configuration, we need a keystore
        // with the appropriate key. These lookup of jetty.home is purely a hack
        // to get access to a keystore that we use in many unit tests and should
        // probably be a direct path to your own keystore.

        String jettyDistKeystore = "../../jetty-distribution/target/distribution/demo-base/etc/keystore";
        String keystorePath = System.getProperty(
                "example.keystore", jettyDistKeystore);
        File keystoreFile = new File(keystorePath);
        if (!keystoreFile.exists())
        {
            throw new FileNotFoundException(keystoreFile.getAbsolutePath());
        }

        // Create a basic jetty server object without declaring the port. Since
        // we are configuring connectors directly we'll be setting ports on
        // those connectors.
        Server server = new Server();

        // HTTP Configuration
        // HttpConfiguration is a collection of configuration information
        // appropriate for http and https. The default scheme for http is
        // <code>http</code> of course, as the default for secured http is
        // <code>https</code> but we show setting the scheme to show it can be
        // done. The port for secured communication is also set here.
        HttpConfiguration http_config = new HttpConfiguration();
        http_config.setSecureScheme("https");
        http_config.setSecurePort(8443);
        http_config.setOutputBufferSize(32768);

        // HTTP connector
        // The first server connector we create is the one for http, passing in
        // the http configuration we configured above so it can get things like
        // the output buffer size, etc. We also set the port (8080) and
        // configure an idle timeout.
        ServerConnector http = new ServerConnector(server,
                new HttpConnectionFactory(http_config));
        http.setPort(8080);
        http.setIdleTimeout(30000);

        // SSL Context Factory for HTTPS
        // SSL requires a certificate so we configure a factory for ssl contents
        // with information pointing to what keystore the ssl connection needs
        // to know about. Much more configuration is available the ssl context,
        // including things like choosing the particular certificate out of a
        // keystore to be used.
        SslContextFactory sslContextFactory = new SslContextFactory();
        sslContextFactory.setKeyStorePath(keystoreFile.getAbsolutePath());
        sslContextFactory.setKeyStorePassword("OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4");
        sslContextFactory.setKeyManagerPassword("OBF:1u2u1wml1z7s1z7a1wnl1u2g");

        // HTTPS Configuration
        // A new HttpConfiguration object is needed for the next connector and
        // you can pass the old one as an argument to effectively clone the
        // contents. On this HttpConfiguration object we add a
        // SecureRequestCustomizer which is how a new connector is able to
        // resolve the https connection before handing control over to the Jetty
        // Server.
        HttpConfiguration https_config = new HttpConfiguration(http_config);
        SecureRequestCustomizer src = new SecureRequestCustomizer();
        src.setStsMaxAge(2000);
        src.setStsIncludeSubDomains(true);
        https_config.addCustomizer(src);

        // HTTPS connector
        // We create a second ServerConnector, passing in the http configuration
        // we just made along with the previously created ssl context factory.
        // Next we set the port and a longer idle timeout.
        ServerConnector https = new ServerConnector(server,
            new SslConnectionFactory(sslContextFactory,HttpVersion.HTTP_1_1.asString()),
                new HttpConnectionFactory(https_config));
        https.setPort(8443);
        https.setIdleTimeout(500000);

        // Here you see the server having multiple connectors registered with
        // it, now requests can flow into the server from both http and https
        // urls to their respective ports and be processed accordingly by jetty.
        // A simple handler is also registered with the server so the example
        // has something to pass requests off to.

        // Set the connectors
        server.setConnectors(new Connector[] { http, https });

        // Set a handler
        server.setHandler(new HelloHandler());

        // Start the server
        server.start();
        server.join();
    }
}
View Code

  添加maven依賴如下:

<dependency>
  <groupId>org.eclipse.jetty</groupId>
  <artifactId>jetty-server</artifactId>
  <version>${project.version}</version>
</dependency>
<dependency>
  <groupId>org.eclipse.jetty</groupId>
  <artifactId>jetty-security</artifactId>
  <version>${project.version}</version>
</dependency>

21.3.4 登錄校驗示例

  這個例子展示了如何通過一個安全管理器來封裝一個普通管理器,我們有一個簡單的Hello處理程序返回一個問候,但增加了一個限制就是得到這個問候你必須進行身份驗證。另外需要注意的是這個例子使用了支持map校驗的ConstraintSecurityHandler,這樣比較容易展示SecurityHandler 的使用方法,如果不需要可以簡單的實現SecurityHandler:

//
//  ========================================================================
//  Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
//  ------------------------------------------------------------------------
//  All rights reserved. This program and the accompanying materials
//  are made available under the terms of the Eclipse Public License v1.0
//  and Apache License v2.0 which accompanies this distribution.
//
//      The Eclipse Public License is available at
//      http://www.eclipse.org/legal/epl-v10.html
//
//      The Apache License v2.0 is available at
//      http://www.opensource.org/licenses/apache2.0.php
//
//  You may elect to redistribute this code under either of these licenses.
//  ========================================================================
//

package org.eclipse.jetty.embedded;

import java.util.Collections;

import org.eclipse.jetty.security.ConstraintMapping;
import org.eclipse.jetty.security.ConstraintSecurityHandler;
import org.eclipse.jetty.security.HashLoginService;
import org.eclipse.jetty.security.LoginService;
import org.eclipse.jetty.security.authentication.BasicAuthenticator;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.security.Constraint;

public class SecuredHelloHandler
{
    public static void main( String[] args ) throws Exception
    {
        // Create a basic jetty server object that will listen on port 8080.
        // Note that if you set this to port 0 then a randomly available port
        // will be assigned that you can either look in the logs for the port,
        // or programmatically obtain it for use in test cases.
        Server server = new Server(8080);

        // Since this example is for our test webapp, we need to setup a
        // LoginService so this shows how to create a very simple hashmap based
        // one. The name of the LoginService needs to correspond to what is
        // configured a webapp's web.xml and since it has a lifecycle of its own
        // we register it as a bean with the Jetty server object so it can be
        // started and stopped according to the lifecycle of the server itself.
        // In this example the name can be whatever you like since we are not
        // dealing with webapp realms.
        LoginService loginService = new HashLoginService("MyRealm",
                "src/test/resources/realm.properties");
        server.addBean(loginService);

        // A security handler is a jetty handler that secures content behind a
        // particular portion of a url space. The ConstraintSecurityHandler is a
        // more specialized handler that allows matching of urls to different
        // constraints. The server sets this as the first handler in the chain,
        // effectively applying these constraints to all subsequent handlers in
        // the chain.
        ConstraintSecurityHandler security = new ConstraintSecurityHandler();
        server.setHandler(security);

        // This constraint requires authentication and in addition that an
        // authenticated user be a member of a given set of roles for
        // authorization purposes.
        Constraint constraint = new Constraint();
        constraint.setName("auth");
        constraint.setAuthenticate(true);
        constraint.setRoles(new String[] { "user", "admin" });

        // Binds a url pattern with the previously created constraint. The roles
        // for this constraing mapping are mined from the Constraint itself
        // although methods exist to declare and bind roles separately as well.
        ConstraintMapping mapping = new ConstraintMapping();
        mapping.setPathSpec("/*");
        mapping.setConstraint(constraint);

        // First you see the constraint mapping being applied to the handler as
        // a singleton list, however you can passing in as many security
        // constraint mappings as you like so long as they follow the mapping
        // requirements of the servlet api. Next we set a BasicAuthenticator
        // instance which is the object that actually checks the credentials
        // followed by the LoginService which is the store of known users, etc.
        security.setConstraintMappings(Collections.singletonList(mapping));
        security.setAuthenticator(new BasicAuthenticator());
        security.setLoginService(loginService);

        // The Hello Handler is the handler we are securing so we create one,
        // and then set it as the handler on the
        // security handler to complain the simple handler chain.
        HelloHandler hh = new HelloHandler();

        // chain the hello handler into the security handler
        security.setHandler(hh);

        // Start things up!
        server.start();

        // The use of server.join() the will make the current thread join and
        // wait until the server is done executing.
        // See
        // http://docs.oracle.com/javase/7/docs/api/java/lang/Thread.html#join()
        server.join();
    }
}
View Code

21.3.4.1 運行程序

  運行程序后,輸入路徑http://localhost:8080/index.html ,會提示輸入用戶名和密碼,如下圖:

21.3.4.2 realm.properties文件內容

#
# This file defines users passwords and roles for a HashUserRealm
#
# The format is
#  <username>: <password>[,<rolename> ...]
#
# Passwords may be clear text, obfuscated or checksummed.  The class
# org.eclipse.util.Password should be used to generate obfuscated
# passwords or password checksums
#
# If DIGEST Authentication is used, the password must be in a recoverable
# format, either plain text or OBF:.
#
jetty: MD5:164c88b302622e17050af52c89945d44,user
admin: CRYPT:adpexzg3FUZAk,server-administrator,content-administrator,admin,user
other: OBF:1xmk1w261u9r1w1c1xmq,user
plain: plain,user
user: password,user

# This entry is for digest auth.  The credential is a MD5 hash of username:realmname:password
digest: MD5:6e120743ad67abfbc385bc2bb754e297,user
View Code

21.3.4.3 Maven坐標

<dependency>
  <groupId>org.eclipse.jetty</groupId>
  <artifactId>jetty-server</artifactId>
  <version>${project.version}</version>
</dependency>

21.3.5 最簡單Servlet

  這個例子展示了如何部署一個最簡單的servlet到Jetty,這是一個嚴格意義上的servlet而不是一個web應用的servlet。這個例子非常適合有一個用來測試一個簡單的servlet。

//
//  ========================================================================
//  Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
//  ------------------------------------------------------------------------
//  All rights reserved. This program and the accompanying materials
//  are made available under the terms of the Eclipse Public License v1.0
//  and Apache License v2.0 which accompanies this distribution.
//
//      The Eclipse Public License is available at
//      http://www.eclipse.org/legal/epl-v10.html
//
//      The Apache License v2.0 is available at
//      http://www.opensource.org/licenses/apache2.0.php
//
//  You may elect to redistribute this code under either of these licenses.
//  ========================================================================
//

package org.eclipse.jetty.embedded;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler;

public class MinimalServlets
{
    public static void main( String[] args ) throws Exception
    {
        // Create a basic jetty server object that will listen on port 8080.
        // Note that if you set this to port 0 then a randomly available port
        // will be assigned that you can either look in the logs for the port,
        // or programmatically obtain it for use in test cases.
        Server server = new Server(8080);

        // The ServletHandler is a dead simple way to create a context handler
        // that is backed by an instance of a Servlet.
        // This handler then needs to be registered with the Server object.
        ServletHandler handler = new ServletHandler();
        server.setHandler(handler);

        // Passing in the class for the Servlet allows jetty to instantiate an
        // instance of that Servlet and mount it on a given context path.

        // IMPORTANT:
        // This is a raw Servlet, not a Servlet that has been configured
        // through a web.xml @WebServlet annotation, or anything similar.
        handler.addServletWithMapping(HelloServlet.class, "/*");

        // Start things up!
        server.start();

        // The use of server.join() the will make the current thread join and
        // wait until the server is done executing.
        // See
        // http://docs.oracle.com/javase/7/docs/api/java/lang/Thread.html#join()
        server.join();
    }

    @SuppressWarnings("serial")
    public static class HelloServlet extends HttpServlet
    {
        @Override
        protected void doGet( HttpServletRequest request,
                              HttpServletResponse response ) throws ServletException,
                                                            IOException
        {
            response.setContentType("text/html");
            response.setStatus(HttpServletResponse.SC_OK);
            response.getWriter().println("<h1>Hello from HelloServlet</h1>");
        }
    }
}
View Code

  Maven依賴:

<dependency>
  <groupId>org.eclipse.jetty</groupId>
  <artifactId>jetty-servlet</artifactId>
  <version>${project.version}</version>
</dependency>

21.3.6 Web應用

  這個例子展示如何部署一個簡單的web應用到一個嵌入式的Jetty上。這種方式非常適合你用編程的方式管理一個服務的生命周期:

//
//  ========================================================================
//  Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
//  ------------------------------------------------------------------------
//  All rights reserved. This program and the accompanying materials
//  are made available under the terms of the Eclipse Public License v1.0
//  and Apache License v2.0 which accompanies this distribution.
//
//      The Eclipse Public License is available at
//      http://www.eclipse.org/legal/epl-v10.html
//
//      The Apache License v2.0 is available at
//      http://www.opensource.org/licenses/apache2.0.php
//
//  You may elect to redistribute this code under either of these licenses.
//  ========================================================================
//

package org.eclipse.jetty.embedded;

import java.io.File;
import java.lang.management.ManagementFactory;

import org.eclipse.jetty.jmx.MBeanContainer;
import org.eclipse.jetty.security.HashLoginService;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.AllowSymLinkAliasChecker;
import org.eclipse.jetty.webapp.WebAppContext;

public class OneWebApp
{
    public static void main( String[] args ) throws Exception
    {
        // Create a basic jetty server object that will listen on port 8080.
        // Note that if you set this to port 0 then a randomly available port
        // will be assigned that you can either look in the logs for the port,
        // or programmatically obtain it for use in test cases.
        Server server = new Server(8080);

        // Setup JMX
        MBeanContainer mbContainer = new MBeanContainer(
                ManagementFactory.getPlatformMBeanServer());
        server.addBean(mbContainer);

        // The WebAppContext is the entity that controls the environment in
        // which a web application lives and breathes. In this example the
        // context path is being set to "/" so it is suitable for serving root
        // context requests and then we see it setting the location of the war.
        // A whole host of other configurations are available, ranging from
        // configuring to support annotation scanning in the webapp (through
        // PlusConfiguration) to choosing where the webapp will unpack itself.
        WebAppContext webapp = new WebAppContext();
        webapp.setContextPath("/");
        File warFile = new File(
                "../../jetty-distribution/target/distribution/test/webapps/test/");
        webapp.setWar(warFile.getAbsolutePath());
        webapp.addAliasCheck(new AllowSymLinkAliasChecker());

        // A WebAppContext is a ContextHandler as well so it needs to be set to
        // the server so it is aware of where to send the appropriate requests.
        server.setHandler(webapp);

        // Start things up!
        server.start();

        // The use of server.join() the will make the current thread join and
        // wait until the server is done executing.
        // See http://docs.oracle.com/javase/7/docs/api/java/lang/Thread.html#join()
        server.join();
    }
}
View Code

  Maven依賴:

<dependency>
  <groupId>org.eclipse.jetty</groupId>
  <artifactId>jetty-webapp</artifactId>
  <version>${project.version}</version>
</dependency>

21.3.7 包含JSP的web應用

  這個例子和前一節的例子非常相似,盡管它使嵌入式的Jetty支持了JSP。Jetty9.2以后,使用了來自Apache的jsp引擎,它依賴servlet3.1規范以及ServletContainerInitializer 初始化。為了使其在Jetty中正常工作,你需要像下面例子一樣使之支持注解:

//
//  ========================================================================
//  Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
//  ------------------------------------------------------------------------
//  All rights reserved. This program and the accompanying materials
//  are made available under the terms of the Eclipse Public License v1.0
//  and Apache License v2.0 which accompanies this distribution.
//
//      The Eclipse Public License is available at
//      http://www.eclipse.org/legal/epl-v10.html
//
//      The Apache License v2.0 is available at
//      http://www.opensource.org/licenses/apache2.0.php
//
//  You may elect to redistribute this code under either of these licenses.
//  ========================================================================
//

package org.eclipse.jetty.embedded;

import java.io.File;
import java.lang.management.ManagementFactory;

import org.eclipse.jetty.jmx.MBeanContainer;
import org.eclipse.jetty.security.HashLoginService;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.webapp.Configuration;
import org.eclipse.jetty.webapp.WebAppContext;

public class OneWebAppWithJsp
{
    public static void main( String[] args ) throws Exception
    {
        // Create a basic jetty server object that will listen on port 8080.
        // Note that if you set this to port 0 then
        // a randomly available port will be assigned that you can either look
        // in the logs for the port,
        // or programmatically obtain it for use in test cases.
        Server server = new Server( 8080 );

        // Setup JMX
        MBeanContainer mbContainer = new MBeanContainer(
                ManagementFactory.getPlatformMBeanServer() );
        server.addBean( mbContainer );

        // The WebAppContext is the entity that controls the environment in
        // which a web application lives and
        // breathes. In this example the context path is being set to "/" so it
        // is suitable for serving root context
        // requests and then we see it setting the location of the war. A whole
        // host of other configurations are
        // available, ranging from configuring to support annotation scanning in
        // the webapp (through
        // PlusConfiguration) to choosing where the webapp will unpack itself.
        WebAppContext webapp = new WebAppContext();
        webapp.setContextPath( "/" );
        File warFile = new File(
                "../../jetty-distribution/target/distribution/demo-base/webapps/test.war" );
        if (!warFile.exists())
        {
            throw new RuntimeException( "Unable to find WAR File: "
                    + warFile.getAbsolutePath() );
        }
        webapp.setWar( warFile.getAbsolutePath() );

        // This webapp will use jsps and jstl. We need to enable the
        // AnnotationConfiguration in order to correctly
        // set up the jsp container
        Configuration.ClassList classlist = Configuration.ClassList
                .setServerDefault( server );
        classlist.addBefore(
                "org.eclipse.jetty.webapp.JettyWebXmlConfiguration",
                "org.eclipse.jetty.annotations.AnnotationConfiguration" );

        // Set the ContainerIncludeJarPattern so that jetty examines these
        // container-path jars for tlds, web-fragments etc.
        // If you omit the jar that contains the jstl .tlds, the jsp engine will
        // scan for them instead.
        webapp.setAttribute(
                "org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern",
                ".*/[^/]*servlet-api-[^/]*\\.jar$|.*/javax.servlet.jsp.jstl-.*\\.jar$|.*/[^/]*taglibs.*\\.jar$" );

        // A WebAppContext is a ContextHandler as well so it needs to be set to
        // the server so it is aware of where to
        // send the appropriate requests.
        server.setHandler( webapp );

        // Configure a LoginService.
        // Since this example is for our test webapp, we need to setup a
        // LoginService so this shows how to create a very simple hashmap based
        // one. The name of the LoginService needs to correspond to what is
        // configured in the webapp's web.xml and since it has a lifecycle of
        // its own we register it as a bean with the Jetty server object so it
        // can be started and stopped according to the lifecycle of the server
        // itself.
        HashLoginService loginService = new HashLoginService();
        loginService.setName( "Test Realm" );
        loginService.setConfig( "src/test/resources/realm.properties" );
        server.addBean( loginService );

        // Start things up!
        server.start();

        // The use of server.join() the will make the current thread join and
        // wait until the server is done executing.
        // See http://docs.oracle.com/javase/7/docs/api/java/lang/Thread.html#join()
        server.join();
    }
}
View Code

  Maven依賴

<dependency>
  <groupId>org.eclipse.jetty</groupId>
  <artifactId>jetty-annotations</artifactId>
  <version>${project.version}</version>
</dependency>
<dependency>
  <groupId>org.eclipse.jetty</groupId>
  <artifactId>apache-jsp</artifactId>
  <version>${project.version}</version>
</dependency>
<dependency>
  <groupId>org.eclipse.jetty</groupId>
  <artifactId>apache-jstl</artifactId>
  <version>${project.version}</version>
</dependency>

二十二、HTTP客戶端

22.1 簡介

  Jetty的HTTP模塊提供簡單易用的APIs和工具來處理HTTP(HTTPS)請求。Jetty的HTTP客戶端是不封閉和異步的。它提供一個從不會堵塞I/O的異步的API,提高線程利用率,適合高性能場景如負載測試或並行計算。然而,如果你所有的請求都是使用GET方式來獲得一個響應,Jetty的HTTP客戶端還提供了一個同步API,這是一個編程接口,線程從發出請求直到請求/響應對話完成才釋放。Jetty的HTTP客戶端支持不同的協議,如HTTP/1.1, FastCGI 和 HTTP/2。這樣HTTP請求可以以不同的方式獲得資源,最常用的事HTTP/1.1。

  FastCGI傳輸過程中大量使用Jetty FastCGI的支持,允許Jetty作為反向代理PHP(Apache或Nginx做完全一樣),因此能夠服務—例如WordPress網站。

  簡單易用的特性會讓你得到這樣的HTTP客戶端:

  • 重定向支持:像302,303重定向碼是自動支持的
  • Cookies支持:Cookies會自動發送到匹配的地址上
  • 認證支持:HTTP的Basic和Digest認證是原始支持的,其他認證方法需要插件
  • 請求代理支持:HTTP代理和SOCKS4 代理支持

22.1.1 開始使用HttpClient

  主要的方法是org.eclipse.jetty.client.HttpClient,你可以把HttpClient 實例做為一個瀏覽器實例,像瀏覽器一樣,它可以把請求發到不同的作用域上,管理重定向、Cookies、認證,你可以為它配置一個代理,它會提供你想要的請求和相應。

  為了使用HttpClient,你必須首先實例它,然后配置它,最后啟動它:

// 實例化一個HttpClient
HttpClient httpClient = new HttpClient();

// 對HttpClient進行配置
httpClient.setFollowRedirects(false); // 啟動HttpClient httpClient.start();

  你可以啟動多個HttpClient,但通常一個web應用啟動一個HttpClient就足夠了,啟動多個HttpClient實例的原因要么是想要不同配置的HttpClient(例如:一個HttpClient使用代理,另一個不用),要么是你想有兩個不同的瀏覽器,為了不同的cookies,不同的認證,還有就是你想有兩個不同的傳輸。

  當你使用無參構造方法實例化一個HttpClient時,你只能響應簡單的HTTP請求,而不能響應HTTPS請求。

  為了支持HTTPS請求,你首先應該創建一個SslContextFactory,配置它並將它傳遞給HttpClient的構造方法。當你創建SslContextFactory后,HttpClient即可在所有作用域里支持HTTP和HTTPS請求。

//創建並配置SslContextFactory
SslContextFactory sslContextFactory = new SslContextFactory();

//通過 SslContextFactory創建HttpClient
HttpClient httpClient = new HttpClient(sslContextFactory); //配置HttpClient httpClient.setFollowRedirects(false); //啟動HttpClient httpClient.start();

22.1.2 終止HttpClient

  當你應用停止時,建議你調用以下方法來終止一個HttpClient 

httpClient.stop();

  終止httpClien能確保,httpClient持有的資源(例如:認證證書,cookies等)被釋放掉,線程和調度任務被適當的停止,並讓被httpCLient使用的線程全部退出。

22.2 API使用

22.2.1 阻塞式APIs

  解析一個http請求的最簡單方法如下:

ContentResponse response = httpClient.GET("http://domain.com/path?query");

  方法 HttpClient.GET(...)用來通過給定的URI來獲得一個HTTP的GET請求,並且當成功響應后返回一個ContentResponse 。

  ContentResponse 包含一個HTTP的相應信息:狀態碼、響應頭、響應內容。內容大小被限制在2M內,為了獲得更大的內容詳見相應內容處理器。

  如果你想訂做請求信息,例如,使用請求頭,請求方式或者瀏覽器版本,做法如下:

ContentResponse response = httpClient.newRequest("http://domain.com/path?query")
        .method(HttpMethod.HEAD)
        .agent("Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:17.0) Gecko/20100101 Firefox/17.0") .send();

  上面代碼時下面代碼的簡寫形式:

Request request = httpClient.newRequest("http://domain.com/path?query");
request.method(HttpMethod.HEAD);
request.agent("Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:17.0) Gecko/20100101 Firefox/17.0"); ContentResponse response = request.send();

  你第一次通過httpClient.newRequest(...)方式創建一個Request對象,然后使用APIs來定制它(可以使用鏈式調用)。當Request請求被定制完成,你可以調用Request.send()方法來獲得成功訪問后的ContentResponse 對象。

  簡單的POST請求如下:

ContentResponse response = httpClient.POST("http://domain.com/entity/1")
        .param("p", "value") .send();

  POST方式的參數通過 param() 方法,並自動進行URL編碼。

  Jetty的客戶端會自動重定向,可以自動處理典型的WEB模式如, POST/Redirect/GET,隨后的重定向可以被單獨禁用或者全局禁用。

  文件上傳僅僅需要簡單的一行,確保使用JDK7及以上版本用於支持java.nio.file類:

ContentResponse response = httpClient.newRequest("http://domain.com/upload")
        .method(HttpMethod.POST)
        .file(Paths.get("file_to_upload.txt"), "text/plain") .send();

  也可以通過如下方式來規定請求超時時間:

ContentResponse response = httpClient.newRequest("http://domain.com/path?query")
        .timeout(5, TimeUnit.SECONDS) .send();

  在上面的那個例子中,當5秒過后沒有響應時,會拋出一個java.util.concurrent.TimeoutException異常。

22.2.2 非堵塞式APIs

  到目前,我們已經展示了如何使用阻塞方式的Jetty HTTP客戶端,線程需要等到請求/響應完成后才能結束。

  這一章節,我們將着眼於非阻塞式、異步的Jetty HTTP客戶端使用方法,這種方法非常適合大文件下載、並行處理,在這些情況下,性能和高效的線程性和資源利用率是一個關鍵因素。

  異步APIs的實現依靠請求和響應各階段的監聽。這些監聽器所實現的應用可以有各種類型的邏輯,監聽器的實現在同一個線程中來完成請求或響應。所以如果在監聽器內的應用程序代碼需要較長的執行時間,那么請求或者響應將會等待直到監聽器返回結果。

  如果你要在監聽器內執行一個需要很長時間的代碼,你必須創建自己的線程,並且深度拷貝你需要的監聽器的任何數據,因為當監聽器返回數據后,監聽器內的數據有可能會被回收、清除、銷毀。  

  請求和響應將會被兩個不同的線程處理也有可能同步進行,一個並發處理典型的例子是一個大文件上傳可能與大大文件下載並發進行。符代替性下,響應的處理有可能會在請求沒有結束前就已經完成了,典型的例子是一個大文件上傳會立即觸發一個響應回復如服務器返回的錯誤信息,響應完成了,但是上傳文件依然在繼續。

  應用程序的線程調用Request.send(Response.CompleteListener)方法來對請求進行處理,直到請求被處理完或者阻塞I/O,那么它將返回(沒有阻塞),如果它將阻塞I/O,那么這個線程會要求I/O系統提供一個I/O可以開始的事件,並返回,當這個事件被觸發的時候,在HttpClient線程池中的線程將返回來繼續處理請求。

  響應會在可以有字節進行讀的這一條件觸發下執行,響應會一直執行知道響應完全執行完成或者將要堵塞I/O。如果它將堵塞I/O,那么線程會要求I/O系統提供一個I/O可以使用的觸發事件,然后線程返回。當這個事件被觸發的時候,在HttpClient線程池中的線程將返回來繼續處理響應。  

  一個簡單的異步GET方式的例子: 

httpClient.newRequest("http://domain.com/path")
        .send(new Response.CompleteListener() { @Override public void onComplete(Result result) { // Your logic here  } });

  方法Request.send(Response.CompleteListener)沒有返回值並且不阻塞,Response.CompleteListener作為一個參數被提供用來提醒當請求/響應會話完成的時候Result 參數會允許你調用響應體。

  你也可以用JDK8的lambda表達式來書寫同樣的代碼:

httpClient.newRequest("http://domain.com/path")
        .send(result -> { /* Your logic here */ });

  你也可以使用同步的方式設置超時時間:

Request request = httpClient.newRequest("http://domain.com/path")
        .timeout(3, TimeUnit.SECONDS) .send(result -> { /* Your logic here */ });

  HTTP客戶端的APIs使用監聽器來監聽請求和響應的各種事件,使用JDK8的lambda表達式會更簡單:

httpClient.newRequest("http://domain.com/path")
        // Add request hooks
        .onRequestQueued(request -> { ... }) .onRequestBegin(request -> { ... }) ... // More request hooks available // Add response hooks .onResponseBegin(response -> { ... }) .onResponseHeaders(response -> { ... }) .onResponseContent((response, buffer) -> { ... }) ... // More response hooks available  .send(result -> { ... });

  這讓Jetty的HTTP 客戶端非常適合測試,因為你可以精確的控制請求/響應的每一步的時間(從來知道請求和響應的時間的花在哪了)。

  request的各種事件有:

  • onBegin:請求開始
  • onHeaders:Headers准備完畢可以發送
  • onCommit:Headers已發送,等待進一步確認
  • onContent:內容已發送
  • onSuccess:成功
  • onFailure:失敗

  response的各種事件有:

  • onBegin:可以解析HTTP版本,HTTP狀態碼並可以解析收到的原因
  • onHeader:接收到header ,不論是否被處理都要返回
  • onHeaders:header 被接收並處理完成后
  • onContent:org.eclipse.jetty.client.api.Response.ContentListener的方法,當響應的內容被接收到,可能會被執行多次,這個方法返回前緩沖區的內容必須被刷新。
  • onContent:org.eclipse.jetty.client.api.Response.AsyncContentListener的方法
  • onSuccess:異步回調方法調用時的響應內容已經收到  
  • onFailure:失敗
  • onComplete:當請求和響應都完畢嗎,不管成不成功

22.2.3 內容處理器

22.2.3.1 請求的內容處理器

  Jetty的http客戶端提供很多工具類用來處理請求內容。

  你可以把String、byte[]、ByteBuffer、java.nio.file.Path、InputStream或者你自己實現ContentProvider的類提供給request,這里有一個使用java.nio.file.Paths的例子:

ContentResponse response = httpClient.newRequest("http://domain.com/upload")
        .method(HttpMethod.POST)
        .file(Paths.get("file_to_upload.txt"), "text/plain") .send();

  上面的例子等同於使用PathContentProvider 工具類:

ContentResponse response = httpClient.newRequest("http://domain.com/upload")
        .method(HttpMethod.POST)
        .content(new PathContentProvider(Paths.get("file_to_upload.txt")), "text/plain") .send();

  同樣,用戶也可以通過InputStreamContentProvider工具類來使用FileInputStream:

ContentResponse response = httpClient.newRequest("http://domain.com/upload")
        .method(HttpMethod.POST)
        .content(new InputStreamContentProvider(new FileInputStream("file_to_upload.txt")), "text/plain") .send();

  當InputStream 被阻塞了,因為輸入的阻塞request也將會被堵塞,為了防止這種情況的發生可以使用HttpClient 異步的APIs。

  如果你已經將內容讀取內存中了,那么可以將內容做為一個byte[]提供給 BytesContentProvider工具類:

byte[] bytes = ...;
ContentResponse response = httpClient.newRequest("http://domain.com/upload") .method(HttpMethod.POST) .content(new BytesContentProvider(bytes), "text/plain") .send();

  如果request需要的內容不能立即准備好,那么你的應用程序會注意到什么時候會發送,DeferredContentProvider 會這么用:

DeferredContentProvider content = new DeferredContentProvider();
httpClient.newRequest("http://domain.com/upload") .method(HttpMethod.POST) .content(content) .send(new Response.CompleteListener() { @Override public void onComplete(Result result) { // 程序的邏輯  } }); // 內容還沒有准備好  ... // 一個事件發生了,內容已經准備好了,提供給context byte[] bytes = ...; content.offer(ByteBuffer.wrap(bytes)); ... // 所有內容完畢后,關閉content content.close();

  當一個請求因為客戶端上傳而等待時,服務器可以異步完成相應(至少響應頭可以)。在這種情況下Response.Listener會在請求完全發送完后被調用。這樣可以允許我們精確的控制請求/響應的會話:例如,服務器通過向客戶端發送一個響應來拒絕客戶端上傳太大的文件到服務器中。

  另一種方法提供請求內容的是OutputStreamContentProvider,這可以允許用戶使用OutputStreamContentProvider提供的OutputStream,來等待有內容可以寫入的時候:

OutputStreamContentProvider content = new OutputStreamContentProvider();

//使用try-with-resources方法在context寫完后關閉OutputStream
try (OutputStream output = content.getOutputStream()) { client.newRequest("localhost", 8080) .method(HttpMethod.POST) .content(content) .send(new Response.CompleteListener() { @Override public void onComplete(Result result) { // 你的邏輯  } }); ... // 寫內容  writeContent(output); } //try-with-resource的結束,output.close()會在寫完后自動調用

 22.2.3.2 響應內容的處理

  Jetty客戶端允許程序通過不同的方式處理響應內容。

  第一種方法是將響應內容緩存到內存中;一般用在阻塞式調用以及內容小於2M的時候。

  如果你想控制響應內容的大小(例如限制響應內容的大小低於標准的2M),那么你可以使用org.eclipse.jetty.client.util.FutureResponseListener來處理:

Request request = httpClient.newRequest("http://domain.com/path");

//限制大小512KB
FutureResponseListener listener = new FutureResponseListener(request, 512 * 1024); request.send(listener); ContentResponse response = listener.get(5, TimeUnit.SECONDS);

  如果響應內容超過指定大小,那么響應將被中斷,一個異常將從get()方法內拋出。

  如果你是用異步apis(非阻塞的方式),你可以使用BufferingResponseListener 工具類:

httpClient.newRequest("http://domain.com/path")
        // 限制大小8M
        .send(new BufferingResponseListener(8 * 1024 * 1024) { @Override public void onComplete(Result result) { if (!result.isFailed()) { byte[] responseContent = getContent(); // 你自己的邏輯  } } });

  第二種方法是最有效的(因為避免了內容的復制)並且允許你指定Response.ContentListener或者它的子類來處理即將到來的內容。在下面的這個例子中,Response.Listener.Adapter是一個同時實現了Response.ContentListener和Response.CompleteListener的可以傳入到Request.send()中的類。Jetty的HTTP客戶端將會執行onContent()方法0次或者多次(一有內容就會執行),並且最終會執行onComplete()方法。

ContentResponse response = httpClient
        .newRequest("http://domain.com/path") .send(new Response.Listener.Adapter() { @Override public void onContent(Response response, ByteBuffer buffer) { // 你的程序邏輯  } });

  第三種方法允許你使用InputStreamResponseListener 工具類來等待response 的內容流:

InputStreamResponseListener listener = new InputStreamResponseListener();
httpClient.newRequest("http://domain.com/path") .send(listener); // 等待response頭信息到來 Response response = listener.get(5, TimeUnit.SECONDS); // 核實response if (response.getStatus() == HttpStatus.OK_200) { // 使用try-with-resources來關閉資源 try (InputStream responseContent = listener.getInputStream()) { // 你自己的邏輯  } }

 22.3 cookies支持

   Jetty HTTP客戶端很容易支持cookies。HttpClient 實例將從HTTP響應中接收cookies並把它們存在java.net.CookieStore中,這個class是JDK的一個類。當有新的請求時,如果有匹配的cookies(簡單來說,cookies不能過期並要匹配請求的路徑),那么cookies將會被添加到請求中。

  應用程序可以以編程的方式訪問cookies並進行設置: 

CookieStore cookieStore = httpClient.getCookieStore();
List<HttpCookie> cookies = cookieStore.get(URI.create("http://domain.com/path"));

  應用程序也可以使用編程的方式來設置cookies是否它們將從一個響應中返回:

CookieStore cookieStore = httpClient.getCookieStore();
HttpCookie cookie = new HttpCookie("foo", "bar"); cookie.setDomain("domain.com"); cookie.setPath("/"); cookie.setMaxAge(TimeUnit.DAYS.toSeconds(1)); cookieStore.add(URI.create("http://domain.com"), cookie);

  cookies可以被添加到指定的請求中:

ContentResponse response = httpClient.newRequest("http://domain.com/path")
        .cookie(new HttpCookie("foo", "bar")) .send();

  如果你不願意在接下來的請求中使用cookies你也可以移除它:

CookieStore cookieStore = httpClient.getCookieStore();
URI uri = URI.create("http://domain.com"); List<HttpCookie> cookies = cookieStore.get(uri); for (HttpCookie cookie : cookies) cookieStore.remove(uri, cookie);

  如果你想完全禁止使用cookies,那么你可以安裝一個HttpCookieStore.Empty 實例這個方法來實現:

httpClient.setCookieStore(new HttpCookieStore.Empty());

  你也可以實現一個cookies存儲器來過濾cookies,用這種方法可以根據登錄來過濾:

httpClient.setCookieStore(new GoogleOnlyCookieStore());

public class GoogleOnlyCookieStore extends HttpCookieStore { @Override public void add(URI uri, HttpCookie cookie) { if (uri.getHost().endsWith("google.com")) super.add(uri, cookie); } }

  上面的例子將會只保留來自 google.com 或其子域的cookies。

22.4 證書支持

  Jetty的客戶端程序支持由 RFC 7235定義的 "Basic" 和"Digest"兩種身份驗證機制。

  你可以在http客戶端實例中配置身份驗證如下: 

URI uri = new URI("http://domain.com/secure");
String realm = "MyRealm"; String user = "username"; String pass = "password"; // 添加身份驗證憑證 AuthenticationStore auth = httpClient.getAuthenticationStore(); auth.addAuthentication(new BasicAuthentication(uri, realm, user, pass)); ContentResponse response = httpClient .newRequest(uri) .send() .get(5, TimeUnit.SECONDS);

  Jetty的http客戶端會自動發送匹配的驗證信息到服務器進行身份驗證,如果驗證成功后,那么將會把結果進行緩存,以后相同的域名或匹配的URI將不會再校驗。

  一個成功校驗的HTTP會話如下:

Application  HttpClient                     Server
     |           |                             |
     |--- GET ---|------------ GET ----------->|
     |           |                             |
     |           |<-- 401 + WWW-Authenticate --|
     |           |                             |
     |           |--- GET + Authentication --->|
     |           |                             |
     |<-- 200 ---|------------ 200 ------------|

  當應用程序接收到401狀態碼時,不會響應這個事件,它將會在HttpClient 內部進行處理,會產生一個簡單的攜帶校驗信息頭的請求到原地址,然后傳遞帶有200狀態碼的響應到應用程序。

  成功校驗的結果會被緩存,但是也可以被清除,通常為了再次強制校驗一遍:

httpClient.getAuthenticationStore().clearAuthenticationResults();

  認證有可能會被提前,避免往返服務時間的資源的消耗:

AuthenticationStore auth = httpClient.getAuthenticationStore();
URI uri = URI.create("http://domain.com/secure"); auth.addAuthenticationResult(new BasicAuthentication.BasicResult(uri, "username", "password"));

  在這種方式中,原始請求會有HttpClient 攜帶一個驗證的頭信息,並且服務端應該返回200,這種方式比返回401要更有挑戰性(因為要保證驗證信息的准確性)。

22.5 代理支持

  Jetty的http客戶端支持以代理的方式來訪問目標地址。

  有兩種簡單的方式來使用代理:一種是HTTP代理(由org.eclipse.jetty.client.HttpProxy類提供),另一種是SOCKS 4代理(由org.eclipse.jetty.client.Socks4Proxy類提供),還可以自己寫一個roxyConfiguration.Proxy.的子類來實現代理。

  一個典型的配置如下:

ProxyConfiguration proxyConfig = httpClient.getProxyConfiguration();
HttpProxy proxy = new HttpProxy("proxyHost", proxyPort); // 不使用代理訪問 localhost:8080 proxy.getExcludedAddresses().add("localhost:8080"); // 添加新的代理 proxyConfig.getProxies().add(proxy); ContentResponse response = httpClient.GET(uri);

  你可以指定代理的地址和端口,也可以有選擇的指定某些地址不是用代理,並將代理配置添加到ProxyConfiguration 實例中。

  如果以這種方式,httpclient建立請求到http代理(純文本請求)或者建立一個隧道連接通過http連接(為了加密HTTPS的請求)。

22.5.1 代理認證支持

  Jetty的http客戶端支持代理身份認證,同樣的它也支持服務端身份認證。

  下面的例子,代理需要一個Basic 身份認證,但是服務器需要Digest 認證,如下:

URI proxyURI = new URI("http://proxy.net:8080");
URI serverURI = new URI("http://domain.com/secure"); AuthenticationStore auth = httpClient.getAuthenticationStore(); // 代理認證. auth.addAuthentication(new BasicAuthentication(proxyURI, "ProxyRealm", "proxyUser", "proxyPass")); // 服務端認證 auth.addAuthentication(new DigestAuthentication(serverURI, "ServerRealm", "serverUser", "serverPass")); // 代理配置 ProxyConfiguration proxyConfig = httpClient.getProxyConfiguration(); HttpProxy proxy = new HttpProxy("proxy.net", 8080); proxyConfig.getProxies().add(proxy); ContentResponse response = httpClient.newRequest(serverURI) .send() .get(5, TimeUnit.SECONDS);

  若HTTP會話成功認證且代理也成功認證的會話過程如下:

Application  HttpClient                         Proxy                    Server
     |           |                                |                         |
     |--- GET -->|------------- GET ------------->|                         |
     |           |                                |                         |
     |           |<----- 407 + Proxy-Authn -------|                         |
     |           |                                |                         |
     |           |------ GET + Proxy-Authz ------>|                         |
     |           |                                |                         |
     |           |                                |---------- GET --------->|
     |           |                                |                         |
     |           |                                |<--- 401 + WWW-Authn ----|
     |           |                                |                         |
     |           |<------ 401 + WWW-Authn --------|                         |
     |           |                                |                         |
     |           |-- GET + Proxy-Authz + Authz -->|                         |
     |           |                                |                         |
     |           |                                |------ GET + Authz ----->|
     |           |                                |                         |
     |<-- 200 ---|<------------ 200 --------------|<--------- 200 ----------|

  應用程序不會接收到407或401狀態碼因為處理全部在httpclient內部完成。

  同樣的驗證,代理和服務端的驗證可能被避免,特別當407和401循環返回時。

22.6 可選擇式傳輸

  Jetty HTTP客戶端可以被配置為使用不同的傳輸進行HTTP請求和響應。這意味着客戶端使用GET方式獲取資源/index.html時,可以使用不同的格式。程序並不會察覺到它們實際應用的協議。用戶可以使用高級語言的API來編寫邏輯隱藏具體的傳輸細節。

  最常用的協議是HTTP/1.1,基於文件的以分割線 \r\n分割的:

  GET /index.html HTTP/1.1\r\n
  Host: domain.com\r\n
  ...
  \r\n

  然而,相同的請求也可以使用一種二進制協議FastCGI:

  x01 x01 x00 x01 x00 x08 x00 x00   x00 x01 x01 x00 x00 x00 x00 x00   x01 x04 x00 x01 xLL xLL x00 x00   x0C x0B D O C U M E    N T _ U R I / i    n d e x . h t m    l   ...

   同樣,HTTP/2也是一種二進制協議,以不同的格式傳輸相同的信息。

22.6.1 HTTP/1.1傳輸協議

  HTTP/1.1傳輸協議是默認的傳輸協議:

// 若沒有指定,則默認使用它
HttpClient client = new HttpClient();
client.start();

  如果你想定制HTTP/1.1協議,你可以配置用如下的方式配置HttpClient :

int selectors = 1;
HttpClientTransportOverHTTP transport = new HttpClientTransportOverHTTP(selectors); HttpClient client = new HttpClient(transport, null); client.start();

  上面的例子允許你定制一定數量的NIO選擇器給HttpClient 使用。

22.6.2 HTTP/2 傳輸協議

  HTTP/2可以用如下的方式進行配置:

HTTP2Client h2Client = new HTTP2Client();
h2Client.setSelectors(1); HttpClientTransportOverHTTP2 transport = new HttpClientTransportOverHTTP2(h2Client); HttpClient client = new HttpClient(transport, null); client.start();

  HTTP2Client是低級別的客戶端,它提供了一個API基於HTTP / 2,像session,strean和frames特定於HTTP / 2。HttpClientTransportOverHTTP2 使用HTTP2Client 來格式高級語義的HTTP請求("GET resource /index.html")。

22.6.3 FastCGI 傳輸協議

  FastCGI 協議可以用如下的方式進行配置:

int selectors = 1;
String scriptRoot = "/var/www/wordpress"; HttpClientTransportOverFCGI transport = new HttpClientTransportOverFCGI(selectors, false, scriptRoot); HttpClient client = new HttpClient(transport, null); client.start();

  為了確保請求使用FastCGI 協議,你需要有一個像 PHP-FPM 一樣的FastCGI 服務器。

  FastCGI 傳輸協議主要用於支持提供頁面的PHP服務支持(例如:WordPress)。 

附言:

  翻譯完第一部分(Jetty入門:http://www.cnblogs.com/yiwangzhibujian/p/5832597.html)后,我先跳過了第二部分(配置指南)和第三部分(管理指南),優先翻譯第四部分(開發指南),因為我覺得大部分用戶都是用來開發的。還有這一部分章節較多,打算分開翻譯,這次翻譯了21章節和22章節。

  所有目錄詳見:http://www.cnblogs.com/yiwangzhibujian/p/5832294.html

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM