【使用新东西】在Spring项目中如何优雅调用http请求

相信各位后端开发工程师项目中util包中都会有HttpUtils这中字眼的工具类,它里面主要是用到:

import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.ParseException;
import org.apache.http.client.ClientProtocolException;
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.config.ConnectionConfig;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.config.SocketConfig;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContexts;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;

然后我们在配置的时候是这样:

/**
     * 发送http get请求
     *
     * @param url        请求的url
     * @param headersMap 请求头
     * @param params     请求的参数
     * @return
     */
    public static String getRequest(String url, Map<String, String> headersMap, Map<String, Object> params) {
        String result = null;
        CloseableHttpClient httpClient = buildHttpClient();
        try {
            String apiUrl = url;
            if (null != params && params.size() > 0) {
                StringBuffer param = new StringBuffer();
                int i = 0;
                for (String key : params.keySet()) {
                    if (i == 0)
                        param.append("?");
                    else
                        param.append("&");
                    param.append(key).append("=").append(params.get(key));
                    i++;
                }
                apiUrl += param;
            }

            HttpGet httpGet = new HttpGet(apiUrl);
            if (null != headersMap && headersMap.size() > 0) {
                for (Entry<String, String> entry : headersMap.entrySet()) {
                    String key = entry.getKey();
                    String value = entry.getValue();
                    httpGet.addHeader(new BasicHeader(key, value));
                }
            }
            CloseableHttpResponse response = httpClient.execute(httpGet);
            try {
                HttpEntity entity = response.getEntity();
                if (null != entity) {
                    result = EntityUtils.toString(entity, defaultEncoding);
                }
            } finally {
                response.close();
            }
        } catch (ClientProtocolException e) {
            e.printStackTrace();
        } catch (ParseException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                httpClient.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return result;
    }
public static  String connectHttpsByPost(String path, byte[] file,String fileName) throws Exception {
        URL urlObj = new URL(path);
        //连接
        HttpURLConnection con = (HttpURLConnection) urlObj.openConnection();
        String result = null;
        con.setDoInput(true);

        con.setDoOutput(true);

        con.setUseCaches(false); // post方式不能使用缓存

        // 设置请求头信息
        con.setRequestProperty("Connection", "Keep-Alive");
        con.setRequestProperty("Charset", "UTF-8");
        // 设置边界
        String BOUNDARY = "----------" + System.currentTimeMillis();
        con.setRequestProperty("Content-Type",
                "multipart/form-data; boundary="
                        + BOUNDARY);

        // 请求正文信息
        // 第一部分:
        StringBuilder sb = new StringBuilder();
        sb.append("--"); // 必须多两道线
        sb.append(BOUNDARY);
        sb.append("\r\n");
        sb.append("Content-Disposition: form-data;name=\"media\";filelength=\"" + file.length + "\";filename=\""

                + fileName + "\"\r\n");
        sb.append("Content-Type:application/octet-stream\r\n\r\n");
        byte[] head = sb.toString().getBytes("utf-8");
        // 获得输出流
        OutputStream out = new DataOutputStream(con.getOutputStream());
        // 输出表头
        out.write(head);

        // 文件正文部分
        // 把文件已流文件的方式 推入到url中
        DataInputStream in = new DataInputStream(new ByteArrayInputStream(file));
        int bytes = 0;
        byte[] bufferOut = new byte[1024];
        while ((bytes = in.read(bufferOut)) != -1) {
            out.write(bufferOut, 0, bytes);
        }
        in.close();
        // 结尾部分
        byte[] foot = ("\r\n--" + BOUNDARY + "--\r\n").getBytes("utf-8");// 定义最后数据分隔线
        out.write(foot);
        out.flush();
        out.close();
        StringBuffer buffer = new StringBuffer();
        BufferedReader reader = null;
        try {
            // 定义BufferedReader输入流来读取URL的响应
            reader = new BufferedReader(new InputStreamReader(con.getInputStream()));
            String line = null;
            while ((line = reader.readLine()) != null) {
                buffer.append(line);
            }
            if (result == null) {
                result = buffer.toString();
            }
        } catch (IOException e) {
            System.out.println("发送POST请求出现异常!" + e);
            e.printStackTrace();
        } finally {
            if (reader != null) {
                reader.close();
            }
        }
        return result;
    }

是不是很恶心,那么如何让我们http请求变得优雅些呢?

这里我就要隆重介绍Spring提供的一种机制:RestTemplate

RestTemplate是Spring提供的用于访问Rest服务的客户端,RestTemplate提供了多种便捷访问远程Http服务的方法,能够大大提高客户端的编写效率。

在spring项目中该如何配置进来呢?
这里我介绍两种配置方式

1.配置类

首先创建RestTemplateConfig.class

import com.alibaba.dubbo.common.logger.Logger;
import com.alibaba.dubbo.common.logger.LoggerFactory;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.impl.client.DefaultConnectionKeepAliveStrategy;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.Header;
import org.apache.http.message.BasicHeader;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.web.client.RestTemplate;

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * RestTemplate 配置信息
 *
* @author xc
* @date 2020/8/11
*/
@Configuration
public class RestTemplateConfig {

    private static final Logger logger= LoggerFactory.getLogger(RestTemplateConfig.class);

    @Bean
    public RestTemplate restTemplate() {
        // 添加内容转换器,使用默认的内容转换器
        RestTemplate restTemplate = new RestTemplate(httpRequestFactory());
        // 设置编码格式为UTF-8
        List<HttpMessageConverter<?>> converterList = restTemplate.getMessageConverters();
        HttpMessageConverter<?> converterTarget = null;
        for (HttpMessageConverter<?> item : converterList) {
            if (item.getClass() == StringHttpMessageConverter.class) {
                converterTarget = item;
                break;
            }
        }
        if (converterTarget != null) {
            converterList.remove(converterTarget);
        }
        HttpMessageConverter<?> converter = new StringHttpMessageConverter(StandardCharsets.UTF_8);
        converterList.add(1,converter);

        logger.info("-----restTemplate-----初始化完成");
        return restTemplate;
    }

    @Bean
    public ClientHttpRequestFactory httpRequestFactory() {

        return new HttpComponentsClientHttpRequestFactory(httpClient());

    }

    @Bean
    public HttpClient httpClient() {
        // 长连接保持30秒
        PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(30, TimeUnit.SECONDS);
        //设置整个连接池最大连接数 根据自己的场景决定
        connectionManager.setMaxTotal(500);
        //同路由的并发数,路由是对maxTotal的细分
        connectionManager.setDefaultMaxPerRoute(500);

        //requestConfig
        RequestConfig requestConfig = RequestConfig.custom()
                //服务器返回数据(response)的时间,超过该时间抛出read timeout
                .setSocketTimeout(10000)
                //连接上服务器(握手成功)的时间,超出该时间抛出connect timeout
                .setConnectTimeout(5000)
                //从连接池中获取连接的超时时间,超过该时间未拿到可用连接,会抛出org.apache.http.conn.ConnectionPoolTimeoutException: Timeout waiting for connection from pool
                .setConnectionRequestTimeout(500)
                .build();
        //headers
        List<Header> headers = new ArrayList<>();
        headers.add(new BasicHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.16 Safari/537.36"));
        headers.add(new BasicHeader("Accept-Encoding", "gzip,deflate"));
        headers.add(new BasicHeader("Accept-Language", "zh-CN"));
        headers.add(new BasicHeader("Connection", "Keep-Alive"));
        headers.add(new BasicHeader("Content-type", "application/json;charset=UTF-8"));

        return HttpClientBuilder.create()
                .setDefaultRequestConfig(requestConfig)
                .setConnectionManager(connectionManager)
                .setDefaultHeaders(headers)
                // 保持长连接配置,需要在头添加Keep-Alive
                .setKeepAliveStrategy(new DefaultConnectionKeepAliveStrategy())
                //重试次数,默认是3次,没有开启
                .setRetryHandler(new DefaultHttpRequestRetryHandler(2, true))
                .build();
    }
}

2.spring配置文件(推荐

 spring配置文件:
  <!--使用httpclient的实现,带连接池-->
  <bean id="pollingConnectionManager" class="org.apache.http.impl.conn.PoolingHttpClientConnectionManager">
      <!--整个连接池的并发-->
      <property name="maxTotal" value="1000" />
      <!--每个主机的并发-->
      <property name="defaultMaxPerRoute" value="1000" />
  </bean>
 
  <bean id="httpClientBuilder" class="org.apache.http.impl.client.HttpClientBuilder" factory-method="create">
      <property name="connectionManager" ref="pollingConnectionManager" />
      <!--开启重试-->
      <property name="retryHandler">
          <bean class="org.apache.http.impl.client.DefaultHttpRequestRetryHandler">
              <constructor-arg value="2"/>
              <constructor-arg value="true"/>
          </bean>
      </property>
      <property name="defaultHeaders">
          <list>
              <bean class="org.apache.http.message.BasicHeader">
                  <constructor-arg value="User-Agent"/>
                  <constructor-arg value="Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.16 Safari/537.36"/>
              </bean>
              <bean class="org.apache.http.message.BasicHeader">
                  <constructor-arg value="Accept-Encoding"/>
                  <constructor-arg value="gzip,deflate"/>
              </bean>
              <bean class="org.apache.http.message.BasicHeader">
                  <constructor-arg value="Accept-Language"/>
                  <constructor-arg value="zh-CN"/>
              </bean>
          </list>
      </property>
  </bean>
 
  <bean id="httpClient" factory-bean="httpClientBuilder" factory-method="build" />
 
  <bean id="clientHttpRequestFactory" class="org.springframework.http.client.HttpComponentsClientHttpRequestFactory">
      <constructor-arg ref="httpClient"/>
      <!--连接超时时间,毫秒-->
      <property name="connectTimeout" value="5000"/>
      <!--读写超时时间,毫秒-->
      <property name="readTimeout" value="10000"/>
  </bean>
 
  <bean id="restTemplate" class="org.springframework.web.client.RestTemplate">
      <constructor-arg ref="clientHttpRequestFactory"/>
      <property name="errorHandler">
          <bean class="org.springframework.web.client.DefaultResponseErrorHandler"/>
      </property>
      <property name="messageConverters">
          <list>
              <bean class="org.springframework.http.converter.FormHttpMessageConverter"/>
              <bean class="org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter"/>
              <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/>
              <bean class="org.springframework.http.converter.StringHttpMessageConverter">
                  <property name="supportedMediaTypes">
                      <list>
                          <value>text/plain;charset=UTF-8</value>
                      </list>
                  </property>
              </bean>
          </list>
      </property>
  </bean>

这里给出一个我再调用微信公众号接口时的使用案例:

    @Autowired
    RestTemplateConfig restTemplateConfig;

    private static final String file_url_ps = "https://api.weixin.qq.com/cgi-bin/media/uploadimg";

    /**
     * 微信公众号 群发图文信息
     * 示例: https://api.weixin.qq.com/cgi-bin/media/uploadimg?access_token=ACCESS_TOKEN
     *
     * @param articles
     * @return
     */
    public String pulishNews(Articles articles) {
        RestTemplate restTemplate = restTemplateConfig.restTemplate();
        StringBuilder sb = new StringBuilder();
        String refresh = refresh();

        sb.append(file_url_ps).append(wenhao);
        sb.append(access_token).append(dengyu).append(refresh);
        String sendUrl = sb.toString();

        String jsonNews = JSONObject.toJSONString(articles);
        String jsonObject = restTemplate.postForObject(sendUrl, jsonNews, String.class);
        return jsonObject;
    }

这里我的一个post请求 在配置好RestTemplate之后总共就一行代码 【String jsonObject = restTemplate.postForObject(sendUrl, jsonNews, String.class);】就完成Post 这在以前都是不敢想象的。

其实我对resetTemplate很多高级用法尚未了解,这里只是简单应用,灵活应用可以减少我们的工作量,例如在post请求就有

这里就先抛砖引玉,具体详见
—–> 官方文档API
—–> GitHub

发表评论

邮箱地址不会被公开。 必填项已用*标注