Jetty開發指導:HTTP Client


介紹

Jetty HTTP client模塊提供易用的API、工具類和一個高性能、異步的實現來運行HTTP和HTTPS請求。
Jetty HTTP client模塊要求Java版本號1.7或者更高,Java 1.8的應用能用lambda表達式在一些HTTP client API中。


Jetty HTTP client被實現和提供一個異步的API。不會由於I/O時間堵塞,因此使它在線程的利用上更有效率,並不是常適合用於負載測試和並行計算。


然而,有時你全部須要做的是對一個資源運行一個GET請求,HTTP client也提供了一個異步API。發起請求的線程將堵塞直到請求處理完畢。
在外部來看。Jetty HTTP client提供:
 1)重定向支持;重定向編碼比如302或者303被自己主動尾隨。
 2)Cookies支持;被服務端送的cookies在匹配的請求中被送回到服務端。
 3)認證支持;HTTP “Basic”和“Digest”熱症被支持。其他的能夠添加;
 4)前轉協議支持。

初始化

基本的類的名稱是org.eclipse.jetty.client.HttpClient,同Jetty 7和Jetty 8中一樣(盡管它和Jetty 7和Jetty 8中的同名類不是向后兼容的)。
你能夠將一個HttpClient實例看作一個瀏覽器實例。像一個瀏覽器,它能發起請求到不同的域,它管理重定向、cookies和認證,你能用代理配置它。而且他提供給你你發起的請求的響應。


為了使用HttpClient,你必須初始化它、配置它、然后啟動它:

// Instantiate HttpClient
HttpClient httpClient = new HttpClient();
 
// Configure HttpClient, for example:
httpClient.setFollowRedirects(false);
 
// Start HttpClient
httpClient.start();

你能創建多個HttpClient的實例。原因可能是你想指定不同的配置參數(比如。一個實例被配置為前轉代理而還有一個不是),或者由於你想有兩個實例履行象兩個不同的瀏覽器,因此有不同的cookies、不同的認證證書等等。
當你用參數構造器創建一個HttpClient實例時,你僅能履行簡單的HTTP請求,而且你將不能履行HTTPS請求。
為了履行HTTPS請求。你首先應該創建一個SslContextFactory,配置它。而且傳遞它到HttpClient的構造器。當用一個SslContextFactory創建時。HttpClient將能履行HTTP和HTTPS請求到不論什么域。

// Instantiate and configure the SslContextFactory
SslContextFactory sslContextFactory = new SslContextFactory();
 
// Instantiate HttpClient with the SslContextFactory
HttpClient httpClient = new HttpClient(sslContextFactory);
 
// Configure HttpClient, for example:
httpClient.setFollowRedirects(false);
 
// Start HttpClient
httpClient.start();

API介紹

堵塞API

為了履行一個HTTP請求更簡單的方法是:

ContentResponse response = httpClient.GET(<a target=_blank href="http://domain.com/path?query">http://domain.com/path?query</a>);

方法HttpClient.GET(...)履行一個HTTP GET請求到一個給定的URI。成功后返回一個ContentResponse。


ContentResponse對象包括HTTP響應信息:狀態碼、headers和可能的內容。內容長度默認被限制到2M;以下“響應內容處理”會介紹怎么處理更大的內容。


假設你想定制請求,比如通過發起一個HEAD請求取代一個GET,而且仿真一個瀏覽器用戶代理。你能使用這樣的方式:

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.send()。當請求處理玩陳過后,返回ContentResponse。
簡單的POST請求也有一個簡寫的方法:

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

POST的參數值被自己主動的URL編碼。
Jetty HTTP client自己主動地尾隨重定向,因此自己主動地處理這樣的典型的web模式POST/Redirect/GET,而且響應對象包括了GET請求的響應的內容。尾隨重定向是一個特征,你能針對每一個請求或者全局來激活/停止。


文件上傳也須要一行,使用JDK 7的java.nio.file類:

ContentResponse response = httpClient.newRequest("http://domain.com/upload")
        .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異常。

異步API

到眼下為止我們展示了怎么使用Jetty HTTP client的堵塞API,即發起請求的線程堵塞直到請求被處理完畢。

在這節我們將看看Jetty HTTP client的異步、非堵塞API,很適合大數據下載、請求/響應的並行處理、在性能和有效的線程、資源的利用是一個關鍵因素的全部場合。
異步API在請求和響應處理的各個階段都依賴於對回調listener的調用。這些listener被應用實現。能夠履行不論什么應用邏輯。實如今處理請求或者響應的線程中調用這些listener。因此,假設在這些listener中的應用代碼須要花費較長時間,請求或者響應的處理將被堵塞。


假設你須要在一個listener內運行耗時操作,你必須使用你自己的線程,而且記住要深度拷貝listener提供的不論什么數據,由於當listener返回后,這些數據可能回收/清除/銷毀。


請求和響應在兩個不同的線程中運行,因此能夠並行的運行。這個並行處理的一個典型樣例是一個回顯server。一個大的上傳和大的回顯下載同一時候進行。

注意。記住響應能夠在請求之前被處理和完畢。一個典型的樣例是被server觸發一個高速響應的一個大的上載(比如一個error):當請求內容任然在上載時。響應已經到達和被完畢了。
應用線程調用Request.send(CompleteListener)履行請求的處理。直到或者請求被充分地處理或者因為堵塞在I/O而返回(因此從不堵塞)。假設它將堵塞在I/O,線程請求I/O系統當I/O完畢時發出一個事件,然后返回。

當如此一個事件被觸發。一個來自HttpClient線程池的線程將恢復響應的處理。
響應被線程處理。這些線程或者是觸發字節碼已經被准備好的I/O系統線程,或者是來自HttpClient線程池的一個線程(這通過HttpClient.isDispatchIO()屬性控制)。響應持續處理直到響應被處理完畢或者堵塞在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)返回void,而且不堵塞;當請求/響應處理完畢后Response.CompleteListener將被通知,result參數能夠獲取到響應對象。
你能用JDK 8的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(new Response.CompleteListener()
        {
            @Override
            public void onComplete(Result result)
            {
                // Your logic here
            }
        });

上面的樣例為請求/響應處理指定了一個超時時間3秒。
HTTP client API廣泛的使用listener為全部可能的請求和響應時間提供鈎子,在JDK 8的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 client非常適合HTTP負載測試,比如。你能精確的知道請求/響應處理的每一步花費的時間(因此知道請求/響應時間被真正消耗的地方)。
了解請求事件請查看Request.Listener,了解響應事件請查看Response.Listener。

內容處理

請求內容處理

Jetty HTTP client提供了很多現成的工具類處理請求內容。
你能提供這些格式的請求內容:String、byte[]、ByteBuffer、java.nio.file.Path、 InputStream,並提供你的org.eclipse.jetty.client.api.ContentProvider的實現。

以下是一個樣例。使用java.nio.file.Paths提供請求內容:

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

這等價於這樣使用PathContentProvider工具類:

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

相同。你能通過InputStreamContentProvider工具類使用FileInputStream:

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

因為InputStream是堵塞的,因此請求的發送將堵塞,能夠考慮使用異步API。
假設你已經將內容讀到內存中。你能使用BytesContentProvider工具類將它作為byte[]傳入:

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

假設請求內容不是馬上可用的,你能用DeferredContentProvider:

DeferredContentProvider content = new DeferredContentProvider();
httpClient.newRequest("http://domain.com/upload")
        .content(content)
        .send(new Response.CompleteListener()
        {
            @Override
            public void onComplete(Result result)
            {
                // Your logic here
            }
        });
 
// Content not available yet here
 
...
 
// An event happens, now content is available
byte[] bytes = ...;
content.offer(ByteBuffer.wrap(bytes));
 
...
 
// All content has arrived
content.close();

提供請求內容的還有一個方法是使用OutputStreamContentProvider,當請求內容可用時同意應用寫請求內容到OutputStreamContentProvider提供的OutputStream:

OutputStreamContentProvider content = new OutputStreamContentProvider();
 
// Use try-with-resources to close the OutputStream when all content is written
try (OutputStream output = content.getOutputStream())
{
    client.newRequest("localhost", 8080)
            .content(content)
            .send(new Response.CompleteListener()
            {
                @Override
                public void onComplete(Result result)
                {
                    // Your logic here
                }
            });
 
    ...
 
    // Write content
    writeContent(output);
}
// End of try-with-resource, output.close() called automatically to signal end of content

響應內容處理

Jetty HTTP client同意應用使用多種方式處理響應內容。
第一種方式是緩存響應內容在內存中;使用堵塞API,在一個ContentResponse 中內容的最大緩存是2MiB。
假設你想控制響應內容的長度(比如限制到小於2MiB的默認值),那么你能用一個org.eclipse.jetty.client.util.FutureResponseListener:

Request request = httpClient.newRequest("http://domain.com/path");
 
// Limit response content buffer to 512 KiB
FutureResponseListener listener = new FutureResponseListener(request, 512 * 1024);
 
request.send(listener);
 
ContentResponse response = listener.get(5, TimeUnit.SECONDS);

假設響應內容長度逸出。響應想被終止,一個異常將被方法get()拋出。
假設你正在使用異步API。你能用BufferingResponseListener工具類:

httpClient.newRequest("http://domain.com/path")
        // Buffer response content up to 8 MiB
        .send(new BufferingResponseListener(8 * 1024 * 1024)
        {
            @Override
            public void onComplete(Result result)
            {
                if (!result.isFailed())
                {
                    byte[] responseContent = getContent();
                    // Your logic here
                }
            }
        });

另外一種方法最有效率(由於它避免了內容拷貝),並同意你指定一個Response.ContentListener,或者一個子類。處理到達的內容:

ContentResponse response = httpClient
        .newRequest("http://domain.com/path")
        .send(new Response.Listener.Empty()
        {
            @Override
            public void onContent(Response response, ByteBuffer buffer)
            {
                // Your logic here
            }
        });

第三種方法同意你等待響應,然后用InputStreamResponseListener工具類輸出內容:

InputStreamResponseListener listener = new InputStreamResponseListener();
httpClient.newRequest("http://domain.com/path")
        .send(listener);
 
// Wait for the response headers to arrive
Response response = listener.get(5, TimeUnit.SECONDS);
 
// Look at the response
if (response.getStatus() == 200)
{
    // Use try-with-resources to close input stream.
    try (InputStream responseContent = listener.getInputStream())
    {
        // Your logic here
    }
}

其他特征

Cookies支持

Jetty HTTP client原生的支持cookie。

HttpClient實例從HTTP響應收到cookie。然后存儲他們在java.net.CookieStore中,這個類屬於JDK。當新請求被創建,cookie緩存被查閱,假設存在匹配的cookie(即,coolie沒有逸出。且匹配域和請求路徑),這些cookie將被加入到請求。
應用能通過編程進入cookie緩存。查找設置的cookie:

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

應用也能通過編程設置cookie,假設他們從一個HTTP響應返回:

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:

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);

假設你想全然地禁用cookie處理,你能安裝一個HttpCookieStore.Empty實例:

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

你能激活cookie過濾,通過安裝一個履行過濾邏輯的cookie緩存:

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。

認證支持

Jetty HTTP client支持"Basic"和"Digest"認證機制,在RFC 2617中定義。
你能在HTTP client實例中配置認證證書例如以下:

URI uri = new URI("http://domain.com/secure");
String realm = "MyRealm";
String user = "username";
String pass = "password";
 
// Add authentication credentials
AuthenticationStore auth = httpClient.getAuthenticationStore();
auth.addAuthentication(new BasicAuthentication(uri, realm, user, pass));
 
ContentResponse response = httpClient
        .newRequest(uri)
        .send()
        .get(5, TimeUnit.SECONDS);

成功的認證被緩存,可是你能清除它們,迫使又一次認證:

httpClient.getAuthenticationStore().clearAuthenticationResults();

代理支持

Jetty的HTTP client能被配置使用代理。


兩種類型的代理是原生的支持的:HTTP代理(通過類org.eclipse.jetty.client.HttpProxy提供)和SOCKS 4代理(通過類org.eclipse.jetty.client.Socks4Proxy提供)。其他實現能夠通過子類ProxyConfiguration.Proxy來寫。
一個典型的配置例如以下:

ProxyConfiguration proxyConfig = httpClient.getProxyConfiguration();
HttpProxy proxy = new HttpProxy("proxyHost", proxyPort);
// Do not proxy requests for localhost:8080
proxy.getExcludedAddresses().add("localhost:8080");
 
httpClient.setProxyConfiguration(proxyConfig);
 
ContentResponse response = httpClient.GET(uri);

你指定代理的主機和port。也設置你不想被代理的地址,然后在HttpClient實例上設置代理配置。


免責聲明!

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



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