HttpClient基础知识(java爬虫03)

来源:互联网 发布:剑三帅气成男脸型数据 编辑:程序博客网 时间:2024/05/02 04:17

翻译文档:
http://hc.apache.org/httpcomponents-client-4.5.x/tutorial/html/fundamentals.html

1. 请求执行:

HttpClient最重要的功能是执行HTTP方法。执行HTTP方法涉及一个或多个HTTP请求/ HTTP响应交换,通常由HttpClient内部处理。用户期望提供一个请求对象来执行,并且希望HttpClient将请求发送到目标服务器返回相应的响应对象,如果执行失败则抛出异常。
很自然,HttpClient API的主要入口点是定义上述合同的HttpClient接口。
这是一个请求执行过程的一个例子,它的最简单的形式是:

CloseableHttpClient httpclient = HttpClients.createDefault();HttpGet httpget = new HttpGet("http://localhost/");CloseableHttpResponse response = httpclient.execute(httpget);try {    <...>} finally {    response.close();}

1.1. HTTP请求

所有HTTP请求都有一个请求行,包括方法名称,请求URI和HTTP协议版本。
HttpClient的支持了在HTTP / 1.1规范中定义的所有HTTP方法的框的:GET,HEAD, POST,PUT,DELETE, TRACE和OPTIONS。没有为每个方法类型:一个特定的类HttpGet, HttpHead,HttpPost, HttpPut,HttpDelete, HttpTrace,和HttpOptions。
Request-URI是统一资源标识符,用于标识应用请求的资源。HTTP请求URI由协议方案,主机名,可选端口,资源路径,可选查询和可选片段组成。

URI uri = new URIBuilder()        .setScheme("http")        .setHost("www.google.com")        .setPath("/search")        .setParameter("q", "httpclient")        .setParameter("btnG", "Google Search")        .setParameter("aq", "f")        .setParameter("oq", "")        .build();HttpGet httpget = new HttpGet(uri);System.out.println(httpget.getURI());

1.2. HTTP响应

HTTP响应是在接收和解释请求消息之后由服务器发送回客户端的消息。该消息的第一行包括协议版本,后跟数字状态代码及其关联的文本短语。

HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK");System.out.println(response.getProtocolVersion());System.out.println(response.getStatusLine().getStatusCode());System.out.println(response.getStatusLine().getReasonPhrase());System.out.println(response.getStatusLine().toString());//输出结果/*HTTP/1.1200OKHTTP/1.1 200 OK*/

1.3. 处理消息头

HTTP消息可以包含描述消息属性的多个头部,如内容长度,内容类型等。HttpClient提供了检索,添加,删除和枚举头文件的方法。
获取给定类型的所有标头的最有效的方法是使用 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()); }

它还提供了方便的方法来将HTTP消息解析为单独的头元素。

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 \""); 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]);     } }

1.4. HTTP实体

HTTP消息可以携带与请求或响应相关联的内容实体。实体可以在一些请求和一些响应中找到,因为它们是可选的。使用实体的请求被称为实体封装请求。HTTP规范定义了两个实体封装请求方法:POST和 PUT。响应通常期望包含内容实体。有例外的情况,如应对 HEAD方法204 No Content, 304 Not Modified,205 Reset Content 响应。
HttpClient根据其内容来源区分三种实体:

  • streamed: 内容是从流中接收的,或者即时生成的。特别地,该类别包括从HTTP响应接收到的实体。流式实体通常不可重复。
  • self-contained: 内容在内存中或通过独立于连接或其他实体的方式获取。自包含的实体通常是可重复的。这种类型的实体将主要用于封闭HTTP请求的实体。
  • wrapping: 内容是从另一个实体获得的。

当从HTTP响应流出内容时,此区别对于连接管理很重要。对于由应用程序创建并且仅使用HttpClient发送的请求实体,流和独立的区别不重要。在这种情况下,建议将不可重复的实体视为流式传输,将可重复的实体视为独立的。

1.4.1. 可重复的实体

实体可以是可重复的,这意味着它的内容可以被读取不止一次。这是唯一可能的自包含的实体(像 ByteArrayEntity或 StringEntity)

1.4.2. 使用HTTP实体

由于实体可以表示二进制和字符内容,它支持字符编码(以支持后者,即字符内容)。
当执行带有封闭内容的请求时,或者当请求成功并且使用响应主体将结果发送回客户端时,实体被创建。
要从实体读取内容,可以通过HttpEntity.getContent()方法来检索输入流,该方法返回一个java.io.InputStream,或者可以向HttpEntity.writeTo(OutputStream)方法提供输出流,一旦所有内容已写入给定流,该方法将返回。
当实体已经接收到传入消息时,方法 HttpEntity.getContentType()和 HttpEntity.getContentLength()方法可用于读取公共元数据,如头Content-Type和 Content-Length头(如果可用)。由于 Content-Type标题可以包含text / plain或text / html等文本MIME类型的字符编码,因此该 HttpEntity.getContentEncoding()方法用于读取此信息。如果标题不可用,则返回长度为-1,内容类型为NULL。如果Content-Type 标题可用,Header将返回一个对象。
当为外发消息创建实体时,该元数据必须由实体的创建者提供。

StringEntity myEntity = new StringEntity("important message",    ContentType.create("text/plain", "UTF-8"));System.out.println(myEntity.getContentType());System.out.println(myEntity.getContentLength());System.out.println(EntityUtils.toString(myEntity));System.out.println(EntityUtils.toByteArray(myEntity).length);

1.5. 确保发布低级别资源

为了确保系统资源的正确释放,必须关闭与实体或响应本身相关联的内容流

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{             //做一些有用的事情        } finally {             instream.close();         }     } } finally {     response.close(); }

关闭内容流和关闭响应之间的区别在于,前者将尝试通过占用实体内容来保持底层连接,而后者会立即关闭并放弃连接。
请注意,HttpEntity.writeTo(OutputStream) 一旦实体完全写出,还需要确保正确释放系统资源的方法。如果此方法获取一个java.io.InputStream通过调用 的实例 HttpEntity.getContent(),那么也希望在finally子句中关闭流。
当使用流实体时,可以使用该 EntityUtils.consume(HttpEntity)方法来确保实体内容已被完全消耗,底层流已经被关闭。
然而,可能会有情况,当只需要检索整个响应内容的一小部分时,消耗剩余内容并使连接可重用的性能损失太高,在这种情况下,可以通过关闭终止内容流响应。

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();         int byteOne = instream.read();         int byteTwo = instream.read();         //不需要休息    } } finally {     response.close(); }

连接不会重复使用,但由其持有的所有级别资源将被正确地分配。

1.6. 消费实体内容

消费实体内容的推荐方法是使用它 HttpEntity.getContent()或 HttpEntity.writeTo(OutputStream)方法。HttpClient还附带了EntityUtils类,它暴露了几种静态方法,以便更容易地从实体读取内容或信息。java.io.InputStream可以使用这个类的方法,而不是直接读取,而不是直接读取字符串/字节数组中的整个内容正文。但是,EntityUtils除非响应实体来自可信HTTP服务器,而且已知其长度有限,否则强烈建议不要使用此功能。

CloseableHttpClient httpclient = HttpClients.createDefault(); HttpGet httpget = new HttpGet("http:// localhost /"); CloseableHttpResponse response = httpclient.execute(httpget); try{     HttpEntity entity = response.getEntity();     if(entity!= null){         long len = entity.getContentLength();         if(len!= -1 && len <2048){             System.out.println(EntityUtils.toString(entity));         } else {             // Stream content out         }     } } finally {     response.close(); }

在某些情况下,可能需要多次读取实体内容。在这种情况下,实体内容必须以某种方式缓存,无论是在内存还是在磁盘上。最简单的方法是通过用BufferedHttpEntity类包装原始实体。这将导致将原始实体的内容读入内存缓冲区。在所有其他方面,实体包装器将具有原始包装器。

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

1.7. 制作实体内容

HttpClient提供了几个类,可以通过HTTP连接高效地流出内容。这些类的实例可以与实体包围请求,如相关联POST并PUT 以包围实体内容分成传出HTTP请求。HttpClient的提供了几个类为最常见的数据的容器,如字符串,字节数组,输入流,和文件:StringEntity, ByteArrayEntity, InputStreamEntity,和 FileEntity。

File file = new File("somefile.txt"); FileEntity entity = new FileEntity(file,    ContentType.create("text / plain","UTF-8"));        HttpPost httppost = new HttpPost("http://localhost/action.do"); httppost.setEntity(entity);

1.7.1 HTML表单

许多应用程序需要模拟提交HTML表单的过程,例如,以登录到Web应用程序或提交输入数据。HttpClient提供实体类 UrlEncodedFormEntity来促进进程。

List <NameValuePair> formparams = new ArrayList <NameValuePair>(); formparams.add(new BasicNameValuePair("param1","value1")); formparams.add(new BasicNameValuePair("param2","value2")); UrlEncodedFormEntity entity = new UrlEncodedFormEntity(formparams,Consts.UTF_8); HttpPost httppost = new HttpPost("http://localhost/handler.do"); httppost.setEntity(entity);

该UrlEncodedFormEntity实例将使用所谓的URL编码来对参数进行编码并产生以下内容:

param1=value1&param2=value2

1.7.2. 内容分块

一般建议让HttpClient根据正在传输的HTTP消息的属性选择最合适的传输编码。然而,可以通知HttpClient,通过设置HttpEntity.setChunked()为true,优先选择块编码。请注意,HttpClient只会使用此标志作为提示。当使用不支持块编码的HTTP协议版本(如HTTP / 1.0)时,此值将被忽略。

StringEntity entity = new StringEntity("important message",        ContentType.create("plain / text",Consts.UTF_8)); entity.setChunked(true); HttpPost httppost = new HttpPost("http://localhost/acrtion.do"); httppost.setEntity(entity);

1.8. 响应处理程序

处理响应的最简单和最方便的方法是使用ResponseHandler包含该handleResponse(HttpResponse response)方法的界面。这种方法完全可以缓解用户不必担心连接管理。使用ResponseHandlerHttpClient 时 ,无论请求执行是成功还是导致异常,HttpClient都会自动保证将连接释放回连接管理器。

CloseableHttpClient httpclient = HttpClients.createDefault();HttpGet httpget = new HttpGet("http://localhost/json");ResponseHandler<MyJsonObject> rh = new ResponseHandler<MyJsonObject>() {    @Override    public JsonObject 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");        }        Gson gson = new GsonBuilder().create();        ContentType contentType = ContentType.getOrDefault(entity);        Charset charset = contentType.getCharset();        Reader reader = new InputStreamReader(entity.getContent(), charset);        return gson.fromJson(reader, MyJsonObject.class);    }};MyJsonObject myjson = client.execute(httpget, rh);

2. HttpClient接口

HttpClient接口代表HTTP请求执行最基本的合同。它对请求执行过程没有施加任何限制或特定细节,并留下连接管理,状态管理,身份验证和重定向处理的具体细节。这应该使得更容易用诸如响应内容缓存的附加功能来装饰接口。
通常,HttpClient实现作为一些专门的处理程序或策略接口实现的外观,负责处理HTTP协议的特定方面,例如重定向或身份验证处理或决定连接持久性并保持活动持续时间。这使得用户能够选择性地将这些方面的默认实现替换为具有特定应用程序的那些方面。

ConnectionKeepAliveStrategy keepAliveStrat = new DefaultConnectionKeepAliveStrategy(){     @Override     public long getKeepAliveDuration(            HttpResponse response,            HttpContext context){         long keepAlive = super.getKeepAliveDuration(response,context);         if(keepAlive == -1){             //如果一个保持活动值            //未被服务器明确设置,则保持连接活动5秒;             KeepAlive = 5000         }         return keepAlive;     } }; CloseableHttpClient httpclient = HttpClients.custom().         setKeepAliveStrategy(keepAliveStrat).build         ();

2.1. HttpClient线程安全

HttpClient实现预期是线程安全的。建议将此类的同一个实例重复用于多个请求执行

2.2. HttpClient资源释放

当一个实例CloseableHttpClient不再需要并且即将超出范围时,与它关联的连接管理器必须通过调用该CloseableHttpClient.close() 方法来关闭。

CloseableHttpClient httpclient = HttpClients.createDefault();try {    <...>} finally {    httpclient.close();}

3. HTTP执行上下文

最初HTTP被设计为无状态的,响应请求的协议。然而,现实世界的应用程序通常需要通过几个逻辑相关的请求 - 响应交换来保持状态信息。为了使应用程序能够保持处理状态HttpClient允许在特定执行上下文(称为HTTP上下文)中执行HTTP请求。如果在连续请求之间重复使用相同的上下文,则多个逻辑相关请求可以参与逻辑会话。HTTP上下文功能类似于a java.util.Map

HttpContext context = <...> HttpClientContext clientContext = HttpClientContext.adapt(context); HttpHost target = clientContext.getTargetHost(); HttpRequest request = clientContext.getRequest(); HttpResponse response = clientContext.getResponse(); RequestConfig config = clientContext.getRequestConfig();

表示逻辑相关会话的多个请求序列应该使用相同的HttpContext实例来执行,以确保在请求之间自动传播会话上下文和状态信息。
在下面的示例中,由初始请求设置的请求配置将保留在执行上下文中,并被传播到共享相同上下文的连续请求。

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

4. HTTP协议拦截器

HTTP协议拦截器是一个实现HTTP协议特定方面的例程。通常,协议拦截器预期作用于输入消息的一个特定头部或一组相关头部,或者使用一个特定头部或一组相关头部填充传出消息。协议拦截器还可以操纵包含消息的内容实体 - 透明内容压缩/解压缩就是一个很好的例子。通常这是通过使用’装饰器’模式来实现的,其中包装器实体类用于装饰原始实体。几个协议拦截器可以组合形成一个逻辑单元。
协议拦截器可以通过HTTP执行上下文共享信息(如处理状态)进行协作。协议拦截器可以使用HTTP上下文来存储一个请求或几个连续请求的处理状态。
通常执行拦截器的顺序不要紧,因为它们不依赖执行上下文的特定状态。如果协议拦截器具有相互依赖关系,因此必须以特定顺序执行,则应将它们按照与其预期执行顺序相同的顺序添加到协议处理器。
协议拦截器必须实现为线程安全。与servlet类似,协议拦截器不应该使用实例变量,除非对这些变量的访问是同步的。
这是一个示例,说明如何使用本地上下文来持续连续请求之间的处理状态:

CloseableHttpClient httpclient = HttpClients.custom()        .addInterceptorLast(new HttpRequestInterceptor() {            public void process(                    final HttpRequest request,                    final HttpContext context) throws HttpException, IOException {                AtomicInteger count = (AtomicInteger) context.getAttribute("count");                request.addHeader("Count", Integer.toString(count.getAndIncrement()));            }        })        .build();AtomicInteger count = new AtomicInteger(1);HttpClientContext localContext = HttpClientContext.create();localContext.setAttribute("count", count);HttpGet httpget = new HttpGet("http://localhost/");for (int i = 0; i < 10; i++) {    CloseableHttpResponse response = httpclient.execute(httpget, localContext);    try {        HttpEntity entity = response.getEntity();    } finally {        response.close();    }}

5. 异常处理

HTTP协议处理器可以抛出两种类型的异常: java.io.IOException在I / O故障(例如套接字超时或套接字复位)的情况下,并HttpException发出HTTP故障,例如违反HTTP协议。通常I / O错误被认为是非致命和可恢复的,而HTTP协议错误被认为是致命的,不能自动恢复。请注意,HttpClient 实现重新抛出HttpExceptions ClientProtocolException,它是一个子类java.io.IOException。这使用户能够HttpClient从单个catch子句处理I / O错误和协议违规。

5.1. HTTP运输安全

重要的是要了解HTTP协议不太适合所有类型的应用程序。HTTP是一种简单的请求/响应导向协议,最初被设计为支持静态或动态生成的内容检索。它从来没有意图支持交易操作。例如,如果HTTP服务器成功接收和处理请求,生成响应并将状态代码发送回客户端,则HTTP服务器将考虑其部分合同。如果客户端由于读取超时,请求取消或系统崩溃而无法全部收到响应,服务器将不会尝试回滚事务。如果客户端决定重试相同的请求,服务器将不可避免地最终不止一次执行相同的事务。在某些情况下,这可能导致应用程序数据损坏或应用程序状态不一致。
即使HTTP从未被设计为支持事务处理,但如果满足某些条件,它仍然可以用作任务关键应用程序的传输协议。为了确保HTTP传输层的安全,系统必须确保应用层上的HTTP方法的等效性。

5.2. 幂等方法

HTTP / 1.1规范定义了一个幂等方法
[ 方法也可以具有”幂等”的属性(除了错误或到期问题),N> 0相同请求的副作用与单个请求相同)
换句话说,应用程序应该确保它准备好处理多个执行相同方法的含义。这可以通过例如提供唯一的事务ID和避免执行相同的逻辑操作的其他手段来实现。
请注意,这个问题不是HttpClient特有的。基于浏览器的应用程序与HTTP方法非幂等性有完全相同的问题。
默认情况下,HttpClient只假定非实体封闭方法,例如 GET并且HEAD是幂等的和实体封闭的方法,POST而PUT不是出于兼容性的原因。

5.3. 自动异常恢复

默认情况下,HttpClient尝试自动从I / O异常恢复。默认的自动恢复机制仅限于一些已知是安全的异常。
HttpClient将不会尝试从任何逻辑或HTTP协议错误(派生自HttpException类)中恢复 。
HttpClient会自动重试那些假定为幂等的方法。
当HTTP请求仍被传送到目标服务器(即请求尚未完全发送到服务器)时,HttpClient将自动重试那些失败的传输异常方法

5.4. 请求重试处理程序

为了启用自定义异常恢复机制,应该提供HttpRequestRetryHandler 接口的实现。

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 替代默认使用的一个,以治疗由RFC-2616定义为幂的安全要求的那些方法来自动重试:GET, HEAD,PUT,DELETE, OPTIONS,和TRACE。

6. 中止请求

在某些情况下,由于目标服务器的负载高或客户端发出的并发请求太多,HTTP请求执行在预期时间内无法完成。在这种情况下,可能需要提前终止请求并解除在I / O操作中阻止的执行线程。HttpClient执行的HTTP请求可以通过调用HttpUriRequest.abort()方法在执行的任何阶段中止 。这种方法是线程安全的,可以从任何线程调用。当一个HTTP请求被中止时,它的执行线程即使当前在I / O操作中被阻塞,也可以通过抛出一个 InterruptedIOException

7. 重定向处理

HttpClient自动处理所有类型的重定向,除了需要用户干预的HTTP规范明确禁止的那些。See Other(状态码303)重定向上POST和 PUT请求转换为GET所要求的HTTP规范的请求。可以使用自定义重定向策略来放宽对HTTP规范强加的POST方法的自动重定向的限制。

LaxRedirectStrategy redirectStrategy = new LaxRedirectStrategy(); CloseableHttpClient httpclient = HttpClients.custom().         setRedirectStrategy(redirectStrategy).build();

HttpClient经常在其执行过程中重写请求消息。默认情况下,HTTP / 1.0和HTTP / 1.1通常使用相对请求URI。同样,原始请求可能会从位置重定向到另一次。最终解释的绝对HTTP位置可以使用原始请求和上下文构建。该实用程序方法URIUtils.resolve可用于构建用于生成最终请求的解释绝对URI。该方法包括来自重定向请求或原始请求的最后一个片段标识符。

CloseableHttpClient httpclient = HttpClients.createDefault();HttpClientContext context = HttpClientContext.create();HttpGet httpget = new HttpGet("http://localhost:8080/");CloseableHttpResponse response = httpclient.execute(httpget, context);try {    HttpHost target = context.getTargetHost();    List<URI> redirectLocations = context.getRedirectLocations();    URI location = URIUtils.resolve(httpget.getURI(), target, redirectLocations);    System.out.println("Final HTTP location: " + location.toASCIIString());    // Expected to be an absolute URI} finally {    response.close();}