Detailed explanation of service call (RestTemplate, HttpClient)

overview

Both microservices and SOA are faced with remote calls between services.

There are two common remote calling methods:

  • RPC: Remote Produce Call remote procedure call, similar to RMI (remote method invoke). Custom data format, based on native TCP communication, fast and efficient. The early webservice and the popular dubbo are both typical representatives of RPC.
  • Http: http is actually a network transmission protocol, based on TCP, which specifies the format of data transmission. Now the client browser and the server basically use the Http protocol for communication, and it can also be used for remote service calls. The disadvantage is that the message package is bloated, and the advantage is that there are no technical restrictions on the service provider and the caller, which is free and flexible, and is more in line with the concept of microservices. Now the popular Rest style can be realized through the http protocol.

If the project all adopts the Java technology stack, it is a good choice to use Dubbo as the microservice architecture.

If the technology stack of the project is diversified, mainly using the Spring and SpringBoot frameworks, then SpringCloud is the best choice to build microservices, using Http to implement inter-service calls.

In java development, use http connection to access the third-party network interface. The commonly used connection tools are RestTemplate, HttpClient and OKHttp.


RestTemplate

Overview and dependencies

HttpClient and OKHttp are two connection tools that are more complicated to use. If you use the spring framework, you can use restTemplate to make http connection requests.

The default connection method of restTemplate is HttpConnection in java, and ClientHttpRequestFactory can be used to specify different HTTP connection methods.

rely

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <version>5.2.2.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.7</version>
</dependency>

configuration class

basic configuration

@Configuration
public class RestTemplateConfig {
    
    
   @Bean
   public RestTemplate restTemplate(ClientHttpRequestFactory factory) {
    
    
       return new RestTemplate(factory);
   }
    
   @Bean
   public ClientHttpRequestFactory simpleClientHttpRequestFactory() {
    
    
       SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
       factory.setReadTimeout(150 * 1000); // ms
       factory.setConnectTimeout(150 * 1000); // ms
       return factory;
   }
}

advanced configuration

import org.apache.http.client.HttpClient;
import org.apache.http.conn.HttpClientConnectionManager;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
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.web.client.RestTemplate;

@Configuration
public class RestTemplateConfig {
    
    
    
    /**
     * http连接管理器
     */
    @Bean
    public HttpClientConnectionManager poolingHttpClientConnectionManager() {
    
    
        /*// 注册http和https请求
        Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
                .register("http", PlainConnectionSocketFactory.getSocketFactory())
                .register("https", SSLConnectionSocketFactory.getSocketFactory())
                .build();
        PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager(registry);*/
        
        PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager();
        // 最大连接数
        poolingHttpClientConnectionManager.setMaxTotal(500);
        // 同路由并发数(每个主机的并发)
        poolingHttpClientConnectionManager.setDefaultMaxPerRoute(100);
        return poolingHttpClientConnectionManager;
    }
    
    /**
     * HttpClient
     * @param poolingHttpClientConnectionManager
     */
    @Bean
    public HttpClient httpClient(HttpClientConnectionManager poolingHttpClientConnectionManager) {
    
    
        HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
        // 设置http连接管理器
        httpClientBuilder.setConnectionManager(poolingHttpClientConnectionManager);
        
        /*// 设置重试次数,默认是3次,没有开启
        httpClientBuilder.setRetryHandler(new DefaultHttpRequestRetryHandler(3, true));*/
        
        // 设置默认请求头
        /*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"));
        httpClientBuilder.setDefaultHeaders(headers);*/
        
        return httpClientBuilder.build();
    }
    
    /**
     * 请求连接池配置
     * @param httpClient
     */
    @Bean
    public ClientHttpRequestFactory clientHttpRequestFactory(HttpClient httpClient) {
    
    
        HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory();
        // httpClient创建器
        clientHttpRequestFactory.setHttpClient(httpClient);
        // 连接超时时间/毫秒(连接上服务器(握手成功)的时间,超出抛出connect timeout)
        clientHttpRequestFactory.setConnectTimeout(5 * 1000);
        // 数据读取超时时间(socketTimeout)/毫秒(服务器返回数据(response)的时间,超过抛出read timeout)
        clientHttpRequestFactory.setReadTimeout(10 * 1000);
        // 从连接池获取请求连接的超时时间,不宜过长,必须设置/毫秒(超时间未拿到可用连接,会抛出org.apache.http.conn.ConnectionPoolTimeoutException: Timeout waiting for connection from pool)
        clientHttpRequestFactory.setConnectionRequestTimeout(10 * 1000);
        return clientHttpRequestFactory;
    }
    
    /**
     * rest模板
     */
    @Bean
    public RestTemplate restTemplate(ClientHttpRequestFactory clientHttpRequestFactory) {
    
    
        // 配置请求工厂
        return new RestTemplate(clientHttpRequestFactory);
    }
    
}

use

Entity class

@Data
@Builder
@NoArgsConstrutor
@AllArgsConstrutor
public class BaseResponse<TempUser> implements Serializable {
    
    
    private static final long serialVersionUID  = 1L;
    
    private String responseCode;
    private String responseMessage;
    private List<TempUser> responseData;
}
@Data
@Builder
@NoArgsConstrutor
@AllArgsConstrutor
public class TempUser implements Serializable {
    
    
    private static final long serialVersionUID  = 1L;
    
    private String userName;   
    private Integer age;
}

GET request

general access

BaseResponse result = restTemplate.getForObject(
    "http://localhost:8080/cs-admin/rest/getUser?userName=张三&age=18", BaseResponse.class);

return HTTP status

ResponseEntity<BaseResponse> responseEntity = restTemplate.getForEntity(
    "http://localhost:8080/cs-admin/rest/getUser?userName=张三&age=18", TempUser.class);
// 获取状态对象
HttpStatus httpStatus = responseEntity.getStatusCode();
// 获取状态码
int statusCodeValue = responseEntity.getStatusCodeValue();
// 获取headers
HttpHeaders httpHeaders = responseEntity.getHeaders();
// 获取body
BaseResponse result = responseEntity.getBody();

Map request parameters

Map<String, Object> paramMap = new HashMap<>();
paramMap.put("userName", "张三");
paramMap.put("age", 18);
BaseResponse result = restTemplate.getForObject(
    "http://localhost:8080/cs-admin/rest/getUser?userName={userName}&age={age}", 
    BaseResponse.class, paramMap);

POST request

common access interface

TempUser param = new TempUser();
param.setUserName("张三");
param.setAge(18);
BaseResponse result = restTemplate.postForObject("http://localhost:8080/cs-admin/rest/getPostUser", 
                                             param, BaseResponse.class);

With HEAD access interface

// 请求头信息
HttpHeaders headers = new HttpHeaders();
//headers.setContentType(MediaType.valueOf("application/json;charset=UTF-8"));
headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
//headers.add("headParam1", "headParamValue");

// 请求体内容
TempUser param = new TempUser();
param.setUserName("张三");
param.setAge(18);

// 组装请求信息
HttpEntity<TempUser> httpEntity=new HttpEntity<>(param, headers);

BaseResponse result = restTemplate.postForObject("http://localhost:8080/cs-admin/rest/getPostUser", 
                                             httpEntity, BaseResponse.class);

Access without request body : only the method is post, and the parameter passing method is still the param method of get

Map<String, Object> paramMap = new HashMap<>();
paramMap.put("userName", "张三");
paramMap.put("age", 18);
BaseResponse result = restTemplate.postForObject(
    "http://localhost:8080/cs-admin/rest/getPostUserNoBody?userName={userName}&age={age}", 
    null, BaseResponse.class, paramMap);
System.out.println(result);

upload files

Background interface code:

@RequestMapping("uploadFile")
public TempUser uploadFile(HttpServletRequest request, TempUser form) {
    
    
    MultipartHttpServletRequest multipartHttpServletRequest = (MultipartHttpServletRequest) request;
    //获取文件信息
    MultipartFile multipartFile = multipartHttpServletRequest.getFile("file");
    TempUser tempUser = new TempUser();
    if (multipartFile != null) {
    
    
        tempUser.setUserName(form.getUserName() + " " + multipartFile.getOriginalFilename());
    }
    if(form!=null){
    
    
        tempUser.setAge(form.getAge());
    }
    return tempUser;
}

interview method:

// 文件
FileSystemResource file=new FileSystemResource("D:\\Elasticsearch权威指南(中文版).pdf");

// 设置请求内容
MultiValueMap<String, Object> param=new LinkedMultiValueMap<>();
param.add("file", file);
// 其他参数
param.add("userName", "张三");
param.add("age", 18);

// 组装请求信息
HttpEntity<MultiValueMap<String, Object>> httpEntity=new HttpEntity<>(param);

// 发送请求
TempUser result = restTemplate.postForObject("http://localhost:8080/cs-admin/rest/uploadFile", 
                                             httpEntity, TempUser.class);

HttpClient

overview

HttpClient creates a connection through a connection pool:

  1. The basic unit of management connection is Route (routing), each route will maintain a certain number of HTTP connections
  2. releaseConnection must be executed after each call
  3. Routing can be understood as a line from the client machine to the target machine
  4. If you do not configure the specified connection manager for httpclient, httpclient will automatically use PoolingHttpClientConnectionManager as the connection manager.
  5. The default maxConnPerRoute and maxConnTotal of PoolingHttpClientConnectionManager are 2 and 20 respectively. That is, each server will only maintain a maximum of 2 connections, which seems a bit small. Therefore, in daily use, it is better to use the connection manager configured by ourselves as much as possible.

connection pool:

  • Connection pooling technology serves as a buffer pooling technology for creating and managing connections.
  • The object managed by the connection pool is a long connection
  • Has the advantage of long connections

**Long connection:** means that once the connection between the client and the server is established, multiple data transmissions can be performed without re-establishing the connection.

  • Advantage:
    • Eliminates the time overhead for each data transfer connection establishment
    • Resource Access Control

**Short connection:**Each data transmission requires the client and server to establish a connection


use

It is very simple to HttpClientsend a request and receive a response. Generally, the following steps are required:

  1. create HttpClientobject
  2. Create an instance of the request method and specify the request URL. If you need to send GETa request, create HttpGetan object; if you need to send POSTa request, create HttpPostan object.
  3. If you need to send request parameters, you can call HttpGet、HttpPostcommon setParams(HetpParams params)methods to add request parameters; for HttpPostobjects, you can also call setEntity(HttpEntity entity)methods to set request parameters, and the parameters must be NameValuePair[]stored in an array
  4. Invokes HttpClientthe object's execute(HttpUriRequest request)send request, which returns aHttpResponse
  5. Call HttpResponse的getAllHeaders(), getHeaders(String name)etc. methods can get the server's response header; the called HttpResponsemethod getEntity()can get HttpEntitythe object, which wraps the server's response content. The program can obtain the response content of the server through this object
  6. Release the connection. The connection must be released regardless of the success of the execution method

rely:

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.2</version>
</dependency>
    <dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient-cache</artifactId>
    <version>4.5.2</version>
</dependency>
    <dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpmime</artifactId>
    <version>4.5.2</version>
</dependency>

java tools

import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.Consts;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;

import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * HttpClient 工具类
 */
@Slf4j
public class HttpClientUtil {
    
    
    public static final String APPLICATION_JSON_VALUE = "application/json";
    private static final Logger logger = log;
    private static final Integer CONN_TIME_OUT = 3000;// 超时时间豪秒
    private static final Integer SOCKET_TIME_OUT = 10000;
    /** 每个路由的最大请求数,默认2 */
    private static final Integer DEFAULT_MAX_PER_ROUTE = 40;
    /** 最大连接数,默认20 */
    private static final Integer MAX_TOTAL = 400;
    
    private static HttpClient httpClient;

    static {
    
    
        // 请求配置
        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectTimeout(CONN_TIME_OUT)
                .setConnectionRequestTimeout(CONN_TIME_OUT)
                .setSocketTimeout(SOCKET_TIME_OUT)
                .build();

        // 管理 http连接池
        PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
        cm.setDefaultMaxPerRoute(DEFAULT_MAX_PER_ROUTE);
        cm.setMaxTotal(MAX_TOTAL);

        httpClient = HttpClients.custom()
            .setConnectionManager(cm)
            .setDefaultRequestConfig(requestConfig)
            .build();
    }

    /**
     * Get请求
     */
    public static String requestGet(String url, Map<String, String> paramsMap) throws Exception {
    
    
        logger.info("GET request  url:{} params:{}", url, paramsMap);
        Long start = System.currentTimeMillis();

        List<NameValuePair> params = initParams(paramsMap);
        // Get请求
        HttpGet httpGet = new HttpGet(url);

        try {
    
    
            // 设置参数
            String str = EntityUtils.toString(new UrlEncodedFormEntity(params, StandardCharsets.UTF_8));
            String uriStr = StringUtils.isEmpty(str) ? 
                httpGet.getURI().toString() : httpGet.getURI().toString() + "?" + str;
            httpGet.setURI(new URI(uriStr));
            // 发送请求
            HttpResponse response = httpClient.execute(httpGet);
            
            logger.info("GET request  url:{} response:{} time:{}",
                    url, response, System.currentTimeMillis() - start);

            // 获取返回数据
            return getSuccessRetFromResp(response, url, JSON.toJSONString(paramsMap));
        } finally {
    
    
            // 必须释放连接,不然连接用完后会阻塞
            httpGet.releaseConnection();
        }
    }

    /**
     * Post请求,Map格式数据
     */
    public static String requestPost(String url, Map<String, String> paramsMap) throws Exception {
    
    
        logger.info("POST request  url:{} params:{}", url, paramsMap);
        Long start = System.currentTimeMillis();

        List<NameValuePair> params = initParams(paramsMap);

        HttpPost httpPost = new HttpPost(url);

        try {
    
    
            httpPost.setEntity(new UrlEncodedFormEntity(params, Consts.UTF_8));

            HttpResponse response = httpClient.execute(httpPost);
            
            logger.info("POST request  url:{} response:{}  time:{}",
                    url, response, System.currentTimeMillis() - start);

            String retStr = getSuccessRetFromResp(response, url, JSON.toJSONString(paramsMap));

            return retStr;
        } finally {
    
    
            httpPost.releaseConnection();
        }
    }

    /**
     * Post请求,json格式数据
     *
     */
    public static String requestPostJsonStr(String url, String json) throws Exception {
    
    
        logger.info("POST request  url:{} params:{}", url, json);
        long start = System.currentTimeMillis();

        HttpPost httpPost = new HttpPost(url);

        try {
    
    
            StringEntity entity = new StringEntity(json, Consts.UTF_8);
            entity.setContentType(APPLICATION_JSON_VALUE);
            httpPost.setEntity(entity);

            HttpResponse response = httpClient.execute(httpPost);

            logger.info("POST request  url:{} response:{}  time:{}", 
                        url, response, System.currentTimeMillis() - start);

            return getSuccessRetFromResp(response, url, json);
        } finally {
    
    
            // 资源释放
            httpPost.releaseConnection();
        }

    }

    /**
     * post Object格式数据
     */
    public static String requestPostJson(String url, Object obj) throws Exception {
    
    
        String params = JSON.toJSONString(obj);
        return requestPostJsonStr(url, params);
    }

    private static String getSuccessRetFromResp(HttpResponse response, String url, String params) throws Exception {
    
    
        String retStr = "";
        // 检验状态码,如果成功接收数据
        int code = response.getStatusLine().getStatusCode();

        if (code == 200) {
    
    
            retStr = EntityUtils.toString(response.getEntity(), Consts.UTF_8);
        } else {
    
    
            throw new RuntimeException(String.format("Http request error:%s, url:%s, params:%s", response, url, params));
        }

        logger.info("Http request retStr:{}. url:{}", retStr, url);
        return retStr;
    }

    private static List<NameValuePair> initParams(Map<String, String> paramsMap) {
    
    
        List<NameValuePair> params = new ArrayList<NameValuePair>();
        if (paramsMap == null)
            return params;

        for (Map.Entry<String, String> entry : paramsMap.entrySet()) {
    
    
            params.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
        }

        return params;
    }
}

Guess you like

Origin blog.csdn.net/footless_bird/article/details/124942235