Watch
一、功能介绍:
- Watch:监听单个配置文件,并返回第一次修改信息
- WatchWithOption:监听配置文件的某个section,并返回该section的第一次修改信息
二、程序实现:
Config结构体:
type Config struct {
filepath string
conflist []map[string]map[string]string
}
listener 接口:
type Listener interface {
listen(infile string)
}
ListenFunc 接口函数:
type ListenFunc func(infile string) (*Config, error)
2.1 init
初始化函数,在包被加载时调用,用于判断当前系统环境为linux还是windows,用于决定注释行的注释符号 # 或是 ;
func init() {
sysType := runtime.GOOS
if sysType == "linux" {
// LINUX系统
sys = "#"
}
if sysType == "windows" {
// windows系统
sys = ";"
}
}
2.2 SetConfig
初始化Config结构体,设定对应的文件路径
func SetConfig(filepath string) *Config {
c := new(Config)
c.filepath = filepath
return c
}
2.3 ReadList
读取文件,并将配置属性存储到Config中
- 打开对应文件,如果打开失败则返回 自定义错误 ,完成打开后,使用ReadString来读取换行符,实现按行读取,并当读取失败时,输出自定义 行读取错误(自定义错误实现)
file, err := os.Open(c.filepath)
if err != nil {
return nil, fmt.Errorf("Error in ReadList func, can't open the file ") // self define error
}
defer file.Close()
var data map[string]map[string]string
var section string
buf := bufio.NewReader(file)
for {
l, err := buf.ReadString('\n')
line := strings.TrimSpace(l)
if err != nil {
if err != io.EOF {
return nil, fmt.Errorf("Error in ReadList func, can't Read the file data ") // self define error
}
if len(line) == 0 {
break
}
}
- 按行进行数据的判断,判断是空行、注释行、section行还是配置行,若配置行的section为空,则设定为默认section: defaultsection
switch {
case len(line) == 0: //empty line
case string(line[0]) == sys: // note line
case line[0] == '[' && line[len(line)-1] == ']':
section = strings.TrimSpace(line[1 : len(line)-1])
data = make(map[string]map[string]string)
data[section] = make(map[string]string)
default:
i := strings.IndexAny(line, "=")
if i == -1 {
fmt.Println("i = -1")
continue
}
value := strings.TrimSpace(line[i+1 : len(line)])
if section == "" {
section = "defaultsection"
}
data = make(map[string]map[string]string)
data[section] = make(map[string]string)
valmap := strings.TrimSpace(line[0:i])
data[section][valmap] = value
if c.unique(section, valmap) == true {
c.conflist = append(c.conflist, data)
}
}
2.4 unique
判断配置项是否重复,防止相同配置的重复写入
func (c *Config) unique(conf string, confmap string) bool {
for _, v := range c.conflist {
for k, kmap := range v {
if k == conf {
for val, _ := range kmap {
if val == confmap {
return false
}
}
}
}
}
return true
}
2.5 Watch
监听文件配置项是否被改变
- 通过调用listen接口函数来实现
func Watch(filename string, Listener ListenFunc) (*Config, error) {
Listener = filelisten
return Listener.listen(filename)
}
通过设定接口函数Listener = filelisten,来调用filelisten函数:
循环读取配置文件,并当配置文件发生改变时,返回改变后的配置属性
func filelisten(infile string) (*Config, error) {
conf := SetConfig(infile)
out, err := conf.ReadList()
if err != nil {
return nil, err
}
var exist bool = false
for {
reconf := SetConfig(infile)
reout, err := reconf.ReadList()
if err != nil {
return nil, err
}
if len(out) != len(reout) {
c := SetConfig(infile)
c.conflist = out
return c, nil
}
for _, val := range reout {
for valval, valend := range val {
for finval, v1 := range valend {
exist = false
for _, oldval := range out {
oldmap1 := oldval[valval]
oldmap2 := oldmap1[finval]
if oldmap2 == v1 {
exist = true
}
}
if exist == false {
c := SetConfig(infile)
c.conflist = out
return c, nil
}
}
}
}
time.Sleep(1000)
}
}
2.6 WatchWithOption
监听配置文件的section项是否改变,并返回第一次改变的配置信息
func WatchWithOption(filename string, Listener ListenFunc, section string) (*Config, error) {
mapstr1 = section
Listener = optionlisten
return Listener.listen(filename)
}
通过设定接口函数Listener = optionlisten,来调用optionlisten函数:
返回监听开始后,配置文件该section变化后的配置文件数据
func optionlisten(infile string) (*Config, error) {
conf := SetConfig(infile)
out, err := conf.ReadList()
if err != nil {
return nil, err
}
var exist bool = false
for {
reconf := SetConfig(infile)
reout, err := reconf.ReadList()
if err != nil {
return nil, err
}
renum := 0
oldnum := 0
for _, val := range reout {
for valnumname, _ := range val {
if valnumname == mapstr1 {
renum++
}
}
}
for _, val := range out {
for valnumname2, _ := range val {
if valnumname2 == mapstr1 {
oldnum++
}
}
}
if oldnum != renum {
c := SetConfig(infile)
c.conflist = out
return c, nil
}
for _, val := range reout {
for valval, valend := range val {
if valval == mapstr1 {
for finval, v1 := range valend {
exist = false
for _, oldval := range out {
for oldvalname, _ := range oldval {
if oldvalname == mapstr1 {
oldmap1 := oldval[valval]
oldmap2 := oldmap1[finval]
if oldmap2 == v1 {
exist = true
}
}
}
}
if exist == false {
c := SetConfig(infile)
c.conflist = out
return c, nil
}
}
}
}
}
time.Sleep(1000)
}
}
三、功能测试:
- changefile函数:
func changefile() {
wireteString := "a = c\n#noteline\n[sec]\nsecindex = index\n"
var d1 = []byte(wireteString)
ioutil.WriteFile("./config.ini", d1, 0666) //写入文件(字节数组)
}
3.1 测试Watch实现:
由于要在gotest中检测到文件改变,所以我们使用go来将changefile函数在子线程运行,让其实现文件的更改供watch函数观察读取,之后进行test的判断
- watchres函数:先在这里调用写入一次读取,之后当读取到子进程的读取操作后就会触发watch,返回读取的配置属性
func watchres(lis ListenFunc) {
wireteString := "e = f\n#notline\n[sec]\nsecbasic = basic"
var d1 = []byte(wireteString)
ioutil.WriteFile("./config.ini", d1, 0666) //写入文件(字节数组)
a, _ := Watch("config.ini", lis)
res = a.Conflist
}
测试函数:
func TestWatch(t *testing.T) {
var lis ListenFunc = Filelisten
go changefile()
watchres(lis)
if res[0]["defaultsection"]["a"] != "c" || res[1]["sec"]["secindex"] != "index" {
t.Errorf("Test watch error%s, %s", res[0]["defaultsection"]["a"], res[1]["sec"]["secindex"])
}
}
测试结果:
3.2 测试WachWithOption实现
由于要在gotest中检测到文件改变,所以我们使用go来将changefile函数在子线程运行,让其实现文件的更改供watch函数观察读取,之后进行test的判断
- watchoptionres函数:先在这里调用写入一次读取,之后当读取到子进程的读取操作后就会触发watch,返回读取的配置属性
func watchoptionres(lis ListenFunc) {
wireteString := "c = d\n#notline\n[sec]\nsecbasic = basic"
var d1 = []byte(wireteString)
ioutil.WriteFile("./config.ini", d1, 0666) //写入文件(字节数组)
a, _ := WatchWithOption("config.ini", lis, "sec")
sectionres = a.Conflist
}
- 测试函数:
func TestWatchWithOption(t *testing.T) {
var lis ListenFunc = Optionlisten
go changefile()
watchoptionres(lis)
if sectionres[0]["defaultsection"]["a"] != "c" || sectionres[1]["sec"]["secindex"] != "index" {
t.Errorf("Test watch error%s, %s", sectionres[0]["defaultsection"]["a"], sectionres[1]["sec"]["secindex"])
}
}
- 测试结果:
3.3 测试SetConfig函数
func TestSetConfig(t *testing.T) {
c := SetConfig("config2.ini")
if c.Filepath != "config2.ini" {
t.Errorf("set config wrong, should be config2.ini, but set is %s", c.Filepath)
}
}
- 测试结果:
- 压力测试:
func BenchmarkSetConfig(b *testing.B) {
for i := 0; i < b.N; i++ {
SetConfig("a")
}
}
- 压测结果:
3.4 测试ReadList函数
func TestReadList(t *testing.T) {
conf := new(Config)
conf.Filepath = "config2.ini"
_, err := conf.ReadList()
if err != nil {
t.Errorf("ReadList wrong\n%s\n", err)
}
}
- 测试结果:
- 压力测试:
func BenchmarkReadList(b *testing.B) {
conf := new(Config)
conf.Filepath = "config2.ini"
for i := 0; i < b.N; i++ {
_, err := conf.ReadList()
if err != nil {
fmt.Printf("ReadList wrong\n%s\n", err)
}
}
}
- 压测结果:
3.5 测试unique函数
func TestUnique(t *testing.T) {
var conf *Config = new(Config)
conf.Filepath = "config2.ini"
data := make(map[string]map[string]string)
section := "a"
data[section] = make(map[string]string)
valmap := "b"
data[section][valmap] = "c"
conf.Conflist = append(conf.Conflist, data)
bool1 := conf.unique("a", "b")
bool2 := conf.unique("c", "d")
if bool1 != false || bool2 != true {
t.Errorf("unique wrong")
}
}
- 测试结果:
- 压力测试:
func BenchmarkUnique(b *testing.B) {
var conf *Config = new(Config)
conf.Filepath = "config2.ini"
data := make(map[string]map[string]string)
section := "a"
data[section] = make(map[string]string)
valmap := "b"
data[section][valmap] = "c"
conf.Conflist = append(conf.Conflist, data)
for i := 0; i < b.N; i++ {
conf.unique("a", "b")
}
}
- 压测结果:
四、简单使用案例:
我们选择使用课件案例的ini文件来进行读取:
# possible values : production, development
app_mode = development
[paths]
# Path to where grafana can store temp files, sessions, and the sqlite3 db (if that is used)
data = /home/git/grafana
[server]
# Protocol (http or https)
protocol = http
# The http port to use
http_port = 9999
# Redirect to correct domain if host header does not match domain
# Prevents DNS rebinding attacks
enforce_domain = true
-
首先定义接口函数,来选择调用监听文件还是配置项section
var listen Listener = Filelisten//or Optionlisten
-
选择调用watch还是watchwithoption函数我们这里选用文件监听
a, err := Watch(infile, listen)//文件监听
a, err := Watch(infile, listen, sectionname)//选用监听section -
输出读取到的更改后的配置信息或者自定义错误:
if err != nil {
fmt.Println(err.Error())
} else {
fmt.Println(a)
} -
完整简单运行代码:
func main() {
var listen ListenFunc = Filelisten //or Optionlisten
infile := "config2.ini"
a, err := Watch(infile, listen)// WatchWithOption(infile, listen, "section")
if err != nil {
fmt.Println(err.Error())
} else {
fmt.Println(a)
}
}
运行结果:(我们在配置文件的http后加一个9,保存后watch即可捕捉到变更
两种测例均放在watch2.go文件的最后,已注释)