• httpclient4.5 的一些细节


    本文转自:http://mercymessi.iteye.com/blog/2250161

    httpclient是Apache下的一个用于执行http网络访问的一个工具包。

    大致流程:新建一个httpclient对象->新建一个httpRequest对象->用httpclient去执行->得到一个response->通过解析这个response来获取自己所需要的信息。

    一、新建httpClient对象:

    在httpClient4.5中,初始化的方式已经和以前版本有一些不同。

    有大致以下几种方式:

    Java代码  收藏代码
    1. static  CloseableHttpClient client = HttpClients.createDefault();  
    2. //最好使用static修饰,以保证用同一个client对象处理请求,以保存进度  
    Java代码  收藏代码
    1. static CloseableHttpClient httpClient=HttpClients.custom().build();  

     此二种都是新建一个默认的httpClient对象。可以在第二种方法里添加一些网络访问选项设置。

    Java代码  收藏代码
    1. /** 
    2.      * initialize a instance of the httpClient depending on your own request 
    3.      */  
    4.     private static CloseableHttpClient getInstanceClient() {  
    5.         CloseableHttpClient httpClient;  
    6.                 StandardHttpRequestRetryHandler standardHandler = new StandardHttpRequestRetryHandler(5, true);  
    7.         HttpRequestRetryHandler handler = new HttpRequestRetryHandler() {  
    8.   
    9.             @Override  
    10.             public boolean retryRequest(IOException arg0, int retryTimes, HttpContext arg2) {  
    11.                 if (arg0 instanceof UnknownHostException || arg0 instanceof ConnectTimeoutException  
    12.                         || !(arg0 instanceof SSLException) || arg0 instanceof NoHttpResponseException) {  
    13.                     return true;  
    14.                 }  
    15.                 if (retryTimes > 5) {  
    16.                     return false;  
    17.                 }  
    18.                 HttpClientContext clientContext = HttpClientContext.adapt(arg2);  
    19.                 HttpRequest request = clientContext.getRequest();  
    20.                 boolean idempotent = !(request instanceof HttpEntityEnclosingRequest);  
    21.                 if (idempotent) {  
    22.                     // 如果请求被认为是幂等的,那么就重试。即重复执行不影响程序其他效果的  
    23.                     return true;  
    24.                 }  
    25.                 return false;  
    26.             }  
    27.         };  
    28.         HttpHost proxy = new HttpHost("127.0.0.1", 80);// 设置代理ip  
    29.         DefaultProxyRoutePlanner routePlanner = new DefaultProxyRoutePlanner(proxy);  
    30.         httpClient = HttpClients.custom().setRoutePlanner(routePlanner).setRetryHandler(handler)  
    31.                 .setConnectionTimeToLive(1, TimeUnit.DAYS).setDefaultCookieStore(cookieStore).build();  
    32.         return httpClient;  
    33.     }  

     在该代码中分别设置了网络代理,重试处理,对于请求的keepalive时间,指定cookiestore用于保存cookie。

    retryHandler:代码里给了两种方式。第一个是简便的用于设置重试,第一个参数为最大重试次数,第二个参数为请求在幂等情况下是否重试。第二种方式详细的规定了在发生了什么exception个下重试,以及幂等和重试次数下的重试情况。

    routePlanner:httpClient支持代理。新建一个httphost对象传给一个routeplanner对象即可。httphost的构造方法中可以指定代理ip和端口

    CookieStore:需要预先新建一个cookieStore对象。初始化方式如下:

    Java代码  收藏代码
    1. CookieStore cookieStore = new BasicCookieStore();  

    二、执行get请求:

    先上代码

    Java代码  收藏代码
    1. /** 
    2.      * used to get the html code from the url 
    3.      */  
    4. static RequestConfig config = RequestConfig.custom().setConnectTimeout(6000).setSocketTimeout(6000)  
    5.             .setCookieSpec(CookieSpecs.STANDARD).build(); // 设置超时及cookie策略  
    6.     public static String getDemo(String url) {  
    7.         HttpGet get = new HttpGet(url);  
    8.         get.setConfig(config);  
    9.         HttpResponse response = null;  
    10.         String html = null;  
    11.         try {  
    12.             response = client.execute(get);  
    13.             int statusCode = response.getStatusLine().getStatusCode();// 连接代码  
    14.             Header[] headers = response.getAllHeaders();  
    15.             // 用于得到返回的文件头  
    16.             for (Header header : headers) {  
    17.                 System.out.println(header);  
    18.             }  
    19.             html = new String(EntityUtils.toString(response.getEntity()).getBytes("iso8859-1"), "gb2312");  
    20.             // 在后面参数输入网站的编码,一般为utf-8  
    21.             // 返回的html代码,避免发生编码错误  
    22.             System.out.println(html);  
    23.         } catch (IOException e) {  
    24.             e.printStackTrace();  
    25.         }  
    26.         return html;  
    27.     }  

     大致流程:新建httpget对象->用httpClient执行->解析返回的response得到自己需要的内容

    cookieSpec:即cookie策略。参数为cookiespecs的一些字段。作用:1、如果网站header中有set-cookie字段时,采用默认方式可能会被cookie reject,无法写入cookie。将此属性设置成CookieSpecs.STANDARD_STRICT可避免此情况。2、如果要想忽略cookie访问,则将此属性设置成CookieSpecs.IGNORE_COOKIES。

    tips:注意网站编码,否则容易出现乱码

    也可以通过uri进行构建httpget:

    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());//http://www.google.com/search?q=httpclient&btnG=Google+Search&aq=f&oq=

    三、执行post请求:

    Java代码  收藏代码
    1. /** 
    2.      * used to post form data which is the url needed 
    3.      */  
    4.     public static void postDemo(String url) {  
    5.         HttpPost post = new HttpPost(url);  
    6.         post.setConfig(config);  
    7.         post.setHeader("User-Agent",  
    8.                 "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.93 Safari/537.36");  
    9.         post.setHeader("Connection", "keep-alive");  
    10.         List<NameValuePair> list = new ArrayList<NameValuePair>();  
    11.         list.add(new BasicNameValuePair("key", "value"));  
    12.         list.add(new BasicNameValuePair("key", "value"));  
    13.         list.add(new BasicNameValuePair("key", "value"));  
    14.         list.add(new BasicNameValuePair("key", "value"));  
    15.         list.add(new BasicNameValuePair("key", "value"));  
    16.         try {  
    17.             HttpEntity entity = new UrlEncodedFormEntity(list, "utf-8");  
    18.             post.setEntity(entity);  
    19.             HttpResponse response = client.execute(post);  
    20.             String responseHtml = EntityUtils.toString(response.getEntity());  
    21.             System.out.println(responseHtml);  
    22.         } catch (IOException e) {  
    23.             e.printStackTrace();  
    24.         }  
    25.     }  

     大致流程:新建post对象->新建需要的表单页->将表单内容设置入请求中->执行并获得response

    四:解析response:

    得到html code:

    Java代码  收藏代码
    1. String responseHtml = EntityUtils.toString(response.getEntity());  

     得到http状态码:

    Java代码  收藏代码
    1. int statusCode = response.getStatusLine().getStatusCode();// 连接返回状态代码,200,400等
    2. System.out.println(response.getProtocolVersion());//HTTP/1.1

    3. System.out.println(response.getStatusLine().getReasonPhrase());//OK

    4. System.out.println(response.getStatusLine().toString());//HTTP/1.1 200 OK

     得到response header:

    Java代码  收藏代码
    1. response.getFirstHeader("key");// 得到第一个名字为key的header  
    2.             response.getHeaders("key");// 得到名字为key的所有header,返回一个数组  
    3.             response.getLastHeader("key");  

    得到inputstream:(下载网络部分资源的时候有可能会对cookie有要求,此时需要用到httpClient来下载。)例如验证码等等。

    Java代码  收藏代码
    1. InputStream inputStream = response.getEntity().getContent();  

    五:管理cookie:

    httpClient里默认自动管理cookie,如果想要提取cookie或者发送自定义的cookie,则需要在httpClient对象初始化时设置一个默认的cookiestore来保存。(方法见初始化httpClient对象里的setDefaultCookieStore)。

    得到当前所有cookie:

    Java代码  收藏代码
    1. List<Cookie> list = cookieStore.getCookies();// get all cookies  
    2.         System.out.println("cookie is:");  
    3.         System.out.println("-----------------------");  
    4.         for (Cookie cookie : list) {  
    5.             System.out.println(cookie);  
    6.         }  
    7.         System.out.println("-----------------------");  

     清除所有cookie:

    Java代码  收藏代码
    1. cookieStore.clear();  

     发送自定义cookie:(new了一个对象之后可以设置多种属性。)

    Java代码  收藏代码
    1. BasicClientCookie cookie = new BasicClientCookie("name", "value");  
    2.         // new a cookie  
    3.         cookie.setDomain("domain");  
    4.         cookie.setExpiryDate(new Date());  
    5.         // set the properties of the cookie  
    6.                 cookieStore.addCookie(cookie);  

     最后通过按得到addCookie将其加入cookieStore。(如有相同的name的cookie将会覆盖,个人觉得类似hashmap的put操作。)

    六:管理header:

    在平常抓取过程中,经常需要在请求中加入许多header伪装成一个正常的浏览器。以免被服务器认出是爬虫而被封。

    设置一些常见header:

    Java代码  收藏代码
    1. post.setHeader("User-Agent",  
    2.                 "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.93 Safari/537.36");  
    3.         post.setHeader("Connection", "keep-alive");  

    注意:下载某些网站的资源时,服务器会获取你的来源站,并发出对应的相应。如果来源站不对,可能会被服务器拒绝。此时只需要在请求中加个header就行。

    Java代码  收藏代码
    1. get1.setHeader("Referer", "http://www.a.com");
    2. response.addHeader("Set-Cookie",
      "c1=a; path=/; domain=localhost");

    3. response.addHeader("Set-Cookie",
      "c2=b; path="/", c3=c; domain="localhost"");

    4. Header h1 = response.getFirstHeader("Set-Cookie");
      System.out.println(h1);//Set-Cookie: c1=a; path=/; domain=localhost

    5. Header h2 = response.getLastHeader("Set-Cookie");
      System.out.println(h2);//Set-Cookie: c2=b; path="/", c3=c; domain="localhost"

    6. Header[] hs = response.getHeaders("Set-Cookie");
      System.out.println(hs.length);//2

    7. HeaderIterator it = response.headerIterator("Set-Cookie");

      while (it.hasNext()) {
      System.out.println(it.next());
      }

    8. 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]);
      }
      }

    HTTP实体

    HTTP消息可以包含内容实体,HTTP定义了两个实体封装请求方法:PUT和POST。HttpClient依靠内容的来源来区分三种实体。
    streamed:内容来源于流或者动态生成,特别是,包含从HTTP响应接收的实体,streamed实体一般不可重复生成的。
    self-contained:内容位于内存中或者是可获得的,意味着它是独立于连接和其他实体的,Self-contained实体一般可重复,这种类型的实体大都用于HTTP请求的封装。
    wrapping:内容来源于其他实体。
    对于连接管理来说,当从HTTP响应中用流输出内容的时候这些区分的重要的。对于仅仅由应用程序创建并且用HttpClient发送的请求实体来说,streamed和self-contained的区别是不重要的。既然如此,那么就认为不可重复的实体是streamed,可重复的实体是self-contained。
    可重复的实体,表示它的内容可以不止一次被读取,例如ByteArrayEntity和StringEntity。为了读取内容,任何人都可以使用HttpEntity#getContent()返回java.io.InputStream,或者用HttpEntity#writeTo(OutputStream)提供给输出流。
    当实体通过一个收到的报文获取时,HttpEntity#getContentType()方法和HttpEntity#getContentLength()方法可以用来读取通用的元数据,如Content-Type和Content-Length头部信息(如果它们是可用的)。因为头部信息Content-Type可以包含对文本MIME类型的字符编码,比如text/plain或text/html,HttpEntity#getContentEncoding()方法用来读取这个信息。如果头部信息Content-Length不可用,那么就返回长度-1,而对于内容类型返回NULL。如果头部信息Content-Type是可用的,那么就会返回一个Header对象。

    [java] view plain copy
     
    1. StringEntity myEntity = new StringEntity("important message",   
    2.    ContentType.create("text/plain", "UTF-8"));  
    3. System.out.println(myEntity.getContentType());  
    4. System.out.println(myEntity.getContentLength());  
    5. System.out.println(EntityUtils.toString(myEntity));  
    6. System.out.println(EntityUtils.toByteArray(myEntity).length);  

    输出

    [java] view plain copy
     
    1. Content-Type: text/plain; charset=utf-8  
    2. 17  
    3. important message  
    4. 17  

    确保低级别资源释放

    为了确保适当地释放系统资源,任何人必须关闭实体相关的流或者response本身。关闭实体相关的流和response的区别是:前者是通过消耗实体内容保持连接可用,后者直接关闭和抛弃连接。请记住,HttpEntity#writeTo(OutputStream)一旦实体被完全写出,也需要保证系统资源适当地释放。 EntityUtils#consume(HttpEntity)可以保证实体内容被完全消耗并且底层的流被关闭。
    [java] view plain copy
     
    1. CloseableHttpClient httpclient = HttpClients.createDefault();  
    2. HttpGet httpget = new HttpGet("http://localhost/");  
    3. CloseableHttpResponse response = httpclient.execute(httpget);  
    4. try {  
    5.     HttpEntity entity = response.getEntity();  
    6.     if (entity != null) {  
    7.         InputStream instream = entity.getContent();  
    8.         try {  
    9.             // do something useful  
    10.         } finally {  
    11.             instream.close();  
    12.         }  
    13.     }  
    14. finally {  
    15.     response.close();  
    16. }  
    有些情况下,仅仅response的一小部分需要被取回并且消耗内容的剩余部分且保持连接可用的性能代价是很高的,这种情况下可以直接关闭response。
    [java] view plain copy
     
    1. CloseableHttpClient httpclient = HttpClients.createDefault();  
    2. HttpGet httpget = new HttpGet("http://localhost/");  
    3. CloseableHttpResponse response = httpclient.execute(httpget);  
    4. try {  
    5.     HttpEntity entity = response.getEntity();  
    6.     if (entity != null) {  
    7.         InputStream instream = entity.getContent();  
    8.         int byteOne = instream.read();  
    9.         int byteTwo = instream.read();  
    10.         // Do not need the rest  
    11.     }  
    12. finally {  
    13.     response.close();  
    14. }  

    消耗实体内容

    推荐的消耗实体内容的方法是HttpEntity#getContent()和HttpEntity#writeTo(OutputStream),但是EntityUtils类有一些静态方法,这些方法可以更加容易地从实体中读取内容或信息。代替直接读取java.io.InputStream,也可以使用这个类中的方法以字符串/字节数组的形式获取整个内容体。然而,EntityUtils的使用是强烈不鼓励的,除非响应实体源自可靠的HTTP服务器和已知的长度限制。
    [java] view plain copy
     
    1. CloseableHttpClient httpclient = HttpClients.createDefault();  
    2. HttpGet httpget = new HttpGet("http://localhost/");  
    3. CloseableHttpResponse response = httpclient.execute(httpget);  
    4. try {  
    5.     HttpEntity entity = response.getEntity();  
    6.     if (entity != null) {  
    7.         long len = entity.getContentLength();  
    8.         if (len != -1 && len < 2048) {  
    9.             System.out.println(EntityUtils.toString(entity));  
    10.         } else {  
    11.             // Stream content out  
    12.         }  
    13.     }  
    14. finally {  
    15.     response.close();  
    16. }  
    在一些情况下可能会不止一次的读取实体。此时实体内容必须以某种方式在内存或磁盘上被缓冲起来。最简单的方法是通过使用BufferedHttpEntity类来包装源实体完成。这会引起源实体内容被读取到内存的缓冲区中。在其它所有方式中,实体包装器将会得到源实体。
    [java] view plain copy
     
    1. CloseableHttpResponse response = <...>  
    2. HttpEntity entity = response.getEntity();  
    3. if (entity != null) {  
    4.     entity = new BufferedHttpEntity(entity);  
    5. }  
     

    生成实体内容

    HttpClient提供一些类,它们可以用于通过HTTP连接有效地生成内容。这些类的实例可以和实体包装请求例如POST和PUT相关联以便包装实体内容。HttpClient提供了几个最为常见的数据容器,比如字符串,字节数组,输入流和文件提供了一些类:StringEntity,ByteArrayEntity,InputStreamEntity和FileEntity。请注意InputStreamEntity是不可重复的,因为它仅仅能从低层数据流中读取一次内容。通常来说,我们推荐实现一个定制的HttpEntity类,这是自我包含式的,用来代替使用通用的InputStreamEntity。FileEntity是一个很好的起点,FileEntity继承HttpEntity。
    [java] view plain copy
     
    1. File file = new File("somefile.txt");  
    2. FileEntity entity = new FileEntity(file,   
    3.     ContentType.create("text/plain", "UTF-8"));          
    4.   
    5. HttpPost httppost = new HttpPost("http://localhost/action.do");  
    6. httppost.setEntity(entity);  

    HTML表单

    许多应用需要模拟表单提交,比如,想要记录一个Web应用程序或提交输入数据。HttpClient提供了实体类UrlEncodedFormEntity使之更容易实现。
    [java] view plain copy
     
    1. List<NameValuePair> formparams = new ArrayList<NameValuePair>();  
    2. formparams.add(new BasicNameValuePair("param1", "value1"));  
    3. formparams.add(new BasicNameValuePair("param2", "value2"));  
    4. UrlEncodedFormEntity entity = new UrlEncodedFormEntity(formparams, Consts.UTF_8);  
    5. HttpPost httppost = new HttpPost("http://localhost/handler.do");  
    6. httppost.setEntity(entity);  

    内容分块

    一般来说,推荐让HttpClient自己根据Http消息传递的特征来选择最合适的传输编码。当然,如果非要手动控制也是可以的,设置HttpEntity#setChunked()为true,可以分块传输,但是如果是HTTP/1.0,那么这个设置就会被忽略,值有HTTP/1.1才支持。
    [java] view plain copy
     
    1. StringEntity entity = new StringEntity("important message",  
    2.         ContentType.create("plain/text", Consts.UTF_8));  
    3. entity.setChunked(true);  
    4. HttpPost httppost = new HttpPost("http://localhost/acrtion.do");  
    5. httppost.setEntity(entity);  

    response处理

    最简单也是最方便的处理http响应的方法就是使用ResponseHandler接口,这个接口中有handleResponse(HttpResponse response)方法。使用这个方法,用户完全不用关心http连接管理器。当使用ResponseHandler时,HttpClient会自动地将Http连接释放给Http管理器,即使http请求失败了或者抛出了异常。
    [java] view plain copy
     
    1. CloseableHttpClient httpclient = HttpClients.createDefault();  
    2. HttpGet httpget = new HttpGet("http://localhost/json");  
    3.   
    4. ResponseHandler<MyJsonObject> rh = new ResponseHandler<MyJsonObject>() {  
    5.   
    6.     @Override  
    7.     public JsonObject handleResponse(  
    8.             final HttpResponse response) throws IOException {  
    9.         StatusLine statusLine = response.getStatusLine();  
    10.         HttpEntity entity = response.getEntity();  
    11.         if (statusLine.getStatusCode() >= 300) {  
    12.             throw new HttpResponseException(  
    13.                     statusLine.getStatusCode(),  
    14.                     statusLine.getReasonPhrase());  
    15.         }  
    16.         if (entity == null) {  
    17.             throw new ClientProtocolException("Response contains no content");  
    18.         }  
    19.         Gson gson = new GsonBuilder().create();  
    20.         ContentType contentType = ContentType.getOrDefault(entity);  
    21.         Charset charset = contentType.getCharset();  
    22.         Reader reader = new InputStreamReader(entity.getContent(), charset);  
    23.         return gson.fromJson(reader, MyJsonObject.class);  
    24.     }  
    25. };  
    26. MyJsonObject myjson = client.execute(httpget, rh);  
     

    HttpClient的接口

    HttpClient的接口代表了请求执行过程中最基本的契约,HttpClient接口没有对Http请求的过程做特别的限制和详细的规定,连接管理、状态管理、认证处理和重定向处理这些功能都单独实现。这样就能更加容易地给接口添加额外的功能例如响应内容缓存。一般说来,HttpClient实际上就是一系列特殊的handler或者说策略接口的实现,这些handler(测试接口)负责着处理Http协议的某一方面,比如重定向、认证处理、有关连接持久性和keep alive持续时间的决策。这样就允许用户使用自定义的参数来代替默认配置,实现个性化的功能。
    [java] view plain copy
     
    1. ConnectionKeepAliveStrategy keepAliveStrat = new DefaultConnectionKeepAliveStrategy() {  
    2.   
    3.     @Override  
    4.     public long getKeepAliveDuration(  
    5.             HttpResponse response,  
    6.             HttpContext context) {  
    7.         long keepAlive = super.getKeepAliveDuration(response, context);  
    8.         if (keepAlive == -1) {  
    9.             // Keep connections alive 5 seconds if a keep-alive value  
    10.             // has not be explicitly set by the server  
    11.             keepAlive = 5000;  
    12.         }  
    13.         return keepAlive;  
    14.     }  
    15.   
    16. };  
    17. CloseableHttpClient httpclient = HttpClients.custom()  
    18.         .setKeepAliveStrategy(keepAliveStrat)  
    19.         .build();  

    HTTPCLIENT的线程安全性

    HttpClient已经实现了线程安全。因此建议一个实例被多个请求重用。
     

    HTTPCLIENT资源分配

    当一个CloseableHttpClient的实例不再被使用,并且它的作用范围即将失效,和它相关的连接必须被关闭,关闭方法可以调用CloseableHttpClient的close()方法。
    [java] view plain copy
     
    1. CloseableHttpClient httpclient = HttpClients.createDefault();  
    2. try {  
    3.     <...>  
    4. finally {  
    5.     httpclient.close();  
    6. }  

    Http执行上下文

    最初,Http被设计成一种无状态的、面向请求-响应的协议。然而,在实际使用中,我们希望能够在一些逻辑相关的请求-响应中,保持状态信息。为了使应用程序可以保持Http的持续状态,HttpClient允许http连接在特定的Http上下文中执行。如果在持续的http请求中使用了同样的上下文,那么这些请求就可以被分配到一个逻辑会话中。HTTP上下文就和一个java.util.Map<String, Object>功能类似。它实际上就是一个任意命名的值的集合。应用程序可以在Http请求执行前填充上下文的值,也可以在请求执行完毕后检查上下文。
    HttpContext可以包含任意类型的对象,因此如果在多线程中共享上下文会不安全。建议每个线程都只包含自己的http上下文。
    在Http请求执行的过程中,HttpClient会自动添加下面的属性到Http上下文中:
    HttpConnection的实例,表示客户端与服务器之间的连接
    HttpHost的实例,表示要连接的目标服务器
    HttpRoute的实例,表示全部的连接路由
    HttpRequest的实例,表示Http请求。在执行上下文中,最终的HttpRequest对象会代表http消息的状态。Http/1.0和Http/1.1都默认使用相对的uri。但是如果使用了非隧道模式的代理服务器,就会使用绝对路径的uri。
    HttpResponse的实例,表示Http响应
    java.lang.Boolean对象,表示是否请求被成功的发送给目标服务器
    RequestConfig对象,表示http request的配置信息
    java.util.List<Uri>对象,表示Http响应中的所有重定向地址
    可以使用HttpClientContext这个适配器来简化和上下文状态交互的过程。
    [java] view plain copy
     
    1. HttpContext context = <...>  
    2. HttpClientContext clientContext = HttpClientContext.adapt(context);  
    3. HttpHost target = clientContext.getTargetHost();  
    4. HttpRequest request = clientContext.getRequest();  
    5. HttpResponse response = clientContext.getResponse();  
    6. RequestConfig config = clientContext.getRequestConfig();  
    同一个逻辑会话中的多个Http请求,应该使用相同的Http上下文来执行,这样就可以自动地在http请求中传递会话上下文和状态信息。
    在下面的例子中,我们在开头设置的参数,会被保存在上下文中,并且会应用到后续的http请求中。
    [java] view plain copy
     
    1. CloseableHttpClient httpclient = HttpClients.createDefault();  
    2. RequestConfig requestConfig = RequestConfig.custom()  
    3.         .setSocketTimeout(1000)  
    4.         .setConnectTimeout(1000)  
    5.         .build();  
    6.   
    7. HttpGet httpget1 = new HttpGet("http://localhost/1");  
    8. httpget1.setConfig(requestConfig);  
    9. CloseableHttpResponse response1 = httpclient.execute(httpget1, context);  
    10. try {  
    11.     HttpEntity entity1 = response1.getEntity();  
    12. finally {  
    13.     response1.close();  
    14. }  
    15. HttpGet httpget2 = new HttpGet("http://localhost/2");  
    16. CloseableHttpResponse response2 = httpclient.execute(httpget2, context);  
    17. try {  
    18.     HttpEntity entity2 = response2.getEntity();  
    19. finally {  
    20.     response2.close();  
    21. }  
     

    HTTP协议拦截器

    HTTP协议拦截器是一种实现一个特定的方面的HTTP协议的代码程序。通常情况下,协议拦截器会将一个或多个头消息加入到接收或者发送的消息中。协议拦截器也可以操作消息的内容实体—消息内容的压缩/解压缩就是个很好的例子。通常,这是通过使用“装饰”开发模式,一个包装实体类用于装饰原来的实体来实现。一个拦截器可以合并,形成一个逻辑单元。协议拦截器可以通过共享信息协作——比如处理状态——通过HTTP执行上下文。协议拦截器可以使用Http上下文存储一个或者多个连续请求的处理状态。通常,只要拦截器不依赖于一个特定状态的http上下文,那么拦截执行的顺序就无所谓。如果协议拦截器有相互依赖关系,必须以特定的顺序执行,那么它们应该按照特定的顺序加入到协议处理器中。协议处理器必须是线程安全的。类似于servlets,协议拦截器不应该使用实例变量,除非访问这些实例变量是同步的(线程安全的)。
    下面是个例子,讲述了本地的上下文时如何在连续请求中记录处理状态的:
    [java] view plain copy
     
    1. CloseableHttpClient httpclient = HttpClients.custom()  
    2.         .addInterceptorLast(new HttpRequestInterceptor() {  
    3.   
    4.             public void process(  
    5.                     final HttpRequest request,  
    6.                     final HttpContext context) throws HttpException, IOException {  
    7.                 AtomicInteger count = (AtomicInteger) context.getAttribute("count");  
    8.                 request.addHeader("Count", Integer.toString(count.getAndIncrement()));  
    9.             }  
    10.   
    11.         })  
    12.         .build();  
    13.   
    14. AtomicInteger count = new AtomicInteger(1);  
    15. HttpClientContext localContext = HttpClientContext.create();  
    16. localContext.setAttribute("count", count);  
    17.   
    18. HttpGet httpget = new HttpGet("http://localhost/");  
    19. for (int i = 0; i < 10; i++) {  
    20.     CloseableHttpResponse response = httpclient.execute(httpget, localContext);  
    21.     try {  
    22.         HttpEntity entity = response.getEntity();  
    23.     } finally {  
    24.         response.close();  
    25.     }  
    26. }  
     

    异常处理

    HttpClient会被抛出两种类型的异常,一种是java.io.IOException,当遇到I/O异常时抛出(socket超时,或者socket被重置);另一种是HttpException,表示Http失败,如Http协议使用不正确。通常认为,I/O错误时不致命、可修复的,而Http协议错误是致命了,不能自动修复的错误。请注意,HttpException如果被重新抛出成IOException的子类ClientProtocolException,就可以让用户在一个catch语句里同时处理I/O错误和协议错误。
     

    HTTP传输安全

    Http协议不能满足所有类型的应用场景,我们需要知道这点。Http是个简单的面向协议的请求/响应的协议,当初它被设计用来支持静态或者动态生成的内容检索,之前从来没有人想过让它支持事务性操作。例如,Http服务器成功接收、处理请求后,生成响应消息,并且把状态码发送给客户端,这个过程是Http协议应该保证的。但是,如果客户端由于读取超时、取消请求或者系统崩溃导致接收响应失败,服务器不会回滚这一事务。如果客户端重新发送这个请求,服务器就会重复的解析、执行这个事务。在一些情况下,这会导致应用程序的数据损坏和应用程序的状态不一致。即使Http当初设计是不支持事务操作,但是它仍旧可以作为传输协议为某些关键程序提供服务。为了保证Http传输层的安全性,系统必须保证应用层上的http方法的幂等性。
     

    方法的幂等性

    HTTP/1.1规范中是这样定义幂等方法的,[Methods can also have the property of "idempotence" in that (aside from error or expiration issues) the side-effects of N > 0 identical requests is the same as for a single request]。用其他话来说,应用程序需要做好准备,处理同一方法多次执行造成的影响。添加一个具有唯一性的id就能避免重复执行同一个逻辑请求,问题解决。请知晓,这个问题不只是HttpClient才会有,基于浏览器的应用程序也会遇到Http方法不幂等的问题。HttpClient默认把非实体方法get、head方法看做幂等方法,把实体方法post、put方法看做非幂等方法。
     

    异常自动修复

    默认情况下,HttpClient会尝试自动修复I/O异常。这种自动修复仅限于修复几个公认安全的异常。
    HttpClient不会尝试修复任何逻辑或者http协议错误(即从HttpException衍生出来的异常)。
    HttpClient会自动再次发送幂等的方法(如果首次执行失败)。
    HttpClient会自动再次发送遇到transport异常的方法,前提是Http请求仍旧保持着连接(例如http请求没有全部发送给目标服务器,HttpClient会再次尝试发送)。
     

    请求重试HANDLER

    如果要自定义异常处理机制,我们需要实现HttpRequestRetryHandler接口。请注意,可以使用StandardHttpRequestRetryHandler替代默认的接口以便使那些在 RFC-2616定义的幂等性的请求方法安全的自动重试:GET, HEAD, PUT, DELETE, OPTIONS, TRACE。
    [java] view plain copy
     
    1. HttpRequestRetryHandler myRetryHandler = new HttpRequestRetryHandler() {  
    2.   
    3.     public boolean retryRequest(  
    4.             IOException exception,  
    5.             int executionCount,  
    6.             HttpContext context) {  
    7.         if (executionCount >= 5) {  
    8.             // Do not retry if over max retry count  
    9.             return false;  
    10.         }  
    11.         if (exception instanceof InterruptedIOException) {  
    12.             // Timeout  
    13.             return false;  
    14.         }  
    15.         if (exception instanceof UnknownHostException) {  
    16.             // Unknown host  
    17.             return false;  
    18.         }  
    19.         if (exception instanceof ConnectTimeoutException) {  
    20.             // Connection refused  
    21.             return false;  
    22.         }  
    23.         if (exception instanceof SSLException) {  
    24.             // SSL handshake exception  
    25.             return false;  
    26.         }  
    27.         HttpClientContext clientContext = HttpClientContext.adapt(context);  
    28.         HttpRequest request = clientContext.getRequest();  
    29.         boolean idempotent = !(request instanceof HttpEntityEnclosingRequest);  
    30.         if (idempotent) {  
    31.             // Retry if the request is considered idempotent  
    32.             return true;  
    33.         }  
    34.         return false;  
    35.     }  
    36.   
    37. };  
    38. CloseableHttpClient httpclient = HttpClients.custom()  
    39.         .setRetryHandler(myRetryHandler)  
    40.         .build();  
     

    中断请求

    有时候由于目标服务器负载过高或者客户端目前有太多请求积压,http请求不能在指定时间内执行完毕。这时候终止这个请求,释放阻塞I/O的进程,就显得很必要。通过HttpClient执行的Http请求,在任何状态下都能通过调用HttpUriRequest的abort()方法来终止。这个方法是线程安全的,并且能在任何线程中调用。当Http请求被终止了,本线程(即使现在正在阻塞I/O)也会通过抛出一个InterruptedIOException异常,来释放资源。
     

    重定向处理

    HttpClient会自动处理所有类型的重定向,除了那些Http规范明确禁止的重定向。See Other (status code 303) redirects on POST and PUT requests are converted to GET requests as required by the HTTP specification. 可以使用自定义的重定向策略来放松Http规范对Post方法重定向的限制。
    [java] view plain copy
     
    1. LaxRedirectStrategy redirectStrategy = new LaxRedirectStrategy();  
    2. CloseableHttpClient httpclient = HttpClients.custom()  
    3.         .setRedirectStrategy(redirectStrategy)  
    4.         .build();  
    HttpClient在请求执行过程中,经常需要重写请求的消息。 HTTP/1.0和HTTP/1.1都默认使用相对的uri路径。同样,原始的请求可能会被者多次的重定向。最终绝对路径可以使用原始的请求和上下文来构建。URIUtils#resolve可以用于构建绝对路径,产生最终的请求。这个方法包含了最后一个分片标识符或者原始请求。
    [java] view plain copy
     
    1. CloseableHttpClient httpclient = HttpClients.createDefault();  
    2. HttpClientContext context = HttpClientContext.create();  
    3. HttpGet httpget = new HttpGet("http://localhost:8080/");  
    4. CloseableHttpResponse response = httpclient.execute(httpget, context);  
    5. try {  
    6.     HttpHost target = context.getTargetHost();  
    7.     List<URI> redirectLocations = context.getRedirectLocations();  
    8.     URI location = URIUtils.resolve(httpget.getURI(), target, redirectLocations);  
    9.     System.out.println("Final HTTP location: " + location.toASCIIString());  
    10.     // Expected to be an absolute URI  
    11. finally {  
    12.     response.close();  
    13. }  

      

    ps:

    1、爬虫也要遵守基本法,在多次请求的之中为了不给对方服务器造成负担(避免被封),尽量在请求间sleep一个随机数值。

    2、爬取非英文网站时注意编码格式,国内一般为utf-8,也有一些是gb2312.获取时注意转码。

    3、多获得一些可靠IP(备胎),一旦自身ip被封,赶快去找备胎。附带一个简单的判断网站是否需要代理方法:

    Java代码  收藏代码
    1. // 判断访问目标网站是否需要代理  
    2.     private boolean isNeedProxy() {  
    3.         boolean result = true;  
    4.         URL url;  
    5.         try {  
    6.             url = new URL("http://apkpure.com/");  
    7.             HttpURLConnection connection = (HttpURLConnection) url.openConnection();  
    8.             connection.setConnectTimeout(6000);  
    9.             // int i = connection.getResponseCode();  
    10.             int i = connection.getContentLength();  
    11.             if (i > 0) {  
    12.                 result = false;  
    13.             }  
    14.         } catch (IOException e) {  
    15.             e.printStackTrace();  
    16.         }  
    17.         return result;  
    18.     }  
  • 相关阅读:
    饱和色
    Server.MapPath()相关
    Oracle修改表结构
    远程关机
    使用sqlplus创建表空间
    自写Js+CSS轮显效果
    右下角随机显示的CSS+JS图片广告
    JavaScript实现鼠标放上动画加载大图的代码
    CSS实现自适应的图片背景边框代码
    JavaScript弹性透明的图片放大代码
  • 原文地址:https://www.cnblogs.com/nizuimeiabc1/p/8524814.html
Copyright © 2020-2023  润新知