mirror of https://github.com/fatedier/frp
commit
3a2946a2ff
|
@ -3,6 +3,7 @@ language: go
|
||||||
|
|
||||||
go:
|
go:
|
||||||
- 1.8.x
|
- 1.8.x
|
||||||
|
- 1.x
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- make
|
- make
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
FROM golang:1.8 as frpBuild
|
||||||
|
|
||||||
|
COPY . /go/src/github.com/fatedier/frp
|
||||||
|
|
||||||
|
ENV CGO_ENABLED=0
|
||||||
|
|
||||||
|
RUN cd /go/src/github.com/fatedier/frp \
|
||||||
|
&& make
|
||||||
|
|
||||||
|
FROM alpine:3.6
|
||||||
|
|
||||||
|
COPY --from=frpBuild /go/src/github.com/fatedier/frp/bin/frpc /
|
||||||
|
COPY --from=frpBuild /go/src/github.com/fatedier/frp/conf/frpc.ini /
|
||||||
|
COPY --from=frpBuild /go/src/github.com/fatedier/frp/bin/frps /
|
||||||
|
COPY --from=frpBuild /go/src/github.com/fatedier/frp/conf/frps.ini /
|
||||||
|
|
||||||
|
EXPOSE 80 443 6000 7000 7500
|
||||||
|
|
||||||
|
WORKDIR /
|
||||||
|
|
||||||
|
CMD ["/frps","-c","frps.ini"]
|
|
@ -1,134 +0,0 @@
|
||||||
{
|
|
||||||
"ImportPath": "github.com/fatedier/frp",
|
|
||||||
"GoVersion": "go1.8",
|
|
||||||
"GodepVersion": "v79",
|
|
||||||
"Packages": [
|
|
||||||
"./..."
|
|
||||||
],
|
|
||||||
"Deps": [
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/armon/go-socks5",
|
|
||||||
"Rev": "e75332964ef517daa070d7c38a9466a0d687e0a5"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/davecgh/go-spew/spew",
|
|
||||||
"Comment": "v1.1.0",
|
|
||||||
"Rev": "346938d642f2ec3594ed81d874461961cd0faa76"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/docopt/docopt-go",
|
|
||||||
"Comment": "0.6.2",
|
|
||||||
"Rev": "784ddc588536785e7299f7272f39101f7faccc3f"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/fatedier/beego/logs",
|
|
||||||
"Comment": "v1.7.2-72-gf73c369",
|
|
||||||
"Rev": "f73c3692bbd70a83728cb59b2c0423ff95e4ecea"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/golang/snappy",
|
|
||||||
"Rev": "5979233c5d6225d4a8e438cdd0b411888449ddab"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/julienschmidt/httprouter",
|
|
||||||
"Comment": "v1.1-41-g8a45e95",
|
|
||||||
"Rev": "8a45e95fc75cb77048068a62daed98cc22fdac7c"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/klauspost/cpuid",
|
|
||||||
"Comment": "v1.0",
|
|
||||||
"Rev": "09cded8978dc9e80714c4d85b0322337b0a1e5e0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/klauspost/reedsolomon",
|
|
||||||
"Comment": "1.3-1-gdde6ad5",
|
|
||||||
"Rev": "dde6ad55c5e5a6379a4e82dcca32ee407346eb6d"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/pkg/errors",
|
|
||||||
"Comment": "v0.8.0-5-gc605e28",
|
|
||||||
"Rev": "c605e284fe17294bda444b34710735b29d1a9d90"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/pmezard/go-difflib/difflib",
|
|
||||||
"Comment": "v1.0.0",
|
|
||||||
"Rev": "792786c7400a136282c1664665ae0a8db921c6c2"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/rakyll/statik/fs",
|
|
||||||
"Comment": "v0.1.0",
|
|
||||||
"Rev": "274df120e9065bdd08eb1120e0375e3dc1ae8465"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/stretchr/testify/assert",
|
|
||||||
"Comment": "v1.1.4-25-g2402e8e",
|
|
||||||
"Rev": "2402e8e7a02fc811447d11f881aa9746cdc57983"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/vaughan0/go-ini",
|
|
||||||
"Rev": "a98ad7ee00ec53921f08832bc06ecf7fd600e6a1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/xtaci/kcp-go",
|
|
||||||
"Comment": "v3.17",
|
|
||||||
"Rev": "df437e2b8ec365a336200f9d9da53441cf72ed47"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/xtaci/smux",
|
|
||||||
"Comment": "v1.0.5-8-g2de5471",
|
|
||||||
"Rev": "2de5471dfcbc029f5fe1392b83fe784127c4943e"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "golang.org/x/crypto/blowfish",
|
|
||||||
"Rev": "e1a4589e7d3ea14a3352255d04b6f1a418845e5e"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "golang.org/x/crypto/cast5",
|
|
||||||
"Rev": "e1a4589e7d3ea14a3352255d04b6f1a418845e5e"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "golang.org/x/crypto/pbkdf2",
|
|
||||||
"Rev": "e1a4589e7d3ea14a3352255d04b6f1a418845e5e"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "golang.org/x/crypto/salsa20",
|
|
||||||
"Rev": "e1a4589e7d3ea14a3352255d04b6f1a418845e5e"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "golang.org/x/crypto/salsa20/salsa",
|
|
||||||
"Rev": "e1a4589e7d3ea14a3352255d04b6f1a418845e5e"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "golang.org/x/crypto/tea",
|
|
||||||
"Rev": "e1a4589e7d3ea14a3352255d04b6f1a418845e5e"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "golang.org/x/crypto/twofish",
|
|
||||||
"Rev": "e1a4589e7d3ea14a3352255d04b6f1a418845e5e"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "golang.org/x/crypto/xtea",
|
|
||||||
"Rev": "e1a4589e7d3ea14a3352255d04b6f1a418845e5e"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "golang.org/x/net/bpf",
|
|
||||||
"Rev": "e4fa1c5465ad6111f206fc92186b8c83d64adbe1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "golang.org/x/net/context",
|
|
||||||
"Rev": "e4fa1c5465ad6111f206fc92186b8c83d64adbe1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "golang.org/x/net/internal/iana",
|
|
||||||
"Rev": "e4fa1c5465ad6111f206fc92186b8c83d64adbe1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "golang.org/x/net/internal/socket",
|
|
||||||
"Rev": "e4fa1c5465ad6111f206fc92186b8c83d64adbe1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ImportPath": "golang.org/x/net/ipv4",
|
|
||||||
"Rev": "e4fa1c5465ad6111f206fc92186b8c83d64adbe1"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
58
README.md
58
README.md
|
@ -11,6 +11,7 @@ frp is a fast reverse proxy to help you expose a local server behind a NAT or fi
|
||||||
## Table of Contents
|
## Table of Contents
|
||||||
|
|
||||||
<!-- vim-markdown-toc GFM -->
|
<!-- vim-markdown-toc GFM -->
|
||||||
|
|
||||||
* [What can I do with frp?](#what-can-i-do-with-frp)
|
* [What can I do with frp?](#what-can-i-do-with-frp)
|
||||||
* [Status](#status)
|
* [Status](#status)
|
||||||
* [Architecture](#architecture)
|
* [Architecture](#architecture)
|
||||||
|
@ -20,6 +21,7 @@ frp is a fast reverse proxy to help you expose a local server behind a NAT or fi
|
||||||
* [Forward DNS query request](#forward-dns-query-request)
|
* [Forward DNS query request](#forward-dns-query-request)
|
||||||
* [Forward unix domain socket](#forward-unix-domain-socket)
|
* [Forward unix domain socket](#forward-unix-domain-socket)
|
||||||
* [Expose your service in security](#expose-your-service-in-security)
|
* [Expose your service in security](#expose-your-service-in-security)
|
||||||
|
* [P2P Mode](#p2p-mode)
|
||||||
* [Connect website through frpc's network](#connect-website-through-frpcs-network)
|
* [Connect website through frpc's network](#connect-website-through-frpcs-network)
|
||||||
* [Features](#features)
|
* [Features](#features)
|
||||||
* [Configuration File](#configuration-file)
|
* [Configuration File](#configuration-file)
|
||||||
|
@ -184,7 +186,7 @@ However, we can expose a http or https service using frp.
|
||||||
|
|
||||||
5. Send dns query request by dig:
|
5. Send dns query request by dig:
|
||||||
|
|
||||||
`dig @x.x.x.x -p 6000 www.goolge.com`
|
`dig @x.x.x.x -p 6000 www.google.com`
|
||||||
|
|
||||||
### Forward unix domain socket
|
### Forward unix domain socket
|
||||||
|
|
||||||
|
@ -242,9 +244,9 @@ Configure frps same as above.
|
||||||
server_addr = x.x.x.x
|
server_addr = x.x.x.x
|
||||||
server_port = 7000
|
server_port = 7000
|
||||||
|
|
||||||
[secret_ssh_vistor]
|
[secret_ssh_visitor]
|
||||||
type = stcp
|
type = stcp
|
||||||
role = vistor
|
role = visitor
|
||||||
server_name = secret_ssh
|
server_name = secret_ssh
|
||||||
sk = abcdefg
|
sk = abcdefg
|
||||||
bind_addr = 127.0.0.1
|
bind_addr = 127.0.0.1
|
||||||
|
@ -255,6 +257,54 @@ Configure frps same as above.
|
||||||
|
|
||||||
`ssh -oPort=6000 test@127.0.0.1`
|
`ssh -oPort=6000 test@127.0.0.1`
|
||||||
|
|
||||||
|
### P2P Mode
|
||||||
|
|
||||||
|
**xtcp** is designed for transmitting a large amount of data directly between two client.
|
||||||
|
|
||||||
|
Now it can't penetrate all types of NAT devices. You can try **stcp** if **xtcp** doesn't work.
|
||||||
|
|
||||||
|
1. Configure a udp port for xtcp:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
bind_udp_port = 7001
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Start frpc, forward ssh port and `remote_port` is useless:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# frpc.ini
|
||||||
|
[common]
|
||||||
|
server_addr = x.x.x.x
|
||||||
|
server_port = 7000
|
||||||
|
|
||||||
|
[p2p_ssh]
|
||||||
|
type = xtcp
|
||||||
|
sk = abcdefg
|
||||||
|
local_ip = 127.0.0.1
|
||||||
|
local_port = 22
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Start another frpc in which you want to connect this ssh server:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# frpc.ini
|
||||||
|
[common]
|
||||||
|
server_addr = x.x.x.x
|
||||||
|
server_port = 7000
|
||||||
|
|
||||||
|
[p2p_ssh_visitor]
|
||||||
|
type = xtcp
|
||||||
|
role = visitor
|
||||||
|
server_name = p2p_ssh
|
||||||
|
sk = abcdefg
|
||||||
|
bind_addr = 127.0.0.1
|
||||||
|
bind_port = 6000
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Connect to server in LAN by ssh assuming that username is test:
|
||||||
|
|
||||||
|
`ssh -oPort=6000 test@127.0.0.1`
|
||||||
|
|
||||||
### Connect website through frpc's network
|
### Connect website through frpc's network
|
||||||
|
|
||||||
Configure frps same as above.
|
Configure frps same as above.
|
||||||
|
@ -550,7 +600,7 @@ plugin_http_passwd = abc
|
||||||
* Direct reverse proxy, like haproxy.
|
* Direct reverse proxy, like haproxy.
|
||||||
* Load balance to different service in frpc.
|
* Load balance to different service in frpc.
|
||||||
* Frpc can directly be a webserver for static files.
|
* Frpc can directly be a webserver for static files.
|
||||||
* P2p communicate by make udp hole to penetrate NAT.
|
* P2p communicate by making udp hole to penetrate NAT.
|
||||||
* kubernetes ingress support.
|
* kubernetes ingress support.
|
||||||
|
|
||||||
|
|
||||||
|
|
62
README_zh.md
62
README_zh.md
|
@ -9,6 +9,7 @@ frp 是一个可用于内网穿透的高性能的反向代理应用,支持 tcp
|
||||||
## 目录
|
## 目录
|
||||||
|
|
||||||
<!-- vim-markdown-toc GFM -->
|
<!-- vim-markdown-toc GFM -->
|
||||||
|
|
||||||
* [frp 的作用](#frp-的作用)
|
* [frp 的作用](#frp-的作用)
|
||||||
* [开发状态](#开发状态)
|
* [开发状态](#开发状态)
|
||||||
* [架构](#架构)
|
* [架构](#架构)
|
||||||
|
@ -18,6 +19,7 @@ frp 是一个可用于内网穿透的高性能的反向代理应用,支持 tcp
|
||||||
* [转发 DNS 查询请求](#转发-dns-查询请求)
|
* [转发 DNS 查询请求](#转发-dns-查询请求)
|
||||||
* [转发 Unix域套接字](#转发-unix域套接字)
|
* [转发 Unix域套接字](#转发-unix域套接字)
|
||||||
* [安全地暴露内网服务](#安全地暴露内网服务)
|
* [安全地暴露内网服务](#安全地暴露内网服务)
|
||||||
|
* [点对点内网穿透](#点对点内网穿透)
|
||||||
* [通过 frpc 所在机器访问外网](#通过-frpc-所在机器访问外网)
|
* [通过 frpc 所在机器访问外网](#通过-frpc-所在机器访问外网)
|
||||||
* [功能说明](#功能说明)
|
* [功能说明](#功能说明)
|
||||||
* [配置文件](#配置文件)
|
* [配置文件](#配置文件)
|
||||||
|
@ -185,7 +187,7 @@ DNS 查询请求通常使用 UDP 协议,frp 支持对内网 UDP 服务的穿
|
||||||
|
|
||||||
5. 通过 dig 测试 UDP 包转发是否成功,预期会返回 `www.google.com` 域名的解析结果:
|
5. 通过 dig 测试 UDP 包转发是否成功,预期会返回 `www.google.com` 域名的解析结果:
|
||||||
|
|
||||||
`dig @x.x.x.x -p 6000 www.goolge.com`
|
`dig @x.x.x.x -p 6000 www.google.com`
|
||||||
|
|
||||||
### 转发 Unix域套接字
|
### 转发 Unix域套接字
|
||||||
|
|
||||||
|
@ -246,10 +248,10 @@ frps 的部署步骤同上。
|
||||||
server_addr = x.x.x.x
|
server_addr = x.x.x.x
|
||||||
server_port = 7000
|
server_port = 7000
|
||||||
|
|
||||||
[secret_ssh_vistor]
|
[secret_ssh_visitor]
|
||||||
type = stcp
|
type = stcp
|
||||||
# stcp 的访问者
|
# stcp 的访问者
|
||||||
role = vistor
|
role = visitor
|
||||||
# 要访问的 stcp 代理的名字
|
# 要访问的 stcp 代理的名字
|
||||||
server_name = secret_ssh
|
server_name = secret_ssh
|
||||||
sk = abcdefg
|
sk = abcdefg
|
||||||
|
@ -262,6 +264,60 @@ frps 的部署步骤同上。
|
||||||
|
|
||||||
`ssh -oPort=6000 test@127.0.0.1`
|
`ssh -oPort=6000 test@127.0.0.1`
|
||||||
|
|
||||||
|
### 点对点内网穿透
|
||||||
|
|
||||||
|
frp 提供了一种新的代理类型 **xtcp** 用于应对在希望传输大量数据且流量不经过服务器的场景。
|
||||||
|
|
||||||
|
使用方式同 **stcp** 类似,需要在两边都部署上 frpc 用于建立直接的连接。
|
||||||
|
|
||||||
|
目前处于开发的初级阶段,并不能穿透所有类型的 NAT 设备,所以穿透成功率较低。穿透失败时可以尝试 **stcp** 的方式。
|
||||||
|
|
||||||
|
1. frps 除正常配置外需要额外配置一个 udp 端口用于支持该类型的客户端:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
bind_udp_port = 7001
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 启动 frpc,转发内网的 ssh 服务,配置如下,不需要指定远程端口:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# frpc.ini
|
||||||
|
[common]
|
||||||
|
server_addr = x.x.x.x
|
||||||
|
server_port = 7000
|
||||||
|
|
||||||
|
[p2p_ssh]
|
||||||
|
type = xtcp
|
||||||
|
# 只有 sk 一致的用户才能访问到此服务
|
||||||
|
sk = abcdefg
|
||||||
|
local_ip = 127.0.0.1
|
||||||
|
local_port = 22
|
||||||
|
```
|
||||||
|
|
||||||
|
3. 在要访问这个服务的机器上启动另外一个 frpc,配置如下:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# frpc.ini
|
||||||
|
[common]
|
||||||
|
server_addr = x.x.x.x
|
||||||
|
server_port = 7000
|
||||||
|
|
||||||
|
[p2p_ssh_visitor]
|
||||||
|
type = xtcp
|
||||||
|
# xtcp 的访问者
|
||||||
|
role = visitor
|
||||||
|
# 要访问的 xtcp 代理的名字
|
||||||
|
server_name = p2p_ssh
|
||||||
|
sk = abcdefg
|
||||||
|
# 绑定本地端口用于访问 ssh 服务
|
||||||
|
bind_addr = 127.0.0.1
|
||||||
|
bind_port = 6000
|
||||||
|
```
|
||||||
|
|
||||||
|
4. 通过 ssh 访问内网机器,假设用户名为 test:
|
||||||
|
|
||||||
|
`ssh -oPort=6000 test@127.0.0.1`
|
||||||
|
|
||||||
### 通过 frpc 所在机器访问外网
|
### 通过 frpc 所在机器访问外网
|
||||||
|
|
||||||
frpc 内置了 http proxy 和 socks5 插件,可以使其他机器通过 frpc 的网络访问互联网。
|
frpc 内置了 http proxy 和 socks5 插件,可以使其他机器通过 frpc 的网络访问互联网。
|
||||||
|
|
|
@ -64,7 +64,7 @@ func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request, _ httprout
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
pxyCfgs, vistorCfgs, err := config.LoadProxyConfFromFile(config.ClientCommonCfg.User, conf, newCommonCfg.Start)
|
pxyCfgs, visitorCfgs, err := config.LoadProxyConfFromFile(config.ClientCommonCfg.User, conf, newCommonCfg.Start)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
res.Code = 3
|
res.Code = 3
|
||||||
res.Msg = err.Error()
|
res.Msg = err.Error()
|
||||||
|
@ -72,7 +72,7 @@ func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request, _ httprout
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
svr.ctl.reloadConf(pxyCfgs, vistorCfgs)
|
svr.ctl.reloadConf(pxyCfgs, visitorCfgs)
|
||||||
log.Info("success reload conf")
|
log.Info("success reload conf")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,11 +49,11 @@ type Control struct {
|
||||||
// proxies
|
// proxies
|
||||||
proxies map[string]Proxy
|
proxies map[string]Proxy
|
||||||
|
|
||||||
// vistor configures
|
// visitor configures
|
||||||
vistorCfgs map[string]config.ProxyConf
|
visitorCfgs map[string]config.ProxyConf
|
||||||
|
|
||||||
// vistors
|
// visitors
|
||||||
vistors map[string]Vistor
|
visitors map[string]Visitor
|
||||||
|
|
||||||
// control connection
|
// control connection
|
||||||
conn frpNet.Conn
|
conn frpNet.Conn
|
||||||
|
@ -84,7 +84,7 @@ type Control struct {
|
||||||
log.Logger
|
log.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewControl(svr *Service, pxyCfgs map[string]config.ProxyConf, vistorCfgs map[string]config.ProxyConf) *Control {
|
func NewControl(svr *Service, pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.ProxyConf) *Control {
|
||||||
loginMsg := &msg.Login{
|
loginMsg := &msg.Login{
|
||||||
Arch: runtime.GOARCH,
|
Arch: runtime.GOARCH,
|
||||||
Os: runtime.GOOS,
|
Os: runtime.GOOS,
|
||||||
|
@ -96,9 +96,9 @@ func NewControl(svr *Service, pxyCfgs map[string]config.ProxyConf, vistorCfgs ma
|
||||||
svr: svr,
|
svr: svr,
|
||||||
loginMsg: loginMsg,
|
loginMsg: loginMsg,
|
||||||
pxyCfgs: pxyCfgs,
|
pxyCfgs: pxyCfgs,
|
||||||
vistorCfgs: vistorCfgs,
|
visitorCfgs: visitorCfgs,
|
||||||
proxies: make(map[string]Proxy),
|
proxies: make(map[string]Proxy),
|
||||||
vistors: make(map[string]Vistor),
|
visitors: make(map[string]Visitor),
|
||||||
sendCh: make(chan msg.Message, 10),
|
sendCh: make(chan msg.Message, 10),
|
||||||
readCh: make(chan msg.Message, 10),
|
readCh: make(chan msg.Message, 10),
|
||||||
closedCh: make(chan int),
|
closedCh: make(chan int),
|
||||||
|
@ -137,16 +137,16 @@ func (ctl *Control) Run() (err error) {
|
||||||
go ctl.writer()
|
go ctl.writer()
|
||||||
go ctl.reader()
|
go ctl.reader()
|
||||||
|
|
||||||
// start all local vistors
|
// start all local visitors
|
||||||
for _, cfg := range ctl.vistorCfgs {
|
for _, cfg := range ctl.visitorCfgs {
|
||||||
vistor := NewVistor(ctl, cfg)
|
visitor := NewVisitor(ctl, cfg)
|
||||||
err = vistor.Run()
|
err = visitor.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
vistor.Warn("start error: %v", err)
|
visitor.Warn("start error: %v", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
ctl.vistors[cfg.GetName()] = vistor
|
ctl.visitors[cfg.GetName()] = visitor
|
||||||
vistor.Info("start vistor success")
|
visitor.Info("start visitor success")
|
||||||
}
|
}
|
||||||
|
|
||||||
// send NewProxy message for all configured proxies
|
// send NewProxy message for all configured proxies
|
||||||
|
@ -271,9 +271,10 @@ func (ctl *Control) login() (err error) {
|
||||||
ctl.conn = conn
|
ctl.conn = conn
|
||||||
// update runId got from server
|
// update runId got from server
|
||||||
ctl.setRunId(loginRespMsg.RunId)
|
ctl.setRunId(loginRespMsg.RunId)
|
||||||
|
config.ClientCommonCfg.ServerUdpPort = loginRespMsg.ServerUdpPort
|
||||||
ctl.ClearLogPrefix()
|
ctl.ClearLogPrefix()
|
||||||
ctl.AddLogPrefix(loginRespMsg.RunId)
|
ctl.AddLogPrefix(loginRespMsg.RunId)
|
||||||
ctl.Info("login to server success, get run id [%s]", loginRespMsg.RunId)
|
ctl.Info("login to server success, get run id [%s], server udp port [%d]", loginRespMsg.RunId, loginRespMsg.ServerUdpPort)
|
||||||
|
|
||||||
// login success, so we let closedCh available again
|
// login success, so we let closedCh available again
|
||||||
ctl.closedCh = make(chan int)
|
ctl.closedCh = make(chan int)
|
||||||
|
@ -440,17 +441,17 @@ func (ctl *Control) controler() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, cfg := range ctl.vistorCfgs {
|
for _, cfg := range ctl.visitorCfgs {
|
||||||
if _, exist := ctl.vistors[cfg.GetName()]; !exist {
|
if _, exist := ctl.visitors[cfg.GetName()]; !exist {
|
||||||
ctl.Info("try to start vistor [%s]", cfg.GetName())
|
ctl.Info("try to start visitor [%s]", cfg.GetName())
|
||||||
vistor := NewVistor(ctl, cfg)
|
visitor := NewVisitor(ctl, cfg)
|
||||||
err = vistor.Run()
|
err = visitor.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
vistor.Warn("start error: %v", err)
|
visitor.Warn("start error: %v", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
ctl.vistors[cfg.GetName()] = vistor
|
ctl.visitors[cfg.GetName()] = visitor
|
||||||
vistor.Info("start vistor success")
|
visitor.Info("start visitor success")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ctl.mu.RUnlock()
|
ctl.mu.RUnlock()
|
||||||
|
@ -548,7 +549,7 @@ func (ctl *Control) getProxyConf(name string) (conf config.ProxyConf, ok bool) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctl *Control) reloadConf(pxyCfgs map[string]config.ProxyConf, vistorCfgs map[string]config.ProxyConf) {
|
func (ctl *Control) reloadConf(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.ProxyConf) {
|
||||||
ctl.mu.Lock()
|
ctl.mu.Lock()
|
||||||
defer ctl.mu.Unlock()
|
defer ctl.mu.Unlock()
|
||||||
|
|
||||||
|
@ -587,35 +588,35 @@ func (ctl *Control) reloadConf(pxyCfgs map[string]config.ProxyConf, vistorCfgs m
|
||||||
}
|
}
|
||||||
ctl.Info("proxy added: %v", addedPxyNames)
|
ctl.Info("proxy added: %v", addedPxyNames)
|
||||||
|
|
||||||
removedVistorName := make([]string, 0)
|
removedVisitorName := make([]string, 0)
|
||||||
for name, oldVistorCfg := range ctl.vistorCfgs {
|
for name, oldVisitorCfg := range ctl.visitorCfgs {
|
||||||
del := false
|
del := false
|
||||||
cfg, ok := vistorCfgs[name]
|
cfg, ok := visitorCfgs[name]
|
||||||
if !ok {
|
if !ok {
|
||||||
del = true
|
del = true
|
||||||
} else {
|
} else {
|
||||||
if !oldVistorCfg.Compare(cfg) {
|
if !oldVisitorCfg.Compare(cfg) {
|
||||||
del = true
|
del = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if del {
|
if del {
|
||||||
removedVistorName = append(removedVistorName, name)
|
removedVisitorName = append(removedVisitorName, name)
|
||||||
delete(ctl.vistorCfgs, name)
|
delete(ctl.visitorCfgs, name)
|
||||||
if vistor, ok := ctl.vistors[name]; ok {
|
if visitor, ok := ctl.visitors[name]; ok {
|
||||||
vistor.Close()
|
visitor.Close()
|
||||||
}
|
}
|
||||||
delete(ctl.vistors, name)
|
delete(ctl.visitors, name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ctl.Info("vistor removed: %v", removedVistorName)
|
ctl.Info("visitor removed: %v", removedVisitorName)
|
||||||
|
|
||||||
addedVistorName := make([]string, 0)
|
addedVisitorName := make([]string, 0)
|
||||||
for name, vistorCfg := range vistorCfgs {
|
for name, visitorCfg := range visitorCfgs {
|
||||||
if _, ok := ctl.vistorCfgs[name]; !ok {
|
if _, ok := ctl.visitorCfgs[name]; !ok {
|
||||||
ctl.vistorCfgs[name] = vistorCfg
|
ctl.visitorCfgs[name] = visitorCfg
|
||||||
addedVistorName = append(addedVistorName, name)
|
addedVisitorName = append(addedVisitorName, name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ctl.Info("vistor added: %v", addedVistorName)
|
ctl.Info("visitor added: %v", addedVisitorName)
|
||||||
}
|
}
|
||||||
|
|
116
client/proxy.go
116
client/proxy.go
|
@ -15,6 +15,7 @@
|
||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
@ -29,6 +30,7 @@ import (
|
||||||
frpIo "github.com/fatedier/frp/utils/io"
|
frpIo "github.com/fatedier/frp/utils/io"
|
||||||
"github.com/fatedier/frp/utils/log"
|
"github.com/fatedier/frp/utils/log"
|
||||||
frpNet "github.com/fatedier/frp/utils/net"
|
frpNet "github.com/fatedier/frp/utils/net"
|
||||||
|
"github.com/fatedier/frp/utils/pool"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Proxy defines how to deal with work connections for different proxy type.
|
// Proxy defines how to deal with work connections for different proxy type.
|
||||||
|
@ -72,6 +74,11 @@ func NewProxy(ctl *Control, pxyConf config.ProxyConf) (pxy Proxy) {
|
||||||
BaseProxy: baseProxy,
|
BaseProxy: baseProxy,
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
}
|
}
|
||||||
|
case *config.XtcpProxyConf:
|
||||||
|
pxy = &XtcpProxy{
|
||||||
|
BaseProxy: baseProxy,
|
||||||
|
cfg: cfg,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -108,7 +115,8 @@ func (pxy *TcpProxy) Close() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pxy *TcpProxy) InWorkConn(conn frpNet.Conn) {
|
func (pxy *TcpProxy) InWorkConn(conn frpNet.Conn) {
|
||||||
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn)
|
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
|
||||||
|
[]byte(config.ClientCommonCfg.PrivilegeToken))
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTP
|
// HTTP
|
||||||
|
@ -136,7 +144,8 @@ func (pxy *HttpProxy) Close() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pxy *HttpProxy) InWorkConn(conn frpNet.Conn) {
|
func (pxy *HttpProxy) InWorkConn(conn frpNet.Conn) {
|
||||||
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn)
|
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
|
||||||
|
[]byte(config.ClientCommonCfg.PrivilegeToken))
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTPS
|
// HTTPS
|
||||||
|
@ -164,7 +173,8 @@ func (pxy *HttpsProxy) Close() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pxy *HttpsProxy) InWorkConn(conn frpNet.Conn) {
|
func (pxy *HttpsProxy) InWorkConn(conn frpNet.Conn) {
|
||||||
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn)
|
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
|
||||||
|
[]byte(config.ClientCommonCfg.PrivilegeToken))
|
||||||
}
|
}
|
||||||
|
|
||||||
// STCP
|
// STCP
|
||||||
|
@ -192,7 +202,101 @@ func (pxy *StcpProxy) Close() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pxy *StcpProxy) InWorkConn(conn frpNet.Conn) {
|
func (pxy *StcpProxy) InWorkConn(conn frpNet.Conn) {
|
||||||
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn)
|
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
|
||||||
|
[]byte(config.ClientCommonCfg.PrivilegeToken))
|
||||||
|
}
|
||||||
|
|
||||||
|
// XTCP
|
||||||
|
type XtcpProxy struct {
|
||||||
|
BaseProxy
|
||||||
|
|
||||||
|
cfg *config.XtcpProxyConf
|
||||||
|
proxyPlugin plugin.Plugin
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *XtcpProxy) Run() (err error) {
|
||||||
|
if pxy.cfg.Plugin != "" {
|
||||||
|
pxy.proxyPlugin, err = plugin.Create(pxy.cfg.Plugin, pxy.cfg.PluginParams)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *XtcpProxy) Close() {
|
||||||
|
if pxy.proxyPlugin != nil {
|
||||||
|
pxy.proxyPlugin.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *XtcpProxy) InWorkConn(conn frpNet.Conn) {
|
||||||
|
defer conn.Close()
|
||||||
|
var natHoleSidMsg msg.NatHoleSid
|
||||||
|
err := msg.ReadMsgInto(conn, &natHoleSidMsg)
|
||||||
|
if err != nil {
|
||||||
|
pxy.Error("xtcp read from workConn error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
natHoleClientMsg := &msg.NatHoleClient{
|
||||||
|
ProxyName: pxy.cfg.ProxyName,
|
||||||
|
Sid: natHoleSidMsg.Sid,
|
||||||
|
}
|
||||||
|
raddr, _ := net.ResolveUDPAddr("udp",
|
||||||
|
fmt.Sprintf("%s:%d", config.ClientCommonCfg.ServerAddr, config.ClientCommonCfg.ServerUdpPort))
|
||||||
|
clientConn, err := net.DialUDP("udp", nil, raddr)
|
||||||
|
defer clientConn.Close()
|
||||||
|
|
||||||
|
err = msg.WriteMsg(clientConn, natHoleClientMsg)
|
||||||
|
if err != nil {
|
||||||
|
pxy.Error("send natHoleClientMsg to server error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for client address at most 5 seconds.
|
||||||
|
var natHoleRespMsg msg.NatHoleResp
|
||||||
|
clientConn.SetReadDeadline(time.Now().Add(5 * time.Second))
|
||||||
|
|
||||||
|
buf := pool.GetBuf(1024)
|
||||||
|
n, err := clientConn.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
pxy.Error("get natHoleRespMsg error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = msg.ReadMsgInto(bytes.NewReader(buf[:n]), &natHoleRespMsg)
|
||||||
|
if err != nil {
|
||||||
|
pxy.Error("get natHoleRespMsg error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
clientConn.SetReadDeadline(time.Time{})
|
||||||
|
clientConn.Close()
|
||||||
|
pxy.Trace("get natHoleRespMsg, sid [%s], client address [%s]", natHoleRespMsg.Sid, natHoleRespMsg.ClientAddr)
|
||||||
|
|
||||||
|
// Send sid to visitor udp address.
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
laddr, _ := net.ResolveUDPAddr("udp", clientConn.LocalAddr().String())
|
||||||
|
daddr, err := net.ResolveUDPAddr("udp", natHoleRespMsg.VisitorAddr)
|
||||||
|
if err != nil {
|
||||||
|
pxy.Error("resolve visitor udp address error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
lConn, err := net.DialUDP("udp", laddr, daddr)
|
||||||
|
if err != nil {
|
||||||
|
pxy.Error("dial visitor udp address error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
lConn.Write([]byte(natHoleRespMsg.Sid))
|
||||||
|
|
||||||
|
kcpConn, err := frpNet.NewKcpConnFromUdp(lConn, true, natHoleRespMsg.VisitorAddr)
|
||||||
|
if err != nil {
|
||||||
|
pxy.Error("create kcp connection from udp connection error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf,
|
||||||
|
frpNet.WrapConn(kcpConn), []byte(pxy.cfg.Sk))
|
||||||
}
|
}
|
||||||
|
|
||||||
// UDP
|
// UDP
|
||||||
|
@ -302,7 +406,7 @@ func (pxy *UdpProxy) InWorkConn(conn frpNet.Conn) {
|
||||||
|
|
||||||
// Common handler for tcp work connections.
|
// Common handler for tcp work connections.
|
||||||
func HandleTcpWorkConnection(localInfo *config.LocalSvrConf, proxyPlugin plugin.Plugin,
|
func HandleTcpWorkConnection(localInfo *config.LocalSvrConf, proxyPlugin plugin.Plugin,
|
||||||
baseInfo *config.BaseProxyConf, workConn frpNet.Conn) {
|
baseInfo *config.BaseProxyConf, workConn frpNet.Conn, encKey []byte) {
|
||||||
|
|
||||||
var (
|
var (
|
||||||
remote io.ReadWriteCloser
|
remote io.ReadWriteCloser
|
||||||
|
@ -310,7 +414,7 @@ func HandleTcpWorkConnection(localInfo *config.LocalSvrConf, proxyPlugin plugin.
|
||||||
)
|
)
|
||||||
remote = workConn
|
remote = workConn
|
||||||
if baseInfo.UseEncryption {
|
if baseInfo.UseEncryption {
|
||||||
remote, err = frpIo.WithEncryption(remote, []byte(config.ClientCommonCfg.PrivilegeToken))
|
remote, err = frpIo.WithEncryption(remote, encKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
workConn.Error("create encryption stream error: %v", err)
|
workConn.Error("create encryption stream error: %v", err)
|
||||||
return
|
return
|
||||||
|
|
|
@ -26,11 +26,11 @@ type Service struct {
|
||||||
closedCh chan int
|
closedCh chan int
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewService(pxyCfgs map[string]config.ProxyConf, vistorCfgs map[string]config.ProxyConf) (svr *Service) {
|
func NewService(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.ProxyConf) (svr *Service) {
|
||||||
svr = &Service{
|
svr = &Service{
|
||||||
closedCh: make(chan int),
|
closedCh: make(chan int),
|
||||||
}
|
}
|
||||||
ctl := NewControl(svr, pxyCfgs, vistorCfgs)
|
ctl := NewControl(svr, pxyCfgs, visitorCfgs)
|
||||||
svr.ctl = ctl
|
svr.ctl = ctl
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,318 @@
|
||||||
|
// Copyright 2017 fatedier, fatedier@gmail.com
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/net/ipv4"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/models/config"
|
||||||
|
"github.com/fatedier/frp/models/msg"
|
||||||
|
frpIo "github.com/fatedier/frp/utils/io"
|
||||||
|
"github.com/fatedier/frp/utils/log"
|
||||||
|
frpNet "github.com/fatedier/frp/utils/net"
|
||||||
|
"github.com/fatedier/frp/utils/pool"
|
||||||
|
"github.com/fatedier/frp/utils/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Visitor is used for forward traffics from local port tot remote service.
|
||||||
|
type Visitor interface {
|
||||||
|
Run() error
|
||||||
|
Close()
|
||||||
|
log.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewVisitor(ctl *Control, pxyConf config.ProxyConf) (visitor Visitor) {
|
||||||
|
baseVisitor := BaseVisitor{
|
||||||
|
ctl: ctl,
|
||||||
|
Logger: log.NewPrefixLogger(pxyConf.GetName()),
|
||||||
|
}
|
||||||
|
switch cfg := pxyConf.(type) {
|
||||||
|
case *config.StcpProxyConf:
|
||||||
|
visitor = &StcpVisitor{
|
||||||
|
BaseVisitor: baseVisitor,
|
||||||
|
cfg: cfg,
|
||||||
|
}
|
||||||
|
case *config.XtcpProxyConf:
|
||||||
|
visitor = &XtcpVisitor{
|
||||||
|
BaseVisitor: baseVisitor,
|
||||||
|
cfg: cfg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type BaseVisitor struct {
|
||||||
|
ctl *Control
|
||||||
|
l frpNet.Listener
|
||||||
|
closed bool
|
||||||
|
mu sync.RWMutex
|
||||||
|
log.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
type StcpVisitor struct {
|
||||||
|
BaseVisitor
|
||||||
|
|
||||||
|
cfg *config.StcpProxyConf
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *StcpVisitor) Run() (err error) {
|
||||||
|
sv.l, err = frpNet.ListenTcp(sv.cfg.BindAddr, int64(sv.cfg.BindPort))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
go sv.worker()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *StcpVisitor) Close() {
|
||||||
|
sv.l.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *StcpVisitor) worker() {
|
||||||
|
for {
|
||||||
|
conn, err := sv.l.Accept()
|
||||||
|
if err != nil {
|
||||||
|
sv.Warn("stcp local listener closed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
go sv.handleConn(conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *StcpVisitor) handleConn(userConn frpNet.Conn) {
|
||||||
|
defer userConn.Close()
|
||||||
|
|
||||||
|
sv.Debug("get a new stcp user connection")
|
||||||
|
visitorConn, err := sv.ctl.connectServer()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer visitorConn.Close()
|
||||||
|
|
||||||
|
now := time.Now().Unix()
|
||||||
|
newVisitorConnMsg := &msg.NewVisitorConn{
|
||||||
|
ProxyName: sv.cfg.ServerName,
|
||||||
|
SignKey: util.GetAuthKey(sv.cfg.Sk, now),
|
||||||
|
Timestamp: now,
|
||||||
|
UseEncryption: sv.cfg.UseEncryption,
|
||||||
|
UseCompression: sv.cfg.UseCompression,
|
||||||
|
}
|
||||||
|
err = msg.WriteMsg(visitorConn, newVisitorConnMsg)
|
||||||
|
if err != nil {
|
||||||
|
sv.Warn("send newVisitorConnMsg to server error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var newVisitorConnRespMsg msg.NewVisitorConnResp
|
||||||
|
visitorConn.SetReadDeadline(time.Now().Add(10 * time.Second))
|
||||||
|
err = msg.ReadMsgInto(visitorConn, &newVisitorConnRespMsg)
|
||||||
|
if err != nil {
|
||||||
|
sv.Warn("get newVisitorConnRespMsg error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
visitorConn.SetReadDeadline(time.Time{})
|
||||||
|
|
||||||
|
if newVisitorConnRespMsg.Error != "" {
|
||||||
|
sv.Warn("start new visitor connection error: %s", newVisitorConnRespMsg.Error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var remote io.ReadWriteCloser
|
||||||
|
remote = visitorConn
|
||||||
|
if sv.cfg.UseEncryption {
|
||||||
|
remote, err = frpIo.WithEncryption(remote, []byte(sv.cfg.Sk))
|
||||||
|
if err != nil {
|
||||||
|
sv.Error("create encryption stream error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if sv.cfg.UseCompression {
|
||||||
|
remote = frpIo.WithCompression(remote)
|
||||||
|
}
|
||||||
|
|
||||||
|
frpIo.Join(userConn, remote)
|
||||||
|
}
|
||||||
|
|
||||||
|
type XtcpVisitor struct {
|
||||||
|
BaseVisitor
|
||||||
|
|
||||||
|
cfg *config.XtcpProxyConf
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *XtcpVisitor) Run() (err error) {
|
||||||
|
sv.l, err = frpNet.ListenTcp(sv.cfg.BindAddr, int64(sv.cfg.BindPort))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
go sv.worker()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *XtcpVisitor) Close() {
|
||||||
|
sv.l.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *XtcpVisitor) worker() {
|
||||||
|
for {
|
||||||
|
conn, err := sv.l.Accept()
|
||||||
|
if err != nil {
|
||||||
|
sv.Warn("stcp local listener closed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
go sv.handleConn(conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *XtcpVisitor) handleConn(userConn frpNet.Conn) {
|
||||||
|
defer userConn.Close()
|
||||||
|
|
||||||
|
sv.Debug("get a new xtcp user connection")
|
||||||
|
if config.ClientCommonCfg.ServerUdpPort == 0 {
|
||||||
|
sv.Error("xtcp is not supported by server")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
raddr, err := net.ResolveUDPAddr("udp",
|
||||||
|
fmt.Sprintf("%s:%d", config.ClientCommonCfg.ServerAddr, config.ClientCommonCfg.ServerUdpPort))
|
||||||
|
visitorConn, err := net.DialUDP("udp", nil, raddr)
|
||||||
|
defer visitorConn.Close()
|
||||||
|
|
||||||
|
now := time.Now().Unix()
|
||||||
|
natHoleVisitorMsg := &msg.NatHoleVisitor{
|
||||||
|
ProxyName: sv.cfg.ServerName,
|
||||||
|
SignKey: util.GetAuthKey(sv.cfg.Sk, now),
|
||||||
|
Timestamp: now,
|
||||||
|
}
|
||||||
|
err = msg.WriteMsg(visitorConn, natHoleVisitorMsg)
|
||||||
|
if err != nil {
|
||||||
|
sv.Warn("send natHoleVisitorMsg to server error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for client address at most 10 seconds.
|
||||||
|
var natHoleRespMsg msg.NatHoleResp
|
||||||
|
visitorConn.SetReadDeadline(time.Now().Add(10 * time.Second))
|
||||||
|
buf := pool.GetBuf(1024)
|
||||||
|
n, err := visitorConn.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
sv.Warn("get natHoleRespMsg error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = msg.ReadMsgInto(bytes.NewReader(buf[:n]), &natHoleRespMsg)
|
||||||
|
if err != nil {
|
||||||
|
sv.Warn("get natHoleRespMsg error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
visitorConn.SetReadDeadline(time.Time{})
|
||||||
|
pool.PutBuf(buf)
|
||||||
|
|
||||||
|
sv.Trace("get natHoleRespMsg, sid [%s], client address [%s]", natHoleRespMsg.Sid, natHoleRespMsg.ClientAddr)
|
||||||
|
|
||||||
|
// Close visitorConn, so we can use it's local address.
|
||||||
|
visitorConn.Close()
|
||||||
|
|
||||||
|
// Send detect message.
|
||||||
|
array := strings.Split(natHoleRespMsg.ClientAddr, ":")
|
||||||
|
if len(array) <= 1 {
|
||||||
|
sv.Error("get natHoleResp client address error: %s", natHoleRespMsg.ClientAddr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
laddr, _ := net.ResolveUDPAddr("udp", visitorConn.LocalAddr().String())
|
||||||
|
/*
|
||||||
|
for i := 1000; i < 65000; i++ {
|
||||||
|
sv.sendDetectMsg(array[0], int64(i), laddr, "a")
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
port, err := strconv.ParseInt(array[1], 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
sv.Error("get natHoleResp client address error: %s", natHoleRespMsg.ClientAddr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sv.sendDetectMsg(array[0], int64(port), laddr, []byte(natHoleRespMsg.Sid))
|
||||||
|
sv.Trace("send all detect msg done")
|
||||||
|
|
||||||
|
// Listen for visitorConn's address and wait for client connection.
|
||||||
|
lConn, _ := net.ListenUDP("udp", laddr)
|
||||||
|
lConn.SetReadDeadline(time.Now().Add(5 * time.Second))
|
||||||
|
sidBuf := pool.GetBuf(1024)
|
||||||
|
n, _, err = lConn.ReadFromUDP(sidBuf)
|
||||||
|
if err != nil {
|
||||||
|
sv.Warn("get sid from client error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
lConn.SetReadDeadline(time.Time{})
|
||||||
|
if string(sidBuf[:n]) != natHoleRespMsg.Sid {
|
||||||
|
sv.Warn("incorrect sid from client")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sv.Info("nat hole connection make success, sid [%s]", string(sidBuf[:n]))
|
||||||
|
pool.PutBuf(sidBuf)
|
||||||
|
|
||||||
|
var remote io.ReadWriteCloser
|
||||||
|
remote, err = frpNet.NewKcpConnFromUdp(lConn, false, natHoleRespMsg.ClientAddr)
|
||||||
|
if err != nil {
|
||||||
|
sv.Error("create kcp connection from udp connection error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if sv.cfg.UseEncryption {
|
||||||
|
remote, err = frpIo.WithEncryption(remote, []byte(sv.cfg.Sk))
|
||||||
|
if err != nil {
|
||||||
|
sv.Error("create encryption stream error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if sv.cfg.UseCompression {
|
||||||
|
remote = frpIo.WithCompression(remote)
|
||||||
|
}
|
||||||
|
|
||||||
|
frpIo.Join(userConn, remote)
|
||||||
|
sv.Debug("join connections closed")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv *XtcpVisitor) sendDetectMsg(addr string, port int64, laddr *net.UDPAddr, content []byte) (err error) {
|
||||||
|
daddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", addr, port))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
tConn, err := net.DialUDP("udp", laddr, daddr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
uConn := ipv4.NewConn(tConn)
|
||||||
|
uConn.SetTTL(3)
|
||||||
|
|
||||||
|
tConn.Write(content)
|
||||||
|
tConn.Close()
|
||||||
|
return nil
|
||||||
|
}
|
145
client/vistor.go
145
client/vistor.go
|
@ -1,145 +0,0 @@
|
||||||
// Copyright 2017 fatedier, fatedier@gmail.com
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package client
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/fatedier/frp/models/config"
|
|
||||||
"github.com/fatedier/frp/models/msg"
|
|
||||||
frpIo "github.com/fatedier/frp/utils/io"
|
|
||||||
"github.com/fatedier/frp/utils/log"
|
|
||||||
frpNet "github.com/fatedier/frp/utils/net"
|
|
||||||
"github.com/fatedier/frp/utils/util"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Vistor is used for forward traffics from local port tot remote service.
|
|
||||||
type Vistor interface {
|
|
||||||
Run() error
|
|
||||||
Close()
|
|
||||||
log.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewVistor(ctl *Control, pxyConf config.ProxyConf) (vistor Vistor) {
|
|
||||||
baseVistor := BaseVistor{
|
|
||||||
ctl: ctl,
|
|
||||||
Logger: log.NewPrefixLogger(pxyConf.GetName()),
|
|
||||||
}
|
|
||||||
switch cfg := pxyConf.(type) {
|
|
||||||
case *config.StcpProxyConf:
|
|
||||||
vistor = &StcpVistor{
|
|
||||||
BaseVistor: baseVistor,
|
|
||||||
cfg: cfg,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
type BaseVistor struct {
|
|
||||||
ctl *Control
|
|
||||||
l frpNet.Listener
|
|
||||||
closed bool
|
|
||||||
mu sync.RWMutex
|
|
||||||
log.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
type StcpVistor struct {
|
|
||||||
BaseVistor
|
|
||||||
|
|
||||||
cfg *config.StcpProxyConf
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sv *StcpVistor) Run() (err error) {
|
|
||||||
sv.l, err = frpNet.ListenTcp(sv.cfg.BindAddr, int64(sv.cfg.BindPort))
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
go sv.worker()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sv *StcpVistor) Close() {
|
|
||||||
sv.l.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sv *StcpVistor) worker() {
|
|
||||||
for {
|
|
||||||
conn, err := sv.l.Accept()
|
|
||||||
if err != nil {
|
|
||||||
sv.Warn("stcp local listener closed")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
go sv.handleConn(conn)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sv *StcpVistor) handleConn(userConn frpNet.Conn) {
|
|
||||||
defer userConn.Close()
|
|
||||||
|
|
||||||
sv.Debug("get a new stcp user connection")
|
|
||||||
vistorConn, err := sv.ctl.connectServer()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer vistorConn.Close()
|
|
||||||
|
|
||||||
now := time.Now().Unix()
|
|
||||||
newVistorConnMsg := &msg.NewVistorConn{
|
|
||||||
ProxyName: sv.cfg.ServerName,
|
|
||||||
SignKey: util.GetAuthKey(sv.cfg.Sk, now),
|
|
||||||
Timestamp: now,
|
|
||||||
UseEncryption: sv.cfg.UseEncryption,
|
|
||||||
UseCompression: sv.cfg.UseCompression,
|
|
||||||
}
|
|
||||||
err = msg.WriteMsg(vistorConn, newVistorConnMsg)
|
|
||||||
if err != nil {
|
|
||||||
sv.Warn("send newVistorConnMsg to server error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var newVistorConnRespMsg msg.NewVistorConnResp
|
|
||||||
vistorConn.SetReadDeadline(time.Now().Add(10 * time.Second))
|
|
||||||
err = msg.ReadMsgInto(vistorConn, &newVistorConnRespMsg)
|
|
||||||
if err != nil {
|
|
||||||
sv.Warn("get newVistorConnRespMsg error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
vistorConn.SetReadDeadline(time.Time{})
|
|
||||||
|
|
||||||
if newVistorConnRespMsg.Error != "" {
|
|
||||||
sv.Warn("start new vistor connection error: %s", newVistorConnRespMsg.Error)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var remote io.ReadWriteCloser
|
|
||||||
remote = vistorConn
|
|
||||||
if sv.cfg.UseEncryption {
|
|
||||||
remote, err = frpIo.WithEncryption(remote, []byte(sv.cfg.Sk))
|
|
||||||
if err != nil {
|
|
||||||
sv.Error("create encryption stream error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if sv.cfg.UseCompression {
|
|
||||||
remote = frpIo.WithCompression(remote)
|
|
||||||
}
|
|
||||||
|
|
||||||
frpIo.Join(userConn, remote)
|
|
||||||
}
|
|
|
@ -60,7 +60,7 @@ Options:
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
var err error
|
var err error
|
||||||
confFile := "./frps.ini"
|
confFile := "./frpc.ini"
|
||||||
// the configures parsed from file will be replaced by those from command line if exist
|
// the configures parsed from file will be replaced by those from command line if exist
|
||||||
args, err := docopt.Parse(usage, nil, true, version.Full(), false)
|
args, err := docopt.Parse(usage, nil, true, version.Full(), false)
|
||||||
|
|
||||||
|
@ -156,7 +156,7 @@ func main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pxyCfgs, vistorCfgs, err := config.LoadProxyConfFromFile(config.ClientCommonCfg.User, conf, config.ClientCommonCfg.Start)
|
pxyCfgs, visitorCfgs, err := config.LoadProxyConfFromFile(config.ClientCommonCfg.User, conf, config.ClientCommonCfg.Start)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
@ -165,7 +165,7 @@ func main() {
|
||||||
log.InitLog(config.ClientCommonCfg.LogWay, config.ClientCommonCfg.LogFile,
|
log.InitLog(config.ClientCommonCfg.LogWay, config.ClientCommonCfg.LogFile,
|
||||||
config.ClientCommonCfg.LogLevel, config.ClientCommonCfg.LogMaxDays)
|
config.ClientCommonCfg.LogLevel, config.ClientCommonCfg.LogMaxDays)
|
||||||
|
|
||||||
svr := client.NewService(pxyCfgs, vistorCfgs)
|
svr := client.NewService(pxyCfgs, visitorCfgs)
|
||||||
|
|
||||||
// Capture the exit signal if we use kcp.
|
// Capture the exit signal if we use kcp.
|
||||||
if config.ClientCommonCfg.Protocol == "kcp" {
|
if config.ClientCommonCfg.Protocol == "kcp" {
|
||||||
|
|
|
@ -119,25 +119,43 @@ plugin_http_passwd = abc
|
||||||
|
|
||||||
[secret_tcp]
|
[secret_tcp]
|
||||||
# If the type is secret tcp, remote_port is useless
|
# If the type is secret tcp, remote_port is useless
|
||||||
# Who want to connect local port should deploy another frpc with stcp proxy and role is vistor
|
# Who want to connect local port should deploy another frpc with stcp proxy and role is visitor
|
||||||
type = stcp
|
type = stcp
|
||||||
# sk used for authentication for vistors
|
# sk used for authentication for visitors
|
||||||
sk = abcdefg
|
sk = abcdefg
|
||||||
local_ip = 127.0.0.1
|
local_ip = 127.0.0.1
|
||||||
local_port = 22
|
local_port = 22
|
||||||
use_encryption = false
|
use_encryption = false
|
||||||
use_compression = false
|
use_compression = false
|
||||||
|
|
||||||
# user of frpc should be same in both stcp server and stcp vistor
|
# user of frpc should be same in both stcp server and stcp visitor
|
||||||
[secret_tcp_vistor]
|
[secret_tcp_visitor]
|
||||||
# frpc role vistor -> frps -> frpc role server
|
# frpc role visitor -> frps -> frpc role server
|
||||||
role = vistor
|
role = visitor
|
||||||
type = stcp
|
type = stcp
|
||||||
# the server name you want to vistor
|
# the server name you want to visitor
|
||||||
server_name = secret_tcp
|
server_name = secret_tcp
|
||||||
sk = abcdefg
|
sk = abcdefg
|
||||||
# connect this address to vistor stcp server
|
# connect this address to visitor stcp server
|
||||||
bind_addr = 127.0.0.1
|
bind_addr = 127.0.0.1
|
||||||
bind_port = 9000
|
bind_port = 9000
|
||||||
use_encryption = false
|
use_encryption = false
|
||||||
use_compression = false
|
use_compression = false
|
||||||
|
|
||||||
|
[p2p_tcp]
|
||||||
|
type = xtcp
|
||||||
|
sk = abcdefg
|
||||||
|
local_ip = 127.0.0.1
|
||||||
|
local_port = 22
|
||||||
|
use_encryption = false
|
||||||
|
use_compression = false
|
||||||
|
|
||||||
|
[p2p_tcp_visitor]
|
||||||
|
role = visitor
|
||||||
|
type = xtcp
|
||||||
|
server_name = p2p_tcp
|
||||||
|
sk = abcdefg
|
||||||
|
bind_addr = 127.0.0.1
|
||||||
|
bind_port = 9001
|
||||||
|
use_encryption = false
|
||||||
|
use_compression = false
|
||||||
|
|
|
@ -5,6 +5,9 @@
|
||||||
bind_addr = 0.0.0.0
|
bind_addr = 0.0.0.0
|
||||||
bind_port = 7000
|
bind_port = 7000
|
||||||
|
|
||||||
|
# udp port to help make udp hole to penetrate nat
|
||||||
|
bind_udp_port = 7001
|
||||||
|
|
||||||
# udp port used for kcp protocol, it can be same with 'bind_port'
|
# udp port used for kcp protocol, it can be same with 'bind_port'
|
||||||
# if not set, kcp is disabled in frps
|
# if not set, kcp is disabled in frps
|
||||||
kcp_bind_port = 7000
|
kcp_bind_port = 7000
|
||||||
|
@ -16,7 +19,10 @@ kcp_bind_port = 7000
|
||||||
vhost_http_port = 80
|
vhost_http_port = 80
|
||||||
vhost_https_port = 443
|
vhost_https_port = 443
|
||||||
|
|
||||||
# set dashboard_port to view dashboard of frps
|
# set dashboard_addr and dashboard_port to view dashboard of frps
|
||||||
|
# dashboard_addr's default value is same with bind_addr
|
||||||
|
# dashboard is available only if dashboard_port is set
|
||||||
|
dashboard_addr = 0.0.0.0
|
||||||
dashboard_port = 7500
|
dashboard_port = 7500
|
||||||
|
|
||||||
# dashboard user and pwd for basic auth protect, if not set, both default value is admin
|
# dashboard user and pwd for basic auth protect, if not set, both default value is admin
|
||||||
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
hash: 03ff8b71f63e9038c0182a4ef2a55aa9349782f4813c331e2d1f02f3dd15b4f8
|
||||||
|
updated: 2017-11-01T16:16:18.577622991+08:00
|
||||||
|
imports:
|
||||||
|
- name: github.com/armon/go-socks5
|
||||||
|
version: e75332964ef517daa070d7c38a9466a0d687e0a5
|
||||||
|
- name: github.com/davecgh/go-spew
|
||||||
|
version: 346938d642f2ec3594ed81d874461961cd0faa76
|
||||||
|
subpackages:
|
||||||
|
- spew
|
||||||
|
- name: github.com/docopt/docopt-go
|
||||||
|
version: 784ddc588536785e7299f7272f39101f7faccc3f
|
||||||
|
- name: github.com/fatedier/beego
|
||||||
|
version: 6c6a4f5bd5eb5a39f7e289b8f345b55f75e7e3e8
|
||||||
|
subpackages:
|
||||||
|
- logs
|
||||||
|
- name: github.com/fatedier/kcp-go
|
||||||
|
version: cd167d2f15f451b0f33780ce862fca97adc0331e
|
||||||
|
- name: github.com/golang/snappy
|
||||||
|
version: 5979233c5d6225d4a8e438cdd0b411888449ddab
|
||||||
|
- name: github.com/julienschmidt/httprouter
|
||||||
|
version: 8a45e95fc75cb77048068a62daed98cc22fdac7c
|
||||||
|
- name: github.com/klauspost/cpuid
|
||||||
|
version: 09cded8978dc9e80714c4d85b0322337b0a1e5e0
|
||||||
|
- name: github.com/klauspost/reedsolomon
|
||||||
|
version: dde6ad55c5e5a6379a4e82dcca32ee407346eb6d
|
||||||
|
- name: github.com/pkg/errors
|
||||||
|
version: c605e284fe17294bda444b34710735b29d1a9d90
|
||||||
|
- name: github.com/pmezard/go-difflib
|
||||||
|
version: 792786c7400a136282c1664665ae0a8db921c6c2
|
||||||
|
subpackages:
|
||||||
|
- difflib
|
||||||
|
- name: github.com/rakyll/statik
|
||||||
|
version: 274df120e9065bdd08eb1120e0375e3dc1ae8465
|
||||||
|
subpackages:
|
||||||
|
- fs
|
||||||
|
- name: github.com/stretchr/testify
|
||||||
|
version: 2402e8e7a02fc811447d11f881aa9746cdc57983
|
||||||
|
subpackages:
|
||||||
|
- assert
|
||||||
|
- name: github.com/templexxx/cpufeat
|
||||||
|
version: 3794dfbfb04749f896b521032f69383f24c3687e
|
||||||
|
- name: github.com/templexxx/reedsolomon
|
||||||
|
version: 7092926d7d05c415fabb892b1464a03f8228ab80
|
||||||
|
- name: github.com/templexxx/xor
|
||||||
|
version: 0af8e873c554da75f37f2049cdffda804533d44c
|
||||||
|
- name: github.com/tjfoc/gmsm
|
||||||
|
version: 21d76dee237dbbc8dfe1510000b9bf2733635aa1
|
||||||
|
subpackages:
|
||||||
|
- sm4
|
||||||
|
- name: github.com/vaughan0/go-ini
|
||||||
|
version: a98ad7ee00ec53921f08832bc06ecf7fd600e6a1
|
||||||
|
- name: github.com/xtaci/kcp-go
|
||||||
|
version: df437e2b8ec365a336200f9d9da53441cf72ed47
|
||||||
|
- name: github.com/xtaci/smux
|
||||||
|
version: 2de5471dfcbc029f5fe1392b83fe784127c4943e
|
||||||
|
- name: golang.org/x/crypto
|
||||||
|
version: e1a4589e7d3ea14a3352255d04b6f1a418845e5e
|
||||||
|
subpackages:
|
||||||
|
- blowfish
|
||||||
|
- cast5
|
||||||
|
- pbkdf2
|
||||||
|
- salsa20
|
||||||
|
- salsa20/salsa
|
||||||
|
- tea
|
||||||
|
- twofish
|
||||||
|
- xtea
|
||||||
|
- name: golang.org/x/net
|
||||||
|
version: e4fa1c5465ad6111f206fc92186b8c83d64adbe1
|
||||||
|
subpackages:
|
||||||
|
- bpf
|
||||||
|
- context
|
||||||
|
- internal/iana
|
||||||
|
- internal/socket
|
||||||
|
- ipv4
|
||||||
|
testImports: []
|
|
@ -0,0 +1,73 @@
|
||||||
|
package: github.com/fatedier/frp
|
||||||
|
import:
|
||||||
|
- package: github.com/armon/go-socks5
|
||||||
|
version: e75332964ef517daa070d7c38a9466a0d687e0a5
|
||||||
|
- package: github.com/davecgh/go-spew
|
||||||
|
version: v1.1.0
|
||||||
|
subpackages:
|
||||||
|
- spew
|
||||||
|
- package: github.com/docopt/docopt-go
|
||||||
|
version: 0.6.2
|
||||||
|
- package: github.com/fatedier/beego
|
||||||
|
version: 6c6a4f5bd5eb5a39f7e289b8f345b55f75e7e3e8
|
||||||
|
subpackages:
|
||||||
|
- logs
|
||||||
|
- package: github.com/fatedier/kcp-go
|
||||||
|
version: cd167d2f15f451b0f33780ce862fca97adc0331e
|
||||||
|
- package: github.com/golang/snappy
|
||||||
|
version: 5979233c5d6225d4a8e438cdd0b411888449ddab
|
||||||
|
- package: github.com/julienschmidt/httprouter
|
||||||
|
version: 8a45e95fc75cb77048068a62daed98cc22fdac7c
|
||||||
|
- package: github.com/klauspost/cpuid
|
||||||
|
version: v1.0
|
||||||
|
- package: github.com/klauspost/reedsolomon
|
||||||
|
version: dde6ad55c5e5a6379a4e82dcca32ee407346eb6d
|
||||||
|
- package: github.com/pkg/errors
|
||||||
|
version: c605e284fe17294bda444b34710735b29d1a9d90
|
||||||
|
- package: github.com/pmezard/go-difflib
|
||||||
|
version: v1.0.0
|
||||||
|
subpackages:
|
||||||
|
- difflib
|
||||||
|
- package: github.com/rakyll/statik
|
||||||
|
version: v0.1.0
|
||||||
|
subpackages:
|
||||||
|
- fs
|
||||||
|
- package: github.com/stretchr/testify
|
||||||
|
version: 2402e8e7a02fc811447d11f881aa9746cdc57983
|
||||||
|
subpackages:
|
||||||
|
- assert
|
||||||
|
- package: github.com/templexxx/cpufeat
|
||||||
|
version: 3794dfbfb04749f896b521032f69383f24c3687e
|
||||||
|
- package: github.com/templexxx/reedsolomon
|
||||||
|
version: 7092926d7d05c415fabb892b1464a03f8228ab80
|
||||||
|
- package: github.com/templexxx/xor
|
||||||
|
version: 0.1.2
|
||||||
|
- package: github.com/tjfoc/gmsm
|
||||||
|
version: 21d76dee237dbbc8dfe1510000b9bf2733635aa1
|
||||||
|
subpackages:
|
||||||
|
- sm4
|
||||||
|
- package: github.com/vaughan0/go-ini
|
||||||
|
version: a98ad7ee00ec53921f08832bc06ecf7fd600e6a1
|
||||||
|
- package: github.com/xtaci/kcp-go
|
||||||
|
version: v3.17
|
||||||
|
- package: github.com/xtaci/smux
|
||||||
|
version: 2de5471dfcbc029f5fe1392b83fe784127c4943e
|
||||||
|
- package: golang.org/x/crypto
|
||||||
|
version: e1a4589e7d3ea14a3352255d04b6f1a418845e5e
|
||||||
|
subpackages:
|
||||||
|
- blowfish
|
||||||
|
- cast5
|
||||||
|
- pbkdf2
|
||||||
|
- salsa20
|
||||||
|
- salsa20/salsa
|
||||||
|
- tea
|
||||||
|
- twofish
|
||||||
|
- xtea
|
||||||
|
- package: golang.org/x/net
|
||||||
|
version: e4fa1c5465ad6111f206fc92186b8c83d64adbe1
|
||||||
|
subpackages:
|
||||||
|
- bpf
|
||||||
|
- context
|
||||||
|
- internal/iana
|
||||||
|
- internal/socket
|
||||||
|
- ipv4
|
|
@ -30,6 +30,7 @@ type ClientCommonConf struct {
|
||||||
ConfigFile string
|
ConfigFile string
|
||||||
ServerAddr string
|
ServerAddr string
|
||||||
ServerPort int64
|
ServerPort int64
|
||||||
|
ServerUdpPort int64 // this is specified by login response message from frps
|
||||||
HttpProxy string
|
HttpProxy string
|
||||||
LogFile string
|
LogFile string
|
||||||
LogWay string
|
LogWay string
|
||||||
|
@ -55,6 +56,7 @@ func GetDeaultClientCommonConf() *ClientCommonConf {
|
||||||
ConfigFile: "./frpc.ini",
|
ConfigFile: "./frpc.ini",
|
||||||
ServerAddr: "0.0.0.0",
|
ServerAddr: "0.0.0.0",
|
||||||
ServerPort: 7000,
|
ServerPort: 7000,
|
||||||
|
ServerUdpPort: 0,
|
||||||
HttpProxy: "",
|
HttpProxy: "",
|
||||||
LogFile: "console",
|
LogFile: "console",
|
||||||
LogWay: "console",
|
LogWay: "console",
|
||||||
|
|
|
@ -36,6 +36,7 @@ func init() {
|
||||||
proxyConfTypeMap[consts.HttpProxy] = reflect.TypeOf(HttpProxyConf{})
|
proxyConfTypeMap[consts.HttpProxy] = reflect.TypeOf(HttpProxyConf{})
|
||||||
proxyConfTypeMap[consts.HttpsProxy] = reflect.TypeOf(HttpsProxyConf{})
|
proxyConfTypeMap[consts.HttpsProxy] = reflect.TypeOf(HttpsProxyConf{})
|
||||||
proxyConfTypeMap[consts.StcpProxy] = reflect.TypeOf(StcpProxyConf{})
|
proxyConfTypeMap[consts.StcpProxy] = reflect.TypeOf(StcpProxyConf{})
|
||||||
|
proxyConfTypeMap[consts.XtcpProxy] = reflect.TypeOf(XtcpProxyConf{})
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewConfByType creates a empty ProxyConf object by proxyType.
|
// NewConfByType creates a empty ProxyConf object by proxyType.
|
||||||
|
@ -594,7 +595,7 @@ type StcpProxyConf struct {
|
||||||
LocalSvrConf
|
LocalSvrConf
|
||||||
PluginConf
|
PluginConf
|
||||||
|
|
||||||
// used in role vistor
|
// used in role visitor
|
||||||
ServerName string `json:"server_name"`
|
ServerName string `json:"server_name"`
|
||||||
BindAddr string `json:"bind_addr"`
|
BindAddr string `json:"bind_addr"`
|
||||||
BindPort int `json:"bind_port"`
|
BindPort int `json:"bind_port"`
|
||||||
|
@ -631,7 +632,7 @@ func (cfg *StcpProxyConf) LoadFromFile(name string, section ini.Section) (err er
|
||||||
}
|
}
|
||||||
|
|
||||||
tmpStr := section["role"]
|
tmpStr := section["role"]
|
||||||
if tmpStr == "server" || tmpStr == "vistor" {
|
if tmpStr == "server" || tmpStr == "visitor" {
|
||||||
cfg.Role = tmpStr
|
cfg.Role = tmpStr
|
||||||
} else {
|
} else {
|
||||||
cfg.Role = "server"
|
cfg.Role = "server"
|
||||||
|
@ -639,7 +640,7 @@ func (cfg *StcpProxyConf) LoadFromFile(name string, section ini.Section) (err er
|
||||||
|
|
||||||
cfg.Sk = section["sk"]
|
cfg.Sk = section["sk"]
|
||||||
|
|
||||||
if tmpStr == "vistor" {
|
if tmpStr == "visitor" {
|
||||||
prefix := section["prefix"]
|
prefix := section["prefix"]
|
||||||
cfg.ServerName = prefix + section["server_name"]
|
cfg.ServerName = prefix + section["server_name"]
|
||||||
if cfg.BindAddr = section["bind_addr"]; cfg.BindAddr == "" {
|
if cfg.BindAddr = section["bind_addr"]; cfg.BindAddr == "" {
|
||||||
|
@ -672,10 +673,99 @@ func (cfg *StcpProxyConf) Check() (err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// XTCP
|
||||||
|
type XtcpProxyConf struct {
|
||||||
|
BaseProxyConf
|
||||||
|
|
||||||
|
Role string `json:"role"`
|
||||||
|
Sk string `json:"sk"`
|
||||||
|
|
||||||
|
// used in role server
|
||||||
|
LocalSvrConf
|
||||||
|
PluginConf
|
||||||
|
|
||||||
|
// used in role visitor
|
||||||
|
ServerName string `json:"server_name"`
|
||||||
|
BindAddr string `json:"bind_addr"`
|
||||||
|
BindPort int `json:"bind_port"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *XtcpProxyConf) Compare(cmp ProxyConf) bool {
|
||||||
|
cmpConf, ok := cmp.(*XtcpProxyConf)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !cfg.BaseProxyConf.compare(&cmpConf.BaseProxyConf) ||
|
||||||
|
!cfg.LocalSvrConf.compare(&cmpConf.LocalSvrConf) ||
|
||||||
|
!cfg.PluginConf.compare(&cmpConf.PluginConf) ||
|
||||||
|
cfg.Role != cmpConf.Role ||
|
||||||
|
cfg.Sk != cmpConf.Sk ||
|
||||||
|
cfg.ServerName != cmpConf.ServerName ||
|
||||||
|
cfg.BindAddr != cmpConf.BindAddr ||
|
||||||
|
cfg.BindPort != cmpConf.BindPort {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only for role server.
|
||||||
|
func (cfg *XtcpProxyConf) LoadFromMsg(pMsg *msg.NewProxy) {
|
||||||
|
cfg.BaseProxyConf.LoadFromMsg(pMsg)
|
||||||
|
cfg.Sk = pMsg.Sk
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *XtcpProxyConf) LoadFromFile(name string, section ini.Section) (err error) {
|
||||||
|
if err = cfg.BaseProxyConf.LoadFromFile(name, section); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpStr := section["role"]
|
||||||
|
if tmpStr == "server" || tmpStr == "visitor" {
|
||||||
|
cfg.Role = tmpStr
|
||||||
|
} else {
|
||||||
|
cfg.Role = "server"
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg.Sk = section["sk"]
|
||||||
|
|
||||||
|
if tmpStr == "visitor" {
|
||||||
|
prefix := section["prefix"]
|
||||||
|
cfg.ServerName = prefix + section["server_name"]
|
||||||
|
if cfg.BindAddr = section["bind_addr"]; cfg.BindAddr == "" {
|
||||||
|
cfg.BindAddr = "127.0.0.1"
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmpStr, ok := section["bind_port"]; ok {
|
||||||
|
if cfg.BindPort, err = strconv.Atoi(tmpStr); err != nil {
|
||||||
|
return fmt.Errorf("Parse conf error: proxy [%s] bind_port error", name)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("Parse conf error: proxy [%s] bind_port not found", name)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err = cfg.PluginConf.LoadFromFile(name, section); err != nil {
|
||||||
|
if err = cfg.LocalSvrConf.LoadFromFile(name, section); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *XtcpProxyConf) UnMarshalToMsg(pMsg *msg.NewProxy) {
|
||||||
|
cfg.BaseProxyConf.UnMarshalToMsg(pMsg)
|
||||||
|
pMsg.Sk = cfg.Sk
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *XtcpProxyConf) Check() (err error) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// if len(startProxy) is 0, start all
|
// if len(startProxy) is 0, start all
|
||||||
// otherwise just start proxies in startProxy map
|
// otherwise just start proxies in startProxy map
|
||||||
func LoadProxyConfFromFile(prefix string, conf ini.File, startProxy map[string]struct{}) (
|
func LoadProxyConfFromFile(prefix string, conf ini.File, startProxy map[string]struct{}) (
|
||||||
proxyConfs map[string]ProxyConf, vistorConfs map[string]ProxyConf, err error) {
|
proxyConfs map[string]ProxyConf, visitorConfs map[string]ProxyConf, err error) {
|
||||||
|
|
||||||
if prefix != "" {
|
if prefix != "" {
|
||||||
prefix += "."
|
prefix += "."
|
||||||
|
@ -686,7 +776,7 @@ func LoadProxyConfFromFile(prefix string, conf ini.File, startProxy map[string]s
|
||||||
startAll = false
|
startAll = false
|
||||||
}
|
}
|
||||||
proxyConfs = make(map[string]ProxyConf)
|
proxyConfs = make(map[string]ProxyConf)
|
||||||
vistorConfs = make(map[string]ProxyConf)
|
visitorConfs = make(map[string]ProxyConf)
|
||||||
for name, section := range conf {
|
for name, section := range conf {
|
||||||
_, shouldStart := startProxy[name]
|
_, shouldStart := startProxy[name]
|
||||||
if name != "common" && (startAll || shouldStart) {
|
if name != "common" && (startAll || shouldStart) {
|
||||||
|
@ -694,12 +784,12 @@ func LoadProxyConfFromFile(prefix string, conf ini.File, startProxy map[string]s
|
||||||
section["prefix"] = prefix
|
section["prefix"] = prefix
|
||||||
cfg, err := NewProxyConfFromFile(name, section)
|
cfg, err := NewProxyConfFromFile(name, section)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return proxyConfs, vistorConfs, err
|
return proxyConfs, visitorConfs, err
|
||||||
}
|
}
|
||||||
|
|
||||||
role := section["role"]
|
role := section["role"]
|
||||||
if role == "vistor" {
|
if role == "visitor" {
|
||||||
vistorConfs[prefix+name] = cfg
|
visitorConfs[prefix+name] = cfg
|
||||||
} else {
|
} else {
|
||||||
proxyConfs[prefix+name] = cfg
|
proxyConfs[prefix+name] = cfg
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,7 @@ type ServerCommonConf struct {
|
||||||
ConfigFile string
|
ConfigFile string
|
||||||
BindAddr string
|
BindAddr string
|
||||||
BindPort int64
|
BindPort int64
|
||||||
|
BindUdpPort int64
|
||||||
KcpBindPort int64
|
KcpBindPort int64
|
||||||
ProxyBindAddr string
|
ProxyBindAddr string
|
||||||
|
|
||||||
|
@ -38,6 +39,7 @@ type ServerCommonConf struct {
|
||||||
|
|
||||||
// if VhostHttpsPort equals 0, don't listen a public port for https protocol
|
// if VhostHttpsPort equals 0, don't listen a public port for https protocol
|
||||||
VhostHttpsPort int64
|
VhostHttpsPort int64
|
||||||
|
DashboardAddr string
|
||||||
|
|
||||||
// if DashboardPort equals 0, dashboard is not available
|
// if DashboardPort equals 0, dashboard is not available
|
||||||
DashboardPort int64
|
DashboardPort int64
|
||||||
|
@ -66,10 +68,12 @@ func GetDefaultServerCommonConf() *ServerCommonConf {
|
||||||
ConfigFile: "./frps.ini",
|
ConfigFile: "./frps.ini",
|
||||||
BindAddr: "0.0.0.0",
|
BindAddr: "0.0.0.0",
|
||||||
BindPort: 7000,
|
BindPort: 7000,
|
||||||
|
BindUdpPort: 0,
|
||||||
KcpBindPort: 0,
|
KcpBindPort: 0,
|
||||||
ProxyBindAddr: "0.0.0.0",
|
ProxyBindAddr: "0.0.0.0",
|
||||||
VhostHttpPort: 0,
|
VhostHttpPort: 0,
|
||||||
VhostHttpsPort: 0,
|
VhostHttpsPort: 0,
|
||||||
|
DashboardAddr: "0.0.0.0",
|
||||||
DashboardPort: 0,
|
DashboardPort: 0,
|
||||||
DashboardUser: "admin",
|
DashboardUser: "admin",
|
||||||
DashboardPwd: "admin",
|
DashboardPwd: "admin",
|
||||||
|
@ -111,6 +115,14 @@ func LoadServerCommonConf(conf ini.File) (cfg *ServerCommonConf, err error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tmpStr, ok = conf.Get("common", "bind_udp_port")
|
||||||
|
if ok {
|
||||||
|
v, err = strconv.ParseInt(tmpStr, 10, 64)
|
||||||
|
if err == nil {
|
||||||
|
cfg.BindUdpPort = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
tmpStr, ok = conf.Get("common", "kcp_bind_port")
|
tmpStr, ok = conf.Get("common", "kcp_bind_port")
|
||||||
if ok {
|
if ok {
|
||||||
v, err = strconv.ParseInt(tmpStr, 10, 64)
|
v, err = strconv.ParseInt(tmpStr, 10, 64)
|
||||||
|
@ -148,6 +160,13 @@ func LoadServerCommonConf(conf ini.File) (cfg *ServerCommonConf, err error) {
|
||||||
cfg.VhostHttpsPort = 0
|
cfg.VhostHttpsPort = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tmpStr, ok = conf.Get("common", "dashboard_addr")
|
||||||
|
if ok {
|
||||||
|
cfg.DashboardAddr = tmpStr
|
||||||
|
} else {
|
||||||
|
cfg.DashboardAddr = cfg.BindAddr
|
||||||
|
}
|
||||||
|
|
||||||
tmpStr, ok = conf.Get("common", "dashboard_port")
|
tmpStr, ok = conf.Get("common", "dashboard_port")
|
||||||
if ok {
|
if ok {
|
||||||
cfg.DashboardPort, err = strconv.ParseInt(tmpStr, 10, 64)
|
cfg.DashboardPort, err = strconv.ParseInt(tmpStr, 10, 64)
|
||||||
|
|
|
@ -28,4 +28,5 @@ var (
|
||||||
HttpProxy string = "http"
|
HttpProxy string = "http"
|
||||||
HttpsProxy string = "https"
|
HttpsProxy string = "https"
|
||||||
StcpProxy string = "stcp"
|
StcpProxy string = "stcp"
|
||||||
|
XtcpProxy string = "xtcp"
|
||||||
)
|
)
|
||||||
|
|
|
@ -28,11 +28,15 @@ const (
|
||||||
TypeNewWorkConn = 'w'
|
TypeNewWorkConn = 'w'
|
||||||
TypeReqWorkConn = 'r'
|
TypeReqWorkConn = 'r'
|
||||||
TypeStartWorkConn = 's'
|
TypeStartWorkConn = 's'
|
||||||
TypeNewVistorConn = 'v'
|
TypeNewVisitorConn = 'v'
|
||||||
TypeNewVistorConnResp = '3'
|
TypeNewVisitorConnResp = '3'
|
||||||
TypePing = 'h'
|
TypePing = 'h'
|
||||||
TypePong = '4'
|
TypePong = '4'
|
||||||
TypeUdpPacket = 'u'
|
TypeUdpPacket = 'u'
|
||||||
|
TypeNatHoleVisitor = 'i'
|
||||||
|
TypeNatHoleClient = 'n'
|
||||||
|
TypeNatHoleResp = 'm'
|
||||||
|
TypeNatHoleSid = '5'
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -52,11 +56,15 @@ func init() {
|
||||||
TypeMap[TypeNewWorkConn] = reflect.TypeOf(NewWorkConn{})
|
TypeMap[TypeNewWorkConn] = reflect.TypeOf(NewWorkConn{})
|
||||||
TypeMap[TypeReqWorkConn] = reflect.TypeOf(ReqWorkConn{})
|
TypeMap[TypeReqWorkConn] = reflect.TypeOf(ReqWorkConn{})
|
||||||
TypeMap[TypeStartWorkConn] = reflect.TypeOf(StartWorkConn{})
|
TypeMap[TypeStartWorkConn] = reflect.TypeOf(StartWorkConn{})
|
||||||
TypeMap[TypeNewVistorConn] = reflect.TypeOf(NewVistorConn{})
|
TypeMap[TypeNewVisitorConn] = reflect.TypeOf(NewVisitorConn{})
|
||||||
TypeMap[TypeNewVistorConnResp] = reflect.TypeOf(NewVistorConnResp{})
|
TypeMap[TypeNewVisitorConnResp] = reflect.TypeOf(NewVisitorConnResp{})
|
||||||
TypeMap[TypePing] = reflect.TypeOf(Ping{})
|
TypeMap[TypePing] = reflect.TypeOf(Ping{})
|
||||||
TypeMap[TypePong] = reflect.TypeOf(Pong{})
|
TypeMap[TypePong] = reflect.TypeOf(Pong{})
|
||||||
TypeMap[TypeUdpPacket] = reflect.TypeOf(UdpPacket{})
|
TypeMap[TypeUdpPacket] = reflect.TypeOf(UdpPacket{})
|
||||||
|
TypeMap[TypeNatHoleVisitor] = reflect.TypeOf(NatHoleVisitor{})
|
||||||
|
TypeMap[TypeNatHoleClient] = reflect.TypeOf(NatHoleClient{})
|
||||||
|
TypeMap[TypeNatHoleResp] = reflect.TypeOf(NatHoleResp{})
|
||||||
|
TypeMap[TypeNatHoleSid] = reflect.TypeOf(NatHoleSid{})
|
||||||
|
|
||||||
for k, v := range TypeMap {
|
for k, v := range TypeMap {
|
||||||
TypeStringMap[v] = k
|
TypeStringMap[v] = k
|
||||||
|
@ -84,6 +92,7 @@ type Login struct {
|
||||||
type LoginResp struct {
|
type LoginResp struct {
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
RunId string `json:"run_id"`
|
RunId string `json:"run_id"`
|
||||||
|
ServerUdpPort int64 `json:"server_udp_port"`
|
||||||
Error string `json:"error"`
|
Error string `json:"error"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,7 +138,7 @@ type StartWorkConn struct {
|
||||||
ProxyName string `json:"proxy_name"`
|
ProxyName string `json:"proxy_name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type NewVistorConn struct {
|
type NewVisitorConn struct {
|
||||||
ProxyName string `json:"proxy_name"`
|
ProxyName string `json:"proxy_name"`
|
||||||
SignKey string `json:"sign_key"`
|
SignKey string `json:"sign_key"`
|
||||||
Timestamp int64 `json:"timestamp"`
|
Timestamp int64 `json:"timestamp"`
|
||||||
|
@ -137,7 +146,7 @@ type NewVistorConn struct {
|
||||||
UseCompression bool `json:"use_compression"`
|
UseCompression bool `json:"use_compression"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type NewVistorConnResp struct {
|
type NewVisitorConnResp struct {
|
||||||
ProxyName string `json:"proxy_name"`
|
ProxyName string `json:"proxy_name"`
|
||||||
Error string `json:"error"`
|
Error string `json:"error"`
|
||||||
}
|
}
|
||||||
|
@ -153,3 +162,24 @@ type UdpPacket struct {
|
||||||
LocalAddr *net.UDPAddr `json:"l"`
|
LocalAddr *net.UDPAddr `json:"l"`
|
||||||
RemoteAddr *net.UDPAddr `json:"r"`
|
RemoteAddr *net.UDPAddr `json:"r"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type NatHoleVisitor struct {
|
||||||
|
ProxyName string `json:"proxy_name"`
|
||||||
|
SignKey string `json:"sign_key"`
|
||||||
|
Timestamp int64 `json:"timestamp"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type NatHoleClient struct {
|
||||||
|
ProxyName string `json:"proxy_name"`
|
||||||
|
Sid string `json:"sid"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type NatHoleResp struct {
|
||||||
|
Sid string `json:"sid"`
|
||||||
|
VisitorAddr string `json:"visitor_addr"`
|
||||||
|
ClientAddr string `json:"client_addr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type NatHoleSid struct {
|
||||||
|
Sid string `json;"sid"`
|
||||||
|
}
|
||||||
|
|
|
@ -99,6 +99,7 @@ func (ctl *Control) Start() {
|
||||||
loginRespMsg := &msg.LoginResp{
|
loginRespMsg := &msg.LoginResp{
|
||||||
Version: version.Full(),
|
Version: version.Full(),
|
||||||
RunId: ctl.runId,
|
RunId: ctl.runId,
|
||||||
|
ServerUdpPort: config.ServerCommonCfg.BindUdpPort,
|
||||||
Error: "",
|
Error: "",
|
||||||
}
|
}
|
||||||
msg.WriteMsg(ctl.conn, loginRespMsg)
|
msg.WriteMsg(ctl.conn, loginRespMsg)
|
||||||
|
|
|
@ -93,46 +93,46 @@ func (pm *ProxyManager) GetByName(name string) (pxy Proxy, ok bool) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Manager for vistor listeners.
|
// Manager for visitor listeners.
|
||||||
type VistorManager struct {
|
type VisitorManager struct {
|
||||||
vistorListeners map[string]*frpNet.CustomListener
|
visitorListeners map[string]*frpNet.CustomListener
|
||||||
skMap map[string]string
|
skMap map[string]string
|
||||||
|
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewVistorManager() *VistorManager {
|
func NewVisitorManager() *VisitorManager {
|
||||||
return &VistorManager{
|
return &VisitorManager{
|
||||||
vistorListeners: make(map[string]*frpNet.CustomListener),
|
visitorListeners: make(map[string]*frpNet.CustomListener),
|
||||||
skMap: make(map[string]string),
|
skMap: make(map[string]string),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (vm *VistorManager) Listen(name string, sk string) (l *frpNet.CustomListener, err error) {
|
func (vm *VisitorManager) Listen(name string, sk string) (l *frpNet.CustomListener, err error) {
|
||||||
vm.mu.Lock()
|
vm.mu.Lock()
|
||||||
defer vm.mu.Unlock()
|
defer vm.mu.Unlock()
|
||||||
|
|
||||||
if _, ok := vm.vistorListeners[name]; ok {
|
if _, ok := vm.visitorListeners[name]; ok {
|
||||||
err = fmt.Errorf("custom listener for [%s] is repeated", name)
|
err = fmt.Errorf("custom listener for [%s] is repeated", name)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
l = frpNet.NewCustomListener()
|
l = frpNet.NewCustomListener()
|
||||||
vm.vistorListeners[name] = l
|
vm.visitorListeners[name] = l
|
||||||
vm.skMap[name] = sk
|
vm.skMap[name] = sk
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (vm *VistorManager) NewConn(name string, conn frpNet.Conn, timestamp int64, signKey string,
|
func (vm *VisitorManager) NewConn(name string, conn frpNet.Conn, timestamp int64, signKey string,
|
||||||
useEncryption bool, useCompression bool) (err error) {
|
useEncryption bool, useCompression bool) (err error) {
|
||||||
|
|
||||||
vm.mu.RLock()
|
vm.mu.RLock()
|
||||||
defer vm.mu.RUnlock()
|
defer vm.mu.RUnlock()
|
||||||
|
|
||||||
if l, ok := vm.vistorListeners[name]; ok {
|
if l, ok := vm.visitorListeners[name]; ok {
|
||||||
var sk string
|
var sk string
|
||||||
if sk = vm.skMap[name]; util.GetAuthKey(sk, timestamp) != signKey {
|
if sk = vm.skMap[name]; util.GetAuthKey(sk, timestamp) != signKey {
|
||||||
err = fmt.Errorf("vistor connection of [%s] auth failed", name)
|
err = fmt.Errorf("visitor connection of [%s] auth failed", name)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -154,10 +154,10 @@ func (vm *VistorManager) NewConn(name string, conn frpNet.Conn, timestamp int64,
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (vm *VistorManager) CloseListener(name string) {
|
func (vm *VisitorManager) CloseListener(name string) {
|
||||||
vm.mu.Lock()
|
vm.mu.Lock()
|
||||||
defer vm.mu.Unlock()
|
defer vm.mu.Unlock()
|
||||||
|
|
||||||
delete(vm.vistorListeners, name)
|
delete(vm.visitorListeners, name)
|
||||||
delete(vm.skMap, name)
|
delete(vm.skMap, name)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,182 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/models/msg"
|
||||||
|
"github.com/fatedier/frp/utils/errors"
|
||||||
|
"github.com/fatedier/frp/utils/log"
|
||||||
|
"github.com/fatedier/frp/utils/pool"
|
||||||
|
"github.com/fatedier/frp/utils/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Timeout seconds.
|
||||||
|
var NatHoleTimeout int64 = 10
|
||||||
|
|
||||||
|
type NatHoleController struct {
|
||||||
|
listener *net.UDPConn
|
||||||
|
|
||||||
|
clientCfgs map[string]*NatHoleClientCfg
|
||||||
|
sessions map[string]*NatHoleSession
|
||||||
|
|
||||||
|
mu sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewNatHoleController(udpBindAddr string) (nc *NatHoleController, err error) {
|
||||||
|
addr, err := net.ResolveUDPAddr("udp", udpBindAddr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
lconn, err := net.ListenUDP("udp", addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
nc = &NatHoleController{
|
||||||
|
listener: lconn,
|
||||||
|
clientCfgs: make(map[string]*NatHoleClientCfg),
|
||||||
|
sessions: make(map[string]*NatHoleSession),
|
||||||
|
}
|
||||||
|
return nc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nc *NatHoleController) ListenClient(name string, sk string) (sidCh chan string) {
|
||||||
|
clientCfg := &NatHoleClientCfg{
|
||||||
|
Name: name,
|
||||||
|
Sk: sk,
|
||||||
|
SidCh: make(chan string),
|
||||||
|
}
|
||||||
|
nc.mu.Lock()
|
||||||
|
nc.clientCfgs[name] = clientCfg
|
||||||
|
nc.mu.Unlock()
|
||||||
|
return clientCfg.SidCh
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nc *NatHoleController) CloseClient(name string) {
|
||||||
|
nc.mu.Lock()
|
||||||
|
defer nc.mu.Unlock()
|
||||||
|
delete(nc.clientCfgs, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nc *NatHoleController) Run() {
|
||||||
|
for {
|
||||||
|
buf := pool.GetBuf(1024)
|
||||||
|
n, raddr, err := nc.listener.ReadFromUDP(buf)
|
||||||
|
if err != nil {
|
||||||
|
log.Trace("nat hole listener read from udp error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rd := bytes.NewReader(buf[:n])
|
||||||
|
rawMsg, err := msg.ReadMsg(rd)
|
||||||
|
if err != nil {
|
||||||
|
log.Trace("read nat hole message error: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch m := rawMsg.(type) {
|
||||||
|
case *msg.NatHoleVisitor:
|
||||||
|
go nc.HandleVisitor(m, raddr)
|
||||||
|
case *msg.NatHoleClient:
|
||||||
|
go nc.HandleClient(m, raddr)
|
||||||
|
default:
|
||||||
|
log.Trace("error nat hole message type")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
pool.PutBuf(buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nc *NatHoleController) GenSid() string {
|
||||||
|
t := time.Now().Unix()
|
||||||
|
id, _ := util.RandId()
|
||||||
|
return fmt.Sprintf("%d%s", t, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nc *NatHoleController) HandleVisitor(m *msg.NatHoleVisitor, raddr *net.UDPAddr) {
|
||||||
|
sid := nc.GenSid()
|
||||||
|
session := &NatHoleSession{
|
||||||
|
Sid: sid,
|
||||||
|
VisitorAddr: raddr,
|
||||||
|
NotifyCh: make(chan struct{}, 0),
|
||||||
|
}
|
||||||
|
nc.mu.Lock()
|
||||||
|
clientCfg, ok := nc.clientCfgs[m.ProxyName]
|
||||||
|
if !ok || m.SignKey != util.GetAuthKey(clientCfg.Sk, m.Timestamp) {
|
||||||
|
nc.mu.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
nc.sessions[sid] = session
|
||||||
|
nc.mu.Unlock()
|
||||||
|
log.Trace("handle visitor message, sid [%s]", sid)
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
nc.mu.Lock()
|
||||||
|
delete(nc.sessions, sid)
|
||||||
|
nc.mu.Unlock()
|
||||||
|
}()
|
||||||
|
|
||||||
|
err := errors.PanicToError(func() {
|
||||||
|
clientCfg.SidCh <- sid
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait client connections.
|
||||||
|
select {
|
||||||
|
case <-session.NotifyCh:
|
||||||
|
resp := nc.GenNatHoleResponse(raddr, session)
|
||||||
|
log.Trace("send nat hole response to visitor")
|
||||||
|
nc.listener.WriteToUDP(resp, raddr)
|
||||||
|
case <-time.After(time.Duration(NatHoleTimeout) * time.Second):
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nc *NatHoleController) HandleClient(m *msg.NatHoleClient, raddr *net.UDPAddr) {
|
||||||
|
nc.mu.RLock()
|
||||||
|
session, ok := nc.sessions[m.Sid]
|
||||||
|
nc.mu.RUnlock()
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Trace("handle client message, sid [%s]", session.Sid)
|
||||||
|
session.ClientAddr = raddr
|
||||||
|
session.NotifyCh <- struct{}{}
|
||||||
|
|
||||||
|
resp := nc.GenNatHoleResponse(raddr, session)
|
||||||
|
log.Trace("send nat hole response to client")
|
||||||
|
nc.listener.WriteToUDP(resp, raddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nc *NatHoleController) GenNatHoleResponse(raddr *net.UDPAddr, session *NatHoleSession) []byte {
|
||||||
|
m := &msg.NatHoleResp{
|
||||||
|
Sid: session.Sid,
|
||||||
|
VisitorAddr: session.VisitorAddr.String(),
|
||||||
|
ClientAddr: session.ClientAddr.String(),
|
||||||
|
}
|
||||||
|
b := bytes.NewBuffer(nil)
|
||||||
|
err := msg.WriteMsg(b, m)
|
||||||
|
if err != nil {
|
||||||
|
return []byte("")
|
||||||
|
}
|
||||||
|
return b.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
type NatHoleSession struct {
|
||||||
|
Sid string
|
||||||
|
VisitorAddr *net.UDPAddr
|
||||||
|
ClientAddr *net.UDPAddr
|
||||||
|
|
||||||
|
NotifyCh chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type NatHoleClientCfg struct {
|
||||||
|
Name string
|
||||||
|
Sk string
|
||||||
|
SidCh chan string
|
||||||
|
}
|
|
@ -148,6 +148,11 @@ func NewProxy(ctl *Control, pxyConf config.ProxyConf) (pxy Proxy, err error) {
|
||||||
BaseProxy: basePxy,
|
BaseProxy: basePxy,
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
}
|
}
|
||||||
|
case *config.XtcpProxyConf:
|
||||||
|
pxy = &XtcpProxy{
|
||||||
|
BaseProxy: basePxy,
|
||||||
|
cfg: cfg,
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return pxy, fmt.Errorf("proxy type not support")
|
return pxy, fmt.Errorf("proxy type not support")
|
||||||
}
|
}
|
||||||
|
@ -285,7 +290,7 @@ type StcpProxy struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pxy *StcpProxy) Run() error {
|
func (pxy *StcpProxy) Run() error {
|
||||||
listener, err := pxy.ctl.svr.vistorManager.Listen(pxy.GetName(), pxy.cfg.Sk)
|
listener, err := pxy.ctl.svr.visitorManager.Listen(pxy.GetName(), pxy.cfg.Sk)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -303,7 +308,55 @@ func (pxy *StcpProxy) GetConf() config.ProxyConf {
|
||||||
|
|
||||||
func (pxy *StcpProxy) Close() {
|
func (pxy *StcpProxy) Close() {
|
||||||
pxy.BaseProxy.Close()
|
pxy.BaseProxy.Close()
|
||||||
pxy.ctl.svr.vistorManager.CloseListener(pxy.GetName())
|
pxy.ctl.svr.visitorManager.CloseListener(pxy.GetName())
|
||||||
|
}
|
||||||
|
|
||||||
|
type XtcpProxy struct {
|
||||||
|
BaseProxy
|
||||||
|
cfg *config.XtcpProxyConf
|
||||||
|
|
||||||
|
closeCh chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *XtcpProxy) Run() error {
|
||||||
|
if pxy.ctl.svr.natHoleController == nil {
|
||||||
|
pxy.Error("udp port for xtcp is not specified.")
|
||||||
|
return fmt.Errorf("xtcp is not supported in frps")
|
||||||
|
}
|
||||||
|
sidCh := pxy.ctl.svr.natHoleController.ListenClient(pxy.GetName(), pxy.cfg.Sk)
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-pxy.closeCh:
|
||||||
|
break
|
||||||
|
case sid := <-sidCh:
|
||||||
|
workConn, err := pxy.GetWorkConnFromPool()
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
m := &msg.NatHoleSid{
|
||||||
|
Sid: sid,
|
||||||
|
}
|
||||||
|
err = msg.WriteMsg(workConn, m)
|
||||||
|
if err != nil {
|
||||||
|
pxy.Warn("write nat hole sid package error, %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *XtcpProxy) GetConf() config.ProxyConf {
|
||||||
|
return pxy.cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pxy *XtcpProxy) Close() {
|
||||||
|
pxy.BaseProxy.Close()
|
||||||
|
pxy.ctl.svr.natHoleController.CloseClient(pxy.GetName())
|
||||||
|
errors.PanicToError(func() {
|
||||||
|
close(pxy.closeCh)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
type UdpProxy struct {
|
type UdpProxy struct {
|
||||||
|
|
|
@ -56,46 +56,50 @@ type Service struct {
|
||||||
// Manage all proxies.
|
// Manage all proxies.
|
||||||
pxyManager *ProxyManager
|
pxyManager *ProxyManager
|
||||||
|
|
||||||
// Manage all vistor listeners.
|
// Manage all visitor listeners.
|
||||||
vistorManager *VistorManager
|
visitorManager *VisitorManager
|
||||||
|
|
||||||
|
// Controller for nat hole connections.
|
||||||
|
natHoleController *NatHoleController
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewService() (svr *Service, err error) {
|
func NewService() (svr *Service, err error) {
|
||||||
svr = &Service{
|
svr = &Service{
|
||||||
ctlManager: NewControlManager(),
|
ctlManager: NewControlManager(),
|
||||||
pxyManager: NewProxyManager(),
|
pxyManager: NewProxyManager(),
|
||||||
vistorManager: NewVistorManager(),
|
visitorManager: NewVisitorManager(),
|
||||||
}
|
}
|
||||||
|
cfg := config.ServerCommonCfg
|
||||||
|
|
||||||
// Init assets.
|
// Init assets.
|
||||||
err = assets.Load(config.ServerCommonCfg.AssetsDir)
|
err = assets.Load(cfg.AssetsDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("Load assets error: %v", err)
|
err = fmt.Errorf("Load assets error: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Listen for accepting connections from client.
|
// Listen for accepting connections from client.
|
||||||
svr.listener, err = frpNet.ListenTcp(config.ServerCommonCfg.BindAddr, config.ServerCommonCfg.BindPort)
|
svr.listener, err = frpNet.ListenTcp(cfg.BindAddr, cfg.BindPort)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("Create server listener error, %v", err)
|
err = fmt.Errorf("Create server listener error, %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Info("frps tcp listen on %s:%d", config.ServerCommonCfg.BindAddr, config.ServerCommonCfg.BindPort)
|
log.Info("frps tcp listen on %s:%d", cfg.BindAddr, cfg.BindPort)
|
||||||
|
|
||||||
// Listen for accepting connections from client using kcp protocol.
|
// Listen for accepting connections from client using kcp protocol.
|
||||||
if config.ServerCommonCfg.KcpBindPort > 0 {
|
if cfg.KcpBindPort > 0 {
|
||||||
svr.kcpListener, err = frpNet.ListenKcp(config.ServerCommonCfg.BindAddr, config.ServerCommonCfg.KcpBindPort)
|
svr.kcpListener, err = frpNet.ListenKcp(cfg.BindAddr, cfg.KcpBindPort)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("Listen on kcp address udp [%s:%d] error: %v", config.ServerCommonCfg.BindAddr, config.ServerCommonCfg.KcpBindPort, err)
|
err = fmt.Errorf("Listen on kcp address udp [%s:%d] error: %v", cfg.BindAddr, cfg.KcpBindPort, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Info("frps kcp listen on udp %s:%d", config.ServerCommonCfg.BindAddr, config.ServerCommonCfg.BindPort)
|
log.Info("frps kcp listen on udp %s:%d", cfg.BindAddr, cfg.BindPort)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create http vhost muxer.
|
// Create http vhost muxer.
|
||||||
if config.ServerCommonCfg.VhostHttpPort > 0 {
|
if cfg.VhostHttpPort > 0 {
|
||||||
var l frpNet.Listener
|
var l frpNet.Listener
|
||||||
l, err = frpNet.ListenTcp(config.ServerCommonCfg.ProxyBindAddr, config.ServerCommonCfg.VhostHttpPort)
|
l, err = frpNet.ListenTcp(cfg.ProxyBindAddr, cfg.VhostHttpPort)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("Create vhost http listener error, %v", err)
|
err = fmt.Errorf("Create vhost http listener error, %v", err)
|
||||||
return
|
return
|
||||||
|
@ -105,13 +109,13 @@ func NewService() (svr *Service, err error) {
|
||||||
err = fmt.Errorf("Create vhost httpMuxer error, %v", err)
|
err = fmt.Errorf("Create vhost httpMuxer error, %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Info("http service listen on %s:%d", config.ServerCommonCfg.ProxyBindAddr, config.ServerCommonCfg.VhostHttpPort)
|
log.Info("http service listen on %s:%d", cfg.ProxyBindAddr, cfg.VhostHttpPort)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create https vhost muxer.
|
// Create https vhost muxer.
|
||||||
if config.ServerCommonCfg.VhostHttpsPort > 0 {
|
if cfg.VhostHttpsPort > 0 {
|
||||||
var l frpNet.Listener
|
var l frpNet.Listener
|
||||||
l, err = frpNet.ListenTcp(config.ServerCommonCfg.ProxyBindAddr, config.ServerCommonCfg.VhostHttpsPort)
|
l, err = frpNet.ListenTcp(cfg.ProxyBindAddr, cfg.VhostHttpsPort)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("Create vhost https listener error, %v", err)
|
err = fmt.Errorf("Create vhost https listener error, %v", err)
|
||||||
return
|
return
|
||||||
|
@ -121,22 +125,38 @@ func NewService() (svr *Service, err error) {
|
||||||
err = fmt.Errorf("Create vhost httpsMuxer error, %v", err)
|
err = fmt.Errorf("Create vhost httpsMuxer error, %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Info("https service listen on %s:%d", config.ServerCommonCfg.ProxyBindAddr, config.ServerCommonCfg.VhostHttpsPort)
|
log.Info("https service listen on %s:%d", cfg.ProxyBindAddr, cfg.VhostHttpsPort)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create nat hole controller.
|
||||||
|
if cfg.BindUdpPort > 0 {
|
||||||
|
var nc *NatHoleController
|
||||||
|
addr := fmt.Sprintf("%s:%d", cfg.BindAddr, cfg.BindUdpPort)
|
||||||
|
nc, err = NewNatHoleController(addr)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("Create nat hole controller error, %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
svr.natHoleController = nc
|
||||||
|
log.Info("nat hole udp service listen on %s:%d", cfg.BindAddr, cfg.BindUdpPort)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create dashboard web server.
|
// Create dashboard web server.
|
||||||
if config.ServerCommonCfg.DashboardPort > 0 {
|
if cfg.DashboardPort > 0 {
|
||||||
err = RunDashboardServer(config.ServerCommonCfg.BindAddr, config.ServerCommonCfg.DashboardPort)
|
err = RunDashboardServer(cfg.DashboardAddr, cfg.DashboardPort)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("Create dashboard web server error, %v", err)
|
err = fmt.Errorf("Create dashboard web server error, %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Info("Dashboard listen on %s:%d", config.ServerCommonCfg.BindAddr, config.ServerCommonCfg.DashboardPort)
|
log.Info("Dashboard listen on %s:%d", cfg.DashboardAddr, cfg.DashboardPort)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svr *Service) Run() {
|
func (svr *Service) Run() {
|
||||||
|
if svr.natHoleController != nil {
|
||||||
|
go svr.natHoleController.Run()
|
||||||
|
}
|
||||||
if config.ServerCommonCfg.KcpBindPort > 0 {
|
if config.ServerCommonCfg.KcpBindPort > 0 {
|
||||||
go svr.HandleListener(svr.kcpListener)
|
go svr.HandleListener(svr.kcpListener)
|
||||||
}
|
}
|
||||||
|
@ -180,16 +200,16 @@ func (svr *Service) HandleListener(l frpNet.Listener) {
|
||||||
}
|
}
|
||||||
case *msg.NewWorkConn:
|
case *msg.NewWorkConn:
|
||||||
svr.RegisterWorkConn(conn, m)
|
svr.RegisterWorkConn(conn, m)
|
||||||
case *msg.NewVistorConn:
|
case *msg.NewVisitorConn:
|
||||||
if err = svr.RegisterVistorConn(conn, m); err != nil {
|
if err = svr.RegisterVisitorConn(conn, m); err != nil {
|
||||||
conn.Warn("%v", err)
|
conn.Warn("%v", err)
|
||||||
msg.WriteMsg(conn, &msg.NewVistorConnResp{
|
msg.WriteMsg(conn, &msg.NewVisitorConnResp{
|
||||||
ProxyName: m.ProxyName,
|
ProxyName: m.ProxyName,
|
||||||
Error: err.Error(),
|
Error: err.Error(),
|
||||||
})
|
})
|
||||||
conn.Close()
|
conn.Close()
|
||||||
} else {
|
} else {
|
||||||
msg.WriteMsg(conn, &msg.NewVistorConnResp{
|
msg.WriteMsg(conn, &msg.NewVisitorConnResp{
|
||||||
ProxyName: m.ProxyName,
|
ProxyName: m.ProxyName,
|
||||||
Error: "",
|
Error: "",
|
||||||
})
|
})
|
||||||
|
@ -280,8 +300,8 @@ func (svr *Service) RegisterWorkConn(workConn frpNet.Conn, newMsg *msg.NewWorkCo
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svr *Service) RegisterVistorConn(vistorConn frpNet.Conn, newMsg *msg.NewVistorConn) error {
|
func (svr *Service) RegisterVisitorConn(visitorConn frpNet.Conn, newMsg *msg.NewVisitorConn) error {
|
||||||
return svr.vistorManager.NewConn(newMsg.ProxyName, vistorConn, newMsg.Timestamp, newMsg.SignKey,
|
return svr.visitorManager.NewConn(newMsg.ProxyName, visitorConn, newMsg.Timestamp, newMsg.SignKey,
|
||||||
newMsg.UseEncryption, newMsg.UseCompression)
|
newMsg.UseEncryption, newMsg.UseCompression)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ import (
|
||||||
|
|
||||||
"github.com/fatedier/frp/utils/log"
|
"github.com/fatedier/frp/utils/log"
|
||||||
|
|
||||||
kcp "github.com/xtaci/kcp-go"
|
kcp "github.com/fatedier/kcp-go"
|
||||||
)
|
)
|
||||||
|
|
||||||
type KcpListener struct {
|
type KcpListener struct {
|
||||||
|
@ -85,3 +85,17 @@ func (l *KcpListener) Close() error {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewKcpConnFromUdp(conn *net.UDPConn, connected bool, raddr string) (net.Conn, error) {
|
||||||
|
kcpConn, err := kcp.NewConnEx(1, connected, raddr, nil, 10, 3, conn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
kcpConn.SetStreamMode(true)
|
||||||
|
kcpConn.SetWriteDelay(true)
|
||||||
|
kcpConn.SetNoDelay(1, 20, 2, 1)
|
||||||
|
kcpConn.SetMtu(1350)
|
||||||
|
kcpConn.SetWindowSize(1024, 1024)
|
||||||
|
kcpConn.SetACKNoDelay(false)
|
||||||
|
return kcpConn, nil
|
||||||
|
}
|
||||||
|
|
|
@ -19,37 +19,31 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
var version string = "0.13.0"
|
var version string = "0.14.0"
|
||||||
|
|
||||||
func Full() string {
|
func Full() string {
|
||||||
return version
|
return version
|
||||||
}
|
}
|
||||||
|
|
||||||
func Proto(v string) int64 {
|
func getSubVersion(v string, position int) int64 {
|
||||||
arr := strings.Split(v, ".")
|
arr := strings.Split(v, ".")
|
||||||
if len(arr) < 3 {
|
if len(arr) < 3 {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
res, _ := strconv.ParseInt(arr[0], 10, 64)
|
res, _ := strconv.ParseInt(arr[position], 10, 64)
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Proto(v string) int64 {
|
||||||
|
return getSubVersion(v, 0)
|
||||||
|
}
|
||||||
|
|
||||||
func Major(v string) int64 {
|
func Major(v string) int64 {
|
||||||
arr := strings.Split(v, ".")
|
return getSubVersion(v, 1)
|
||||||
if len(arr) < 3 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
res, _ := strconv.ParseInt(arr[1], 10, 64)
|
|
||||||
return res
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Minor(v string) int64 {
|
func Minor(v string) int64 {
|
||||||
arr := strings.Split(v, ".")
|
return getSubVersion(v, 2)
|
||||||
if len(arr) < 3 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
res, _ := strconv.ParseInt(arr[2], 10, 64)
|
|
||||||
return res
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// add every case there if server will not accept client's protocol and return false
|
// add every case there if server will not accept client's protocol and return false
|
||||||
|
|
|
@ -18,6 +18,7 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/utils/errors"
|
||||||
"github.com/fatedier/frp/utils/log"
|
"github.com/fatedier/frp/utils/log"
|
||||||
frpNet "github.com/fatedier/frp/utils/net"
|
frpNet "github.com/fatedier/frp/utils/net"
|
||||||
)
|
)
|
||||||
|
@ -162,7 +163,12 @@ func (v *VhostMuxer) handle(c frpNet.Conn) {
|
||||||
c = sConn
|
c = sConn
|
||||||
|
|
||||||
l.Debug("get new http request host [%s] path [%s]", name, path)
|
l.Debug("get new http request host [%s] path [%s]", name, path)
|
||||||
|
err = errors.PanicToError(func() {
|
||||||
l.accept <- c
|
l.accept <- c
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
l.Warn("listener is already closed, ignore this request")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Listener struct {
|
type Listener struct {
|
||||||
|
|
|
@ -0,0 +1,119 @@
|
||||||
|
package socks5
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNoAuth(t *testing.T) {
|
||||||
|
req := bytes.NewBuffer(nil)
|
||||||
|
req.Write([]byte{1, NoAuth})
|
||||||
|
var resp bytes.Buffer
|
||||||
|
|
||||||
|
s, _ := New(&Config{})
|
||||||
|
ctx, err := s.authenticate(&resp, req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx.Method != NoAuth {
|
||||||
|
t.Fatal("Invalid Context Method")
|
||||||
|
}
|
||||||
|
|
||||||
|
out := resp.Bytes()
|
||||||
|
if !bytes.Equal(out, []byte{socks5Version, NoAuth}) {
|
||||||
|
t.Fatalf("bad: %v", out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPasswordAuth_Valid(t *testing.T) {
|
||||||
|
req := bytes.NewBuffer(nil)
|
||||||
|
req.Write([]byte{2, NoAuth, UserPassAuth})
|
||||||
|
req.Write([]byte{1, 3, 'f', 'o', 'o', 3, 'b', 'a', 'r'})
|
||||||
|
var resp bytes.Buffer
|
||||||
|
|
||||||
|
cred := StaticCredentials{
|
||||||
|
"foo": "bar",
|
||||||
|
}
|
||||||
|
|
||||||
|
cator := UserPassAuthenticator{Credentials: cred}
|
||||||
|
|
||||||
|
s, _ := New(&Config{AuthMethods: []Authenticator{cator}})
|
||||||
|
|
||||||
|
ctx, err := s.authenticate(&resp, req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx.Method != UserPassAuth {
|
||||||
|
t.Fatal("Invalid Context Method")
|
||||||
|
}
|
||||||
|
|
||||||
|
val, ok := ctx.Payload["Username"]
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("Missing key Username in auth context's payload")
|
||||||
|
}
|
||||||
|
|
||||||
|
if val != "foo" {
|
||||||
|
t.Fatal("Invalid Username in auth context's payload")
|
||||||
|
}
|
||||||
|
|
||||||
|
out := resp.Bytes()
|
||||||
|
if !bytes.Equal(out, []byte{socks5Version, UserPassAuth, 1, authSuccess}) {
|
||||||
|
t.Fatalf("bad: %v", out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPasswordAuth_Invalid(t *testing.T) {
|
||||||
|
req := bytes.NewBuffer(nil)
|
||||||
|
req.Write([]byte{2, NoAuth, UserPassAuth})
|
||||||
|
req.Write([]byte{1, 3, 'f', 'o', 'o', 3, 'b', 'a', 'z'})
|
||||||
|
var resp bytes.Buffer
|
||||||
|
|
||||||
|
cred := StaticCredentials{
|
||||||
|
"foo": "bar",
|
||||||
|
}
|
||||||
|
cator := UserPassAuthenticator{Credentials: cred}
|
||||||
|
s, _ := New(&Config{AuthMethods: []Authenticator{cator}})
|
||||||
|
|
||||||
|
ctx, err := s.authenticate(&resp, req)
|
||||||
|
if err != UserAuthFailed {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx != nil {
|
||||||
|
t.Fatal("Invalid Context Method")
|
||||||
|
}
|
||||||
|
|
||||||
|
out := resp.Bytes()
|
||||||
|
if !bytes.Equal(out, []byte{socks5Version, UserPassAuth, 1, authFailure}) {
|
||||||
|
t.Fatalf("bad: %v", out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNoSupportedAuth(t *testing.T) {
|
||||||
|
req := bytes.NewBuffer(nil)
|
||||||
|
req.Write([]byte{1, NoAuth})
|
||||||
|
var resp bytes.Buffer
|
||||||
|
|
||||||
|
cred := StaticCredentials{
|
||||||
|
"foo": "bar",
|
||||||
|
}
|
||||||
|
cator := UserPassAuthenticator{Credentials: cred}
|
||||||
|
|
||||||
|
s, _ := New(&Config{AuthMethods: []Authenticator{cator}})
|
||||||
|
|
||||||
|
ctx, err := s.authenticate(&resp, req)
|
||||||
|
if err != NoSupportedAuth {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx != nil {
|
||||||
|
t.Fatal("Invalid Context Method")
|
||||||
|
}
|
||||||
|
|
||||||
|
out := resp.Bytes()
|
||||||
|
if !bytes.Equal(out, []byte{socks5Version, noAcceptable}) {
|
||||||
|
t.Fatalf("bad: %v", out)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
package socks5
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestStaticCredentials(t *testing.T) {
|
||||||
|
creds := StaticCredentials{
|
||||||
|
"foo": "bar",
|
||||||
|
"baz": "",
|
||||||
|
}
|
||||||
|
|
||||||
|
if !creds.Valid("foo", "bar") {
|
||||||
|
t.Fatalf("expect valid")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !creds.Valid("baz", "") {
|
||||||
|
t.Fatalf("expect valid")
|
||||||
|
}
|
||||||
|
|
||||||
|
if creds.Valid("foo", "") {
|
||||||
|
t.Fatalf("expect invalid")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,169 @@
|
||||||
|
package socks5
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MockConn struct {
|
||||||
|
buf bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockConn) Write(b []byte) (int, error) {
|
||||||
|
return m.buf.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockConn) RemoteAddr() net.Addr {
|
||||||
|
return &net.TCPAddr{IP: []byte{127, 0, 0, 1}, Port: 65432}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRequest_Connect(t *testing.T) {
|
||||||
|
// Create a local listener
|
||||||
|
l, err := net.Listen("tcp", "127.0.0.1:0")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
conn, err := l.Accept()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
buf := make([]byte, 4)
|
||||||
|
if _, err := io.ReadAtLeast(conn, buf, 4); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(buf, []byte("ping")) {
|
||||||
|
t.Fatalf("bad: %v", buf)
|
||||||
|
}
|
||||||
|
conn.Write([]byte("pong"))
|
||||||
|
}()
|
||||||
|
lAddr := l.Addr().(*net.TCPAddr)
|
||||||
|
|
||||||
|
// Make server
|
||||||
|
s := &Server{config: &Config{
|
||||||
|
Rules: PermitAll(),
|
||||||
|
Resolver: DNSResolver{},
|
||||||
|
Logger: log.New(os.Stdout, "", log.LstdFlags),
|
||||||
|
}}
|
||||||
|
|
||||||
|
// Create the connect request
|
||||||
|
buf := bytes.NewBuffer(nil)
|
||||||
|
buf.Write([]byte{5, 1, 0, 1, 127, 0, 0, 1})
|
||||||
|
|
||||||
|
port := []byte{0, 0}
|
||||||
|
binary.BigEndian.PutUint16(port, uint16(lAddr.Port))
|
||||||
|
buf.Write(port)
|
||||||
|
|
||||||
|
// Send a ping
|
||||||
|
buf.Write([]byte("ping"))
|
||||||
|
|
||||||
|
// Handle the request
|
||||||
|
resp := &MockConn{}
|
||||||
|
req, err := NewRequest(buf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.handleRequest(req, resp); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify response
|
||||||
|
out := resp.buf.Bytes()
|
||||||
|
expected := []byte{
|
||||||
|
5,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
127, 0, 0, 1,
|
||||||
|
0, 0,
|
||||||
|
'p', 'o', 'n', 'g',
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore the port for both
|
||||||
|
out[8] = 0
|
||||||
|
out[9] = 0
|
||||||
|
|
||||||
|
if !bytes.Equal(out, expected) {
|
||||||
|
t.Fatalf("bad: %v %v", out, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRequest_Connect_RuleFail(t *testing.T) {
|
||||||
|
// Create a local listener
|
||||||
|
l, err := net.Listen("tcp", "127.0.0.1:0")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
conn, err := l.Accept()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
buf := make([]byte, 4)
|
||||||
|
if _, err := io.ReadAtLeast(conn, buf, 4); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(buf, []byte("ping")) {
|
||||||
|
t.Fatalf("bad: %v", buf)
|
||||||
|
}
|
||||||
|
conn.Write([]byte("pong"))
|
||||||
|
}()
|
||||||
|
lAddr := l.Addr().(*net.TCPAddr)
|
||||||
|
|
||||||
|
// Make server
|
||||||
|
s := &Server{config: &Config{
|
||||||
|
Rules: PermitNone(),
|
||||||
|
Resolver: DNSResolver{},
|
||||||
|
Logger: log.New(os.Stdout, "", log.LstdFlags),
|
||||||
|
}}
|
||||||
|
|
||||||
|
// Create the connect request
|
||||||
|
buf := bytes.NewBuffer(nil)
|
||||||
|
buf.Write([]byte{5, 1, 0, 1, 127, 0, 0, 1})
|
||||||
|
|
||||||
|
port := []byte{0, 0}
|
||||||
|
binary.BigEndian.PutUint16(port, uint16(lAddr.Port))
|
||||||
|
buf.Write(port)
|
||||||
|
|
||||||
|
// Send a ping
|
||||||
|
buf.Write([]byte("ping"))
|
||||||
|
|
||||||
|
// Handle the request
|
||||||
|
resp := &MockConn{}
|
||||||
|
req, err := NewRequest(buf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.handleRequest(req, resp); !strings.Contains(err.Error(), "blocked by rules") {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify response
|
||||||
|
out := resp.buf.Bytes()
|
||||||
|
expected := []byte{
|
||||||
|
5,
|
||||||
|
2,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0, 0, 0, 0,
|
||||||
|
0, 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(out, expected) {
|
||||||
|
t.Fatalf("bad: %v %v", out, expected)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
package socks5
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDNSResolver(t *testing.T) {
|
||||||
|
d := DNSResolver{}
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
_, addr, err := d.Resolve(ctx, "localhost")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !addr.IsLoopback() {
|
||||||
|
t.Fatalf("expected loopback")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
package socks5
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPermitCommand(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
r := &PermitCommand{true, false, false}
|
||||||
|
|
||||||
|
if _, ok := r.Allow(ctx, &Request{Command: ConnectCommand}); !ok {
|
||||||
|
t.Fatalf("expect connect")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := r.Allow(ctx, &Request{Command: BindCommand}); ok {
|
||||||
|
t.Fatalf("do not expect bind")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := r.Allow(ctx, &Request{Command: AssociateCommand}); ok {
|
||||||
|
t.Fatalf("do not expect associate")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,110 @@
|
||||||
|
package socks5
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSOCKS5_Connect(t *testing.T) {
|
||||||
|
// Create a local listener
|
||||||
|
l, err := net.Listen("tcp", "127.0.0.1:0")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
conn, err := l.Accept()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
buf := make([]byte, 4)
|
||||||
|
if _, err := io.ReadAtLeast(conn, buf, 4); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(buf, []byte("ping")) {
|
||||||
|
t.Fatalf("bad: %v", buf)
|
||||||
|
}
|
||||||
|
conn.Write([]byte("pong"))
|
||||||
|
}()
|
||||||
|
lAddr := l.Addr().(*net.TCPAddr)
|
||||||
|
|
||||||
|
// Create a socks server
|
||||||
|
creds := StaticCredentials{
|
||||||
|
"foo": "bar",
|
||||||
|
}
|
||||||
|
cator := UserPassAuthenticator{Credentials: creds}
|
||||||
|
conf := &Config{
|
||||||
|
AuthMethods: []Authenticator{cator},
|
||||||
|
Logger: log.New(os.Stdout, "", log.LstdFlags),
|
||||||
|
}
|
||||||
|
serv, err := New(conf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start listening
|
||||||
|
go func() {
|
||||||
|
if err := serv.ListenAndServe("tcp", "127.0.0.1:12365"); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
|
||||||
|
// Get a local conn
|
||||||
|
conn, err := net.Dial("tcp", "127.0.0.1:12365")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect, auth and connec to local
|
||||||
|
req := bytes.NewBuffer(nil)
|
||||||
|
req.Write([]byte{5})
|
||||||
|
req.Write([]byte{2, NoAuth, UserPassAuth})
|
||||||
|
req.Write([]byte{1, 3, 'f', 'o', 'o', 3, 'b', 'a', 'r'})
|
||||||
|
req.Write([]byte{5, 1, 0, 1, 127, 0, 0, 1})
|
||||||
|
|
||||||
|
port := []byte{0, 0}
|
||||||
|
binary.BigEndian.PutUint16(port, uint16(lAddr.Port))
|
||||||
|
req.Write(port)
|
||||||
|
|
||||||
|
// Send a ping
|
||||||
|
req.Write([]byte("ping"))
|
||||||
|
|
||||||
|
// Send all the bytes
|
||||||
|
conn.Write(req.Bytes())
|
||||||
|
|
||||||
|
// Verify response
|
||||||
|
expected := []byte{
|
||||||
|
socks5Version, UserPassAuth,
|
||||||
|
1, authSuccess,
|
||||||
|
5,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
127, 0, 0, 1,
|
||||||
|
0, 0,
|
||||||
|
'p', 'o', 'n', 'g',
|
||||||
|
}
|
||||||
|
out := make([]byte, len(expected))
|
||||||
|
|
||||||
|
conn.SetDeadline(time.Now().Add(time.Second))
|
||||||
|
if _, err := io.ReadAtLeast(conn, out, len(out)); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore the port
|
||||||
|
out[12] = 0
|
||||||
|
out[13] = 0
|
||||||
|
|
||||||
|
if !bytes.Equal(out, expected) {
|
||||||
|
t.Fatalf("bad: %v", out)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||||
|
*.o
|
||||||
|
*.a
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Folders
|
||||||
|
_obj
|
||||||
|
_test
|
||||||
|
|
||||||
|
# Architecture specific extensions/prefixes
|
||||||
|
*.[568vq]
|
||||||
|
[568vq].out
|
||||||
|
|
||||||
|
*.cgo1.go
|
||||||
|
*.cgo2.c
|
||||||
|
_cgo_defun.c
|
||||||
|
_cgo_gotypes.go
|
||||||
|
_cgo_export.*
|
||||||
|
|
||||||
|
_testmain.go
|
||||||
|
|
||||||
|
*.exe
|
|
@ -0,0 +1,14 @@
|
||||||
|
language: go
|
||||||
|
go:
|
||||||
|
- 1.5.4
|
||||||
|
- 1.6.3
|
||||||
|
- 1.7
|
||||||
|
install:
|
||||||
|
- go get -v golang.org/x/tools/cmd/cover
|
||||||
|
script:
|
||||||
|
- go test -v -tags=safe ./spew
|
||||||
|
- go test -v -tags=testcgo ./spew -covermode=count -coverprofile=profile.cov
|
||||||
|
after_success:
|
||||||
|
- go get -v github.com/mattn/goveralls
|
||||||
|
- export PATH=$PATH:$HOME/gopath/bin
|
||||||
|
- goveralls -coverprofile=profile.cov -service=travis-ci
|
|
@ -0,0 +1,205 @@
|
||||||
|
go-spew
|
||||||
|
=======
|
||||||
|
|
||||||
|
[![Build Status](https://img.shields.io/travis/davecgh/go-spew.svg)]
|
||||||
|
(https://travis-ci.org/davecgh/go-spew) [![ISC License]
|
||||||
|
(http://img.shields.io/badge/license-ISC-blue.svg)](http://copyfree.org) [![Coverage Status]
|
||||||
|
(https://img.shields.io/coveralls/davecgh/go-spew.svg)]
|
||||||
|
(https://coveralls.io/r/davecgh/go-spew?branch=master)
|
||||||
|
|
||||||
|
|
||||||
|
Go-spew implements a deep pretty printer for Go data structures to aid in
|
||||||
|
debugging. A comprehensive suite of tests with 100% test coverage is provided
|
||||||
|
to ensure proper functionality. See `test_coverage.txt` for the gocov coverage
|
||||||
|
report. Go-spew is licensed under the liberal ISC license, so it may be used in
|
||||||
|
open source or commercial projects.
|
||||||
|
|
||||||
|
If you're interested in reading about how this package came to life and some
|
||||||
|
of the challenges involved in providing a deep pretty printer, there is a blog
|
||||||
|
post about it
|
||||||
|
[here](https://web.archive.org/web/20160304013555/https://blog.cyphertite.com/go-spew-a-journey-into-dumping-go-data-structures/).
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
[![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg)]
|
||||||
|
(http://godoc.org/github.com/davecgh/go-spew/spew)
|
||||||
|
|
||||||
|
Full `go doc` style documentation for the project can be viewed online without
|
||||||
|
installing this package by using the excellent GoDoc site here:
|
||||||
|
http://godoc.org/github.com/davecgh/go-spew/spew
|
||||||
|
|
||||||
|
You can also view the documentation locally once the package is installed with
|
||||||
|
the `godoc` tool by running `godoc -http=":6060"` and pointing your browser to
|
||||||
|
http://localhost:6060/pkg/github.com/davecgh/go-spew/spew
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ go get -u github.com/davecgh/go-spew/spew
|
||||||
|
```
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
Add this import line to the file you're working in:
|
||||||
|
|
||||||
|
```Go
|
||||||
|
import "github.com/davecgh/go-spew/spew"
|
||||||
|
```
|
||||||
|
|
||||||
|
To dump a variable with full newlines, indentation, type, and pointer
|
||||||
|
information use Dump, Fdump, or Sdump:
|
||||||
|
|
||||||
|
```Go
|
||||||
|
spew.Dump(myVar1, myVar2, ...)
|
||||||
|
spew.Fdump(someWriter, myVar1, myVar2, ...)
|
||||||
|
str := spew.Sdump(myVar1, myVar2, ...)
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternatively, if you would prefer to use format strings with a compacted inline
|
||||||
|
printing style, use the convenience wrappers Printf, Fprintf, etc with %v (most
|
||||||
|
compact), %+v (adds pointer addresses), %#v (adds types), or %#+v (adds types
|
||||||
|
and pointer addresses):
|
||||||
|
|
||||||
|
```Go
|
||||||
|
spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
||||||
|
spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
||||||
|
spew.Fprintf(someWriter, "myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
||||||
|
spew.Fprintf(someWriter, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Debugging a Web Application Example
|
||||||
|
|
||||||
|
Here is an example of how you can use `spew.Sdump()` to help debug a web application. Please be sure to wrap your output using the `html.EscapeString()` function for safety reasons. You should also only use this debugging technique in a development environment, never in production.
|
||||||
|
|
||||||
|
```Go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"html"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
)
|
||||||
|
|
||||||
|
func handler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "text/html")
|
||||||
|
fmt.Fprintf(w, "Hi there, %s!", r.URL.Path[1:])
|
||||||
|
fmt.Fprintf(w, "<!--\n" + html.EscapeString(spew.Sdump(w)) + "\n-->")
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
http.HandleFunc("/", handler)
|
||||||
|
http.ListenAndServe(":8080", nil)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Sample Dump Output
|
||||||
|
|
||||||
|
```
|
||||||
|
(main.Foo) {
|
||||||
|
unexportedField: (*main.Bar)(0xf84002e210)({
|
||||||
|
flag: (main.Flag) flagTwo,
|
||||||
|
data: (uintptr) <nil>
|
||||||
|
}),
|
||||||
|
ExportedField: (map[interface {}]interface {}) {
|
||||||
|
(string) "one": (bool) true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
([]uint8) {
|
||||||
|
00000000 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 |............... |
|
||||||
|
00000010 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 |!"#$%&'()*+,-./0|
|
||||||
|
00000020 31 32 |12|
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Sample Formatter Output
|
||||||
|
|
||||||
|
Double pointer to a uint8:
|
||||||
|
```
|
||||||
|
%v: <**>5
|
||||||
|
%+v: <**>(0xf8400420d0->0xf8400420c8)5
|
||||||
|
%#v: (**uint8)5
|
||||||
|
%#+v: (**uint8)(0xf8400420d0->0xf8400420c8)5
|
||||||
|
```
|
||||||
|
|
||||||
|
Pointer to circular struct with a uint8 field and a pointer to itself:
|
||||||
|
```
|
||||||
|
%v: <*>{1 <*><shown>}
|
||||||
|
%+v: <*>(0xf84003e260){ui8:1 c:<*>(0xf84003e260)<shown>}
|
||||||
|
%#v: (*main.circular){ui8:(uint8)1 c:(*main.circular)<shown>}
|
||||||
|
%#+v: (*main.circular)(0xf84003e260){ui8:(uint8)1 c:(*main.circular)(0xf84003e260)<shown>}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration Options
|
||||||
|
|
||||||
|
Configuration of spew is handled by fields in the ConfigState type. For
|
||||||
|
convenience, all of the top-level functions use a global state available via the
|
||||||
|
spew.Config global.
|
||||||
|
|
||||||
|
It is also possible to create a ConfigState instance that provides methods
|
||||||
|
equivalent to the top-level functions. This allows concurrent configuration
|
||||||
|
options. See the ConfigState documentation for more details.
|
||||||
|
|
||||||
|
```
|
||||||
|
* Indent
|
||||||
|
String to use for each indentation level for Dump functions.
|
||||||
|
It is a single space by default. A popular alternative is "\t".
|
||||||
|
|
||||||
|
* MaxDepth
|
||||||
|
Maximum number of levels to descend into nested data structures.
|
||||||
|
There is no limit by default.
|
||||||
|
|
||||||
|
* DisableMethods
|
||||||
|
Disables invocation of error and Stringer interface methods.
|
||||||
|
Method invocation is enabled by default.
|
||||||
|
|
||||||
|
* DisablePointerMethods
|
||||||
|
Disables invocation of error and Stringer interface methods on types
|
||||||
|
which only accept pointer receivers from non-pointer variables. This option
|
||||||
|
relies on access to the unsafe package, so it will not have any effect when
|
||||||
|
running in environments without access to the unsafe package such as Google
|
||||||
|
App Engine or with the "safe" build tag specified.
|
||||||
|
Pointer method invocation is enabled by default.
|
||||||
|
|
||||||
|
* DisablePointerAddresses
|
||||||
|
DisablePointerAddresses specifies whether to disable the printing of
|
||||||
|
pointer addresses. This is useful when diffing data structures in tests.
|
||||||
|
|
||||||
|
* DisableCapacities
|
||||||
|
DisableCapacities specifies whether to disable the printing of capacities
|
||||||
|
for arrays, slices, maps and channels. This is useful when diffing data
|
||||||
|
structures in tests.
|
||||||
|
|
||||||
|
* ContinueOnMethod
|
||||||
|
Enables recursion into types after invoking error and Stringer interface
|
||||||
|
methods. Recursion after method invocation is disabled by default.
|
||||||
|
|
||||||
|
* SortKeys
|
||||||
|
Specifies map keys should be sorted before being printed. Use
|
||||||
|
this to have a more deterministic, diffable output. Note that
|
||||||
|
only native types (bool, int, uint, floats, uintptr and string)
|
||||||
|
and types which implement error or Stringer interfaces are supported,
|
||||||
|
with other types sorted according to the reflect.Value.String() output
|
||||||
|
which guarantees display stability. Natural map order is used by
|
||||||
|
default.
|
||||||
|
|
||||||
|
* SpewKeys
|
||||||
|
SpewKeys specifies that, as a last resort attempt, map keys should be
|
||||||
|
spewed to strings and sorted by those strings. This is only considered
|
||||||
|
if SortKeys is true.
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## Unsafe Package Dependency
|
||||||
|
|
||||||
|
This package relies on the unsafe package to perform some of the more advanced
|
||||||
|
features, however it also supports a "limited" mode which allows it to work in
|
||||||
|
environments where the unsafe package is not available. By default, it will
|
||||||
|
operate in this mode on Google App Engine and when compiled with GopherJS. The
|
||||||
|
"safe" build tag may also be specified to force the package to build without
|
||||||
|
using the unsafe package.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Go-spew is licensed under the [copyfree](http://copyfree.org) ISC License.
|
|
@ -0,0 +1,22 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# This script uses gocov to generate a test coverage report.
|
||||||
|
# The gocov tool my be obtained with the following command:
|
||||||
|
# go get github.com/axw/gocov/gocov
|
||||||
|
#
|
||||||
|
# It will be installed to $GOPATH/bin, so ensure that location is in your $PATH.
|
||||||
|
|
||||||
|
# Check for gocov.
|
||||||
|
if ! type gocov >/dev/null 2>&1; then
|
||||||
|
echo >&2 "This script requires the gocov tool."
|
||||||
|
echo >&2 "You may obtain it with the following command:"
|
||||||
|
echo >&2 "go get github.com/axw/gocov/gocov"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Only run the cgo tests if gcc is installed.
|
||||||
|
if type gcc >/dev/null 2>&1; then
|
||||||
|
(cd spew && gocov test -tags testcgo | gocov report)
|
||||||
|
else
|
||||||
|
(cd spew && gocov test | gocov report)
|
||||||
|
fi
|
|
@ -0,0 +1,298 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package spew_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
)
|
||||||
|
|
||||||
|
// custom type to test Stinger interface on non-pointer receiver.
|
||||||
|
type stringer string
|
||||||
|
|
||||||
|
// String implements the Stringer interface for testing invocation of custom
|
||||||
|
// stringers on types with non-pointer receivers.
|
||||||
|
func (s stringer) String() string {
|
||||||
|
return "stringer " + string(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// custom type to test Stinger interface on pointer receiver.
|
||||||
|
type pstringer string
|
||||||
|
|
||||||
|
// String implements the Stringer interface for testing invocation of custom
|
||||||
|
// stringers on types with only pointer receivers.
|
||||||
|
func (s *pstringer) String() string {
|
||||||
|
return "stringer " + string(*s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// xref1 and xref2 are cross referencing structs for testing circular reference
|
||||||
|
// detection.
|
||||||
|
type xref1 struct {
|
||||||
|
ps2 *xref2
|
||||||
|
}
|
||||||
|
type xref2 struct {
|
||||||
|
ps1 *xref1
|
||||||
|
}
|
||||||
|
|
||||||
|
// indirCir1, indirCir2, and indirCir3 are used to generate an indirect circular
|
||||||
|
// reference for testing detection.
|
||||||
|
type indirCir1 struct {
|
||||||
|
ps2 *indirCir2
|
||||||
|
}
|
||||||
|
type indirCir2 struct {
|
||||||
|
ps3 *indirCir3
|
||||||
|
}
|
||||||
|
type indirCir3 struct {
|
||||||
|
ps1 *indirCir1
|
||||||
|
}
|
||||||
|
|
||||||
|
// embed is used to test embedded structures.
|
||||||
|
type embed struct {
|
||||||
|
a string
|
||||||
|
}
|
||||||
|
|
||||||
|
// embedwrap is used to test embedded structures.
|
||||||
|
type embedwrap struct {
|
||||||
|
*embed
|
||||||
|
e *embed
|
||||||
|
}
|
||||||
|
|
||||||
|
// panicer is used to intentionally cause a panic for testing spew properly
|
||||||
|
// handles them
|
||||||
|
type panicer int
|
||||||
|
|
||||||
|
func (p panicer) String() string {
|
||||||
|
panic("test panic")
|
||||||
|
}
|
||||||
|
|
||||||
|
// customError is used to test custom error interface invocation.
|
||||||
|
type customError int
|
||||||
|
|
||||||
|
func (e customError) Error() string {
|
||||||
|
return fmt.Sprintf("error: %d", int(e))
|
||||||
|
}
|
||||||
|
|
||||||
|
// stringizeWants converts a slice of wanted test output into a format suitable
|
||||||
|
// for a test error message.
|
||||||
|
func stringizeWants(wants []string) string {
|
||||||
|
s := ""
|
||||||
|
for i, want := range wants {
|
||||||
|
if i > 0 {
|
||||||
|
s += fmt.Sprintf("want%d: %s", i+1, want)
|
||||||
|
} else {
|
||||||
|
s += "want: " + want
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// testFailed returns whether or not a test failed by checking if the result
|
||||||
|
// of the test is in the slice of wanted strings.
|
||||||
|
func testFailed(result string, wants []string) bool {
|
||||||
|
for _, want := range wants {
|
||||||
|
if result == want {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
type sortableStruct struct {
|
||||||
|
x int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ss sortableStruct) String() string {
|
||||||
|
return fmt.Sprintf("ss.%d", ss.x)
|
||||||
|
}
|
||||||
|
|
||||||
|
type unsortableStruct struct {
|
||||||
|
x int
|
||||||
|
}
|
||||||
|
|
||||||
|
type sortTestCase struct {
|
||||||
|
input []reflect.Value
|
||||||
|
expected []reflect.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
func helpTestSortValues(tests []sortTestCase, cs *spew.ConfigState, t *testing.T) {
|
||||||
|
getInterfaces := func(values []reflect.Value) []interface{} {
|
||||||
|
interfaces := []interface{}{}
|
||||||
|
for _, v := range values {
|
||||||
|
interfaces = append(interfaces, v.Interface())
|
||||||
|
}
|
||||||
|
return interfaces
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
spew.SortValues(test.input, cs)
|
||||||
|
// reflect.DeepEqual cannot really make sense of reflect.Value,
|
||||||
|
// probably because of all the pointer tricks. For instance,
|
||||||
|
// v(2.0) != v(2.0) on a 32-bits system. Turn them into interface{}
|
||||||
|
// instead.
|
||||||
|
input := getInterfaces(test.input)
|
||||||
|
expected := getInterfaces(test.expected)
|
||||||
|
if !reflect.DeepEqual(input, expected) {
|
||||||
|
t.Errorf("Sort mismatch:\n %v != %v", input, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSortValues ensures the sort functionality for relect.Value based sorting
|
||||||
|
// works as intended.
|
||||||
|
func TestSortValues(t *testing.T) {
|
||||||
|
v := reflect.ValueOf
|
||||||
|
|
||||||
|
a := v("a")
|
||||||
|
b := v("b")
|
||||||
|
c := v("c")
|
||||||
|
embedA := v(embed{"a"})
|
||||||
|
embedB := v(embed{"b"})
|
||||||
|
embedC := v(embed{"c"})
|
||||||
|
tests := []sortTestCase{
|
||||||
|
// No values.
|
||||||
|
{
|
||||||
|
[]reflect.Value{},
|
||||||
|
[]reflect.Value{},
|
||||||
|
},
|
||||||
|
// Bools.
|
||||||
|
{
|
||||||
|
[]reflect.Value{v(false), v(true), v(false)},
|
||||||
|
[]reflect.Value{v(false), v(false), v(true)},
|
||||||
|
},
|
||||||
|
// Ints.
|
||||||
|
{
|
||||||
|
[]reflect.Value{v(2), v(1), v(3)},
|
||||||
|
[]reflect.Value{v(1), v(2), v(3)},
|
||||||
|
},
|
||||||
|
// Uints.
|
||||||
|
{
|
||||||
|
[]reflect.Value{v(uint8(2)), v(uint8(1)), v(uint8(3))},
|
||||||
|
[]reflect.Value{v(uint8(1)), v(uint8(2)), v(uint8(3))},
|
||||||
|
},
|
||||||
|
// Floats.
|
||||||
|
{
|
||||||
|
[]reflect.Value{v(2.0), v(1.0), v(3.0)},
|
||||||
|
[]reflect.Value{v(1.0), v(2.0), v(3.0)},
|
||||||
|
},
|
||||||
|
// Strings.
|
||||||
|
{
|
||||||
|
[]reflect.Value{b, a, c},
|
||||||
|
[]reflect.Value{a, b, c},
|
||||||
|
},
|
||||||
|
// Array
|
||||||
|
{
|
||||||
|
[]reflect.Value{v([3]int{3, 2, 1}), v([3]int{1, 3, 2}), v([3]int{1, 2, 3})},
|
||||||
|
[]reflect.Value{v([3]int{1, 2, 3}), v([3]int{1, 3, 2}), v([3]int{3, 2, 1})},
|
||||||
|
},
|
||||||
|
// Uintptrs.
|
||||||
|
{
|
||||||
|
[]reflect.Value{v(uintptr(2)), v(uintptr(1)), v(uintptr(3))},
|
||||||
|
[]reflect.Value{v(uintptr(1)), v(uintptr(2)), v(uintptr(3))},
|
||||||
|
},
|
||||||
|
// SortableStructs.
|
||||||
|
{
|
||||||
|
// Note: not sorted - DisableMethods is set.
|
||||||
|
[]reflect.Value{v(sortableStruct{2}), v(sortableStruct{1}), v(sortableStruct{3})},
|
||||||
|
[]reflect.Value{v(sortableStruct{2}), v(sortableStruct{1}), v(sortableStruct{3})},
|
||||||
|
},
|
||||||
|
// UnsortableStructs.
|
||||||
|
{
|
||||||
|
// Note: not sorted - SpewKeys is false.
|
||||||
|
[]reflect.Value{v(unsortableStruct{2}), v(unsortableStruct{1}), v(unsortableStruct{3})},
|
||||||
|
[]reflect.Value{v(unsortableStruct{2}), v(unsortableStruct{1}), v(unsortableStruct{3})},
|
||||||
|
},
|
||||||
|
// Invalid.
|
||||||
|
{
|
||||||
|
[]reflect.Value{embedB, embedA, embedC},
|
||||||
|
[]reflect.Value{embedB, embedA, embedC},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
cs := spew.ConfigState{DisableMethods: true, SpewKeys: false}
|
||||||
|
helpTestSortValues(tests, &cs, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSortValuesWithMethods ensures the sort functionality for relect.Value
|
||||||
|
// based sorting works as intended when using string methods.
|
||||||
|
func TestSortValuesWithMethods(t *testing.T) {
|
||||||
|
v := reflect.ValueOf
|
||||||
|
|
||||||
|
a := v("a")
|
||||||
|
b := v("b")
|
||||||
|
c := v("c")
|
||||||
|
tests := []sortTestCase{
|
||||||
|
// Ints.
|
||||||
|
{
|
||||||
|
[]reflect.Value{v(2), v(1), v(3)},
|
||||||
|
[]reflect.Value{v(1), v(2), v(3)},
|
||||||
|
},
|
||||||
|
// Strings.
|
||||||
|
{
|
||||||
|
[]reflect.Value{b, a, c},
|
||||||
|
[]reflect.Value{a, b, c},
|
||||||
|
},
|
||||||
|
// SortableStructs.
|
||||||
|
{
|
||||||
|
[]reflect.Value{v(sortableStruct{2}), v(sortableStruct{1}), v(sortableStruct{3})},
|
||||||
|
[]reflect.Value{v(sortableStruct{1}), v(sortableStruct{2}), v(sortableStruct{3})},
|
||||||
|
},
|
||||||
|
// UnsortableStructs.
|
||||||
|
{
|
||||||
|
// Note: not sorted - SpewKeys is false.
|
||||||
|
[]reflect.Value{v(unsortableStruct{2}), v(unsortableStruct{1}), v(unsortableStruct{3})},
|
||||||
|
[]reflect.Value{v(unsortableStruct{2}), v(unsortableStruct{1}), v(unsortableStruct{3})},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
cs := spew.ConfigState{DisableMethods: false, SpewKeys: false}
|
||||||
|
helpTestSortValues(tests, &cs, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSortValuesWithSpew ensures the sort functionality for relect.Value
|
||||||
|
// based sorting works as intended when using spew to stringify keys.
|
||||||
|
func TestSortValuesWithSpew(t *testing.T) {
|
||||||
|
v := reflect.ValueOf
|
||||||
|
|
||||||
|
a := v("a")
|
||||||
|
b := v("b")
|
||||||
|
c := v("c")
|
||||||
|
tests := []sortTestCase{
|
||||||
|
// Ints.
|
||||||
|
{
|
||||||
|
[]reflect.Value{v(2), v(1), v(3)},
|
||||||
|
[]reflect.Value{v(1), v(2), v(3)},
|
||||||
|
},
|
||||||
|
// Strings.
|
||||||
|
{
|
||||||
|
[]reflect.Value{b, a, c},
|
||||||
|
[]reflect.Value{a, b, c},
|
||||||
|
},
|
||||||
|
// SortableStructs.
|
||||||
|
{
|
||||||
|
[]reflect.Value{v(sortableStruct{2}), v(sortableStruct{1}), v(sortableStruct{3})},
|
||||||
|
[]reflect.Value{v(sortableStruct{1}), v(sortableStruct{2}), v(sortableStruct{3})},
|
||||||
|
},
|
||||||
|
// UnsortableStructs.
|
||||||
|
{
|
||||||
|
[]reflect.Value{v(unsortableStruct{2}), v(unsortableStruct{1}), v(unsortableStruct{3})},
|
||||||
|
[]reflect.Value{v(unsortableStruct{1}), v(unsortableStruct{2}), v(unsortableStruct{3})},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
cs := spew.ConfigState{DisableMethods: true, SpewKeys: true}
|
||||||
|
helpTestSortValues(tests, &cs, t)
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,99 @@
|
||||||
|
// Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
// NOTE: Due to the following build constraints, this file will only be compiled
|
||||||
|
// when both cgo is supported and "-tags testcgo" is added to the go test
|
||||||
|
// command line. This means the cgo tests are only added (and hence run) when
|
||||||
|
// specifially requested. This configuration is used because spew itself
|
||||||
|
// does not require cgo to run even though it does handle certain cgo types
|
||||||
|
// specially. Rather than forcing all clients to require cgo and an external
|
||||||
|
// C compiler just to run the tests, this scheme makes them optional.
|
||||||
|
// +build cgo,testcgo
|
||||||
|
|
||||||
|
package spew_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/davecgh/go-spew/spew/testdata"
|
||||||
|
)
|
||||||
|
|
||||||
|
func addCgoDumpTests() {
|
||||||
|
// C char pointer.
|
||||||
|
v := testdata.GetCgoCharPointer()
|
||||||
|
nv := testdata.GetCgoNullCharPointer()
|
||||||
|
pv := &v
|
||||||
|
vcAddr := fmt.Sprintf("%p", v)
|
||||||
|
vAddr := fmt.Sprintf("%p", pv)
|
||||||
|
pvAddr := fmt.Sprintf("%p", &pv)
|
||||||
|
vt := "*testdata._Ctype_char"
|
||||||
|
vs := "116"
|
||||||
|
addDumpTest(v, "("+vt+")("+vcAddr+")("+vs+")\n")
|
||||||
|
addDumpTest(pv, "(*"+vt+")("+vAddr+"->"+vcAddr+")("+vs+")\n")
|
||||||
|
addDumpTest(&pv, "(**"+vt+")("+pvAddr+"->"+vAddr+"->"+vcAddr+")("+vs+")\n")
|
||||||
|
addDumpTest(nv, "("+vt+")(<nil>)\n")
|
||||||
|
|
||||||
|
// C char array.
|
||||||
|
v2, v2l, v2c := testdata.GetCgoCharArray()
|
||||||
|
v2Len := fmt.Sprintf("%d", v2l)
|
||||||
|
v2Cap := fmt.Sprintf("%d", v2c)
|
||||||
|
v2t := "[6]testdata._Ctype_char"
|
||||||
|
v2s := "(len=" + v2Len + " cap=" + v2Cap + ") " +
|
||||||
|
"{\n 00000000 74 65 73 74 32 00 " +
|
||||||
|
" |test2.|\n}"
|
||||||
|
addDumpTest(v2, "("+v2t+") "+v2s+"\n")
|
||||||
|
|
||||||
|
// C unsigned char array.
|
||||||
|
v3, v3l, v3c := testdata.GetCgoUnsignedCharArray()
|
||||||
|
v3Len := fmt.Sprintf("%d", v3l)
|
||||||
|
v3Cap := fmt.Sprintf("%d", v3c)
|
||||||
|
v3t := "[6]testdata._Ctype_unsignedchar"
|
||||||
|
v3t2 := "[6]testdata._Ctype_uchar"
|
||||||
|
v3s := "(len=" + v3Len + " cap=" + v3Cap + ") " +
|
||||||
|
"{\n 00000000 74 65 73 74 33 00 " +
|
||||||
|
" |test3.|\n}"
|
||||||
|
addDumpTest(v3, "("+v3t+") "+v3s+"\n", "("+v3t2+") "+v3s+"\n")
|
||||||
|
|
||||||
|
// C signed char array.
|
||||||
|
v4, v4l, v4c := testdata.GetCgoSignedCharArray()
|
||||||
|
v4Len := fmt.Sprintf("%d", v4l)
|
||||||
|
v4Cap := fmt.Sprintf("%d", v4c)
|
||||||
|
v4t := "[6]testdata._Ctype_schar"
|
||||||
|
v4t2 := "testdata._Ctype_schar"
|
||||||
|
v4s := "(len=" + v4Len + " cap=" + v4Cap + ") " +
|
||||||
|
"{\n (" + v4t2 + ") 116,\n (" + v4t2 + ") 101,\n (" + v4t2 +
|
||||||
|
") 115,\n (" + v4t2 + ") 116,\n (" + v4t2 + ") 52,\n (" + v4t2 +
|
||||||
|
") 0\n}"
|
||||||
|
addDumpTest(v4, "("+v4t+") "+v4s+"\n")
|
||||||
|
|
||||||
|
// C uint8_t array.
|
||||||
|
v5, v5l, v5c := testdata.GetCgoUint8tArray()
|
||||||
|
v5Len := fmt.Sprintf("%d", v5l)
|
||||||
|
v5Cap := fmt.Sprintf("%d", v5c)
|
||||||
|
v5t := "[6]testdata._Ctype_uint8_t"
|
||||||
|
v5s := "(len=" + v5Len + " cap=" + v5Cap + ") " +
|
||||||
|
"{\n 00000000 74 65 73 74 35 00 " +
|
||||||
|
" |test5.|\n}"
|
||||||
|
addDumpTest(v5, "("+v5t+") "+v5s+"\n")
|
||||||
|
|
||||||
|
// C typedefed unsigned char array.
|
||||||
|
v6, v6l, v6c := testdata.GetCgoTypdefedUnsignedCharArray()
|
||||||
|
v6Len := fmt.Sprintf("%d", v6l)
|
||||||
|
v6Cap := fmt.Sprintf("%d", v6c)
|
||||||
|
v6t := "[6]testdata._Ctype_custom_uchar_t"
|
||||||
|
v6s := "(len=" + v6Len + " cap=" + v6Cap + ") " +
|
||||||
|
"{\n 00000000 74 65 73 74 36 00 " +
|
||||||
|
" |test6.|\n}"
|
||||||
|
addDumpTest(v6, "("+v6t+") "+v6s+"\n")
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
// Copyright (c) 2013 Dave Collins <dave@davec.name>
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
// NOTE: Due to the following build constraints, this file will only be compiled
|
||||||
|
// when either cgo is not supported or "-tags testcgo" is not added to the go
|
||||||
|
// test command line. This file intentionally does not setup any cgo tests in
|
||||||
|
// this scenario.
|
||||||
|
// +build !cgo !testcgo
|
||||||
|
|
||||||
|
package spew_test
|
||||||
|
|
||||||
|
func addCgoDumpTests() {
|
||||||
|
// Don't add any tests for cgo since this file is only compiled when
|
||||||
|
// there should not be any cgo tests.
|
||||||
|
}
|
|
@ -0,0 +1,226 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package spew_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Flag int
|
||||||
|
|
||||||
|
const (
|
||||||
|
flagOne Flag = iota
|
||||||
|
flagTwo
|
||||||
|
)
|
||||||
|
|
||||||
|
var flagStrings = map[Flag]string{
|
||||||
|
flagOne: "flagOne",
|
||||||
|
flagTwo: "flagTwo",
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f Flag) String() string {
|
||||||
|
if s, ok := flagStrings[f]; ok {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("Unknown flag (%d)", int(f))
|
||||||
|
}
|
||||||
|
|
||||||
|
type Bar struct {
|
||||||
|
data uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
type Foo struct {
|
||||||
|
unexportedField Bar
|
||||||
|
ExportedField map[interface{}]interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This example demonstrates how to use Dump to dump variables to stdout.
|
||||||
|
func ExampleDump() {
|
||||||
|
// The following package level declarations are assumed for this example:
|
||||||
|
/*
|
||||||
|
type Flag int
|
||||||
|
|
||||||
|
const (
|
||||||
|
flagOne Flag = iota
|
||||||
|
flagTwo
|
||||||
|
)
|
||||||
|
|
||||||
|
var flagStrings = map[Flag]string{
|
||||||
|
flagOne: "flagOne",
|
||||||
|
flagTwo: "flagTwo",
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f Flag) String() string {
|
||||||
|
if s, ok := flagStrings[f]; ok {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("Unknown flag (%d)", int(f))
|
||||||
|
}
|
||||||
|
|
||||||
|
type Bar struct {
|
||||||
|
data uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
type Foo struct {
|
||||||
|
unexportedField Bar
|
||||||
|
ExportedField map[interface{}]interface{}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Setup some sample data structures for the example.
|
||||||
|
bar := Bar{uintptr(0)}
|
||||||
|
s1 := Foo{bar, map[interface{}]interface{}{"one": true}}
|
||||||
|
f := Flag(5)
|
||||||
|
b := []byte{
|
||||||
|
0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
|
||||||
|
0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
|
||||||
|
0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
|
||||||
|
0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30,
|
||||||
|
0x31, 0x32,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dump!
|
||||||
|
spew.Dump(s1, f, b)
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// (spew_test.Foo) {
|
||||||
|
// unexportedField: (spew_test.Bar) {
|
||||||
|
// data: (uintptr) <nil>
|
||||||
|
// },
|
||||||
|
// ExportedField: (map[interface {}]interface {}) (len=1) {
|
||||||
|
// (string) (len=3) "one": (bool) true
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// (spew_test.Flag) Unknown flag (5)
|
||||||
|
// ([]uint8) (len=34 cap=34) {
|
||||||
|
// 00000000 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 |............... |
|
||||||
|
// 00000010 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 |!"#$%&'()*+,-./0|
|
||||||
|
// 00000020 31 32 |12|
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
// This example demonstrates how to use Printf to display a variable with a
|
||||||
|
// format string and inline formatting.
|
||||||
|
func ExamplePrintf() {
|
||||||
|
// Create a double pointer to a uint 8.
|
||||||
|
ui8 := uint8(5)
|
||||||
|
pui8 := &ui8
|
||||||
|
ppui8 := &pui8
|
||||||
|
|
||||||
|
// Create a circular data type.
|
||||||
|
type circular struct {
|
||||||
|
ui8 uint8
|
||||||
|
c *circular
|
||||||
|
}
|
||||||
|
c := circular{ui8: 1}
|
||||||
|
c.c = &c
|
||||||
|
|
||||||
|
// Print!
|
||||||
|
spew.Printf("ppui8: %v\n", ppui8)
|
||||||
|
spew.Printf("circular: %v\n", c)
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// ppui8: <**>5
|
||||||
|
// circular: {1 <*>{1 <*><shown>}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This example demonstrates how to use a ConfigState.
|
||||||
|
func ExampleConfigState() {
|
||||||
|
// Modify the indent level of the ConfigState only. The global
|
||||||
|
// configuration is not modified.
|
||||||
|
scs := spew.ConfigState{Indent: "\t"}
|
||||||
|
|
||||||
|
// Output using the ConfigState instance.
|
||||||
|
v := map[string]int{"one": 1}
|
||||||
|
scs.Printf("v: %v\n", v)
|
||||||
|
scs.Dump(v)
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// v: map[one:1]
|
||||||
|
// (map[string]int) (len=1) {
|
||||||
|
// (string) (len=3) "one": (int) 1
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
// This example demonstrates how to use ConfigState.Dump to dump variables to
|
||||||
|
// stdout
|
||||||
|
func ExampleConfigState_Dump() {
|
||||||
|
// See the top-level Dump example for details on the types used in this
|
||||||
|
// example.
|
||||||
|
|
||||||
|
// Create two ConfigState instances with different indentation.
|
||||||
|
scs := spew.ConfigState{Indent: "\t"}
|
||||||
|
scs2 := spew.ConfigState{Indent: " "}
|
||||||
|
|
||||||
|
// Setup some sample data structures for the example.
|
||||||
|
bar := Bar{uintptr(0)}
|
||||||
|
s1 := Foo{bar, map[interface{}]interface{}{"one": true}}
|
||||||
|
|
||||||
|
// Dump using the ConfigState instances.
|
||||||
|
scs.Dump(s1)
|
||||||
|
scs2.Dump(s1)
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// (spew_test.Foo) {
|
||||||
|
// unexportedField: (spew_test.Bar) {
|
||||||
|
// data: (uintptr) <nil>
|
||||||
|
// },
|
||||||
|
// ExportedField: (map[interface {}]interface {}) (len=1) {
|
||||||
|
// (string) (len=3) "one": (bool) true
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// (spew_test.Foo) {
|
||||||
|
// unexportedField: (spew_test.Bar) {
|
||||||
|
// data: (uintptr) <nil>
|
||||||
|
// },
|
||||||
|
// ExportedField: (map[interface {}]interface {}) (len=1) {
|
||||||
|
// (string) (len=3) "one": (bool) true
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
// This example demonstrates how to use ConfigState.Printf to display a variable
|
||||||
|
// with a format string and inline formatting.
|
||||||
|
func ExampleConfigState_Printf() {
|
||||||
|
// See the top-level Dump example for details on the types used in this
|
||||||
|
// example.
|
||||||
|
|
||||||
|
// Create two ConfigState instances and modify the method handling of the
|
||||||
|
// first ConfigState only.
|
||||||
|
scs := spew.NewDefaultConfig()
|
||||||
|
scs2 := spew.NewDefaultConfig()
|
||||||
|
scs.DisableMethods = true
|
||||||
|
|
||||||
|
// Alternatively
|
||||||
|
// scs := spew.ConfigState{Indent: " ", DisableMethods: true}
|
||||||
|
// scs2 := spew.ConfigState{Indent: " "}
|
||||||
|
|
||||||
|
// This is of type Flag which implements a Stringer and has raw value 1.
|
||||||
|
f := flagTwo
|
||||||
|
|
||||||
|
// Dump using the ConfigState instances.
|
||||||
|
scs.Printf("f: %v\n", f)
|
||||||
|
scs2.Printf("f: %v\n", f)
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// f: 1
|
||||||
|
// f: flagTwo
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,87 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
This test file is part of the spew package rather than than the spew_test
|
||||||
|
package because it needs access to internals to properly test certain cases
|
||||||
|
which are not possible via the public interface since they should never happen.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package spew
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// dummyFmtState implements a fake fmt.State to use for testing invalid
|
||||||
|
// reflect.Value handling. This is necessary because the fmt package catches
|
||||||
|
// invalid values before invoking the formatter on them.
|
||||||
|
type dummyFmtState struct {
|
||||||
|
bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dfs *dummyFmtState) Flag(f int) bool {
|
||||||
|
if f == int('+') {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dfs *dummyFmtState) Precision() (int, bool) {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dfs *dummyFmtState) Width() (int, bool) {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestInvalidReflectValue ensures the dump and formatter code handles an
|
||||||
|
// invalid reflect value properly. This needs access to internal state since it
|
||||||
|
// should never happen in real code and therefore can't be tested via the public
|
||||||
|
// API.
|
||||||
|
func TestInvalidReflectValue(t *testing.T) {
|
||||||
|
i := 1
|
||||||
|
|
||||||
|
// Dump invalid reflect value.
|
||||||
|
v := new(reflect.Value)
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
d := dumpState{w: buf, cs: &Config}
|
||||||
|
d.dump(*v)
|
||||||
|
s := buf.String()
|
||||||
|
want := "<invalid>"
|
||||||
|
if s != want {
|
||||||
|
t.Errorf("InvalidReflectValue #%d\n got: %s want: %s", i, s, want)
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
|
||||||
|
// Formatter invalid reflect value.
|
||||||
|
buf2 := new(dummyFmtState)
|
||||||
|
f := formatState{value: *v, cs: &Config, fs: buf2}
|
||||||
|
f.format(*v)
|
||||||
|
s = buf2.String()
|
||||||
|
want = "<invalid>"
|
||||||
|
if s != want {
|
||||||
|
t.Errorf("InvalidReflectValue #%d got: %s want: %s", i, s, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SortValues makes the internal sortValues function available to the test
|
||||||
|
// package.
|
||||||
|
func SortValues(values []reflect.Value, cs *ConfigState) {
|
||||||
|
sortValues(values, cs)
|
||||||
|
}
|
|
@ -0,0 +1,102 @@
|
||||||
|
// Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||||
|
|
||||||
|
// Permission to use, copy, modify, and distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
// NOTE: Due to the following build constraints, this file will only be compiled
|
||||||
|
// when the code is not running on Google App Engine, compiled by GopherJS, and
|
||||||
|
// "-tags safe" is not added to the go build command line. The "disableunsafe"
|
||||||
|
// tag is deprecated and thus should not be used.
|
||||||
|
// +build !js,!appengine,!safe,!disableunsafe
|
||||||
|
|
||||||
|
/*
|
||||||
|
This test file is part of the spew package rather than than the spew_test
|
||||||
|
package because it needs access to internals to properly test certain cases
|
||||||
|
which are not possible via the public interface since they should never happen.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package spew
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// changeKind uses unsafe to intentionally change the kind of a reflect.Value to
|
||||||
|
// the maximum kind value which does not exist. This is needed to test the
|
||||||
|
// fallback code which punts to the standard fmt library for new types that
|
||||||
|
// might get added to the language.
|
||||||
|
func changeKind(v *reflect.Value, readOnly bool) {
|
||||||
|
rvf := (*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(v)) + offsetFlag))
|
||||||
|
*rvf = *rvf | ((1<<flagKindWidth - 1) << flagKindShift)
|
||||||
|
if readOnly {
|
||||||
|
*rvf |= flagRO
|
||||||
|
} else {
|
||||||
|
*rvf &= ^uintptr(flagRO)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestAddedReflectValue tests functionaly of the dump and formatter code which
|
||||||
|
// falls back to the standard fmt library for new types that might get added to
|
||||||
|
// the language.
|
||||||
|
func TestAddedReflectValue(t *testing.T) {
|
||||||
|
i := 1
|
||||||
|
|
||||||
|
// Dump using a reflect.Value that is exported.
|
||||||
|
v := reflect.ValueOf(int8(5))
|
||||||
|
changeKind(&v, false)
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
d := dumpState{w: buf, cs: &Config}
|
||||||
|
d.dump(v)
|
||||||
|
s := buf.String()
|
||||||
|
want := "(int8) 5"
|
||||||
|
if s != want {
|
||||||
|
t.Errorf("TestAddedReflectValue #%d\n got: %s want: %s", i, s, want)
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
|
||||||
|
// Dump using a reflect.Value that is not exported.
|
||||||
|
changeKind(&v, true)
|
||||||
|
buf.Reset()
|
||||||
|
d.dump(v)
|
||||||
|
s = buf.String()
|
||||||
|
want = "(int8) <int8 Value>"
|
||||||
|
if s != want {
|
||||||
|
t.Errorf("TestAddedReflectValue #%d\n got: %s want: %s", i, s, want)
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
|
||||||
|
// Formatter using a reflect.Value that is exported.
|
||||||
|
changeKind(&v, false)
|
||||||
|
buf2 := new(dummyFmtState)
|
||||||
|
f := formatState{value: v, cs: &Config, fs: buf2}
|
||||||
|
f.format(v)
|
||||||
|
s = buf2.String()
|
||||||
|
want = "5"
|
||||||
|
if s != want {
|
||||||
|
t.Errorf("TestAddedReflectValue #%d got: %s want: %s", i, s, want)
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
|
||||||
|
// Formatter using a reflect.Value that is not exported.
|
||||||
|
changeKind(&v, true)
|
||||||
|
buf2.Reset()
|
||||||
|
f = formatState{value: v, cs: &Config, fs: buf2}
|
||||||
|
f.format(v)
|
||||||
|
s = buf2.String()
|
||||||
|
want = "<int8 Value>"
|
||||||
|
if s != want {
|
||||||
|
t.Errorf("TestAddedReflectValue #%d got: %s want: %s", i, s, want)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,320 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package spew_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
)
|
||||||
|
|
||||||
|
// spewFunc is used to identify which public function of the spew package or
|
||||||
|
// ConfigState a test applies to.
|
||||||
|
type spewFunc int
|
||||||
|
|
||||||
|
const (
|
||||||
|
fCSFdump spewFunc = iota
|
||||||
|
fCSFprint
|
||||||
|
fCSFprintf
|
||||||
|
fCSFprintln
|
||||||
|
fCSPrint
|
||||||
|
fCSPrintln
|
||||||
|
fCSSdump
|
||||||
|
fCSSprint
|
||||||
|
fCSSprintf
|
||||||
|
fCSSprintln
|
||||||
|
fCSErrorf
|
||||||
|
fCSNewFormatter
|
||||||
|
fErrorf
|
||||||
|
fFprint
|
||||||
|
fFprintln
|
||||||
|
fPrint
|
||||||
|
fPrintln
|
||||||
|
fSdump
|
||||||
|
fSprint
|
||||||
|
fSprintf
|
||||||
|
fSprintln
|
||||||
|
)
|
||||||
|
|
||||||
|
// Map of spewFunc values to names for pretty printing.
|
||||||
|
var spewFuncStrings = map[spewFunc]string{
|
||||||
|
fCSFdump: "ConfigState.Fdump",
|
||||||
|
fCSFprint: "ConfigState.Fprint",
|
||||||
|
fCSFprintf: "ConfigState.Fprintf",
|
||||||
|
fCSFprintln: "ConfigState.Fprintln",
|
||||||
|
fCSSdump: "ConfigState.Sdump",
|
||||||
|
fCSPrint: "ConfigState.Print",
|
||||||
|
fCSPrintln: "ConfigState.Println",
|
||||||
|
fCSSprint: "ConfigState.Sprint",
|
||||||
|
fCSSprintf: "ConfigState.Sprintf",
|
||||||
|
fCSSprintln: "ConfigState.Sprintln",
|
||||||
|
fCSErrorf: "ConfigState.Errorf",
|
||||||
|
fCSNewFormatter: "ConfigState.NewFormatter",
|
||||||
|
fErrorf: "spew.Errorf",
|
||||||
|
fFprint: "spew.Fprint",
|
||||||
|
fFprintln: "spew.Fprintln",
|
||||||
|
fPrint: "spew.Print",
|
||||||
|
fPrintln: "spew.Println",
|
||||||
|
fSdump: "spew.Sdump",
|
||||||
|
fSprint: "spew.Sprint",
|
||||||
|
fSprintf: "spew.Sprintf",
|
||||||
|
fSprintln: "spew.Sprintln",
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f spewFunc) String() string {
|
||||||
|
if s, ok := spewFuncStrings[f]; ok {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("Unknown spewFunc (%d)", int(f))
|
||||||
|
}
|
||||||
|
|
||||||
|
// spewTest is used to describe a test to be performed against the public
|
||||||
|
// functions of the spew package or ConfigState.
|
||||||
|
type spewTest struct {
|
||||||
|
cs *spew.ConfigState
|
||||||
|
f spewFunc
|
||||||
|
format string
|
||||||
|
in interface{}
|
||||||
|
want string
|
||||||
|
}
|
||||||
|
|
||||||
|
// spewTests houses the tests to be performed against the public functions of
|
||||||
|
// the spew package and ConfigState.
|
||||||
|
//
|
||||||
|
// These tests are only intended to ensure the public functions are exercised
|
||||||
|
// and are intentionally not exhaustive of types. The exhaustive type
|
||||||
|
// tests are handled in the dump and format tests.
|
||||||
|
var spewTests []spewTest
|
||||||
|
|
||||||
|
// redirStdout is a helper function to return the standard output from f as a
|
||||||
|
// byte slice.
|
||||||
|
func redirStdout(f func()) ([]byte, error) {
|
||||||
|
tempFile, err := ioutil.TempFile("", "ss-test")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
fileName := tempFile.Name()
|
||||||
|
defer os.Remove(fileName) // Ignore error
|
||||||
|
|
||||||
|
origStdout := os.Stdout
|
||||||
|
os.Stdout = tempFile
|
||||||
|
f()
|
||||||
|
os.Stdout = origStdout
|
||||||
|
tempFile.Close()
|
||||||
|
|
||||||
|
return ioutil.ReadFile(fileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func initSpewTests() {
|
||||||
|
// Config states with various settings.
|
||||||
|
scsDefault := spew.NewDefaultConfig()
|
||||||
|
scsNoMethods := &spew.ConfigState{Indent: " ", DisableMethods: true}
|
||||||
|
scsNoPmethods := &spew.ConfigState{Indent: " ", DisablePointerMethods: true}
|
||||||
|
scsMaxDepth := &spew.ConfigState{Indent: " ", MaxDepth: 1}
|
||||||
|
scsContinue := &spew.ConfigState{Indent: " ", ContinueOnMethod: true}
|
||||||
|
scsNoPtrAddr := &spew.ConfigState{DisablePointerAddresses: true}
|
||||||
|
scsNoCap := &spew.ConfigState{DisableCapacities: true}
|
||||||
|
|
||||||
|
// Variables for tests on types which implement Stringer interface with and
|
||||||
|
// without a pointer receiver.
|
||||||
|
ts := stringer("test")
|
||||||
|
tps := pstringer("test")
|
||||||
|
|
||||||
|
type ptrTester struct {
|
||||||
|
s *struct{}
|
||||||
|
}
|
||||||
|
tptr := &ptrTester{s: &struct{}{}}
|
||||||
|
|
||||||
|
// depthTester is used to test max depth handling for structs, array, slices
|
||||||
|
// and maps.
|
||||||
|
type depthTester struct {
|
||||||
|
ic indirCir1
|
||||||
|
arr [1]string
|
||||||
|
slice []string
|
||||||
|
m map[string]int
|
||||||
|
}
|
||||||
|
dt := depthTester{indirCir1{nil}, [1]string{"arr"}, []string{"slice"},
|
||||||
|
map[string]int{"one": 1}}
|
||||||
|
|
||||||
|
// Variable for tests on types which implement error interface.
|
||||||
|
te := customError(10)
|
||||||
|
|
||||||
|
spewTests = []spewTest{
|
||||||
|
{scsDefault, fCSFdump, "", int8(127), "(int8) 127\n"},
|
||||||
|
{scsDefault, fCSFprint, "", int16(32767), "32767"},
|
||||||
|
{scsDefault, fCSFprintf, "%v", int32(2147483647), "2147483647"},
|
||||||
|
{scsDefault, fCSFprintln, "", int(2147483647), "2147483647\n"},
|
||||||
|
{scsDefault, fCSPrint, "", int64(9223372036854775807), "9223372036854775807"},
|
||||||
|
{scsDefault, fCSPrintln, "", uint8(255), "255\n"},
|
||||||
|
{scsDefault, fCSSdump, "", uint8(64), "(uint8) 64\n"},
|
||||||
|
{scsDefault, fCSSprint, "", complex(1, 2), "(1+2i)"},
|
||||||
|
{scsDefault, fCSSprintf, "%v", complex(float32(3), 4), "(3+4i)"},
|
||||||
|
{scsDefault, fCSSprintln, "", complex(float64(5), 6), "(5+6i)\n"},
|
||||||
|
{scsDefault, fCSErrorf, "%#v", uint16(65535), "(uint16)65535"},
|
||||||
|
{scsDefault, fCSNewFormatter, "%v", uint32(4294967295), "4294967295"},
|
||||||
|
{scsDefault, fErrorf, "%v", uint64(18446744073709551615), "18446744073709551615"},
|
||||||
|
{scsDefault, fFprint, "", float32(3.14), "3.14"},
|
||||||
|
{scsDefault, fFprintln, "", float64(6.28), "6.28\n"},
|
||||||
|
{scsDefault, fPrint, "", true, "true"},
|
||||||
|
{scsDefault, fPrintln, "", false, "false\n"},
|
||||||
|
{scsDefault, fSdump, "", complex(-10, -20), "(complex128) (-10-20i)\n"},
|
||||||
|
{scsDefault, fSprint, "", complex(-1, -2), "(-1-2i)"},
|
||||||
|
{scsDefault, fSprintf, "%v", complex(float32(-3), -4), "(-3-4i)"},
|
||||||
|
{scsDefault, fSprintln, "", complex(float64(-5), -6), "(-5-6i)\n"},
|
||||||
|
{scsNoMethods, fCSFprint, "", ts, "test"},
|
||||||
|
{scsNoMethods, fCSFprint, "", &ts, "<*>test"},
|
||||||
|
{scsNoMethods, fCSFprint, "", tps, "test"},
|
||||||
|
{scsNoMethods, fCSFprint, "", &tps, "<*>test"},
|
||||||
|
{scsNoPmethods, fCSFprint, "", ts, "stringer test"},
|
||||||
|
{scsNoPmethods, fCSFprint, "", &ts, "<*>stringer test"},
|
||||||
|
{scsNoPmethods, fCSFprint, "", tps, "test"},
|
||||||
|
{scsNoPmethods, fCSFprint, "", &tps, "<*>stringer test"},
|
||||||
|
{scsMaxDepth, fCSFprint, "", dt, "{{<max>} [<max>] [<max>] map[<max>]}"},
|
||||||
|
{scsMaxDepth, fCSFdump, "", dt, "(spew_test.depthTester) {\n" +
|
||||||
|
" ic: (spew_test.indirCir1) {\n <max depth reached>\n },\n" +
|
||||||
|
" arr: ([1]string) (len=1 cap=1) {\n <max depth reached>\n },\n" +
|
||||||
|
" slice: ([]string) (len=1 cap=1) {\n <max depth reached>\n },\n" +
|
||||||
|
" m: (map[string]int) (len=1) {\n <max depth reached>\n }\n}\n"},
|
||||||
|
{scsContinue, fCSFprint, "", ts, "(stringer test) test"},
|
||||||
|
{scsContinue, fCSFdump, "", ts, "(spew_test.stringer) " +
|
||||||
|
"(len=4) (stringer test) \"test\"\n"},
|
||||||
|
{scsContinue, fCSFprint, "", te, "(error: 10) 10"},
|
||||||
|
{scsContinue, fCSFdump, "", te, "(spew_test.customError) " +
|
||||||
|
"(error: 10) 10\n"},
|
||||||
|
{scsNoPtrAddr, fCSFprint, "", tptr, "<*>{<*>{}}"},
|
||||||
|
{scsNoPtrAddr, fCSSdump, "", tptr, "(*spew_test.ptrTester)({\ns: (*struct {})({\n})\n})\n"},
|
||||||
|
{scsNoCap, fCSSdump, "", make([]string, 0, 10), "([]string) {\n}\n"},
|
||||||
|
{scsNoCap, fCSSdump, "", make([]string, 1, 10), "([]string) (len=1) {\n(string) \"\"\n}\n"},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSpew executes all of the tests described by spewTests.
|
||||||
|
func TestSpew(t *testing.T) {
|
||||||
|
initSpewTests()
|
||||||
|
|
||||||
|
t.Logf("Running %d tests", len(spewTests))
|
||||||
|
for i, test := range spewTests {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
switch test.f {
|
||||||
|
case fCSFdump:
|
||||||
|
test.cs.Fdump(buf, test.in)
|
||||||
|
|
||||||
|
case fCSFprint:
|
||||||
|
test.cs.Fprint(buf, test.in)
|
||||||
|
|
||||||
|
case fCSFprintf:
|
||||||
|
test.cs.Fprintf(buf, test.format, test.in)
|
||||||
|
|
||||||
|
case fCSFprintln:
|
||||||
|
test.cs.Fprintln(buf, test.in)
|
||||||
|
|
||||||
|
case fCSPrint:
|
||||||
|
b, err := redirStdout(func() { test.cs.Print(test.in) })
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%v #%d %v", test.f, i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
buf.Write(b)
|
||||||
|
|
||||||
|
case fCSPrintln:
|
||||||
|
b, err := redirStdout(func() { test.cs.Println(test.in) })
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%v #%d %v", test.f, i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
buf.Write(b)
|
||||||
|
|
||||||
|
case fCSSdump:
|
||||||
|
str := test.cs.Sdump(test.in)
|
||||||
|
buf.WriteString(str)
|
||||||
|
|
||||||
|
case fCSSprint:
|
||||||
|
str := test.cs.Sprint(test.in)
|
||||||
|
buf.WriteString(str)
|
||||||
|
|
||||||
|
case fCSSprintf:
|
||||||
|
str := test.cs.Sprintf(test.format, test.in)
|
||||||
|
buf.WriteString(str)
|
||||||
|
|
||||||
|
case fCSSprintln:
|
||||||
|
str := test.cs.Sprintln(test.in)
|
||||||
|
buf.WriteString(str)
|
||||||
|
|
||||||
|
case fCSErrorf:
|
||||||
|
err := test.cs.Errorf(test.format, test.in)
|
||||||
|
buf.WriteString(err.Error())
|
||||||
|
|
||||||
|
case fCSNewFormatter:
|
||||||
|
fmt.Fprintf(buf, test.format, test.cs.NewFormatter(test.in))
|
||||||
|
|
||||||
|
case fErrorf:
|
||||||
|
err := spew.Errorf(test.format, test.in)
|
||||||
|
buf.WriteString(err.Error())
|
||||||
|
|
||||||
|
case fFprint:
|
||||||
|
spew.Fprint(buf, test.in)
|
||||||
|
|
||||||
|
case fFprintln:
|
||||||
|
spew.Fprintln(buf, test.in)
|
||||||
|
|
||||||
|
case fPrint:
|
||||||
|
b, err := redirStdout(func() { spew.Print(test.in) })
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%v #%d %v", test.f, i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
buf.Write(b)
|
||||||
|
|
||||||
|
case fPrintln:
|
||||||
|
b, err := redirStdout(func() { spew.Println(test.in) })
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%v #%d %v", test.f, i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
buf.Write(b)
|
||||||
|
|
||||||
|
case fSdump:
|
||||||
|
str := spew.Sdump(test.in)
|
||||||
|
buf.WriteString(str)
|
||||||
|
|
||||||
|
case fSprint:
|
||||||
|
str := spew.Sprint(test.in)
|
||||||
|
buf.WriteString(str)
|
||||||
|
|
||||||
|
case fSprintf:
|
||||||
|
str := spew.Sprintf(test.format, test.in)
|
||||||
|
buf.WriteString(str)
|
||||||
|
|
||||||
|
case fSprintln:
|
||||||
|
str := spew.Sprintln(test.in)
|
||||||
|
buf.WriteString(str)
|
||||||
|
|
||||||
|
default:
|
||||||
|
t.Errorf("%v #%d unrecognized function", test.f, i)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
s := buf.String()
|
||||||
|
if test.want != s {
|
||||||
|
t.Errorf("ConfigState #%d\n got: %s want: %s", i, s, test.want)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
// Copyright (c) 2013 Dave Collins <dave@davec.name>
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
// NOTE: Due to the following build constraints, this file will only be compiled
|
||||||
|
// when both cgo is supported and "-tags testcgo" is added to the go test
|
||||||
|
// command line. This code should really only be in the dumpcgo_test.go file,
|
||||||
|
// but unfortunately Go will not allow cgo in test files, so this is a
|
||||||
|
// workaround to allow cgo types to be tested. This configuration is used
|
||||||
|
// because spew itself does not require cgo to run even though it does handle
|
||||||
|
// certain cgo types specially. Rather than forcing all clients to require cgo
|
||||||
|
// and an external C compiler just to run the tests, this scheme makes them
|
||||||
|
// optional.
|
||||||
|
// +build cgo,testcgo
|
||||||
|
|
||||||
|
package testdata
|
||||||
|
|
||||||
|
/*
|
||||||
|
#include <stdint.h>
|
||||||
|
typedef unsigned char custom_uchar_t;
|
||||||
|
|
||||||
|
char *ncp = 0;
|
||||||
|
char *cp = "test";
|
||||||
|
char ca[6] = {'t', 'e', 's', 't', '2', '\0'};
|
||||||
|
unsigned char uca[6] = {'t', 'e', 's', 't', '3', '\0'};
|
||||||
|
signed char sca[6] = {'t', 'e', 's', 't', '4', '\0'};
|
||||||
|
uint8_t ui8ta[6] = {'t', 'e', 's', 't', '5', '\0'};
|
||||||
|
custom_uchar_t tuca[6] = {'t', 'e', 's', 't', '6', '\0'};
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
// GetCgoNullCharPointer returns a null char pointer via cgo. This is only
|
||||||
|
// used for tests.
|
||||||
|
func GetCgoNullCharPointer() interface{} {
|
||||||
|
return C.ncp
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCgoCharPointer returns a char pointer via cgo. This is only used for
|
||||||
|
// tests.
|
||||||
|
func GetCgoCharPointer() interface{} {
|
||||||
|
return C.cp
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCgoCharArray returns a char array via cgo and the array's len and cap.
|
||||||
|
// This is only used for tests.
|
||||||
|
func GetCgoCharArray() (interface{}, int, int) {
|
||||||
|
return C.ca, len(C.ca), cap(C.ca)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCgoUnsignedCharArray returns an unsigned char array via cgo and the
|
||||||
|
// array's len and cap. This is only used for tests.
|
||||||
|
func GetCgoUnsignedCharArray() (interface{}, int, int) {
|
||||||
|
return C.uca, len(C.uca), cap(C.uca)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCgoSignedCharArray returns a signed char array via cgo and the array's len
|
||||||
|
// and cap. This is only used for tests.
|
||||||
|
func GetCgoSignedCharArray() (interface{}, int, int) {
|
||||||
|
return C.sca, len(C.sca), cap(C.sca)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCgoUint8tArray returns a uint8_t array via cgo and the array's len and
|
||||||
|
// cap. This is only used for tests.
|
||||||
|
func GetCgoUint8tArray() (interface{}, int, int) {
|
||||||
|
return C.ui8ta, len(C.ui8ta), cap(C.ui8ta)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCgoTypdefedUnsignedCharArray returns a typedefed unsigned char array via
|
||||||
|
// cgo and the array's len and cap. This is only used for tests.
|
||||||
|
func GetCgoTypdefedUnsignedCharArray() (interface{}, int, int) {
|
||||||
|
return C.tuca, len(C.tuca), cap(C.tuca)
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
|
||||||
|
github.com/davecgh/go-spew/spew/dump.go dumpState.dump 100.00% (88/88)
|
||||||
|
github.com/davecgh/go-spew/spew/format.go formatState.format 100.00% (82/82)
|
||||||
|
github.com/davecgh/go-spew/spew/format.go formatState.formatPtr 100.00% (52/52)
|
||||||
|
github.com/davecgh/go-spew/spew/dump.go dumpState.dumpPtr 100.00% (44/44)
|
||||||
|
github.com/davecgh/go-spew/spew/dump.go dumpState.dumpSlice 100.00% (39/39)
|
||||||
|
github.com/davecgh/go-spew/spew/common.go handleMethods 100.00% (30/30)
|
||||||
|
github.com/davecgh/go-spew/spew/common.go printHexPtr 100.00% (18/18)
|
||||||
|
github.com/davecgh/go-spew/spew/common.go unsafeReflectValue 100.00% (13/13)
|
||||||
|
github.com/davecgh/go-spew/spew/format.go formatState.constructOrigFormat 100.00% (12/12)
|
||||||
|
github.com/davecgh/go-spew/spew/dump.go fdump 100.00% (11/11)
|
||||||
|
github.com/davecgh/go-spew/spew/format.go formatState.Format 100.00% (11/11)
|
||||||
|
github.com/davecgh/go-spew/spew/common.go init 100.00% (10/10)
|
||||||
|
github.com/davecgh/go-spew/spew/common.go printComplex 100.00% (9/9)
|
||||||
|
github.com/davecgh/go-spew/spew/common.go valuesSorter.Less 100.00% (8/8)
|
||||||
|
github.com/davecgh/go-spew/spew/format.go formatState.buildDefaultFormat 100.00% (7/7)
|
||||||
|
github.com/davecgh/go-spew/spew/format.go formatState.unpackValue 100.00% (5/5)
|
||||||
|
github.com/davecgh/go-spew/spew/dump.go dumpState.indent 100.00% (4/4)
|
||||||
|
github.com/davecgh/go-spew/spew/common.go catchPanic 100.00% (4/4)
|
||||||
|
github.com/davecgh/go-spew/spew/config.go ConfigState.convertArgs 100.00% (4/4)
|
||||||
|
github.com/davecgh/go-spew/spew/spew.go convertArgs 100.00% (4/4)
|
||||||
|
github.com/davecgh/go-spew/spew/format.go newFormatter 100.00% (3/3)
|
||||||
|
github.com/davecgh/go-spew/spew/dump.go Sdump 100.00% (3/3)
|
||||||
|
github.com/davecgh/go-spew/spew/common.go printBool 100.00% (3/3)
|
||||||
|
github.com/davecgh/go-spew/spew/common.go sortValues 100.00% (3/3)
|
||||||
|
github.com/davecgh/go-spew/spew/config.go ConfigState.Sdump 100.00% (3/3)
|
||||||
|
github.com/davecgh/go-spew/spew/dump.go dumpState.unpackValue 100.00% (3/3)
|
||||||
|
github.com/davecgh/go-spew/spew/spew.go Printf 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/spew.go Println 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/spew.go Sprint 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/spew.go Sprintf 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/spew.go Sprintln 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/common.go printFloat 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/config.go NewDefaultConfig 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/common.go printInt 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/common.go printUint 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/common.go valuesSorter.Len 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/common.go valuesSorter.Swap 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/config.go ConfigState.Errorf 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/config.go ConfigState.Fprint 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/config.go ConfigState.Fprintf 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/config.go ConfigState.Fprintln 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/config.go ConfigState.Print 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/config.go ConfigState.Printf 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/config.go ConfigState.Println 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/config.go ConfigState.Sprint 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/config.go ConfigState.Sprintf 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/config.go ConfigState.Sprintln 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/config.go ConfigState.NewFormatter 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/config.go ConfigState.Fdump 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/config.go ConfigState.Dump 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/dump.go Fdump 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/dump.go Dump 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/spew.go Fprintln 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/format.go NewFormatter 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/spew.go Errorf 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/spew.go Fprint 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/spew.go Fprintf 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew/spew.go Print 100.00% (1/1)
|
||||||
|
github.com/davecgh/go-spew/spew ------------------------------- 100.00% (505/505)
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,37 @@
|
||||||
|
package docopt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExampleParse() {
|
||||||
|
usage := `Usage:
|
||||||
|
config_example tcp [<host>] [--force] [--timeout=<seconds>]
|
||||||
|
config_example serial <port> [--baud=<rate>] [--timeout=<seconds>]
|
||||||
|
config_example -h | --help | --version`
|
||||||
|
// parse the command line `comfig_example tcp 127.0.0.1 --force`
|
||||||
|
argv := []string{"tcp", "127.0.0.1", "--force"}
|
||||||
|
arguments, _ := Parse(usage, argv, true, "0.1.1rc", false)
|
||||||
|
// sort the keys of the arguments map
|
||||||
|
var keys []string
|
||||||
|
for k := range arguments {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
// print the argument keys and values
|
||||||
|
for _, k := range keys {
|
||||||
|
fmt.Printf("%9s %v\n", k, arguments[k])
|
||||||
|
}
|
||||||
|
// output:
|
||||||
|
// --baud <nil>
|
||||||
|
// --force true
|
||||||
|
// --help false
|
||||||
|
// --timeout <nil>
|
||||||
|
// --version false
|
||||||
|
// -h false
|
||||||
|
// <host> 127.0.0.1
|
||||||
|
// <port> <nil>
|
||||||
|
// serial false
|
||||||
|
// tcp true
|
||||||
|
}
|
29
vendor/github.com/docopt/docopt-go/examples/arguments/arguments_example.go
generated
vendored
Normal file
29
vendor/github.com/docopt/docopt-go/examples/arguments/arguments_example.go
generated
vendored
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/docopt/docopt-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
usage := `Usage: arguments_example [-vqrh] [FILE] ...
|
||||||
|
arguments_example (--left | --right) CORRECTION FILE
|
||||||
|
|
||||||
|
Process FILE and optionally apply correction to either left-hand side or
|
||||||
|
right-hand side.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
FILE optional input file
|
||||||
|
CORRECTION correction angle, needs FILE, --left or --right to be present
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-h --help
|
||||||
|
-v verbose mode
|
||||||
|
-q quiet mode
|
||||||
|
-r make report
|
||||||
|
--left use left-hand side
|
||||||
|
--right use right-hand side`
|
||||||
|
|
||||||
|
arguments, _ := docopt.Parse(usage, nil, true, "", false)
|
||||||
|
fmt.Println(arguments)
|
||||||
|
}
|
26
vendor/github.com/docopt/docopt-go/examples/calculator/calculator_example.go
generated
vendored
Normal file
26
vendor/github.com/docopt/docopt-go/examples/calculator/calculator_example.go
generated
vendored
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/docopt/docopt-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
usage := `Not a serious example.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
calculator_example <value> ( ( + | - | * | / ) <value> )...
|
||||||
|
calculator_example <function> <value> [( , <value> )]...
|
||||||
|
calculator_example (-h | --help)
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
calculator_example 1 + 2 + 3 + 4 + 5
|
||||||
|
calculator_example 1 + 2 '*' 3 / 4 - 5 # note quotes around '*'
|
||||||
|
calculator_example sum 10 , 20 , 30 , 40
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-h, --help
|
||||||
|
`
|
||||||
|
arguments, _ := docopt.Parse(usage, nil, true, "", false)
|
||||||
|
fmt.Println(arguments)
|
||||||
|
}
|
76
vendor/github.com/docopt/docopt-go/examples/config_file/config_file_example.go
generated
vendored
Normal file
76
vendor/github.com/docopt/docopt-go/examples/config_file/config_file_example.go
generated
vendored
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/docopt/docopt-go"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func loadJSONConfig() map[string]interface{} {
|
||||||
|
var result map[string]interface{}
|
||||||
|
jsonData := []byte(`{"--force": true, "--timeout": "10", "--baud": "9600"}`)
|
||||||
|
json.Unmarshal(jsonData, &result)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadIniConfig() map[string]interface{} {
|
||||||
|
iniData := `
|
||||||
|
[default-arguments]
|
||||||
|
--force
|
||||||
|
--baud=19200
|
||||||
|
<host>=localhost`
|
||||||
|
// trivial ini parser
|
||||||
|
// default value for an item is bool: true (for --force)
|
||||||
|
// otherwise the value is a string
|
||||||
|
iniParsed := make(map[string]map[string]interface{})
|
||||||
|
var section string
|
||||||
|
for _, line := range strings.Split(iniData, "\n") {
|
||||||
|
if strings.HasPrefix(line, "[") {
|
||||||
|
section = line
|
||||||
|
iniParsed[section] = make(map[string]interface{})
|
||||||
|
} else if section != "" {
|
||||||
|
kv := strings.SplitN(line, "=", 2)
|
||||||
|
if len(kv) == 1 {
|
||||||
|
iniParsed[section][kv[0]] = true
|
||||||
|
} else if len(kv) == 2 {
|
||||||
|
iniParsed[section][kv[0]] = kv[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return iniParsed["[default-arguments]"]
|
||||||
|
}
|
||||||
|
|
||||||
|
// merge combines two maps.
|
||||||
|
// truthiness takes priority over falsiness
|
||||||
|
// mapA takes priority over mapB
|
||||||
|
func merge(mapA, mapB map[string]interface{}) map[string]interface{} {
|
||||||
|
result := make(map[string]interface{})
|
||||||
|
for k, v := range mapA {
|
||||||
|
result[k] = v
|
||||||
|
}
|
||||||
|
for k, v := range mapB {
|
||||||
|
if _, ok := result[k]; !ok || result[k] == nil || result[k] == false {
|
||||||
|
result[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
usage := `Usage:
|
||||||
|
config_file_example tcp [<host>] [--force] [--timeout=<seconds>]
|
||||||
|
config_file_example serial <port> [--baud=<rate>] [--timeout=<seconds>]
|
||||||
|
config_file_example -h | --help | --version`
|
||||||
|
|
||||||
|
jsonConfig := loadJSONConfig()
|
||||||
|
iniConfig := loadIniConfig()
|
||||||
|
arguments, _ := docopt.Parse(usage, nil, true, "0.1.1rc", false)
|
||||||
|
|
||||||
|
// Arguments take priority over INI, INI takes priority over JSON
|
||||||
|
result := merge(arguments, merge(iniConfig, jsonConfig))
|
||||||
|
|
||||||
|
fmt.Println("JSON config: ", jsonConfig)
|
||||||
|
fmt.Println("INI config: ", iniConfig)
|
||||||
|
fmt.Println("Result: ", result)
|
||||||
|
}
|
22
vendor/github.com/docopt/docopt-go/examples/counted/counted_example.go
generated
vendored
Normal file
22
vendor/github.com/docopt/docopt-go/examples/counted/counted_example.go
generated
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/docopt/docopt-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
usage := `Usage: counted_example --help
|
||||||
|
counted_example -v...
|
||||||
|
counted_example go [go]
|
||||||
|
counted_example (--path=<path>)...
|
||||||
|
counted_example <file> <file>
|
||||||
|
|
||||||
|
Try: counted_example -vvvvvvvvvv
|
||||||
|
counted_example go go
|
||||||
|
counted_example --path ./here --path ./there
|
||||||
|
counted_example this.txt that.txt`
|
||||||
|
|
||||||
|
arguments, _ := docopt.Parse(usage, nil, true, "", false)
|
||||||
|
fmt.Println(arguments)
|
||||||
|
}
|
38
vendor/github.com/docopt/docopt-go/examples/git/branch/git_branch.go
generated
vendored
Normal file
38
vendor/github.com/docopt/docopt-go/examples/git/branch/git_branch.go
generated
vendored
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
package git
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/docopt/docopt-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
usage := `usage: git branch [options] [-r | -a] [--merged=<commit> | --no-merged=<commit>]
|
||||||
|
git branch [options] [-l] [-f] <branchname> [<start-point>]
|
||||||
|
git branch [options] [-r] (-d | -D) <branchname>
|
||||||
|
git branch [options] (-m | -M) [<oldbranch>] <newbranch>
|
||||||
|
|
||||||
|
Generic options:
|
||||||
|
-h, --help
|
||||||
|
-v, --verbose show hash and subject, give twice for upstream branch
|
||||||
|
-t, --track set up tracking mode (see git-pull(1))
|
||||||
|
--set-upstream change upstream info
|
||||||
|
--color=<when> use colored output
|
||||||
|
-r act on remote-tracking branches
|
||||||
|
--contains=<commit> print only branches that contain the commit
|
||||||
|
--abbrev=<n> use <n> digits to display SHA-1s
|
||||||
|
|
||||||
|
Specific git-branch actions:
|
||||||
|
-a list both remote-tracking and local branches
|
||||||
|
-d delete fully merged branch
|
||||||
|
-D delete branch (even if not merged)
|
||||||
|
-m move/rename a branch and its reflog
|
||||||
|
-M move/rename a branch, even if target exists
|
||||||
|
-l create the branch's reflog
|
||||||
|
-f, --force force creation (when already exists)
|
||||||
|
--no-merged=<commit> print only not merged branches
|
||||||
|
--merged=<commit> print only merged branches
|
||||||
|
`
|
||||||
|
|
||||||
|
args, _ := docopt.Parse(usage, nil, true, "", false)
|
||||||
|
fmt.Println(args)
|
||||||
|
}
|
30
vendor/github.com/docopt/docopt-go/examples/git/checkout/git_checkout.go
generated
vendored
Normal file
30
vendor/github.com/docopt/docopt-go/examples/git/checkout/git_checkout.go
generated
vendored
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
package git
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/docopt/docopt-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
usage := `usage: git checkout [options] <branch>
|
||||||
|
git checkout [options] <branch> -- <file>...
|
||||||
|
|
||||||
|
options:
|
||||||
|
-q, --quiet suppress progress reporting
|
||||||
|
-b <branch> create and checkout a new branch
|
||||||
|
-B <branch> create/reset and checkout a branch
|
||||||
|
-l create reflog for new branch
|
||||||
|
-t, --track set upstream info for new branch
|
||||||
|
--orphan <new branch>
|
||||||
|
new unparented branch
|
||||||
|
-2, --ours checkout our version for unmerged files
|
||||||
|
-3, --theirs checkout their version for unmerged files
|
||||||
|
-f, --force force checkout (throw away local modifications)
|
||||||
|
-m, --merge perform a 3-way merge with the new branch
|
||||||
|
--conflict <style> conflict style (merge or diff3)
|
||||||
|
-p, --patch select hunks interactively
|
||||||
|
`
|
||||||
|
|
||||||
|
args, _ := docopt.Parse(usage, nil, true, "", false)
|
||||||
|
fmt.Println(args)
|
||||||
|
}
|
37
vendor/github.com/docopt/docopt-go/examples/git/clone/git_clone.go
generated
vendored
Normal file
37
vendor/github.com/docopt/docopt-go/examples/git/clone/git_clone.go
generated
vendored
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
package git
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/docopt/docopt-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
usage := `usage: git clone [options] [--] <repo> [<dir>]
|
||||||
|
|
||||||
|
options:
|
||||||
|
-v, --verbose be more verbose
|
||||||
|
-q, --quiet be more quiet
|
||||||
|
--progress force progress reporting
|
||||||
|
-n, --no-checkout don't create a checkout
|
||||||
|
--bare create a bare repository
|
||||||
|
--mirror create a mirror repository (implies bare)
|
||||||
|
-l, --local to clone from a local repository
|
||||||
|
--no-hardlinks don't use local hardlinks, always copy
|
||||||
|
-s, --shared setup as shared repository
|
||||||
|
--recursive initialize submodules in the clone
|
||||||
|
--recurse-submodules initialize submodules in the clone
|
||||||
|
--template <template-directory>
|
||||||
|
directory from which templates will be used
|
||||||
|
--reference <repo> reference repository
|
||||||
|
-o, --origin <branch>
|
||||||
|
use <branch> instead of 'origin' to track upstream
|
||||||
|
-b, --branch <branch>
|
||||||
|
checkout <branch> instead of the remote's HEAD
|
||||||
|
-u, --upload-pack <path>
|
||||||
|
path to git-upload-pack on the remote
|
||||||
|
--depth <depth> create a shallow clone of that depth
|
||||||
|
`
|
||||||
|
|
||||||
|
args, _ := docopt.Parse(usage, nil, true, "", false)
|
||||||
|
fmt.Println(args)
|
||||||
|
}
|
|
@ -0,0 +1,108 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/docopt/docopt-go"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
usage := `usage: git [--version] [--exec-path=<path>] [--html-path]
|
||||||
|
[-p|--paginate|--no-pager] [--no-replace-objects]
|
||||||
|
[--bare] [--git-dir=<path>] [--work-tree=<path>]
|
||||||
|
[-c <name>=<value>] [--help]
|
||||||
|
<command> [<args>...]
|
||||||
|
|
||||||
|
options:
|
||||||
|
-c <name=value>
|
||||||
|
-h, --help
|
||||||
|
-p, --paginate
|
||||||
|
|
||||||
|
The most commonly used git commands are:
|
||||||
|
add Add file contents to the index
|
||||||
|
branch List, create, or delete branches
|
||||||
|
checkout Checkout a branch or paths to the working tree
|
||||||
|
clone Clone a repository into a new directory
|
||||||
|
commit Record changes to the repository
|
||||||
|
push Update remote refs along with associated objects
|
||||||
|
remote Manage set of tracked repositories
|
||||||
|
|
||||||
|
See 'git help <command>' for more information on a specific command.
|
||||||
|
`
|
||||||
|
args, _ := docopt.Parse(usage, nil, true, "git version 1.7.4.4", true)
|
||||||
|
|
||||||
|
fmt.Println("global arguments:")
|
||||||
|
fmt.Println(args)
|
||||||
|
|
||||||
|
fmt.Println("command arguments:")
|
||||||
|
cmd := args["<command>"].(string)
|
||||||
|
cmdArgs := args["<args>"].([]string)
|
||||||
|
|
||||||
|
err := runCommand(cmd, cmdArgs)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func goRun(scriptName string, args []string) (err error) {
|
||||||
|
cmdArgs := make([]string, 2)
|
||||||
|
cmdArgs[0] = "run"
|
||||||
|
cmdArgs[1] = scriptName
|
||||||
|
cmdArgs = append(cmdArgs, args...)
|
||||||
|
osCmd := exec.Command("go", cmdArgs...)
|
||||||
|
var out []byte
|
||||||
|
out, err = osCmd.Output()
|
||||||
|
fmt.Println(string(out))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func runCommand(cmd string, args []string) (err error) {
|
||||||
|
argv := make([]string, 1)
|
||||||
|
argv[0] = cmd
|
||||||
|
argv = append(argv, args...)
|
||||||
|
switch cmd {
|
||||||
|
case "add":
|
||||||
|
// subcommand is a function call
|
||||||
|
return cmdAdd(argv)
|
||||||
|
case "branch":
|
||||||
|
// subcommand is a script
|
||||||
|
return goRun("branch/git_branch.go", argv)
|
||||||
|
case "checkout", "clone", "commit", "push", "remote":
|
||||||
|
// subcommand is a script
|
||||||
|
scriptName := fmt.Sprintf("%s/git_%s.go", cmd, cmd)
|
||||||
|
return goRun(scriptName, argv)
|
||||||
|
case "help", "":
|
||||||
|
return goRun("git.go", []string{"git_add.go", "--help"})
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("%s is not a git command. See 'git help'", cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func cmdAdd(argv []string) (err error) {
|
||||||
|
usage := `usage: git add [options] [--] [<filepattern>...]
|
||||||
|
|
||||||
|
options:
|
||||||
|
-h, --help
|
||||||
|
-n, --dry-run dry run
|
||||||
|
-v, --verbose be verbose
|
||||||
|
-i, --interactive interactive picking
|
||||||
|
-p, --patch select hunks interactively
|
||||||
|
-e, --edit edit current diff and apply
|
||||||
|
-f, --force allow adding otherwise ignored files
|
||||||
|
-u, --update update tracked files
|
||||||
|
-N, --intent-to-add record only the fact that the path will be added later
|
||||||
|
-A, --all add all, noticing removal of tracked files
|
||||||
|
--refresh don't add, only refresh the index
|
||||||
|
--ignore-errors just skip files which cannot be added because of errors
|
||||||
|
--ignore-missing check if - even missing - files are ignored in dry run
|
||||||
|
`
|
||||||
|
|
||||||
|
args, _ := docopt.Parse(usage, nil, true, "", false)
|
||||||
|
fmt.Println(args)
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
package git
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/docopt/docopt-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
usage := `usage: git push [options] [<repository> [<refspec>...]]
|
||||||
|
|
||||||
|
options:
|
||||||
|
-h, --help
|
||||||
|
-v, --verbose be more verbose
|
||||||
|
-q, --quiet be more quiet
|
||||||
|
--repo <repository> repository
|
||||||
|
--all push all refs
|
||||||
|
--mirror mirror all refs
|
||||||
|
--delete delete refs
|
||||||
|
--tags push tags (can't be used with --all or --mirror)
|
||||||
|
-n, --dry-run dry run
|
||||||
|
--porcelain machine-readable output
|
||||||
|
-f, --force force updates
|
||||||
|
--thin use thin pack
|
||||||
|
--receive-pack <receive-pack>
|
||||||
|
receive pack program
|
||||||
|
--exec <receive-pack>
|
||||||
|
receive pack program
|
||||||
|
-u, --set-upstream set upstream for git pull/status
|
||||||
|
--progress force progress reporting
|
||||||
|
`
|
||||||
|
|
||||||
|
args, _ := docopt.Parse(usage, nil, true, "", false)
|
||||||
|
fmt.Println(args)
|
||||||
|
}
|
28
vendor/github.com/docopt/docopt-go/examples/git/remote/git_remote.go
generated
vendored
Normal file
28
vendor/github.com/docopt/docopt-go/examples/git/remote/git_remote.go
generated
vendored
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
package git
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/docopt/docopt-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
usage := `usage: git remote [-v | --verbose]
|
||||||
|
git remote add [-t <branch>] [-m <master>] [-f] [--mirror] <name> <url>
|
||||||
|
git remote rename <old> <new>
|
||||||
|
git remote rm <name>
|
||||||
|
git remote set-head <name> (-a | -d | <branch>)
|
||||||
|
git remote [-v | --verbose] show [-n] <name>
|
||||||
|
git remote prune [-n | --dry-run] <name>
|
||||||
|
git remote [-v | --verbose] update [-p | --prune] [(<group> | <remote>)...]
|
||||||
|
git remote set-branches <name> [--add] <branch>...
|
||||||
|
git remote set-url <name> <newurl> [<oldurl>]
|
||||||
|
git remote set-url --add <name> <newurl>
|
||||||
|
git remote set-url --delete <name> <url>
|
||||||
|
|
||||||
|
options:
|
||||||
|
-v, --verbose be verbose; must be placed before a subcommand
|
||||||
|
`
|
||||||
|
|
||||||
|
args, _ := docopt.Parse(usage, nil, true, "", false)
|
||||||
|
fmt.Println(args)
|
||||||
|
}
|
28
vendor/github.com/docopt/docopt-go/examples/naval_fate/naval_fate.go
generated
vendored
Normal file
28
vendor/github.com/docopt/docopt-go/examples/naval_fate/naval_fate.go
generated
vendored
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/docopt/docopt-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
usage := `Naval Fate.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
naval_fate ship new <name>...
|
||||||
|
naval_fate ship <name> move <x> <y> [--speed=<kn>]
|
||||||
|
naval_fate ship shoot <x> <y>
|
||||||
|
naval_fate mine (set|remove) <x> <y> [--moored|--drifting]
|
||||||
|
naval_fate -h | --help
|
||||||
|
naval_fate --version
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-h --help Show this screen.
|
||||||
|
--version Show version.
|
||||||
|
--speed=<kn> Speed in knots [default: 10].
|
||||||
|
--moored Moored (anchored) mine.
|
||||||
|
--drifting Drifting mine.`
|
||||||
|
|
||||||
|
arguments, _ := docopt.Parse(usage, nil, true, "Naval Fate 2.0", false)
|
||||||
|
fmt.Println(arguments)
|
||||||
|
}
|
19
vendor/github.com/docopt/docopt-go/examples/odd_even/odd_even_example.go
generated
vendored
Normal file
19
vendor/github.com/docopt/docopt-go/examples/odd_even/odd_even_example.go
generated
vendored
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/docopt/docopt-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
usage := `Usage: odd_even_example [-h | --help] (ODD EVEN)...
|
||||||
|
|
||||||
|
Example, try:
|
||||||
|
odd_even_example 1 2 3 4
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-h, --help`
|
||||||
|
|
||||||
|
arguments, _ := docopt.Parse(usage, nil, true, "", false)
|
||||||
|
fmt.Println(arguments)
|
||||||
|
}
|
43
vendor/github.com/docopt/docopt-go/examples/options/options_example.go
generated
vendored
Normal file
43
vendor/github.com/docopt/docopt-go/examples/options/options_example.go
generated
vendored
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/docopt/docopt-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
usage := `Example of program with many options using docopt.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
options_example [-hvqrf NAME] [--exclude=PATTERNS]
|
||||||
|
[--select=ERRORS | --ignore=ERRORS] [--show-source]
|
||||||
|
[--statistics] [--count] [--benchmark] PATH...
|
||||||
|
options_example (--doctest | --testsuite=DIR)
|
||||||
|
options_example --version
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
PATH destination path
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-h --help show this help message and exit
|
||||||
|
--version show version and exit
|
||||||
|
-v --verbose print status messages
|
||||||
|
-q --quiet report only file names
|
||||||
|
-r --repeat show all occurrences of the same error
|
||||||
|
--exclude=PATTERNS exclude files or directories which match these comma
|
||||||
|
separated patterns [default: .svn,CVS,.bzr,.hg,.git]
|
||||||
|
-f NAME --file=NAME when parsing directories, only check filenames matching
|
||||||
|
these comma separated patterns [default: *.go]
|
||||||
|
--select=ERRORS select errors and warnings (e.g. E,W6)
|
||||||
|
--ignore=ERRORS skip errors and warnings (e.g. E4,W)
|
||||||
|
--show-source show source code for each error
|
||||||
|
--statistics count errors and warnings
|
||||||
|
--count print total number of errors and warnings to standard
|
||||||
|
error and set exit code to 1 if total is not null
|
||||||
|
--benchmark measure processing speed
|
||||||
|
--testsuite=DIR run regression tests from dir
|
||||||
|
--doctest run doctest on myself`
|
||||||
|
|
||||||
|
arguments, _ := docopt.Parse(usage, nil, true, "1.0.0rc2", false)
|
||||||
|
fmt.Println(arguments)
|
||||||
|
}
|
24
vendor/github.com/docopt/docopt-go/examples/options_shortcut/options_shortcut_example.go
generated
vendored
Normal file
24
vendor/github.com/docopt/docopt-go/examples/options_shortcut/options_shortcut_example.go
generated
vendored
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/docopt/docopt-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
usage := `Example of program which uses [options] shortcut in pattern.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
options_shortcut_example [options] <port>
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-h --help show this help message and exit
|
||||||
|
--version show version and exit
|
||||||
|
-n, --number N use N as a number
|
||||||
|
-t, --timeout TIMEOUT set timeout TIMEOUT seconds
|
||||||
|
--apply apply changes to database
|
||||||
|
-q operate in quiet mode`
|
||||||
|
|
||||||
|
arguments, _ := docopt.Parse(usage, nil, true, "1.0.0rc2", false)
|
||||||
|
fmt.Println(arguments)
|
||||||
|
}
|
16
vendor/github.com/docopt/docopt-go/examples/quick/quick_example.go
generated
vendored
Normal file
16
vendor/github.com/docopt/docopt-go/examples/quick/quick_example.go
generated
vendored
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/docopt/docopt-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
usage := `Usage:
|
||||||
|
quick_example tcp <host> <port> [--timeout=<seconds>]
|
||||||
|
quick_example serial <port> [--baud=9600] [--timeout=<seconds>]
|
||||||
|
quick_example -h | --help | --version`
|
||||||
|
|
||||||
|
arguments, _ := docopt.Parse(usage, nil, true, "0.1.1rc", false)
|
||||||
|
fmt.Println(arguments)
|
||||||
|
}
|
31
vendor/github.com/docopt/docopt-go/examples/type_assert/type_assert_example.go
generated
vendored
Normal file
31
vendor/github.com/docopt/docopt-go/examples/type_assert/type_assert_example.go
generated
vendored
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/docopt/docopt-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
usage := `usage: foo [-x] [-y]`
|
||||||
|
|
||||||
|
arguments, err := docopt.Parse(usage, nil, true, "", false)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(arguments)
|
||||||
|
|
||||||
|
var x = arguments["-x"].(bool) // type assertion required
|
||||||
|
if x == true {
|
||||||
|
fmt.Println("x is true")
|
||||||
|
}
|
||||||
|
|
||||||
|
y := arguments["-y"] // no type assertion needed
|
||||||
|
if y == true {
|
||||||
|
fmt.Println("y is true")
|
||||||
|
}
|
||||||
|
y2 := arguments["-y"]
|
||||||
|
if y2 == 10 { // this will never be true, a type assertion would have produced a build error
|
||||||
|
fmt.Println("y is 10")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
Please answer these questions before submitting your issue. Thanks!
|
||||||
|
|
||||||
|
1. What version of Go and beego are you using (`bee version`)?
|
||||||
|
|
||||||
|
|
||||||
|
2. What operating system and processor architecture are you using (`go env`)?
|
||||||
|
|
||||||
|
|
||||||
|
3. What did you do?
|
||||||
|
If possible, provide a recipe for reproducing the error.
|
||||||
|
A complete runnable program is good.
|
||||||
|
|
||||||
|
|
||||||
|
4. What did you expect to see?
|
||||||
|
|
||||||
|
|
||||||
|
5. What did you see instead?
|
|
@ -0,0 +1,6 @@
|
||||||
|
.idea
|
||||||
|
.vscode
|
||||||
|
.DS_Store
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
beego.iml
|
|
@ -0,0 +1,51 @@
|
||||||
|
language: go
|
||||||
|
|
||||||
|
go:
|
||||||
|
- 1.6
|
||||||
|
- 1.5.3
|
||||||
|
- 1.4.3
|
||||||
|
services:
|
||||||
|
- redis-server
|
||||||
|
- mysql
|
||||||
|
- postgresql
|
||||||
|
- memcached
|
||||||
|
env:
|
||||||
|
- ORM_DRIVER=sqlite3 ORM_SOURCE=$TRAVIS_BUILD_DIR/orm_test.db
|
||||||
|
- ORM_DRIVER=mysql ORM_SOURCE="root:@/orm_test?charset=utf8"
|
||||||
|
- ORM_DRIVER=postgres ORM_SOURCE="user=postgres dbname=orm_test sslmode=disable"
|
||||||
|
before_install:
|
||||||
|
- git clone git://github.com/ideawu/ssdb.git
|
||||||
|
- cd ssdb
|
||||||
|
- make
|
||||||
|
- cd ..
|
||||||
|
install:
|
||||||
|
- go get github.com/lib/pq
|
||||||
|
- go get github.com/go-sql-driver/mysql
|
||||||
|
- go get github.com/mattn/go-sqlite3
|
||||||
|
- go get github.com/bradfitz/gomemcache/memcache
|
||||||
|
- go get github.com/garyburd/redigo/redis
|
||||||
|
- go get github.com/beego/x2j
|
||||||
|
- go get github.com/couchbase/go-couchbase
|
||||||
|
- go get github.com/beego/goyaml2
|
||||||
|
- go get github.com/belogik/goes
|
||||||
|
- go get github.com/siddontang/ledisdb/config
|
||||||
|
- go get github.com/siddontang/ledisdb/ledis
|
||||||
|
- go get github.com/ssdb/gossdb/ssdb
|
||||||
|
- go get github.com/cloudflare/golz4
|
||||||
|
- go get github.com/gogo/protobuf/proto
|
||||||
|
before_script:
|
||||||
|
- psql --version
|
||||||
|
- sh -c "if [ '$ORM_DRIVER' = 'postgres' ]; then psql -c 'create database orm_test;' -U postgres; fi"
|
||||||
|
- sh -c "if [ '$ORM_DRIVER' = 'mysql' ]; then mysql -u root -e 'create database orm_test;'; fi"
|
||||||
|
- sh -c "if [ '$ORM_DRIVER' = 'sqlite' ]; then touch $TRAVIS_BUILD_DIR/orm_test.db; fi"
|
||||||
|
- sh -c "if [ $(go version) == *1.[5-9]* ]; then go get github.com/golang/lint/golint; golint ./...; fi"
|
||||||
|
- sh -c "if [ $(go version) == *1.[5-9]* ]; then go tool vet .; fi"
|
||||||
|
- mkdir -p res/var
|
||||||
|
- ./ssdb/ssdb-server ./ssdb/ssdb.conf -d
|
||||||
|
after_script:
|
||||||
|
-killall -w ssdb-server
|
||||||
|
- rm -rf ./res/var/*
|
||||||
|
script:
|
||||||
|
- go test -v ./...
|
||||||
|
addons:
|
||||||
|
postgresql: "9.4"
|
|
@ -0,0 +1,52 @@
|
||||||
|
# Contributing to beego
|
||||||
|
|
||||||
|
beego is an open source project.
|
||||||
|
|
||||||
|
It is the work of hundreds of contributors. We appreciate your help!
|
||||||
|
|
||||||
|
Here are instructions to get you started. They are probably not perfect,
|
||||||
|
please let us know if anything feels wrong or incomplete.
|
||||||
|
|
||||||
|
## Contribution guidelines
|
||||||
|
|
||||||
|
### Pull requests
|
||||||
|
|
||||||
|
First of all. beego follow the gitflow. So please send you pull request
|
||||||
|
to **develop** branch. We will close the pull request to master branch.
|
||||||
|
|
||||||
|
We are always happy to receive pull requests, and do our best to
|
||||||
|
review them as fast as possible. Not sure if that typo is worth a pull
|
||||||
|
request? Do it! We will appreciate it.
|
||||||
|
|
||||||
|
If your pull request is not accepted on the first try, don't be
|
||||||
|
discouraged! Sometimes we can make a mistake, please do more explaining
|
||||||
|
for us. We will appreciate it.
|
||||||
|
|
||||||
|
We're trying very hard to keep beego simple and fast. We don't want it
|
||||||
|
to do everything for everybody. This means that we might decide against
|
||||||
|
incorporating a new feature. But we will give you some advice on how to
|
||||||
|
do it in other way.
|
||||||
|
|
||||||
|
### Create issues
|
||||||
|
|
||||||
|
Any significant improvement should be documented as [a GitHub
|
||||||
|
issue](https://github.com/astaxie/beego/issues) before anybody
|
||||||
|
starts working on it.
|
||||||
|
|
||||||
|
Also when filing an issue, make sure to answer these five questions:
|
||||||
|
|
||||||
|
- What version of beego are you using (bee version)?
|
||||||
|
- What operating system and processor architecture are you using?
|
||||||
|
- What did you do?
|
||||||
|
- What did you expect to see?
|
||||||
|
- What did you see instead?
|
||||||
|
|
||||||
|
### but check existing issues and docs first!
|
||||||
|
|
||||||
|
Please take a moment to check that an issue doesn't already exist
|
||||||
|
documenting your bug report or improvement proposal. If it does, it
|
||||||
|
never hurts to add a quick "+1" or "I have this problem too". This will
|
||||||
|
help prioritize the most common problems and requests.
|
||||||
|
|
||||||
|
Also if you don't know how to use it. please make sure you have read though
|
||||||
|
the docs in http://beego.me/docs
|
|
@ -0,0 +1,62 @@
|
||||||
|
## Beego
|
||||||
|
|
||||||
|
[![Build Status](https://travis-ci.org/astaxie/beego.svg?branch=master)](https://travis-ci.org/astaxie/beego)
|
||||||
|
[![GoDoc](http://godoc.org/github.com/astaxie/beego?status.svg)](http://godoc.org/github.com/astaxie/beego)
|
||||||
|
[![Foundation](https://img.shields.io/badge/Golang-Foundation-green.svg)](http://golangfoundation.org)
|
||||||
|
|
||||||
|
beego is used for rapid development of RESTful APIs, web apps and backend services in Go.
|
||||||
|
It is inspired by Tornado, Sinatra and Flask. beego has some Go-specific features such as interfaces and struct embedding.
|
||||||
|
|
||||||
|
More info [beego.me](http://beego.me)
|
||||||
|
|
||||||
|
##Quick Start
|
||||||
|
######Download and install
|
||||||
|
|
||||||
|
go get github.com/astaxie/beego
|
||||||
|
|
||||||
|
######Create file `hello.go`
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "github.com/astaxie/beego"
|
||||||
|
|
||||||
|
func main(){
|
||||||
|
beego.Run()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
######Build and run
|
||||||
|
```bash
|
||||||
|
go build hello.go
|
||||||
|
./hello
|
||||||
|
```
|
||||||
|
######Congratulations!
|
||||||
|
You just built your first beego app.
|
||||||
|
Open your browser and visit `http://localhost:8080`.
|
||||||
|
Please see [Documentation](http://beego.me/docs) for more.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
* RESTful support
|
||||||
|
* MVC architecture
|
||||||
|
* Modularity
|
||||||
|
* Auto API documents
|
||||||
|
* Annotation router
|
||||||
|
* Namespace
|
||||||
|
* Powerful development tools
|
||||||
|
* Full stack for Web & API
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
* [English](http://beego.me/docs/intro/)
|
||||||
|
* [中文文档](http://beego.me/docs/intro/)
|
||||||
|
* [Русский](http://beego.me/docs/intro/)
|
||||||
|
|
||||||
|
## Community
|
||||||
|
|
||||||
|
* [http://beego.me/community](http://beego.me/community)
|
||||||
|
* Welcome to join us in Slack: [https://beego.slack.com](https://beego.slack.com), you can get invited from [here](https://github.com/beego/beedoc/issues/232)
|
||||||
|
|
||||||
|
## LICENSE
|
||||||
|
|
||||||
|
beego source code is licensed under the Apache Licence, Version 2.0
|
||||||
|
(http://www.apache.org/licenses/LICENSE-2.0.html).
|
|
@ -0,0 +1,401 @@
|
||||||
|
// Copyright 2014 beego Author. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package beego
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"text/template"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego/grace"
|
||||||
|
"github.com/astaxie/beego/logs"
|
||||||
|
"github.com/astaxie/beego/toolbox"
|
||||||
|
"github.com/astaxie/beego/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BeeAdminApp is the default adminApp used by admin module.
|
||||||
|
var beeAdminApp *adminApp
|
||||||
|
|
||||||
|
// FilterMonitorFunc is default monitor filter when admin module is enable.
|
||||||
|
// if this func returns, admin module records qbs for this request by condition of this function logic.
|
||||||
|
// usage:
|
||||||
|
// func MyFilterMonitor(method, requestPath string, t time.Duration) bool {
|
||||||
|
// if method == "POST" {
|
||||||
|
// return false
|
||||||
|
// }
|
||||||
|
// if t.Nanoseconds() < 100 {
|
||||||
|
// return false
|
||||||
|
// }
|
||||||
|
// if strings.HasPrefix(requestPath, "/astaxie") {
|
||||||
|
// return false
|
||||||
|
// }
|
||||||
|
// return true
|
||||||
|
// }
|
||||||
|
// beego.FilterMonitorFunc = MyFilterMonitor.
|
||||||
|
var FilterMonitorFunc func(string, string, time.Duration) bool
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
beeAdminApp = &adminApp{
|
||||||
|
routers: make(map[string]http.HandlerFunc),
|
||||||
|
}
|
||||||
|
beeAdminApp.Route("/", adminIndex)
|
||||||
|
beeAdminApp.Route("/qps", qpsIndex)
|
||||||
|
beeAdminApp.Route("/prof", profIndex)
|
||||||
|
beeAdminApp.Route("/healthcheck", healthcheck)
|
||||||
|
beeAdminApp.Route("/task", taskStatus)
|
||||||
|
beeAdminApp.Route("/listconf", listConf)
|
||||||
|
FilterMonitorFunc = func(string, string, time.Duration) bool { return true }
|
||||||
|
}
|
||||||
|
|
||||||
|
// AdminIndex is the default http.Handler for admin module.
|
||||||
|
// it matches url pattern "/".
|
||||||
|
func adminIndex(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
execTpl(rw, map[interface{}]interface{}{}, indexTpl, defaultScriptsTpl)
|
||||||
|
}
|
||||||
|
|
||||||
|
// QpsIndex is the http.Handler for writing qbs statistics map result info in http.ResponseWriter.
|
||||||
|
// it's registered with url pattern "/qbs" in admin module.
|
||||||
|
func qpsIndex(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
data := make(map[interface{}]interface{})
|
||||||
|
data["Content"] = toolbox.StatisticsMap.GetMap()
|
||||||
|
execTpl(rw, data, qpsTpl, defaultScriptsTpl)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListConf is the http.Handler of displaying all beego configuration values as key/value pair.
|
||||||
|
// it's registered with url pattern "/listconf" in admin module.
|
||||||
|
func listConf(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
r.ParseForm()
|
||||||
|
command := r.Form.Get("command")
|
||||||
|
if command == "" {
|
||||||
|
rw.Write([]byte("command not support"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
data := make(map[interface{}]interface{})
|
||||||
|
switch command {
|
||||||
|
case "conf":
|
||||||
|
m := make(map[string]interface{})
|
||||||
|
list("BConfig", BConfig, m)
|
||||||
|
m["AppConfigPath"] = appConfigPath
|
||||||
|
m["AppConfigProvider"] = appConfigProvider
|
||||||
|
tmpl := template.Must(template.New("dashboard").Parse(dashboardTpl))
|
||||||
|
tmpl = template.Must(tmpl.Parse(configTpl))
|
||||||
|
tmpl = template.Must(tmpl.Parse(defaultScriptsTpl))
|
||||||
|
|
||||||
|
data["Content"] = m
|
||||||
|
|
||||||
|
tmpl.Execute(rw, data)
|
||||||
|
|
||||||
|
case "router":
|
||||||
|
var (
|
||||||
|
content = map[string]interface{}{
|
||||||
|
"Fields": []string{
|
||||||
|
"Router Pattern",
|
||||||
|
"Methods",
|
||||||
|
"Controller",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
methods = []string{}
|
||||||
|
methodsData = make(map[string]interface{})
|
||||||
|
)
|
||||||
|
for method, t := range BeeApp.Handlers.routers {
|
||||||
|
|
||||||
|
resultList := new([][]string)
|
||||||
|
|
||||||
|
printTree(resultList, t)
|
||||||
|
|
||||||
|
methods = append(methods, method)
|
||||||
|
methodsData[method] = resultList
|
||||||
|
}
|
||||||
|
|
||||||
|
content["Data"] = methodsData
|
||||||
|
content["Methods"] = methods
|
||||||
|
data["Content"] = content
|
||||||
|
data["Title"] = "Routers"
|
||||||
|
execTpl(rw, data, routerAndFilterTpl, defaultScriptsTpl)
|
||||||
|
case "filter":
|
||||||
|
var (
|
||||||
|
content = map[string]interface{}{
|
||||||
|
"Fields": []string{
|
||||||
|
"Router Pattern",
|
||||||
|
"Filter Function",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
filterTypes = []string{}
|
||||||
|
filterTypeData = make(map[string]interface{})
|
||||||
|
)
|
||||||
|
|
||||||
|
if BeeApp.Handlers.enableFilter {
|
||||||
|
var filterType string
|
||||||
|
for k, fr := range map[int]string{
|
||||||
|
BeforeStatic: "Before Static",
|
||||||
|
BeforeRouter: "Before Router",
|
||||||
|
BeforeExec: "Before Exec",
|
||||||
|
AfterExec: "After Exec",
|
||||||
|
FinishRouter: "Finish Router"} {
|
||||||
|
if bf := BeeApp.Handlers.filters[k]; len(bf) > 0 {
|
||||||
|
filterType = fr
|
||||||
|
filterTypes = append(filterTypes, filterType)
|
||||||
|
resultList := new([][]string)
|
||||||
|
for _, f := range bf {
|
||||||
|
var result = []string{
|
||||||
|
fmt.Sprintf("%s", f.pattern),
|
||||||
|
fmt.Sprintf("%s", utils.GetFuncName(f.filterFunc)),
|
||||||
|
}
|
||||||
|
*resultList = append(*resultList, result)
|
||||||
|
}
|
||||||
|
filterTypeData[filterType] = resultList
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
content["Data"] = filterTypeData
|
||||||
|
content["Methods"] = filterTypes
|
||||||
|
|
||||||
|
data["Content"] = content
|
||||||
|
data["Title"] = "Filters"
|
||||||
|
execTpl(rw, data, routerAndFilterTpl, defaultScriptsTpl)
|
||||||
|
default:
|
||||||
|
rw.Write([]byte("command not support"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func list(root string, p interface{}, m map[string]interface{}) {
|
||||||
|
pt := reflect.TypeOf(p)
|
||||||
|
pv := reflect.ValueOf(p)
|
||||||
|
if pt.Kind() == reflect.Ptr {
|
||||||
|
pt = pt.Elem()
|
||||||
|
pv = pv.Elem()
|
||||||
|
}
|
||||||
|
for i := 0; i < pv.NumField(); i++ {
|
||||||
|
var key string
|
||||||
|
if root == "" {
|
||||||
|
key = pt.Field(i).Name
|
||||||
|
} else {
|
||||||
|
key = root + "." + pt.Field(i).Name
|
||||||
|
}
|
||||||
|
if pv.Field(i).Kind() == reflect.Struct {
|
||||||
|
list(key, pv.Field(i).Interface(), m)
|
||||||
|
} else {
|
||||||
|
m[key] = pv.Field(i).Interface()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func printTree(resultList *[][]string, t *Tree) {
|
||||||
|
for _, tr := range t.fixrouters {
|
||||||
|
printTree(resultList, tr)
|
||||||
|
}
|
||||||
|
if t.wildcard != nil {
|
||||||
|
printTree(resultList, t.wildcard)
|
||||||
|
}
|
||||||
|
for _, l := range t.leaves {
|
||||||
|
if v, ok := l.runObject.(*controllerInfo); ok {
|
||||||
|
if v.routerType == routerTypeBeego {
|
||||||
|
var result = []string{
|
||||||
|
v.pattern,
|
||||||
|
fmt.Sprintf("%s", v.methods),
|
||||||
|
fmt.Sprintf("%s", v.controllerType),
|
||||||
|
}
|
||||||
|
*resultList = append(*resultList, result)
|
||||||
|
} else if v.routerType == routerTypeRESTFul {
|
||||||
|
var result = []string{
|
||||||
|
v.pattern,
|
||||||
|
fmt.Sprintf("%s", v.methods),
|
||||||
|
"",
|
||||||
|
}
|
||||||
|
*resultList = append(*resultList, result)
|
||||||
|
} else if v.routerType == routerTypeHandler {
|
||||||
|
var result = []string{
|
||||||
|
v.pattern,
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
}
|
||||||
|
*resultList = append(*resultList, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProfIndex is a http.Handler for showing profile command.
|
||||||
|
// it's in url pattern "/prof" in admin module.
|
||||||
|
func profIndex(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
r.ParseForm()
|
||||||
|
command := r.Form.Get("command")
|
||||||
|
if command == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
format = r.Form.Get("format")
|
||||||
|
data = make(map[interface{}]interface{})
|
||||||
|
result bytes.Buffer
|
||||||
|
)
|
||||||
|
toolbox.ProcessInput(command, &result)
|
||||||
|
data["Content"] = result.String()
|
||||||
|
|
||||||
|
if format == "json" && command == "gc summary" {
|
||||||
|
dataJSON, err := json.Marshal(data)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rw.Header().Set("Content-Type", "application/json")
|
||||||
|
rw.Write(dataJSON)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
data["Title"] = command
|
||||||
|
defaultTpl := defaultScriptsTpl
|
||||||
|
if command == "gc summary" {
|
||||||
|
defaultTpl = gcAjaxTpl
|
||||||
|
}
|
||||||
|
execTpl(rw, data, profillingTpl, defaultTpl)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Healthcheck is a http.Handler calling health checking and showing the result.
|
||||||
|
// it's in "/healthcheck" pattern in admin module.
|
||||||
|
func healthcheck(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
var (
|
||||||
|
data = make(map[interface{}]interface{})
|
||||||
|
result = []string{}
|
||||||
|
resultList = new([][]string)
|
||||||
|
content = map[string]interface{}{
|
||||||
|
"Fields": []string{"Name", "Message", "Status"},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
for name, h := range toolbox.AdminCheckList {
|
||||||
|
if err := h.Check(); err != nil {
|
||||||
|
result = []string{
|
||||||
|
fmt.Sprintf("error"),
|
||||||
|
fmt.Sprintf("%s", name),
|
||||||
|
fmt.Sprintf("%s", err.Error()),
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
result = []string{
|
||||||
|
fmt.Sprintf("success"),
|
||||||
|
fmt.Sprintf("%s", name),
|
||||||
|
fmt.Sprintf("OK"),
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
*resultList = append(*resultList, result)
|
||||||
|
}
|
||||||
|
content["Data"] = resultList
|
||||||
|
data["Content"] = content
|
||||||
|
data["Title"] = "Health Check"
|
||||||
|
execTpl(rw, data, healthCheckTpl, defaultScriptsTpl)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TaskStatus is a http.Handler with running task status (task name, status and the last execution).
|
||||||
|
// it's in "/task" pattern in admin module.
|
||||||
|
func taskStatus(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
data := make(map[interface{}]interface{})
|
||||||
|
|
||||||
|
// Run Task
|
||||||
|
req.ParseForm()
|
||||||
|
taskname := req.Form.Get("taskname")
|
||||||
|
if taskname != "" {
|
||||||
|
if t, ok := toolbox.AdminTaskList[taskname]; ok {
|
||||||
|
if err := t.Run(); err != nil {
|
||||||
|
data["Message"] = []string{"error", fmt.Sprintf("%s", err)}
|
||||||
|
}
|
||||||
|
data["Message"] = []string{"success", fmt.Sprintf("%s run success,Now the Status is <br>%s", taskname, t.GetStatus())}
|
||||||
|
} else {
|
||||||
|
data["Message"] = []string{"warning", fmt.Sprintf("there's no task which named: %s", taskname)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// List Tasks
|
||||||
|
content := make(map[string]interface{})
|
||||||
|
resultList := new([][]string)
|
||||||
|
var result = []string{}
|
||||||
|
var fields = []string{
|
||||||
|
"Task Name",
|
||||||
|
"Task Spec",
|
||||||
|
"Task Status",
|
||||||
|
"Last Time",
|
||||||
|
"",
|
||||||
|
}
|
||||||
|
for tname, tk := range toolbox.AdminTaskList {
|
||||||
|
result = []string{
|
||||||
|
tname,
|
||||||
|
fmt.Sprintf("%s", tk.GetSpec()),
|
||||||
|
fmt.Sprintf("%s", tk.GetStatus()),
|
||||||
|
tk.GetPrev().String(),
|
||||||
|
}
|
||||||
|
*resultList = append(*resultList, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
content["Fields"] = fields
|
||||||
|
content["Data"] = resultList
|
||||||
|
data["Content"] = content
|
||||||
|
data["Title"] = "Tasks"
|
||||||
|
execTpl(rw, data, tasksTpl, defaultScriptsTpl)
|
||||||
|
}
|
||||||
|
|
||||||
|
func execTpl(rw http.ResponseWriter, data map[interface{}]interface{}, tpls ...string) {
|
||||||
|
tmpl := template.Must(template.New("dashboard").Parse(dashboardTpl))
|
||||||
|
for _, tpl := range tpls {
|
||||||
|
tmpl = template.Must(tmpl.Parse(tpl))
|
||||||
|
}
|
||||||
|
tmpl.Execute(rw, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// adminApp is an http.HandlerFunc map used as beeAdminApp.
|
||||||
|
type adminApp struct {
|
||||||
|
routers map[string]http.HandlerFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
// Route adds http.HandlerFunc to adminApp with url pattern.
|
||||||
|
func (admin *adminApp) Route(pattern string, f http.HandlerFunc) {
|
||||||
|
admin.routers[pattern] = f
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run adminApp http server.
|
||||||
|
// Its addr is defined in configuration file as adminhttpaddr and adminhttpport.
|
||||||
|
func (admin *adminApp) Run() {
|
||||||
|
if len(toolbox.AdminTaskList) > 0 {
|
||||||
|
toolbox.StartTask()
|
||||||
|
}
|
||||||
|
addr := BConfig.Listen.AdminAddr
|
||||||
|
|
||||||
|
if BConfig.Listen.AdminPort != 0 {
|
||||||
|
addr = fmt.Sprintf("%s:%d", BConfig.Listen.AdminAddr, BConfig.Listen.AdminPort)
|
||||||
|
}
|
||||||
|
for p, f := range admin.routers {
|
||||||
|
http.Handle(p, f)
|
||||||
|
}
|
||||||
|
logs.Info("Admin server Running on %s", addr)
|
||||||
|
|
||||||
|
var err error
|
||||||
|
if BConfig.Listen.Graceful {
|
||||||
|
err = grace.ListenAndServe(addr, nil)
|
||||||
|
} else {
|
||||||
|
err = http.ListenAndServe(addr, nil)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
logs.Critical("Admin ListenAndServe: ", err, fmt.Sprintf("%d", os.Getpid()))
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
package beego
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestList_01(t *testing.T) {
|
||||||
|
m := make(map[string]interface{})
|
||||||
|
list("BConfig", BConfig, m)
|
||||||
|
t.Log(m)
|
||||||
|
om := oldMap()
|
||||||
|
for k, v := range om {
|
||||||
|
if fmt.Sprint(m[k]) != fmt.Sprint(v) {
|
||||||
|
t.Log(k, "old-key", v, "new-key", m[k])
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func oldMap() map[string]interface{} {
|
||||||
|
m := make(map[string]interface{})
|
||||||
|
m["BConfig.AppName"] = BConfig.AppName
|
||||||
|
m["BConfig.RunMode"] = BConfig.RunMode
|
||||||
|
m["BConfig.RouterCaseSensitive"] = BConfig.RouterCaseSensitive
|
||||||
|
m["BConfig.ServerName"] = BConfig.ServerName
|
||||||
|
m["BConfig.RecoverPanic"] = BConfig.RecoverPanic
|
||||||
|
m["BConfig.CopyRequestBody"] = BConfig.CopyRequestBody
|
||||||
|
m["BConfig.EnableGzip"] = BConfig.EnableGzip
|
||||||
|
m["BConfig.MaxMemory"] = BConfig.MaxMemory
|
||||||
|
m["BConfig.EnableErrorsShow"] = BConfig.EnableErrorsShow
|
||||||
|
m["BConfig.Listen.Graceful"] = BConfig.Listen.Graceful
|
||||||
|
m["BConfig.Listen.ServerTimeOut"] = BConfig.Listen.ServerTimeOut
|
||||||
|
m["BConfig.Listen.ListenTCP4"] = BConfig.Listen.ListenTCP4
|
||||||
|
m["BConfig.Listen.EnableHTTP"] = BConfig.Listen.EnableHTTP
|
||||||
|
m["BConfig.Listen.HTTPAddr"] = BConfig.Listen.HTTPAddr
|
||||||
|
m["BConfig.Listen.HTTPPort"] = BConfig.Listen.HTTPPort
|
||||||
|
m["BConfig.Listen.EnableHTTPS"] = BConfig.Listen.EnableHTTPS
|
||||||
|
m["BConfig.Listen.HTTPSAddr"] = BConfig.Listen.HTTPSAddr
|
||||||
|
m["BConfig.Listen.HTTPSPort"] = BConfig.Listen.HTTPSPort
|
||||||
|
m["BConfig.Listen.HTTPSCertFile"] = BConfig.Listen.HTTPSCertFile
|
||||||
|
m["BConfig.Listen.HTTPSKeyFile"] = BConfig.Listen.HTTPSKeyFile
|
||||||
|
m["BConfig.Listen.EnableAdmin"] = BConfig.Listen.EnableAdmin
|
||||||
|
m["BConfig.Listen.AdminAddr"] = BConfig.Listen.AdminAddr
|
||||||
|
m["BConfig.Listen.AdminPort"] = BConfig.Listen.AdminPort
|
||||||
|
m["BConfig.Listen.EnableFcgi"] = BConfig.Listen.EnableFcgi
|
||||||
|
m["BConfig.Listen.EnableStdIo"] = BConfig.Listen.EnableStdIo
|
||||||
|
m["BConfig.WebConfig.AutoRender"] = BConfig.WebConfig.AutoRender
|
||||||
|
m["BConfig.WebConfig.EnableDocs"] = BConfig.WebConfig.EnableDocs
|
||||||
|
m["BConfig.WebConfig.FlashName"] = BConfig.WebConfig.FlashName
|
||||||
|
m["BConfig.WebConfig.FlashSeparator"] = BConfig.WebConfig.FlashSeparator
|
||||||
|
m["BConfig.WebConfig.DirectoryIndex"] = BConfig.WebConfig.DirectoryIndex
|
||||||
|
m["BConfig.WebConfig.StaticDir"] = BConfig.WebConfig.StaticDir
|
||||||
|
m["BConfig.WebConfig.StaticExtensionsToGzip"] = BConfig.WebConfig.StaticExtensionsToGzip
|
||||||
|
m["BConfig.WebConfig.TemplateLeft"] = BConfig.WebConfig.TemplateLeft
|
||||||
|
m["BConfig.WebConfig.TemplateRight"] = BConfig.WebConfig.TemplateRight
|
||||||
|
m["BConfig.WebConfig.ViewsPath"] = BConfig.WebConfig.ViewsPath
|
||||||
|
m["BConfig.WebConfig.EnableXSRF"] = BConfig.WebConfig.EnableXSRF
|
||||||
|
m["BConfig.WebConfig.XSRFExpire"] = BConfig.WebConfig.XSRFExpire
|
||||||
|
m["BConfig.WebConfig.Session.SessionOn"] = BConfig.WebConfig.Session.SessionOn
|
||||||
|
m["BConfig.WebConfig.Session.SessionProvider"] = BConfig.WebConfig.Session.SessionProvider
|
||||||
|
m["BConfig.WebConfig.Session.SessionName"] = BConfig.WebConfig.Session.SessionName
|
||||||
|
m["BConfig.WebConfig.Session.SessionGCMaxLifetime"] = BConfig.WebConfig.Session.SessionGCMaxLifetime
|
||||||
|
m["BConfig.WebConfig.Session.SessionProviderConfig"] = BConfig.WebConfig.Session.SessionProviderConfig
|
||||||
|
m["BConfig.WebConfig.Session.SessionCookieLifeTime"] = BConfig.WebConfig.Session.SessionCookieLifeTime
|
||||||
|
m["BConfig.WebConfig.Session.SessionAutoSetCookie"] = BConfig.WebConfig.Session.SessionAutoSetCookie
|
||||||
|
m["BConfig.WebConfig.Session.SessionDomain"] = BConfig.WebConfig.Session.SessionDomain
|
||||||
|
m["BConfig.WebConfig.Session.SessionDisableHTTPOnly"] = BConfig.WebConfig.Session.SessionDisableHTTPOnly
|
||||||
|
m["BConfig.Log.AccessLogs"] = BConfig.Log.AccessLogs
|
||||||
|
m["BConfig.Log.FileLineNum"] = BConfig.Log.FileLineNum
|
||||||
|
m["BConfig.Log.Outputs"] = BConfig.Log.Outputs
|
||||||
|
return m
|
||||||
|
}
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,366 @@
|
||||||
|
// Copyright 2014 beego Author. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package beego
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/http/fcgi"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego/grace"
|
||||||
|
"github.com/astaxie/beego/logs"
|
||||||
|
"github.com/astaxie/beego/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// BeeApp is an application instance
|
||||||
|
BeeApp *App
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// create beego application
|
||||||
|
BeeApp = NewApp()
|
||||||
|
}
|
||||||
|
|
||||||
|
// App defines beego application with a new PatternServeMux.
|
||||||
|
type App struct {
|
||||||
|
Handlers *ControllerRegister
|
||||||
|
Server *http.Server
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewApp returns a new beego application.
|
||||||
|
func NewApp() *App {
|
||||||
|
cr := NewControllerRegister()
|
||||||
|
app := &App{Handlers: cr, Server: &http.Server{}}
|
||||||
|
return app
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run beego application.
|
||||||
|
func (app *App) Run() {
|
||||||
|
addr := BConfig.Listen.HTTPAddr
|
||||||
|
|
||||||
|
if BConfig.Listen.HTTPPort != 0 {
|
||||||
|
addr = fmt.Sprintf("%s:%d", BConfig.Listen.HTTPAddr, BConfig.Listen.HTTPPort)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
l net.Listener
|
||||||
|
endRunning = make(chan bool, 1)
|
||||||
|
)
|
||||||
|
|
||||||
|
// run cgi server
|
||||||
|
if BConfig.Listen.EnableFcgi {
|
||||||
|
if BConfig.Listen.EnableStdIo {
|
||||||
|
if err = fcgi.Serve(nil, app.Handlers); err == nil { // standard I/O
|
||||||
|
logs.Info("Use FCGI via standard I/O")
|
||||||
|
} else {
|
||||||
|
logs.Critical("Cannot use FCGI via standard I/O", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if BConfig.Listen.HTTPPort == 0 {
|
||||||
|
// remove the Socket file before start
|
||||||
|
if utils.FileExists(addr) {
|
||||||
|
os.Remove(addr)
|
||||||
|
}
|
||||||
|
l, err = net.Listen("unix", addr)
|
||||||
|
} else {
|
||||||
|
l, err = net.Listen("tcp", addr)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
logs.Critical("Listen: ", err)
|
||||||
|
}
|
||||||
|
if err = fcgi.Serve(l, app.Handlers); err != nil {
|
||||||
|
logs.Critical("fcgi.Serve: ", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
app.Server.Handler = app.Handlers
|
||||||
|
app.Server.ReadTimeout = time.Duration(BConfig.Listen.ServerTimeOut) * time.Second
|
||||||
|
app.Server.WriteTimeout = time.Duration(BConfig.Listen.ServerTimeOut) * time.Second
|
||||||
|
app.Server.ErrorLog = logs.GetLogger("HTTP")
|
||||||
|
|
||||||
|
// run graceful mode
|
||||||
|
if BConfig.Listen.Graceful {
|
||||||
|
httpsAddr := BConfig.Listen.HTTPSAddr
|
||||||
|
app.Server.Addr = httpsAddr
|
||||||
|
if BConfig.Listen.EnableHTTPS {
|
||||||
|
go func() {
|
||||||
|
time.Sleep(20 * time.Microsecond)
|
||||||
|
if BConfig.Listen.HTTPSPort != 0 {
|
||||||
|
httpsAddr = fmt.Sprintf("%s:%d", BConfig.Listen.HTTPSAddr, BConfig.Listen.HTTPSPort)
|
||||||
|
app.Server.Addr = httpsAddr
|
||||||
|
}
|
||||||
|
server := grace.NewServer(httpsAddr, app.Handlers)
|
||||||
|
server.Server.ReadTimeout = app.Server.ReadTimeout
|
||||||
|
server.Server.WriteTimeout = app.Server.WriteTimeout
|
||||||
|
if err := server.ListenAndServeTLS(BConfig.Listen.HTTPSCertFile, BConfig.Listen.HTTPSKeyFile); err != nil {
|
||||||
|
logs.Critical("ListenAndServeTLS: ", err, fmt.Sprintf("%d", os.Getpid()))
|
||||||
|
time.Sleep(100 * time.Microsecond)
|
||||||
|
endRunning <- true
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
if BConfig.Listen.EnableHTTP {
|
||||||
|
go func() {
|
||||||
|
server := grace.NewServer(addr, app.Handlers)
|
||||||
|
server.Server.ReadTimeout = app.Server.ReadTimeout
|
||||||
|
server.Server.WriteTimeout = app.Server.WriteTimeout
|
||||||
|
if BConfig.Listen.ListenTCP4 {
|
||||||
|
server.Network = "tcp4"
|
||||||
|
}
|
||||||
|
if err := server.ListenAndServe(); err != nil {
|
||||||
|
logs.Critical("ListenAndServe: ", err, fmt.Sprintf("%d", os.Getpid()))
|
||||||
|
time.Sleep(100 * time.Microsecond)
|
||||||
|
endRunning <- true
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
<-endRunning
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// run normal mode
|
||||||
|
if BConfig.Listen.EnableHTTPS {
|
||||||
|
go func() {
|
||||||
|
time.Sleep(20 * time.Microsecond)
|
||||||
|
if BConfig.Listen.HTTPSPort != 0 {
|
||||||
|
app.Server.Addr = fmt.Sprintf("%s:%d", BConfig.Listen.HTTPSAddr, BConfig.Listen.HTTPSPort)
|
||||||
|
} else if BConfig.Listen.EnableHTTP {
|
||||||
|
BeeLogger.Info("Start https server error, confict with http.Please reset https port")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
logs.Info("https server Running on https://%s", app.Server.Addr)
|
||||||
|
if err := app.Server.ListenAndServeTLS(BConfig.Listen.HTTPSCertFile, BConfig.Listen.HTTPSKeyFile); err != nil {
|
||||||
|
logs.Critical("ListenAndServeTLS: ", err)
|
||||||
|
time.Sleep(100 * time.Microsecond)
|
||||||
|
endRunning <- true
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
if BConfig.Listen.EnableHTTP {
|
||||||
|
go func() {
|
||||||
|
app.Server.Addr = addr
|
||||||
|
logs.Info("http server Running on http://%s", app.Server.Addr)
|
||||||
|
if BConfig.Listen.ListenTCP4 {
|
||||||
|
ln, err := net.Listen("tcp4", app.Server.Addr)
|
||||||
|
if err != nil {
|
||||||
|
logs.Critical("ListenAndServe: ", err)
|
||||||
|
time.Sleep(100 * time.Microsecond)
|
||||||
|
endRunning <- true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = app.Server.Serve(ln); err != nil {
|
||||||
|
logs.Critical("ListenAndServe: ", err)
|
||||||
|
time.Sleep(100 * time.Microsecond)
|
||||||
|
endRunning <- true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := app.Server.ListenAndServe(); err != nil {
|
||||||
|
logs.Critical("ListenAndServe: ", err)
|
||||||
|
time.Sleep(100 * time.Microsecond)
|
||||||
|
endRunning <- true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
<-endRunning
|
||||||
|
}
|
||||||
|
|
||||||
|
// Router adds a patterned controller handler to BeeApp.
|
||||||
|
// it's an alias method of App.Router.
|
||||||
|
// usage:
|
||||||
|
// simple router
|
||||||
|
// beego.Router("/admin", &admin.UserController{})
|
||||||
|
// beego.Router("/admin/index", &admin.ArticleController{})
|
||||||
|
//
|
||||||
|
// regex router
|
||||||
|
//
|
||||||
|
// beego.Router("/api/:id([0-9]+)", &controllers.RController{})
|
||||||
|
//
|
||||||
|
// custom rules
|
||||||
|
// beego.Router("/api/list",&RestController{},"*:ListFood")
|
||||||
|
// beego.Router("/api/create",&RestController{},"post:CreateFood")
|
||||||
|
// beego.Router("/api/update",&RestController{},"put:UpdateFood")
|
||||||
|
// beego.Router("/api/delete",&RestController{},"delete:DeleteFood")
|
||||||
|
func Router(rootpath string, c ControllerInterface, mappingMethods ...string) *App {
|
||||||
|
BeeApp.Handlers.Add(rootpath, c, mappingMethods...)
|
||||||
|
return BeeApp
|
||||||
|
}
|
||||||
|
|
||||||
|
// Include will generate router file in the router/xxx.go from the controller's comments
|
||||||
|
// usage:
|
||||||
|
// beego.Include(&BankAccount{}, &OrderController{},&RefundController{},&ReceiptController{})
|
||||||
|
// type BankAccount struct{
|
||||||
|
// beego.Controller
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// register the function
|
||||||
|
// func (b *BankAccount)Mapping(){
|
||||||
|
// b.Mapping("ShowAccount" , b.ShowAccount)
|
||||||
|
// b.Mapping("ModifyAccount", b.ModifyAccount)
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
// //@router /account/:id [get]
|
||||||
|
// func (b *BankAccount) ShowAccount(){
|
||||||
|
// //logic
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// //@router /account/:id [post]
|
||||||
|
// func (b *BankAccount) ModifyAccount(){
|
||||||
|
// //logic
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// the comments @router url methodlist
|
||||||
|
// url support all the function Router's pattern
|
||||||
|
// methodlist [get post head put delete options *]
|
||||||
|
func Include(cList ...ControllerInterface) *App {
|
||||||
|
BeeApp.Handlers.Include(cList...)
|
||||||
|
return BeeApp
|
||||||
|
}
|
||||||
|
|
||||||
|
// RESTRouter adds a restful controller handler to BeeApp.
|
||||||
|
// its' controller implements beego.ControllerInterface and
|
||||||
|
// defines a param "pattern/:objectId" to visit each resource.
|
||||||
|
func RESTRouter(rootpath string, c ControllerInterface) *App {
|
||||||
|
Router(rootpath, c)
|
||||||
|
Router(path.Join(rootpath, ":objectId"), c)
|
||||||
|
return BeeApp
|
||||||
|
}
|
||||||
|
|
||||||
|
// AutoRouter adds defined controller handler to BeeApp.
|
||||||
|
// it's same to App.AutoRouter.
|
||||||
|
// if beego.AddAuto(&MainContorlller{}) and MainController has methods List and Page,
|
||||||
|
// visit the url /main/list to exec List function or /main/page to exec Page function.
|
||||||
|
func AutoRouter(c ControllerInterface) *App {
|
||||||
|
BeeApp.Handlers.AddAuto(c)
|
||||||
|
return BeeApp
|
||||||
|
}
|
||||||
|
|
||||||
|
// AutoPrefix adds controller handler to BeeApp with prefix.
|
||||||
|
// it's same to App.AutoRouterWithPrefix.
|
||||||
|
// if beego.AutoPrefix("/admin",&MainContorlller{}) and MainController has methods List and Page,
|
||||||
|
// visit the url /admin/main/list to exec List function or /admin/main/page to exec Page function.
|
||||||
|
func AutoPrefix(prefix string, c ControllerInterface) *App {
|
||||||
|
BeeApp.Handlers.AddAutoPrefix(prefix, c)
|
||||||
|
return BeeApp
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get used to register router for Get method
|
||||||
|
// usage:
|
||||||
|
// beego.Get("/", func(ctx *context.Context){
|
||||||
|
// ctx.Output.Body("hello world")
|
||||||
|
// })
|
||||||
|
func Get(rootpath string, f FilterFunc) *App {
|
||||||
|
BeeApp.Handlers.Get(rootpath, f)
|
||||||
|
return BeeApp
|
||||||
|
}
|
||||||
|
|
||||||
|
// Post used to register router for Post method
|
||||||
|
// usage:
|
||||||
|
// beego.Post("/api", func(ctx *context.Context){
|
||||||
|
// ctx.Output.Body("hello world")
|
||||||
|
// })
|
||||||
|
func Post(rootpath string, f FilterFunc) *App {
|
||||||
|
BeeApp.Handlers.Post(rootpath, f)
|
||||||
|
return BeeApp
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete used to register router for Delete method
|
||||||
|
// usage:
|
||||||
|
// beego.Delete("/api", func(ctx *context.Context){
|
||||||
|
// ctx.Output.Body("hello world")
|
||||||
|
// })
|
||||||
|
func Delete(rootpath string, f FilterFunc) *App {
|
||||||
|
BeeApp.Handlers.Delete(rootpath, f)
|
||||||
|
return BeeApp
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put used to register router for Put method
|
||||||
|
// usage:
|
||||||
|
// beego.Put("/api", func(ctx *context.Context){
|
||||||
|
// ctx.Output.Body("hello world")
|
||||||
|
// })
|
||||||
|
func Put(rootpath string, f FilterFunc) *App {
|
||||||
|
BeeApp.Handlers.Put(rootpath, f)
|
||||||
|
return BeeApp
|
||||||
|
}
|
||||||
|
|
||||||
|
// Head used to register router for Head method
|
||||||
|
// usage:
|
||||||
|
// beego.Head("/api", func(ctx *context.Context){
|
||||||
|
// ctx.Output.Body("hello world")
|
||||||
|
// })
|
||||||
|
func Head(rootpath string, f FilterFunc) *App {
|
||||||
|
BeeApp.Handlers.Head(rootpath, f)
|
||||||
|
return BeeApp
|
||||||
|
}
|
||||||
|
|
||||||
|
// Options used to register router for Options method
|
||||||
|
// usage:
|
||||||
|
// beego.Options("/api", func(ctx *context.Context){
|
||||||
|
// ctx.Output.Body("hello world")
|
||||||
|
// })
|
||||||
|
func Options(rootpath string, f FilterFunc) *App {
|
||||||
|
BeeApp.Handlers.Options(rootpath, f)
|
||||||
|
return BeeApp
|
||||||
|
}
|
||||||
|
|
||||||
|
// Patch used to register router for Patch method
|
||||||
|
// usage:
|
||||||
|
// beego.Patch("/api", func(ctx *context.Context){
|
||||||
|
// ctx.Output.Body("hello world")
|
||||||
|
// })
|
||||||
|
func Patch(rootpath string, f FilterFunc) *App {
|
||||||
|
BeeApp.Handlers.Patch(rootpath, f)
|
||||||
|
return BeeApp
|
||||||
|
}
|
||||||
|
|
||||||
|
// Any used to register router for all methods
|
||||||
|
// usage:
|
||||||
|
// beego.Any("/api", func(ctx *context.Context){
|
||||||
|
// ctx.Output.Body("hello world")
|
||||||
|
// })
|
||||||
|
func Any(rootpath string, f FilterFunc) *App {
|
||||||
|
BeeApp.Handlers.Any(rootpath, f)
|
||||||
|
return BeeApp
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handler used to register a Handler router
|
||||||
|
// usage:
|
||||||
|
// beego.Handler("/api", func(ctx *context.Context){
|
||||||
|
// ctx.Output.Body("hello world")
|
||||||
|
// })
|
||||||
|
func Handler(rootpath string, h http.Handler, options ...interface{}) *App {
|
||||||
|
BeeApp.Handlers.Handler(rootpath, h, options...)
|
||||||
|
return BeeApp
|
||||||
|
}
|
||||||
|
|
||||||
|
// InsertFilter adds a FilterFunc with pattern condition and action constant.
|
||||||
|
// The pos means action constant including
|
||||||
|
// beego.BeforeStatic, beego.BeforeRouter, beego.BeforeExec, beego.AfterExec and beego.FinishRouter.
|
||||||
|
// The bool params is for setting the returnOnOutput value (false allows multiple filters to execute)
|
||||||
|
func InsertFilter(pattern string, pos int, filter FilterFunc, params ...bool) *App {
|
||||||
|
BeeApp.Handlers.InsertFilter(pattern, pos, filter, params...)
|
||||||
|
return BeeApp
|
||||||
|
}
|
|
@ -0,0 +1,100 @@
|
||||||
|
// Copyright 2014 beego Author. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package beego
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// VERSION represent beego web framework version.
|
||||||
|
VERSION = "1.8.0"
|
||||||
|
|
||||||
|
// DEV is for develop
|
||||||
|
DEV = "dev"
|
||||||
|
// PROD is for production
|
||||||
|
PROD = "prod"
|
||||||
|
)
|
||||||
|
|
||||||
|
//hook function to run
|
||||||
|
type hookfunc func() error
|
||||||
|
|
||||||
|
var (
|
||||||
|
hooks = make([]hookfunc, 0) //hook function slice to store the hookfunc
|
||||||
|
)
|
||||||
|
|
||||||
|
// AddAPPStartHook is used to register the hookfunc
|
||||||
|
// The hookfuncs will run in beego.Run()
|
||||||
|
// such as sessionInit, middlerware start, buildtemplate, admin start
|
||||||
|
func AddAPPStartHook(hf hookfunc) {
|
||||||
|
hooks = append(hooks, hf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run beego application.
|
||||||
|
// beego.Run() default run on HttpPort
|
||||||
|
// beego.Run("localhost")
|
||||||
|
// beego.Run(":8089")
|
||||||
|
// beego.Run("127.0.0.1:8089")
|
||||||
|
func Run(params ...string) {
|
||||||
|
|
||||||
|
initBeforeHTTPRun()
|
||||||
|
|
||||||
|
if len(params) > 0 && params[0] != "" {
|
||||||
|
strs := strings.Split(params[0], ":")
|
||||||
|
if len(strs) > 0 && strs[0] != "" {
|
||||||
|
BConfig.Listen.HTTPAddr = strs[0]
|
||||||
|
}
|
||||||
|
if len(strs) > 1 && strs[1] != "" {
|
||||||
|
BConfig.Listen.HTTPPort, _ = strconv.Atoi(strs[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BeeApp.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
func initBeforeHTTPRun() {
|
||||||
|
//init hooks
|
||||||
|
AddAPPStartHook(registerMime)
|
||||||
|
AddAPPStartHook(registerDefaultErrorHandler)
|
||||||
|
AddAPPStartHook(registerSession)
|
||||||
|
AddAPPStartHook(registerTemplate)
|
||||||
|
AddAPPStartHook(registerAdmin)
|
||||||
|
AddAPPStartHook(registerGzip)
|
||||||
|
|
||||||
|
for _, hk := range hooks {
|
||||||
|
if err := hk(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestBeegoInit is for test package init
|
||||||
|
func TestBeegoInit(ap string) {
|
||||||
|
path := filepath.Join(ap, "conf", "app.conf")
|
||||||
|
os.Chdir(ap)
|
||||||
|
InitBeegoBeforeTest(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InitBeegoBeforeTest is for test package init
|
||||||
|
func InitBeegoBeforeTest(appConfigPath string) {
|
||||||
|
if err := LoadAppConfig(appConfigProvider, appConfigPath); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
BConfig.RunMode = "test"
|
||||||
|
initBeforeHTTPRun()
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
## cache
|
||||||
|
cache is a Go cache manager. It can use many cache adapters. The repo is inspired by `database/sql` .
|
||||||
|
|
||||||
|
|
||||||
|
## How to install?
|
||||||
|
|
||||||
|
go get github.com/astaxie/beego/cache
|
||||||
|
|
||||||
|
|
||||||
|
## What adapters are supported?
|
||||||
|
|
||||||
|
As of now this cache support memory, Memcache and Redis.
|
||||||
|
|
||||||
|
|
||||||
|
## How to use it?
|
||||||
|
|
||||||
|
First you must import it
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/astaxie/beego/cache"
|
||||||
|
)
|
||||||
|
|
||||||
|
Then init a Cache (example with memory adapter)
|
||||||
|
|
||||||
|
bm, err := cache.NewCache("memory", `{"interval":60}`)
|
||||||
|
|
||||||
|
Use it like this:
|
||||||
|
|
||||||
|
bm.Put("astaxie", 1, 10 * time.Second)
|
||||||
|
bm.Get("astaxie")
|
||||||
|
bm.IsExist("astaxie")
|
||||||
|
bm.Delete("astaxie")
|
||||||
|
|
||||||
|
|
||||||
|
## Memory adapter
|
||||||
|
|
||||||
|
Configure memory adapter like this:
|
||||||
|
|
||||||
|
{"interval":60}
|
||||||
|
|
||||||
|
interval means the gc time. The cache will check at each time interval, whether item has expired.
|
||||||
|
|
||||||
|
|
||||||
|
## Memcache adapter
|
||||||
|
|
||||||
|
Memcache adapter use the [gomemcache](http://github.com/bradfitz/gomemcache) client.
|
||||||
|
|
||||||
|
Configure like this:
|
||||||
|
|
||||||
|
{"conn":"127.0.0.1:11211"}
|
||||||
|
|
||||||
|
|
||||||
|
## Redis adapter
|
||||||
|
|
||||||
|
Redis adapter use the [redigo](http://github.com/garyburd/redigo) client.
|
||||||
|
|
||||||
|
Configure like this:
|
||||||
|
|
||||||
|
{"conn":":6039"}
|
|
@ -0,0 +1,103 @@
|
||||||
|
// Copyright 2014 beego Author. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// Package cache provide a Cache interface and some implemetn engine
|
||||||
|
// Usage:
|
||||||
|
//
|
||||||
|
// import(
|
||||||
|
// "github.com/astaxie/beego/cache"
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// bm, err := cache.NewCache("memory", `{"interval":60}`)
|
||||||
|
//
|
||||||
|
// Use it like this:
|
||||||
|
//
|
||||||
|
// bm.Put("astaxie", 1, 10 * time.Second)
|
||||||
|
// bm.Get("astaxie")
|
||||||
|
// bm.IsExist("astaxie")
|
||||||
|
// bm.Delete("astaxie")
|
||||||
|
//
|
||||||
|
// more docs http://beego.me/docs/module/cache.md
|
||||||
|
package cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Cache interface contains all behaviors for cache adapter.
|
||||||
|
// usage:
|
||||||
|
// cache.Register("file",cache.NewFileCache) // this operation is run in init method of file.go.
|
||||||
|
// c,err := cache.NewCache("file","{....}")
|
||||||
|
// c.Put("key",value, 3600 * time.Second)
|
||||||
|
// v := c.Get("key")
|
||||||
|
//
|
||||||
|
// c.Incr("counter") // now is 1
|
||||||
|
// c.Incr("counter") // now is 2
|
||||||
|
// count := c.Get("counter").(int)
|
||||||
|
type Cache interface {
|
||||||
|
// get cached value by key.
|
||||||
|
Get(key string) interface{}
|
||||||
|
// GetMulti is a batch version of Get.
|
||||||
|
GetMulti(keys []string) []interface{}
|
||||||
|
// set cached value with key and expire time.
|
||||||
|
Put(key string, val interface{}, timeout time.Duration) error
|
||||||
|
// delete cached value by key.
|
||||||
|
Delete(key string) error
|
||||||
|
// increase cached int value by key, as a counter.
|
||||||
|
Incr(key string) error
|
||||||
|
// decrease cached int value by key, as a counter.
|
||||||
|
Decr(key string) error
|
||||||
|
// check if cached value exists or not.
|
||||||
|
IsExist(key string) bool
|
||||||
|
// clear all cache.
|
||||||
|
ClearAll() error
|
||||||
|
// start gc routine based on config string settings.
|
||||||
|
StartAndGC(config string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Instance is a function create a new Cache Instance
|
||||||
|
type Instance func() Cache
|
||||||
|
|
||||||
|
var adapters = make(map[string]Instance)
|
||||||
|
|
||||||
|
// Register makes a cache adapter available by the adapter name.
|
||||||
|
// If Register is called twice with the same name or if driver is nil,
|
||||||
|
// it panics.
|
||||||
|
func Register(name string, adapter Instance) {
|
||||||
|
if adapter == nil {
|
||||||
|
panic("cache: Register adapter is nil")
|
||||||
|
}
|
||||||
|
if _, ok := adapters[name]; ok {
|
||||||
|
panic("cache: Register called twice for adapter " + name)
|
||||||
|
}
|
||||||
|
adapters[name] = adapter
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCache Create a new cache driver by adapter name and config string.
|
||||||
|
// config need to be correct JSON as string: {"interval":360}.
|
||||||
|
// it will start gc automatically.
|
||||||
|
func NewCache(adapterName, config string) (adapter Cache, err error) {
|
||||||
|
instanceFunc, ok := adapters[adapterName]
|
||||||
|
if !ok {
|
||||||
|
err = fmt.Errorf("cache: unknown adapter name %q (forgot to import?)", adapterName)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
adapter = instanceFunc()
|
||||||
|
err = adapter.StartAndGC(config)
|
||||||
|
if err != nil {
|
||||||
|
adapter = nil
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,168 @@
|
||||||
|
// Copyright 2014 beego Author. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCache(t *testing.T) {
|
||||||
|
bm, err := NewCache("memory", `{"interval":20}`)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("init err")
|
||||||
|
}
|
||||||
|
timeoutDuration := 10 * time.Second
|
||||||
|
if err = bm.Put("astaxie", 1, timeoutDuration); err != nil {
|
||||||
|
t.Error("set Error", err)
|
||||||
|
}
|
||||||
|
if !bm.IsExist("astaxie") {
|
||||||
|
t.Error("check err")
|
||||||
|
}
|
||||||
|
|
||||||
|
if v := bm.Get("astaxie"); v.(int) != 1 {
|
||||||
|
t.Error("get err")
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(30 * time.Second)
|
||||||
|
|
||||||
|
if bm.IsExist("astaxie") {
|
||||||
|
t.Error("check err")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = bm.Put("astaxie", 1, timeoutDuration); err != nil {
|
||||||
|
t.Error("set Error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = bm.Incr("astaxie"); err != nil {
|
||||||
|
t.Error("Incr Error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v := bm.Get("astaxie"); v.(int) != 2 {
|
||||||
|
t.Error("get err")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = bm.Decr("astaxie"); err != nil {
|
||||||
|
t.Error("Decr Error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v := bm.Get("astaxie"); v.(int) != 1 {
|
||||||
|
t.Error("get err")
|
||||||
|
}
|
||||||
|
bm.Delete("astaxie")
|
||||||
|
if bm.IsExist("astaxie") {
|
||||||
|
t.Error("delete err")
|
||||||
|
}
|
||||||
|
|
||||||
|
//test GetMulti
|
||||||
|
if err = bm.Put("astaxie", "author", timeoutDuration); err != nil {
|
||||||
|
t.Error("set Error", err)
|
||||||
|
}
|
||||||
|
if !bm.IsExist("astaxie") {
|
||||||
|
t.Error("check err")
|
||||||
|
}
|
||||||
|
if v := bm.Get("astaxie"); v.(string) != "author" {
|
||||||
|
t.Error("get err")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = bm.Put("astaxie1", "author1", timeoutDuration); err != nil {
|
||||||
|
t.Error("set Error", err)
|
||||||
|
}
|
||||||
|
if !bm.IsExist("astaxie1") {
|
||||||
|
t.Error("check err")
|
||||||
|
}
|
||||||
|
|
||||||
|
vv := bm.GetMulti([]string{"astaxie", "astaxie1"})
|
||||||
|
if len(vv) != 2 {
|
||||||
|
t.Error("GetMulti ERROR")
|
||||||
|
}
|
||||||
|
if vv[0].(string) != "author" {
|
||||||
|
t.Error("GetMulti ERROR")
|
||||||
|
}
|
||||||
|
if vv[1].(string) != "author1" {
|
||||||
|
t.Error("GetMulti ERROR")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileCache(t *testing.T) {
|
||||||
|
bm, err := NewCache("file", `{"CachePath":"cache","FileSuffix":".bin","DirectoryLevel":2,"EmbedExpiry":0}`)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("init err")
|
||||||
|
}
|
||||||
|
timeoutDuration := 10 * time.Second
|
||||||
|
if err = bm.Put("astaxie", 1, timeoutDuration); err != nil {
|
||||||
|
t.Error("set Error", err)
|
||||||
|
}
|
||||||
|
if !bm.IsExist("astaxie") {
|
||||||
|
t.Error("check err")
|
||||||
|
}
|
||||||
|
|
||||||
|
if v := bm.Get("astaxie"); v.(int) != 1 {
|
||||||
|
t.Error("get err")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = bm.Incr("astaxie"); err != nil {
|
||||||
|
t.Error("Incr Error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v := bm.Get("astaxie"); v.(int) != 2 {
|
||||||
|
t.Error("get err")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = bm.Decr("astaxie"); err != nil {
|
||||||
|
t.Error("Decr Error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v := bm.Get("astaxie"); v.(int) != 1 {
|
||||||
|
t.Error("get err")
|
||||||
|
}
|
||||||
|
bm.Delete("astaxie")
|
||||||
|
if bm.IsExist("astaxie") {
|
||||||
|
t.Error("delete err")
|
||||||
|
}
|
||||||
|
|
||||||
|
//test string
|
||||||
|
if err = bm.Put("astaxie", "author", timeoutDuration); err != nil {
|
||||||
|
t.Error("set Error", err)
|
||||||
|
}
|
||||||
|
if !bm.IsExist("astaxie") {
|
||||||
|
t.Error("check err")
|
||||||
|
}
|
||||||
|
if v := bm.Get("astaxie"); v.(string) != "author" {
|
||||||
|
t.Error("get err")
|
||||||
|
}
|
||||||
|
|
||||||
|
//test GetMulti
|
||||||
|
if err = bm.Put("astaxie1", "author1", timeoutDuration); err != nil {
|
||||||
|
t.Error("set Error", err)
|
||||||
|
}
|
||||||
|
if !bm.IsExist("astaxie1") {
|
||||||
|
t.Error("check err")
|
||||||
|
}
|
||||||
|
|
||||||
|
vv := bm.GetMulti([]string{"astaxie", "astaxie1"})
|
||||||
|
if len(vv) != 2 {
|
||||||
|
t.Error("GetMulti ERROR")
|
||||||
|
}
|
||||||
|
if vv[0].(string) != "author" {
|
||||||
|
t.Error("GetMulti ERROR")
|
||||||
|
}
|
||||||
|
if vv[1].(string) != "author1" {
|
||||||
|
t.Error("GetMulti ERROR")
|
||||||
|
}
|
||||||
|
|
||||||
|
os.RemoveAll("cache")
|
||||||
|
}
|
|
@ -0,0 +1,100 @@
|
||||||
|
// Copyright 2014 beego Author. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetString convert interface to string.
|
||||||
|
func GetString(v interface{}) string {
|
||||||
|
switch result := v.(type) {
|
||||||
|
case string:
|
||||||
|
return result
|
||||||
|
case []byte:
|
||||||
|
return string(result)
|
||||||
|
default:
|
||||||
|
if v != nil {
|
||||||
|
return fmt.Sprintf("%v", result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetInt convert interface to int.
|
||||||
|
func GetInt(v interface{}) int {
|
||||||
|
switch result := v.(type) {
|
||||||
|
case int:
|
||||||
|
return result
|
||||||
|
case int32:
|
||||||
|
return int(result)
|
||||||
|
case int64:
|
||||||
|
return int(result)
|
||||||
|
default:
|
||||||
|
if d := GetString(v); d != "" {
|
||||||
|
value, _ := strconv.Atoi(d)
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetInt64 convert interface to int64.
|
||||||
|
func GetInt64(v interface{}) int64 {
|
||||||
|
switch result := v.(type) {
|
||||||
|
case int:
|
||||||
|
return int64(result)
|
||||||
|
case int32:
|
||||||
|
return int64(result)
|
||||||
|
case int64:
|
||||||
|
return result
|
||||||
|
default:
|
||||||
|
|
||||||
|
if d := GetString(v); d != "" {
|
||||||
|
value, _ := strconv.ParseInt(d, 10, 64)
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFloat64 convert interface to float64.
|
||||||
|
func GetFloat64(v interface{}) float64 {
|
||||||
|
switch result := v.(type) {
|
||||||
|
case float64:
|
||||||
|
return result
|
||||||
|
default:
|
||||||
|
if d := GetString(v); d != "" {
|
||||||
|
value, _ := strconv.ParseFloat(d, 64)
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBool convert interface to bool.
|
||||||
|
func GetBool(v interface{}) bool {
|
||||||
|
switch result := v.(type) {
|
||||||
|
case bool:
|
||||||
|
return result
|
||||||
|
default:
|
||||||
|
if d := GetString(v); d != "" {
|
||||||
|
value, _ := strconv.ParseBool(d)
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
|
@ -0,0 +1,143 @@
|
||||||
|
// Copyright 2014 beego Author. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetString(t *testing.T) {
|
||||||
|
var t1 = "test1"
|
||||||
|
if "test1" != GetString(t1) {
|
||||||
|
t.Error("get string from string error")
|
||||||
|
}
|
||||||
|
var t2 = []byte("test2")
|
||||||
|
if "test2" != GetString(t2) {
|
||||||
|
t.Error("get string from byte array error")
|
||||||
|
}
|
||||||
|
var t3 = 1
|
||||||
|
if "1" != GetString(t3) {
|
||||||
|
t.Error("get string from int error")
|
||||||
|
}
|
||||||
|
var t4 int64 = 1
|
||||||
|
if "1" != GetString(t4) {
|
||||||
|
t.Error("get string from int64 error")
|
||||||
|
}
|
||||||
|
var t5 = 1.1
|
||||||
|
if "1.1" != GetString(t5) {
|
||||||
|
t.Error("get string from float64 error")
|
||||||
|
}
|
||||||
|
|
||||||
|
if "" != GetString(nil) {
|
||||||
|
t.Error("get string from nil error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetInt(t *testing.T) {
|
||||||
|
var t1 = 1
|
||||||
|
if 1 != GetInt(t1) {
|
||||||
|
t.Error("get int from int error")
|
||||||
|
}
|
||||||
|
var t2 int32 = 32
|
||||||
|
if 32 != GetInt(t2) {
|
||||||
|
t.Error("get int from int32 error")
|
||||||
|
}
|
||||||
|
var t3 int64 = 64
|
||||||
|
if 64 != GetInt(t3) {
|
||||||
|
t.Error("get int from int64 error")
|
||||||
|
}
|
||||||
|
var t4 = "128"
|
||||||
|
if 128 != GetInt(t4) {
|
||||||
|
t.Error("get int from num string error")
|
||||||
|
}
|
||||||
|
if 0 != GetInt(nil) {
|
||||||
|
t.Error("get int from nil error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetInt64(t *testing.T) {
|
||||||
|
var i int64 = 1
|
||||||
|
var t1 = 1
|
||||||
|
if i != GetInt64(t1) {
|
||||||
|
t.Error("get int64 from int error")
|
||||||
|
}
|
||||||
|
var t2 int32 = 1
|
||||||
|
if i != GetInt64(t2) {
|
||||||
|
t.Error("get int64 from int32 error")
|
||||||
|
}
|
||||||
|
var t3 int64 = 1
|
||||||
|
if i != GetInt64(t3) {
|
||||||
|
t.Error("get int64 from int64 error")
|
||||||
|
}
|
||||||
|
var t4 = "1"
|
||||||
|
if i != GetInt64(t4) {
|
||||||
|
t.Error("get int64 from num string error")
|
||||||
|
}
|
||||||
|
if 0 != GetInt64(nil) {
|
||||||
|
t.Error("get int64 from nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetFloat64(t *testing.T) {
|
||||||
|
var f = 1.11
|
||||||
|
var t1 float32 = 1.11
|
||||||
|
if f != GetFloat64(t1) {
|
||||||
|
t.Error("get float64 from float32 error")
|
||||||
|
}
|
||||||
|
var t2 = 1.11
|
||||||
|
if f != GetFloat64(t2) {
|
||||||
|
t.Error("get float64 from float64 error")
|
||||||
|
}
|
||||||
|
var t3 = "1.11"
|
||||||
|
if f != GetFloat64(t3) {
|
||||||
|
t.Error("get float64 from string error")
|
||||||
|
}
|
||||||
|
|
||||||
|
var f2 float64 = 1
|
||||||
|
var t4 = 1
|
||||||
|
if f2 != GetFloat64(t4) {
|
||||||
|
t.Error("get float64 from int error")
|
||||||
|
}
|
||||||
|
|
||||||
|
if 0 != GetFloat64(nil) {
|
||||||
|
t.Error("get float64 from nil error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetBool(t *testing.T) {
|
||||||
|
var t1 = true
|
||||||
|
if true != GetBool(t1) {
|
||||||
|
t.Error("get bool from bool error")
|
||||||
|
}
|
||||||
|
var t2 = "true"
|
||||||
|
if true != GetBool(t2) {
|
||||||
|
t.Error("get bool from string error")
|
||||||
|
}
|
||||||
|
if false != GetBool(nil) {
|
||||||
|
t.Error("get bool from nil error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func byteArrayEquals(a []byte, b []byte) bool {
|
||||||
|
if len(a) != len(b) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i, v := range a {
|
||||||
|
if v != b[i] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
|
@ -0,0 +1,255 @@
|
||||||
|
// Copyright 2014 beego Author. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/md5"
|
||||||
|
"encoding/gob"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FileCacheItem is basic unit of file cache adapter.
|
||||||
|
// it contains data and expire time.
|
||||||
|
type FileCacheItem struct {
|
||||||
|
Data interface{}
|
||||||
|
Lastaccess time.Time
|
||||||
|
Expired time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileCache Config
|
||||||
|
var (
|
||||||
|
FileCachePath = "cache" // cache directory
|
||||||
|
FileCacheFileSuffix = ".bin" // cache file suffix
|
||||||
|
FileCacheDirectoryLevel = 2 // cache file deep level if auto generated cache files.
|
||||||
|
FileCacheEmbedExpiry time.Duration // cache expire time, default is no expire forever.
|
||||||
|
)
|
||||||
|
|
||||||
|
// FileCache is cache adapter for file storage.
|
||||||
|
type FileCache struct {
|
||||||
|
CachePath string
|
||||||
|
FileSuffix string
|
||||||
|
DirectoryLevel int
|
||||||
|
EmbedExpiry int
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFileCache Create new file cache with no config.
|
||||||
|
// the level and expiry need set in method StartAndGC as config string.
|
||||||
|
func NewFileCache() Cache {
|
||||||
|
// return &FileCache{CachePath:FileCachePath, FileSuffix:FileCacheFileSuffix}
|
||||||
|
return &FileCache{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartAndGC will start and begin gc for file cache.
|
||||||
|
// the config need to be like {CachePath:"/cache","FileSuffix":".bin","DirectoryLevel":2,"EmbedExpiry":0}
|
||||||
|
func (fc *FileCache) StartAndGC(config string) error {
|
||||||
|
|
||||||
|
var cfg map[string]string
|
||||||
|
json.Unmarshal([]byte(config), &cfg)
|
||||||
|
if _, ok := cfg["CachePath"]; !ok {
|
||||||
|
cfg["CachePath"] = FileCachePath
|
||||||
|
}
|
||||||
|
if _, ok := cfg["FileSuffix"]; !ok {
|
||||||
|
cfg["FileSuffix"] = FileCacheFileSuffix
|
||||||
|
}
|
||||||
|
if _, ok := cfg["DirectoryLevel"]; !ok {
|
||||||
|
cfg["DirectoryLevel"] = strconv.Itoa(FileCacheDirectoryLevel)
|
||||||
|
}
|
||||||
|
if _, ok := cfg["EmbedExpiry"]; !ok {
|
||||||
|
cfg["EmbedExpiry"] = strconv.FormatInt(int64(FileCacheEmbedExpiry.Seconds()), 10)
|
||||||
|
}
|
||||||
|
fc.CachePath = cfg["CachePath"]
|
||||||
|
fc.FileSuffix = cfg["FileSuffix"]
|
||||||
|
fc.DirectoryLevel, _ = strconv.Atoi(cfg["DirectoryLevel"])
|
||||||
|
fc.EmbedExpiry, _ = strconv.Atoi(cfg["EmbedExpiry"])
|
||||||
|
|
||||||
|
fc.Init()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init will make new dir for file cache if not exist.
|
||||||
|
func (fc *FileCache) Init() {
|
||||||
|
if ok, _ := exists(fc.CachePath); !ok { // todo : error handle
|
||||||
|
_ = os.MkdirAll(fc.CachePath, os.ModePerm) // todo : error handle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// get cached file name. it's md5 encoded.
|
||||||
|
func (fc *FileCache) getCacheFileName(key string) string {
|
||||||
|
m := md5.New()
|
||||||
|
io.WriteString(m, key)
|
||||||
|
keyMd5 := hex.EncodeToString(m.Sum(nil))
|
||||||
|
cachePath := fc.CachePath
|
||||||
|
switch fc.DirectoryLevel {
|
||||||
|
case 2:
|
||||||
|
cachePath = filepath.Join(cachePath, keyMd5[0:2], keyMd5[2:4])
|
||||||
|
case 1:
|
||||||
|
cachePath = filepath.Join(cachePath, keyMd5[0:2])
|
||||||
|
}
|
||||||
|
|
||||||
|
if ok, _ := exists(cachePath); !ok { // todo : error handle
|
||||||
|
_ = os.MkdirAll(cachePath, os.ModePerm) // todo : error handle
|
||||||
|
}
|
||||||
|
|
||||||
|
return filepath.Join(cachePath, fmt.Sprintf("%s%s", keyMd5, fc.FileSuffix))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get value from file cache.
|
||||||
|
// if non-exist or expired, return empty string.
|
||||||
|
func (fc *FileCache) Get(key string) interface{} {
|
||||||
|
fileData, err := FileGetContents(fc.getCacheFileName(key))
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
var to FileCacheItem
|
||||||
|
GobDecode(fileData, &to)
|
||||||
|
if to.Expired.Before(time.Now()) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return to.Data
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMulti gets values from file cache.
|
||||||
|
// if non-exist or expired, return empty string.
|
||||||
|
func (fc *FileCache) GetMulti(keys []string) []interface{} {
|
||||||
|
var rc []interface{}
|
||||||
|
for _, key := range keys {
|
||||||
|
rc = append(rc, fc.Get(key))
|
||||||
|
}
|
||||||
|
return rc
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put value into file cache.
|
||||||
|
// timeout means how long to keep this file, unit of ms.
|
||||||
|
// if timeout equals FileCacheEmbedExpiry(default is 0), cache this item forever.
|
||||||
|
func (fc *FileCache) Put(key string, val interface{}, timeout time.Duration) error {
|
||||||
|
gob.Register(val)
|
||||||
|
|
||||||
|
item := FileCacheItem{Data: val}
|
||||||
|
if timeout == FileCacheEmbedExpiry {
|
||||||
|
item.Expired = time.Now().Add((86400 * 365 * 10) * time.Second) // ten years
|
||||||
|
} else {
|
||||||
|
item.Expired = time.Now().Add(timeout)
|
||||||
|
}
|
||||||
|
item.Lastaccess = time.Now()
|
||||||
|
data, err := GobEncode(item)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return FilePutContents(fc.getCacheFileName(key), data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete file cache value.
|
||||||
|
func (fc *FileCache) Delete(key string) error {
|
||||||
|
filename := fc.getCacheFileName(key)
|
||||||
|
if ok, _ := exists(filename); ok {
|
||||||
|
return os.Remove(filename)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Incr will increase cached int value.
|
||||||
|
// fc value is saving forever unless Delete.
|
||||||
|
func (fc *FileCache) Incr(key string) error {
|
||||||
|
data := fc.Get(key)
|
||||||
|
var incr int
|
||||||
|
if reflect.TypeOf(data).Name() != "int" {
|
||||||
|
incr = 0
|
||||||
|
} else {
|
||||||
|
incr = data.(int) + 1
|
||||||
|
}
|
||||||
|
fc.Put(key, incr, FileCacheEmbedExpiry)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decr will decrease cached int value.
|
||||||
|
func (fc *FileCache) Decr(key string) error {
|
||||||
|
data := fc.Get(key)
|
||||||
|
var decr int
|
||||||
|
if reflect.TypeOf(data).Name() != "int" || data.(int)-1 <= 0 {
|
||||||
|
decr = 0
|
||||||
|
} else {
|
||||||
|
decr = data.(int) - 1
|
||||||
|
}
|
||||||
|
fc.Put(key, decr, FileCacheEmbedExpiry)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsExist check value is exist.
|
||||||
|
func (fc *FileCache) IsExist(key string) bool {
|
||||||
|
ret, _ := exists(fc.getCacheFileName(key))
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearAll will clean cached files.
|
||||||
|
// not implemented.
|
||||||
|
func (fc *FileCache) ClearAll() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// check file exist.
|
||||||
|
func exists(path string) (bool, error) {
|
||||||
|
_, err := os.Stat(path)
|
||||||
|
if err == nil {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileGetContents Get bytes to file.
|
||||||
|
// if non-exist, create this file.
|
||||||
|
func FileGetContents(filename string) (data []byte, e error) {
|
||||||
|
return ioutil.ReadFile(filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FilePutContents Put bytes to file.
|
||||||
|
// if non-exist, create this file.
|
||||||
|
func FilePutContents(filename string, content []byte) error {
|
||||||
|
return ioutil.WriteFile(filename, content, os.ModePerm)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GobEncode Gob encodes file cache item.
|
||||||
|
func GobEncode(data interface{}) ([]byte, error) {
|
||||||
|
buf := bytes.NewBuffer(nil)
|
||||||
|
enc := gob.NewEncoder(buf)
|
||||||
|
err := enc.Encode(data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return buf.Bytes(), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GobDecode Gob decodes file cache item.
|
||||||
|
func GobDecode(data []byte, to *FileCacheItem) error {
|
||||||
|
buf := bytes.NewBuffer(data)
|
||||||
|
dec := gob.NewDecoder(buf)
|
||||||
|
return dec.Decode(&to)
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Register("file", NewFileCache)
|
||||||
|
}
|
|
@ -0,0 +1,191 @@
|
||||||
|
// Copyright 2014 beego Author. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// Package memcache for cache provider
|
||||||
|
//
|
||||||
|
// depend on github.com/bradfitz/gomemcache/memcache
|
||||||
|
//
|
||||||
|
// go install github.com/bradfitz/gomemcache/memcache
|
||||||
|
//
|
||||||
|
// Usage:
|
||||||
|
// import(
|
||||||
|
// _ "github.com/astaxie/beego/cache/memcache"
|
||||||
|
// "github.com/astaxie/beego/cache"
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// bm, err := cache.NewCache("memcache", `{"conn":"127.0.0.1:11211"}`)
|
||||||
|
//
|
||||||
|
// more docs http://beego.me/docs/module/cache.md
|
||||||
|
package memcache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego/cache"
|
||||||
|
"github.com/bradfitz/gomemcache/memcache"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Cache Memcache adapter.
|
||||||
|
type Cache struct {
|
||||||
|
conn *memcache.Client
|
||||||
|
conninfo []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMemCache create new memcache adapter.
|
||||||
|
func NewMemCache() cache.Cache {
|
||||||
|
return &Cache{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get get value from memcache.
|
||||||
|
func (rc *Cache) Get(key string) interface{} {
|
||||||
|
if rc.conn == nil {
|
||||||
|
if err := rc.connectInit(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if item, err := rc.conn.Get(key); err == nil {
|
||||||
|
return item.Value
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMulti get value from memcache.
|
||||||
|
func (rc *Cache) GetMulti(keys []string) []interface{} {
|
||||||
|
size := len(keys)
|
||||||
|
var rv []interface{}
|
||||||
|
if rc.conn == nil {
|
||||||
|
if err := rc.connectInit(); err != nil {
|
||||||
|
for i := 0; i < size; i++ {
|
||||||
|
rv = append(rv, err)
|
||||||
|
}
|
||||||
|
return rv
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mv, err := rc.conn.GetMulti(keys)
|
||||||
|
if err == nil {
|
||||||
|
for _, v := range mv {
|
||||||
|
rv = append(rv, v.Value)
|
||||||
|
}
|
||||||
|
return rv
|
||||||
|
}
|
||||||
|
for i := 0; i < size; i++ {
|
||||||
|
rv = append(rv, err)
|
||||||
|
}
|
||||||
|
return rv
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put put value to memcache.
|
||||||
|
func (rc *Cache) Put(key string, val interface{}, timeout time.Duration) error {
|
||||||
|
if rc.conn == nil {
|
||||||
|
if err := rc.connectInit(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
item := memcache.Item{Key: key, Expiration: int32(timeout / time.Second)}
|
||||||
|
if v, ok := val.([]byte); ok {
|
||||||
|
item.Value = v
|
||||||
|
} else if str, ok := val.(string); ok {
|
||||||
|
item.Value = []byte(str)
|
||||||
|
} else {
|
||||||
|
return errors.New("val only support string and []byte")
|
||||||
|
}
|
||||||
|
return rc.conn.Set(&item)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete delete value in memcache.
|
||||||
|
func (rc *Cache) Delete(key string) error {
|
||||||
|
if rc.conn == nil {
|
||||||
|
if err := rc.connectInit(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rc.conn.Delete(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Incr increase counter.
|
||||||
|
func (rc *Cache) Incr(key string) error {
|
||||||
|
if rc.conn == nil {
|
||||||
|
if err := rc.connectInit(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, err := rc.conn.Increment(key, 1)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decr decrease counter.
|
||||||
|
func (rc *Cache) Decr(key string) error {
|
||||||
|
if rc.conn == nil {
|
||||||
|
if err := rc.connectInit(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, err := rc.conn.Decrement(key, 1)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsExist check value exists in memcache.
|
||||||
|
func (rc *Cache) IsExist(key string) bool {
|
||||||
|
if rc.conn == nil {
|
||||||
|
if err := rc.connectInit(); err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, err := rc.conn.Get(key)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearAll clear all cached in memcache.
|
||||||
|
func (rc *Cache) ClearAll() error {
|
||||||
|
if rc.conn == nil {
|
||||||
|
if err := rc.connectInit(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rc.conn.FlushAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartAndGC start memcache adapter.
|
||||||
|
// config string is like {"conn":"connection info"}.
|
||||||
|
// if connecting error, return.
|
||||||
|
func (rc *Cache) StartAndGC(config string) error {
|
||||||
|
var cf map[string]string
|
||||||
|
json.Unmarshal([]byte(config), &cf)
|
||||||
|
if _, ok := cf["conn"]; !ok {
|
||||||
|
return errors.New("config has no conn key")
|
||||||
|
}
|
||||||
|
rc.conninfo = strings.Split(cf["conn"], ";")
|
||||||
|
if rc.conn == nil {
|
||||||
|
if err := rc.connectInit(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// connect to memcache and keep the connection.
|
||||||
|
func (rc *Cache) connectInit() error {
|
||||||
|
rc.conn = memcache.New(rc.conninfo...)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
cache.Register("memcache", NewMemCache)
|
||||||
|
}
|
|
@ -0,0 +1,108 @@
|
||||||
|
// Copyright 2014 beego Author. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package memcache
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "github.com/bradfitz/gomemcache/memcache"
|
||||||
|
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego/cache"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMemcacheCache(t *testing.T) {
|
||||||
|
bm, err := cache.NewCache("memcache", `{"conn": "127.0.0.1:11211"}`)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("init err")
|
||||||
|
}
|
||||||
|
timeoutDuration := 10 * time.Second
|
||||||
|
if err = bm.Put("astaxie", "1", timeoutDuration); err != nil {
|
||||||
|
t.Error("set Error", err)
|
||||||
|
}
|
||||||
|
if !bm.IsExist("astaxie") {
|
||||||
|
t.Error("check err")
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(11 * time.Second)
|
||||||
|
|
||||||
|
if bm.IsExist("astaxie") {
|
||||||
|
t.Error("check err")
|
||||||
|
}
|
||||||
|
if err = bm.Put("astaxie", "1", timeoutDuration); err != nil {
|
||||||
|
t.Error("set Error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, err := strconv.Atoi(string(bm.Get("astaxie").([]byte))); err != nil || v != 1 {
|
||||||
|
t.Error("get err")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = bm.Incr("astaxie"); err != nil {
|
||||||
|
t.Error("Incr Error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, err := strconv.Atoi(string(bm.Get("astaxie").([]byte))); err != nil || v != 2 {
|
||||||
|
t.Error("get err")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = bm.Decr("astaxie"); err != nil {
|
||||||
|
t.Error("Decr Error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, err := strconv.Atoi(string(bm.Get("astaxie").([]byte))); err != nil || v != 1 {
|
||||||
|
t.Error("get err")
|
||||||
|
}
|
||||||
|
bm.Delete("astaxie")
|
||||||
|
if bm.IsExist("astaxie") {
|
||||||
|
t.Error("delete err")
|
||||||
|
}
|
||||||
|
|
||||||
|
//test string
|
||||||
|
if err = bm.Put("astaxie", "author", timeoutDuration); err != nil {
|
||||||
|
t.Error("set Error", err)
|
||||||
|
}
|
||||||
|
if !bm.IsExist("astaxie") {
|
||||||
|
t.Error("check err")
|
||||||
|
}
|
||||||
|
|
||||||
|
if v := bm.Get("astaxie").([]byte); string(v) != "author" {
|
||||||
|
t.Error("get err")
|
||||||
|
}
|
||||||
|
|
||||||
|
//test GetMulti
|
||||||
|
if err = bm.Put("astaxie1", "author1", timeoutDuration); err != nil {
|
||||||
|
t.Error("set Error", err)
|
||||||
|
}
|
||||||
|
if !bm.IsExist("astaxie1") {
|
||||||
|
t.Error("check err")
|
||||||
|
}
|
||||||
|
|
||||||
|
vv := bm.GetMulti([]string{"astaxie", "astaxie1"})
|
||||||
|
if len(vv) != 2 {
|
||||||
|
t.Error("GetMulti ERROR")
|
||||||
|
}
|
||||||
|
if string(vv[0].([]byte)) != "author" && string(vv[0].([]byte)) != "author1" {
|
||||||
|
t.Error("GetMulti ERROR")
|
||||||
|
}
|
||||||
|
if string(vv[1].([]byte)) != "author1" && string(vv[1].([]byte)) != "author" {
|
||||||
|
t.Error("GetMulti ERROR")
|
||||||
|
}
|
||||||
|
|
||||||
|
// test clear all
|
||||||
|
if err = bm.ClearAll(); err != nil {
|
||||||
|
t.Error("clear all err")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,244 @@
|
||||||
|
// Copyright 2014 beego Author. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// DefaultEvery means the clock time of recycling the expired cache items in memory.
|
||||||
|
DefaultEvery = 60 // 1 minute
|
||||||
|
)
|
||||||
|
|
||||||
|
// MemoryItem store memory cache item.
|
||||||
|
type MemoryItem struct {
|
||||||
|
val interface{}
|
||||||
|
createdTime time.Time
|
||||||
|
lifespan time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mi *MemoryItem) isExpire() bool {
|
||||||
|
// 0 means forever
|
||||||
|
if mi.lifespan == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return time.Now().Sub(mi.createdTime) > mi.lifespan
|
||||||
|
}
|
||||||
|
|
||||||
|
// MemoryCache is Memory cache adapter.
|
||||||
|
// it contains a RW locker for safe map storage.
|
||||||
|
type MemoryCache struct {
|
||||||
|
sync.RWMutex
|
||||||
|
dur time.Duration
|
||||||
|
items map[string]*MemoryItem
|
||||||
|
Every int // run an expiration check Every clock time
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMemoryCache returns a new MemoryCache.
|
||||||
|
func NewMemoryCache() Cache {
|
||||||
|
cache := MemoryCache{items: make(map[string]*MemoryItem)}
|
||||||
|
return &cache
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get cache from memory.
|
||||||
|
// if non-existed or expired, return nil.
|
||||||
|
func (bc *MemoryCache) Get(name string) interface{} {
|
||||||
|
bc.RLock()
|
||||||
|
defer bc.RUnlock()
|
||||||
|
if itm, ok := bc.items[name]; ok {
|
||||||
|
if itm.isExpire() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return itm.val
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMulti gets caches from memory.
|
||||||
|
// if non-existed or expired, return nil.
|
||||||
|
func (bc *MemoryCache) GetMulti(names []string) []interface{} {
|
||||||
|
var rc []interface{}
|
||||||
|
for _, name := range names {
|
||||||
|
rc = append(rc, bc.Get(name))
|
||||||
|
}
|
||||||
|
return rc
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put cache to memory.
|
||||||
|
// if lifespan is 0, it will be forever till restart.
|
||||||
|
func (bc *MemoryCache) Put(name string, value interface{}, lifespan time.Duration) error {
|
||||||
|
bc.Lock()
|
||||||
|
defer bc.Unlock()
|
||||||
|
bc.items[name] = &MemoryItem{
|
||||||
|
val: value,
|
||||||
|
createdTime: time.Now(),
|
||||||
|
lifespan: lifespan,
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete cache in memory.
|
||||||
|
func (bc *MemoryCache) Delete(name string) error {
|
||||||
|
bc.Lock()
|
||||||
|
defer bc.Unlock()
|
||||||
|
if _, ok := bc.items[name]; !ok {
|
||||||
|
return errors.New("key not exist")
|
||||||
|
}
|
||||||
|
delete(bc.items, name)
|
||||||
|
if _, ok := bc.items[name]; ok {
|
||||||
|
return errors.New("delete key error")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Incr increase cache counter in memory.
|
||||||
|
// it supports int,int32,int64,uint,uint32,uint64.
|
||||||
|
func (bc *MemoryCache) Incr(key string) error {
|
||||||
|
bc.RLock()
|
||||||
|
defer bc.RUnlock()
|
||||||
|
itm, ok := bc.items[key]
|
||||||
|
if !ok {
|
||||||
|
return errors.New("key not exist")
|
||||||
|
}
|
||||||
|
switch itm.val.(type) {
|
||||||
|
case int:
|
||||||
|
itm.val = itm.val.(int) + 1
|
||||||
|
case int32:
|
||||||
|
itm.val = itm.val.(int32) + 1
|
||||||
|
case int64:
|
||||||
|
itm.val = itm.val.(int64) + 1
|
||||||
|
case uint:
|
||||||
|
itm.val = itm.val.(uint) + 1
|
||||||
|
case uint32:
|
||||||
|
itm.val = itm.val.(uint32) + 1
|
||||||
|
case uint64:
|
||||||
|
itm.val = itm.val.(uint64) + 1
|
||||||
|
default:
|
||||||
|
return errors.New("item val is not (u)int (u)int32 (u)int64")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decr decrease counter in memory.
|
||||||
|
func (bc *MemoryCache) Decr(key string) error {
|
||||||
|
bc.RLock()
|
||||||
|
defer bc.RUnlock()
|
||||||
|
itm, ok := bc.items[key]
|
||||||
|
if !ok {
|
||||||
|
return errors.New("key not exist")
|
||||||
|
}
|
||||||
|
switch itm.val.(type) {
|
||||||
|
case int:
|
||||||
|
itm.val = itm.val.(int) - 1
|
||||||
|
case int64:
|
||||||
|
itm.val = itm.val.(int64) - 1
|
||||||
|
case int32:
|
||||||
|
itm.val = itm.val.(int32) - 1
|
||||||
|
case uint:
|
||||||
|
if itm.val.(uint) > 0 {
|
||||||
|
itm.val = itm.val.(uint) - 1
|
||||||
|
} else {
|
||||||
|
return errors.New("item val is less than 0")
|
||||||
|
}
|
||||||
|
case uint32:
|
||||||
|
if itm.val.(uint32) > 0 {
|
||||||
|
itm.val = itm.val.(uint32) - 1
|
||||||
|
} else {
|
||||||
|
return errors.New("item val is less than 0")
|
||||||
|
}
|
||||||
|
case uint64:
|
||||||
|
if itm.val.(uint64) > 0 {
|
||||||
|
itm.val = itm.val.(uint64) - 1
|
||||||
|
} else {
|
||||||
|
return errors.New("item val is less than 0")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return errors.New("item val is not int int64 int32")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsExist check cache exist in memory.
|
||||||
|
func (bc *MemoryCache) IsExist(name string) bool {
|
||||||
|
bc.RLock()
|
||||||
|
defer bc.RUnlock()
|
||||||
|
if v, ok := bc.items[name]; ok {
|
||||||
|
return !v.isExpire()
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearAll will delete all cache in memory.
|
||||||
|
func (bc *MemoryCache) ClearAll() error {
|
||||||
|
bc.Lock()
|
||||||
|
defer bc.Unlock()
|
||||||
|
bc.items = make(map[string]*MemoryItem)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartAndGC start memory cache. it will check expiration in every clock time.
|
||||||
|
func (bc *MemoryCache) StartAndGC(config string) error {
|
||||||
|
var cf map[string]int
|
||||||
|
json.Unmarshal([]byte(config), &cf)
|
||||||
|
if _, ok := cf["interval"]; !ok {
|
||||||
|
cf = make(map[string]int)
|
||||||
|
cf["interval"] = DefaultEvery
|
||||||
|
}
|
||||||
|
dur := time.Duration(cf["interval"]) * time.Second
|
||||||
|
bc.Every = cf["interval"]
|
||||||
|
bc.dur = dur
|
||||||
|
go bc.vaccuum()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// check expiration.
|
||||||
|
func (bc *MemoryCache) vaccuum() {
|
||||||
|
if bc.Every < 1 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
<-time.After(bc.dur)
|
||||||
|
if bc.items == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for name := range bc.items {
|
||||||
|
bc.itemExpired(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// itemExpired returns true if an item is expired.
|
||||||
|
func (bc *MemoryCache) itemExpired(name string) bool {
|
||||||
|
bc.Lock()
|
||||||
|
defer bc.Unlock()
|
||||||
|
|
||||||
|
itm, ok := bc.items[name]
|
||||||
|
if !ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if itm.isExpire() {
|
||||||
|
delete(bc.items, name)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Register("memory", NewMemoryCache)
|
||||||
|
}
|
|
@ -0,0 +1,240 @@
|
||||||
|
// Copyright 2014 beego Author. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// Package redis for cache provider
|
||||||
|
//
|
||||||
|
// depend on github.com/garyburd/redigo/redis
|
||||||
|
//
|
||||||
|
// go install github.com/garyburd/redigo/redis
|
||||||
|
//
|
||||||
|
// Usage:
|
||||||
|
// import(
|
||||||
|
// _ "github.com/astaxie/beego/cache/redis"
|
||||||
|
// "github.com/astaxie/beego/cache"
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// bm, err := cache.NewCache("redis", `{"conn":"127.0.0.1:11211"}`)
|
||||||
|
//
|
||||||
|
// more docs http://beego.me/docs/module/cache.md
|
||||||
|
package redis
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/garyburd/redigo/redis"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego/cache"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// DefaultKey the collection name of redis for cache adapter.
|
||||||
|
DefaultKey = "beecacheRedis"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Cache is Redis cache adapter.
|
||||||
|
type Cache struct {
|
||||||
|
p *redis.Pool // redis connection pool
|
||||||
|
conninfo string
|
||||||
|
dbNum int
|
||||||
|
key string
|
||||||
|
password string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRedisCache create new redis cache with default collection name.
|
||||||
|
func NewRedisCache() cache.Cache {
|
||||||
|
return &Cache{key: DefaultKey}
|
||||||
|
}
|
||||||
|
|
||||||
|
// actually do the redis cmds
|
||||||
|
func (rc *Cache) do(commandName string, args ...interface{}) (reply interface{}, err error) {
|
||||||
|
c := rc.p.Get()
|
||||||
|
defer c.Close()
|
||||||
|
|
||||||
|
return c.Do(commandName, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get cache from redis.
|
||||||
|
func (rc *Cache) Get(key string) interface{} {
|
||||||
|
if v, err := rc.do("GET", key); err == nil {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMulti get cache from redis.
|
||||||
|
func (rc *Cache) GetMulti(keys []string) []interface{} {
|
||||||
|
size := len(keys)
|
||||||
|
var rv []interface{}
|
||||||
|
c := rc.p.Get()
|
||||||
|
defer c.Close()
|
||||||
|
var err error
|
||||||
|
for _, key := range keys {
|
||||||
|
err = c.Send("GET", key)
|
||||||
|
if err != nil {
|
||||||
|
goto ERROR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err = c.Flush(); err != nil {
|
||||||
|
goto ERROR
|
||||||
|
}
|
||||||
|
for i := 0; i < size; i++ {
|
||||||
|
if v, err := c.Receive(); err == nil {
|
||||||
|
rv = append(rv, v.([]byte))
|
||||||
|
} else {
|
||||||
|
rv = append(rv, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rv
|
||||||
|
ERROR:
|
||||||
|
rv = rv[0:0]
|
||||||
|
for i := 0; i < size; i++ {
|
||||||
|
rv = append(rv, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
return rv
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put put cache to redis.
|
||||||
|
func (rc *Cache) Put(key string, val interface{}, timeout time.Duration) error {
|
||||||
|
var err error
|
||||||
|
if _, err = rc.do("SETEX", key, int64(timeout/time.Second), val); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = rc.do("HSET", rc.key, key, true); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete delete cache in redis.
|
||||||
|
func (rc *Cache) Delete(key string) error {
|
||||||
|
var err error
|
||||||
|
if _, err = rc.do("DEL", key); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = rc.do("HDEL", rc.key, key)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsExist check cache's existence in redis.
|
||||||
|
func (rc *Cache) IsExist(key string) bool {
|
||||||
|
v, err := redis.Bool(rc.do("EXISTS", key))
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if v == false {
|
||||||
|
if _, err = rc.do("HDEL", rc.key, key); err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Incr increase counter in redis.
|
||||||
|
func (rc *Cache) Incr(key string) error {
|
||||||
|
_, err := redis.Bool(rc.do("INCRBY", key, 1))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decr decrease counter in redis.
|
||||||
|
func (rc *Cache) Decr(key string) error {
|
||||||
|
_, err := redis.Bool(rc.do("INCRBY", key, -1))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearAll clean all cache in redis. delete this redis collection.
|
||||||
|
func (rc *Cache) ClearAll() error {
|
||||||
|
cachedKeys, err := redis.Strings(rc.do("HKEYS", rc.key))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, str := range cachedKeys {
|
||||||
|
if _, err = rc.do("DEL", str); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, err = rc.do("DEL", rc.key)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartAndGC start redis cache adapter.
|
||||||
|
// config is like {"key":"collection key","conn":"connection info","dbNum":"0"}
|
||||||
|
// the cache item in redis are stored forever,
|
||||||
|
// so no gc operation.
|
||||||
|
func (rc *Cache) StartAndGC(config string) error {
|
||||||
|
var cf map[string]string
|
||||||
|
json.Unmarshal([]byte(config), &cf)
|
||||||
|
|
||||||
|
if _, ok := cf["key"]; !ok {
|
||||||
|
cf["key"] = DefaultKey
|
||||||
|
}
|
||||||
|
if _, ok := cf["conn"]; !ok {
|
||||||
|
return errors.New("config has no conn key")
|
||||||
|
}
|
||||||
|
if _, ok := cf["dbNum"]; !ok {
|
||||||
|
cf["dbNum"] = "0"
|
||||||
|
}
|
||||||
|
if _, ok := cf["password"]; !ok {
|
||||||
|
cf["password"] = ""
|
||||||
|
}
|
||||||
|
rc.key = cf["key"]
|
||||||
|
rc.conninfo = cf["conn"]
|
||||||
|
rc.dbNum, _ = strconv.Atoi(cf["dbNum"])
|
||||||
|
rc.password = cf["password"]
|
||||||
|
|
||||||
|
rc.connectInit()
|
||||||
|
|
||||||
|
c := rc.p.Get()
|
||||||
|
defer c.Close()
|
||||||
|
|
||||||
|
return c.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
// connect to redis.
|
||||||
|
func (rc *Cache) connectInit() {
|
||||||
|
dialFunc := func() (c redis.Conn, err error) {
|
||||||
|
c, err = redis.Dial("tcp", rc.conninfo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if rc.password != "" {
|
||||||
|
if _, err := c.Do("AUTH", rc.password); err != nil {
|
||||||
|
c.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, selecterr := c.Do("SELECT", rc.dbNum)
|
||||||
|
if selecterr != nil {
|
||||||
|
c.Close()
|
||||||
|
return nil, selecterr
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// initialize a new pool
|
||||||
|
rc.p = &redis.Pool{
|
||||||
|
MaxIdle: 3,
|
||||||
|
IdleTimeout: 180 * time.Second,
|
||||||
|
Dial: dialFunc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
cache.Register("redis", NewRedisCache)
|
||||||
|
}
|
|
@ -0,0 +1,106 @@
|
||||||
|
// Copyright 2014 beego Author. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package redis
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego/cache"
|
||||||
|
"github.com/garyburd/redigo/redis"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRedisCache(t *testing.T) {
|
||||||
|
bm, err := cache.NewCache("redis", `{"conn": "127.0.0.1:6379"}`)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("init err")
|
||||||
|
}
|
||||||
|
timeoutDuration := 10 * time.Second
|
||||||
|
if err = bm.Put("astaxie", 1, timeoutDuration); err != nil {
|
||||||
|
t.Error("set Error", err)
|
||||||
|
}
|
||||||
|
if !bm.IsExist("astaxie") {
|
||||||
|
t.Error("check err")
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(11 * time.Second)
|
||||||
|
|
||||||
|
if bm.IsExist("astaxie") {
|
||||||
|
t.Error("check err")
|
||||||
|
}
|
||||||
|
if err = bm.Put("astaxie", 1, timeoutDuration); err != nil {
|
||||||
|
t.Error("set Error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, _ := redis.Int(bm.Get("astaxie"), err); v != 1 {
|
||||||
|
t.Error("get err")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = bm.Incr("astaxie"); err != nil {
|
||||||
|
t.Error("Incr Error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, _ := redis.Int(bm.Get("astaxie"), err); v != 2 {
|
||||||
|
t.Error("get err")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = bm.Decr("astaxie"); err != nil {
|
||||||
|
t.Error("Decr Error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, _ := redis.Int(bm.Get("astaxie"), err); v != 1 {
|
||||||
|
t.Error("get err")
|
||||||
|
}
|
||||||
|
bm.Delete("astaxie")
|
||||||
|
if bm.IsExist("astaxie") {
|
||||||
|
t.Error("delete err")
|
||||||
|
}
|
||||||
|
|
||||||
|
//test string
|
||||||
|
if err = bm.Put("astaxie", "author", timeoutDuration); err != nil {
|
||||||
|
t.Error("set Error", err)
|
||||||
|
}
|
||||||
|
if !bm.IsExist("astaxie") {
|
||||||
|
t.Error("check err")
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, _ := redis.String(bm.Get("astaxie"), err); v != "author" {
|
||||||
|
t.Error("get err")
|
||||||
|
}
|
||||||
|
|
||||||
|
//test GetMulti
|
||||||
|
if err = bm.Put("astaxie1", "author1", timeoutDuration); err != nil {
|
||||||
|
t.Error("set Error", err)
|
||||||
|
}
|
||||||
|
if !bm.IsExist("astaxie1") {
|
||||||
|
t.Error("check err")
|
||||||
|
}
|
||||||
|
|
||||||
|
vv := bm.GetMulti([]string{"astaxie", "astaxie1"})
|
||||||
|
if len(vv) != 2 {
|
||||||
|
t.Error("GetMulti ERROR")
|
||||||
|
}
|
||||||
|
if v, _ := redis.String(vv[0], nil); v != "author" {
|
||||||
|
t.Error("GetMulti ERROR")
|
||||||
|
}
|
||||||
|
if v, _ := redis.String(vv[1], nil); v != "author1" {
|
||||||
|
t.Error("GetMulti ERROR")
|
||||||
|
}
|
||||||
|
|
||||||
|
// test clear all
|
||||||
|
if err = bm.ClearAll(); err != nil {
|
||||||
|
t.Error("clear all err")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,240 @@
|
||||||
|
package ssdb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ssdb/gossdb/ssdb"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego/cache"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Cache SSDB adapter
|
||||||
|
type Cache struct {
|
||||||
|
conn *ssdb.Client
|
||||||
|
conninfo []string
|
||||||
|
}
|
||||||
|
|
||||||
|
//NewSsdbCache create new ssdb adapter.
|
||||||
|
func NewSsdbCache() cache.Cache {
|
||||||
|
return &Cache{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get get value from memcache.
|
||||||
|
func (rc *Cache) Get(key string) interface{} {
|
||||||
|
if rc.conn == nil {
|
||||||
|
if err := rc.connectInit(); err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
value, err := rc.conn.Get(key)
|
||||||
|
if err == nil {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMulti get value from memcache.
|
||||||
|
func (rc *Cache) GetMulti(keys []string) []interface{} {
|
||||||
|
size := len(keys)
|
||||||
|
var values []interface{}
|
||||||
|
if rc.conn == nil {
|
||||||
|
if err := rc.connectInit(); err != nil {
|
||||||
|
for i := 0; i < size; i++ {
|
||||||
|
values = append(values, err)
|
||||||
|
}
|
||||||
|
return values
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res, err := rc.conn.Do("multi_get", keys)
|
||||||
|
resSize := len(res)
|
||||||
|
if err == nil {
|
||||||
|
for i := 1; i < resSize; i += 2 {
|
||||||
|
values = append(values, string(res[i+1]))
|
||||||
|
}
|
||||||
|
return values
|
||||||
|
}
|
||||||
|
for i := 0; i < size; i++ {
|
||||||
|
values = append(values, err)
|
||||||
|
}
|
||||||
|
return values
|
||||||
|
}
|
||||||
|
|
||||||
|
// DelMulti get value from memcache.
|
||||||
|
func (rc *Cache) DelMulti(keys []string) error {
|
||||||
|
if rc.conn == nil {
|
||||||
|
if err := rc.connectInit(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, err := rc.conn.Do("multi_del", keys)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put put value to memcache. only support string.
|
||||||
|
func (rc *Cache) Put(key string, value interface{}, timeout time.Duration) error {
|
||||||
|
if rc.conn == nil {
|
||||||
|
if err := rc.connectInit(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v, ok := value.(string)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("value must string")
|
||||||
|
}
|
||||||
|
var resp []string
|
||||||
|
var err error
|
||||||
|
ttl := int(timeout / time.Second)
|
||||||
|
if ttl < 0 {
|
||||||
|
resp, err = rc.conn.Do("set", key, v)
|
||||||
|
} else {
|
||||||
|
resp, err = rc.conn.Do("setx", key, v, ttl)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(resp) == 2 && resp[0] == "ok" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return errors.New("bad response")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete delete value in memcache.
|
||||||
|
func (rc *Cache) Delete(key string) error {
|
||||||
|
if rc.conn == nil {
|
||||||
|
if err := rc.connectInit(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, err := rc.conn.Del(key)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Incr increase counter.
|
||||||
|
func (rc *Cache) Incr(key string) error {
|
||||||
|
if rc.conn == nil {
|
||||||
|
if err := rc.connectInit(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, err := rc.conn.Do("incr", key, 1)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decr decrease counter.
|
||||||
|
func (rc *Cache) Decr(key string) error {
|
||||||
|
if rc.conn == nil {
|
||||||
|
if err := rc.connectInit(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, err := rc.conn.Do("incr", key, -1)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsExist check value exists in memcache.
|
||||||
|
func (rc *Cache) IsExist(key string) bool {
|
||||||
|
if rc.conn == nil {
|
||||||
|
if err := rc.connectInit(); err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resp, err := rc.conn.Do("exists", key)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if len(resp) == 2 && resp[1] == "1" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearAll clear all cached in memcache.
|
||||||
|
func (rc *Cache) ClearAll() error {
|
||||||
|
if rc.conn == nil {
|
||||||
|
if err := rc.connectInit(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
keyStart, keyEnd, limit := "", "", 50
|
||||||
|
resp, err := rc.Scan(keyStart, keyEnd, limit)
|
||||||
|
for err == nil {
|
||||||
|
size := len(resp)
|
||||||
|
if size == 1 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
keys := []string{}
|
||||||
|
for i := 1; i < size; i += 2 {
|
||||||
|
keys = append(keys, string(resp[i]))
|
||||||
|
}
|
||||||
|
_, e := rc.conn.Do("multi_del", keys)
|
||||||
|
if e != nil {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
keyStart = resp[size-2]
|
||||||
|
resp, err = rc.Scan(keyStart, keyEnd, limit)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan key all cached in ssdb.
|
||||||
|
func (rc *Cache) Scan(keyStart string, keyEnd string, limit int) ([]string, error) {
|
||||||
|
if rc.conn == nil {
|
||||||
|
if err := rc.connectInit(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resp, err := rc.conn.Do("scan", keyStart, keyEnd, limit)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartAndGC start memcache adapter.
|
||||||
|
// config string is like {"conn":"connection info"}.
|
||||||
|
// if connecting error, return.
|
||||||
|
func (rc *Cache) StartAndGC(config string) error {
|
||||||
|
var cf map[string]string
|
||||||
|
json.Unmarshal([]byte(config), &cf)
|
||||||
|
if _, ok := cf["conn"]; !ok {
|
||||||
|
return errors.New("config has no conn key")
|
||||||
|
}
|
||||||
|
rc.conninfo = strings.Split(cf["conn"], ";")
|
||||||
|
if rc.conn == nil {
|
||||||
|
if err := rc.connectInit(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// connect to memcache and keep the connection.
|
||||||
|
func (rc *Cache) connectInit() error {
|
||||||
|
conninfoArray := strings.Split(rc.conninfo[0], ":")
|
||||||
|
host := conninfoArray[0]
|
||||||
|
port, e := strconv.Atoi(conninfoArray[1])
|
||||||
|
if e != nil {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
rc.conn, err = ssdb.Connect(host, port)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
cache.Register("ssdb", NewSsdbCache)
|
||||||
|
}
|
|
@ -0,0 +1,104 @@
|
||||||
|
package ssdb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego/cache"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSsdbcacheCache(t *testing.T) {
|
||||||
|
ssdb, err := cache.NewCache("ssdb", `{"conn": "127.0.0.1:8888"}`)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("init err")
|
||||||
|
}
|
||||||
|
|
||||||
|
// test put and exist
|
||||||
|
if ssdb.IsExist("ssdb") {
|
||||||
|
t.Error("check err")
|
||||||
|
}
|
||||||
|
timeoutDuration := 10 * time.Second
|
||||||
|
//timeoutDuration := -10*time.Second if timeoutDuration is negtive,it means permanent
|
||||||
|
if err = ssdb.Put("ssdb", "ssdb", timeoutDuration); err != nil {
|
||||||
|
t.Error("set Error", err)
|
||||||
|
}
|
||||||
|
if !ssdb.IsExist("ssdb") {
|
||||||
|
t.Error("check err")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get test done
|
||||||
|
if err = ssdb.Put("ssdb", "ssdb", timeoutDuration); err != nil {
|
||||||
|
t.Error("set Error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v := ssdb.Get("ssdb"); v != "ssdb" {
|
||||||
|
t.Error("get Error")
|
||||||
|
}
|
||||||
|
|
||||||
|
//inc/dec test done
|
||||||
|
if err = ssdb.Put("ssdb", "2", timeoutDuration); err != nil {
|
||||||
|
t.Error("set Error", err)
|
||||||
|
}
|
||||||
|
if err = ssdb.Incr("ssdb"); err != nil {
|
||||||
|
t.Error("incr Error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, err := strconv.Atoi(ssdb.Get("ssdb").(string)); err != nil || v != 3 {
|
||||||
|
t.Error("get err")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = ssdb.Decr("ssdb"); err != nil {
|
||||||
|
t.Error("decr error")
|
||||||
|
}
|
||||||
|
|
||||||
|
// test del
|
||||||
|
if err = ssdb.Put("ssdb", "3", timeoutDuration); err != nil {
|
||||||
|
t.Error("set Error", err)
|
||||||
|
}
|
||||||
|
if v, err := strconv.Atoi(ssdb.Get("ssdb").(string)); err != nil || v != 3 {
|
||||||
|
t.Error("get err")
|
||||||
|
}
|
||||||
|
if err := ssdb.Delete("ssdb"); err == nil {
|
||||||
|
if ssdb.IsExist("ssdb") {
|
||||||
|
t.Error("delete err")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//test string
|
||||||
|
if err = ssdb.Put("ssdb", "ssdb", -10*time.Second); err != nil {
|
||||||
|
t.Error("set Error", err)
|
||||||
|
}
|
||||||
|
if !ssdb.IsExist("ssdb") {
|
||||||
|
t.Error("check err")
|
||||||
|
}
|
||||||
|
if v := ssdb.Get("ssdb").(string); v != "ssdb" {
|
||||||
|
t.Error("get err")
|
||||||
|
}
|
||||||
|
|
||||||
|
//test GetMulti done
|
||||||
|
if err = ssdb.Put("ssdb1", "ssdb1", -10*time.Second); err != nil {
|
||||||
|
t.Error("set Error", err)
|
||||||
|
}
|
||||||
|
if !ssdb.IsExist("ssdb1") {
|
||||||
|
t.Error("check err")
|
||||||
|
}
|
||||||
|
vv := ssdb.GetMulti([]string{"ssdb", "ssdb1"})
|
||||||
|
if len(vv) != 2 {
|
||||||
|
t.Error("getmulti error")
|
||||||
|
}
|
||||||
|
if vv[0].(string) != "ssdb" {
|
||||||
|
t.Error("getmulti error")
|
||||||
|
}
|
||||||
|
if vv[1].(string) != "ssdb1" {
|
||||||
|
t.Error("getmulti error")
|
||||||
|
}
|
||||||
|
|
||||||
|
// test clear all done
|
||||||
|
if err = ssdb.ClearAll(); err != nil {
|
||||||
|
t.Error("clear all err")
|
||||||
|
}
|
||||||
|
if ssdb.IsExist("ssdb") || ssdb.IsExist("ssdb1") {
|
||||||
|
t.Error("check err")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,489 @@
|
||||||
|
// Copyright 2014 beego Author. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package beego
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego/config"
|
||||||
|
"github.com/astaxie/beego/context"
|
||||||
|
"github.com/astaxie/beego/logs"
|
||||||
|
"github.com/astaxie/beego/session"
|
||||||
|
"github.com/astaxie/beego/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Config is the main struct for BConfig
|
||||||
|
type Config struct {
|
||||||
|
AppName string //Application name
|
||||||
|
RunMode string //Running Mode: dev | prod
|
||||||
|
RouterCaseSensitive bool
|
||||||
|
ServerName string
|
||||||
|
RecoverPanic bool
|
||||||
|
RecoverFunc func(*context.Context)
|
||||||
|
CopyRequestBody bool
|
||||||
|
EnableGzip bool
|
||||||
|
MaxMemory int64
|
||||||
|
EnableErrorsShow bool
|
||||||
|
EnableErrorsRender bool
|
||||||
|
Listen Listen
|
||||||
|
WebConfig WebConfig
|
||||||
|
Log LogConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen holds for http and https related config
|
||||||
|
type Listen struct {
|
||||||
|
Graceful bool // Graceful means use graceful module to start the server
|
||||||
|
ServerTimeOut int64
|
||||||
|
ListenTCP4 bool
|
||||||
|
EnableHTTP bool
|
||||||
|
HTTPAddr string
|
||||||
|
HTTPPort int
|
||||||
|
EnableHTTPS bool
|
||||||
|
HTTPSAddr string
|
||||||
|
HTTPSPort int
|
||||||
|
HTTPSCertFile string
|
||||||
|
HTTPSKeyFile string
|
||||||
|
EnableAdmin bool
|
||||||
|
AdminAddr string
|
||||||
|
AdminPort int
|
||||||
|
EnableFcgi bool
|
||||||
|
EnableStdIo bool // EnableStdIo works with EnableFcgi Use FCGI via standard I/O
|
||||||
|
}
|
||||||
|
|
||||||
|
// WebConfig holds web related config
|
||||||
|
type WebConfig struct {
|
||||||
|
AutoRender bool
|
||||||
|
EnableDocs bool
|
||||||
|
FlashName string
|
||||||
|
FlashSeparator string
|
||||||
|
DirectoryIndex bool
|
||||||
|
StaticDir map[string]string
|
||||||
|
StaticExtensionsToGzip []string
|
||||||
|
TemplateLeft string
|
||||||
|
TemplateRight string
|
||||||
|
ViewsPath string
|
||||||
|
EnableXSRF bool
|
||||||
|
XSRFKey string
|
||||||
|
XSRFExpire int
|
||||||
|
Session SessionConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// SessionConfig holds session related config
|
||||||
|
type SessionConfig struct {
|
||||||
|
SessionOn bool
|
||||||
|
SessionProvider string
|
||||||
|
SessionName string
|
||||||
|
SessionGCMaxLifetime int64
|
||||||
|
SessionProviderConfig string
|
||||||
|
SessionCookieLifeTime int
|
||||||
|
SessionAutoSetCookie bool
|
||||||
|
SessionDomain string
|
||||||
|
SessionDisableHTTPOnly bool // used to allow for cross domain cookies/javascript cookies.
|
||||||
|
SessionEnableSidInHTTPHeader bool // enable store/get the sessionId into/from http headers
|
||||||
|
SessionNameInHTTPHeader string
|
||||||
|
SessionEnableSidInURLQuery bool // enable get the sessionId from Url Query params
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogConfig holds Log related config
|
||||||
|
type LogConfig struct {
|
||||||
|
AccessLogs bool
|
||||||
|
FileLineNum bool
|
||||||
|
Outputs map[string]string // Store Adaptor : config
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// BConfig is the default config for Application
|
||||||
|
BConfig *Config
|
||||||
|
// AppConfig is the instance of Config, store the config information from file
|
||||||
|
AppConfig *beegoAppConfig
|
||||||
|
// AppPath is the absolute path to the app
|
||||||
|
AppPath string
|
||||||
|
// GlobalSessions is the instance for the session manager
|
||||||
|
GlobalSessions *session.Manager
|
||||||
|
|
||||||
|
// appConfigPath is the path to the config files
|
||||||
|
appConfigPath string
|
||||||
|
// appConfigProvider is the provider for the config, default is ini
|
||||||
|
appConfigProvider = "ini"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
BConfig = newBConfig()
|
||||||
|
var err error
|
||||||
|
if AppPath, err = filepath.Abs(filepath.Dir(os.Args[0])); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
workPath, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
appConfigPath = filepath.Join(workPath, "conf", "app.conf")
|
||||||
|
if !utils.FileExists(appConfigPath) {
|
||||||
|
appConfigPath = filepath.Join(AppPath, "conf", "app.conf")
|
||||||
|
if !utils.FileExists(appConfigPath) {
|
||||||
|
AppConfig = &beegoAppConfig{innerConfig: config.NewFakeConfig()}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err = parseConfig(appConfigPath); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func recoverPanic(ctx *context.Context) {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
if err == ErrAbort {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !BConfig.RecoverPanic {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if BConfig.EnableErrorsShow {
|
||||||
|
if _, ok := ErrorMaps[fmt.Sprint(err)]; ok {
|
||||||
|
exception(fmt.Sprint(err), ctx)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var stack string
|
||||||
|
logs.Critical("the request url is ", ctx.Input.URL())
|
||||||
|
logs.Critical("Handler crashed with error", err)
|
||||||
|
for i := 1; ; i++ {
|
||||||
|
_, file, line, ok := runtime.Caller(i)
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
logs.Critical(fmt.Sprintf("%s:%d", file, line))
|
||||||
|
stack = stack + fmt.Sprintln(fmt.Sprintf("%s:%d", file, line))
|
||||||
|
}
|
||||||
|
if BConfig.RunMode == DEV && BConfig.EnableErrorsRender {
|
||||||
|
showErr(err, ctx, stack)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newBConfig() *Config {
|
||||||
|
return &Config{
|
||||||
|
AppName: "beego",
|
||||||
|
RunMode: DEV,
|
||||||
|
RouterCaseSensitive: true,
|
||||||
|
ServerName: "beegoServer:" + VERSION,
|
||||||
|
RecoverPanic: true,
|
||||||
|
RecoverFunc: recoverPanic,
|
||||||
|
CopyRequestBody: false,
|
||||||
|
EnableGzip: false,
|
||||||
|
MaxMemory: 1 << 26, //64MB
|
||||||
|
EnableErrorsShow: true,
|
||||||
|
EnableErrorsRender: true,
|
||||||
|
Listen: Listen{
|
||||||
|
Graceful: false,
|
||||||
|
ServerTimeOut: 0,
|
||||||
|
ListenTCP4: false,
|
||||||
|
EnableHTTP: true,
|
||||||
|
HTTPAddr: "",
|
||||||
|
HTTPPort: 8080,
|
||||||
|
EnableHTTPS: false,
|
||||||
|
HTTPSAddr: "",
|
||||||
|
HTTPSPort: 10443,
|
||||||
|
HTTPSCertFile: "",
|
||||||
|
HTTPSKeyFile: "",
|
||||||
|
EnableAdmin: false,
|
||||||
|
AdminAddr: "",
|
||||||
|
AdminPort: 8088,
|
||||||
|
EnableFcgi: false,
|
||||||
|
EnableStdIo: false,
|
||||||
|
},
|
||||||
|
WebConfig: WebConfig{
|
||||||
|
AutoRender: true,
|
||||||
|
EnableDocs: false,
|
||||||
|
FlashName: "BEEGO_FLASH",
|
||||||
|
FlashSeparator: "BEEGOFLASH",
|
||||||
|
DirectoryIndex: false,
|
||||||
|
StaticDir: map[string]string{"/static": "static"},
|
||||||
|
StaticExtensionsToGzip: []string{".css", ".js"},
|
||||||
|
TemplateLeft: "{{",
|
||||||
|
TemplateRight: "}}",
|
||||||
|
ViewsPath: "views",
|
||||||
|
EnableXSRF: false,
|
||||||
|
XSRFKey: "beegoxsrf",
|
||||||
|
XSRFExpire: 0,
|
||||||
|
Session: SessionConfig{
|
||||||
|
SessionOn: false,
|
||||||
|
SessionProvider: "memory",
|
||||||
|
SessionName: "beegosessionID",
|
||||||
|
SessionGCMaxLifetime: 3600,
|
||||||
|
SessionProviderConfig: "",
|
||||||
|
SessionDisableHTTPOnly: false,
|
||||||
|
SessionCookieLifeTime: 0, //set cookie default is the browser life
|
||||||
|
SessionAutoSetCookie: true,
|
||||||
|
SessionDomain: "",
|
||||||
|
SessionEnableSidInHTTPHeader: false, // enable store/get the sessionId into/from http headers
|
||||||
|
SessionNameInHTTPHeader: "Beegosessionid",
|
||||||
|
SessionEnableSidInURLQuery: false, // enable get the sessionId from Url Query params
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Log: LogConfig{
|
||||||
|
AccessLogs: false,
|
||||||
|
FileLineNum: true,
|
||||||
|
Outputs: map[string]string{"console": ""},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// now only support ini, next will support json.
|
||||||
|
func parseConfig(appConfigPath string) (err error) {
|
||||||
|
AppConfig, err = newAppConfig(appConfigProvider, appConfigPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return assignConfig(AppConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
func assignConfig(ac config.Configer) error {
|
||||||
|
for _, i := range []interface{}{BConfig, &BConfig.Listen, &BConfig.WebConfig, &BConfig.Log, &BConfig.WebConfig.Session} {
|
||||||
|
assignSingleConfig(i, ac)
|
||||||
|
}
|
||||||
|
// set the run mode first
|
||||||
|
if envRunMode := os.Getenv("BEEGO_RUNMODE"); envRunMode != "" {
|
||||||
|
BConfig.RunMode = envRunMode
|
||||||
|
} else if runMode := ac.String("RunMode"); runMode != "" {
|
||||||
|
BConfig.RunMode = runMode
|
||||||
|
}
|
||||||
|
|
||||||
|
if sd := ac.String("StaticDir"); sd != "" {
|
||||||
|
BConfig.WebConfig.StaticDir = map[string]string{}
|
||||||
|
sds := strings.Fields(sd)
|
||||||
|
for _, v := range sds {
|
||||||
|
if url2fsmap := strings.SplitN(v, ":", 2); len(url2fsmap) == 2 {
|
||||||
|
BConfig.WebConfig.StaticDir["/"+strings.Trim(url2fsmap[0], "/")] = url2fsmap[1]
|
||||||
|
} else {
|
||||||
|
BConfig.WebConfig.StaticDir["/"+strings.Trim(url2fsmap[0], "/")] = url2fsmap[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if sgz := ac.String("StaticExtensionsToGzip"); sgz != "" {
|
||||||
|
extensions := strings.Split(sgz, ",")
|
||||||
|
fileExts := []string{}
|
||||||
|
for _, ext := range extensions {
|
||||||
|
ext = strings.TrimSpace(ext)
|
||||||
|
if ext == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !strings.HasPrefix(ext, ".") {
|
||||||
|
ext = "." + ext
|
||||||
|
}
|
||||||
|
fileExts = append(fileExts, ext)
|
||||||
|
}
|
||||||
|
if len(fileExts) > 0 {
|
||||||
|
BConfig.WebConfig.StaticExtensionsToGzip = fileExts
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if lo := ac.String("LogOutputs"); lo != "" {
|
||||||
|
// if lo is not nil or empty
|
||||||
|
// means user has set his own LogOutputs
|
||||||
|
// clear the default setting to BConfig.Log.Outputs
|
||||||
|
BConfig.Log.Outputs = make(map[string]string)
|
||||||
|
los := strings.Split(lo, ";")
|
||||||
|
for _, v := range los {
|
||||||
|
if logType2Config := strings.SplitN(v, ",", 2); len(logType2Config) == 2 {
|
||||||
|
BConfig.Log.Outputs[logType2Config[0]] = logType2Config[1]
|
||||||
|
} else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//init log
|
||||||
|
logs.Reset()
|
||||||
|
for adaptor, config := range BConfig.Log.Outputs {
|
||||||
|
err := logs.SetLogger(adaptor, config)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, fmt.Sprintf("%s with the config %q got err:%s", adaptor, config, err.Error()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logs.SetLogFuncCall(BConfig.Log.FileLineNum)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func assignSingleConfig(p interface{}, ac config.Configer) {
|
||||||
|
pt := reflect.TypeOf(p)
|
||||||
|
if pt.Kind() != reflect.Ptr {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pt = pt.Elem()
|
||||||
|
if pt.Kind() != reflect.Struct {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pv := reflect.ValueOf(p).Elem()
|
||||||
|
|
||||||
|
for i := 0; i < pt.NumField(); i++ {
|
||||||
|
pf := pv.Field(i)
|
||||||
|
if !pf.CanSet() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
name := pt.Field(i).Name
|
||||||
|
switch pf.Kind() {
|
||||||
|
case reflect.String:
|
||||||
|
pf.SetString(ac.DefaultString(name, pf.String()))
|
||||||
|
case reflect.Int, reflect.Int64:
|
||||||
|
pf.SetInt(int64(ac.DefaultInt64(name, pf.Int())))
|
||||||
|
case reflect.Bool:
|
||||||
|
pf.SetBool(ac.DefaultBool(name, pf.Bool()))
|
||||||
|
case reflect.Struct:
|
||||||
|
default:
|
||||||
|
//do nothing here
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadAppConfig allow developer to apply a config file
|
||||||
|
func LoadAppConfig(adapterName, configPath string) error {
|
||||||
|
absConfigPath, err := filepath.Abs(configPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !utils.FileExists(absConfigPath) {
|
||||||
|
return fmt.Errorf("the target config file: %s don't exist", configPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
appConfigPath = absConfigPath
|
||||||
|
appConfigProvider = adapterName
|
||||||
|
|
||||||
|
return parseConfig(appConfigPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
type beegoAppConfig struct {
|
||||||
|
innerConfig config.Configer
|
||||||
|
}
|
||||||
|
|
||||||
|
func newAppConfig(appConfigProvider, appConfigPath string) (*beegoAppConfig, error) {
|
||||||
|
ac, err := config.NewConfig(appConfigProvider, appConfigPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &beegoAppConfig{ac}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *beegoAppConfig) Set(key, val string) error {
|
||||||
|
if err := b.innerConfig.Set(BConfig.RunMode+"::"+key, val); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return b.innerConfig.Set(key, val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *beegoAppConfig) String(key string) string {
|
||||||
|
if v := b.innerConfig.String(BConfig.RunMode + "::" + key); v != "" {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return b.innerConfig.String(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *beegoAppConfig) Strings(key string) []string {
|
||||||
|
if v := b.innerConfig.Strings(BConfig.RunMode + "::" + key); len(v) > 0 {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return b.innerConfig.Strings(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *beegoAppConfig) Int(key string) (int, error) {
|
||||||
|
if v, err := b.innerConfig.Int(BConfig.RunMode + "::" + key); err == nil {
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
return b.innerConfig.Int(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *beegoAppConfig) Int64(key string) (int64, error) {
|
||||||
|
if v, err := b.innerConfig.Int64(BConfig.RunMode + "::" + key); err == nil {
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
return b.innerConfig.Int64(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *beegoAppConfig) Bool(key string) (bool, error) {
|
||||||
|
if v, err := b.innerConfig.Bool(BConfig.RunMode + "::" + key); err == nil {
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
return b.innerConfig.Bool(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *beegoAppConfig) Float(key string) (float64, error) {
|
||||||
|
if v, err := b.innerConfig.Float(BConfig.RunMode + "::" + key); err == nil {
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
return b.innerConfig.Float(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *beegoAppConfig) DefaultString(key string, defaultVal string) string {
|
||||||
|
if v := b.String(key); v != "" {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return defaultVal
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *beegoAppConfig) DefaultStrings(key string, defaultVal []string) []string {
|
||||||
|
if v := b.Strings(key); len(v) != 0 {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return defaultVal
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *beegoAppConfig) DefaultInt(key string, defaultVal int) int {
|
||||||
|
if v, err := b.Int(key); err == nil {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return defaultVal
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *beegoAppConfig) DefaultInt64(key string, defaultVal int64) int64 {
|
||||||
|
if v, err := b.Int64(key); err == nil {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return defaultVal
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *beegoAppConfig) DefaultBool(key string, defaultVal bool) bool {
|
||||||
|
if v, err := b.Bool(key); err == nil {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return defaultVal
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *beegoAppConfig) DefaultFloat(key string, defaultVal float64) float64 {
|
||||||
|
if v, err := b.Float(key); err == nil {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return defaultVal
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *beegoAppConfig) DIY(key string) (interface{}, error) {
|
||||||
|
return b.innerConfig.DIY(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *beegoAppConfig) GetSection(section string) (map[string]string, error) {
|
||||||
|
return b.innerConfig.GetSection(section)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *beegoAppConfig) SaveConfigFile(filename string) error {
|
||||||
|
return b.innerConfig.SaveConfigFile(filename)
|
||||||
|
}
|
|
@ -0,0 +1,242 @@
|
||||||
|
// Copyright 2014 beego Author. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// Package config is used to parse config.
|
||||||
|
// Usage:
|
||||||
|
// import "github.com/astaxie/beego/config"
|
||||||
|
//Examples.
|
||||||
|
//
|
||||||
|
// cnf, err := config.NewConfig("ini", "config.conf")
|
||||||
|
//
|
||||||
|
// cnf APIS:
|
||||||
|
//
|
||||||
|
// cnf.Set(key, val string) error
|
||||||
|
// cnf.String(key string) string
|
||||||
|
// cnf.Strings(key string) []string
|
||||||
|
// cnf.Int(key string) (int, error)
|
||||||
|
// cnf.Int64(key string) (int64, error)
|
||||||
|
// cnf.Bool(key string) (bool, error)
|
||||||
|
// cnf.Float(key string) (float64, error)
|
||||||
|
// cnf.DefaultString(key string, defaultVal string) string
|
||||||
|
// cnf.DefaultStrings(key string, defaultVal []string) []string
|
||||||
|
// cnf.DefaultInt(key string, defaultVal int) int
|
||||||
|
// cnf.DefaultInt64(key string, defaultVal int64) int64
|
||||||
|
// cnf.DefaultBool(key string, defaultVal bool) bool
|
||||||
|
// cnf.DefaultFloat(key string, defaultVal float64) float64
|
||||||
|
// cnf.DIY(key string) (interface{}, error)
|
||||||
|
// cnf.GetSection(section string) (map[string]string, error)
|
||||||
|
// cnf.SaveConfigFile(filename string) error
|
||||||
|
//More docs http://beego.me/docs/module/config.md
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Configer defines how to get and set value from configuration raw data.
|
||||||
|
type Configer interface {
|
||||||
|
Set(key, val string) error //support section::key type in given key when using ini type.
|
||||||
|
String(key string) string //support section::key type in key string when using ini and json type; Int,Int64,Bool,Float,DIY are same.
|
||||||
|
Strings(key string) []string //get string slice
|
||||||
|
Int(key string) (int, error)
|
||||||
|
Int64(key string) (int64, error)
|
||||||
|
Bool(key string) (bool, error)
|
||||||
|
Float(key string) (float64, error)
|
||||||
|
DefaultString(key string, defaultVal string) string // support section::key type in key string when using ini and json type; Int,Int64,Bool,Float,DIY are same.
|
||||||
|
DefaultStrings(key string, defaultVal []string) []string //get string slice
|
||||||
|
DefaultInt(key string, defaultVal int) int
|
||||||
|
DefaultInt64(key string, defaultVal int64) int64
|
||||||
|
DefaultBool(key string, defaultVal bool) bool
|
||||||
|
DefaultFloat(key string, defaultVal float64) float64
|
||||||
|
DIY(key string) (interface{}, error)
|
||||||
|
GetSection(section string) (map[string]string, error)
|
||||||
|
SaveConfigFile(filename string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config is the adapter interface for parsing config file to get raw data to Configer.
|
||||||
|
type Config interface {
|
||||||
|
Parse(key string) (Configer, error)
|
||||||
|
ParseData(data []byte) (Configer, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
var adapters = make(map[string]Config)
|
||||||
|
|
||||||
|
// Register makes a config adapter available by the adapter name.
|
||||||
|
// If Register is called twice with the same name or if driver is nil,
|
||||||
|
// it panics.
|
||||||
|
func Register(name string, adapter Config) {
|
||||||
|
if adapter == nil {
|
||||||
|
panic("config: Register adapter is nil")
|
||||||
|
}
|
||||||
|
if _, ok := adapters[name]; ok {
|
||||||
|
panic("config: Register called twice for adapter " + name)
|
||||||
|
}
|
||||||
|
adapters[name] = adapter
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewConfig adapterName is ini/json/xml/yaml.
|
||||||
|
// filename is the config file path.
|
||||||
|
func NewConfig(adapterName, filename string) (Configer, error) {
|
||||||
|
adapter, ok := adapters[adapterName]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("config: unknown adaptername %q (forgotten import?)", adapterName)
|
||||||
|
}
|
||||||
|
return adapter.Parse(filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewConfigData adapterName is ini/json/xml/yaml.
|
||||||
|
// data is the config data.
|
||||||
|
func NewConfigData(adapterName string, data []byte) (Configer, error) {
|
||||||
|
adapter, ok := adapters[adapterName]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("config: unknown adaptername %q (forgotten import?)", adapterName)
|
||||||
|
}
|
||||||
|
return adapter.ParseData(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExpandValueEnvForMap convert all string value with environment variable.
|
||||||
|
func ExpandValueEnvForMap(m map[string]interface{}) map[string]interface{} {
|
||||||
|
for k, v := range m {
|
||||||
|
switch value := v.(type) {
|
||||||
|
case string:
|
||||||
|
m[k] = ExpandValueEnv(value)
|
||||||
|
case map[string]interface{}:
|
||||||
|
m[k] = ExpandValueEnvForMap(value)
|
||||||
|
case map[string]string:
|
||||||
|
for k2, v2 := range value {
|
||||||
|
value[k2] = ExpandValueEnv(v2)
|
||||||
|
}
|
||||||
|
m[k] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExpandValueEnv returns value of convert with environment variable.
|
||||||
|
//
|
||||||
|
// Return environment variable if value start with "${" and end with "}".
|
||||||
|
// Return default value if environment variable is empty or not exist.
|
||||||
|
//
|
||||||
|
// It accept value formats "${env}" , "${env||}}" , "${env||defaultValue}" , "defaultvalue".
|
||||||
|
// Examples:
|
||||||
|
// v1 := config.ExpandValueEnv("${GOPATH}") // return the GOPATH environment variable.
|
||||||
|
// v2 := config.ExpandValueEnv("${GOAsta||/usr/local/go}") // return the default value "/usr/local/go/".
|
||||||
|
// v3 := config.ExpandValueEnv("Astaxie") // return the value "Astaxie".
|
||||||
|
func ExpandValueEnv(value string) (realValue string) {
|
||||||
|
realValue = value
|
||||||
|
|
||||||
|
vLen := len(value)
|
||||||
|
// 3 = ${}
|
||||||
|
if vLen < 3 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Need start with "${" and end with "}", then return.
|
||||||
|
if value[0] != '$' || value[1] != '{' || value[vLen-1] != '}' {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
key := ""
|
||||||
|
defalutV := ""
|
||||||
|
// value start with "${"
|
||||||
|
for i := 2; i < vLen; i++ {
|
||||||
|
if value[i] == '|' && (i+1 < vLen && value[i+1] == '|') {
|
||||||
|
key = value[2:i]
|
||||||
|
defalutV = value[i+2 : vLen-1] // other string is default value.
|
||||||
|
break
|
||||||
|
} else if value[i] == '}' {
|
||||||
|
key = value[2:i]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
realValue = os.Getenv(key)
|
||||||
|
if realValue == "" {
|
||||||
|
realValue = defalutV
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseBool returns the boolean value represented by the string.
|
||||||
|
//
|
||||||
|
// It accepts 1, 1.0, t, T, TRUE, true, True, YES, yes, Yes,Y, y, ON, on, On,
|
||||||
|
// 0, 0.0, f, F, FALSE, false, False, NO, no, No, N,n, OFF, off, Off.
|
||||||
|
// Any other value returns an error.
|
||||||
|
func ParseBool(val interface{}) (value bool, err error) {
|
||||||
|
if val != nil {
|
||||||
|
switch v := val.(type) {
|
||||||
|
case bool:
|
||||||
|
return v, nil
|
||||||
|
case string:
|
||||||
|
switch v {
|
||||||
|
case "1", "t", "T", "true", "TRUE", "True", "YES", "yes", "Yes", "Y", "y", "ON", "on", "On":
|
||||||
|
return true, nil
|
||||||
|
case "0", "f", "F", "false", "FALSE", "False", "NO", "no", "No", "N", "n", "OFF", "off", "Off":
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
case int8, int32, int64:
|
||||||
|
strV := fmt.Sprintf("%s", v)
|
||||||
|
if strV == "1" {
|
||||||
|
return true, nil
|
||||||
|
} else if strV == "0" {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
case float64:
|
||||||
|
if v == 1 {
|
||||||
|
return true, nil
|
||||||
|
} else if v == 0 {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, fmt.Errorf("parsing %q: invalid syntax", val)
|
||||||
|
}
|
||||||
|
return false, fmt.Errorf("parsing <nil>: invalid syntax")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToString converts values of any type to string.
|
||||||
|
func ToString(x interface{}) string {
|
||||||
|
switch y := x.(type) {
|
||||||
|
|
||||||
|
// Handle dates with special logic
|
||||||
|
// This needs to come above the fmt.Stringer
|
||||||
|
// test since time.Time's have a .String()
|
||||||
|
// method
|
||||||
|
case time.Time:
|
||||||
|
return y.Format("A Monday")
|
||||||
|
|
||||||
|
// Handle type string
|
||||||
|
case string:
|
||||||
|
return y
|
||||||
|
|
||||||
|
// Handle type with .String() method
|
||||||
|
case fmt.Stringer:
|
||||||
|
return y.String()
|
||||||
|
|
||||||
|
// Handle type with .Error() method
|
||||||
|
case error:
|
||||||
|
return y.Error()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle named string type
|
||||||
|
if v := reflect.ValueOf(x); v.Kind() == reflect.String {
|
||||||
|
return v.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to fmt package for anything else like numeric types
|
||||||
|
return fmt.Sprint(x)
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
// Copyright 2016 beego Author. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestExpandValueEnv(t *testing.T) {
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
item string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"", ""},
|
||||||
|
{"$", "$"},
|
||||||
|
{"{", "{"},
|
||||||
|
{"{}", "{}"},
|
||||||
|
{"${}", ""},
|
||||||
|
{"${|}", ""},
|
||||||
|
{"${}", ""},
|
||||||
|
{"${{}}", ""},
|
||||||
|
{"${{||}}", "}"},
|
||||||
|
{"${pwd||}", ""},
|
||||||
|
{"${pwd||}", ""},
|
||||||
|
{"${pwd||}", ""},
|
||||||
|
{"${pwd||}}", "}"},
|
||||||
|
{"${pwd||{{||}}}", "{{||}}"},
|
||||||
|
{"${GOPATH}", os.Getenv("GOPATH")},
|
||||||
|
{"${GOPATH||}", os.Getenv("GOPATH")},
|
||||||
|
{"${GOPATH||root}", os.Getenv("GOPATH")},
|
||||||
|
{"${GOPATH_NOT||root}", "root"},
|
||||||
|
{"${GOPATH_NOT||||root}", "||root"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range testCases {
|
||||||
|
if got := ExpandValueEnv(c.item); got != c.want {
|
||||||
|
t.Errorf("expand value error, item %q want %q, got %q", c.item, c.want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,85 @@
|
||||||
|
// Copyright 2014 beego Author. All Rights Reserved.
|
||||||
|
// Copyright 2017 Faissal Elamraoui. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
package env
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
var env *utils.BeeMap
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
env = utils.NewBeeMap()
|
||||||
|
for _, e := range os.Environ() {
|
||||||
|
splits := strings.Split(e, "=")
|
||||||
|
env.Set(splits[0], os.Getenv(splits[0]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns a value by key.
|
||||||
|
// If the key does not exist, the default value will be returned.
|
||||||
|
func Get(key string, defVal string) string {
|
||||||
|
if val := env.Get(key); val != nil {
|
||||||
|
return val.(string)
|
||||||
|
}
|
||||||
|
return defVal
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustGet returns a value by key.
|
||||||
|
// If the key does not exist, it will return an error.
|
||||||
|
func MustGet(key string) (string, error) {
|
||||||
|
if val := env.Get(key); val != nil {
|
||||||
|
return val.(string), nil
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("no env variable with %s", key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set sets a value in the ENV copy.
|
||||||
|
// This does not affect the child process environment.
|
||||||
|
func Set(key string, value string) {
|
||||||
|
env.Set(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustSet sets a value in the ENV copy and the child process environment.
|
||||||
|
// It returns an error in case the set operation failed.
|
||||||
|
func MustSet(key string, value string) error {
|
||||||
|
err := os.Setenv(key, value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
env.Set(key, value)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAll returns all keys/values in the current child process environment.
|
||||||
|
func GetAll() map[string]string {
|
||||||
|
items := env.Items()
|
||||||
|
envs := make(map[string]string, env.Count())
|
||||||
|
|
||||||
|
for key, val := range items {
|
||||||
|
switch key := key.(type) {
|
||||||
|
case string:
|
||||||
|
switch val := val.(type) {
|
||||||
|
case string:
|
||||||
|
envs[key] = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return envs
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
// Copyright 2014 beego Author. All Rights Reserved.
|
||||||
|
// Copyright 2017 Faissal Elamraoui. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
package env
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEnvGet(t *testing.T) {
|
||||||
|
gopath := Get("GOPATH", "")
|
||||||
|
if gopath != os.Getenv("GOPATH") {
|
||||||
|
t.Error("expected GOPATH not empty.")
|
||||||
|
}
|
||||||
|
|
||||||
|
noExistVar := Get("NOEXISTVAR", "foo")
|
||||||
|
if noExistVar != "foo" {
|
||||||
|
t.Errorf("expected NOEXISTVAR to equal foo, got %s.", noExistVar)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEnvMustGet(t *testing.T) {
|
||||||
|
gopath, err := MustGet("GOPATH")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if gopath != os.Getenv("GOPATH") {
|
||||||
|
t.Errorf("expected GOPATH to be the same, got %s.", gopath)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = MustGet("NOEXISTVAR")
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected error to be non-nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEnvSet(t *testing.T) {
|
||||||
|
Set("MYVAR", "foo")
|
||||||
|
myVar := Get("MYVAR", "bar")
|
||||||
|
if myVar != "foo" {
|
||||||
|
t.Errorf("expected MYVAR to equal foo, got %s.", myVar)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEnvMustSet(t *testing.T) {
|
||||||
|
err := MustSet("FOO", "bar")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fooVar := os.Getenv("FOO")
|
||||||
|
if fooVar != "bar" {
|
||||||
|
t.Errorf("expected FOO variable to equal bar, got %s.", fooVar)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEnvGetAll(t *testing.T) {
|
||||||
|
envMap := GetAll()
|
||||||
|
if len(envMap) == 0 {
|
||||||
|
t.Error("expected environment not empty.")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,134 @@
|
||||||
|
// Copyright 2014 beego Author. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type fakeConfigContainer struct {
|
||||||
|
data map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *fakeConfigContainer) getData(key string) string {
|
||||||
|
return c.data[strings.ToLower(key)]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *fakeConfigContainer) Set(key, val string) error {
|
||||||
|
c.data[strings.ToLower(key)] = val
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *fakeConfigContainer) String(key string) string {
|
||||||
|
return c.getData(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *fakeConfigContainer) DefaultString(key string, defaultval string) string {
|
||||||
|
v := c.String(key)
|
||||||
|
if v == "" {
|
||||||
|
return defaultval
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *fakeConfigContainer) Strings(key string) []string {
|
||||||
|
v := c.String(key)
|
||||||
|
if v == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return strings.Split(v, ";")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *fakeConfigContainer) DefaultStrings(key string, defaultval []string) []string {
|
||||||
|
v := c.Strings(key)
|
||||||
|
if v == nil {
|
||||||
|
return defaultval
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *fakeConfigContainer) Int(key string) (int, error) {
|
||||||
|
return strconv.Atoi(c.getData(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *fakeConfigContainer) DefaultInt(key string, defaultval int) int {
|
||||||
|
v, err := c.Int(key)
|
||||||
|
if err != nil {
|
||||||
|
return defaultval
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *fakeConfigContainer) Int64(key string) (int64, error) {
|
||||||
|
return strconv.ParseInt(c.getData(key), 10, 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *fakeConfigContainer) DefaultInt64(key string, defaultval int64) int64 {
|
||||||
|
v, err := c.Int64(key)
|
||||||
|
if err != nil {
|
||||||
|
return defaultval
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *fakeConfigContainer) Bool(key string) (bool, error) {
|
||||||
|
return ParseBool(c.getData(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *fakeConfigContainer) DefaultBool(key string, defaultval bool) bool {
|
||||||
|
v, err := c.Bool(key)
|
||||||
|
if err != nil {
|
||||||
|
return defaultval
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *fakeConfigContainer) Float(key string) (float64, error) {
|
||||||
|
return strconv.ParseFloat(c.getData(key), 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *fakeConfigContainer) DefaultFloat(key string, defaultval float64) float64 {
|
||||||
|
v, err := c.Float(key)
|
||||||
|
if err != nil {
|
||||||
|
return defaultval
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *fakeConfigContainer) DIY(key string) (interface{}, error) {
|
||||||
|
if v, ok := c.data[strings.ToLower(key)]; ok {
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
return nil, errors.New("key not find")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *fakeConfigContainer) GetSection(section string) (map[string]string, error) {
|
||||||
|
return nil, errors.New("not implement in the fakeConfigContainer")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *fakeConfigContainer) SaveConfigFile(filename string) error {
|
||||||
|
return errors.New("not implement in the fakeConfigContainer")
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Configer = new(fakeConfigContainer)
|
||||||
|
|
||||||
|
// NewFakeConfig return a fake Congiger
|
||||||
|
func NewFakeConfig() Configer {
|
||||||
|
return &fakeConfigContainer{
|
||||||
|
data: make(map[string]string),
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,474 @@
|
||||||
|
// Copyright 2014 beego Author. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
defaultSection = "default" // default section means if some ini items not in a section, make them in default section,
|
||||||
|
bNumComment = []byte{'#'} // number signal
|
||||||
|
bSemComment = []byte{';'} // semicolon signal
|
||||||
|
bEmpty = []byte{}
|
||||||
|
bEqual = []byte{'='} // equal signal
|
||||||
|
bDQuote = []byte{'"'} // quote signal
|
||||||
|
sectionStart = []byte{'['} // section start signal
|
||||||
|
sectionEnd = []byte{']'} // section end signal
|
||||||
|
lineBreak = "\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IniConfig implements Config to parse ini file.
|
||||||
|
type IniConfig struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse creates a new Config and parses the file configuration from the named file.
|
||||||
|
func (ini *IniConfig) Parse(name string) (Configer, error) {
|
||||||
|
return ini.parseFile(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ini *IniConfig) parseFile(name string) (*IniConfigContainer, error) {
|
||||||
|
data, err := ioutil.ReadFile(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ini.parseData(filepath.Dir(name), data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ini *IniConfig) parseData(dir string, data []byte) (*IniConfigContainer, error) {
|
||||||
|
cfg := &IniConfigContainer{
|
||||||
|
data: make(map[string]map[string]string),
|
||||||
|
sectionComment: make(map[string]string),
|
||||||
|
keyComment: make(map[string]string),
|
||||||
|
RWMutex: sync.RWMutex{},
|
||||||
|
}
|
||||||
|
cfg.Lock()
|
||||||
|
defer cfg.Unlock()
|
||||||
|
|
||||||
|
var comment bytes.Buffer
|
||||||
|
buf := bufio.NewReader(bytes.NewBuffer(data))
|
||||||
|
// check the BOM
|
||||||
|
head, err := buf.Peek(3)
|
||||||
|
if err == nil && head[0] == 239 && head[1] == 187 && head[2] == 191 {
|
||||||
|
for i := 1; i <= 3; i++ {
|
||||||
|
buf.ReadByte()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
section := defaultSection
|
||||||
|
for {
|
||||||
|
line, _, err := buf.ReadLine()
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
//It might be a good idea to throw a error on all unknonw errors?
|
||||||
|
if _, ok := err.(*os.PathError); ok {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
line = bytes.TrimSpace(line)
|
||||||
|
if bytes.Equal(line, bEmpty) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var bComment []byte
|
||||||
|
switch {
|
||||||
|
case bytes.HasPrefix(line, bNumComment):
|
||||||
|
bComment = bNumComment
|
||||||
|
case bytes.HasPrefix(line, bSemComment):
|
||||||
|
bComment = bSemComment
|
||||||
|
}
|
||||||
|
if bComment != nil {
|
||||||
|
line = bytes.TrimLeft(line, string(bComment))
|
||||||
|
// Need append to a new line if multi-line comments.
|
||||||
|
if comment.Len() > 0 {
|
||||||
|
comment.WriteByte('\n')
|
||||||
|
}
|
||||||
|
comment.Write(line)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if bytes.HasPrefix(line, sectionStart) && bytes.HasSuffix(line, sectionEnd) {
|
||||||
|
section = strings.ToLower(string(line[1 : len(line)-1])) // section name case insensitive
|
||||||
|
if comment.Len() > 0 {
|
||||||
|
cfg.sectionComment[section] = comment.String()
|
||||||
|
comment.Reset()
|
||||||
|
}
|
||||||
|
if _, ok := cfg.data[section]; !ok {
|
||||||
|
cfg.data[section] = make(map[string]string)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := cfg.data[section]; !ok {
|
||||||
|
cfg.data[section] = make(map[string]string)
|
||||||
|
}
|
||||||
|
keyValue := bytes.SplitN(line, bEqual, 2)
|
||||||
|
|
||||||
|
key := string(bytes.TrimSpace(keyValue[0])) // key name case insensitive
|
||||||
|
key = strings.ToLower(key)
|
||||||
|
|
||||||
|
// handle include "other.conf"
|
||||||
|
if len(keyValue) == 1 && strings.HasPrefix(key, "include") {
|
||||||
|
|
||||||
|
includefiles := strings.Fields(key)
|
||||||
|
if includefiles[0] == "include" && len(includefiles) == 2 {
|
||||||
|
|
||||||
|
otherfile := strings.Trim(includefiles[1], "\"")
|
||||||
|
if !filepath.IsAbs(otherfile) {
|
||||||
|
otherfile = filepath.Join(dir, otherfile)
|
||||||
|
}
|
||||||
|
|
||||||
|
i, err := ini.parseFile(otherfile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for sec, dt := range i.data {
|
||||||
|
if _, ok := cfg.data[sec]; !ok {
|
||||||
|
cfg.data[sec] = make(map[string]string)
|
||||||
|
}
|
||||||
|
for k, v := range dt {
|
||||||
|
cfg.data[sec][k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for sec, comm := range i.sectionComment {
|
||||||
|
cfg.sectionComment[sec] = comm
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, comm := range i.keyComment {
|
||||||
|
cfg.keyComment[k] = comm
|
||||||
|
}
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(keyValue) != 2 {
|
||||||
|
return nil, errors.New("read the content error: \"" + string(line) + "\", should key = val")
|
||||||
|
}
|
||||||
|
val := bytes.TrimSpace(keyValue[1])
|
||||||
|
if bytes.HasPrefix(val, bDQuote) {
|
||||||
|
val = bytes.Trim(val, `"`)
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg.data[section][key] = ExpandValueEnv(string(val))
|
||||||
|
if comment.Len() > 0 {
|
||||||
|
cfg.keyComment[section+"."+key] = comment.String()
|
||||||
|
comment.Reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return cfg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseData parse ini the data
|
||||||
|
// When include other.conf,other.conf is either absolute directory
|
||||||
|
// or under beego in default temporary directory(/tmp/beego).
|
||||||
|
func (ini *IniConfig) ParseData(data []byte) (Configer, error) {
|
||||||
|
dir := filepath.Join(os.TempDir(), "beego")
|
||||||
|
os.MkdirAll(dir, os.ModePerm)
|
||||||
|
|
||||||
|
return ini.parseData(dir, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IniConfigContainer A Config represents the ini configuration.
|
||||||
|
// When set and get value, support key as section:name type.
|
||||||
|
type IniConfigContainer struct {
|
||||||
|
data map[string]map[string]string // section=> key:val
|
||||||
|
sectionComment map[string]string // section : comment
|
||||||
|
keyComment map[string]string // id: []{comment, key...}; id 1 is for main comment.
|
||||||
|
sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bool returns the boolean value for a given key.
|
||||||
|
func (c *IniConfigContainer) Bool(key string) (bool, error) {
|
||||||
|
return ParseBool(c.getdata(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultBool returns the boolean value for a given key.
|
||||||
|
// if err != nil return defaltval
|
||||||
|
func (c *IniConfigContainer) DefaultBool(key string, defaultval bool) bool {
|
||||||
|
v, err := c.Bool(key)
|
||||||
|
if err != nil {
|
||||||
|
return defaultval
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int returns the integer value for a given key.
|
||||||
|
func (c *IniConfigContainer) Int(key string) (int, error) {
|
||||||
|
return strconv.Atoi(c.getdata(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultInt returns the integer value for a given key.
|
||||||
|
// if err != nil return defaltval
|
||||||
|
func (c *IniConfigContainer) DefaultInt(key string, defaultval int) int {
|
||||||
|
v, err := c.Int(key)
|
||||||
|
if err != nil {
|
||||||
|
return defaultval
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64 returns the int64 value for a given key.
|
||||||
|
func (c *IniConfigContainer) Int64(key string) (int64, error) {
|
||||||
|
return strconv.ParseInt(c.getdata(key), 10, 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultInt64 returns the int64 value for a given key.
|
||||||
|
// if err != nil return defaltval
|
||||||
|
func (c *IniConfigContainer) DefaultInt64(key string, defaultval int64) int64 {
|
||||||
|
v, err := c.Int64(key)
|
||||||
|
if err != nil {
|
||||||
|
return defaultval
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float returns the float value for a given key.
|
||||||
|
func (c *IniConfigContainer) Float(key string) (float64, error) {
|
||||||
|
return strconv.ParseFloat(c.getdata(key), 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultFloat returns the float64 value for a given key.
|
||||||
|
// if err != nil return defaltval
|
||||||
|
func (c *IniConfigContainer) DefaultFloat(key string, defaultval float64) float64 {
|
||||||
|
v, err := c.Float(key)
|
||||||
|
if err != nil {
|
||||||
|
return defaultval
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the string value for a given key.
|
||||||
|
func (c *IniConfigContainer) String(key string) string {
|
||||||
|
return c.getdata(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultString returns the string value for a given key.
|
||||||
|
// if err != nil return defaltval
|
||||||
|
func (c *IniConfigContainer) DefaultString(key string, defaultval string) string {
|
||||||
|
v := c.String(key)
|
||||||
|
if v == "" {
|
||||||
|
return defaultval
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strings returns the []string value for a given key.
|
||||||
|
// Return nil if config value does not exist or is empty.
|
||||||
|
func (c *IniConfigContainer) Strings(key string) []string {
|
||||||
|
v := c.String(key)
|
||||||
|
if v == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return strings.Split(v, ";")
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultStrings returns the []string value for a given key.
|
||||||
|
// if err != nil return defaltval
|
||||||
|
func (c *IniConfigContainer) DefaultStrings(key string, defaultval []string) []string {
|
||||||
|
v := c.Strings(key)
|
||||||
|
if v == nil {
|
||||||
|
return defaultval
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSection returns map for the given section
|
||||||
|
func (c *IniConfigContainer) GetSection(section string) (map[string]string, error) {
|
||||||
|
if v, ok := c.data[section]; ok {
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
return nil, errors.New("not exist section")
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveConfigFile save the config into file.
|
||||||
|
//
|
||||||
|
// BUG(env): The environment variable config item will be saved with real value in SaveConfigFile Funcation.
|
||||||
|
func (c *IniConfigContainer) SaveConfigFile(filename string) (err error) {
|
||||||
|
// Write configuration file by filename.
|
||||||
|
f, err := os.Create(filename)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
// Get section or key comments. Fixed #1607
|
||||||
|
getCommentStr := func(section, key string) string {
|
||||||
|
comment, ok := "", false
|
||||||
|
if len(key) == 0 {
|
||||||
|
comment, ok = c.sectionComment[section]
|
||||||
|
} else {
|
||||||
|
comment, ok = c.keyComment[section+"."+key]
|
||||||
|
}
|
||||||
|
|
||||||
|
if ok {
|
||||||
|
// Empty comment
|
||||||
|
if len(comment) == 0 || len(strings.TrimSpace(comment)) == 0 {
|
||||||
|
return string(bNumComment)
|
||||||
|
}
|
||||||
|
prefix := string(bNumComment)
|
||||||
|
// Add the line head character "#"
|
||||||
|
return prefix + strings.Replace(comment, lineBreak, lineBreak+prefix, -1)
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := bytes.NewBuffer(nil)
|
||||||
|
// Save default section at first place
|
||||||
|
if dt, ok := c.data[defaultSection]; ok {
|
||||||
|
for key, val := range dt {
|
||||||
|
if key != " " {
|
||||||
|
// Write key comments.
|
||||||
|
if v := getCommentStr(defaultSection, key); len(v) > 0 {
|
||||||
|
if _, err = buf.WriteString(v + lineBreak); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write key and value.
|
||||||
|
if _, err = buf.WriteString(key + string(bEqual) + val + lineBreak); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put a line between sections.
|
||||||
|
if _, err = buf.WriteString(lineBreak); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Save named sections
|
||||||
|
for section, dt := range c.data {
|
||||||
|
if section != defaultSection {
|
||||||
|
// Write section comments.
|
||||||
|
if v := getCommentStr(section, ""); len(v) > 0 {
|
||||||
|
if _, err = buf.WriteString(v + lineBreak); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write section name.
|
||||||
|
if _, err = buf.WriteString(string(sectionStart) + section + string(sectionEnd) + lineBreak); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, val := range dt {
|
||||||
|
if key != " " {
|
||||||
|
// Write key comments.
|
||||||
|
if v := getCommentStr(section, key); len(v) > 0 {
|
||||||
|
if _, err = buf.WriteString(v + lineBreak); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write key and value.
|
||||||
|
if _, err = buf.WriteString(key + string(bEqual) + val + lineBreak); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put a line between sections.
|
||||||
|
if _, err = buf.WriteString(lineBreak); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = buf.WriteTo(f); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set writes a new value for key.
|
||||||
|
// if write to one section, the key need be "section::key".
|
||||||
|
// if the section is not existed, it panics.
|
||||||
|
func (c *IniConfigContainer) Set(key, value string) error {
|
||||||
|
c.Lock()
|
||||||
|
defer c.Unlock()
|
||||||
|
if len(key) == 0 {
|
||||||
|
return errors.New("key is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
section, k string
|
||||||
|
sectionKey = strings.Split(key, "::")
|
||||||
|
)
|
||||||
|
|
||||||
|
if len(sectionKey) >= 2 {
|
||||||
|
section = sectionKey[0]
|
||||||
|
k = sectionKey[1]
|
||||||
|
} else {
|
||||||
|
section = defaultSection
|
||||||
|
k = sectionKey[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := c.data[section]; !ok {
|
||||||
|
c.data[section] = make(map[string]string)
|
||||||
|
}
|
||||||
|
c.data[section][k] = value
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DIY returns the raw value by a given key.
|
||||||
|
func (c *IniConfigContainer) DIY(key string) (v interface{}, err error) {
|
||||||
|
if v, ok := c.data[strings.ToLower(key)]; ok {
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
return v, errors.New("key not find")
|
||||||
|
}
|
||||||
|
|
||||||
|
// section.key or key
|
||||||
|
func (c *IniConfigContainer) getdata(key string) string {
|
||||||
|
if len(key) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
c.RLock()
|
||||||
|
defer c.RUnlock()
|
||||||
|
|
||||||
|
var (
|
||||||
|
section, k string
|
||||||
|
sectionKey = strings.Split(strings.ToLower(key), "::")
|
||||||
|
)
|
||||||
|
if len(sectionKey) >= 2 {
|
||||||
|
section = sectionKey[0]
|
||||||
|
k = sectionKey[1]
|
||||||
|
} else {
|
||||||
|
section = defaultSection
|
||||||
|
k = sectionKey[0]
|
||||||
|
}
|
||||||
|
if v, ok := c.data[section]; ok {
|
||||||
|
if vv, ok := v[k]; ok {
|
||||||
|
return vv
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Register("ini", &IniConfig{})
|
||||||
|
}
|
|
@ -0,0 +1,190 @@
|
||||||
|
// Copyright 2014 beego Author. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestIni(t *testing.T) {
|
||||||
|
|
||||||
|
var (
|
||||||
|
inicontext = `
|
||||||
|
;comment one
|
||||||
|
#comment two
|
||||||
|
appname = beeapi
|
||||||
|
httpport = 8080
|
||||||
|
mysqlport = 3600
|
||||||
|
PI = 3.1415976
|
||||||
|
runmode = "dev"
|
||||||
|
autorender = false
|
||||||
|
copyrequestbody = true
|
||||||
|
session= on
|
||||||
|
cookieon= off
|
||||||
|
newreg = OFF
|
||||||
|
needlogin = ON
|
||||||
|
enableSession = Y
|
||||||
|
enableCookie = N
|
||||||
|
flag = 1
|
||||||
|
path1 = ${GOPATH}
|
||||||
|
path2 = ${GOPATH||/home/go}
|
||||||
|
[demo]
|
||||||
|
key1="asta"
|
||||||
|
key2 = "xie"
|
||||||
|
CaseInsensitive = true
|
||||||
|
peers = one;two;three
|
||||||
|
password = ${GOPATH}
|
||||||
|
`
|
||||||
|
|
||||||
|
keyValue = map[string]interface{}{
|
||||||
|
"appname": "beeapi",
|
||||||
|
"httpport": 8080,
|
||||||
|
"mysqlport": int64(3600),
|
||||||
|
"pi": 3.1415976,
|
||||||
|
"runmode": "dev",
|
||||||
|
"autorender": false,
|
||||||
|
"copyrequestbody": true,
|
||||||
|
"session": true,
|
||||||
|
"cookieon": false,
|
||||||
|
"newreg": false,
|
||||||
|
"needlogin": true,
|
||||||
|
"enableSession": true,
|
||||||
|
"enableCookie": false,
|
||||||
|
"flag": true,
|
||||||
|
"path1": os.Getenv("GOPATH"),
|
||||||
|
"path2": os.Getenv("GOPATH"),
|
||||||
|
"demo::key1": "asta",
|
||||||
|
"demo::key2": "xie",
|
||||||
|
"demo::CaseInsensitive": true,
|
||||||
|
"demo::peers": []string{"one", "two", "three"},
|
||||||
|
"demo::password": os.Getenv("GOPATH"),
|
||||||
|
"null": "",
|
||||||
|
"demo2::key1": "",
|
||||||
|
"error": "",
|
||||||
|
"emptystrings": []string{},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
f, err := os.Create("testini.conf")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
_, err = f.WriteString(inicontext)
|
||||||
|
if err != nil {
|
||||||
|
f.Close()
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
f.Close()
|
||||||
|
defer os.Remove("testini.conf")
|
||||||
|
iniconf, err := NewConfig("ini", "testini.conf")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for k, v := range keyValue {
|
||||||
|
var err error
|
||||||
|
var value interface{}
|
||||||
|
switch v.(type) {
|
||||||
|
case int:
|
||||||
|
value, err = iniconf.Int(k)
|
||||||
|
case int64:
|
||||||
|
value, err = iniconf.Int64(k)
|
||||||
|
case float64:
|
||||||
|
value, err = iniconf.Float(k)
|
||||||
|
case bool:
|
||||||
|
value, err = iniconf.Bool(k)
|
||||||
|
case []string:
|
||||||
|
value = iniconf.Strings(k)
|
||||||
|
case string:
|
||||||
|
value = iniconf.String(k)
|
||||||
|
default:
|
||||||
|
value, err = iniconf.DIY(k)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("get key %q value fail,err %s", k, err)
|
||||||
|
} else if fmt.Sprintf("%v", v) != fmt.Sprintf("%v", value) {
|
||||||
|
t.Fatalf("get key %q value, want %v got %v .", k, v, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
if err = iniconf.Set("name", "astaxie"); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if iniconf.String("name") != "astaxie" {
|
||||||
|
t.Fatal("get name error")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIniSave(t *testing.T) {
|
||||||
|
|
||||||
|
const (
|
||||||
|
inicontext = `
|
||||||
|
app = app
|
||||||
|
;comment one
|
||||||
|
#comment two
|
||||||
|
# comment three
|
||||||
|
appname = beeapi
|
||||||
|
httpport = 8080
|
||||||
|
# DB Info
|
||||||
|
# enable db
|
||||||
|
[dbinfo]
|
||||||
|
# db type name
|
||||||
|
# suport mysql,sqlserver
|
||||||
|
name = mysql
|
||||||
|
`
|
||||||
|
|
||||||
|
saveResult = `
|
||||||
|
app=app
|
||||||
|
#comment one
|
||||||
|
#comment two
|
||||||
|
# comment three
|
||||||
|
appname=beeapi
|
||||||
|
httpport=8080
|
||||||
|
|
||||||
|
# DB Info
|
||||||
|
# enable db
|
||||||
|
[dbinfo]
|
||||||
|
# db type name
|
||||||
|
# suport mysql,sqlserver
|
||||||
|
name=mysql
|
||||||
|
`
|
||||||
|
)
|
||||||
|
cfg, err := NewConfigData("ini", []byte(inicontext))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
name := "newIniConfig.ini"
|
||||||
|
if err := cfg.SaveConfigFile(name); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.Remove(name)
|
||||||
|
|
||||||
|
if data, err := ioutil.ReadFile(name); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else {
|
||||||
|
cfgData := string(data)
|
||||||
|
datas := strings.Split(saveResult, "\n")
|
||||||
|
for _, line := range datas {
|
||||||
|
if strings.Contains(cfgData, line+"\n") == false {
|
||||||
|
t.Fatalf("different after save ini config file. need contains %q", line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,266 @@
|
||||||
|
// Copyright 2014 beego Author. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// JSONConfig is a json config parser and implements Config interface.
|
||||||
|
type JSONConfig struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse returns a ConfigContainer with parsed json config map.
|
||||||
|
func (js *JSONConfig) Parse(filename string) (Configer, error) {
|
||||||
|
file, err := os.Open(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
content, err := ioutil.ReadAll(file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return js.ParseData(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseData returns a ConfigContainer with json string
|
||||||
|
func (js *JSONConfig) ParseData(data []byte) (Configer, error) {
|
||||||
|
x := &JSONConfigContainer{
|
||||||
|
data: make(map[string]interface{}),
|
||||||
|
}
|
||||||
|
err := json.Unmarshal(data, &x.data)
|
||||||
|
if err != nil {
|
||||||
|
var wrappingArray []interface{}
|
||||||
|
err2 := json.Unmarshal(data, &wrappingArray)
|
||||||
|
if err2 != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
x.data["rootArray"] = wrappingArray
|
||||||
|
}
|
||||||
|
|
||||||
|
x.data = ExpandValueEnvForMap(x.data)
|
||||||
|
|
||||||
|
return x, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSONConfigContainer A Config represents the json configuration.
|
||||||
|
// Only when get value, support key as section:name type.
|
||||||
|
type JSONConfigContainer struct {
|
||||||
|
data map[string]interface{}
|
||||||
|
sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bool returns the boolean value for a given key.
|
||||||
|
func (c *JSONConfigContainer) Bool(key string) (bool, error) {
|
||||||
|
val := c.getData(key)
|
||||||
|
if val != nil {
|
||||||
|
return ParseBool(val)
|
||||||
|
}
|
||||||
|
return false, fmt.Errorf("not exist key: %q", key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultBool return the bool value if has no error
|
||||||
|
// otherwise return the defaultval
|
||||||
|
func (c *JSONConfigContainer) DefaultBool(key string, defaultval bool) bool {
|
||||||
|
if v, err := c.Bool(key); err == nil {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return defaultval
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int returns the integer value for a given key.
|
||||||
|
func (c *JSONConfigContainer) Int(key string) (int, error) {
|
||||||
|
val := c.getData(key)
|
||||||
|
if val != nil {
|
||||||
|
if v, ok := val.(float64); ok {
|
||||||
|
return int(v), nil
|
||||||
|
}
|
||||||
|
return 0, errors.New("not int value")
|
||||||
|
}
|
||||||
|
return 0, errors.New("not exist key:" + key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultInt returns the integer value for a given key.
|
||||||
|
// if err != nil return defaltval
|
||||||
|
func (c *JSONConfigContainer) DefaultInt(key string, defaultval int) int {
|
||||||
|
if v, err := c.Int(key); err == nil {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return defaultval
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64 returns the int64 value for a given key.
|
||||||
|
func (c *JSONConfigContainer) Int64(key string) (int64, error) {
|
||||||
|
val := c.getData(key)
|
||||||
|
if val != nil {
|
||||||
|
if v, ok := val.(float64); ok {
|
||||||
|
return int64(v), nil
|
||||||
|
}
|
||||||
|
return 0, errors.New("not int64 value")
|
||||||
|
}
|
||||||
|
return 0, errors.New("not exist key:" + key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultInt64 returns the int64 value for a given key.
|
||||||
|
// if err != nil return defaltval
|
||||||
|
func (c *JSONConfigContainer) DefaultInt64(key string, defaultval int64) int64 {
|
||||||
|
if v, err := c.Int64(key); err == nil {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return defaultval
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float returns the float value for a given key.
|
||||||
|
func (c *JSONConfigContainer) Float(key string) (float64, error) {
|
||||||
|
val := c.getData(key)
|
||||||
|
if val != nil {
|
||||||
|
if v, ok := val.(float64); ok {
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
return 0.0, errors.New("not float64 value")
|
||||||
|
}
|
||||||
|
return 0.0, errors.New("not exist key:" + key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultFloat returns the float64 value for a given key.
|
||||||
|
// if err != nil return defaltval
|
||||||
|
func (c *JSONConfigContainer) DefaultFloat(key string, defaultval float64) float64 {
|
||||||
|
if v, err := c.Float(key); err == nil {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return defaultval
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the string value for a given key.
|
||||||
|
func (c *JSONConfigContainer) String(key string) string {
|
||||||
|
val := c.getData(key)
|
||||||
|
if val != nil {
|
||||||
|
if v, ok := val.(string); ok {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultString returns the string value for a given key.
|
||||||
|
// if err != nil return defaltval
|
||||||
|
func (c *JSONConfigContainer) DefaultString(key string, defaultval string) string {
|
||||||
|
// TODO FIXME should not use "" to replace non existence
|
||||||
|
if v := c.String(key); v != "" {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return defaultval
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strings returns the []string value for a given key.
|
||||||
|
func (c *JSONConfigContainer) Strings(key string) []string {
|
||||||
|
stringVal := c.String(key)
|
||||||
|
if stringVal == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return strings.Split(c.String(key), ";")
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultStrings returns the []string value for a given key.
|
||||||
|
// if err != nil return defaltval
|
||||||
|
func (c *JSONConfigContainer) DefaultStrings(key string, defaultval []string) []string {
|
||||||
|
if v := c.Strings(key); v != nil {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return defaultval
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSection returns map for the given section
|
||||||
|
func (c *JSONConfigContainer) GetSection(section string) (map[string]string, error) {
|
||||||
|
if v, ok := c.data[section]; ok {
|
||||||
|
return v.(map[string]string), nil
|
||||||
|
}
|
||||||
|
return nil, errors.New("nonexist section " + section)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveConfigFile save the config into file
|
||||||
|
func (c *JSONConfigContainer) SaveConfigFile(filename string) (err error) {
|
||||||
|
// Write configuration file by filename.
|
||||||
|
f, err := os.Create(filename)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
b, err := json.MarshalIndent(c.data, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = f.Write(b)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set writes a new value for key.
|
||||||
|
func (c *JSONConfigContainer) Set(key, val string) error {
|
||||||
|
c.Lock()
|
||||||
|
defer c.Unlock()
|
||||||
|
c.data[key] = val
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DIY returns the raw value by a given key.
|
||||||
|
func (c *JSONConfigContainer) DIY(key string) (v interface{}, err error) {
|
||||||
|
val := c.getData(key)
|
||||||
|
if val != nil {
|
||||||
|
return val, nil
|
||||||
|
}
|
||||||
|
return nil, errors.New("not exist key")
|
||||||
|
}
|
||||||
|
|
||||||
|
// section.key or key
|
||||||
|
func (c *JSONConfigContainer) getData(key string) interface{} {
|
||||||
|
if len(key) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
c.RLock()
|
||||||
|
defer c.RUnlock()
|
||||||
|
|
||||||
|
sectionKeys := strings.Split(key, "::")
|
||||||
|
if len(sectionKeys) >= 2 {
|
||||||
|
curValue, ok := c.data[sectionKeys[0]]
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
for _, key := range sectionKeys[1:] {
|
||||||
|
if v, ok := curValue.(map[string]interface{}); ok {
|
||||||
|
if curValue, ok = v[key]; !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return curValue
|
||||||
|
}
|
||||||
|
if v, ok := c.data[key]; ok {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Register("json", &JSONConfig{})
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue