Fluent API

As of version of 4.2 HttpClient comes with an easy to use facade API based on the concept of a fluent interface. Fluent facade API exposes only the most fundamental functions of HttpClient and is intended for simple use cases that do not require the full flexibility of HttpClient. For instance, fluent facade API relieves the users from having to deal with connection management and resource deallocation.

注:这个最后一句话强调了用户不必处理连接管理和资源释放。(不然,也就不能称为简洁了)。

翻译: HttpClient 在4.2版本的时候开始提供了一种基于流畅接口概念的容易使用的 Facade API。Fluent Facade API 只公开了 HttpClient 的最基本的功能,并且适用于不需要 HttpClient 的全部灵活性的简单用例。

注:Facade 在设计模式里面,我们称为外观模式。 所以,这个Fluent API 应该就是使用外观模式所做的一种简化。

简单使用实例

我看大部分的人都是直接把官方文档复制到博客中,所以一开始我想运行一下代码都很困难,因为没有指明具体的包在哪里。HttpClient 不是一个jar,而是一系列jar。

所以,除了需要核心包以外,还需要一个单独的含有Fluent API的jar包。
在这里插入图片描述

Maven 坐标

说明:第一个是httpclient的核心包,下面的是我们需要的 Fluent API 包。

<dependencies>
	<dependency>
	    <groupId>org.apache.httpcomponents</groupId>
	    <artifactId>httpclient</artifactId>
	    <version>4.5.6</version>
	</dependency>
	
	<dependency>
	    <groupId>org.apache.httpcomponents</groupId>
	    <artifactId>fluent-hc</artifactId>
	    <version>4.5.6</version>
	</dependency>
  </dependencies>

发送请求且响应内容为字符串

static void asString() {
	//执行一个设置了超时时间的get请求,并且返回响应内容为字符串。
	//添加头信息
	Header UA = new BasicHeader(HttpHeaders.USER_AGENT, "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3100.0 Safari/537.36");
	
	try {
		//获取一个笑话页面
		String html = Request.Get("http://xiaohua.zol.com.cn/detail60/59425.html")
					.connectTimeout(3*1000)
					.socketTimeout(3*1000)
					.setHeader(UA)
					.execute().returnContent().asString(Charset.forName("gbk"));
		
		System.out.println(html);
	} catch (IOException e) {
		e.printStackTrace();
	}	
}

执行上面这段代码,会请求一个笑话的页面,并打印在控制台上。


发送请求且响应内容为字节

static void asBytes() {
	//执行一个设置了超时时间的get请求,并且返回响应内容为字节数组
	//添加必要的头信息
	Header UA = new BasicHeader(HttpHeaders.USER_AGENT, "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3100.0 Safari/537.36");
	Header referer = new BasicHeader(HttpHeaders.REFERER, "https://www.mzitu.com/225784/70");
	
	try {
		byte[] imgData = Request.Get("https://i3.mmzztt.com/2020/03/23a70.jpg")
			.connectTimeout(3*1000)
			.socketTimeout(3*1000)
			.setHeaders(UA, referer)  //这个方法不是很好,居然是可变参数,我以为可以是一个List呢?
			.execute().returnContent().asBytes();
		
		Path imgPath = Paths.get("image", "img.jpg");
		Files.write(imgPath, imgData); 
	} catch (IOException e) {
		e.printStackTrace();
	}
}

首先在项目的根目录创建一个 image 文件夹,然后执行该段代码,刷新文件夹,可以在 image 文件夹中看到一张图片。(如果尝试成功了,可以给我评论一下。)如果图片连接失效了,那就从百度图片中复制一个连接,同时删除 referer 这一行。(这一行是为了绕过上面那张图片的反爬虫机制而添加的。)

使用Executor来执行请求

官方的说明是:直接用于在特定安全上下文中执行请求,从而缓存身份验证详细信息并将其重新用于后续请求。

也就是说,Executor对象本身可以保存一些必要的请求信息,然后使用Executor对象来执行多个请求,不需要再次输入信息。

官方示例

Executor executor = Executor.newInstance()
        .auth(new HttpHost("somehost"), "username", "password")
        .auth(new HttpHost("myproxy", 8080), "username", "password")
        .authPreemptive(new HttpHost("myproxy", 8080));

executor.execute(Request.Get("http://somehost/"))
        .returnContent().asString();

executor.execute(Request.Post("http://somehost/do-stuff")
        .useExpectContinue()
        .bodyString("Important stuff", ContentType.DEFAULT_TEXT))
        .returnContent().asString();

不过这里,我就简单的使用了。上面添加身份验证的东西,我也没有,这里就只是创建一个简单的 Executor 实例了。

static void executorAsString() {
	Executor executor = Executor.newInstance();
	Header UA = new BasicHeader(HttpHeaders.USER_AGENT, "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3100.0 Safari/537.36");
	Header referer = new BasicHeader(HttpHeaders.REFERER, "https://www.mzitu.com/225784/70");  //这个是为了获取那张图片特意设置的头部信息
	
	try {
		String text = executor.execute(Request.Get("http://xiaohua.zol.com.cn/detail60/59425.html").setHeader(UA))
		.returnContent()
		.asString();
		
		System.out.println(text);
		
		//执行器可以多次执行请求,这样不用每次为请求设置一些特定的信息,如密码之类的信息。
		//但是这里的执行器很简单,也就看不太出来它的好处了。
		InputStream in = executor.execute(Request.Get("https://i3.mmzztt.com/2020/03/23a70.jpg").setHeaders(UA, referer))
			.returnContent()
			.asStream();
		
		//这里使用流看起来不是很方便,但是复杂的是下面的处理过程,如果只看上面的请求还是很简单的。
		InputStream input = new BufferedInputStream(in);
		//创建输出流
		try (BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(new File("image", "image3.jpg")))) {	
			int len = 0;
			byte[] data = new byte[1024];
			while ((len = input.read(data)) != -1) {
				out.write(data, 0, len);
			}
		}
	} catch (IOException e) {
		e.printStackTrace();
	}
}

上面的例子:首先使用执行器请求一个笑话页面,然后又请求了一张图片,不过这两个请求并没有联系,通常是访问同一个网站的信息,这里只是一个示例。


响应处理 ResponseHandler

Fluent Facade API 通常免除用户处理连接管理和资源处理问题。但是,在大多数情况下,这样做的代价是必须缓冲内存中响应消息的内容。强烈建议使用 HTTP 响应处理(ResponseHandler),以避免在内存中缓冲内容。

官方示例:

Document result = Request.Get("http://somehost/content")
        .execute().handleResponse(new ResponseHandler<Document>() {

    public Document handleResponse(final HttpResponse response) throws IOException {
        StatusLine statusLine = response.getStatusLine();
        HttpEntity entity = response.getEntity();
        if (statusLine.getStatusCode() >= 300) {
            throw new HttpResponseException(
                    statusLine.getStatusCode(),
                    statusLine.getReasonPhrase());
        }
        if (entity == null) {
            throw new ClientProtocolException("Response contains no content");
        }
        DocumentBuilderFactory dbfac = DocumentBuilderFactory.newInstance();
        try {
            DocumentBuilder docBuilder = dbfac.newDocumentBuilder();
            ContentType contentType = ContentType.getOrDefault(entity);
            if (!contentType.equals(ContentType.APPLICATION_XML)) {
                throw new ClientProtocolException("Unexpected content type:" +
                    contentType);
            }
            String charset = contentType.getCharset();
            if (charset == null) {
                charset = HTTP.DEFAULT_CONTENT_CHARSET;
            }
            return docBuilder.parse(entity.getContent(), charset);
        } catch (ParserConfigurationException ex) {
            throw new IllegalStateException(ex);
        } catch (SAXException ex) {
            throw new ClientProtocolException("Malformed XML document", ex);
        }
    }
});

不得不说,官方代码写得就是好,加上了很多得异常抛出和异常处理。但是我只是初学而已,用不到这么复杂得代码。这里提供一个简单一点的例子。

同样是请求图片,但是处理方式不同了。而且我也不想处理返回值了,所以泛型就就使用 Void 了

//避免缓存文件
static void responseHandler() {
	Header UA = new BasicHeader(HttpHeaders.USER_AGENT, "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3100.0 Safari/537.36");
	Header referer = new BasicHeader(HttpHeaders.REFERER, "https://www.mzitu.com/208839");
	String url = "https://i3.mmzztt.com/2019/10/26b01.jpg";
	
	try {
		Request.Get(url)
		.setHeaders(UA, referer)
		.execute().handleResponse(new ResponseHandler<Void>() {
			@Override
			public Void handleResponse(HttpResponse response) throws ClientProtocolException, IOException {
				HttpEntity entity = response.getEntity();
				if (entity != null) {
					byte[] imgData = EntityUtils.toByteArray(entity);
					Path imgPath = Paths.get("image", "img2.jpg");
					Files.write(imgPath, imgData); 
				}
				return null;
			}
		});
	} catch (IOException e) {
		e.printStackTrace();
	}
}

执行上面这段代码,在 image 文件夹中可以看到一张图片。
注意:既然是为了避免内存中缓存响应内容,那么这样使用应该是会提高程序的性能。不过,感觉就不是很简洁了。当然了,如果是小规模使用,应该也不需要这样处理了。


这个 ResponseHandler 是一个接口,而且接口中只有一个方法,也就是说,它可以看作是一个函数式接口,那么我们就可以使用 Lambda 来简化匿名对象的使用了。

使用 Lambda 表达式创建 ResponseHandler 对象:

//避免缓存文件
static void lambdaResponseHandler() {
	Header UA = new BasicHeader(HttpHeaders.USER_AGENT, "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3100.0 Safari/537.36");
	Header referer = new BasicHeader(HttpHeaders.REFERER, "https://www.mzitu.com/225784");
	String url = "https://i3.mmzztt.com/2020/03/23a01.jpg";
	
	try {
		Request.Get(url)
		.setHeaders(UA, referer)
		.execute().handleResponse((response)->{
				HttpEntity entity = response.getEntity();
				if (entity != null) {
					byte[] imgData = EntityUtils.toByteArray(entity);
					Path imgPath = Paths.get("image", "img3.jpg");
					Files.write(imgPath, imgData);
				}
				return null;
		});
	} catch (IOException e) {
		e.printStackTrace();
	}
}

主类

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.fluent.Executor;
import org.apache.http.client.fluent.Request;
import org.apache.http.message.BasicHeader;
import org.apache.http.util.EntityUtils;

public class FulentAPITest {
	public static void main(String[] args) {

		int flag = 4;
		switch (flag) {
		case 0: asString(); break;
		case 1: asBytes(); break;
		case 2: executorAsString(); break;
		case 3: responseHandler(); break;
		case 4: lambdaResponseHandler(); break;
		default: System.out.println("未执行任何方法!"); break;
		}
	}
}

上面的方法都是这个类的静态方法,直接复制进行,就可以调用了,上面也给出了导入包的信息,这些东西有时候是必要的,毕竟但凭类名还是不好区分到底是哪个类的。



运行上面所有方法后的image文件夹

感兴趣的可以自己运行一下上面的代码,应该就可以看到这些图片了。这里当作一个彩蛋吧,虽然也没啥惊喜。
在这里插入图片描述

说明

这个 Fluent facade API 使用起来感觉很便捷,它应该就是为了那种不需要很复杂的请求和很高的性能而设计的,应该是一个值得学习的东西。这里我先了解一下,毕竟以后使用的机会还是很多的。

Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐