java实现HTTP请求(三)

httpClient4.5 发送了http请求(推荐)

引入依赖

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.11</version>
    <scope>test</scope>
</dependency> 

代码实现

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.springframework.util.StringUtils;

public class HttpClient4 {
    /**
     * doGet Get请求
     * @param url
     * @return
     */
    public static String doGet(String url) {
        CloseableHttpClient httpClient = null;
        CloseableHttpResponse response = null;
        String result = "";
        try {
            httpClient = HttpClients.createDefault();// 通过址默认配置创建一个httpClient实例
            HttpGet httpGet = new HttpGet(url);// 创建httpGet远程连接实例
            httpGet.setHeader("Authorization", "Bearer da3efcbf-0845-4fe3-8aba-ee040be542c0");// 设置请求头信息,鉴权
            RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(35000)// 连接主机服务超时时间
                    .setConnectionRequestTimeout(35000)// 请求超时时间
                    .setSocketTimeout(60000)// 数据读取超时时间
                    .build();
            httpGet.setConfig(requestConfig);// 为httpGet实例设置配置
            response = httpClient.execute(httpGet);// 执行get请求得到返回对象
            HttpEntity entity = response.getEntity();// 通过返回对象获取返回数据
            // 通过EntityUtils中的toString方法将结果转换为字符串
            result = EntityUtils.toString(entity);
        } catch (ClientProtocolException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (null != response) {
                try {
                    response.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (null != httpClient) {
                try {
                    httpClient.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return result;
    }


    /**
     * post 请求
     * @param url 请求地址
     * @param paramMap 请求参数
     * @param contentType 请求头
     * @return
     */
    public static String doPost(String url, Map<String, Object> paramMap,String contentType) {
        CloseableHttpClient httpClient = null;
        CloseableHttpResponse httpResponse = null;
        String result = "";
        httpClient = HttpClients.createDefault();// 创建httpClient实例
        HttpPost httpPost = new HttpPost(url);// 创建httpPost远程连接实例
        // 配置请求参数实例
        RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(35000)// 设置连接主机服务超时时间
                .setConnectionRequestTimeout(35000)// 设置连接请求超时时间
                .setSocketTimeout(60000)// 设置读取数据连接超时时间
                .build();
        httpPost.setConfig(requestConfig);// 为httpPost实例设置配置

        if(StringUtils.isEmpty(contentType)){//设置请求头
            httpPost.addHeader("Content-Type", "application/x-www-form-urlencoded");
        }else {
            httpPost.addHeader("Content-Type", contentType);
        }
        // 封装post请求参数
        if (null != paramMap && paramMap.size() > 0) {
            List<NameValuePair> nvps = new ArrayList<NameValuePair>();
            Set<Entry<String, Object>> entrySet = paramMap.entrySet();// 通过map集成entrySet方法获取entity
            Iterator<Entry<String, Object>> iterator = entrySet.iterator();// 循环遍历,获取迭代器
            while (iterator.hasNext()) {
                Entry<String, Object> mapEntry = iterator.next();
                nvps.add(new BasicNameValuePair(mapEntry.getKey(), mapEntry.getValue().toString()));
            }
            // 为httpPost设置封装好的请求参数
            try {
                httpPost.setEntity(new UrlEncodedFormEntity(nvps, "UTF-8"));
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
        }
        try {
            httpResponse = httpClient.execute(httpPost);// httpClient对象执行post请求,并返回响应参数对象
            HttpEntity entity = httpResponse.getEntity();// 从响应对象中获取响应内容
            result = EntityUtils.toString(entity);
        } catch (ClientProtocolException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (null != httpResponse) {
                try {
                    httpResponse.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (null != httpClient) {
                try {
                    httpClient.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return result;
    }
}

 

----------------------------------------------------------------------让我们一起分析下httpClient4.5 底层源码----------------------------------------------------------------------

通过分析httpclient4.5源码,http的交互处理. 而httpclient实例则使用了http连接池

常规连接池的注意点

  连接池的使用需要保证如下几点, 尤其对自研的连接池.
  1. Connection的get/release配对.
  2. 保证一次http交互中请求/响应处理完整干净(cleanup).
  比如一次请求交互中, 因某种原因没有消费掉响应内容, 导致该内容还处于socket的缓存中. 继而使得同一个连接下的第二次交互其响应内容为第一次的响应结果, 后果十分可怕. 

连接封装

  httpclient引入了ConnectionHolder类, 构建了真实连接(HttpCilentConnection)和连接池(HttpClientConnectionManager)的桥梁, 同时维护了该连接的可重用(reusable)和租赁(leased)状态.

class ConnectionHolder implements ConnectionReleaseTrigger,
        Cancellable, Closeable {
    private final Log log;
    private final HttpClientConnectionManager manager;
    private final HttpClientConnection managedConn;
    private final AtomicBoolean released;  // 连接池租赁状态
    private volatile boolean reusable;     // 连接是否可复用
}

该类最重要的一个方法为releaseConnection, 后续的执行流程多多少少会涉及到该方法.

private void releaseConnection(boolean reusable) {
    //判断租赁状态, 若已归还连接池, 则不再执行后续的代码
    if(this.released.compareAndSet(false, true)) {
        HttpClientConnection var2 = this.managedConn;
        synchronized(this.managedConn) {
            //根据可重用性分情况处理, 同时归还到连接池中
            if(reusable) {
                this.manager.releaseConnection(this.managedConn,this.state, this.validDuration, this.tunit);
            } else {
                try {
                    //关闭连接
                    this.managedConn.close();
                    this.log.debug("Connection discarded");
                } catch (IOException var9) {
                    if(this.log.isDebugEnabled()) {
                        this.log.debug(var9.getMessage(), var9);
                    }
                } finally {
                    this.manager.releaseConnection(this.managedConn,(Object)null, 0L, TimeUnit.MILLISECONDS);
                }
            }
        }
    }
}

而CloseableHttpResponse又持有ConnectionHolder对象, 它close方法, 本质上就是间接调用了ConnectionHolder的releaseConnection方法.

class HttpResponseProxy implements CloseableHttpResponse {
    public void close() throws IOException {
        if(this.connHolder != null) {
            this.connHolder.close();
        }
    }
}
 
class ConnectionHolder implements ConnectionReleaseTrigger, Cancellable, Closeable {
    public void close() throws IOException {
        this.releaseConnection(false);
    }
}

由此可见, 官方sample的推荐做法, 在finally中保证ClosableHttpResponse#close的调用, 能够确保连接池的get/release配对. 若是close前, 连接状态依旧为租赁状态(leased为false), 则该连接明确不被复用.

摘自:https://www.cnblogs.com/mumuxinfei/p/9121829.html

 

posted @ 2020-03-20 10:32  An-Optimistic-Person  阅读(443)  评论(0)    收藏  举报