package com.app.mvc.proxy; import com.app.mvc.beans.JsonMapper; import com.app.mvc.config.GlobalConfig; import com.app.mvc.config.GlobalConfigKey; import com.google.common.base.Preconditions; import com.google.common.base.Splitter; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.MapUtils; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.math.NumberUtils; import org.apache.commons.net.telnet.TelnetClient; import org.apache.http.HttpHost; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.StatusLine; import org.apache.http.client.HttpClient; import org.apache.http.conn.ConnectTimeoutException; import org.apache.http.util.EntityUtils; import java.io.IOException; import java.net.SocketTimeoutException; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import static org.apache.http.conn.params.ConnRoutePNames.DEFAULT_PROXY; /** * Created by jimin on 16/5/5. */ @Slf4j public class ProxyManager { /** * 测试与代理连接的超时 */ private final static int CONNECT_TIME_OUT = 1000; private final static Splitter proxyIpPortSplitter = Splitter.on(",").trimResults().omitEmptyStrings(); private final static Splitter proxyKeySplitter = Splitter.on(",").trimResults().omitEmptyStrings(); private final static int USE_PROXY_BY_CALC = 0; private final static int FORCE_USE_PROXY = 1; private final static int FORCE_NOT_USE_PROXY = 2; /** * 当前支持自动切换代理的处理类 */ private static ImmutableMap<String, UrlConnectionChecker> urlConnectionCheckerMap = ImmutableMap.<String, UrlConnectionChecker>builder() //.put(key, DefaultUrlConnectionChecker) .build(); /** * 代理列表 */ private final static Map<String, Set<Proxy>> proxies = Maps.newConcurrentMap(); /** * 存储本机最佳效果的代理 */ private final static Map<String, ProxyResponse> bestProxyConnectResponseMap = Maps.newConcurrentMap(); /** * 心跳检测线程 */ private static final ScheduledExecutorService proxyHealthCheckScheduler = Executors.newScheduledThreadPool(1); /** * 定时检测代理和直连的效果 */ private static final ScheduledExecutorService urlConnectCheckScheduler = Executors.newScheduledThreadPool(1); /** * 单例实例 */ private static ProxyManager proxyManager = new ProxyManager(); private ProxyManager() { // 心跳检测代理是否有效 /* proxyHealthCheckScheduler.scheduleAtFixedRate(new Runnable() { @Override public void run() { refresh(); } }, 10, 10, TimeUnit.SECONDS); */ // 检测当前直连和代理中最佳的, 每次计算完延迟60s开始下一次 /* urlConnectCheckScheduler.scheduleWithFixedDelay(new Runnable() { @Override public void run() { calcBestUrlConnect(); } }, 15, 60, TimeUnit.SECONDS); */ } public static void reload(Map<String, String> conf) { //清空所有代理,重新加载 proxies.clear(); // 清空之前计算的结果 bestProxyConnectResponseMap.clear(); log.info("reload size:{}, key:{}, value:{}", conf.size(), conf.keySet(), conf.values()); String proxyIpsKey = conf.get(GlobalConfigKey.PROXY_IPS_KEY); if (StringUtils.isEmpty(proxyIpsKey)) { return; } List<String> proxyIpsKeyList = proxyKeySplitter.splitToList(proxyIpsKey); for (String proxyKey : proxyIpsKeyList) { String proxyIps = conf.get(proxyKey + GlobalConfigKey.PROXY_IPS_SUFFIX); if (StringUtils.isBlank(proxyIps)) { proxyIps = conf.get(GlobalConfigKey.DEFAULT_PROXY_IPS); log.info("没有配置对应的代理,使用默认配置, key:{}, setting:{}", proxyKey, proxyIps); } if (StringUtils.isBlank(proxyIps)) { log.warn("没有配置对应的代理,也未配置默认的代理, key:{}", proxyKey); continue; } Set<Proxy> proxySet = proxies.get(proxyKey); if (proxySet == null) { proxySet = Sets.newConcurrentHashSet(); proxies.put(proxyKey, proxySet); } for (String outEntry : proxyIpPortSplitter.split(proxyIps)) { String[] inEntry = StringUtils.split(outEntry, ":"); if (ArrayUtils.isNotEmpty(inEntry) && inEntry.length == 2) { Proxy proxy = new Proxy(inEntry[0], NumberUtils.toInt(inEntry[1], 7001)); proxy.setAlive(isConnect(proxy.getIp(), proxy.getPort())); proxySet.add(proxy); } else { log.warn("代理配置的有问题, 过滤, key:{}, str:{}", proxyKey, outEntry); } } } log.info("proxies:{}", proxies); } /** * 刷新代理状态 */ private static void refresh() { if (MapUtils.isEmpty(proxies)) { log.info("no proxy to refresh"); } for (Iterator<String> iterator = proxies.keySet().iterator(); iterator.hasNext(); ) { String proxyKey = iterator.next(); Set<Proxy> proxySet = proxies.get(proxyKey); for (Iterator<Proxy> it = proxySet.iterator(); it.hasNext(); ) { Proxy proxy = it.next(); proxy.setAlive(isConnect(proxy.getIp(), proxy.getPort())); } } } /** * 验证代理是否能连接 */ private static boolean isConnect(String ip, int port) { try { TelnetClient client = new TelnetClient(); client.setDefaultTimeout(CONNECT_TIME_OUT); client.connect(ip, port); return true; } catch (Exception e) { return false; } } /** * 处理代理 * * @param client */ public static void handleProxy(String proxyKey, HttpClient client, String url) { try { if (StringUtils.isEmpty(proxyKey)) { return; } Proxy proxy = getProxy(proxyKey); if (proxy != null && proxy.isAlive()) { HttpHost httpHost = new HttpHost(proxy.getIp(), proxy.getPort()); client.getParams().setParameter(DEFAULT_PROXY, httpHost); log.info("使用代理, proxyKey={}, url={}, proxy={}", proxyKey, url, proxy); } else { client.getParams().removeParameter(DEFAULT_PROXY); } } catch (Exception e) { log.warn("proxyKey:{}, url:{} , exception:{}", proxyKey, url, e.toString(), e); } } /** * 获取代理ip端口 * * @return 可以使用的代理, null代表不走代理 */ private static Proxy getProxy(String proxyKey) { int proxyFlag = GlobalConfig.getIntValue(GlobalConfigKey.PROXY_FLAG, USE_PROXY_BY_CALC); if (proxyFlag == FORCE_NOT_USE_PROXY) { // 强制不走代理 // LOG.info("强制不走代理, key:{}", proxyKey); return null; } // 1 强制走代理 // 2 没有配置自动检测url的,但是却在要切代理的列表里, 这种case主要用于处理那些通过代理商id切换代理 if (proxyFlag == FORCE_USE_PROXY || (!urlConnectionCheckerMap.containsKey(proxyKey) && proxies.containsKey(proxyKey))) { // 强制走代理 Set<Proxy> proxySet = getProxiesByKey(proxyKey); if (CollectionUtils.isNotEmpty(proxySet)) { for (int i = 0; i < proxySet.size(); i++) { Proxy proxy = (Proxy) proxySet.toArray()[i]; if (proxy.isAlive()) { // LOG.info("强制走代理, key:{}", proxyKey); return proxy; } } } } if (!urlConnectionCheckerMap.containsKey(proxyKey)) { return null; } ProxyResponse proxyResponse = bestProxyConnectResponseMap.get(proxyKey); if (proxyResponse == null || !proxyResponse.isCanVisit()) { // 都连不通, 走直连 return null; } if (proxyResponse.getProxy().isLocal()) { // 代表是直连的 return null; } return proxyResponse.getProxy(); } /** * 获取代理管理实例 */ public static ProxyManager getProxyManager() { return proxyManager; } /** * 允许外部查询当前指定key配置的代理集合 */ public static Set<Proxy> getProxiesByKey(String proxyKey) { if (proxies.containsKey(proxyKey)) { return proxies.get(proxyKey); } return null; } /** * 计算每个interfaceCode最佳的代理, 存储到bestProxyConnectResponseMap中 * 如果选择直连, 则bestProxyConnectResponseMap中存储的信息为null */ private static void calcBestUrlConnect() { String proxyKeys = GlobalConfig.getStringValue(GlobalConfigKey.PROXY_IPS_KEY, ""); if (StringUtils.isEmpty(proxyKeys)) { log.info("没有需要走自动代理的配置"); return; } List<String> proxyKeyList = proxyKeySplitter.splitToList(proxyKeys); for (String proxyKey : proxyKeyList) { UrlConnectionChecker checker = urlConnectionCheckerMap.get(proxyKey); if (checker == null) { // 只处理当前系统已经支持的和qconfig配置的交集部分 log.warn("没有实现检测类, key:{}", proxyKey); continue; } // 先计算直连的效果 ProxyResponse directResponse = checkUrlConnection(proxyKey, checker, null); log.info("直连检测, key:{}, {}", proxyKey, JsonMapper.obj2String(directResponse)); // 直连效果已经很好,就不需要关注代理了(代理尽量少用) if (directResponse != null && directResponse.isCanVisit() && directResponse.getCost() < GlobalConfig .getIntValue(GlobalConfigKey.PROXY_VISIT_BASE_MILLSECONDS, 5000)) { bestProxyConnectResponseMap.put(proxyKey, toLocalResponse(directResponse)); log.info("直连效果可以,不需要继续尝试代理了, key:{}", proxyKey); continue; } // 计算代理的效果 ProxyResponse bestProxyResponse = directResponse; boolean isDirectBest = true; // 记录直连是否是最佳的 Set<Proxy> proxySet = getProxiesByKey(proxyKey); for (Proxy proxy : proxySet) { if (proxy.isAlive()) { // 如果这个代理是活着的,去测试对应的test url ProxyResponse proxyResponse = checkUrlConnection(proxyKey, checker, proxy); log.info("代理检测, key:{}, {}", proxyKey, JsonMapper.obj2String(proxyResponse)); // 选出最佳效果的代理 if (proxyResponse != null && proxyResponse.isCanVisit()) { if (proxyResponse.getCost() < bestProxyResponse.getCost()) { isDirectBest = false; bestProxyResponse = proxyResponse; } } } } if (!isDirectBest) { log.info("本次检测到最佳的代理, key:{}, {}", proxyKey, JsonMapper.obj2String(bestProxyResponse)); bestProxyConnectResponseMap.put(proxyKey, bestProxyResponse); } else { bestProxyConnectResponseMap.put(proxyKey, toLocalResponse(directResponse)); } } } private static ProxyResponse toLocalResponse(ProxyResponse proxyResponse) { return new ProxyResponse(proxyResponse.getUrl(), proxyResponse.getCost(), proxyResponse.isCanVisit()); } private static ProxyResponse checkUrlConnection(String key, UrlConnectionChecker checker, Proxy proxy) { Preconditions.checkNotNull(checker, "请求信息不可以为空"); final HttpClient httpClient = checker.httpClient(); final String url = checker.url(); if (proxy != null) { httpClient.getParams().setParameter(DEFAULT_PROXY, new HttpHost(proxy.getIp(), proxy.getPort())); } else { httpClient.getParams().removeParameter(DEFAULT_PROXY); } long start = System.currentTimeMillis(); HttpResponse response = null; try { if (checker.httpGet() != null) { response = httpClient.execute(checker.httpGet()); } else { response = httpClient.execute(checker.httpPost()); } StatusLine status = response.getStatusLine(); if (status != null && status.getStatusCode() == HttpStatus.SC_OK) { return ProxyResponse.success(proxy, url, System.currentTimeMillis() - start); } else { return ProxyResponse.failed(proxy, url, System.currentTimeMillis() - start); } } catch (SocketTimeoutException e1) { log.info("key:{}, exception: SocketTimeoutException, proxy:{}", key, JsonMapper.obj2String(proxy)); return ProxyResponse.failed(proxy, url, System.currentTimeMillis() - start); } catch (ConnectTimeoutException e2) { log.info("key:{}, exception: ConnectTimeoutException, proxy:{}", key, JsonMapper.obj2String(proxy)); return ProxyResponse.failed(proxy, url, System.currentTimeMillis() - start); } catch (IOException e3) { log.info("key:{}, exception: IOException, proxy:{}", key, JsonMapper.obj2String(proxy)); return ProxyResponse.failed(proxy, url, System.currentTimeMillis() - start); } catch (Throwable t) { log.warn(String.format("测试出现未知异常, key:%s, url:%s, %s", key, url, JsonMapper.obj2String(proxy)), t); return ProxyResponse.success(proxy, url, System.currentTimeMillis() - start); } finally { if (checker.httpGet() != null) { checker.httpGet().releaseConnection(); } if (checker.httpPost() != null) { checker.httpPost().releaseConnection(); } if (response != null) { try { EntityUtils.consume(response.getEntity()); } catch (Throwable t) { log.warn(String.format("EntityUtils.consume(entity)出现异常, url:%s, %s", url, JsonMapper.obj2String(proxy)), t); } } } } }