彻底掌握网络通信(二)Apache的HttpClient基础知识

xiaoxiao2021-02-28  48

网络通信系列文章序

彻底掌握网络通信(一)Http协议基础知识 彻底掌握网络通信(二)Apache的HttpClient基础知识 彻底掌握网络通信(三)Android源码中HttpClient的在不同版本的使用 彻底掌握网络通信(四)Android源码中HttpClient的发送框架解析 彻底掌握网络通信(五)DefaultRequestDirector解析 彻底掌握网络通信(六)HttpRequestRetryHandler解析 彻底掌握网络通信(七)ConnectionReuseStrategy,ConnectionKeepAliveStrategy解析 彻底掌握网络通信(八)AsyncHttpClient源码解读 彻底掌握网络通信(九)AsyncHttpClient为什么无法用Fiddler来抓包 彻底掌握网络通信(十)AsyncHttpClient如何发送JSON解析JSON,以及一些其他用法


接上面的基础知识,这篇主要介绍下Apache中的HttpClient,通过这篇你可以对DefaultHttpClient用到的各种知识有一个更好的了解

1)先解释下何为HttpClient HttpClient 是Apache Jakarta Common 下的子项目,他是一个第三方库,开发者可以使用这个库提供的API方便进行HTTP请求,HttpClient是基于HttpCore来实现的一个库; HttpClient主页

2)何为HttpCore HttpCore是一组Http传输组件,可以说HttpClient进行的Http请求都是由HttpCore来进行的; HttpCore主页


3)HttpClient详解 3.0)特征 - 基于java,实现了http 1.0和1.1 - 实现了所有的HTTP方法 - 支持HTTPS - 支持COOKIE处理 - 支持HTTP1.1的响应缓存

3.1)范围 - 基于httpcore而实现的第三方库 - 基于阻塞I/0

3.2)部分样例代码 - 返回消息对头信息的处理,HttpClient提供取回,添加,移除,枚举头的方法

HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK"); response.addHeader("Set-Cookie", "c1=a; path=/; domain=localhost"); response.addHeader("Set-Cookie", "c2=b; path=\"/\", c3=c; domain=\"localhost\""); Header h1 = response.getFirstHeader("Set-Cookie"); Header h2 = response.getLastHeader("Set-Cookie"); Header[] hs = response.getHeaders("Set-Cookie");

最有效的获取全部Header的方法是使用HeaderIterator 接口

HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK"); response.addHeader("Set-Cookie", "c1=a; path=/; domain=localhost"); response.addHeader("Set-Cookie", "c2=b; path=\"/\", c3=c; domain=\"localhost\""); HeaderIterator it = response.headerIterator("Set-Cookie"); while (it.hasNext()) { System.out.println(it.next()); }

在使用HeaderIterator接口的时候,我们也可以通过HeaderElement获取Header的key value值

HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK"); HeaderElementIterator it = new BasicHeaderElementIterator( response.headerIterator("Set-Cookie")); while (it.hasNext()) { HeaderElement elem = it.nextElement(); System.out.println(elem.getName() + " = " + elem.getValue()); NameValuePair[] params = elem.getParameters(); for (int i = 0; i < params.length; i++) { System.out.println(" " + params[i]); } }

-终止请求只要调用

HttpUriRequest#abort()

即可,该方法是线程安全的,调用之后会抛出InterruptedIOException

3.3)Http Entity实体        Http Entity即可以表示请求里面的请求实体,也可以是响应中的响应实体,Http Entity可以分为如下几类    1)代表底层流的基本实体:通常是在http报文中获取的实体。他只有一个空参的构造方法。刚创建时没有内容,长度为负值。需要通过两个方法,把值赋进去。    2)自我包含的,可重复获得使用的实体    3)流式不可以重复的实体

其中代表底层流的基本实体的类有 1.1)BasicHttpEntity

InputStream is = null; //BasicHttpEntity这类就是一个输入流的内容包装类,包装内容的相关的编码格式,长度等 BasicHttpEntity entity = new BasicHttpEntity(); //设置内容 entity.setContent(is); //设置长度 entity.setContentLength(is.available());

其中代表自我包含的,可重复获得使用的实体类有 2.1)ByteArrayEntity:从指定的字节数组中取出内容的实体

ByteArrayEntity entity = new ByteArrayEntity("内容".getBytes()); ByteArrayInputStream is = (ByteArrayInputStream) entity.getContent();

2.2)StringEntity:通过String创建的实体

String content = "测试StringEntity"; StringEntity entity = new StringEntity(content); //创建带字符创参数和字符编码的 StringEntity entity2 = new StringEntity(content, "UTF-8");

2.3)FileEntity:通过文件构建的Entity,参数传入文件和文件类型

FileEntity entity = new FileEntity(new File(""), "application/java-achive");

其中代表是流式不可以重复的实体类有 3.1)InputreamEntity:构造方法是InputStream 和内容长度,内容长度是输入流的长度

InputStream is = null; //InputStreamEntity严格是对内容和长度相匹配的。用法和BasicHttpEntity类似 InputStreamEntity entity = new InputStreamEntity(is, is.available());

综述:各种Entity,比如FileEntity,StringEntity等,这些都是实现了HttpEntity接口,同时实现自己的一些方法罢了;上面的实体的类型,如代表底层流的实体他们只是在构造上不同罢了,不同的创建方式决定这个Entity是不是可重用,是不是一块一块的等等;还有一些类如HttpEntityWrapper,BufferedHttpEntity,这些都是对各种Entity的包装;下图列明了各种Entity的含义

在我们使用实体之后,我们还需要对实体进行管理,在httpclient中,有一个EntityUtils的类,他是一个静态的对象用来我们处理Entity 这个类在使用的时候,我们应该注意下,在Android使用的HttpClient中他没有consume方法,因此在使用完entity的时候,需要注意的entity的释放

-释放资源 正确释放系统资源,我们应该做到释放response本身和响应实体本身的流,关闭内容流和关闭响应的区别在于,前者会尝试通过消耗实体内容来保持底层连接的活动状态,而后者会立即关闭并丢弃连接。

CloseableHttpClient httpclient = HttpClients.createDefault(); HttpGet httpget = new HttpGet("http://localhost/"); CloseableHttpResponse response = httpclient.execute(httpget); try { HttpEntity entity = response.getEntity(); if (entity != null) { InputStream instream = entity.getContent(); try { // do something useful } finally { instream.close(); } } } finally { response.close(); }

而有的时候,我们可能需要不止一次阅读实体内容。 在这种情况下,实体内容必须以某种方式进行缓冲,无论是在内存中还是在磁盘上。 最简单的方法是用BufferedHttpEntity类包装原始实体。 这会导致原始实体的内容被读入内存缓冲区。

CloseableHttpResponse response = <...> HttpEntity entity = response.getEntity(); if (entity != null) { entity = new BufferedHttpEntity(entity); }

3.4)HttpClient的线程安全 建议使用同一个HttpClient的实例来确保线程安全

3.5)HttpContext 这是一个Http请求上下文类,如果是同一个上下文,则两次请求间可以共享这个上线文保存的信息。有的同学会问,通一个httpclient本身就具备维护cookies的功能,为什么要用HttpContext?其实HttpContext的妙用就是在于多个httpclient实例之间可以共享httpcontext。

HttpContext和Map很相似,通过key值来完成多个会话之间的数据共享;通过HttpContext封装信息之后,如果代表一个逻辑相关的会话中的多个请求序列被同一个HttpContext实例执行,则请求之间会话上下文和状态信息可以自动传输。

/*演示使用同一个httpcontext,设置请求参数,多个请求之间共享这个参数设置,即httpget1和httpget2共享了requestConfig,因为此处他们使用的是同一个context*/ CloseableHttpClient httpclient = HttpClients.createDefault(); RequestConfig requestConfig = RequestConfig.custom() .setSocketTimeout(1000) .setConnectTimeout(1000) .build(); HttpGet httpget1 = new HttpGet("http://localhost/1"); httpget1.setConfig(requestConfig); CloseableHttpResponse response1 = httpclient.execute(httpget1, context); try { HttpEntity entity1 = response1.getEntity(); } finally { response1.close(); } HttpGet httpget2 = new HttpGet("http://localhost/2"); CloseableHttpResponse response2 = httpclient.execute(httpget2, context); try { HttpEntity entity2 = response2.getEntity(); } finally { response2.close(); }

这里我们配置了请求参数,同理你也可以配置其他参数,如下

CloseableHttpClient httpClient = HttpClients.createDefault(); try { List<NameValuePair> params = new ArrayList<NameValuePair>(); params.add(new BasicNameValuePair("username", "**")); UrlEncodedFormEntity entity = new UrlEncodedFormEntity(params,"UTF-8"); HttpPost httpPost = new HttpPost("url"); httpPost.setEntity(entity); CloseableHttpResponse response = httpClient.execute(httpPost,context); HttpEntity responseEntity = response.getEntity(); } finally { httpClient.close(); response.close(); } CloseableHttpClient httpClient2 = HttpClients.createDefault(); try { HttpGet httpGet = new HttpGet("url"); CloseableHttpResponse response = httpClient2.execute(httpGet,context); HttpEntity entity = response.getEntity(); } finally { httpClient2.close(); response.close(); }

3.6)HTTP 拦截器 这个拦截器可以设置在请求发送之前,也可以设置在获取到响应实体之后,在android中我们可以通过继承AbstractHttpClient类,并实现addRequestInterceptor和addResponseInterceptor两个方法实现目的

public class Client extends DefaultHttpClient { private static ClientConnectionManager mClientConnectionManager; public Client(Handler handler) { super(makeClientConnectionManager(), new BasicHttpParams()); // gzip stream support,添加请求拦截 addRequestInterceptor(new HttpRequestInterceptor() { public void process(final HttpRequest request, final HttpContext context) throws HttpException, IOException { if (!request.containsHeader("Accept-Encoding")) { request.addHeader("Accept-Encoding", "gzip"); } } }); //响应拦截 addResponseInterceptor(new HttpResponseInterceptor() { public void process(final HttpResponse response, final HttpContext context) throws HttpException, IOException { HttpEntity entity = response.getEntity(); if (entity != null) { Header ceheader = entity.getContentEncoding(); if (ceheader != null) { HeaderElement[] codecs = ceheader.getElements(); if (codecs != null) { for (int i = 0; i < codecs.length; i++) { if (codecs[i].getName().equalsIgnoreCase("gzip")) { response.setEntity(new GzipDecompressingEntity(response.getEntity())); return; } } } } } } }); } } static final ClientConnectionManager makeClientConnectionManager() { SchemeRegistry schemeRegistry = new SchemeRegistry(); schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80)); schemeRegistry.register(new Scheme("https", SSLSocketFactory.getSocketFactory(), 443)); mClientConnectionManager = new ThreadSafeClientConnManager(new BasicHttpParams(), schemeRegistry); return mClientConnectionManager; }

3.7)HTTP的安全性和幂等性 安全性仅指该方法的多次调用不会产生副作用,不涉及传统意义上的“安全”,这里的副作用是指资源状态。即,安全的方法不会修改资源状态,尽管多次调用的返回值可能不一样(被其他非安全方法修改过)。幂等性,是指该方法多次调用返回的效果(形式)一致,客户端可以重复调用并且期望同样的结果

3.8)Http的异常恢复机制 默认情况下,HttpClient会尝试从I / O异常中自动恢复。 默认的自动恢复机制仅限于少数已知安全的例外。 3.8.1)HttpClient不会尝试从任何逻辑或HTTP协议错误(从HttpException类派生的错误)中恢复。 3.8.2)HttpClient会自动重试那些被认为是幂等的方法。 3.8.3)当HTTP请求仍然被传送到目标服务器时(即请求没有完全传送到服务器),HttpClient会自动重试那些传送异常失败的方法。

我们可以使用如下代码进行异常恢复

HttpRequestRetryHandler myRetryHandler = new HttpRequestRetryHandler() { public boolean retryRequest( IOException exception, int executionCount, HttpContext context) { if (executionCount >= 5) { // Do not retry if over max retry count return false; } if (exception instanceof InterruptedIOException) { // Timeout return false; } if (exception instanceof UnknownHostException) { // Unknown host return false; } if (exception instanceof ConnectTimeoutException) { // Connection refused return false; } if (exception instanceof SSLException) { // SSL handshake exception return false; } HttpClientContext clientContext = HttpClientContext.adapt(context); HttpRequest request = clientContext.getRequest(); boolean idempotent = !(request instanceof HttpEntityEnclosingRequest); if (idempotent) { // Retry if the request is considered idempotent return true; } return false; } }; CloseableHttpClient httpclient = HttpClients.custom() .setRetryHandler(myRetryHandler) .build();

同时我们可以使用StandardHttpRequestRetryHandler替代默认的HttpRequestRetryHandler,以便将那些被RFC-2616定义为幂等的请求方法视为安全自动重试:GET,HEAD,PUT,DELETE,OPTIONS和TRACE

转载请注明原文地址: https://www.6miu.com/read-2623943.html

最新回复(0)