花生壳,提供域名跟本地 IP 的映射服务,有点意思的技术。
吐槽
- WNR500 无线路由器,设置动态 DNS 连接到花生壳,失败!
- 树莓派基于 ARM 内核,不太了解 x86_64、i386、arm 之间的区别,失败!
- Linux 小白,搞不定交叉编译,失败!
概述
想实现域名指向本地环境,进而做一些实验性工程。例如:微信、微博、OpenVPN、subVersion 等等…
但是在实现过程中,从无线路由器自带动态 DNS 用不起来,到花生壳下载安装版安装不上去,再到花生壳下载安装版交叉编译失败,最后选择网页版实现。
本文主要讲,如何实现花生壳的网页版。
技术背景
基于 Spring,Ehcache 等技术实现。
为了更好的清楚明白,可能需要了解以下内容:
- Spring 基本配置
- Ehcache 基本配置
- 定时器
- Get 请求
实现说明
首先是阅览花生壳提供的官方文档:软件下载、花生壳:http协议说明。
官方提供的两种方式实现:
- 使用 URL 验证
- 原始 HTTP GET 访问
从难易程度来说,第一种最简单,第二种稍微比较复杂。
从安全程度来说,第一种不安全,第二种稍微安全一些。
在实现技术无压力的情况下,当然选择略复杂的第二种方法来实现。
思路:
通过定时器,设置时间间隔后,重复提交请求来保证域名对应的 IP 是有效的。(如果有固定外网 IP,那么直接去花生壳官网配置把。)但是,考虑到本地环境的资源有限的情况下,可能真的每个几分钟发送一个请求。同时,如果间隔的时间太长,有可能导致本地外网 IP 被刷新,没有及时更新到花生壳。
为了解决上述的问题,引入缓存。首先,缓存需要一个失效时间,防止奇葩情况,即强制某个时间间隔(略微长一些,60 分钟以上)必须重新提交。其次,提交前,先从缓存获取 IP,比较存在改动后重新提交,更新成功后,将外网 IP 压入缓存。
关键是两个时间点:
- 定时器,每隔几分钟先判断缓存,再判断是否提交请求;
- 缓存有效时间,每隔几小时直接清空缓存,必须重新提交请求。
Ehcache.xml 配置
<!-- ORAY缓存 保存100分钟 -->
<cache name="orayCache"
maxEntriesLocalHeap="10"
eternal="false"
timeToIdleSeconds="6000"
timeToLiveSeconds="6000"
overflowToDisk="false"
statistics="false">
</cache>applicationContext.xml 配置
添加:
- xmlns:task=”http://www.springframework.org/schema/task”
- xsi:schemaLocation=”http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd”
- <task:annotation-driven/>
第一、二项是添加在 beans 属性里,第三项放在 context:component-scan 之后。
HttpCommon.java
package cn.live.common;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.List;
import java.util.Map;
/**
* @ClassName: HttpCommon HTTP 操作
* @Description:
* @author: FoamValue
* @mail: foamvalue@live.cn
* @time: 2015年6月29日 下午10:57:52
* @version: V1.0
*/
public class HttpCommon {
/**
* Get 方式请求
* @param urlStr
* @param params
* @return
*/
public static String sendGetURL(String urlStr, Map<String, String> params) {
String result = "";
BufferedReader in = null;
try {
URL url = new URL(urlStr);
// 打开和URL之间的连接
URLConnection connection = url.openConnection();
// 设置通用的请求属性
if (params != null && !params.isEmpty()) {
for (String key : params.keySet()) {
connection.setRequestProperty(key, params.get(key));
}
}
// 建立实际的连接
connection.connect();
// 获取所有响应头字段
Map<String, List<String>> map = connection.getHeaderFields();
// 遍历所有的响应头字段
for (String key : map.keySet()) {
System.out.println(key + "--->" + map.get(key));
}
// 定义 BufferedReader输入流来读取URL的响应
in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String line;
while ((line = in.readLine()) != null) {
result += line;
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (in != null) {
in.close();
}
} catch (Exception e2) {
e2.printStackTrace();
}
}
return result;
}
}OrayTimer.java
package cn.live.timer;
import java.util.HashMap;
import java.util.Map;
import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;
import org.apache.commons.lang.StringUtils;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import cn.live.common.BaseCommon;
import cn.live.common.HttpCommon;
/**
* @ClassName: OrayTimer 花生壳定时器
* @Description: 采用网页版的方式刷新花生壳 IP
* @author: FoamValue
* @mail: foamvalue@live.cn
* @time: 2015年6月29日 下午10:44:10
* @version: V1.0
*/
@Component
public class OrayTimer {
/**
* 开通花生壳服务的域名
*/
private final static String orayUrl = "ORAYURL";
/**
* 花生壳帐号
*/
private final static String userName = "USERNAME";
/**
* 花生壳密码
*/
private final static String password = "PASSWORD";
/**
* 测试外网IP
*/
private final static String checkUrl = "http://ddns.oray.com/checkip";
/**
* 缓存管理器
*/
private static CacheManager cacheManager = CacheManager.create();
/**
* 花生壳缓存
*/
private static Cache orayRefreshCache;
/**
* 花生壳缓存KEY
*/
private final static String orayCacheKey = "_ORAYCACHEKEY";
/**
* 刷新 IP
*/
@Scheduled(cron = "0 0/2 * * * ?")
public static void refreshIP() {
orayRefreshCache = cacheManager.getCache("orayCache");
Element element = orayRefreshCache.get(orayCacheKey);
String ip = null;
if (element != null) ip = orayRefreshCache.get(orayCacheKey).getObjectValue().toString();
String checkIp = checkIP();
if (StringUtils.isBlank(ip) || !ip.equals(checkIp)) {
// GET /ph/update?hostname=yourhostname&myip=ipaddress HTTP/1.0
// Host: ddns.oray.com
// Authorization: Basic base-64-authorization
// User-Agent: Oray
StringBuilder urlStr = new StringBuilder("http://ddns.oray.com/ph/update?hostname=")
.append(orayUrl)
.append("&myip=");
Map<String, String> params = new HashMap<String, String>();
params.put("Host", "ddns.oray.com");
params.put("Authorization", "Basic " + BaseCommon.getBase64(userName + ":" + password));
params.put("User-Agent", "Oray");
String result = HttpCommon.sendGetURL(urlStr.toString(), params);
// good 更新成功,域名的IP地址已经更新,同时会返回本次更新成功的IP,用空格隔开,如:good 1.2.3.4
// nochg 更新成功,但没有改变IP。一般这种情况为本次提交的IP跟上一次的一样
// notfqdn 未有激活花生壳的域名
// nohost 域名不存在或未激活花生壳
// abuse 请求失败,频繁请求或验证失败时会出现
// !donator 表示此功能需要付费用户才能使用,如https
// 911 系统错误
if (result.contains("good") || result.contains("nochg")) {
orayRefreshCache.put(new Element(orayCacheKey, checkIp));
} else {
orayRefreshCache.put(new Element(orayCacheKey, null));
}
}
}
/**
* 获取外网 IP
* @return IP 地址
*/
public static String checkIP() {
String ip = null;
try {
String result = HttpCommon.sendGetURL(checkUrl, null);
if (StringUtils.isNotBlank(result) && result.contains("Current IP Address: ")) {
ip = result.substring(result.indexOf("Current IP Address: ") + "Current IP Address: ".length(), result.indexOf("</body>")).trim();
}
} catch (Exception e) {
e.printStackTrace();
}
return ip;
}
public static void main (String[] args) {
refreshIP();
// System.out.println(checkIP());
}
}综上,配置好缓存、定时器之后,就可以愉快的玩耍了。
HTTP 请求包含两种:
- 花生壳更新请求
- 获取外网 IP 请求
结束语
一直折腾,简直停不下来。