/*
|
* Copyright (c) 2018-2028, DreamLu All rights reserved.
|
*
|
* Redistribution and use in source and binary forms, with or without
|
* modification, are permitted provided that the following conditions are met:
|
*
|
* Redistributions of source code must retain the above copyright notice,
|
* this list of conditions and the following disclaimer.
|
* Redistributions in binary form must reproduce the above copyright
|
* notice, this list of conditions and the following disclaimer in the
|
* documentation and/or other materials provided with the distribution.
|
* Neither the name of the dreamlu.net developer nor the names of its
|
* contributors may be used to endorse or promote products derived from
|
* this software without specific prior written permission.
|
* Author: DreamLu 卢春梦 (596392912@qq.com)
|
*/
|
package org.springblade.core.cloud.http;
|
|
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
import lombok.RequiredArgsConstructor;
|
import lombok.extern.slf4j.Slf4j;
|
import okhttp3.ConnectionPool;
|
import okhttp3.OkHttpClient;
|
import org.springblade.core.cloud.http.logger.HttpLoggingInterceptor;
|
import org.springblade.core.cloud.http.logger.OkHttpSlf4jLogger;
|
import org.springblade.core.tool.ssl.DisableValidationTrustManager;
|
import org.springblade.core.tool.ssl.TrustAllHostNames;
|
import org.springblade.core.tool.utils.Charsets;
|
import org.springblade.core.tool.utils.Holder;
|
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
import org.springframework.boot.web.client.RestTemplateBuilder;
|
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
|
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.annotation.Bean;
|
import org.springframework.http.client.OkHttp3ClientHttpRequestFactory;
|
import org.springframework.http.converter.HttpMessageConverter;
|
import org.springframework.http.converter.StringHttpMessageConverter;
|
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
|
import org.springframework.web.client.RestTemplate;
|
|
import javax.net.ssl.SSLContext;
|
import javax.net.ssl.SSLSocketFactory;
|
import javax.net.ssl.TrustManager;
|
import javax.net.ssl.X509TrustManager;
|
import java.security.KeyManagementException;
|
import java.security.NoSuchAlgorithmException;
|
import java.util.List;
|
import java.util.concurrent.TimeUnit;
|
|
/**
|
* Http RestTemplateHeaderInterceptor 配置
|
*
|
* @author L.cm
|
*/
|
@Slf4j
|
@RequiredArgsConstructor
|
@AutoConfiguration
|
@ConditionalOnClass(OkHttpClient.class)
|
@ConditionalOnProperty(value = "blade.http.enabled", matchIfMissing = true)
|
public class RestTemplateConfiguration {
|
private final BladeHttpProperties properties;
|
|
/**
|
* okhttp3 请求日志拦截器
|
*
|
* @return HttpLoggingInterceptor
|
*/
|
@Bean
|
public HttpLoggingInterceptor loggingInterceptor() {
|
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor(new OkHttpSlf4jLogger());
|
interceptor.setLevel(properties.getLevel());
|
return interceptor;
|
}
|
|
/**
|
* okhttp3 链接池配置
|
*
|
* @return okhttp3.ConnectionPool
|
*/
|
@Bean
|
@ConditionalOnMissingBean
|
public ConnectionPool httpClientConnectionPool() {
|
int maxTotalConnections = properties.getMaxConnections();
|
long timeToLive = properties.getTimeToLive();
|
TimeUnit ttlUnit = properties.getTimeUnit();
|
return new ConnectionPool(maxTotalConnections, timeToLive, ttlUnit);
|
}
|
|
/**
|
* 配置OkHttpClient
|
*
|
* @param connectionPool 链接池配置
|
* @param interceptor 拦截器
|
* @return OkHttpClient
|
*/
|
@Bean
|
@ConditionalOnMissingBean
|
public OkHttpClient okHttpClient(ConnectionPool connectionPool, HttpLoggingInterceptor interceptor) {
|
boolean followRedirects = properties.isFollowRedirects();
|
int connectTimeout = properties.getConnectionTimeout();
|
return this.createBuilder(properties.isDisableSslValidation())
|
.connectTimeout(connectTimeout, TimeUnit.MILLISECONDS)
|
.writeTimeout(30, TimeUnit.SECONDS)
|
.readTimeout(30, TimeUnit.SECONDS)
|
.followRedirects(followRedirects)
|
.connectionPool(connectionPool)
|
.addInterceptor(interceptor)
|
.build();
|
}
|
|
private OkHttpClient.Builder createBuilder(boolean disableSslValidation) {
|
OkHttpClient.Builder builder = new OkHttpClient.Builder();
|
if (disableSslValidation) {
|
try {
|
X509TrustManager disabledTrustManager = DisableValidationTrustManager.INSTANCE;
|
TrustManager[] trustManagers = new TrustManager[]{disabledTrustManager};
|
SSLContext sslContext = SSLContext.getInstance("SSL");
|
sslContext.init(null, trustManagers, Holder.SECURE_RANDOM);
|
SSLSocketFactory disabledSslSocketFactory = sslContext.getSocketFactory();
|
builder.sslSocketFactory(disabledSslSocketFactory, disabledTrustManager);
|
builder.hostnameVerifier(TrustAllHostNames.INSTANCE);
|
} catch (NoSuchAlgorithmException | KeyManagementException e) {
|
log.warn("Error setting SSLSocketFactory in OKHttpClient", e);
|
}
|
}
|
return builder;
|
}
|
|
@Bean
|
public RestTemplateHeaderInterceptor requestHeaderInterceptor() {
|
return new RestTemplateHeaderInterceptor();
|
}
|
|
@AutoConfiguration
|
@RequiredArgsConstructor
|
@ConditionalOnClass(OkHttpClient.class)
|
@ConditionalOnProperty(value = "blade.http.rest-template.enable")
|
public static class RestTemplateAutoConfiguration {
|
private final ApplicationContext context;
|
|
/**
|
* 普通的 RestTemplate,不透传请求头,一般只做外部 http 调用
|
*
|
* @param okHttpClient OkHttpClient
|
* @return RestTemplate
|
*/
|
@Bean
|
@ConditionalOnMissingBean
|
public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder, OkHttpClient okHttpClient) {
|
restTemplateBuilder.requestFactory(() -> new OkHttp3ClientHttpRequestFactory(okHttpClient));
|
RestTemplate restTemplate = restTemplateBuilder.build();
|
configMessageConverters(context, restTemplate.getMessageConverters());
|
return restTemplate;
|
}
|
}
|
|
@AutoConfiguration
|
@RequiredArgsConstructor
|
@ConditionalOnClass(OkHttpClient.class)
|
@ConditionalOnProperty(value = "blade.http.lb-rest-template.enable")
|
public static class LbRestTemplateAutoConfiguration {
|
private final ApplicationContext context;
|
|
/**
|
* 支持负载均衡的 LbRestTemplate
|
*
|
* @param okHttpClient OkHttpClient
|
* @return LbRestTemplate
|
*/
|
@Bean
|
@LoadBalanced
|
@ConditionalOnMissingBean
|
public LbRestTemplate lbRestTemplate(RestTemplateBuilder restTemplateBuilder, OkHttpClient okHttpClient) {
|
restTemplateBuilder.requestFactory(() -> new OkHttp3ClientHttpRequestFactory(okHttpClient));
|
LbRestTemplate restTemplate = restTemplateBuilder.build(LbRestTemplate.class);
|
restTemplate.getInterceptors().add(context.getBean(RestTemplateHeaderInterceptor.class));
|
configMessageConverters(context, restTemplate.getMessageConverters());
|
return restTemplate;
|
}
|
}
|
|
private static void configMessageConverters(ApplicationContext context, List<HttpMessageConverter<?>> converters) {
|
converters.removeIf(x -> x instanceof StringHttpMessageConverter || x instanceof MappingJackson2HttpMessageConverter);
|
converters.add(new StringHttpMessageConverter(Charsets.UTF_8));
|
converters.add(new MappingJackson2HttpMessageConverter(context.getBean(ObjectMapper.class)));
|
}
|
}
|