SpringBoot Configuring okhttp3 Operations

  • 2021-08-28 20:22:15
  • OfStack

1. Maven Adding Dependencies


<dependency>
 <groupId>com.squareup.okhttp3</groupId>
 <artifactId>okhttp</artifactId>
 <version>3.10.0</version>
</dependency>

2. application. properties configuration file


ok.http.connect-timeout=30
ok.http.read-timeout=30
ok.http.write-timeout=30
#  Maximum number of free connections in the whole connection pool 
ok.http.max-idle-connections=200
#  The maximum connection idle time is  300  Seconds 
ok.http.keep-alive-duration=300

3. OkHttpConfiguration configuration class


import okhttp3.ConnectionPool;
import okhttp3.OkHttpClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.net.ssl.*;
import java.security.*;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.concurrent.TimeUnit;
/**
 * @author Answer.AI.L
 * @date 2019-04-09
 */
@Configuration
public class OkHttpConfiguration {
 @Value("${ok.http.connect-timeout}")
 private Integer connectTimeout;
 @Value("${ok.http.read-timeout}")
 private Integer readTimeout;
 @Value("${ok.http.write-timeout}")
 private Integer writeTimeout;
 @Value("${ok.http.max-idle-connections}")
 private Integer maxIdleConnections;
 @Value("${ok.http.keep-alive-duration}")
 private Long keepAliveDuration;
 @Bean
 public OkHttpClient okHttpClient() {
  return new OkHttpClient.Builder()
    .sslSocketFactory(sslSocketFactory(), x509TrustManager())
    //  Whether to turn on caching 
    .retryOnConnectionFailure(false)
    .connectionPool(pool())
    .connectTimeout(connectTimeout, TimeUnit.SECONDS)
    .readTimeout(readTimeout, TimeUnit.SECONDS)
    .writeTimeout(writeTimeout,TimeUnit.SECONDS)
   	.hostnameVerifier((hostname, session) -> true)
   	//  Set up proxy 
//   	.proxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress("127.0.0.1", 8888)))
    //  Interceptor 
//    .addInterceptor()
    .build();
 }
 @Bean
 public X509TrustManager x509TrustManager() {
  return new X509TrustManager() {
   @Override
   public void checkClientTrusted(X509Certificate[] chain, String authType)
     throws CertificateException {
   }
   @Override
   public void checkServerTrusted(X509Certificate[] chain, String authType)
     throws CertificateException {
   }
   @Override
   public X509Certificate[] getAcceptedIssuers() {
    return new X509Certificate[0];
   }
  };
 }
 @Bean
 public SSLSocketFactory sslSocketFactory() {
  try {
   //  Trust any link 
   SSLContext sslContext = SSLContext.getInstance("TLS");
   sslContext.init(null, new TrustManager[]{x509TrustManager()}, new SecureRandom());
   return sslContext.getSocketFactory();
  } catch (NoSuchAlgorithmException | KeyManagementException e) {
   e.printStackTrace();
  }
  return null;
 }
 @Bean
 public ConnectionPool pool() {
  return new ConnectionPool(maxIdleConnections, keepAliveDuration, TimeUnit.SECONDS);
 }
}

4. OkHttp category


import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
 * @author Answer.AI.L
 * @date 2019-04-09
 */
@Slf4j
@Component
public class OkHttpCli {
 private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
 private static final MediaType XML = MediaType.parse("application/xml; charset=utf-8");
 @Autowired
 private OkHttpClient okHttpClient;
 /**
  * get  Request 
  * @param url   Request url Address 
  * @return string
  * */
 public String doGet(String url) {
  return doGet(url, null, null);
 }
 /**
  * get  Request 
  * @param url   Request url Address 
  * @param params  Request parameter  map
  * @return string
  * */
 public String doGet(String url, Map<String, String> params) {
  return doGet(url, params, null);
 }
 /**
  * get  Request 
  * @param url   Request url Address 
  * @param headers  Request header field  {k1, v1 k2, v2, ...}
  * @return string
  * */
 public String doGet(String url, String[] headers) {
  return doGet(url, null, headers);
 }
 /**
  * get  Request 
  * @param url   Request url Address 
  * @param params  Request parameter  map
  * @param headers  Request header field  {k1, v1 k2, v2, ...}
  * @return string
  * */
 public String doGet(String url, Map<String, String> params, String[] headers) {
  StringBuilder sb = new StringBuilder(url);
  if (params != null && params.keySet().size() > 0) {
   boolean firstFlag = true;
   for (String key : params.keySet()) {
    if (firstFlag) {
     sb.append("?").append(key).append("=").append(params.get(key));
     firstFlag = false;
    } else {
     sb.append("&").append(key).append("=").append(params.get(key));
    }
   }
  }
  Request.Builder builder = new Request.Builder();
  if (headers != null && headers.length > 0) {
   if (headers.length % 2 == 0) {
    for (int i = 0; i < headers.length; i = i + 2) {
     builder.addHeader(headers[i], headers[i + 1]);
    }
   } else {
    log.warn("headers's length[{}] is error.", headers.length);
   }
  }
  Request request = builder.url(sb.toString()).build();
  log.info("do get request and url[{}]", sb.toString());
  return execute(request);
 }
 /**
  * post  Request 
  * @param url   Request url Address 
  * @param params  Request parameter  map
  * @return string
  */
 public String doPost(String url, Map<String, String> params) {
  FormBody.Builder builder = new FormBody.Builder();
  if (params != null && params.keySet().size() > 0) {
   for (String key : params.keySet()) {
    builder.add(key, params.get(key));
   }
  }
  Request request = new Request.Builder().url(url).post(builder.build()).build();
  log.info("do post request and url[{}]", url);
  return execute(request);
 }
 /**
  * post  Request ,  Request data is  json  String of 
  * @param url   Request url Address 
  * @param json   Request data , json  String 
  * @return string
  */
 public String doPostJson(String url, String json) {
  log.info("do post request and url[{}]", url);
  return exectePost(url, json, JSON);
 }
 /**
  * post  Request ,  Request data is  xml  String of 
  * @param url   Request url Address 
  * @param xml   Request data , xml  String 
  * @return string
  */
 public String doPostXml(String url, String xml) {
  log.info("do post request and url[{}]", url);
  return exectePost(url, xml, XML);
 }
 private String exectePost(String url, String data, MediaType contentType) {
  RequestBody requestBody = RequestBody.create(contentType, data);
  Request request = new Request.Builder().url(url).post(requestBody).build();
  return execute(request);
 }
 private String execute(Request request) {
  Response response = null;
  try {
   response = okHttpClient.newCall(request).execute();
   if (response.isSuccessful()) {
    return response.body().string();
   }
  } catch (Exception e) {
   log.error(ExceptionUtils.getStackTrace(e));
  } finally {
   if (response != null) {
    response.close();
   }
  }
  return "";
 }
}

Step 5 Use validation


@RestController
public class AnswerController {
 @Autowired
 private OkHttpCli okHttpCli;
 
 
 @RequestMapping(value = "show", method = RequestMethod.GET)
 public String show() {
  String url = "https://www.baidu.com/";
 String message = okHttpCli.doGet(url);
  return message;
 }
 
}

6. Two-way authentication (to be certified)


@Bean
public SSLSocketFactory sslSocketFactory() {
 String certPath = "";
 String caPath = "";
 String certPwd = "";
 String caPwd = "";
 try {
  ClassPathResource selfcertPath = new ClassPathResource(certPath);
  ClassPathResource trustcaPath = new ClassPathResource(caPath);
  KeyStore selfCert = KeyStore.getInstance("pkcs12");
  selfCert.load(selfcertPath.getInputStream(), certPwd.toCharArray());
  KeyManagerFactory kmf = KeyManagerFactory.getInstance("sunx509");
  kmf.init(selfCert, certPwd.toCharArray());
  KeyStore caCert = KeyStore.getInstance("jks");
  caCert.load(trustcaPath.getInputStream(), caPwd.toCharArray());
  TrustManagerFactory tmf = TrustManagerFactory.getInstance("sunx509");
  tmf.init(caCert);
  SSLContext sslContext = SSLContext.getInstance("TLS");
  sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
  return sslContext.getSocketFactory();
 } catch (Exception e) {
  e.printStackTrace();
 }
 return null;
}

Added: Spring Cloud Feign Summarize Problems, Points to Pay Attention, Performance Tuning, Switch okhttp3

Summary of Feign Frequently Asked Questions

If the @ PathVariable interface is used, the value attribute must be specified


// In 1 In some earlier versions,  @PathVariable("id")  In  "id"  That is, value Property, which must be specified and cannot be omitted. 
@FeignClient("microservice-provider-user")
public interface UserFeignClient {
 @RequestMapping(value = "/simple/{id}", method = RequestMethod.GET)
 public User findById(@PathVariable("id") Long id);
 ...
}

Java Code Custom Feign Client Attention Points and Pits


@FeignClient(name = "microservice-provider-user", configuration = UserFeignConfig.class)
public interface UserFeignClient {
 @GetMapping("/users/{id}")
 User findById(@PathVariable("id") Long id);
}
/**
 *  The Feign Client Configuration class of, note: 
 * 1.  This class can go out independently; 
 * 2.  You can also add a @Configuration The statement is 1 Configuration classes; 
 *  Configuration class can also be added @Configuration Note, stating that this is 1 Configuration classes; 
 *  But do not place this in the main application context at this time @ComponentScan Of the packets scanned, 
 *  Otherwise, the configuration will be used by all Feign Client Sharing, fine-grained configuration cannot be realized! 
 *  Personal advice: Like me 1 Sample, do not add @Configuration Annotation 
 *
 * @author zhouli
 */
class UserFeignConfig {
 @Bean
 public Logger.Level logger() {
 return Logger.Level.FULL;
 }
}

You can also add @ Configuraiton annotation to the configuration class, stating that this is a configuration class; But don't put this in the package scanned by the main application context @ ComponentScan at this time, otherwise, this configuration will be shared by all Feign Client (equivalent to becoming a general configuration, but in fact, it is a problem caused by overlapping Spring parent-child context scanning packages), and fine-grained configuration cannot be realized!

Personal suggestion: Like me 1, don't add @ Configuration annotation, so as to save going into the pit.

Best Practice: Try to customize the configuration of Feign with configuration attributes! ! !

@ FeignClient Annotation Properties


//@FeignClient(name = "microservice-provider-user")
// In the early days Spring Cloud Version, there is no need to provide name Property, from the Brixton At the beginning of the edition, @FeignClient Must provide name Property, otherwise the application will not start normally! 
// In addition, name , url Placeholders are supported for properties such as. For example: 
@FeignClient(name = "${feign.name}", url = "${feign.url}")

Class level @ RequestMapping is loaded by Spring MVC


@RequestMapping("/users")
@FeignClient(name = "microservice-user")
public class TestFeignClient {
 // ...
}

The @ RequestMapping annotation on the class is also loaded by Spring MVC. This problem has now been solved, and earlier versions have two solutions: Scenario 1: No @ RequestMapping annotation on the class; Scenario 2: Add the following code:


ok.http.connect-timeout=30
ok.http.read-timeout=30
ok.http.write-timeout=30
#  Maximum number of free connections in the whole connection pool 
ok.http.max-idle-connections=200
#  The maximum connection idle time is  300  Seconds 
ok.http.keep-alive-duration=300
0

Starvation Load (eager-load) Mode for First Request Failure Ribbon

If you need to generate Hystrix Stream monitoring information, you need to do one additional operation. Feign itself has integrated Hystrix. You can directly use @ FeignClient (value = "microservice-provider-user", fallback = ES90class) to specify fallback class, which inherits the interface marked by @ FeignClient.

But assuming that Hystrix Stream is used for monitoring, by default, accessing http://IP: PORT/actuator/hystrix. stream returns 404, because Feign integrates Hystrix, but does not integrate monitoring of Hystrix. How do I add monitoring support? The following steps are required:

Step 1: Add dependencies, example:


<!--  Integration hystrix , actually feign It comes with it in hystrix The dependency was introduced primarily to use the hystrix-metrics-event-stream , used for dashboard -->
<dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>

Step 2: Add the @ EnableCircuitBreaker annotation to the startup class. Example:


@SpringBootApplication
@EnableFeignClients
@EnableDiscoveryClient
@EnableCircuitBreaker
public class MovieFeignHystrixApplication {
 public static void main(String[] args) {
 SpringApplication.run(MovieFeignHystrixApplication.class, args);
 }
}

Step 3: Expose the hystrix. stream endpoint by adding the following to application. yml:


ok.http.connect-timeout=30
ok.http.read-timeout=30
ok.http.write-timeout=30
#  Maximum number of free connections in the whole connection pool 
ok.http.max-idle-connections=200
#  The maximum connection idle time is  300  Seconds 
ok.http.keep-alive-duration=300
3

In this way, after accessing API of any Feign Client interface, accessing http://IP: PORT/actuator/hystrix. stream will show a large amount of Hystrix monitoring data.

Feign Upload File

Add dependency


ok.http.connect-timeout=30
ok.http.read-timeout=30
ok.http.write-timeout=30
#  Maximum number of free connections in the whole connection pool 
ok.http.max-idle-connections=200
#  The maximum connection idle time is  300  Seconds 
ok.http.keep-alive-duration=300
4

Writing Feign Client


ok.http.connect-timeout=30
ok.http.read-timeout=30
ok.http.write-timeout=30
#  Maximum number of free connections in the whole connection pool 
ok.http.max-idle-connections=200
#  The maximum connection idle time is  300  Seconds 
ok.http.keep-alive-duration=300
5

As the code shows, in this Feign Client, we refer to the configuration class MultipartSupportConfig, and in MultipartSupportConfig, we instantiate SpringFormEncoder. So this Feign Client can be uploaded.

Attention points


ok.http.connect-timeout=30
ok.http.read-timeout=30
ok.http.write-timeout=30
#  Maximum number of free connections in the whole connection pool 
ok.http.max-idle-connections=200
#  The maximum connection idle time is  300  Seconds 
ok.http.keep-alive-duration=300
6

The annotation @ RequestPart (value = "file") in the interface definition cannot be written as @ RequestParam (value = "file").

It is better to set the timeout time of Hystrix as long as 1 point, for example, 5 seconds, otherwise the file may not be uploaded, and Hystrix will timeout, resulting in an error report on the client side.

Implementation of Form Form Submission by Feign

Add dependencies:


<dependency>
 <groupId>io.github.openfeign.form</groupId>
 <artifactId>feign-form</artifactId>
 <version>3.2.2</version>
</dependency>
<dependency>
 <groupId>io.github.openfeign.form</groupId>
 <artifactId>feign-form-spring</artifactId>
 <version>3.2.2</version>
</dependency>

Feign Client Example:


@FeignClient(name = "xxx", url = "http://www.itmuch.com/", configuration = TestFeignClient.FormSupportConfig.class)
public interface TestFeignClient {
 @PostMapping(value = "/test",
   consumes = {MediaType.APPLICATION_FORM_URLENCODED_VALUE},
   produces = {MediaType.APPLICATION_JSON_UTF8_VALUE}
   )
 void post(Map<String, ?> queryParam);
 class FormSupportConfig {
  @Autowired
  private ObjectFactory<HttpMessageConverters> messageConverters;
  // new1 A form Encoder, implementation support form Form submission 
  @Bean
  public Encoder feignFormEncoder() {
   return new SpringFormEncoder(new SpringEncoder(messageConverters));
  }
  //  Open Feign Log of 
  @Bean
  public Logger.Level logger() {
   return Logger.Level.FULL;
  }
 }
}

Invoke the example:


ok.http.connect-timeout=30
ok.http.read-timeout=30
ok.http.write-timeout=30
#  Maximum number of free connections in the whole connection pool 
ok.http.max-idle-connections=200
#  The maximum connection idle time is  300  Seconds 
ok.http.keep-alive-duration=300
9

Journal:


...[TestFeignClient#post] ---> POST http://www.baidu.com/test HTTP/1.1
...[TestFeignClient#post] Accept: application/json;charset=UTF-8
...[TestFeignClient#post] Content-Type: application/x-www-form-urlencoded; charset=UTF-8
...[TestFeignClient#post] Content-Length: 30
...[TestFeignClient#post] 
...[TestFeignClient#post] password=pwd&username=zhangsan
...[TestFeignClient#post] ---> END HTTP (30-byte body)

It can be seen from the log that Feign can submit data by using Form form at this time.

Feign GET Request How to Construct Multiple Parameters

Suppose the requested URL contains multiple parameters, such as http://microservice-provider-user/get? id=1 & username = Zhang 3, how to use Feign construction? As we know, Spring Cloud adds Spring MVC annotation support to Feign, so we might as well try 1 according to the writing of Spring MVC:


@FeignClient("microservice-provider-user")
public interface UserFeignClient {
 @RequestMapping(value = "/get", method = RequestMethod.GET)
 public User get0(User user);
}

However, this writing is incorrect, and the console will output an exception similar to the following.


feign.FeignException: status 405 reading UserFeignClient#get0(User); content:
{"timestamp":1482676142940,"status":405,"error":"Method Not Allowed","exception":"org.springframework.web.HttpRequestMethodNotSupportedException","message":"Request method 'POST' not supported","path":"/get"}

As you can see from the exception, Feign uses the POST method to send the request even though we specified the GET method. This leads to an anomaly. The correct writing is as follows

Method 1 [Recommended] Note: Fegin's inheritance pattern cannot be used with this method


@FeignClient("microservice-provider-user")
public interface UserFeignClient {
 @GetMapping("/get")
 public User get0(@SpringQueryMap User user);
}

Method 2 [Recommended]


@FeignClient(name = "microservice-provider-user")
public interface UserFeignClient {
 @RequestMapping(value = "/get", method = RequestMethod.GET)
 public User get1(@RequestParam("id") Long id, @RequestParam("username") String username);
}

This is the most intuitive way. URL has several parameters, and methods in Feign interface have several parameters. Use the @ RequestParam annotation to specify what the parameters of the request are.

Method 3 [Not Recommended] Multi-parameter URL can also be constructed using Map. This simplifies the writing of the Feign interface when the target URL parameters are numerous.


@FeignClient(name = "microservice-provider-user")
public interface UserFeignClient {
 @RequestMapping(value = "/get", method = RequestMethod.GET)
 public User get2(@RequestParam Map<String, Object> map);
}

When called, you can use code similar to the following.


public User get(String username, String password) {
 HashMap<String, Object> map = Maps.newHashMap();
 map.put("id", "1");
 map.put("username", " Zhang 3");
 return this.userFeignClient.get2(map);
}

Note: This method is not recommended. Mainly because of poor readability, and if the parameters are empty, there will be 1 problem, such as map. put ("username", null); Causes the service caller (consumer service) to receive username as "" instead of null.

Switch to Okhttp3 to improve QPS performance optimization

Adding dependency to introduce okhttp3


<dependency>
 <groupId>io.github.openfeign</groupId>
 <artifactId>feign-okhttp</artifactId>
 <version>${version}</version>
</dependency>

Write configuration


feign:
 # feign Enable hystrix In order to fuse and degrade 
 # hystrix:
 # enabled: true
 #  Enable  okhttp  Turn off default  httpclient
 httpclient:
 enabled: false # Shut down httpclient
 #  Configure connection pooling 
 max-connections: 200 #feign Maximum number of connections of 
 max-connections-per-route: 50 #fegin Maximum number of connections per path 
 okhttp:
 enabled: true
 #  Compression of request and response to improve communication efficiency 
 compression:
 request:
  enabled: true
  min-request-size: 2048
  mime-types: text/xml,application/xml,application/json
 response:
  enabled: true

Parameter configuration


/**
 *  Configure  okhttp  Connection pool 
 * ConnectionPool  Create by default 5 Threads, keeping 5 Minute long connection 
 */
@Configuration
@ConditionalOnClass(Feign.class)
@AutoConfigureBefore(FeignAutoConfiguration.class) //SpringBoot Automatic configuration 
public class OkHttpConfig {
 //  By default, foreigners leave you with Chinese garbled eggs, and add it  OK
 @Bean
 public Encoder encoder() {
  return new FormEncoder();
 }
 @Bean
 public okhttp3.OkHttpClient okHttpClient() {
  return new okhttp3.OkHttpClient.Builder()
    // Set connection timeout 
    .connectTimeout(10, TimeUnit.SECONDS)
    // Set read timeout 
    .readTimeout(10, TimeUnit.SECONDS)
    // Set write timeout 
    .writeTimeout(10, TimeUnit.SECONDS)
    // Whether to reconnect automatically 
    .retryOnConnectionFailure(true)
    .connectionPool(new ConnectionPool(10, 5L, TimeUnit.MINUTES))
    .build();
 }
}

Related articles: