1. 概述

在这篇文章中,我们将探讨Apache HttpClient库的高级用法。我们将介绍如何在HTTP请求中添加自定义头,并学习如何配置客户端通过代理服务器进行授权和发送请求。

我们将使用Wiremock来模拟HTTP服务器的行为。有关Wiremock的更多信息,请参阅这篇教程

2. 带有自定义User-Agent头的HTTP请求

假设我们想在HTTP GET请求中添加一个自定义的User-Agent头。User-Agent头包含一个标识请求软件类型、操作系统和软件供应商或软件版本的独特字符串。

在开始编写HTTP客户端之前,我们需要启动嵌入式模拟服务器:

@Rule
public WireMockRule serviceMock = new WireMockRule(8089);

当我们创建HttpGet实例时,可以使用setHeader()方法传递头的名称及其值。这个头将被添加到HTTP请求中:

String userAgent = "BaeldungAgent/1.0"; 
HttpClient httpClient = HttpClients.createDefault();

HttpGet httpGet = new HttpGet("http://localhost:8089/detail");
httpGet.setHeader(HttpHeaders.USER_AGENT, userAgent);

HttpResponse response = httpClient.execute(httpGet);

assertEquals(response.getCode(), 200);

我们添加了一个User-Agent头,并通过execute()方法发送请求。

当向URL /detail 发送带有User-Agent值为“BaeldungAgent/1.0”的GET请求时,serviceMock将返回200 HTTP响应码:

serviceMock.stubFor(get(urlEqualTo("/detail"))
  .withHeader("User-Agent", equalTo(userAgent))
  .willReturn(aResponse().withStatus(200)));

有关4.5版本的相关Javadoc,请参考此链接,并参见结论部分的GitHub链接。

3. 在POST请求体中发送数据

通常,当我们执行HTTP POST方法时,希望将实体作为请求体。创建HttpPost对象实例时,我们可以使用setEntity()方法向请求中添加主体:

String xmlBody = "<xml><id>1</id></xml>";
HttpClient httpClient = HttpClients.createDefault();
HttpPost httpPost = new HttpPost("http://localhost:8089/person");
httpPost.setHeader("Content-Type", "application/xml");

StringEntity xmlEntity = new StringEntity(xmlBody);
httpPost.setEntity(xmlEntity);

HttpResponse response = httpClient.execute(httpPost);

assertEquals(response.getCode(), 200);

我们创建了一个StringEntity实例,其中包含以XML格式编写的主体。设置Content-Type头为“application/xml"至关重要,以便向服务器传达我们发送的内容类型。当serviceMock接收到带有XML主体的POST请求时,它将返回200 OK状态码:

serviceMock.stubFor(post(urlEqualTo("/person"))
  .withHeader("Content-Type", equalTo("application/xml"))
  .withRequestBody(equalTo(xmlBody))
  .willReturn(aResponse().withStatus(200)));

有关4.5版本的相关Javadoc,请参考此链接,并参见结论部分的GitHub链接。

4. 通过代理服务器发送请求

通常,我们的Web服务可能位于一个执行额外逻辑、缓存静态资源等的代理服务器之后。在创建HTTP客户端并向实际服务发送请求时,我们不想每次请求都处理这些事情。

为了测试这种情况,我们需要启动另一个嵌入式Web服务器:

@Rule
public WireMockRule proxyMock = new WireMockRule(8090);

有两个嵌入式服务器,第一个实际服务监听8089端口,代理服务器监听8090端口。

我们通过创建一个使用代理作为参数的DefaultProxyRoutePlanner来配置我们的HttpClient,使其通过代理发送所有请求:

HttpHost proxy = new HttpHost("localhost", 8090);
DefaultProxyRoutePlanner routePlanner = new DefaultProxyRoutePlanner(proxy);
HttpClient httpclient = HttpClients.custom()
  .setRoutePlanner(routePlanner)
  .build();

我们的代理服务器将所有请求重定向到监听8090端口的实际服务。在测试结束时,我们验证请求已通过代理发送到实际服务:

proxyMock.stubFor(get(urlMatching(".*"))
  .willReturn(aResponse().proxiedFrom("http://localhost:8089/")));

serviceMock.stubFor(get(urlEqualTo("/private"))
  .willReturn(aResponse().withStatus(200)));

assertEquals(response.getCode(), 200);
proxyMock.verify(getRequestedFor(urlEqualTo("/private")));
serviceMock.verify(getRequestedFor(urlEqualTo("/private")));

有关4.5版本的相关Javadoc,请参考此链接,并参见结论部分的GitHub链接。

5. 配置HttpClient通过代理进行授权

在扩展前面的例子时,有些情况下代理服务器用于执行授权。在这种配置下,代理可以对所有请求进行授权,然后将它们传递给隐藏在代理后面的服务器。

我们可以配置HttpClient,使其通过代理发送每个请求,并附带一个用于进行授权过程的Authorization头。假设我们有一个只授权一个用户“username_admin"和密码“secret_password"的代理服务器。

我们需要创建一个包含将通过代理授权的用户的BasicCredentialsProvider实例。为了使HttpClient自动为每个请求添加具有适当值的Authorization头,我们需要创建一个包含提供的凭据的HttpClientContext,以及存储凭据的BasicAuthCache

HttpHost proxy = new HttpHost("localhost", 8090);
DefaultProxyRoutePlanner routePlanner = new DefaultProxyRoutePlanner(proxy);
//Client credentials
CredentialsProvider credentialsProvider = CredentialsProviderBuilder.create()    
   .add(new AuthScope(proxy), "username_admin", "secret_password".toCharArray())    
   .build();

// Create AuthCache instance 
AuthCache authCache = new BasicAuthCache(); 
// Generate BASIC scheme object and add it to the local auth cache
BasicScheme basicAuth = new BasicScheme(); 
authCache.put(proxy, basicAuth); 
HttpClientContext context = HttpClientContext.create(); 
context.setCredentialsProvider(credentialsProvider); 
context.setAuthCache(authCache); 

HttpClient httpclient = HttpClients.custom() 
 .setRoutePlanner(routePlanner) 
 .setDefaultCredentialsProvider(credentialsProvider) 
 .build();

当我们设置HttpClient时,向服务发送请求将导致通过代理发送带有Authorization头的请求以进行授权过程。这将在每个请求中自动设置。

让我们执行一个实际到服务的请求:

HttpGet httpGet = new HttpGet("http://localhost:8089/private");
httpGet.setHeader("Authorization", StandardAuthScheme.BASIC);
HttpResponse response = httpclient.execute(httpGet, context);

验证httpClientexecute()方法与我们的配置确认请求已通过带有Authorization头的代理发送:

proxyMock.stubFor(get(urlMatching("/private"))
  .willReturn(aResponse().proxiedFrom("http://localhost:8089/")));
serviceMock.stubFor(get(urlEqualTo("/private"))
  .willReturn(aResponse().withStatus(200)));

assertEquals(response.getCode(), 200);
proxyMock.verify(getRequestedFor(urlEqualTo("/private"))
  .withHeader("Authorization", containing("Basic")));
serviceMock.verify(getRequestedFor(urlEqualTo("/private")));

有关4.5版本的相关Javadoc,请参考此链接,并参见结论部分的GitHub链接。

6. 结论

本文展示了如何配置Apache HttpClient进行高级HTTP调用。我们了解了如何通过代理服务器发送请求以及如何通过代理进行授权。

所有这些示例和代码片段的实现可以在GitHub项目中找到——这是一个Maven项目,因此导入并运行起来非常容易。要获取4.5版本的片段,请使用这个链接