HttpClient5的基础封装

本想封装一下HttpClient5,基本的get请求已经实现,但是Post带参提交一直无法获得参数,代码记录一下,后面再找寻解决办法吧

pom.xml配置,httpclient版本为

5.1.3


    org.apache.httpcomponents.client5
    httpclient5
    ${httpclient.version}

封装配置类HttpConnectorConfig,代码如下:

package com.vtarj.pythagoras.explorer;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.cookie.BasicCookieStore;
import org.apache.hc.client5.http.cookie.CookieStore;
import org.apache.hc.client5.http.entity.UrlEncodedFormEntity;
import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider;
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
import org.apache.hc.client5.http.socket.ConnectionSocketFactory;
import org.apache.hc.client5.http.socket.PlainConnectionSocketFactory;
import org.apache.hc.client5.http.ssl.NoopHostnameVerifier;
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
import org.apache.hc.core5.http.ClassicHttpRequest;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.HttpEntity;
import org.apache.hc.core5.http.NameValuePair;
import org.apache.hc.core5.http.config.Registry;
import org.apache.hc.core5.http.config.RegistryBuilder;
import org.apache.hc.core5.http.io.SocketConfig;
import org.apache.hc.core5.http.io.entity.StringEntity;
import org.apache.hc.core5.http.message.BasicHeader;
import org.apache.hc.core5.util.TimeValue;
import org.apache.tomcat.util.json.JSONParser;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @Author Vtarj
 * @Description TODO
 * @Time 2022/3/30 11:03
 **/
public class HttpConfig {

    //定义HttpClient构造器
    private HttpClientBuilder builder = HttpClientBuilder.create();
    //定义头信息
    private Map headerMap;
    //定义CookieStore对象
    private CookieStore cookieStore;
    //定义Basic Auth管理对象
    private BasicCredentialsProvider basicCredentialsProvider;
    //定义请求参数
    private List pairs;
    //定义默认请求类型
    private static String contentType = "application/json";
    //定义请求和响应字符编码
    private Charset reqCode = StandardCharsets.UTF_8;
    private Charset resCode = StandardCharsets.UTF_8;

    public HttpConfig(){
        //设置连接基本配置
        //注册访问协议相关的Socket工厂
        Registry registry = RegistryBuilder.create()
                .register("http", PlainConnectionSocketFactory.INSTANCE)
                .register("https",trustHttpsCertificates())
                .build();
        //设置连接池管理器
        PoolingHttpClientConnectionManager manager = new PoolingHttpClientConnectionManager(registry);
        manager.setMaxTotal(100);
        manager.setDefaultMaxPerRoute(manager.getMaxTotal() / 2);
        manager.setValidateAfterInactivity(TimeValue.ofMinutes(5));
        manager.setDefaultSocketConfig(SocketConfig.custom().setSoTimeout(30,TimeUnit.SECONDS).setTcpNoDelay(true).build());
        //为HttpClientBuilder设置连接池
        builder.setConnectionManager(manager);
        //设置定期清理连接池中过期的连接
        builder.evictExpiredConnections();
        builder.evictIdleConnections(TimeValue.ofMinutes(3));

        //设置请求基础配置
        //创建默认CookieStore
        cookieStore = new BasicCookieStore();
        //创建默认Basic Auth对象
        basicCredentialsProvider = new BasicCredentialsProvider();
        //设置Http请求基本参数
        RequestConfig requestConfig = RequestConfig.custom()
                // 设置启用重定向
                .setRedirectsEnabled(true)
                // 设置最大重定向次数
                .setMaxRedirects(30)
                // 设置连接超时时间
                .setConnectTimeout(2,TimeUnit.MINUTES)
                // 设置请求超时时间
                .setConnectionRequestTimeout(2,TimeUnit.MINUTES)
                // 设置响应超时时间
                .setResponseTimeout(2,TimeUnit.MINUTES)
                .build();
        //为HttpClientBuilder设置连接配置
        builder.setDefaultRequestConfig(requestConfig);
        //为HttpClientBuilder设置头信息
        builder.setDefaultHeaders(buildHeader());
        builder.setDefaultCookieStore(cookieStore);
        builder.setDefaultCredentialsProvider(basicCredentialsProvider);

    }

    /**
     * 设置默认请求头
     */
    private void setDefaultHeader(){
        if (this.getHeaderMap() == null){
            headerMap = new HashMap<>();
        }
        if(!headerMap.containsKey("User-Agent")){
            headerMap.put("User-Agent","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.74 Safari/537.36 Edg/99.0.1150.55");
        }
        if (!headerMap.containsKey("Content-Type")){
            headerMap.put("Content-Type", contentType);
        }
    }

    /**
     * 将Map类型的Header构建为标准Header列表
     * @return
     */
    private List
buildHeader(){ setDefaultHeader(); return this.getHeaderMap() .entrySet().stream() .map(v -> new BasicHeader(v.getKey(),v.getValue())) .collect(Collectors.toList()); } /** * 将参数列表转换为字符串拼接,以&符分隔 * @param list 参数列表 * @return 转换后的字符串 */ public static String pairsToString(List list){ return list.stream().map(v -> v.getName() + "=" + v.getValue()).collect(Collectors.joining("&")); } /** * 构建Get请求 * @param uri 请求地址 */ public HttpGet buildGet(String uri){ uri = formatURI(uri); if (this.getPairs() != null && this.getPairs().size() > 0){ uri = uri.contains("?") ? uri + "&" : uri + "?"; uri = uri + pairsToString(this.getPairs()); } HttpGet httpGet = new HttpGet(uri); /** List
headerList = this.buildHeader(); Header[] headers = headerList.toArray(new Header[headerList.size()]); httpGet.setHeaders(headers); **/ return httpGet; } /** * 构建Post请求,模拟Form表单提交 * @param uri 请求地址 * @return Post请求 */ public HttpPost buildPost(String uri){ uri = formatURI(uri); HttpPost httpPost = new HttpPost(uri); httpPost.addHeader("Content-Type","application/x-www-form-urlencoded;charset=utf-8"); //设置请求参数 if (this.getPairs() != null && this.getPairs().size() > 0){ httpPost.setEntity(new UrlEncodedFormEntity(pairs,this.getReqCode())); } return httpPost; } /** * 格式化uri连接地址,补全协议 * @param uri 待格式化的连接地址 * @return 格式化后的地址 */ private static String formatURI(String uri) { if (!uri.toLowerCase().startsWith("http://") && !uri.toLowerCase().startsWith("https://")){ return "http://" + uri; } return uri; } /** * Https证书管理 * @return 可识别证书集合 */ private static ConnectionSocketFactory trustHttpsCertificates() { TrustManager[] trustAllCertificates = new TrustManager[]{ new X509TrustManager() { @Override public void checkClientTrusted(X509Certificate[] x509Certificates, String s) { } @Override public void checkServerTrusted(X509Certificate[] x509Certificates, String s) { } @Override public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; } } }; SSLContext sslContext = null; try { sslContext = SSLContext.getInstance("TLS"); sslContext.init(null,trustAllCertificates,new SecureRandom()); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (KeyManagementException e) { e.printStackTrace(); } return new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE); } public HttpClientBuilder getBuilder() { return builder; } public Map getHeaderMap() { return headerMap; } /** * 设置请求头信息 * @param headerMap 请求头信息 * @return 返回HttpConfig,用于链式调用 */ public HttpConfig setHeaderMap(Map headerMap) { this.headerMap = headerMap; return this; } /** * 自定义添加请求头信息 * @param key 请求头键 * @param value 请求头值 * @return 返回HttpConfig,用于链式调用 */ public HttpConfig addHeader(String key,String value){ this.headerMap.put(key,value); return this; } public CookieStore getCookieStore() { return cookieStore; } /** * 设置CookieStore * @param cookieStore cookiestore * @return 返回HttpConfig,用于链式调用 */ public HttpConfig setCookieStore(CookieStore cookieStore) { this.cookieStore = cookieStore; return this; } public BasicCredentialsProvider getBasicCredentialsProvider() { return basicCredentialsProvider; } /** * * @param basicCredentialsProvider * @return 返回HttpConfig,用于链式调用 */ public HttpConfig setBasicCredentialsProvider(BasicCredentialsProvider basicCredentialsProvider) { this.basicCredentialsProvider = basicCredentialsProvider; return this; } public List getPairs() { return pairs; } /** * 设置请求参数 * @param pairs 请求参数集合 * @return 返回HttpConfig,用于链式调用 */ public HttpConfig setPairs(List pairs) { this.pairs = pairs; return this; } public Charset getReqCode() { return reqCode; } public HttpConfig setReqCode(Charset reqCode) { this.reqCode = reqCode; return this; } public Charset getResCode() { return resCode; } public HttpConfig setResCode(Charset resCode) { this.resCode = resCode; return this; } public HttpExplorer build(){ return new HttpExplorer(this); } }

封装探测器类HttpExplorer,代码如下:

package com.vtarj.pythagoras.explorer;

import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.client5.http.protocol.HttpClientContext;

import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;

/**
 * @Author Vtarj
 * @Description TODO
 * @Time 2022/3/30 11:00
 **/
public class HttpExplorer {

    private CloseableHttpClient httpClient = null;
    private HttpConfig config;

    public HttpExplorer (HttpConfig config) {
        if (httpClient == null){
            synchronized (HttpExplorer.class) {
                this.config = config;
                httpClient = config.getBuilder().build();
            }
        }
    }

    /**
     * 向指定地址发送Get请求
     * @param uri   目标地址
     * @return  响应信息
     */
    public HttpResponse get(String uri){
        try {
            CloseableHttpResponse response = this.httpClient.execute(config.buildGet(uri));
            return HttpResponseHandler.ofString(config.getResCode()).handler(response,this);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    /**
     * 向指定地址发送Get请求
     * @param uri   目标地址
     * @return  响应信息
     */
    public HttpResponse getFile(String uri, String path){
        try {
            CloseableHttpResponse response = this.httpClient.execute(config.buildGet(uri));
            return HttpResponseHandler
                    .ofFile(Path.of(path), StandardCopyOption.REPLACE_EXISTING)
                    .handler(response,this);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 向指定地址发送Post请求
     * @param uri   目标地址
     * @return  响应信息
     */
    public HttpResponse post(String uri){
        try {
            HttpPost httpPost = config.buildPost(uri);
            CloseableHttpResponse response = this.httpClient.execute(httpPost,new HttpClientContext());
            return HttpResponseHandler.ofString(config.getResCode()).handler(response,this);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 创建按构造器,用于构造HttpClient配置
     * @return
     */
    public static HttpConfig builder(){
        return new HttpConfig();
    }
}

封装响应类HttpResponse,代码如下:

package com.vtarj.pythagoras.explorer;

import org.apache.hc.core5.http.ClassicHttpResponse;
import org.apache.hc.core5.http.HttpEntity;
import org.apache.hc.core5.http.ProtocolVersion;

import java.util.Locale;

/**
 * @Author Vtarj
 * @Description 响应信息封装
 * @Time 2022/3/30 15:30
 **/
public class HttpResponse {
    private final int code;
    private final HttpEntity entity;
    private final T data;
    private final ProtocolVersion version;
    private final Locale locale;
    private final String reasonPhrase;

    public HttpResponse(int code, HttpEntity entity, T data, ProtocolVersion version, Locale locale, String reasonPhrase) {
        this.code = code;
        this.entity = entity;
        this.data = data;
        this.version = version;
        this.locale = locale;
        this.reasonPhrase = reasonPhrase;
    }

    public static  HttpResponse build(ClassicHttpResponse response, T data) {
        return new HttpResponse(response.getCode(), response.getEntity(), data, response.getVersion(), response.getLocale(), response.getReasonPhrase());
    }

    public int getCode() {
        return code;
    }

    public HttpEntity getEntity() {
        return entity;
    }

    public T getData() {
        return data;
    }

    public ProtocolVersion getVersion() {
        return version;
    }

    public Locale getLocale() {
        return locale;
    }

    public String getReasonPhrase() {
        return reasonPhrase;
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder("Response{");
        sb.append("code=").append(code);
        sb.append(", entity=").append(entity);
        sb.append(", data=").append(data);
        sb.append(", version=").append(version);
        sb.append(", locale=").append(locale);
        sb.append(", reasonPhrase='").append(reasonPhrase).append('\'');
        sb.append('}');
        return sb.toString();
    }
}

响应代理接口HttpResponseHandler,代码如下:

package com.vtarj.pythagoras.explorer;

import org.apache.hc.core5.http.ClassicHttpResponse;
import org.apache.hc.core5.http.io.entity.EntityUtils;

import java.io.File;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.nio.file.CopyOption;
import java.nio.file.Files;
import java.nio.file.Path;

/**
 * @Author Vtarj
 * @Description 响应信息处理
 * @Time 2022/3/30 15:31
 **/
public interface HttpResponseHandler {

    HttpResponse handler(ClassicHttpResponse response, HttpExplorer explorer);

    /**
     * 返回字符串
     *
     * @param defaultCharset 默认字符集
     */
    static HttpResponseHandler ofString(Charset... defaultCharset) {
        return (response, client) -> {
            try {
                Charset charset = defaultCharset != null && defaultCharset.length > 0 ? defaultCharset[0] : Charset.defaultCharset();
                return HttpResponse.build(response, EntityUtils.toString(response.getEntity(), charset));
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        };
    }

    /**
     * 保存为文件
     *
     * @param path    保存文件路径
     * @param options StandardCopyOption: REPLACE_EXISTING(替换更新), COPY_ATTRIBUTES(复制属性), ATOMIC_MOVE(原子移动)
     */
    static HttpResponseHandler ofFile(Path path, CopyOption... options) {
        return (response, client) -> {
            File file = path.toFile();
            if (!file.exists()) {
                file.getParentFile().mkdirs();
            }
            try (InputStream in = response.getEntity().getContent()) {
                Files.copy(in, path, options);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
            return HttpResponse.build(response, file);
        };
    }
}

程序测试

    @Test
    void testHttpExplorer4Get(){
        //网页资源访问
        HttpResponse stringHttpResponse = HttpExplorer.builder()
                .build()
                .get("http://www.stats.gov.cn/tjsj/tjbz/tjyqhdmhcxhfdm/2021/index.html");
        System.out.println(stringHttpResponse.toString());
    }

    @Test
    void testHttpExplorer4GetFile(){
        //文件下载
        HttpResponse fileHttpResponse = HttpExplorer.builder()
                .build()
                .getFile("http://**********/YZSoft/Attachment/default.ashx?202203280003","D://a.pdf");
        System.out.println(fileHttpResponse.getData().getName());
    }

    /**
     * HttpClient5 的post带参提交无法获得参数,测试失败
     */
    @Test
    void testPost(){
        try (CloseableHttpClient httpclient = HttpClients.createDefault()) {

            HttpPost httpPost = new HttpPost("https://register-api.did.id/v1/account/search");
            List nvps = new ArrayList<>();
            nvps.add(new BasicNameValuePair("account","mama.bit"));
            nvps.add(new BasicNameValuePair("address",""));
            httpPost.setEntity(new UrlEncodedFormEntity(nvps));

            CloseableHttpResponse response = httpclient.execute(httpPost);
            HttpResponse result = HttpResponseHandler.ofString().handler(response,null);
            System.out.println(result);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * HttpClient5 的post带参提交无法获得参数,测试失败
     */
    @Test
    void testHttpExplorer4PostWithDAS(){
        List nvps = new ArrayList<>();
        nvps.add(new BasicNameValuePair("account","mama.bit"));
        nvps.add(new BasicNameValuePair("address",""));
        HttpResponse response = HttpExplorer
                .builder()
                .setPairs(nvps)
                .addHeader("Content-Type","application/x-www-form-urlencoded;charset=utf-8")
                .build()
                .post("https://register-api.did.id/v1/account/search");
        System.out.println(response.toString());
    }

    /**
     * HttpClient5 的post带参提交无法获得参数,测试失败
     */
    @Test
    void testHttpExplorer4Post(){
        List nvps = new ArrayList<>();
        nvps.add(new BasicNameValuePair("username","sysadmin"));
        nvps.add(new BasicNameValuePair("password","Test000000@"));
        nvps.add(new BasicNameValuePair("execution","e1s1"));
        nvps.add(new BasicNameValuePair("_eventId","submit"));
        nvps.add(new BasicNameValuePair("submit", "登录"));

        HttpResponse response = HttpExplorer.builder()
                .addHeader("Content-Type","text/html;charset=UTF-8")
                .addHeader("Referer","http://192.168.1.191:8008/cas/login")
                .setPairs(nvps)
                .build()
                .post("http://192.168.1.191:8008/cas/login");
        System.out.println(response.toString());
    }

基本封装已经完成,但是存在两个疑问:

疑问一:HttpClientBuilder为何要提供setDefaultHeaders方法?

HttpClientBuilder用于构造HttpClient通道,但是在这里设置Header的目的是什么?后续构造HttpRequest之后仍要重新设置Header,因此不明白这里为何要设置Header等信息?

疑问二:HttpPost.setEntity在网上查找了解到它是以application/x-www-form-urlencoded这种方式模拟Form提交,因此实际无需再指定request的header,但是为何form表单提交后服务端还是无法获取参数?

至此,封装暂告一段落,希望对你有一点微薄帮助

你可能感兴趣的:(Java,java,http,网络协议)