HttpClient4.5 入门

引言

本文主要记录如何构建HttpClient(4.5.x) 和 入门使用.

参考官方文档
http://hc.apache.org/httpcomponents-client-4.5.x/current/tutorial/html/

1. HttpClient构建

1.1 关于HttpClinet

在构建之前, 先大致了解一下HttpClient类
HttpClient4.5 入门
HttpClient子类

  • DecompressingHttpClient: 装饰器类(已弃用),支持解压缩逻辑,可用RequestAcceptEncoding/ResponseContentEncoding取代.
  • AutoRetryHttpClient: 装饰器类(已弃用),支持服务异常重试逻辑,可用ServiceUnavailableRetryStrategy取代.
  • CloseableHttpClient: 默认的HttpClient
    • MinimalHttpClient: 简易版HttpClient
    • AbstractHttpClient: 已弃用,文档推荐HttpClientBuilder, 构建InternalHttpClient对象.
    • InternalHttpClient: 默认访问权限, 只能通过HttpClientBuilder构建.

经过上述说明, 4.5.x版本中, 我们只需关心如何通过HttpClientBuilder构建HttpClient即可.

此外,HttpClient还提供了HttpClients作为工厂类, 封装了HttpClientBuilder和MinimalHttpClient构建.

1.2 构建HttpClient(默认)

RequestConfig requestConfig = RequestConfig.custom()
                .setSocketTimeout(2000) // 创建Socket超时时间
                .setConnectTimeout(2000) // 请求响应超时时间
                .build();

CloseableHttpClient httpClient = HttpClients.custom()
                .setDefaultRequestConfig(requestConfig)
                .build()

2. HttpClient使用入门

2.0 构建URI

// 构建URI 方式1
URI uri = URI.create("http://localhost:8080/hello?user=roylion")

// 构建URI 方式2
URI uri = new URIBuilder("http://localhost:8080")
                .setPath("/hello")
                .addParameter("user", "roylion")
                .build();

2.1 Get请求

基础GET请求

    /**
     * 基础get请求
     *
     * @param uri     请求路径
     * @param headers 请求header参数 {@link BasicHeader#BasicHeader(String, String)}
     * @param reqCfg  请求配置参数
     * @return 返回响应, 用完必须close()
     * @throws IOException
     */
    public static CloseableHttpResponse baseGet(URI uri, Header[] headers, RequestConfig reqCfg) throws IOException {
        final HttpGet httpGet = new HttpGet();
        httpGet.setURI(uri);
        httpGet.setHeaders(headers);
        httpGet.setConfig(reqCfg);
        return httpClient.execute(httpGet);
    }

GET - FORM表单提交

   /**
     * form表单提交
     * 
     * @param uri      请求路径
     * @param formData 表单数据列表
     * @return 响应结果
     * @throws URISyntaxException
     * @throws IOException
     */
    public static String getFrom(URI uri, List<NameValuePair> formData) throws URISyntaxException, IOException {
        URIBuilder uriBuilder = new URIBuilder(uri);
        for (NameValuePair nameValuePair : formData) {
            uriBuilder.addParameter(nameValuePair.getName(), nameValuePair.getValue());
        }
        URI finalUri = uriBuilder.build();

        Header[] headers = new Header[]{new BasicHeader("Content-Type", "application/x-www-form-urlencoded")};

        try (CloseableHttpResponse resp = baseGet(finalUri, headers, null);) {
            return toString(resp.getEntity());
        }
    }

2.2 Post请求

基础POST请求

   /**
     * 基础post请求
     *
     * @param uri        请求路径
     * @param headers    请求header参数 {@link BasicHeader#BasicHeader(String, String)}
     * @param reqCfg     请求配置参数
     * @param httpEntity 请求体对象
     *                   String(文本, json...) {@link StringEntity}
     *                   form(表单) {@link UrlEncodedFormEntity}
     *                   multipartForm(文件, 图片, 文本...) {@link MultipartEntityBuilder#build()}
     * @return 返回响应, 用完必须close()
     * @throws IOException
     */
    public static CloseableHttpResponse basePost(URI uri, Header[] headers, RequestConfig reqCfg, HttpEntity httpEntity) throws IOException {
        HttpPost httpPost = new HttpPost();
        httpPost.setURI(uri);
        httpPost.setHeaders(headers);
        httpPost.setConfig(reqCfg);
        httpPost.setEntity(httpEntity);
        return httpClient.execute(httpPost);
    }

POST - JSON 请求

    /**
     * post请求, 传递JSON请求体
     *
     * @param uri      请求路径
     * @param jsonBody json字符串
     * @return 返回响应结果
     * @throws IOException
     */
    public static String postJson(URI uri, String jsonBody) throws IOException {
        StringEntity body = new StringEntity(jsonBody, ContentType.APPLICATION_JSON);
        try (CloseableHttpResponse resp = basePost(uri, null, null, body)) {
            return toString(resp.getEntity());
        }
    }

POST - FORM 表单提交

    /**
     * post请求, 表单提交
     *
     * @param uri      请求路径
     * @param formData 表单参数列表
     * @return 返回响应结果
     * @throws IOException
     */
    public static String postForm(URI uri, List<NameValuePair> formData) throws IOException {
        UrlEncodedFormEntity body = new UrlEncodedFormEntity(formData);
        try (CloseableHttpResponse resp = basePost(uri, null, null, body)) {
            return toString(resp.getEntity());
        }
    }

POST文件上传

    /**
     * 上传文件
     *
     * @param uri         请求路径
     * @param name        上传文件参数名称
     * @param inputStream 文件的输入流
     * @param contentType 文件类型
     *                    普通文件 {@link ContentType#MULTIPART_FORM_DATA}
     *                    图片 {@link ContentType#IMAGE_JPEG} {@link ContentType#IMAGE_PNG} ...
     * @param fileName    上传文件名称
     * @return
     * @throws IOException
     */
    public static String postFile(URI uri, String name, InputStream inputStream, ContentType contentType, String fileName) throws IOException {
        // HttpMultipartMode.RFC6532参数的设定是为避免文件名为中文时乱码
        MultipartEntityBuilder multipartEntityBuilder = MultipartEntityBuilder.create().setMode(HttpMultipartMode.RFC6532);
        multipartEntityBuilder.addBinaryBody(name, inputStream, contentType, fileName);
        HttpEntity fileEntity = multipartEntityBuilder.build();

        try (CloseableHttpResponse resp = basePost(uri, null, null, fileEntity)) {
            return toString(resp.getEntity());
        }
    }

POST多文件上传

   /**
     * 多文件上传
     *
     * @param uri      请求路径
     * @param name     上传文件参数名称
     * @param fileList 文件列表
     * @return
     */
    public static String postMultipartFile(URI uri, String name, List<InputStreamBody> fileList) throws IOException {
        // HttpMultipartMode.RFC6532参数的设定是为避免文件名为中文时乱码
        MultipartEntityBuilder multipartEntityBuilder = MultipartEntityBuilder.create().setMode(HttpMultipartMode.RFC6532);
        for (InputStreamBody inputStreamBody : fileList) {
            multipartEntityBuilder.addPart(name, inputStreamBody);
        }
        HttpEntity fileEntity = multipartEntityBuilder.build();

        try (CloseableHttpResponse resp = basePost(uri, null, null, fileEntity)) {
            return toString(resp.getEntity());
        }s
    }

3. HttpClient详细配置

		以下配置均参考于HttpClientBuilder#build()方法。
		
   	    // userAgent
        /**
         * 获取agent
         * [system]http.agent
         * 配置userAgent, 优先级高于system. {@link HttpClientBuilder#setUserAgent(String)}
         * 禁用默认userAgent. {@link HttpClientBuilder#disableDefaultUserAgent()}
         */
        String userAgentCopy = VersionInfo.getUserAgent("Apache-HttpClient",
                "org.apache.http.client", HttpClients.class);

        /**
         * 配置公共域名后缀
         *  {@link HttpClientBuilder#setPublicSuffixMatcher(PublicSuffixMatcher)}
         */
        PublicSuffixMatcher publicSuffixMatcherCopy = PublicSuffixMatcherLoader.getDefault();

        // =============================> 连接管理 <============================= //
        /**
         * 请求执行器, 发送请求的核心代码
         *  {@link HttpClientBuilder#setRequestExecutor(HttpRequestExecutor)}
         */
        HttpRequestExecutor requestExecCopy = new HttpRequestExecutor();
        /**
         * SslSocket工厂类
         * [system]https.protocols => String[] supportedProtocols
         * [system]https.cipherSuites => String[] supportedCipherSuites
         * {@link HttpClientBuilder#setSSLSocketFactory(LayeredConnectionSocketFactory)}
         * {@link HttpClientBuilder#setSSLContext(SSLContext)}
         * {@link HttpClientBuilder#setSSLHostnameVerifier(HostnameVerifier)} 用于SSL连接中主机名的校验,因为SSL是要校验证书的
         */
        LayeredConnectionSocketFactory sslSocketFactoryCopy = new SSLConnectionSocketFactory(
                SSLContexts.createDefault(),
                new DefaultHostnameVerifier(publicSuffixMatcherCopy));
        /**
         * 连接管理器(分单连接和池化连接)
         * {@link HttpClientBuilder#setConnectionManager(HttpClientConnectionManager)}
         *
         * [system]http.keepAlive = true
         *      => [system]http.maxConnections = {max} => DefaultMaxPerRoute=max; MaxTotal=2*max
         * {@link HttpClientBuilder#setDnsResolver(DnsResolver)}
         * {@link HttpClientBuilder#setConnectionTimeToLive(long, TimeUnit)}
         * {@link HttpClientBuilder#setDefaultSocketConfig(SocketConfig)}
         * {@link HttpClientBuilder#setDefaultConnectionConfig(ConnectionConfig)}
         * {@link HttpClientBuilder#setMaxConnTotal(int)}
         * {@link HttpClientBuilder#setMaxConnPerRoute(int)}
         */
        HttpClientConnectionManager connManagerCopy = new PoolingHttpClientConnectionManager(
                RegistryBuilder.<ConnectionSocketFactory>create()
                        .register("http", PlainConnectionSocketFactory.getSocketFactory())
                        .register("https", sslSocketFactoryCopy)
                        .build(),
                null,
                null,
                null,
                -1,
                TimeUnit.MILLISECONDS);
        /**
         * 连接复用策略 根据'Connection: keep-alive'确认是否需要保持连接
         * http.keepAlive = false => NoConnectionReuseStrategy.INSTANCE(不复用连接)
         * {@link HttpClientBuilder#setConnectionReuseStrategy(ConnectionReuseStrategy)}
         */
        ConnectionReuseStrategy reuseStrategyCopy = DefaultClientConnectionReuseStrategy.INSTANCE;
        /**
         * keepAlive策略 根据'Keep-Alive: timeout=300'确认连接过期时间
         * {@link HttpClientBuilder#setKeepAliveStrategy(ConnectionKeepAliveStrategy)}
         */
        ConnectionKeepAliveStrategy keepAliveStrategyCopy = DefaultConnectionKeepAliveStrategy.INSTANCE;

        // =============================> 权限认证(代理)相关 <============================= //
        /**
         * 权限认证策略
         * {@link HttpClientBuilder#setTargetAuthenticationStrategy(AuthenticationStrategy)}
         */
        AuthenticationStrategy targetAuthStrategyCopy = TargetAuthenticationStrategy.INSTANCE;
        /**
         * 代理权限认证策略
         * {@link HttpClientBuilder#setProxyAuthenticationStrategy(AuthenticationStrategy)}
         */
        AuthenticationStrategy proxyAuthStrategyCopy = ProxyAuthenticationStrategy.INSTANCE;
        /**
         * 获取用户Token
         * {@link HttpClientBuilder#setUserTokenHandler(UserTokenHandler)}
         */
        UserTokenHandler userTokenHandlerCopy = DefaultUserTokenHandler.INSTANCE;
        /**
         * 权限认证注册仓库
         * {@link HttpClientBuilder#setDefaultAuthSchemeRegistry(Lookup)}
         */
        Lookup<AuthSchemeProvider> authSchemeRegistryCopy = RegistryBuilder.<AuthSchemeProvider>create()
                .register(AuthSchemes.BASIC, new BasicSchemeFactory())
                .register(AuthSchemes.DIGEST, new DigestSchemeFactory())
                .register(AuthSchemes.NTLM, new NTLMSchemeFactory())
                .register(AuthSchemes.SPNEGO, new SPNegoSchemeFactory())
                .register(AuthSchemes.KERBEROS, new KerberosSchemeFactory())
                .build();
        /**
         * 权限认证信息仓库
         * {@link HttpClientBuilder#setDefaultCredentialsProvider(CredentialsProvider)}
         */
        CredentialsProvider defaultCredentialsProvider = new BasicCredentialsProvider();

        // =============================> Cookie相关 <============================= //
        /**
         * Cookie规格仓库
         * {@link HttpClientBuilder#setDefaultCookieSpecRegistry(Lookup)}
         */
        Lookup<CookieSpecProvider> cookieSpecRegistryCopy = CookieSpecRegistries.createDefault(publicSuffixMatcherCopy);
        /**
         * Cookie存储仓库
         * {@link HttpClientBuilder#setDefaultCookieStore(CookieStore)}
         */
        CookieStore defaultCookieStore = new BasicCookieStore();

        // =============================> 执行器职责链 <============================= //
        /** 构建执行器职责链
         * MainClientExec(基础)
         * => ProtocolExec(添加了HttpProcessor拦截器链)
         * => RetryExec(请求异常重试机制)
         * => ServiceUnavailableRetryExec(请求响应异常, 延迟重试机制)
         * => RedirectExec(支持重定向执行器)
         * => BackoffStrategyExec(回退降级机制)
         */
        ClientExecChain execChain = new MainClientExec(  // 核心执行器
                requestExecCopy,
                connManagerCopy,
                reuseStrategyCopy,
                keepAliveStrategyCopy,
                new ImmutableHttpProcessor(new RequestTargetHost(), new RequestUserAgent(userAgentCopy)),
                targetAuthStrategyCopy,
                proxyAuthStrategyCopy,
                userTokenHandlerCopy);

        // =============================> 拦截器 <============================= //
        /**
         * 构建HttpProcessor拦截器链
         *
         * 添加优先级高的拦截器
         * {@link HttpClientBuilder#addInterceptorFirst(HttpRequestInterceptor)}
         * {@link HttpClientBuilder#addInterceptorFirst(HttpResponseInterceptor)}
         */
        final HttpProcessorBuilder b = HttpProcessorBuilder.create();
        b.addAll(
                new RequestDefaultHeaders(Collections.EMPTY_LIST),
                new RequestContent(),
                new RequestTargetHost(),
                new RequestClientConnControl(),
                new RequestUserAgent(userAgentCopy),
                new RequestExpectContinue());
        /**
         * 往请求中添加cookie的拦截器
         * {@link HttpClientBuilder#disableCookieManagement()}
         */
        b.add(new RequestAddCookies());
        /**
         * 往请求中配置'Accept-Encoding'的拦截器
         * {@link HttpClientBuilder#disableContentCompression()}
         * {@link HttpClientBuilder#setContentDecoderRegistry(Map)}
         */
        b.add(new RequestAcceptEncoding());
        /**
         * 权限认证缓存的拦截器
         * {@link HttpClientBuilder#disableAuthCaching()}
         */
        b.add(new RequestAuthCache());
        /**
         * 处理响应中的'Set-Cookie'的拦截器
         * {@link HttpClientBuilder#disableCookieManagement()}
         */
        b.add(new ResponseProcessCookies());
        /**
         * 处理'Content-Encoding'响应首部的拦截器
         * {@link HttpClientBuilder#disableContentCompression()}
         * {@link HttpClientBuilder#setContentDecoderRegistry(Map)}
         */
        b.add(new ResponseContentEncoding());
        /**
         * 添加优先级低的拦截器
         * {@link HttpClientBuilder#addInterceptorLast(HttpRequestInterceptor)}
         * {@link HttpClientBuilder#addInterceptorLast(HttpResponseInterceptor)}
         */
        HttpProcessor httpprocessorCopy = b.build();
        execChain = new ProtocolExec(execChain, httpprocessorCopy); // 带拦截器职责链的执行器

        /**
         * 请求重试机制
         * {@link HttpClientBuilder#disableAutomaticRetries()}
         * {@link HttpClientBuilder#setRetryHandler(HttpRequestRetryHandler)}
         */
        HttpRequestRetryHandler retryHandlerCopy = DefaultHttpRequestRetryHandler.INSTANCE;
        execChain = new RetryExec(execChain, retryHandlerCopy);     // 支持请求异常重试的执行器

        /**
         * 可选, 依赖服务不可用, 提供延迟重试机制
         *
         * {@link HttpClientBuilder#setServiceUnavailableRetryStrategy(ServiceUnavailableRetryStrategy)}
         */
        final ServiceUnavailableRetryStrategy serviceUnavailStrategyCopy = null;
        if (serviceUnavailStrategyCopy != null) {
            execChain = new ServiceUnavailableRetryExec(execChain, serviceUnavailStrategyCopy); // 支持响应异常, 延迟重试的执行器
        }

        /**
         * Http路由规划 (代理, 端口解析...)
         * {@link HttpClientBuilder#setRoutePlanner(HttpRoutePlanner)}
         * {@link HttpClientBuilder#setSchemePortResolver(SchemePortResolver)}
         * {@link HttpClientBuilder#setProxy(HttpHost)}
         */
        SchemePortResolver schemePortResolverCopy = DefaultSchemePortResolver.INSTANCE;
        HttpRoutePlanner routePlannerCopy = new DefaultRoutePlanner(schemePortResolverCopy);
        /**
         * 重定向策略
         * {@link HttpClientBuilder#disableRedirectHandling()}
         * {@link HttpClientBuilder#setRedirectStrategy(RedirectStrategy)}
         */
        RedirectStrategy redirectStrategyCopy = DefaultRedirectStrategy.INSTANCE;
        execChain = new RedirectExec(execChain, routePlannerCopy, redirectStrategyCopy); // 支持重定向的执行器

        /**
         * 回退(服务降级)机制, 重试是自私的, 当重试造成服务超负载时, 应使用回退机制, 使下游系统不可用, 过一段时间后再自动恢复.
         * {@link HttpClientBuilder#setConnectionBackoffStrategy(ConnectionBackoffStrategy)}
         * {@link HttpClientBuilder#setBackoffManager(BackoffManager)}
         */
        if (false) {
            execChain = new BackoffStrategyExec(execChain, null, null);  // 支持回退降级的执行器
        }

        // =============================> 关闭资源 <============================= //
        /**
         * 配置关闭HttpClient时资源释放操作
         * {@link  HttpClientBuilder#addCloseable(Closeable)}
         * {@link  HttpClientBuilder#setConnectionManagerShared(boolean)}
         * {@link  HttpClientBuilder#evictExpiredConnections()}
         * {@link  HttpClientBuilder#evictIdleConnections(long, TimeUnit)}
         *
         */
        List<Closeable> closeablesCopy = new ArrayList<Closeable>(1);
        // 可选, 设置定时关闭空闲/过期的连接
        if (false) {
            final IdleConnectionEvictor connectionEvictor = new IdleConnectionEvictor(connManagerCopy,
                    10, TimeUnit.SECONDS,
                    0, null);
            closeablesCopy.add(new Closeable() {
                @Override
                public void close() throws IOException {
                    connectionEvictor.shutdown();
                    try {
                        connectionEvictor.awaitTermination(1L, TimeUnit.SECONDS);
                    } catch (final InterruptedException interrupted) {
                        Thread.currentThread().interrupt();
                    }
                }
            });
            // 启动定时关闭空闲/过期的连接
            connectionEvictor.start();
        }
        // 关闭连接管理器
        closeablesCopy.add(new Closeable() {
            @Override
            public void close() throws IOException {
                connManagerCopy.shutdown();
            }
        });

        new InternalHttpClient(
                execChain,
                connManagerCopy,
                routePlannerCopy,
                cookieSpecRegistryCopy,
                authSchemeRegistryCopy,
                defaultCookieStore,
                defaultCredentialsProvider,
                defaultRequestConfig != null ? defaultRequestConfig : RequestConfig.DEFAULT,
                closeablesCopy);
上一篇:springboot项目提示No converter found for return value of type:


下一篇:Eigen在VS2017下的安装与使用