SpringBoot如何实现一个实时更新的进度条的示例代码

前言

导入excel表格批量修改状态,期间如果发生错误则所有数据不成功,为了防止重复提交,做一个类似进度条的东东。

那么下面我会结合实际业务对这个功能进行分析和记录。

正文

思路

前端使用bootstrap,后端使用SpringBoot分布式到注册中心,原先的想法是导入表格后异步调用修改数据状态的方法,然后每次计算修改的进度然后存放在session中,前台jquery写定时任务访问获取session中的进度,更新进度条进度和百分比。但是这存在session在服务间不共享,跨域问题。那么换一个方式存放,存放在redis中,前台定时任务直接操作获取redis的数据。

实施
进度条

先来看一下bootstrap的进度条

<div class="progress progress-striped active">
  <div class="progress-bar progress-bar-success" role="progressbar"
     aria-valuenow="60" aria-valuemin="0" aria-valuemax="100"
     style="width: 40%;">
    40%
  </div>
</div>

 进度条更新主要更新style="width: 40%;"的值即可,div里面的40%可以省略,无非时看着明确。

可以考虑将进度条放入弹出层。

定时任务
//点击确认导入执行此方法
function bulkImportChanges() {
  //获取批量操作状态文件
  var files = $("#importChanges").prop("files");
  var changesFile = files[0];
  var formData = new FormData();
  formData.append("importFile",changesFile);
  $.ajax({
    type : 'post',
    url : "/risk/bulk***es",
    data : formData,
    processData : false,   //文件ajax上传要加这两个的,要不然上传不了
    contentType : false,   //
    success : function(obj) {
      //导入成功
      if (obj.rspCode == "00") {
        //定时任务获取redis导入修改进度
        var progress = "";
        var timingTask = setInterval(function(){
          $.ajax({
            type: 'post',
            url: "/risk/t***k",
            dataType : 'json',
            success: function(result) {
              progress = result.value;
              if (progress != "error"){
                var date = progress.substring(0,6);
                //这里更新进度条的进度和数据
                $(".progress-bar").width(parseFloat(date)+"%");
                $(".progress-bar").text(parseFloat(date)+"%");
              }
            }
          });
          //导入修改完成或异常(停止定时任务)
          if (parseInt(progress)==100 || progress == "error") {
            //清除定时执行
            clearInterval(timingTask);
            $.ajax({
              type: 'post',
              url: "/risk/de***ess",
              dataType : 'json',
              success: function(result) {
                $("#bulkImportChangesProcessor").hide();
                if (parseInt(progress) == 100) {
                  alert("批量导入修改状态成功");
                }
                if (progress == "error") {
                  alert("批量导入修改状态失败");
                }
                //获取最新数据
                window.location.href="/risk/re***ByParam" rel="external nofollow" rel="external nofollow" ;
              }
            });
          }
        }, 1000)
      }else {
        $("#bulkImportChangesProcessor").hide();
        alert(obj.rspMsg);
        window.location.href="/risk/re***ByParam" rel="external nofollow" rel="external nofollow" ;
      }
    }
  });
}

解释:点击确认导入文件后成功后开启定时任务每一秒(一千毫秒)访问一次后台获取redis存放的进度,返回更新进度条,如果更新完成或者更新失败(根据后台返回的数据决定)则停止定时任务显示相应的信息并刷新页面。获取最新数据。

后台控制层
/**
 * 退单管理批量修改状态导入文件
 * @param importFile
 * @return
 */
 @ResponseBody
 @RequestMapping("/bulk***es")
 public Map<String,Object> bulk***es(MultipartFile importFile){
   log.info("退单管理批量修改状态导入文件,传入参数:"+importFile);
 Map<String,Object> map = new HashMap<>();
 List<Bulk***esEntity> fromExcel = null;
 try{
      //使用工具类导入转成list
  String[] header = {"sy***um","t***mt","ha***ult","re***nd","sy***nd","r**k"};
  fromExcel = importExcelUtil.importDataFromExcel(importFile, header, BulkImportChangesEntity.class);
  if (fromExcel.size()==0){
  map.put("rspCode","99");
  map.put("rspMsg","导入数据不能为空");
  return map;
  }
 }catch (Exception e){
  map.put("rspCode","99");
  map.put("rspMsg","导入操作表失败,请注意数据列格式");
  return map;
 }
 try {
  //这里会对list集合中的数据进行处理

  log.info("调用服务开始,参数:"+JSON.toJSONString(fromExcel));
  //String url = p4_zuul_url+"/***/ri***eat/bu***nges";
  String url = p4_zuul_url+"/***-surpass/ri***eat/bu***nges";
  String result = HttpClientUtil.doPost(url,JSON.toJSONString(fromExcel));
  log.info("调用服务结束,返回数据:"+result);
  if (result != null){
  map = JSONObject.parseObject(result, Map.class);
  log.info("批量修改状态导入:"+JSON.toJSONString(map));
  }
 }catch (Exception e){
  map.put("rspCode","99");
  map.put("rspMsg","导入操作表失败");
  log.info("bu***es exception",e);
  return map;
 }
 return map;
 }

 /**
 * 获取退单管理批量修改状态导入文件进度条进度
 * @return
 */
 @ResponseBody
 @RequestMapping("/t***sk")
 public Map<String,Object> t***sk(){
 Map<String,Object> map = new HashMap<>();
    //获取redis值
 String progress = HttpClientUtil.doGet(
  p4_zuul_url + "/" + p4_redis + "/redis***ler/get?key=progressSchedule");
 if (progress != null){
  map = JSONObject.parseObject(progress, Map.class);
  log.info("进度条进度:"+JSON.toJSONString(map));
  map.put("progressSchedule",progress);
 }else {
  HttpClientUtil.doGet(
   p4_zuul_url + "/" + p4_redis + "/redis***ler/del?key=progressSchedule");
 }
 return map;
 }

 /**
 * 清除redis进度条进度
 * @return
 */
 @ResponseBody
 @RequestMapping("/de***ess")
 public Map<String,Object> de***ess(){
 Map<String,Object> map = new HashMap<>();
 String progress = HttpClientUtil.doGet(
  p4_zuul_url + "/" + p4_redis + "/redis***ler/del?key=progressSchedule");
 if (progress != null){
  map = JSONObject.parseObject(progress, Map.class);
  log.info("返回数据:"+JSON.toJSONString(map));
 }
 return map;
 }

导入时调用第一个bulk***es方法,定时任务调用t***sk方法,导入完成或发生错误调用de***ess方法删除redis数据,避免占用资源。

服务层
@Async//开启异步
 @Transactional(rollbackFor = Exception.class)//事务回滚级别
 @Override
 public void bulkImportChanges(List<BulkImportChangesParam> list) {
 //初始化进度
 Double progressBarSchedule = 0.0;
 redisClient.set("progressSchedule", progressBarSchedule + "");//存redis
 try {
  for (int i = 1; i <= list.size(); i++) {
  RiskRetreatEntity entity = riskRetreatMapper.selectRetreatListBySysRefNum(list.get(i-1).getSysRefNum());
  if (entity == null){
   //查询结果为空直接进行下次循环不抛出
   continue;
  }
  //实体封装
        ···
        //更新
  riskRetreatMapper.updateRetreatByImport(entity);
  //计算修改进度并存放redis保存(1.0 / list.size())为一条数据进度
  progressBarSchedule = (1.0 / list.size()) * i*100;
  redisClient.set("progressSchedule", progressBarSchedule+"");
  if (i==list.size()){
   redisClient.set("progressSchedule", "100");
  }
  }
 }catch (Exception e){
  //当发生错误则清除缓存直接抛出回滚
  redisClient.set("progressSchedule","error");
  log.info("导入更新错误,回滚");
  log.info("bulkImportChanges exception:",e);
  throw e;
 }
 }

每更新一条数据存放进度,当发生错误则进行回滚。如果开启异步则需要在启动类添加注解@EnableAsync。

@EnableAsync
···//其他注解
public class Application {
  public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
  }
}

继续阅读SpringBoot如何实现一个实时更新的进度条的示例代码

Android开源VPN客户端之ICS OpenVPN

1. 简介

Android API level 14+提供了VPNService服务框架,在不root的情况下,开发者可以创建自己的VPN服务应用。
目前主要有三种,分别为OpenVPN for Android(ICS OpenVPN),OpenVPN Connect,OpenVPN Settings。

OpenVPN for Android和OpenVPN Connect使用官方VPNService API (Android 4.0+),不需要root权限,而OpenVPN Settings需要root权限。
OpenVPN for Android是一个开源的客户端,由Arne Schwabe开发,他定位于更高级的用户,提供更多的设置,它可以在app内导入配置文件。
OpenVPN Connect是一个不开源的客户端,它由OpenVPN Technologies , Inc. 开发,它定位于普通用户,它基于OpenVPN协议的C++实现。
OpenVPN Settings是最老的客户端,需要root权限,它并不使用VPNService API。

2. 编译OpenVPN for Android

2.1 开发环境搭建

Ubuntu16,Android Studio,NDK,SDK
首先在Ubuntu下安装Android studio,安装完毕后,使用sdk manager工具安装sdk。
关于NDK,可以使用sdk manager来下载,不过在编译源码的过程中出现了错误,经过尝试发现sdk manager下载的是14版本的ndk,而14版本的ndk编译OpenVPN for Android会出现错误,建议使用12版本的ndk。

2.2 配置环境变量

安装完Android studio,解压完ndk后,需要配置环境变量。

~/android-studio/bin Android studio安装目录
~/Android/android-ndk-r12b ndk解压目录

进入用户目录(cd /home/bleach),打开配置文件(vim .bashrc),添加如下内容:

export PATH=$PATH:~/android-studio/bin:~/Android/android-ndk-r12b

2.3 获取OpenVPN for Android源码

打开搜索引擎,输入ics openvpn,进行搜索,然后进入ics openvpn的github主页,选择master分支里的一个已经添加标签的版本,本文选择的是0.6.64版本的源码。

进入Ubuntu命令行,创建一个源码目录,然后使用git clone命令将源码下载到本地。

$ mkdir ics-openvpn-v0.6.64

$ cd ics-openvpn-v0.6.64

$ git clone https://github.com/schwabe/ics-openvpn.git

2.4 编译源码

step1:进入ics-openvpn-v0.6.64/ics-openvpn目录,修改.gitmodules文件。

$ cd ics-openvpn-v0.6.64/ics-openvpn

$ ls –a

$ vim .gitmodules

将.gitmodules文件中的url更改掉。

[submodule "main/openvpn"]
path = main/openvpn
url = https://github.com/schwabe/openvpn.git

[submodule "main/openssl"]
path = main/openssl
url = https://github.com/schwabe/platform_external_openssl.git

[submodule "main/breakpad"]
path = main/breakpad
url = https://github.com/schwabe/breakpad.git

step2:执行指令

$ git submodule sync

step3:然后执行指令

$ git submodule init

step4:然后执行指令

$ git submodule update

upate指令会消耗一定的时间。
step5:最后进入ics-openvpn-v0.6.64/ics-openvpn/main目录,执行./misc/build-native.sh,等待编译完成,可能需要修改build-native.sh的执行权限。

$ cd main

$ chmod +x ./mics/build-native.sh

$ ./misc/build-native.sh

3. 编译常见问题

3.1 ndk-build not found

未配置ndk环境变量,请参考2.2。

3.2 各种编译错误,比如unknown directive。

很有可能是ndk版本的问题,请使用12版本的ndk尝试编译。

3.3 cannot find .git directory in openvpn,aborting

不要在github上下载zip包的源码,请使用git clone指令将源码下载到本地。

4. 导入源码

将源码导入到Android studio中,目前OpenVPN for Android不支持导入到eclipse中,请使用Android studio开发环境。
打开Android studio,将源码导入,Android studio会自动更新gradle信息,更新完毕后,编译工程,就会生成对应apk包。

参考链接


Android开源VPN客户端之ICS OpenVPN

Java 实现TLS/SSL证书的自动安装校验

前言

最近实现Socks5 proxy与HTTP proxy中遇到了SSLSocket隧道的问题,当然,最终问题经过自动证书校验安装管理器实现了证书的管理,也解决了SSLSocket,但是目前的问题是浏览器对Socks5和HTTP proxy还有很多不足,目前实现的两个代理工具只能在程序中使用。当然,我们今天的主要话题如下:

Java 实现TLS/SSL证书的自动安装校验,主要经过ssl/tls握手,密钥交换,证书校验机制实现。我们这里模拟浏览器,实现自动校验和证书的检测。

主要实现如下功能:

1.自动检测,校验根证书,校验过期时间,校验签名的证书是否有效,校验证书和域名是否匹配

2.实现证书的自动存储,自动安装,加载

3.实现普通Socket自动升级为SSLSocket

一.实现配置类

首先,我们先添加2个配置类

package com.ssl.rx.http;

import java.security.KeyStore;

public class ConnectionConfiguration {

	/** 证书文件路径 */
private String truststorePath;
	/** 证书类型 */
private String truststoreType;
	/** 证书文件密码 */
private String truststorePassword;
	/** 是否验证证书链的签名有效性 */
private boolean verifyChainEnabled = true;
	/** 是否校验根证书,注意,自签名证书没有根证书 */
private boolean verifyRootCAEnabled = true;
	/** 是否允许通过自签名证书 */
private boolean selfSignedCertificateEnabled = false;
	/** 是否检查证书的有效期 */
private boolean expiredCertificatesCheckEnabled = true;
	/** 检查域名的匹配情况 */
private boolean notMatchingDomainCheckEnabled = true;

	private String server;
	private int port;

	public ConnectionConfiguration() {
		truststorePassword = "WlZSak5GcFVUbTlsVjJSNg==";
		truststorePath = "socket_tls_clientTrust.cert";
		truststoreType = "jks";
	}

	public int getPort() {
		return port;
	}

	public void setPort(int port) {
		this.port = port;
	}

	public String getServer() {
		return server;
	}

	public void setServer(String server) {
		this.server = server;
	}

	public boolean isExpiredCertificatesCheckEnabled() {
		return expiredCertificatesCheckEnabled;
	}

	public void setSelfSignedCertificateEnabled(boolean selfSignedCertificateEnabled) {
		this.selfSignedCertificateEnabled = selfSignedCertificateEnabled;
	}

	public void setExpiredCertificatesCheckEnabled(boolean expiredCertificatesCheckEnabled) {
		this.expiredCertificatesCheckEnabled = expiredCertificatesCheckEnabled;
	}

	public boolean isSelfSignedCertificateEnabled() {
		return selfSignedCertificateEnabled;
	}

	public boolean isNotMatchingDomainCheckEnabled() {
		return notMatchingDomainCheckEnabled;
	}

	public boolean isVerifyRootCAEnabled() {
		return verifyRootCAEnabled;
	}

	public void setVerifyRootCAEnabled(boolean verifyRootCAEnabled) {
		this.verifyRootCAEnabled = verifyRootCAEnabled;
	}

	public void setVerifyChainEnabled(boolean verifyChainEnabled) {
		this.verifyChainEnabled = verifyChainEnabled;
	}

	public boolean isVerifyChainEnabled() {

		return verifyChainEnabled;
	}

	public String getTruststoreType() {
		return truststoreType;
	}

	public void setTruststoreType(String truststoreType) {
		this.truststoreType = truststoreType;
	}

	public String getTruststorePassword() {
		return truststorePassword;
	}

	public void setTruststorePassword(String truststorePassword) {
		this.truststorePassword = truststorePassword;
	}

	public String getTruststorePath() {
		return truststorePath;
	}

	public void setTruststorePath(String truststorePath) {
		this.truststorePath = truststorePath;
	}

	public void setNotMatchingDomainCheckEnabled(boolean notMatchingDomainCheckEnabled) {
		this.notMatchingDomainCheckEnabled = notMatchingDomainCheckEnabled;
	}

}


然后增加一个用于存储keystore的javaBean

package com.ssl.rx.http;

public class KeyStoreOptions {
	private final String type;
	private final String path;
	private final String password;

	public KeyStoreOptions(String type, String path, String password) {
		super();
		this.type = type;
		this.path = path;
		this.password = password;
	}

	public String getType() {
		return type;
	}

	public String getPath() {
		return path;
	}

	public String getPassword() {
		return password;
	}

	@Override
public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((password == null) ? 0 : password.hashCode());
		result = prime * result + ((path == null) ? 0 : path.hashCode());
		result = prime * result + ((type == null) ? 0 : type.hashCode());
		return result;
	}

	@Override
public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		KeyStoreOptions other = (KeyStoreOptions) obj;
		if (password == null) {
			if (other.password != null)
				return false;
		} else if (!password.equals(other.password))
			return false;
		if (path == null) {
			if (other.path != null)
				return false;
		} else if (!path.equals(other.path))
			return false;
		if (type == null) {
			if (other.type != null)
				return false;
		} else if (!type.equals(other.type))
			return false;
		return true;
	}
}

最后,我们来实现核心部分,证书管理器

二.实现核心代码

package com.ssl.rx.http;


public class SSLX509CertificateManager {

	private static final Logger logger = Logger.getLogger("SSLX509CertificateManager");
	private static final char[] HEXDIGITS = "0123456789abcdef".toCharArray();
	private static Pattern cnPattern = Pattern.compile("(?i)(cn=)([^,]*)");
	private static Map<KeyStoreOptions, KeyStore> stores = new HashMap<KeyStoreOptions, KeyStore>();

	private static String toHexString(byte[] bytes) {
		StringBuilder sb = new StringBuilder(bytes.length * 3);
		for (int b : bytes) {
			b &= 0xff;
			sb.append(HEXDIGITS[b >> 4]);
			sb.append(HEXDIGITS[b & 15]);
			sb.append(' ');
		}
		return sb.toString();
	}

	

	/**
	 * 开始握手等一系列密钥协商
	 * 
	 * @param socket
	 * @return
	 */
public static boolean startHandshake(SSLSocket socket) {
		try {
			logger.log(Level.INFO, "-开始握手,认证服务器证书-");
			socket.startHandshake();
			System.out.println();
			logger.log(Level.INFO, "-握手结束,结束认证服务器证书-");
		} catch (SSLException e) {
			e.printStackTrace();
			return false;
		} catch (IOException e) {
			e.printStackTrace();
			return false;
		}
		return true;
	}

	public static SSLSocket createTrustCASocket(String host, int port, ConnectionConfiguration config)
throws Exception {
		if (config == null) {
			config = new ConnectionConfiguration();
		}
		KeyStore ks = getKeyStore(config);
		SSLContext context = SSLContext.getInstance("TLS");
		TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
		tmf.init(ks);
		X509TrustManager defaultTrustManager = (X509TrustManager) tmf.getTrustManagers()[0];
		CAX509TrustManager tm = new CAX509TrustManager(defaultTrustManager, ks, config);

		context.init(null, new TrustManager[] { tm }, new SecureRandom());
		SSLSocketFactory factory = context.getSocketFactory();

		logger.log(Level.INFO, "开始连接: " + host + ":" + port + "...");
		SSLSocket socket = (SSLSocket) factory.createSocket(host, port);
		socket.setSoTimeout(10000);

		config.setServer(host);
		config.setPort(port);
		// config.setTrustKeyStore(ks);
		X509Certificate certificate = (X509Certificate) ks.getCertificate(host + ":" + port);

		if (certificate != null && isValid(certificate)) {
			logger.log(Level.INFO, "-证书文件存在并且有效,无需进行握手-");
			return socket;
		}
		if (!startHandshake(socket)) {
			logger.log(Level.SEVERE, "-握手失败-");
			return null;
		}
		X509Certificate[] chain = tm.chain;
		if (chain == null || chain.length == 0) {
			logger.log(Level.SEVERE, "-证书链为空,认证失败-");
			return null;
		}

		if (config.isVerifyRootCAEnabled()) {
			boolean isValidRootCA = checkX509CertificateRootCA(ks, chain, config.isSelfSignedCertificateEnabled());
			if (!isValidRootCA) {
				return null;
			}
		}

		return socket;
	}

	/**
	 * 获取keystore,防治多次加载
	 * 
	 * @param config
	 * @return
	 * @throws KeyStoreException
	 * @throws IOException
	 * @throws NoSuchAlgorithmException
	 * @throws CertificateException
	 * @throws FileNotFoundException
	 */
private static KeyStore getKeyStore(ConnectionConfiguration config) throws KeyStoreException, IOException,
			NoSuchAlgorithmException, CertificateException, FileNotFoundException {
		KeyStore ks;
		synchronized (stores) {
			KeyStoreOptions options = new KeyStoreOptions(config.getTruststoreType(), config.getTruststorePath(),
					config.getTruststorePassword());
			if (stores.containsKey(options)) {
				logger.log(Level.INFO, "从缓存中获取trustKeystore");
				ks = stores.get(options);

			} else {
				File file = new File(config.getTruststorePath());
				char[] password = config.getTruststorePassword().toCharArray();

				logger.log(Level.INFO, "加载" + file + "证书文件");
				ks = KeyStore.getInstance(KeyStore.getDefaultType());
				if (!file.exists()) {
					logger.log(Level.INFO, "证书文件不存在,选择自动创建");
					ks.load(null, password);
				} else {
					logger.log(Level.INFO, "证书文件存在,开始加载");
					InputStream in = new FileInputStream(file);
					ks.load(in, password);
					in.close();
				}
				stores.put(options, ks);
			}

		}
		return ks;
	}

	public static SSLSocket createTrustCASocket(String host, int port) throws Exception {

		return createTrustCASocket(host, port, null);
	}

	public static SSLSocket createTrustCASocket(Socket s, ConnectionConfiguration config) throws Exception {
		if (config == null) {
			config = new ConnectionConfiguration();
		}

		KeyStore ks = getKeyStore(config);
		SSLContext context = SSLContext.getInstance("TLS");
		TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
		tmf.init(ks);
		X509TrustManager defaultTrustManager = (X509TrustManager) tmf.getTrustManagers()[0];
		CAX509TrustManager tm = new CAX509TrustManager(defaultTrustManager, ks, config);

		context.init(null, new TrustManager[] { tm }, new SecureRandom());
		SSLSocketFactory factory = context.getSocketFactory();

		String host = s.getInetAddress().getHostName();
		int port = s.getPort();
		logger.log(Level.INFO, "开始连接: " + host + ":" + port + "...");

		SSLSocket socket = (SSLSocket) factory.createSocket(s, host, port, true);
		socket.setSoTimeout(10000);

		config.setServer(s.getInetAddress().getHostName());
		config.setPort(s.getPort());

		X509Certificate certificate = (X509Certificate) ks.getCertificate(host + ":" + s.getPort());
		if (certificate != null && isValid(certificate)) {
			logger.log(Level.INFO, "-证书文件存在并且有效,无需进行握手-");
			return socket;
		}
		if (!startHandshake(socket)) {
			return null;
		}
		X509Certificate[] chain = tm.chain;
		if (chain == null || chain.length == 0) {
			logger.log(Level.SEVERE, "-证书链为空,认证失败-");
			return null;
		}
		if (config.isVerifyRootCAEnabled()) {
			boolean isValidRootCA = checkX509CertificateRootCA(ks, chain, config.isSelfSignedCertificateEnabled());
			if (!isValidRootCA) {
				logger.log(Level.SEVERE, "根证书校验无效");
				return null;
			}
		}
		return socket;

	}

	public static SSLSocket createTrustCASocket(Socket s) throws Exception {

		return createTrustCASocket(s, null);
	}

	public static class CAX509TrustManager implements X509TrustManager {

		private final X509TrustManager tm;
		private X509Certificate[] chain;
		private KeyStore keyStore;
		private ConnectionConfiguration config;
		public MessageDigest sha1 = null;
		public MessageDigest md5 = null;

		public CAX509TrustManager(X509TrustManager tm, KeyStore ks, ConnectionConfiguration config)
throws NoSuchAlgorithmException {
			this.tm = tm;
			this.keyStore = ks;
			sha1 = MessageDigest.getInstance("SHA1");
			md5 = MessageDigest.getInstance("MD5");
			this.config = config;
		}

		public X509Certificate[] getAcceptedIssuers() {
			return tm.getAcceptedIssuers(); // 生成证书数组,用于存储新证书
		}

		public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
			tm.checkClientTrusted(chain, authType); // 检查客户端
		}

		public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
			if (this.chain == null) {
				this.chain = getAcceptedIssuers();
			}
			if (chain != null && chain.length > 0) {
				if (!checkX509CertificateValid(chain, config)) {
					logger.log(Level.SEVERE, "证书校验未通过");
					return;
				}
				for (int i = 0; i < chain.length; i++) {
					X509Certificate certificate = chain[i];
					if (i == 0) {
						saveCAToKeyStore(certificate, config.getServer() + ":" + config.getPort());
					} else {
						saveCAToKeyStore(certificate, null);
					}
				}
			}
		}

		public void saveCAToKeyStore(X509Certificate certificate, String aliasKey) throws CertificateEncodingException {
			try {
				X509Certificate cert = certificate;
				System.out.println(" Subject " + cert.getSubjectDN());
				System.out.println("  Issuer  " + cert.getIssuerDN());
				sha1.update(cert.getEncoded());
				System.out.println("  sha1    " + toHexString(sha1.digest()));
				md5.update(cert.getEncoded());
				System.out.println("  md5     " + toHexString(md5.digest()));

				String alias = keyStore.getCertificateAlias(cert);
				if (alias == null || alias != null && !isValid(certificate)) {
					if (aliasKey == null || aliasKey.length() == 0) {
						alias = cert.getSubjectDN().getName();
					} else {
						alias = aliasKey;
						logger.log(Level.INFO, "设定指定证书别名:" + alias);
					}
					keyStore.setCertificateEntry(alias, certificate);
					OutputStream out = new FileOutputStream(config.getTruststorePath());
					keyStore.store(out, config.getTruststorePassword().toCharArray());
					out.close();
					chain = Arrays.copyOf(chain, chain.length + 1);
					chain[chain.length - 1] = certificate;
					logger.fine(certificate.toString());
				}

			} catch (KeyStoreException e) {
				e.printStackTrace();
			} catch (FileNotFoundException e) {
				e.printStackTrace();
			} catch (NoSuchAlgorithmException e) {
				e.printStackTrace();
			} catch (CertificateException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

	}

	public static boolean isValid(X509Certificate cert) {
		if (cert == null) {
			return false;
		}
		try {
			cert.checkValidity();
		} catch (CertificateExpiredException e) {
			e.printStackTrace();
			return false;
		} catch (CertificateNotYetValidException e) {
			e.printStackTrace();
			return false;
		}
		return true;
	}

	/**
	 * 校验证书的有效性
	 * 
	 * @param chain
	 * @param config
	 * @return
	 */
private static boolean checkX509CertificateValid(X509Certificate[] chain, ConnectionConfiguration config) {
		boolean result = true;
		if (config.isExpiredCertificatesCheckEnabled()) {
			result = result && checkX509CertificateExpired(chain);
		}

		if (config.isVerifyChainEnabled()) {
			result = result && checkX509CertificateChain(chain);
		}

		if (config.isNotMatchingDomainCheckEnabled()) {
			result = result && checkIsMatchDomain(chain, config.getServer());
		}

		return result;

	}

	/**
	 * 检查是否匹配域名
	 * 
	 * @param x509Certificates
	 * @param server
	 * @return
	 */
public static boolean checkIsMatchDomain(X509Certificate[] x509Certificates, String server) {
		server = server.toLowerCase();
		List<String> peerIdentities = getPeerIdentity(x509Certificates[0]);
		if (peerIdentities.size() == 1 && peerIdentities.get(0).startsWith("*.")) {
			String peerIdentity = peerIdentities.get(0).replace("*.", "");
			if (!server.endsWith(peerIdentity)) {
				return false;
			}
		} else {
			for (int i = 0; i < peerIdentities.size(); i++) {
				String peerIdentity = peerIdentities.get(i).replace("*.", "");
				if (server.endsWith(peerIdentity)) {
					return true;
				}
			}
		}
		return false;
	}

	/**
	 * 校验根证书
	 * 
	 * @param trustStore
	 * @param x509Certificates
	 * @param isSelfSignedCertificate
	 *            是否自签名证书
	 * @return
	 */
public static boolean checkX509CertificateRootCA(KeyStore trustStore, X509Certificate[] x509Certificates,
			boolean isSelfSignedCertificate) {
		List<String> peerIdentities = getPeerIdentity(x509Certificates[0]);
		boolean trusted = false;
		try {
			int size = x509Certificates.length;
			trusted = trustStore.getCertificateAlias(x509Certificates[size - 1]) != null;
			if (!trusted && size == 1 && isSelfSignedCertificate) {
				logger.log(Level.WARNING, "-强制认可自签名证书-");
				trusted = true;
			}
		} catch (KeyStoreException e) {
			e.printStackTrace();
		}
		if (!trusted) {
			logger.log(Level.SEVERE, "-根证书签名的网站:" + peerIdentities + "不能被信任");
		}

		return trusted;
	}

	/**
	 * 检查证书是否过期
	 * 
	 * @param x509Certificates
	 * @return
	 */
public static boolean checkX509CertificateExpired(X509Certificate[] x509Certificates) {
		Date date = new Date();
		for (int i = 0; i < x509Certificates.length; i++) {
			try {
				x509Certificates[i].checkValidity(date);
			} catch (GeneralSecurityException generalsecurityexception) {
				logger.log(Level.SEVERE, "-证书已经过期-");
				return false;
			}
		}
		return true;
	}

	/**
	 * 校验证书链的完整性
	 * 
	 * @param x509Certificates
	 * @return
	 */
public static boolean checkX509CertificateChain(X509Certificate[] x509Certificates) {
		Principal principalLast = null;
		List<String> peerIdentities = getPeerIdentity(x509Certificates[0]);

		for (int i = x509Certificates.length - 1; i >= 0; i--) {
			X509Certificate x509certificate = x509Certificates[i];
			Principal principalIssuer = x509certificate.getIssuerDN();
			Principal principalSubject = x509certificate.getSubjectDN();
			if (principalLast != null) {
				if (principalIssuer.equals(principalLast)) {
					try {
						PublicKey publickey = x509Certificates[i + 1].getPublicKey();
						x509Certificates[i].verify(publickey);
					} catch (GeneralSecurityException generalsecurityexception) {

						logger.log(Level.SEVERE, "-无效的证书签名-" + peerIdentities);
						return false;
					}
				} else {
					logger.log(Level.SEVERE, "-无效的证书签名-" + peerIdentities);
					return false;
				}
			}
			principalLast = principalSubject;
		}

		return true;
	}

	/**
	 * 返回所有可用的签名方式 键值对 如CN=VeriSignMPKI-2-6
	 * 
	 * @param certificate
	 * @return
	 */
private static List<String> getSubjectAlternativeNames(X509Certificate certificate) {
		List<String> identities = new ArrayList<String>();
		try {
			Collection<List<?>> altNames = certificate.getSubjectAlternativeNames();
			if (altNames == null) {
				return Collections.emptyList();
			}

			Iterator<List<?>> iterator = altNames.iterator();
			do {
				if (!iterator.hasNext())
					break;
				List<?> altName = iterator.next();
				int size = altName.size();
				if (size >= 2) {
					identities.add((String) altName.get(1));
				}

			} while (true);
		} catch (CertificateParsingException e) {
			e.printStackTrace();
		}
		return identities;
	}

	/**
	 * 返回所有可用的签名方式的值
	 * 
	 * @param certificate
	 * @return
	 */
public static List<String> getPeerIdentity(X509Certificate x509Certificate) {
		List<String> names = getSubjectAlternativeNames(x509Certificate);
		if (names.isEmpty()) {
			String name = x509Certificate.getSubjectDN().getName();
			Matcher matcher = cnPattern.matcher(name);
			if (matcher.find()) {
				name = matcher.group(2);
			}
			names = new ArrayList<String>();
			names.add(name);
		}
		return names;
	}
}

三.测试代码

public class TestX509CertManager {

 public static void main(String[] args) {
		try {
			SSLSocket baiduSocket = SSLX509CertificateManager.createTrustCASocket("www.baidu.com", 443);
			SSLSocket taobaoSocket = SSLX509CertificateManager.createTrustCASocket("www.taobao.com", 443);
			SSLSocket imququSocket = SSLX509CertificateManager.createTrustCASocket("imququ.com", 443);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

四.附加测试代码

我们这里附加一个工具类,专门来实现Server-Side与Client-Side的SSLSocket 连接,也可以用于测试我们的上述代码,只不过需要稍加改造。

package com.tianwt.rx.http;

public class SSLTrustManager implements javax.net.ssl.TrustManager,
            javax.net.ssl.X509TrustManager ,HostnameVerifier {
        public java.security.cert.X509Certificate[] getAcceptedIssuers() {
            return new X509Certificate[]{};
        }
  
        public boolean isServerTrusted(
                java.security.cert.X509Certificate[] certs) {
            return true;
        }
  
        public boolean isClientTrusted(
                java.security.cert.X509Certificate[] certs) {
            return true;
        }
  
        public void checkServerTrusted(
                java.security.cert.X509Certificate[] certs, String authType)
                throws java.security.cert.CertificateException {
            return;
        }
  
        public void checkClientTrusted(
                java.security.cert.X509Certificate[] certs, String authType)
                throws java.security.cert.CertificateException {
            return;
        }
         
            @Override
        public boolean verify(String urlHostName, SSLSession session) { //允许所有主机
            return true;
        }
        
   /**
    * 客户端使用
    */
   public static HttpURLConnection connectTrustAllServer(String strUrl) throws Exception {
         
        return connectTrustAllServer(strUrl,null);
    }
   /**
    * 客户端使用
    * 
    * @param strUrl 要访问的地址
    * @param proxy 需要经过的代理
    * @return
    * @throws Exception
    */
   public static HttpURLConnection connectTrustAllServer(String strUrl,Proxy proxy) throws Exception {
         
         javax.net.ssl.TrustManager[] trustCertsmanager = new javax.net.ssl.TrustManager[1];
         javax.net.ssl.TrustManager tm = new SSLTrustManager();
         trustCertsmanager[0] = tm;
         javax.net.ssl.SSLContext sc = javax.net.ssl.SSLContext
                 .getInstance("TLS");
         sc.init(null, trustCertsmanager, null);
         javax.net.ssl.HttpsURLConnection.setDefaultSSLSocketFactory(sc
                 .getSocketFactory());
          
         HttpsURLConnection.setDefaultHostnameVerifier((HostnameVerifier) tm);
          
        URL url = new URL(strUrl);
        HttpURLConnection urlConn = null;
        if(proxy==null)
        {
        	 urlConn = (HttpURLConnection) url.openConnection();
        }else{
        	 urlConn = (HttpURLConnection) url.openConnection(proxy);
        }
        urlConn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36");
        return urlConn;
    }

   /**
    * 用于双向认证,客户端使用
    * 
    * @param strUrl
    * @param proxy
    * @return
    * @throws KeyStoreException
    * @throws NoSuchAlgorithmException
    * @throws CertificateException
    * @throws FileNotFoundException
    * @throws IOException
    * @throws UnrecoverableKeyException
    * @throws KeyManagementException
    */
   public static HttpURLConnection connectProxyTrustCA(String strUrl,Proxy proxy) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, FileNotFoundException, IOException, UnrecoverableKeyException, KeyManagementException
   {
	   HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
			
			@Override
public boolean verify(String s, SSLSession sslsession) {
				
				return true;
			}
		});
	   String clientKeyStoreFile = "D:/JDK8Home/tianwt/sslClientKeys";  
       String clientKeyStorePwd = "123456";  
       String catServerKeyPwd = "123456";  
       
       String serverTrustKeyStoreFile = "D:/JDK8Home/tianwt/sslClientTrust";  
       String serverTrustKeyStorePwd = "123456";  
       KeyStore serverKeyStore = KeyStore.getInstance("JKS");  
       serverKeyStore.load(new FileInputStream(clientKeyStoreFile), clientKeyStorePwd.toCharArray());  
 
       KeyStore serverTrustKeyStore = KeyStore.getInstance("JKS");  
       serverTrustKeyStore.load(new FileInputStream(serverTrustKeyStoreFile), serverTrustKeyStorePwd.toCharArray());  
 
       KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());  
       kmf.init(serverKeyStore, catServerKeyPwd.toCharArray());  
 
       TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());  
       tmf.init(serverTrustKeyStore);  
 
       SSLContext sslContext = SSLContext.getInstance("TLS");  
       sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom());  
        
       HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
		
	   URL url = new URL(strUrl);
	   HttpURLConnection httpURLConnection = null;
	   if(proxy==null)
	   {
		   httpURLConnection = (HttpURLConnection) url.openConnection();
	   }else{
		   httpURLConnection = (HttpURLConnection) url.openConnection(proxy);
	   }
	   httpURLConnection.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36");
	   return httpURLConnection;

   }
   /**
    * 用于单向认证,客户端使用
    * 
    * server侧只需要自己的keystore文件,不需要truststore文件
	* client侧不需要自己的keystore文件,只需要truststore文件(其中包含server的公钥)。
    * 此外server侧需要在创建SSLServerSocket之后设定不需要客户端证书:setNeedClientAuth(false)
    * @param strUrl
    * @param proxy
    * @return
    * @throws KeyStoreException
    * @throws NoSuchAlgorithmException
    * @throws CertificateException
    * @throws FileNotFoundException
    * @throws IOException
    * @throws UnrecoverableKeyException
    * @throws KeyManagementException
    */
   public static HttpURLConnection connectProxyTrustCA2(String strUrl,Proxy proxy) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, FileNotFoundException, IOException, UnrecoverableKeyException, KeyManagementException
   {
	   HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
			
			@Override
public boolean verify(String s, SSLSession sslsession) {
				
				return true;
			}
		});
       
       String serverTrustKeyStoreFile = "D:/JDK8Home/tianwt/sslClientTrust";  
       String serverTrustKeyStorePwd = "123456";  
 
       KeyStore serverTrustKeyStore = KeyStore.getInstance("JKS");  
       serverTrustKeyStore.load(new FileInputStream(serverTrustKeyStoreFile), serverTrustKeyStorePwd.toCharArray());  
 
       TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());  
       tmf.init(serverTrustKeyStore);  
 
       SSLContext sslContext = SSLContext.getInstance("TLS");  
       sslContext.init(null, tmf.getTrustManagers(), null);  
        
       HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
		
	   URL url = new URL(strUrl);
	   HttpURLConnection httpURLConnection = null;
	   if(proxy==null)
	   {
		   httpURLConnection = (HttpURLConnection) url.openConnection();
	   }else{
		   httpURLConnection = (HttpURLConnection) url.openConnection(proxy);
	   }
	   httpURLConnection.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36");
	   return httpURLConnection;

   }
   /**
    * 用于双向认证
    * @param socketClient 是否产生socket
    * @return
    * @throws KeyStoreException
    * @throws NoSuchAlgorithmException
    * @throws CertificateException
    * @throws FileNotFoundException
    * @throws IOException
    * @throws UnrecoverableKeyException
    * @throws KeyManagementException
    */
   public SSLSocket createTlsConnect(Socket socketClient) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, FileNotFoundException, IOException, UnrecoverableKeyException, KeyManagementException
	{
		
		    String protocol = "TLS";  
	        String serverKey = "D:/JDK8Home/tianwt/sslServerKeys";  
	        String serverTrust = "D:/JDK8Home/tianwt/sslServerTrust";  
	        String serverKeyPwd = "123456";  //私钥密码
	        String serverTrustPwd = "123456";  //信任证书密码
	        String serverKeyStorePwd = "123456";  // keystore存储密码
	          
	        KeyStore keyStore = KeyStore.getInstance("JKS");    
	        keyStore.load(new FileInputStream(serverKey),serverKeyPwd.toCharArray());  
	        
	        KeyStore tks = KeyStore.getInstance("JKS");
	        tks.load(new FileInputStream(serverTrust), serverTrustPwd.toCharArray());
	        
	        KeyManagerFactory km = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());   
	        km.init(keyStore, serverKeyStorePwd.toCharArray());
	        
	        TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());  
	        tmf.init(tks);
	        
	        SSLContext sslContext = SSLContext.getInstance(protocol); 
	        sslContext.init(km.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom());  //第一项是用来做服务器验证的
	        
	        SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
	        SSLSocket clientSSLSocket = (SSLSocket) sslSocketFactory.createSocket(socketClient,socketClient.getInetAddress().getHostAddress(),socketClient.getPort(), true);
	        clientSSLSocket.setNeedClientAuth(false);
	        clientSSLSocket.setUseClientMode(false);
	        
	        return clientSSLSocket;
	}
   /**
    * 用于单向认证
    * server侧只需要自己的keystore文件,不需要truststore文件
	* client侧不需要自己的keystore文件,只需要truststore文件(其中包含server的公钥)。
    * 此外server侧需要在创建SSLServerSocket之后设定不需要客户端证书:setNeedClientAuth(false)
    * @param socketClient
    * @return
    * @throws KeyStoreException
    * @throws NoSuchAlgorithmException
    * @throws CertificateException
    * @throws FileNotFoundException
    * @throws IOException
    * @throws UnrecoverableKeyException
    * @throws KeyManagementException
    */
   public static SSLSocket createTlsConnect2(Socket socketClient) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, FileNotFoundException, IOException, UnrecoverableKeyException, KeyManagementException
  	{
  		
  		    String protocol = "TLS";  
  	        String serverKey = "D:/JDK8Home/tianwt/sslServerKeys";  
  	        String serverKeyPwd = "123456";  //私钥密码
  	        String serverKeyStorePwd = "123456";  // keystore存储密码
  	          
  	        KeyStore keyStore = KeyStore.getInstance("JKS");    
  	        keyStore.load(new FileInputStream(serverKey),serverKeyPwd.toCharArray());  
  	        
  	        KeyManagerFactory km = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());   
  	        km.init(keyStore, serverKeyStorePwd.toCharArray());
  	        
  	        SSLContext sslContext = SSLContext.getInstance(protocol); 
  	        sslContext.init(km.getKeyManagers(), null, new SecureRandom());  //第一项是用来做服务器验证的
  	        
  	        SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
  	        SSLSocket clientSSLSocket = (SSLSocket) sslSocketFactory.createSocket(socketClient,socketClient.getInetAddress().getHostAddress(),socketClient.getPort(), true);
  	        clientSSLSocket.setNeedClientAuth(false);
  	        clientSSLSocket.setUseClientMode(false);
  	        
  	        return clientSSLSocket;
  	}
   
   /**
    * 将普通的socket转为sslsocket,客户端和服务端均可使用
    * 
    * 服务端使用的时候是把普通的socket转为sslsocket,并且作为服务器套接字(注意:指的不是ServerSocket,当然ServerSocket的本质也是普通socket)
    * 
    * @param remoteHost
    * @param isClient
    * @return
    */
public static SSLSocket getTlsTrustAllSocket(Socket remoteHost,boolean isClient)
{
		SSLSocket remoteSSLSocket = null;
		SSLContext context = SSLTrustManager.getTrustAllSSLContext(isClient);
		try {
			remoteSSLSocket =  (SSLSocket) context.getSocketFactory().createSocket(remoteHost, remoteHost.getInetAddress().getHostName(),remoteHost.getPort(), true);
			remoteSSLSocket.setTcpNoDelay(true);
			remoteSSLSocket.setSoTimeout(5000);
			remoteSSLSocket.setNeedClientAuth(false); //这里设置为true时会强制握手
			remoteSSLSocket.setUseClientMode(isClient); //注意服务器和客户的角色选择 
			 
		} catch (IOException e) {
			e.printStackTrace();
		}
		return remoteSSLSocket;
	}
	/**
	 * 用于客户端,通过所有证书验证
	 * @param isClient 是否生成客户端SSLContext,否则生成服务端SSLContext
	 * @return
	 */
   public static SSLContext getTrustAllSSLContext(boolean isClient)
   {
	   String protocol = "TLS"; 
	   javax.net.ssl.SSLContext sc = null;
	   try {
		javax.net.ssl.TrustManager[] trustAllCerts = new javax.net.ssl.TrustManager[1];
		   javax.net.ssl.TrustManager tm = new SSLTrustManager();
		   trustAllCerts[0] = tm;
		    sc = javax.net.ssl.SSLContext
		           .getInstance(protocol);
		   
		    if(isClient)
		    {
		    	sc.init(null, trustAllCerts, null); //作为客户端使用
		    }
		    else
		    {
	  	        String serverKeyPath = "D:/JDK8Home/tianwt/sslServerKeys";  
	  	        String serverKeyPwd = "123456";  //私钥密码
	  	        String serverKeyStorePwd = "123456";  // keystore存储密码
	  	        
	  	        KeyStore keyStore = KeyStore.getInstance("JKS");    
	  	        keyStore.load(new FileInputStream(serverKeyPath),serverKeyPwd.toCharArray());  
	  	        
		    	KeyManagerFactory km = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());   
		  	    km.init(keyStore, serverKeyStorePwd.toCharArray());
		  	    KeyManager[] keyManagers = km.getKeyManagers();
		  	    keyManagers = Arrays.copyOf(keyManagers, keyManagers.length+1);
	  	        sc.init(keyManagers, null, new SecureRandom());
		    }
	} catch (KeyManagementException e) {
		e.printStackTrace();
	} catch (NoSuchAlgorithmException e) {
		e.printStackTrace();
	} catch (UnrecoverableKeyException e) {
		e.printStackTrace();
	} catch (KeyStoreException e) {
		e.printStackTrace();
	} catch (CertificateException e) {
		e.printStackTrace();
	} catch (FileNotFoundException e) {
		e.printStackTrace();
	} catch (IOException e) {
		e.printStackTrace();
	}
	   return sc;
   }
 }

参考链接


Java 实现TLS/SSL证书的自动安装校验

oKHttp 3.0忽略/检查https证书

最近公司项目需要,网络协议支持HTTPS,之前接触不多,所以这次想总结一下https在Android开发中的相关内容

一、HTTPS 证书

对于HTTPS和证书的概念,大家可以自行搜索百度。

证书分两种:

1、花钱向认证机构购买的证书。服务器如果使用了此类证书的话,那对于移动端来说,直接可以忽略此证书,直接用https访问。与之不同的是ios内置了很多信任的证书,所以他们不需要做任何操作

2、另一种是自己制作的证书,使用此类证书的话是不受信任的,也不需要花钱,所以需要我们在代码中将此类证书设置为信任证书

二、如何忽略证书

注意,下面的操作在线上环境强烈不建议采纳,只能是在测试环境中解决测试问题的时候才能使用。

1、服务器如果加上了证书的话,那么你们的网络请求的url将从http:xx改成https:xx,如果你直接也将http改成https的话而什么也不做的话,客户端将直接报错,如图:

继续阅读oKHttp 3.0忽略/检查https证书

Android:调整线程调用栈大小

在常规的`Android`开发过程中,随着业务逻辑越来越复杂,调用栈可能会越来越深,难免会遇到调用栈越界的情况,这种情况下,就需要调整线程栈的大小。

当然,主要还是增大线程栈大小,尤其是存在`jni`调用的情况下,`C++`层的栈开销有时候是非常恐怖的,比如说递归调用。

这就需要分三种情况,主线程,自定义线程池,`AsyncTask`。

主线程的线程栈是没有办法进行修改的,这个没办法处理。

针对线程池的情况,需要在创建线程的时候,调用构造函数

public Thread(@RecentlyNullable ThreadGroup group, @RecentlyNullable Runnable target, @RecentlyNonNull String name, long stackSize)

通过设置`stackSize`参数来解决问题。

参考代码如下:

import android.support.annotation.NonNull;
import android.util.Log;

import java.util.concurrent.ThreadFactory;

/**
 * A ThreadFactory implementation which create new threads for the thread pool.
 */
public class SimpleThreadFactory implements ThreadFactory {
    private static final String TAG = "SimpleThreadFactory";
    private final static ThreadGroup group = new ThreadGroup("SimpleThreadFactoryGroup");
    // 工作线程堆栈大小调整为2MB
    private final static int workerStackSize = 2 * 1024 * 1024;

    @Override
    public Thread newThread(@NonNull final Runnable runnable) {
        final Thread thread = new Thread(group, runnable, "PoolWorkerThread", workerStackSize);
        // A exception handler is created to log the exception from threads
        thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
            @Override
            public void uncaughtException(@NonNull Thread thread, @NonNull Throwable ex) {
                Log.e(TAG, thread.getName() + " encountered an error: " + ex.getMessage());
            }
        });

        return thread;
    }
}
import android.support.annotation.AnyThread;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * A Singleton thread pool
 */
public class ThreadPool {
    private static final String TAG = "ThreadPool";
    private static final int KEEP_ALIVE_TIME = 1;
    private static volatile ThreadPool sInstance = null;
    private static int NUMBER_OF_CORES = Runtime.getRuntime().availableProcessors();
    private final ExecutorService mExecutor;
    private final BlockingQueue<Runnable> mTaskQueue;

    // Made constructor private to avoid the class being initiated from outside
    private ThreadPool() {
        // initialize a queue for the thread pool. New tasks will be added to this queue
        mTaskQueue = new LinkedBlockingQueue<>();

        Log.d(TAG, "Available cores: " + NUMBER_OF_CORES);

        mExecutor = new ThreadPoolExecutor(NUMBER_OF_CORES, NUMBER_OF_CORES * 2, KEEP_ALIVE_TIME, TimeUnit.SECONDS, mTaskQueue, new SimpleThreadFactory());
    }

    @NonNull
    @AnyThread
    public static ThreadPool getInstance() {
        if (null == sInstance) {
            synchronized (ThreadPool.class) {
                if (null == sInstance) {
                    sInstance = new ThreadPool();
                }
            }
        }
        return sInstance;
    }

    private boolean isThreadPoolAlive() {
        return (null != mExecutor) && !mExecutor.isTerminated() && !mExecutor.isShutdown();
    }

    @Nullable
    @AnyThread
    public <T> Future<T> submitCallable(@NonNull final Callable<T> c) {
        synchronized (this) {
            if (isThreadPoolAlive()) {
                return mExecutor.submit(c);
            }
        }
        return null;
    }

    @Nullable
    @AnyThread
    public Future<?> submitRunnable(@NonNull final Runnable r) {
        synchronized (this) {
            if (isThreadPoolAlive()) {
                return mExecutor.submit(r);
            }
        }
        return null;
    }

    /* Remove all tasks in the queue and stop all running threads
     */
    @AnyThread
    public void shutdownNow() {
        synchronized (this) {
            mTaskQueue.clear();
            if ((!mExecutor.isShutdown()) && (!mExecutor.isTerminated())) {
                mExecutor.shutdownNow();
            }
        }
    }
}

针对`AsyncTask`的情况,一般是通过调用

public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec, Params... params)

指定线程池来运行,在特定的线程池中调整线程栈的大小。

参考代码如下:

import android.os.AsyncTask;
import android.support.annotation.AnyThread;
import android.support.annotation.NonNull;
import android.util.Log;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public abstract class AsyncTaskEx<Params, Progress, Result> extends AsyncTask<Params, Progress, Result> {

    private static final String TAG = "AsyncTaskEx";
    private static final int KEEP_ALIVE_TIME = 1;
    private static volatile ThreadPool sInstance = null;
    private static int NUMBER_OF_CORES = Runtime.getRuntime().availableProcessors();
    private final ExecutorService mExecutor;
    private final BlockingQueue<Runnable> mTaskQueue;

    public AsyncTaskEx() {
        // initialize a queue for the thread pool. New tasks will be added to this queue
        mTaskQueue = new LinkedBlockingQueue<>();

        Log.d(TAG, "Available cores: " + NUMBER_OF_CORES);

        mExecutor = new ThreadPoolExecutor(NUMBER_OF_CORES, NUMBER_OF_CORES * 2, KEEP_ALIVE_TIME, TimeUnit.SECONDS, mTaskQueue, new SimpleThreadFactory());
    }

    public AsyncTask<Params, Progress, Result> executeAsync(@NonNull final Params... params) {
        return super.executeOnExecutor(mExecutor, params);
    }

    /* Remove all tasks in the queue and stop all running threads
     */
    @AnyThread
    public void shutdownNow() {
        synchronized (this) {
            mTaskQueue.clear();
            if ((!mExecutor.isShutdown()) && (!mExecutor.isTerminated())) {
                mExecutor.shutdownNow();
            }
        }
    }
}

参考链接


用SpringBoot框架来接收multipart/form-data文件

在本文中,您将学习如何使用 Spring Boot 实现 Web 服务中的文件上传和下载功能。首先会构建一个 REST APIs 实现上传及下载的功能,然后使用 Postman 工具来测试这些接口,最后创建一个 Web 界面使用 JavaScript 调用接口演示完整的功能。最终界面及功能如下:

继续阅读用SpringBoot框架来接收multipart/form-data文件

macOS 10.15.7运行JNLP文件

系统升级到`macOS 10.15.7`之后,发现`JNLP`文件无法打开了,默认的`Java`被切换到`OpenJDK`了。

但是根据官方说明,从`Java 1.8`开始,`OpenJDK`已经默认不携带`Java Web Start`功能了,这些功能被限定为`Oracle JRE`专有的功能。因此默认的`javaws`已经无法找到了。

Java Web Start (JWS) was deprecated in Java 9, and starting with Java 11, Oracle removed JWS from their JDK distributions. This means that clients that have the latest version of Java installed can no longer use JWS-based applications. And since public support of Java 8 has ended in Q2/2019, companies no longer get any updates and security fixes for Java Web Start.

解决方法有两个,一个是使用 OpenWebStart 来打开`JNLP`文件,可惜的是,目前功能不够完善,很多应用无法正常运行,比如`HP Gen8`的`集成远程控制台`系统的`Java Web Start`。

另一个是使用 Orocle JRE 来打开`JNLP`文件,目前这个功能是比较好用的,就是需要注册账号才能下载。

参考连接


关于提示“The type StringEscapeUtils is deprecated”问题的解决

如题所示,之前一直使用`commons-lang3-3.x.jar` 这个jar包里面的 `org.apache.commons.lang3.StringEscapeUtils` 类来转义特殊字符,但是最近发现使用这个类会出现以下提示:

Multiple markers at this line
	- The type StringEscapeUtils is deprecated
	- The method escapeXml11(String) from the type StringEscapeUtils is 
	 deprecated

看提示是说 `StringEscapeUtils` 这个类已经过期了,提醒使用新的替代类。看了下这个类的官方文档,很自然地找到了新的替代类——`org.apache.commons.text.StringEscapeUtils`

因此,使用Maven引用相应的jar包即可解决这个问题:

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-text</artifactId>
    <version>1.9</version>
</dependency>

或者直接去Maven仓库下载这个jar包,下载地址如下:http://mvnrepository.com/artifact/org.apache.commons/commons-text/1.1

参考链接


关于提示“The type StringEscapeUtils is deprecated”问题的解决

Robolectric单元测试报错“ReflectionHelpers.java java.lang.IllegalAccessException at UnsafeFieldAccessorImpl.java”

`macOS Catalina 10.15.6`,使用`HomeBrew`执行`brew install java`,安装了目前最新的`openjdk-14.0.1`之后,执行`bash gradlew clean build`,报告如下错误信息:

com.xxxx.plugin.face.FaceTest > executeAsync_Success FAILED
    java.lang.RuntimeException at ReflectionHelpers.java:223
        Caused by: java.lang.RuntimeException at ReflectionHelpers.java:208
            Caused by: java.lang.IllegalAccessException at UnsafeFieldAccessorImpl.java:76

目前测试发现,升级`Robolectric`版本并不能解决问题。其实对于`Android Studio`来说,完全可以指定`Android Studio`自带的`JDK`进行编译。

$ echo "export JAVA_HOME='/Applications/Android Studio.app/Contents/jre/jdk/Contents/Home'" >> ~/.bashrc

$ source ~/.bashrc

# 由于macOS Catalina 10.15.6已经切换到zsh了,因此需要配置zsh的配置文件
$ echo "export JAVA_HOME='/Applications/Android Studio.app/Contents/jre/jdk/Contents/Home'" >> ~/.zprofile 

$ source ~/.zprofile

关闭`SHELL`,之后重新打开新的`SHELL`,重新执行编译命令即可。

建议`Android`版本发布使用的`JDK`就是`Android Studio`自带的`JDK`,这样可以保证应用的稳定性,减少由于`JDK`差异导致的各种问题。

当前建议的版本

$ java -version
openjdk version "1.8.0_242-release"
OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b3-6222593)
OpenJDK 64-Bit Server VM (build 25.242-b3-6222593, mixed mode)

参考链接