一、简介

超文本传输协议(HTTP)是目前互联网上使用的最重要的协议,尽管java.net包中提供了通过HTTP访问资源的基本功能,但它并没有提供出许多应用程序所需的全部灵活性或功能。HttpClient试图通过提供一个实现了最新HTTP标准和建议的高效且功能丰富的客户端包,从而填补这一空白。

二、下载与安装

可以下载HttpClient及其依赖的jar或者添加Maven依赖:

<!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient -->
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.12</version>
</dependency>

三、基本用法

1、术语介绍

  • HTTP Message

HTTP消息由头部分和可选实体组成,共有两种消息:请求和响应。它们在第一行的格式上有所不同,但都可以有头字段和可选的实体。

  • HTTP Request

HTTP请求是从客户端发送到服务器的消息,第一行包括发送请求的URI和服务器应该为客户机执行的方法。

  • HTTP Response

HTTP响应是从服务器发送到客户端的响应消息,第一行包含一个状态代码,用于告知请求的成功或失败。

HTTP定义了一组状态代码,比如200表示成功,404表示未找到。

  • Method

方法是服务器请求的操作,HTTP定义了一组操作,最常见的是GET和POST。

  • Header Fields

头字段是name-value对,其中名称和值都是文本;头字段的名称不区分大小写;可以将多个值分配给同一个名称。

  • Entity

实体是通过HTTP消息发送的数据。例如,响应可以包含作为实体正在下载的页面或图像;请求可以包含在Web表单中输入的参数。

HTTP消息的实体可以具有任意的数据格式,这种格式通常在头字段中指定为MIME类型。

  • Session

会话是从单个源到服务器的一系列请求;服务器可以保存会话数据,并且需要识别每个传入请求所属的会话。

  • Cookies

Cookies是服务器跟踪会话的首选方式。服务器响应请求提供一段称为cookie的数据,服务器期望客户端在头字段中发送该数据块,并在同一会话的每个请求之后发送该数据块。

每个会话的cookie是不同的,因此服务器可以通过查看cookie来标识请求属于哪个会话。如果请求中缺少cookie,那么服务器将不会按预期响应。

2、执行Get请求

CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpGet = new HttpGet("http://localhost:8080/StudySpringMVC/getUser");
CloseableHttpResponse response = httpclient.execute(httpGet);
try {
	System.out.println(response.getStatusLine());
	HttpEntity entity = response.getEntity();
	// do something useful with the response body
	// and ensure it is fully consumed
	EntityUtils.consume(entity);
} finally {
	response.close();
}

3、执行Post请求

CloseableHttpClient httpclient = HttpClients.createDefault();
HttpPost httpPost = new HttpPost("http://localhost:8080/StudySpringMVC/greeting");
List <NameValuePair> nvps = new ArrayList <NameValuePair>();
nvps.add(new BasicNameValuePair("name", "albert"));
httpPost.setEntity(new UrlEncodedFormEntity(nvps));
CloseableHttpResponse response = httpclient.execute(httpPost);
try {
	System.out.println(response.getStatusLine());
	HttpEntity entity = response.getEntity();
	// do something useful with the response body
	// and ensure it is fully consumed
	EntityUtils.consume(entity);
} finally {
	response.close();
}

四、样例

样例中使用了httpbin.org,它是一个简单的HTTP请求和响应服务。

1、响应处理

此样例演示如何使用响应处理程序处理HTTP响应,这是执行HTTP请求和处理HTTP响应的推荐方法。这种方法使调用方能够专注于处理HTTP响应的过程,并将系统资源释放任务委托给HttpClient。使用HTTP响应处理程序可以保证在所有情况下,底层的HTTP连接都会被自动释放回连接管理器。

public class ClientWithResponseHandler {
	public static void main(String[] args) throws Exception {
		CloseableHttpClient httpClient = HttpClients.createDefault();
		try{
			HttpGet httpGet = new HttpGet("https://jsonplaceholder.typicode.com/users/1");
			System.out.println("Executing request " + httpGet.getRequestLine());
			//Create a custom response handler
			ResponseHandler<String> responseHandler = new ResponseHandler<String>() {
				public String handleResponse(HttpResponse response) throws ClientProtocolException, IOException {
					int status = response.getStatusLine().getStatusCode();
					if(status >= 200 && status < 300){
						HttpEntity entity = response.getEntity();
						return entity != null ? EntityUtils.toString(entity) : null;
					}else {
                        throw new ClientProtocolException("Unexpected response status: " + status);
                    }
				}
			};
			String responseBody = httpClient.execute(httpGet, responseHandler);
			System.out.println("----------------------------------------");
            System.out.println(responseBody);
		}finally{
			httpClient.close();
		}
	}
}

2、手动释放连接

此样例演示了如何在手动处理HTTP响应的情况下,确保将底层HTTP连接释放回连接管理器。

public class ClientConnectionRelease {

	public static void main(String[] args) throws Exception{
		CloseableHttpClient httpClient = HttpClients.createDefault();
		try{
			HttpGet httpGet = new HttpGet("http://httpbin.org/get");
			System.out.println("Executing request " + httpGet.getRequestLine());
			CloseableHttpResponse response = httpClient.execute(httpGet);
			try{
				System.out.println("----------------------------------------");
                System.out.println(response.getStatusLine());
                
                //响应实体
                HttpEntity entity = response.getEntity();
                //如果响应未包含实体,则无需为连接释放而烦恼
                if(entity != null){
                	InputStream inStream = entity.getContent();
                	try{
                		ByteArrayOutputStream result = new ByteArrayOutputStream();
                		byte[] buffer = new byte[1024];
                		int len;
                		while((len = inStream.read(buffer)) != -1){
                			result.write(buffer, 0, len);
                		}
                		System.out.println(result.toString("UTF-8"));
                	}catch(IOException ex){
                		//如果发生IOException,则连接将被释放自动返回到连接管理器
                		throw ex;
                	}finally{
                		//关闭输入流将触发连接释放
                		inStream.close();
                	}
                }
			}finally{
				response.close();
			}
		}finally{
			httpClient.close();
		}
	}
}

3、中止请求

此样例演示如何在HTTP请求正常完成之前中止该请求。

public class ClientAbortMethod {

	public static void main(String[] args) throws Exception{
		CloseableHttpClient httpClient = HttpClients.createDefault();
		try{
			HttpGet httpGet = new HttpGet("http://httpbin.org/get");
			System.out.println("Executing request " + httpGet.getURI());
			CloseableHttpResponse response = httpClient.execute(httpGet);
			try{
				System.out.println("----------------------------------------");
                System.out.println(response.getStatusLine());
                //不想处理响应正文,在请求对象上调用中止
                httpGet.abort();
			}finally{
				response.close();
			}
		}finally{
			httpClient.close();
		}
	}
}

4、客户端认证

此样例使用HttpClient对需要用户身份验证的目标站点执行HTTP请求。

需要身份验证的站点:

public class ClientAuthentication {

	public static void main(String[] args) throws Exception{
		CredentialsProvider provider = new BasicCredentialsProvider();
		AuthScope authscope = new AuthScope("httpbin.org", 80);
		Credentials credentials = new UsernamePasswordCredentials("user", "passwd");
		provider.setCredentials(authscope, credentials);
		CloseableHttpClient httpClient = HttpClients.custom().setDefaultCredentialsProvider(provider).build();
		try{
			HttpGet httpGet = new HttpGet("http://httpbin.org/basic-auth/user/passwd");
			CloseableHttpResponse response = httpClient.execute(httpGet);
			try{
				System.out.println("----------------------------------------");
                System.out.println(response.getStatusLine());
                System.out.println(EntityUtils.toString(response.getEntity()));
			}finally{
				response.close();
			}
		}finally{
			httpClient.close();
		}
	}
}

5、使用代理

此样例演示了如何通过代理发送HTTP请求。

public class ClientExecuteProxy {

	public static void main(String[] args) throws Exception{
		CloseableHttpClient httpClient = HttpClients.createDefault();
		try{
			HttpHost target = new HttpHost("httpbin.org", 443, "http");
			HttpHost proxy = new HttpHost("127.0.0.1", 8080, "http");
			RequestConfig config = RequestConfig.custom().setProxy(proxy).build();
			HttpGet request = new HttpGet("/");
			request.setConfig(config);
			
			System.out.printf("Executing request %s to %s via %s.", request.getRequestLine(), target, proxy);
			CloseableHttpResponse response = httpClient.execute(target, request);
			try{
				System.out.println("----------------------------------------");
                System.out.println(response.getStatusLine());
                System.out.println(EntityUtils.toString(response.getEntity()));
			}finally{
				response.close();
			}
		}finally{
			httpClient.close();
		}
	}
}

6、代理认证

此样例演示通过认证代理隧道的安全连接执行HTTP请求。

public class ClientProxyAuthentication {

	public static void main(String[] args) throws Exception{
		String targetHost = "httpbin.org";
		int targetPort = 80;
		String proxyHost = "localhost";
		int proxyPort = 8080;
		
		CredentialsProvider provider = new BasicCredentialsProvider();
		AuthScope authscope = new AuthScope(proxyHost, proxyPort);
		Credentials credentials = new UsernamePasswordCredentials("squid", "squid");
		provider.setCredentials(authscope, credentials);
		authscope = new AuthScope(targetHost, targetPort);
		credentials = new UsernamePasswordCredentials("user", "passwd");
		provider.setCredentials(authscope, credentials);
		CloseableHttpClient httpClient = HttpClients.custom().setDefaultCredentialsProvider(provider).build();
		try{
			HttpHost target = new HttpHost(targetHost, targetPort);
			HttpHost proxy = new HttpHost(proxyHost, proxyPort);
			
			RequestConfig config = RequestConfig.custom().setProxy(proxy).build();
			HttpGet request = new HttpGet("/basic-auth/user/passwd");
			request.setConfig(config);
			
			System.out.printf("Executing request %s to %s via %s.", request.getRequestLine(), target, proxy);
			CloseableHttpResponse response = httpClient.execute(target, request);
			try{
				System.out.println("----------------------------------------");
				System.out.println(response.getStatusLine());
				System.out.println(EntityUtils.toString(response.getEntity()));
			}finally{
				response.close();
			}
		}finally{
			httpClient.close();
		}
	}
}

7、块编码的POST请求

此样例演示如何使用块编码流式传输请求实体。

public class ClientChunkEncodedPost {

	public static void main(String[] args) throws Exception{
		String filePath = "D:/Temp/movie/readme.txt";
		CloseableHttpClient httpClient = HttpClients.createDefault();
		try{
			HttpPost request = new HttpPost("http://httpbin.org/post");
			File file = new File(filePath);
			//使用InputStreamEntity能够从任意来源中流式传输数据,此处也可以使用FileEntity
			//FileEntity entity = new FileEntity(file, "binary/octet-stream");
			InputStreamEntity entity = new InputStreamEntity(
					new FileInputStream(file), -1, ContentType.APPLICATION_OCTET_STREAM);
			entity.setChunked(true);
			request.setEntity(entity);
			
			System.out.println("Executing request: " + request.getRequestLine());
			
			CloseableHttpResponse response = httpClient.execute(request);
			try{
				System.out.println("----------------------------------------");
				System.out.println(response.getStatusLine());
				System.out.println(EntityUtils.toString(response.getEntity()));
			}finally{
				response.close();
			}
		}finally{
			httpClient.close();
		}
	}
}

8、自定义执行上下文

此样例演示如何使用填充了自定义属性的本地HTTP上下文。

public class ClientCustomContext {

	public static void main(String[] args) throws Exception{
		CloseableHttpClient httpClient = HttpClients.createDefault();
		try{
			CookieStore cookieStore = new BasicCookieStore();
			cookieStore.addCookie(new BasicClientCookie("uid", "666666"));
			HttpClientContext clientContext = HttpClientContext.create();
			clientContext.setCookieStore(cookieStore);
			HttpGet request = new HttpGet("http://httpbin.org/cookies");
			
			System.out.println("Executing request " + request.getRequestLine());
			CloseableHttpResponse response = httpClient.execute(request, clientContext);
			try{
				System.out.println("----------------------------------------");
                System.out.println(response.getStatusLine());
                List<Cookie> cookies = cookieStore.getCookies();
                for(int i = 0; i < cookies.size(); i++){
                	System.out.println("Local cookie: " + cookies.get(i));
                }
                EntityUtils.consume(response.getEntity());
			}finally{
				response.close();
			}
		}finally{
			httpClient.close();
		}
	}
}

9、基于表单的登录

此样例演示如何使用HttpClient执行基于表单的登录。

public static void main(String[] args) throws Exception{
	CookieStore cookieStore = new BasicCookieStore();
	CloseableHttpClient httpClient = HttpClients.custom().setDefaultCookieStore(cookieStore).build();
	try{
		HttpGet request = new HttpGet("http://localhost:8080/StudySpringMVC/index");
		CloseableHttpResponse response = httpClient.execute(request);
		try{
			HttpEntity entity = response.getEntity();
			System.out.println("Login form get: " + response.getStatusLine());
			EntityUtils.consume(entity);
			
			System.out.println("Initial set of cookies:");
			printCookies(cookieStore.getCookies());
		}finally{
			response.close();
		}
		
		HttpUriRequest login = RequestBuilder.post()
				.setUri(new URI("http://localhost:8080/StudySpringMVC/greeting"))
				.addParameter("name", "albert")
				.addParameter("passwd", "666666")
				.build();
		response = httpClient.execute(login);
		try{
			HttpEntity entity = response.getEntity();
			System.out.println("Login form get: " + response.getStatusLine());
			EntityUtils.consume(entity);
			
			System.out.println("Post logon cookies:");
			printCookies(cookieStore.getCookies());
		}finally{
			response.close();
		}
	}finally{
		httpClient.close();
	}
}

public static void printCookies(List<Cookie> cookies){
	if(cookies.isEmpty()){
		System.out.println("Cookies is none!");
	}else{
		for(int i = 0; i < cookies.size(); i++){
			System.out.println("- " + cookies.get(i).toString());
		}
	}
}

客户端输出:

Login form get: HTTP/1.1 200 OK
Initial set of cookies:
- [version: 0][name: JSESSIONID][value: F8B0CA3D8CDC4B159D72EFA024359A14][domain: localhost][path: /StudySpringMVC/][expiry: null]
Login form get: HTTP/1.1 200 OK
Post logon cookies:
- [version: 0][name: JSESSIONID][value: F8B0CA3D8CDC4B159D72EFA024359A14][domain: localhost][path: /StudySpringMVC/][expiry: null]

更多样例请参考官网样例。

参考资料:

HttpClient

HttpClient Quick Start

HttpClient Examples