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();
}
}