mirror of
https://github.com/ouqiang/gocron.git
synced 2024-04-21 12:31:58 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8ec80a02b6 | ||
|
|
601f250882 | ||
|
|
019fee2cce | ||
|
|
d4e0898674 | ||
|
|
d642c9641d | ||
|
|
2ba8cb67c8 | ||
|
|
350dc0881e | ||
|
|
db1ef3b317 | ||
|
|
1c2696798c | ||
|
|
2b1c7f16cf | ||
|
|
337ee35357 | ||
|
|
b509fcec55 | ||
|
|
6e8622e4f3 | ||
|
|
ff2228ed50 | ||
|
|
5d97ffad3b | ||
|
|
d13f1997dd | ||
|
|
5b8a8ae890 | ||
|
|
3d61c7694b | ||
|
|
3e8c3a236e | ||
|
|
e6560e8483 | ||
|
|
7e010d0aca | ||
|
|
b8f13b4b0e | ||
|
|
eb02804aec | ||
|
|
eb7d8d7bf5 | ||
|
|
b5d476b339 | ||
|
|
a78d14693b | ||
|
|
157e899e06 | ||
|
|
90a3136ce5 | ||
|
|
22069aa156 | ||
|
|
3de70760a0 | ||
|
|
e94a7dc28a | ||
|
|
dc60aae381 | ||
|
|
49e2c15473 | ||
|
|
4a524742cc | ||
|
|
7633cb8556 | ||
|
|
2bf6ea0b6a | ||
|
|
8e6b212b1d | ||
|
|
5a8c3391f5 | ||
|
|
bbed4f4c20 | ||
|
|
ff0dc6b704 | ||
|
|
499bf561d9 | ||
|
|
7e68ed9b60 | ||
|
|
527780105f | ||
|
|
ef996f8536 | ||
|
|
3d5c23128d | ||
|
|
6e0afb91c0 | ||
|
|
ccad05e50f | ||
|
|
604384a5f4 | ||
|
|
df0db4b843 | ||
|
|
ed521f519b | ||
|
|
2f5fff47d8 | ||
|
|
94197e0511 | ||
|
|
519c6613c7 | ||
|
|
4a77958556 | ||
|
|
95278d373e | ||
|
|
d91ae4fa44 | ||
|
|
5e8f2da70e | ||
|
|
8779b3ad8b | ||
|
|
28eda835c0 |
+8
-6
@@ -25,12 +25,14 @@ _testmain.go
|
||||
|
||||
.idea
|
||||
log/*
|
||||
data/sessions/*
|
||||
data/ssh/password/*
|
||||
data/ssh/private_key/*
|
||||
conf/install.lock
|
||||
conf/app.ini
|
||||
conf/ansible_hosts.ini
|
||||
data/*
|
||||
conf/*
|
||||
profile/*
|
||||
public/resource/javascript/vue.js
|
||||
gocron
|
||||
gocron.exe
|
||||
gocron-node
|
||||
gocron-node.exe
|
||||
|
||||
node_modules
|
||||
package.json
|
||||
+5
-1
@@ -1,4 +1,8 @@
|
||||
language: go
|
||||
go:
|
||||
- 1.7.x
|
||||
script: go test `go list ./... | grep -v vendor`
|
||||
script: go test `go list ./... | grep -v vendor`
|
||||
|
||||
notifications:
|
||||
on_success: never
|
||||
on_failure: always
|
||||
@@ -1,23 +1,27 @@
|
||||
[](https://travis-ci.org/ouqiang/gocron)
|
||||
# gocron - 定时任务web管理系统
|
||||
# gocron - 定时任务管理系统
|
||||
|
||||
# 项目简介
|
||||
使用Go语言开发的定时任务集中调度和管理系统, 用于替代Linux-crontab [查看文档](https://github.com/ouqiang/gocron/wiki)
|
||||
使用Go语言开发的定时任务集中调度和管理系统, 用于替代Linux-crontab [查看文档](https://github.com/ouqiang/gocron/wiki)
|
||||
|
||||
原有的延时任务拆分为独立项目[延迟队列](https://github.com/ouqiang/delay-queue)
|
||||
|
||||
## 功能特性
|
||||
* 支持任务CURD
|
||||
* Web界面管理定时任务, 支持动态添加、删除任务
|
||||
* crontab时间表达式,精确到秒
|
||||
* 任务执行失败重试设置
|
||||
* 任务超时设置
|
||||
* 延时任务
|
||||
* 任务执行方式
|
||||
* 调用本机系统命令
|
||||
* 通过SSH执行远程命令
|
||||
* 执行HTTP-GET请求
|
||||
* 任务依赖配置
|
||||
* 任务类型
|
||||
* shell任务
|
||||
> 在任务节点上执行shell命令, 支持任务同时在多个节点上运行
|
||||
* HTTP任务
|
||||
> 访问指定的URL地址, 由调度器直接执行, 不依赖任务节点
|
||||
* 查看任务执行日志
|
||||
* 任务执行结果通知, 支持邮件、Slack
|
||||
|
||||
### 截图
|
||||

|
||||

|
||||

|
||||
|
||||
@@ -29,42 +33,88 @@
|
||||
|
||||
|
||||
## 下载
|
||||
* [Linux-64位](http://opns468ov.bkt.clouddn.com/gocron/gocron-linux-amd64.tar.gz)
|
||||
* [Mac OS-64位](http://opns468ov.bkt.clouddn.com/gocron/gocron-darwin-amd64.tar.gz)
|
||||
* [Windows-64位](http://opns468ov.bkt.clouddn.com/gocron/gocron-windows-amd64.zip)
|
||||
[v1.2](https://github.com/ouqiang/gocron/releases/tag/v1.2)
|
||||
|
||||
[版本升级](https://github.com/ouqiang/gocron/wiki/版本升级)
|
||||
|
||||
## 安装
|
||||
|
||||
### 二进制安装
|
||||
1. 解压压缩包
|
||||
1. 解压压缩包
|
||||
2. `cd 解压目录`
|
||||
3. 启动
|
||||
* Windows: `gocron.exe web`
|
||||
* Linux、Mac OS: `./gocron web`
|
||||
3. 启动
|
||||
* 调度器启动
|
||||
* Windows: `gocron.exe web`
|
||||
* Linux、Mac OS: `./gocron web`
|
||||
* 任务节点启动, 默认监听0.0.0.0:5921
|
||||
* Windows: `gocron-node.exe`
|
||||
* Linux、Mac OS: `./gocron-node`
|
||||
4. 浏览器访问 http://localhost:5920
|
||||
|
||||
### 源码安装
|
||||
1. `go`语言版本1.7+
|
||||
2. `go get -d github.com/ouqiang/gocron`
|
||||
3. 编译 `go build`
|
||||
3. 编译
|
||||
* 调度器 `go build -tags gocron -o gocron`
|
||||
* 任务节点 `go build -tags node -o gocron-node`
|
||||
4. 启动、访问方式同上
|
||||
|
||||
### 启动可选参数
|
||||
### 命令
|
||||
|
||||
* -p 端口, 指定端口, 默认5920
|
||||
* -e 指定运行环境, dev|test|prod, dev模式下可查看更多日志信息, 默认prod
|
||||
* -h 查看帮助
|
||||
* gocron
|
||||
* -v 查看版本
|
||||
|
||||
## 安全
|
||||
* 使用`https`访问保证数据传输安全, 可在web服务器如nginx中配置https,通过反向代理,访问内部的gocron
|
||||
* 网站访问设置IP白名单
|
||||
* SSH登录设置IP白名单
|
||||
* gocron web
|
||||
* --host 默认0.0.0.0
|
||||
* -p 端口, 指定端口, 默认5920
|
||||
* -e 指定运行环境, dev|test|prod, dev模式下可查看更多日志信息, 默认prod
|
||||
* -h 查看帮助
|
||||
* gocron-node
|
||||
* -allow-root *nix平台允许以root用户运行
|
||||
* -s ip:port 监听地址
|
||||
* -cert-file 证书文件
|
||||
* -key-file 私钥文件
|
||||
* -token
|
||||
* -h 查看帮助
|
||||
* -v 查看版本
|
||||
|
||||
## To Do List
|
||||
- [x] 版本升级
|
||||
- [x] 批量开启、关闭、删除任务
|
||||
- [x] 调度器与任务节点通信支持https
|
||||
- [ ] 任务分组
|
||||
- [ ] 多用户
|
||||
- [ ] 权限控制
|
||||
- [ ] 新增任务API接口
|
||||
|
||||
## 程序使用的组件
|
||||
* web框架 [Macaron](http://go-macaron.com/)
|
||||
* 定时任务调度 [cron](https://github.com/robfig/cron)
|
||||
* Web框架 [Macaron](http://go-macaron.com/)
|
||||
* 定时任务调度 [Cron](https://github.com/robfig/cron)
|
||||
* ORM [Xorm](https://github.com/go-xorm/xorm)
|
||||
* UI框架 [Semantic UI](https://semantic-ui.com/)
|
||||
* 依赖管理(所有依赖包放入vendor目录) [govendor](https://github.com/kardianos/govendor)
|
||||
* 依赖管理(所有依赖包放入vendor目录) [Govendor](https://github.com/kardianos/govendor)
|
||||
* RPC框架 [gRPC](https://github.com/grpc/grpc)
|
||||
|
||||
## 贡献
|
||||
欢迎提交PR
|
||||
## 反馈
|
||||
提交[issue](https://github.com/ouqiang/gocron/issues/new)
|
||||
|
||||
## ChangeLog
|
||||
|
||||
v1.2
|
||||
--------
|
||||
* 用户登录页增加图形验证码
|
||||
* 支持从旧版本升级
|
||||
* 任务批量开启、关闭、删除
|
||||
* 调度器与任务节点支持HTTPS通信
|
||||
* 修复任务列表页总记录数显示错误
|
||||
|
||||
|
||||
|
||||
v1.1
|
||||
--------
|
||||
|
||||
* 任务可同时在多个节点上运行
|
||||
* *nix平台默认禁止以root用户运行任务节点
|
||||
* 子任务命令中增加预定义占位符, 子任务可根据主任务运行结果执行相应操作
|
||||
* 删除守护进程模块
|
||||
* Web访问日志输出到终端
|
||||
|
||||
@@ -52,9 +52,14 @@ if [[ $ARCH != '386' && $ARCH != 'amd64' ]];then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo '开始编译'
|
||||
GOOS=$OS GOARCH=$ARCH go build
|
||||
if [[ ! $? ]];then
|
||||
echo '开始编译调度器'
|
||||
if [[ $OS = 'windows' ]];then
|
||||
GOOS=$OS GOARCH=$ARCH go build -tags gocron -ldflags '-w'
|
||||
else
|
||||
GOOS=$OS GOARCH=$ARCH go build -tags gocron -ldflags '-w'
|
||||
fi
|
||||
|
||||
if [[ $? != 0 ]];then
|
||||
exit 1
|
||||
fi
|
||||
echo '编译完成'
|
||||
@@ -68,7 +73,7 @@ else
|
||||
fi
|
||||
|
||||
mkdir -p $TEMP_DIR/$APP_NAME
|
||||
if [[ ! $? ]]; then
|
||||
if [[ $? != 0 ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# set -x -u
|
||||
# 任务节点打包, 生成压缩包 gocron-node.zip或gocron-node.tar.gz
|
||||
# ./build-node.sh -p windows -a amd64
|
||||
# 参数含义
|
||||
# -p 指定平台(windows|linux|darwin)
|
||||
# -a 指定体系架构(amd64|386), 默认amd64
|
||||
|
||||
|
||||
# 目标平台 windows,linux,darwin
|
||||
OS=''
|
||||
# 目标平台架构
|
||||
ARCH=''
|
||||
# 应用名称
|
||||
APP_NAME='gocron-node'
|
||||
# 可执行文件名
|
||||
EXEC_NAME=''
|
||||
# 压缩包名称
|
||||
COMPRESS_FILE=''
|
||||
|
||||
|
||||
# -p 平台 -a 架构
|
||||
while getopts "p:a:" OPT;
|
||||
do
|
||||
case $OPT in
|
||||
p) OS=$OPTARG
|
||||
;;
|
||||
a) ARCH=$OPTARG
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -z $OS ]];then
|
||||
echo "平台不能为空"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ $OS != 'windows' && $OS != 'linux' && $OS != 'darwin' ]];then
|
||||
echo '平台错误,支持的平台 windows linux darmin(osx)'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z $ARCH ]];then
|
||||
ARCH='amd64'
|
||||
fi
|
||||
|
||||
if [[ $ARCH != '386' && $ARCH != 'amd64' ]];then
|
||||
echo 'arch错误,仅支持 386 amd64'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ $OS = 'windows' ]];then
|
||||
EXEC_NAME=${APP_NAME}.exe
|
||||
COMPRESS_FILE=${APP_NAME}-${OS}-${ARCH}.zip
|
||||
else
|
||||
EXEC_NAME=${APP_NAME}
|
||||
COMPRESS_FILE=${APP_NAME}-${OS}-${ARCH}.tar.gz
|
||||
fi
|
||||
|
||||
echo '开始编译任务节点'
|
||||
if [[ $OS = 'windows' ]];then
|
||||
GOOS=$OS GOARCH=$ARCH go build -tags node -ldflags '-w' -o $EXEC_NAME
|
||||
else
|
||||
GOOS=$OS GOARCH=$ARCH go build -tags node -ldflags '-w' -o $EXEC_NAME
|
||||
fi
|
||||
|
||||
if [[ $? != 0 ]];then
|
||||
exit 1
|
||||
fi
|
||||
echo '编译完成'
|
||||
|
||||
if [[ $OS = 'windows' ]];then
|
||||
zip -rq $COMPRESS_FILE $EXEC_NAME
|
||||
else
|
||||
tar czf $COMPRESS_FILE $EXEC_NAME
|
||||
fi
|
||||
|
||||
|
||||
rm $EXEC_NAME
|
||||
|
||||
echo '打包完成'
|
||||
echo '生成压缩文件--' $COMPRESS_FILE
|
||||
+56
-35
@@ -13,20 +13,27 @@ import (
|
||||
"github.com/ouqiang/gocron/models"
|
||||
"github.com/ouqiang/gocron/modules/setting"
|
||||
"time"
|
||||
"github.com/ouqiang/gocron/modules/rpc/grpcpool"
|
||||
)
|
||||
|
||||
// web服务器默认端口
|
||||
const DefaultPort = 5920
|
||||
|
||||
|
||||
var CmdWeb = cli.Command{
|
||||
Name: "web",
|
||||
Usage: "run web server",
|
||||
Action: run,
|
||||
Action: runWeb,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "host",
|
||||
Value: "0.0.0.0",
|
||||
Usage: "bind host",
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "port,p",
|
||||
Value: DefaultPort,
|
||||
Usage: "bind port number",
|
||||
Usage: "bind port",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "env,e",
|
||||
@@ -36,11 +43,11 @@ var CmdWeb = cli.Command{
|
||||
},
|
||||
}
|
||||
|
||||
func run(ctx *cli.Context) {
|
||||
func runWeb(ctx *cli.Context) {
|
||||
// 设置运行环境
|
||||
setEnvironment(ctx)
|
||||
// 初始化应用
|
||||
app.InitEnv()
|
||||
app.InitEnv(ctx.App.Version)
|
||||
// 初始化模块 DB、定时任务等
|
||||
initModule()
|
||||
// 捕捉信号,配置热更新等
|
||||
@@ -51,8 +58,9 @@ func run(ctx *cli.Context) {
|
||||
routers.Register(m)
|
||||
// 注册中间件.
|
||||
routers.RegisterMiddleware(m)
|
||||
host := parseHost(ctx)
|
||||
port := parsePort(ctx)
|
||||
m.Run(port)
|
||||
m.Run(host, port)
|
||||
}
|
||||
|
||||
func initModule() {
|
||||
@@ -66,32 +74,15 @@ func initModule() {
|
||||
}
|
||||
app.Setting = config
|
||||
|
||||
// 初始化DB
|
||||
models.Db = models.CreateDb()
|
||||
|
||||
// 版本升级
|
||||
upgradeIfNeed()
|
||||
|
||||
// 初始化定时任务
|
||||
serviceTask := new(service.Task)
|
||||
serviceTask.Initialize()
|
||||
|
||||
// 初始化延时任务
|
||||
delayTaskEnabled, err := config.Key("delay.task.enable").Bool()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if !delayTaskEnabled {
|
||||
return
|
||||
}
|
||||
delayTaskSlots, err := config.Key("delay.task.slots").Int()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
delayTaskTick := config.Key("delay.task.tick").String()
|
||||
tick, err := time.ParseDuration(delayTaskTick)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
serviceDelayTask := new(service.DelayTask)
|
||||
serviceDelayTask.Initialize(tick, delayTaskSlots)
|
||||
}
|
||||
|
||||
// 解析端口
|
||||
@@ -107,6 +98,14 @@ func parsePort(ctx *cli.Context) int {
|
||||
return port
|
||||
}
|
||||
|
||||
func parseHost(ctx *cli.Context) string {
|
||||
if ctx.IsSet("host") {
|
||||
return ctx.String("host")
|
||||
}
|
||||
|
||||
return "0.0.0.0"
|
||||
}
|
||||
|
||||
func setEnvironment(ctx *cli.Context) {
|
||||
var env string = "prod"
|
||||
if ctx.IsSet("env") {
|
||||
@@ -140,9 +139,15 @@ func catchSignal() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 应用退出
|
||||
func shutdown() {
|
||||
if !app.Installed {
|
||||
defer func() {
|
||||
logger.Info("已退出")
|
||||
os.Exit(0)
|
||||
}()
|
||||
|
||||
if !app.Installed {
|
||||
return
|
||||
}
|
||||
logger.Info("应用准备退出")
|
||||
@@ -150,12 +155,7 @@ func shutdown() {
|
||||
// 停止所有任务调度
|
||||
logger.Info("停止定时任务调度")
|
||||
serviceTask.StopAll()
|
||||
delayTaskEnable, _ := app.Setting.Key("delay.task.enable").Bool()
|
||||
if delayTaskEnable {
|
||||
logger.Info("停止延时任务调度")
|
||||
serviceDelayTask := new(service.DelayTask)
|
||||
serviceDelayTask.Stop()
|
||||
}
|
||||
|
||||
taskNumInRunning := service.TaskNum.Num()
|
||||
logger.Infof("正在运行的任务有%d个", taskNumInRunning)
|
||||
if taskNumInRunning > 0 {
|
||||
@@ -168,6 +168,27 @@ func shutdown() {
|
||||
time.Sleep(3 * time.Second)
|
||||
taskNumInRunning = service.TaskNum.Num()
|
||||
}
|
||||
logger.Info("已退出")
|
||||
os.Exit(0)
|
||||
|
||||
// 释放gRPC连接池
|
||||
grpcpool.Pool.ReleaseAll()
|
||||
}
|
||||
|
||||
// 判断应用是否需要升级, 当存在版本号文件且版本小于app.VersionId时升级
|
||||
func upgradeIfNeed() {
|
||||
currentVersionId := app.GetCurrentVersionId()
|
||||
// 没有版本号文件
|
||||
if currentVersionId == 0 {
|
||||
return;
|
||||
}
|
||||
if currentVersionId >= app.VersionId {
|
||||
return
|
||||
}
|
||||
|
||||
migration := new(models.Migration)
|
||||
logger.Infof("版本升级开始, 当前版本号%d", currentVersionId)
|
||||
|
||||
migration.Upgrade(currentVersionId)
|
||||
app.UpdateVersionFile()
|
||||
|
||||
logger.Infof("已升级到最新版本%d", app.VersionId)
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
// +build node
|
||||
// 任务节点
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/ouqiang/gocron/modules/rpc/server"
|
||||
"flag"
|
||||
"runtime"
|
||||
"os"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const AppVersion = "1.2"
|
||||
|
||||
func main() {
|
||||
var serverAddr string
|
||||
var allowRoot bool
|
||||
var version bool
|
||||
var keyFile string
|
||||
var certFile string
|
||||
var token string
|
||||
flag.BoolVar(&allowRoot, "allow-root", false, "./gocron-node -allow-root")
|
||||
flag.StringVar(&serverAddr, "s", "0.0.0.0:5921", "./gocron-node -s ip:port")
|
||||
flag.StringVar(&certFile, "cert-file", "", "./gocron-node -cert-file path")
|
||||
flag.StringVar(&keyFile, "key-file", "", "./gocron-node -key-file path")
|
||||
flag.StringVar(&token, "token", "", "./gocron-node -token")
|
||||
flag.BoolVar(&version, "v", false, "./gocron-node -v")
|
||||
flag.Parse()
|
||||
|
||||
if version {
|
||||
fmt.Println(AppVersion)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
certFile = strings.TrimSpace(certFile)
|
||||
keyFile = strings.TrimSpace(keyFile)
|
||||
|
||||
if certFile != "" && keyFile == "" {
|
||||
fmt.Println("missing argument key-file")
|
||||
return
|
||||
}
|
||||
|
||||
if keyFile != "" && certFile == "" {
|
||||
fmt.Println("missing argument cert-file")
|
||||
return
|
||||
}
|
||||
|
||||
if runtime.GOOS != "windows" && os.Getuid() == 0 && !allowRoot {
|
||||
fmt.Println("Do not run gocron-node as root user")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
|
||||
|
||||
server.Start(serverAddr, certFile, keyFile, token)
|
||||
}
|
||||
@@ -1,10 +1,7 @@
|
||||
package main
|
||||
// +build gocron
|
||||
// 调度中心
|
||||
|
||||
/*--------------------------------------------------------
|
||||
定时任务调度
|
||||
兼容Linux crontab时间格式语法,最小粒度可精确到每秒
|
||||
支持通过HTTP、SSH协议执行任务
|
||||
--------------------------------------------------------*/
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/urfave/cli"
|
||||
@@ -13,7 +10,7 @@ import (
|
||||
"github.com/ouqiang/gocron/cmd"
|
||||
)
|
||||
|
||||
const AppVersion = "0.2"
|
||||
const AppVersion = "1.2"
|
||||
|
||||
func main() {
|
||||
app := cli.NewApp()
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
"github.com/go-xorm/xorm"
|
||||
)
|
||||
|
||||
|
||||
// 延迟任务
|
||||
type DelayTask struct {
|
||||
Id int64 `xorm:"bigint pk autoincr"`
|
||||
Url string `xorm:"varchar(128) not null"`
|
||||
Params string `xorm:"varchar(256) not null default '' "`
|
||||
Delay int `xorm:"mediumint notnull default 0"` // 延迟时间
|
||||
Status Status `xorm:"tinyint notnull index(u_status_created) default 5"` // 状态 0:执行失败 1:执行中 2:执行成功 5: 待执行
|
||||
Created time.Time `xorm:"datetime notnull created index(u_status_created)"`
|
||||
Updated time.Time `xorm:"datetime updated"`
|
||||
BaseModel `xorm:"-"`
|
||||
}
|
||||
|
||||
func (task *DelayTask) Create() (insertId int64, err error) {
|
||||
_, err = Db.Insert(task)
|
||||
if err == nil {
|
||||
insertId = task.Id
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// 获取所有待执行任务
|
||||
func (task *DelayTask) ActiveList(endTime time.Time) ([]DelayTask, error) {
|
||||
list := make([]DelayTask, 0)
|
||||
fields := "id,url,params,delay,created"
|
||||
err := Db.Where("status = ? AND created <= ?", Waiting, endTime.Format(DefaultTimeFormat)).Cols(fields).Limit(task.PageSize, task.pageLimitOffset()).Find(&list)
|
||||
|
||||
return list, err
|
||||
}
|
||||
|
||||
// 获取待执行任务数量
|
||||
func (task *DelayTask) ActiveNum(endTime time.Time) (int, error) {
|
||||
count ,err := Db.Where("status = ? AND created <= ?", Waiting, endTime.Format(DefaultTimeFormat)).Count(task)
|
||||
|
||||
return int(count), err
|
||||
}
|
||||
|
||||
func (task *DelayTask) List(params CommonMap) ([]DelayTask, error) {
|
||||
task.parsePageAndPageSize(params)
|
||||
list := make([]DelayTask, 0)
|
||||
session := Db.Desc("id")
|
||||
task.parseWhere(session, params)
|
||||
err := session.Limit(task.PageSize, task.pageLimitOffset()).Find(&list)
|
||||
|
||||
return list, err
|
||||
}
|
||||
|
||||
|
||||
// 更新任务状态
|
||||
func (task *DelayTask) UpdateStatus(id int64, status Status) (int64, error) {
|
||||
return Db.Table(task).Id(id).Update(CommonMap{
|
||||
"status": status,
|
||||
})
|
||||
}
|
||||
|
||||
// 解析where
|
||||
func (task *DelayTask) parseWhere(session *xorm.Session, params CommonMap) {
|
||||
if len(params) == 0 {
|
||||
return
|
||||
}
|
||||
status, ok := params["Status"]
|
||||
if ok && status.(int) > -1 {
|
||||
session.And("status = ?", status)
|
||||
}
|
||||
}
|
||||
|
||||
// 删除N个月前的日志
|
||||
func (task *DelayTask) Remove(id int) (int64, error) {
|
||||
t := time.Now().AddDate(0, -id, 0)
|
||||
return Db.Where("created <= ?", t.Format(DefaultTimeFormat)).Delete(task)
|
||||
}
|
||||
|
||||
func (task *DelayTask) Total(params CommonMap) (int64, error) {
|
||||
session := Db.NewSession()
|
||||
task.parseWhere(session, params)
|
||||
return session.Count(task)
|
||||
}
|
||||
+6
-44
@@ -1,14 +1,7 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"github.com/ouqiang/gocron/modules/ssh"
|
||||
"github.com/go-xorm/xorm"
|
||||
"github.com/ouqiang/gocron/modules/app"
|
||||
"github.com/ouqiang/gocron/modules/utils"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"github.com/ouqiang/gocron/modules/logger"
|
||||
)
|
||||
|
||||
// 主机
|
||||
@@ -16,11 +9,12 @@ type Host struct {
|
||||
Id int16 `xorm:"smallint pk autoincr"`
|
||||
Name string `xorm:"varchar(64) notnull"` // 主机名称
|
||||
Alias string `xorm:"varchar(32) notnull default '' "` // 主机别名
|
||||
Username string `xorm:"varchar(32) notnull default '' "` // ssh 用户名
|
||||
Port int `xorm:"notnull default 22"` // 主机端口
|
||||
CertFile string `xorm:"varchar(64) notnull default '' "`
|
||||
Token string `xorm:"varchar(128) notnull default '' "`
|
||||
Remark string `xorm:"varchar(100) notnull default '' "` // 备注
|
||||
AuthType ssh.HostAuthType `xorm:"tinyint notnull default 1"` // 认证方式 1: 密码 2: 公钥
|
||||
BaseModel `xorm:"-"`
|
||||
Selected bool `xorm:"-"`
|
||||
}
|
||||
|
||||
// 新增
|
||||
@@ -34,7 +28,7 @@ func (host *Host) Create() (insertId int16, err error) {
|
||||
}
|
||||
|
||||
func (host *Host) UpdateBean(id int16) (int64, error) {
|
||||
return Db.ID(id).Cols("name,alias,username,port,remark,auth_type").Update(host)
|
||||
return Db.ID(id).Cols("name,alias,port,cert_file,token,remark").Update(host)
|
||||
}
|
||||
|
||||
|
||||
@@ -45,7 +39,7 @@ func (host *Host) Update(id int, data CommonMap) (int64, error) {
|
||||
|
||||
// 删除
|
||||
func (host *Host) Delete(id int) (int64, error) {
|
||||
return Db.Id(id).Delete(host)
|
||||
return Db.Id(id).Delete(new(Host))
|
||||
}
|
||||
|
||||
func (host *Host) Find(id int) error {
|
||||
@@ -76,7 +70,7 @@ func (host *Host) List(params CommonMap) ([]Host, error) {
|
||||
|
||||
func (host *Host) AllList() ([]Host, error) {
|
||||
list := make([]Host, 0)
|
||||
err := Db.Desc("id").Find(&list)
|
||||
err := Db.Cols("name,port").Desc("id").Find(&list)
|
||||
|
||||
return list, err
|
||||
}
|
||||
@@ -87,38 +81,6 @@ func (host *Host) Total(params CommonMap) (int64, error) {
|
||||
return session.Count(host)
|
||||
}
|
||||
|
||||
func (h *Host) GetPasswordByHost(host string) (string, error) {
|
||||
path := app.DataDir + "/ssh/password/" + host
|
||||
|
||||
|
||||
return h.readFile(path)
|
||||
}
|
||||
|
||||
func (h *Host) GetPrivateKeyByHost(host string) (string,error) {
|
||||
path := app.DataDir + "/ssh/private_key/" + host
|
||||
|
||||
return h.readFile(path)
|
||||
}
|
||||
|
||||
func (host *Host) readFile(file string) (string, error) {
|
||||
logger.Debug("认证文件路径: ", file)
|
||||
if !utils.FileExist(file) {
|
||||
return "", errors.New(file + "-认证文件不存在或无权限访问")
|
||||
}
|
||||
|
||||
contentByte, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
content := string(contentByte)
|
||||
content = strings.TrimSpace(content)
|
||||
if content == "" {
|
||||
return "", errors.New("密码为空")
|
||||
}
|
||||
|
||||
return content, nil
|
||||
}
|
||||
|
||||
// 解析where
|
||||
func (host *Host) parseWhere(session *xorm.Session, params CommonMap) {
|
||||
if len(params) == 0 {
|
||||
|
||||
+113
-4
@@ -2,20 +2,24 @@ package models
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/ouqiang/gocron/modules/logger"
|
||||
"github.com/go-xorm/xorm"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// 创建数据库表
|
||||
|
||||
type Migration struct{}
|
||||
|
||||
func (migration *Migration) Exec(dbName string) error {
|
||||
// 首次安装, 创建数据库表
|
||||
func (migration *Migration) Install(dbName string) error {
|
||||
if !isDatabaseExist(dbName) {
|
||||
return errors.New("数据库不存在")
|
||||
}
|
||||
setting := new(Setting)
|
||||
task := new(Task)
|
||||
tables := []interface{}{
|
||||
&User{}, task, &DelayTask{}, &TaskLog{}, &Host{}, setting,&LoginLog{},
|
||||
&User{}, task, &TaskLog{}, &Host{}, setting,&LoginLog{},&TaskHost{},
|
||||
}
|
||||
for _, table := range tables {
|
||||
exist, err:= Db.IsTableExist(table)
|
||||
@@ -36,9 +40,114 @@ func (migration *Migration) Exec(dbName string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 创建数据库
|
||||
// 判断数据库是否存在
|
||||
func isDatabaseExist(name string) bool {
|
||||
_, err := Db.Exec("use ?", name)
|
||||
|
||||
return err != nil
|
||||
}
|
||||
|
||||
// 迭代升级数据库, 新建表、新增字段等
|
||||
func (migration *Migration) Upgrade(oldVersionId int) {
|
||||
versionIds := []int{110, 120}
|
||||
upgradeFuncs := []func(*xorm.Session) error {
|
||||
migration.upgradeFor110,
|
||||
migration.upgradeFor120,
|
||||
}
|
||||
|
||||
// 默认当前版本为v1.0
|
||||
startIndex := 0
|
||||
// 从当前版本的下一版本开始升级
|
||||
for i, value := range versionIds {
|
||||
if oldVersionId == value {
|
||||
startIndex = i + 1
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
length := len(versionIds)
|
||||
if startIndex >= length {
|
||||
return
|
||||
}
|
||||
|
||||
session := Db.NewSession()
|
||||
err := session.Begin()
|
||||
if err != nil {
|
||||
logger.Fatalf("开启事务失败-%s", err.Error())
|
||||
}
|
||||
for startIndex < length {
|
||||
err = upgradeFuncs[startIndex](session)
|
||||
if err == nil {
|
||||
startIndex++
|
||||
continue
|
||||
}
|
||||
dbErr := session.Rollback()
|
||||
if dbErr != nil {
|
||||
logger.Fatalf("事务回滚失败-%s",dbErr.Error())
|
||||
}
|
||||
logger.Fatal(err)
|
||||
}
|
||||
err = session.Commit()
|
||||
if err != nil {
|
||||
logger.Fatalf("提交事务失败-%s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// 升级到v1.1版本
|
||||
func (migration *Migration) upgradeFor110(session *xorm.Session) error {
|
||||
logger.Info("开始升级到v1.1")
|
||||
// 创建表task_host
|
||||
err := session.Sync2(new(TaskHost))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tableName := TablePrefix + "task"
|
||||
// 把task对应的host_id写入task_host表
|
||||
sql := fmt.Sprintf("SELECT id, host_id FROM %s WHERE host_id > 0", tableName)
|
||||
results, err := session.Query(sql)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, value := range results {
|
||||
taskHostModel := &TaskHost{}
|
||||
taskId, err := strconv.Atoi(string(value["id"]))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hostId, err := strconv.Atoi(string(value["host_id"]))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
taskHostModel.TaskId = taskId
|
||||
taskHostModel.HostId = int16(hostId)
|
||||
_, err = session.Insert(taskHostModel)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 删除task表host_id字段
|
||||
_, err = session.Exec(fmt.Sprintf("ALTER TABLE %s DROP COLUMN host_id", tableName))
|
||||
|
||||
logger.Info("已升级到v1.1\n")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
// 升级到v1.2版本
|
||||
func (migration *Migration) upgradeFor120(session *xorm.Session) error {
|
||||
// host表增加cert_file字段
|
||||
tableName := TablePrefix + "host"
|
||||
_, err := session.Exec(fmt.Sprintf("ALTER TABLE %s Add COLUMN cert_file VARCHAR(64) NOT NULL DEFAULT ''", tableName))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = session.Exec(fmt.Sprintf("ALTER TABLE %s Add COLUMN token VARCHAR(64) NOT NULL DEFAULT ''", tableName))
|
||||
|
||||
return err
|
||||
}
|
||||
+25
-12
@@ -7,9 +7,10 @@ import (
|
||||
"github.com/go-xorm/xorm"
|
||||
"gopkg.in/macaron.v1"
|
||||
"strings"
|
||||
"time"
|
||||
"github.com/ouqiang/gocron/modules/logger"
|
||||
"github.com/ouqiang/gocron/modules/app"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Status int8
|
||||
@@ -25,7 +26,6 @@ const (
|
||||
Running Status = 1 // 运行中
|
||||
Finish Status = 2 // 完成
|
||||
Cancel Status = 3 // 取消
|
||||
Background Status = 4 // 后台运行
|
||||
Waiting Status = 5 // 等待中
|
||||
)
|
||||
|
||||
@@ -54,8 +54,8 @@ func (model *BaseModel) parsePageAndPageSize(params CommonMap) {
|
||||
if model.Page <= 0 {
|
||||
model.Page = Page
|
||||
}
|
||||
if model.PageSize <= 0 || model.PageSize > MaxPageSize {
|
||||
model.PageSize = PageSize
|
||||
if model.PageSize <= 0 {
|
||||
model.PageSize = MaxPageSize
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,6 +71,17 @@ func CreateDb() *xorm.Engine {
|
||||
if err != nil {
|
||||
logger.Fatal("创建xorm引擎失败", err)
|
||||
}
|
||||
maxIdleConns, err := strconv.Atoi(config["max_idle_conns"])
|
||||
maxOpenConns, err := strconv.Atoi(config["max_open_conns"])
|
||||
if maxIdleConns <= 0 {
|
||||
maxIdleConns = 30
|
||||
}
|
||||
if maxOpenConns <= 0 {
|
||||
maxOpenConns = 100
|
||||
}
|
||||
engine.SetMaxIdleConns(maxIdleConns)
|
||||
engine.SetMaxOpenConns(maxOpenConns)
|
||||
|
||||
if config["prefix"] != "" {
|
||||
// 设置表前缀
|
||||
TablePrefix = config["prefix"]
|
||||
@@ -113,14 +124,6 @@ func getDbEngineDSN(engine string, config map[string]string) string {
|
||||
return dsn
|
||||
}
|
||||
|
||||
// 定时ping, 防止因数据库超时设置连接被断开
|
||||
func keepDbAlived(engine *xorm.Engine) {
|
||||
t := time.Tick(180 * time.Second)
|
||||
for {
|
||||
<- t
|
||||
engine.Ping()
|
||||
}
|
||||
}
|
||||
|
||||
// 获取数据库配置
|
||||
func getDbConfig() map[string]string {
|
||||
@@ -133,6 +136,16 @@ func getDbConfig() map[string]string {
|
||||
db["charset"] = app.Setting.Key("db.charset").String()
|
||||
db["prefix"] = app.Setting.Key("db.prefix").String()
|
||||
db["engine"] = app.Setting.Key("db.engine").String()
|
||||
db["max_idle_conns"] = app.Setting.Key("db.max.idle.conns").String()
|
||||
db["max_open_conns"] = app.Setting.Key("db.max.open.conns").String()
|
||||
|
||||
return db
|
||||
}
|
||||
|
||||
func keepDbAlived(engine *xorm.Engine) {
|
||||
t := time.Tick(180 * time.Second)
|
||||
for {
|
||||
<- t
|
||||
engine.Ping()
|
||||
}
|
||||
}
|
||||
+129
-65
@@ -2,51 +2,58 @@ package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
"github.com/ouqiang/gocron/modules/ssh"
|
||||
"github.com/go-xorm/xorm"
|
||||
"github.com/ouqiang/gocron/modules/utils"
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type TaskProtocol int8
|
||||
|
||||
const (
|
||||
TaskHTTP TaskProtocol = iota + 1 // HTTP协议
|
||||
TaskSSH // SSH命令
|
||||
TaskLocalCommand // 本地命令
|
||||
TaskRPC // RPC方式执行命令
|
||||
)
|
||||
|
||||
type TaskLevel int8
|
||||
|
||||
const (
|
||||
TaskLevelParent TaskLevel = 1 // 父任务
|
||||
TaskLevelChild TaskLevel = 2 // 子任务(依赖任务)
|
||||
)
|
||||
|
||||
type TaskDependencyStatus int8
|
||||
|
||||
const (
|
||||
TaskDependencyStatusStrong TaskDependencyStatus = 1 // 强依赖
|
||||
TaskDependencyStatusWeak TaskDependencyStatus = 2 // 弱依赖
|
||||
)
|
||||
|
||||
// 任务
|
||||
type Task struct {
|
||||
Id int `xorm:"int pk autoincr"`
|
||||
Name string `xorm:"varchar(32) notnull"` // 任务名称
|
||||
Level TaskLevel `xorm:"smallint notnull index default 1"` // 任务等级 1: 主任务 2: 依赖任务
|
||||
DependencyTaskId string `xorm:"varchar(64) notnull default ''"` // 依赖任务ID,多个ID逗号分隔
|
||||
DependencyStatus TaskDependencyStatus `xorm:"smallint notnull default 1"` // 依赖关系 1:强依赖 主任务执行成功, 依赖任务才会被执行 2:弱依赖
|
||||
Spec string `xorm:"varchar(64) notnull"` // crontab
|
||||
Protocol TaskProtocol `xorm:"tinyint notnull"` // 协议 1:http 2:ssh-command 3: 系统命令
|
||||
Protocol TaskProtocol `xorm:"tinyint notnull index"` // 协议 1:http 2:系统命令
|
||||
Command string `xorm:"varchar(256) notnull"` // URL地址或shell命令
|
||||
Timeout int `xorm:"mediumint notnull default 0"` // 任务执行超时时间(单位秒),0不限制
|
||||
Multi int8 `xorm:"tinyint notnull default 1"` // 是否允许多实例运行
|
||||
RetryTimes int8 `xorm:"tinyint notnull default 0"` // 重试次数
|
||||
HostId int16 `xorm:"smallint notnull default 0"` // SSH host id,
|
||||
NotifyStatus int8 `xorm:"smallint notnull default 1"` // 任务执行结束是否通知 0: 不通知 1: 失败通知 2: 执行结束通知
|
||||
NotifyType int8 `xorm:"smallint notnull default 0"` // 通知类型 1: 邮件 2: slack
|
||||
NotifyReceiverId string `xorm:"varchar(256) notnull default '' "` // 通知接受者ID, setting表主键ID,多个ID逗号分隔
|
||||
Remark string `xorm:"varchar(100) notnull default ''"` // 备注
|
||||
Status Status `xorm:"tinyint notnull default 0"` // 状态 1:正常 0:停止
|
||||
Status Status `xorm:"tinyint notnull index default 0"` // 状态 1:正常 0:停止
|
||||
Created time.Time `xorm:"datetime notnull created"` // 创建时间
|
||||
Deleted time.Time `xorm:"datetime deleted"` // 删除时间
|
||||
BaseModel `xorm:"-"`
|
||||
Hosts []TaskHostDetail `xorm:"-"`
|
||||
}
|
||||
|
||||
type TaskHost struct {
|
||||
Task `xorm:"extends"`
|
||||
Name string
|
||||
Port int
|
||||
Username string
|
||||
Alias string
|
||||
AuthType ssh.HostAuthType
|
||||
}
|
||||
|
||||
func (TaskHost) TableName() string {
|
||||
return TablePrefix + "task"
|
||||
func taskHostTableName() []string {
|
||||
return []string{TablePrefix + "task_host", "th"}
|
||||
}
|
||||
|
||||
// 新增
|
||||
@@ -63,29 +70,19 @@ func (task *Task) Create() (insertId int, err error) {
|
||||
func (task *Task) CreateTestTask() {
|
||||
// HTTP任务
|
||||
task.Name = "测试HTTP任务"
|
||||
task.Level = TaskLevelParent
|
||||
task.Protocol = TaskHTTP
|
||||
task.Spec = "*/30 * * * * *"
|
||||
// 查询IP地址区域信息
|
||||
task.Command = "http://ip.taobao.com/service/getIpInfo.php?ip=117.27.140.253"
|
||||
task.Status = Enabled
|
||||
task.Create()
|
||||
|
||||
// 系统命令
|
||||
task.Id = 0
|
||||
task.Name = "测试系统命令任务"
|
||||
task.Protocol = TaskLocalCommand
|
||||
task.Spec = "@every 1m"
|
||||
task.Status = Enabled
|
||||
if utils.IsWindows() {
|
||||
task.Command = "dir"
|
||||
} else {
|
||||
task.Command = "ls"
|
||||
}
|
||||
task.Create()
|
||||
}
|
||||
|
||||
func (task *Task) UpdateBean(id int) (int64, error) {
|
||||
return Db.ID(id).Cols("name,spec,protocol,command,timeout,multi,retry_times,host_id,remark,notify_status,notify_type,notify_receiver_id").Update(task)
|
||||
return Db.ID(id).
|
||||
Cols("name,spec,protocol,command,timeout,multi,retry_times,remark,notify_status,notify_type,notify_receiver_id, dependency_task_id, dependency_status").
|
||||
Update(task)
|
||||
}
|
||||
|
||||
// 更新
|
||||
@@ -109,28 +106,48 @@ func (task *Task) Enable(id int) (int64, error) {
|
||||
}
|
||||
|
||||
// 获取所有激活任务
|
||||
func (task *Task) ActiveList() ([]TaskHost, error) {
|
||||
list := make([]TaskHost, 0)
|
||||
fields := "t.*, host.alias,host.name,host.username,host.port,host.auth_type"
|
||||
err := Db.Alias("t").Join("LEFT", hostTableName(), "t.host_id=host.id").Where("t.status = ?", Enabled).Cols(fields).Find(&list)
|
||||
func (task *Task) ActiveList() ([]Task, error) {
|
||||
list := make([]Task, 0)
|
||||
err := Db.Where("status = ? AND level = ?", Enabled, TaskLevelParent).
|
||||
Find(&list)
|
||||
|
||||
return list, err
|
||||
if err != nil {
|
||||
return list, err
|
||||
}
|
||||
|
||||
return task.setHostsForTasks(list)
|
||||
}
|
||||
|
||||
// 获取某个主机下的所有激活任务
|
||||
func (task *Task) ActiveListByHostId(hostId int16) ([]TaskHost, error) {
|
||||
list := make([]TaskHost, 0)
|
||||
fields := "t.*, host.alias,host.name,host.username,host.port,host.auth_type"
|
||||
err := Db.Alias("t").Join("LEFT", hostTableName(), "t.host_id=host.id").Where("t.status = ? AND t.host_id = ?", Enabled, hostId).Cols(fields).Find(&list)
|
||||
func (task *Task) ActiveListByHostId(hostId int16) ([]Task, error) {
|
||||
taskHostModel := new(TaskHost)
|
||||
taskIds, err := taskHostModel.GetTaskIdsByHostId(hostId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
list := make([]Task, 0)
|
||||
err = Db.Where("status = ? AND level = ?", Enabled, TaskLevelParent).
|
||||
In("id", taskIds...).
|
||||
Find(&list)
|
||||
if err != nil {
|
||||
return list, err
|
||||
}
|
||||
|
||||
return list, err
|
||||
return task.setHostsForTasks(list)
|
||||
}
|
||||
|
||||
// 判断主机id是否有引用
|
||||
func (task *Task) HostIdExist(hostId int16) (bool, error) {
|
||||
count, err := Db.Where("host_id = ?", hostId).Count(task);
|
||||
func (task *Task) setHostsForTasks(tasks []Task) ([]Task, error) {
|
||||
taskHostModel := new(TaskHost)
|
||||
var err error
|
||||
for i, value := range tasks {
|
||||
taskHostDetails, err := taskHostModel.GetHostIdsByTaskId(value.Id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tasks[i].Hosts = taskHostDetails
|
||||
}
|
||||
|
||||
return count > 0, err
|
||||
return tasks, err
|
||||
}
|
||||
|
||||
// 判断任务名称是否存在
|
||||
@@ -144,29 +161,79 @@ func (task *Task) NameExist(name string, id int) (bool, error) {
|
||||
return count > 0, err
|
||||
}
|
||||
|
||||
func(task *Task) Detail(id int) (TaskHost, error) {
|
||||
taskHost := TaskHost{}
|
||||
fields := "t.*, host.alias,host.name,host.username,host.port,host.auth_type"
|
||||
_, err := Db.Alias("t").Join("LEFT", hostTableName(), "t.host_id=host.id").Where("t.id=?", id).Cols(fields).Get(&taskHost)
|
||||
func (task *Task) GetStatus(id int) (Status, error) {
|
||||
exist, err := Db.Id(id).Get(task)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if !exist {
|
||||
return 0, errors.New("not exist")
|
||||
}
|
||||
|
||||
return taskHost, err
|
||||
return task.Status, nil
|
||||
}
|
||||
|
||||
func (task *Task) List(params CommonMap) ([]TaskHost, error) {
|
||||
task.parsePageAndPageSize(params)
|
||||
list := make([]TaskHost, 0)
|
||||
fields := "t.*, host.alias,host.name"
|
||||
session := Db.Alias("t").Join("LEFT", hostTableName(), "t.host_id=host.id")
|
||||
task.parseWhere(session, params)
|
||||
err := session.Cols(fields).Desc("t.id").Limit(task.PageSize, task.pageLimitOffset()).Find(&list)
|
||||
func(task *Task) Detail(id int) (Task, error) {
|
||||
t := Task{}
|
||||
_, err := Db.Where("id=?", id).Get(&t)
|
||||
|
||||
return list, err
|
||||
if err != nil {
|
||||
return t, err
|
||||
}
|
||||
|
||||
taskHostModel := new(TaskHost)
|
||||
t.Hosts, err = taskHostModel.GetHostIdsByTaskId(id)
|
||||
|
||||
return t, err
|
||||
}
|
||||
|
||||
func (task *Task) List(params CommonMap) ([]Task, error) {
|
||||
task.parsePageAndPageSize(params)
|
||||
list := make([]Task, 0)
|
||||
session := Db.Alias("t").Join("LEFT", taskHostTableName(), "t.id = th.task_id")
|
||||
task.parseWhere(session, params)
|
||||
err := session.GroupBy("t.id").Desc("t.id").Cols("t.*").Limit(task.PageSize, task.pageLimitOffset()).Find(&list)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return task.setHostsForTasks(list)
|
||||
}
|
||||
|
||||
// 获取依赖任务列表
|
||||
func (task *Task) GetDependencyTaskList(ids string) ([]Task, error) {
|
||||
list := make([]Task, 0)
|
||||
if ids == "" {
|
||||
return list, nil
|
||||
}
|
||||
idList := strings.Split(ids, ",")
|
||||
taskIds := make([]interface{}, len(idList))
|
||||
for i, v := range idList {
|
||||
taskIds[i] = v
|
||||
}
|
||||
fields := "t.*"
|
||||
err := Db.Alias("t").
|
||||
Where("t.level = ?", TaskLevelChild).
|
||||
In("t.id", taskIds).
|
||||
Cols(fields).
|
||||
Find(&list)
|
||||
|
||||
if err != nil {
|
||||
return list, err
|
||||
}
|
||||
|
||||
return task.setHostsForTasks(list)
|
||||
}
|
||||
|
||||
func (task *Task) Total(params CommonMap) (int64, error) {
|
||||
session := Db.Alias("t").Join("LEFT", hostTableName(), "t.host_id=host.id")
|
||||
session := Db.Alias("t").Join("LEFT", taskHostTableName(), "t.id = th.task_id")
|
||||
task.parseWhere(session, params)
|
||||
return session.Count(task)
|
||||
list := make([]Task, 0)
|
||||
|
||||
err := session.GroupBy("t.id").Find(&list)
|
||||
|
||||
return int64(len(list)), err
|
||||
}
|
||||
|
||||
// 解析where
|
||||
@@ -180,7 +247,7 @@ func (task *Task) parseWhere(session *xorm.Session, params CommonMap) {
|
||||
}
|
||||
hostId, ok := params["HostId"]
|
||||
if ok && hostId.(int) > 0 {
|
||||
session.And("host_id = ?", hostId)
|
||||
session.And("th.host_id = ?", hostId)
|
||||
}
|
||||
name, ok := params["Name"]
|
||||
if ok && name.(string) != "" {
|
||||
@@ -196,6 +263,3 @@ func (task *Task) parseWhere(session *xorm.Session, params CommonMap) {
|
||||
}
|
||||
}
|
||||
|
||||
func hostTableName() []string {
|
||||
return []string{TablePrefix + "host", "host"}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package models
|
||||
|
||||
|
||||
type TaskHost struct {
|
||||
Id int `xorm:"int pk autoincr"`
|
||||
TaskId int `xorm:"int not null index"`
|
||||
HostId int16 `xorm:"smallint not null index"`
|
||||
}
|
||||
|
||||
type TaskHostDetail struct {
|
||||
TaskHost `xorm:"extends"`
|
||||
Name string
|
||||
Port int
|
||||
Alias string
|
||||
CertFile string
|
||||
Token string
|
||||
}
|
||||
|
||||
func (TaskHostDetail) TableName() string {
|
||||
return TablePrefix + "task_host"
|
||||
}
|
||||
|
||||
func hostTableName() []string {
|
||||
return []string{TablePrefix + "host", "h"}
|
||||
}
|
||||
|
||||
func (th *TaskHost) Remove(taskId int) error {
|
||||
_, err := Db.Where("task_id = ?", taskId).Delete(new(TaskHost))
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (th *TaskHost) Add(taskId int, hostIds []int) error {
|
||||
|
||||
err := th.Remove(taskId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
taskHosts := make([]TaskHost, len(hostIds))
|
||||
for i, value := range hostIds {
|
||||
taskHosts[i].TaskId = taskId
|
||||
taskHosts[i].HostId = int16(value)
|
||||
}
|
||||
|
||||
_, err = Db.Insert(&taskHosts)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (th *TaskHost) GetHostIdsByTaskId(taskId int) ([]TaskHostDetail, error) {
|
||||
list := make([]TaskHostDetail, 0)
|
||||
fields := "th.id,th.host_id,h.alias,h.name,h.port,h.cert_file,h.token"
|
||||
err := Db.Alias("th").
|
||||
Join("LEFT", hostTableName(), "th.host_id=h.id").
|
||||
Where("th.task_id = ?", taskId).
|
||||
Cols(fields).
|
||||
Find(&list)
|
||||
|
||||
return list, err
|
||||
}
|
||||
|
||||
func (th *TaskHost) GetTaskIdsByHostId(hostId int16) ([]interface{}, error) {
|
||||
list := make([]TaskHost, 0)
|
||||
err := Db.Where("host_id = ?", hostId).Cols("task_id").Find(&list)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
taskIds := make([]interface{}, len(list))
|
||||
for i, value := range list {
|
||||
taskIds[i] = value.TaskId
|
||||
}
|
||||
|
||||
return taskIds, err
|
||||
}
|
||||
|
||||
// 判断主机id是否有引用
|
||||
func (th *TaskHost) HostIdExist(hostId int16) (bool, error) {
|
||||
count, err := Db.Where("host_id = ?", hostId).Count(th);
|
||||
|
||||
return count > 0, err
|
||||
}
|
||||
+4
-25
@@ -14,15 +14,14 @@ type TaskLog struct {
|
||||
TaskId int `xorm:"int notnull index default 0"` // 任务id
|
||||
Name string `xorm:"varchar(32) notnull"` // 任务名称
|
||||
Spec string `xorm:"varchar(64) notnull"` // crontab
|
||||
Protocol TaskProtocol `xorm:"tinyint notnull"` // 协议 1:http 2:ssh-command 3:系统命令
|
||||
Protocol TaskProtocol `xorm:"tinyint notnull index"` // 协议 1:http 2:RPC
|
||||
Command string `xorm:"varchar(256) notnull"` // URL地址或shell命令
|
||||
Timeout int `xorm:"mediumint notnull default 0"` // 任务执行超时时间(单位秒),0不限制
|
||||
RetryTimes int8 `xorm:"tinyint notnull default 0"` // 任务重试次数
|
||||
Hostname string `xorm:"varchar(128) notnull defalut '' "` // SSH主机名,逗号分隔
|
||||
Hostname string `xorm:"varchar(128) notnull defalut '' "` // RPC主机名,逗号分隔
|
||||
StartTime time.Time `xorm:"datetime created"` // 开始执行时间
|
||||
EndTime time.Time `xorm:"datetime updated"` // 执行完成(失败)时间
|
||||
Status Status `xorm:"tinyint notnull default 1"` // 状态 0:执行失败 1:执行中 2:执行完毕 3:任务取消(上次任务未执行完成) 4:异步执行
|
||||
NotifyId string `xorm:"varchar(32) notnull default '' "` // 回调通知ID
|
||||
Status Status `xorm:"tinyint notnull index default 1"` // 状态 0:执行失败 1:执行中 2:执行完毕 3:任务取消(上次任务未执行完成) 4:异步执行
|
||||
Result string `xorm:"mediumtext notnull defalut '' "` // 执行结果
|
||||
TotalTime int `xorm:"-"` // 执行总时长
|
||||
BaseModel `xorm:"-"`
|
||||
@@ -42,16 +41,6 @@ func (taskLog *TaskLog) Update(id int64, data CommonMap) (int64, error) {
|
||||
return Db.Table(taskLog).ID(id).Update(data)
|
||||
}
|
||||
|
||||
func (taskLog *TaskLog) UpdateStatus(notifyId string, status Status, result string) (int64, error) {
|
||||
taskLog.Status = status
|
||||
taskLog.Result = result
|
||||
return Db.Cols("status,result").Where("notify_id = ?", notifyId).Update(taskLog)
|
||||
}
|
||||
|
||||
func (taskLog *TaskLog) setStatus(id int64, status Status) (int64, error) {
|
||||
return taskLog.Update(id, CommonMap{"status": status})
|
||||
}
|
||||
|
||||
func (taskLog *TaskLog) List(params CommonMap) ([]TaskLog, error) {
|
||||
taskLog.parsePageAndPageSize(params)
|
||||
list := make([]TaskLog, 0)
|
||||
@@ -72,17 +61,6 @@ func (taskLog *TaskLog) List(params CommonMap) ([]TaskLog, error) {
|
||||
return list, err
|
||||
}
|
||||
|
||||
// 根据通知ID获取任务ID
|
||||
func (taskLog *TaskLog) GetTaskIdByNotifyId(notifyId string) (taskId int, err error) {
|
||||
exist, err := Db.Where("notify_id = ?", notifyId).Get(taskLog)
|
||||
if !exist || err != nil {
|
||||
return
|
||||
}
|
||||
taskId = taskLog.TaskId
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// 清空表
|
||||
func (taskLog *TaskLog) Clear() (int64, error) {
|
||||
return Db.Where("1=1").Delete(taskLog);
|
||||
@@ -96,6 +74,7 @@ func (taskLog *TaskLog) Remove(id int) (int64, error) {
|
||||
|
||||
func (taskLog *TaskLog) Total(params CommonMap) (int64, error) {
|
||||
session := Db.NewSession()
|
||||
defer session.Close()
|
||||
taskLog.parseWhere(session, params)
|
||||
return session.Count(taskLog)
|
||||
}
|
||||
|
||||
+56
-4
@@ -4,9 +4,11 @@ import (
|
||||
"os"
|
||||
|
||||
"github.com/ouqiang/gocron/modules/logger"
|
||||
"runtime"
|
||||
"github.com/ouqiang/gocron/modules/utils"
|
||||
"gopkg.in/ini.v1"
|
||||
"io/ioutil"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -17,10 +19,12 @@ var (
|
||||
AppConfig string // 应用配置文件
|
||||
Installed bool // 应用是否安装过
|
||||
Setting *ini.Section // 应用配置
|
||||
VersionId int // 版本号
|
||||
VersionFile string // 版本号文件
|
||||
)
|
||||
|
||||
func InitEnv() {
|
||||
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||
|
||||
func InitEnv(versionString string) {
|
||||
logger.InitLogger()
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
@@ -31,11 +35,13 @@ func InitEnv() {
|
||||
LogDir = AppDir + "/log"
|
||||
DataDir = AppDir + "/data"
|
||||
AppConfig = ConfDir + "/app.ini"
|
||||
VersionFile = ConfDir + "/.version"
|
||||
checkDirExists(ConfDir, LogDir, DataDir)
|
||||
Installed = IsInstalled()
|
||||
VersionId = ToNumberVersion(versionString)
|
||||
}
|
||||
|
||||
// 判断应用是否安装过
|
||||
// 判断应用是否已安装
|
||||
func IsInstalled() bool {
|
||||
_, err := os.Stat(ConfDir + "/install.lock")
|
||||
if os.IsNotExist(err) {
|
||||
@@ -55,6 +61,52 @@ func CreateInstallLock() error {
|
||||
return err
|
||||
}
|
||||
|
||||
// 更新应用版本号文件
|
||||
func UpdateVersionFile() {
|
||||
err := ioutil.WriteFile(VersionFile,
|
||||
[]byte(strconv.Itoa(VersionId)),
|
||||
0644,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
logger.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// 获取应用当前版本号, 从版本号文件中读取
|
||||
func GetCurrentVersionId() int {
|
||||
if !utils.FileExist(VersionFile) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
bytes, err := ioutil.ReadFile(VersionFile)
|
||||
if err != nil {
|
||||
logger.Fatal(err)
|
||||
}
|
||||
|
||||
versionId, err := strconv.Atoi(strings.TrimSpace(string(bytes)))
|
||||
if err != nil {
|
||||
logger.Fatal(err)
|
||||
}
|
||||
|
||||
return versionId
|
||||
}
|
||||
|
||||
// 把字符串版本号a.b.c转换为整数版本号abc
|
||||
func ToNumberVersion(versionString string) int {
|
||||
v := strings.Replace(versionString, ".", "", -1)
|
||||
if len(v) < 3 {
|
||||
v += "0"
|
||||
}
|
||||
|
||||
versionId, err := strconv.Atoi(v)
|
||||
if err != nil {
|
||||
logger.Fatal(err)
|
||||
}
|
||||
|
||||
return versionId
|
||||
}
|
||||
|
||||
// 检测目录是否存在
|
||||
func checkDirExists(path ...string) {
|
||||
for _, value := range path {
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
pb "github.com/ouqiang/gocron/modules/rpc/proto"
|
||||
"golang.org/x/net/context"
|
||||
"fmt"
|
||||
"time"
|
||||
"errors"
|
||||
"github.com/ouqiang/gocron/modules/rpc/grpcpool"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc"
|
||||
"github.com/ouqiang/gocron/modules/logger"
|
||||
)
|
||||
|
||||
var (
|
||||
errUnavailable = errors.New("无法连接远程服务器")
|
||||
)
|
||||
|
||||
func ExecWithRetry(ip string, port int, certFile string, token string, taskReq *pb.TaskRequest) (string, error) {
|
||||
tryTimes := 15
|
||||
i := 0
|
||||
for i < tryTimes {
|
||||
output, err := Exec(ip, port, certFile, token, taskReq)
|
||||
if err != errUnavailable {
|
||||
return output, err
|
||||
}
|
||||
i++
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
|
||||
return "", errUnavailable
|
||||
}
|
||||
|
||||
func Exec(ip string, port int, certFile string, token string, taskReq *pb.TaskRequest) (string, error) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
logger.Error("panic#rpc/client.go:Exec#", err)
|
||||
}
|
||||
} ()
|
||||
addr := fmt.Sprintf("%s:%d", ip, port)
|
||||
conn, err := grpcpool.Pool.Get(addr, certFile, token)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
isConnClosed := false
|
||||
defer func() {
|
||||
if !isConnClosed {
|
||||
grpcpool.Pool.Put(addr, conn)
|
||||
}
|
||||
}()
|
||||
c := pb.NewTaskClient(conn)
|
||||
if taskReq.Timeout <= 0 || taskReq.Timeout > 86400 {
|
||||
taskReq.Timeout = 86400
|
||||
}
|
||||
timeout := time.Duration(taskReq.Timeout) * time.Second
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
resp, err := c.Run(ctx, taskReq)
|
||||
if err != nil {
|
||||
return parseGRPCError(err, conn, &isConnClosed)
|
||||
}
|
||||
|
||||
if resp.Error == "" {
|
||||
return resp.Output, nil
|
||||
}
|
||||
|
||||
return resp.Output, errors.New(resp.Error)
|
||||
}
|
||||
|
||||
func parseGRPCError(err error, conn *grpc.ClientConn, connClosed *bool) (string, error) {
|
||||
switch grpc.Code(err) {
|
||||
case codes.Unavailable, codes.Internal:
|
||||
conn.Close()
|
||||
*connClosed = true
|
||||
return "", errUnavailable
|
||||
case codes.DeadlineExceeded:
|
||||
return "", errors.New("执行超时, 强制结束")
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
package grpcpool
|
||||
|
||||
import (
|
||||
"github.com/silenceper/pool"
|
||||
"sync"
|
||||
"time"
|
||||
"google.golang.org/grpc"
|
||||
"errors"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"golang.org/x/net/context"
|
||||
"strings"
|
||||
)
|
||||
|
||||
|
||||
var (
|
||||
Pool GRPCPool
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalidConn = errors.New("invalid connection")
|
||||
)
|
||||
|
||||
func init() {
|
||||
Pool = GRPCPool{
|
||||
make(map[string]pool.Pool),
|
||||
sync.RWMutex{},
|
||||
}
|
||||
}
|
||||
|
||||
type GRPCPool struct {
|
||||
// map key格式 ip:port
|
||||
conns map[string]pool.Pool
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
func (p *GRPCPool) Get(addr, certFile, token string) (*grpc.ClientConn, error) {
|
||||
p.RLock()
|
||||
pool, ok := p.conns[addr]
|
||||
p.RUnlock()
|
||||
if !ok {
|
||||
err := p.newCommonPool(addr, certFile, token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
p.RLock()
|
||||
pool = p.conns[addr]
|
||||
p.RUnlock()
|
||||
conn, err := pool.Get()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return conn.(*grpc.ClientConn), nil
|
||||
}
|
||||
|
||||
func (p *GRPCPool) Put(addr string, conn *grpc.ClientConn) error {
|
||||
p.RLock()
|
||||
defer p.RUnlock()
|
||||
pool, ok := p.conns[addr]
|
||||
if ok {
|
||||
return pool.Put(conn)
|
||||
}
|
||||
|
||||
return ErrInvalidConn
|
||||
}
|
||||
|
||||
|
||||
// 释放连接池
|
||||
func (p *GRPCPool) Release(addr string) {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
pool, ok := p.conns[addr]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
pool.Release()
|
||||
delete(p.conns, addr)
|
||||
}
|
||||
|
||||
// 释放所有连接池
|
||||
func (p *GRPCPool) ReleaseAll() {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
for _, pool := range(p.conns) {
|
||||
pool.Release()
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化底层连接池
|
||||
func (p *GRPCPool) newCommonPool(addr, certFile, token string) (error) {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
commonPool, ok := p.conns[addr]
|
||||
if ok {
|
||||
return nil
|
||||
}
|
||||
poolConfig := &pool.PoolConfig{
|
||||
InitialCap: 1,
|
||||
MaxCap: 30,
|
||||
Factory: func() (interface{}, error) {
|
||||
if certFile == "" {
|
||||
return grpc.Dial(addr, grpc.WithInsecure())
|
||||
}
|
||||
|
||||
server := strings.Split(addr, ":")
|
||||
creds, err := credentials.NewClientTLSFromFile(certFile, server[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
customCredential := &CustomCredential{Token: token}
|
||||
|
||||
|
||||
return grpc.Dial(addr,
|
||||
grpc.WithTransportCredentials(creds),
|
||||
grpc.WithPerRPCCredentials(customCredential),
|
||||
)
|
||||
},
|
||||
Close: func(v interface{}) error {
|
||||
conn, ok := v.(*grpc.ClientConn)
|
||||
if ok && conn != nil {
|
||||
return conn.Close()
|
||||
}
|
||||
return ErrInvalidConn
|
||||
},
|
||||
IdleTimeout: 3 * time.Minute,
|
||||
}
|
||||
|
||||
commonPool, err := pool.NewChannelPool(poolConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.conns[addr] = commonPool
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type CustomCredential struct
|
||||
{
|
||||
Token string
|
||||
}
|
||||
|
||||
func (c CustomCredential) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
|
||||
return map[string]string{
|
||||
"token": c.Token,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c CustomCredential) RequireTransportSecurity() bool {
|
||||
return true
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// source: task.proto
|
||||
|
||||
/*
|
||||
Package rpc is a generated protocol buffer package.
|
||||
|
||||
It is generated from these files:
|
||||
task.proto
|
||||
|
||||
It has these top-level messages:
|
||||
TaskRequest
|
||||
TaskResponse
|
||||
*/
|
||||
package rpc
|
||||
|
||||
import proto "github.com/golang/protobuf/proto"
|
||||
import fmt "fmt"
|
||||
import math "math"
|
||||
|
||||
import (
|
||||
context "golang.org/x/net/context"
|
||||
grpc "google.golang.org/grpc"
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
|
||||
|
||||
type TaskRequest struct {
|
||||
Command string `protobuf:"bytes,2,opt,name=command" json:"command,omitempty"`
|
||||
Timeout int32 `protobuf:"varint,3,opt,name=timeout" json:"timeout,omitempty"`
|
||||
}
|
||||
|
||||
func (m *TaskRequest) Reset() { *m = TaskRequest{} }
|
||||
func (m *TaskRequest) String() string { return proto.CompactTextString(m) }
|
||||
func (*TaskRequest) ProtoMessage() {}
|
||||
func (*TaskRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
|
||||
|
||||
func (m *TaskRequest) GetCommand() string {
|
||||
if m != nil {
|
||||
return m.Command
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *TaskRequest) GetTimeout() int32 {
|
||||
if m != nil {
|
||||
return m.Timeout
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type TaskResponse struct {
|
||||
Output string `protobuf:"bytes,1,opt,name=output" json:"output,omitempty"`
|
||||
Error string `protobuf:"bytes,2,opt,name=error" json:"error,omitempty"`
|
||||
}
|
||||
|
||||
func (m *TaskResponse) Reset() { *m = TaskResponse{} }
|
||||
func (m *TaskResponse) String() string { return proto.CompactTextString(m) }
|
||||
func (*TaskResponse) ProtoMessage() {}
|
||||
func (*TaskResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }
|
||||
|
||||
func (m *TaskResponse) GetOutput() string {
|
||||
if m != nil {
|
||||
return m.Output
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *TaskResponse) GetError() string {
|
||||
if m != nil {
|
||||
return m.Error
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*TaskRequest)(nil), "rpc.TaskRequest")
|
||||
proto.RegisterType((*TaskResponse)(nil), "rpc.TaskResponse")
|
||||
}
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ context.Context
|
||||
var _ grpc.ClientConn
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
const _ = grpc.SupportPackageIsVersion4
|
||||
|
||||
// Client API for Task service
|
||||
|
||||
type TaskClient interface {
|
||||
Run(ctx context.Context, in *TaskRequest, opts ...grpc.CallOption) (*TaskResponse, error)
|
||||
}
|
||||
|
||||
type taskClient struct {
|
||||
cc *grpc.ClientConn
|
||||
}
|
||||
|
||||
func NewTaskClient(cc *grpc.ClientConn) TaskClient {
|
||||
return &taskClient{cc}
|
||||
}
|
||||
|
||||
func (c *taskClient) Run(ctx context.Context, in *TaskRequest, opts ...grpc.CallOption) (*TaskResponse, error) {
|
||||
out := new(TaskResponse)
|
||||
err := grpc.Invoke(ctx, "/rpc.Task/Run", in, out, c.cc, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Server API for Task service
|
||||
|
||||
type TaskServer interface {
|
||||
Run(context.Context, *TaskRequest) (*TaskResponse, error)
|
||||
}
|
||||
|
||||
func RegisterTaskServer(s *grpc.Server, srv TaskServer) {
|
||||
s.RegisterService(&_Task_serviceDesc, srv)
|
||||
}
|
||||
|
||||
func _Task_Run_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(TaskRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(TaskServer).Run(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/rpc.Task/Run",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(TaskServer).Run(ctx, req.(*TaskRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
var _Task_serviceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "rpc.Task",
|
||||
HandlerType: (*TaskServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "Run",
|
||||
Handler: _Task_Run_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "task.proto",
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("task.proto", fileDescriptor0) }
|
||||
|
||||
var fileDescriptor0 = []byte{
|
||||
// 170 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x4c, 0x8f, 0xb1, 0x0e, 0x82, 0x40,
|
||||
0x0c, 0x86, 0x45, 0x04, 0x63, 0x75, 0xd0, 0xc6, 0x98, 0x8b, 0x13, 0x61, 0x62, 0x30, 0x0c, 0xea,
|
||||
0xe8, 0xe2, 0x2b, 0x5c, 0x7c, 0x01, 0xc4, 0x1b, 0x0c, 0x81, 0x9e, 0xbd, 0xde, 0xfb, 0x1b, 0x38,
|
||||
0x48, 0x18, 0xbf, 0x36, 0xfd, 0xfa, 0xff, 0x00, 0x52, 0xb9, 0xa6, 0xb4, 0x4c, 0x42, 0x18, 0xb3,
|
||||
0xad, 0xf3, 0x27, 0x6c, 0x5f, 0x95, 0x6b, 0xb4, 0xf9, 0x79, 0xe3, 0x04, 0x15, 0xac, 0x6b, 0x6a,
|
||||
0xdb, 0xaa, 0xfb, 0xa8, 0x65, 0x16, 0x15, 0x1b, 0x3d, 0x61, 0xbf, 0x91, 0x6f, 0x6b, 0xc8, 0x8b,
|
||||
0x8a, 0xb3, 0xa8, 0x48, 0xf4, 0x84, 0xf9, 0x03, 0x76, 0x41, 0xe1, 0x2c, 0x75, 0xce, 0xe0, 0x09,
|
||||
0x52, 0xf2, 0x62, 0xbd, 0xa8, 0x68, 0x50, 0x8c, 0x84, 0x47, 0x48, 0x0c, 0x33, 0xf1, 0x68, 0x0e,
|
||||
0x70, 0xbd, 0xc3, 0xaa, 0xbf, 0xc6, 0x0b, 0xc4, 0xda, 0x77, 0xb8, 0x2f, 0xd9, 0xd6, 0xe5, 0x2c,
|
||||
0xd2, 0xf9, 0x30, 0x9b, 0x84, 0x0f, 0xf9, 0xe2, 0x9d, 0x0e, 0x15, 0x6e, 0xff, 0x00, 0x00, 0x00,
|
||||
0xff, 0xff, 0xef, 0x3c, 0x71, 0x6b, 0xd0, 0x00, 0x00, 0x00,
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package rpc;
|
||||
|
||||
service Task {
|
||||
rpc Run(TaskRequest) returns (TaskResponse) {}
|
||||
}
|
||||
|
||||
message TaskRequest {
|
||||
string command = 2; // 命令
|
||||
int32 timeout = 3; // 任务执行超时时间
|
||||
}
|
||||
|
||||
message TaskResponse {
|
||||
string output = 1; // 命令标准输出
|
||||
string error = 2; // 命令错误
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"golang.org/x/net/context"
|
||||
"net"
|
||||
"google.golang.org/grpc/grpclog"
|
||||
"google.golang.org/grpc"
|
||||
pb "github.com/ouqiang/gocron/modules/rpc/proto"
|
||||
"github.com/ouqiang/gocron/modules/utils"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"errors"
|
||||
)
|
||||
|
||||
type Server struct
|
||||
{
|
||||
Token string
|
||||
}
|
||||
|
||||
func (s Server) auth(ctx context.Context) error {
|
||||
// 验证token是否有效
|
||||
meta, ok := metadata.FromContext(ctx)
|
||||
if !ok {
|
||||
return errors.New("missing metadata")
|
||||
}
|
||||
|
||||
token, ok := meta["token"]
|
||||
if !ok {
|
||||
return errors.New("missing param token")
|
||||
}
|
||||
if token[0] != s.Token {
|
||||
return errors.New("invalid token")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s Server) Run(ctx context.Context, req *pb.TaskRequest) (*pb.TaskResponse, error) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
grpclog.Println(err)
|
||||
}
|
||||
} ()
|
||||
|
||||
|
||||
if s.Token != "" {
|
||||
err := s.auth(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
output, err := utils.ExecShell(ctx, req.Command)
|
||||
resp := new(pb.TaskResponse)
|
||||
resp.Output = output
|
||||
if err != nil {
|
||||
resp.Error = err.Error()
|
||||
} else {
|
||||
resp.Error = ""
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func Start(addr, certFile, keyFile, token string) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
grpclog.Println("panic", err)
|
||||
}
|
||||
} ()
|
||||
l, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
grpclog.Fatal(err)
|
||||
}
|
||||
|
||||
var s *grpc.Server
|
||||
server := Server{Token: token}
|
||||
if certFile != "" {
|
||||
// TLS认证
|
||||
creds, err := credentials.NewServerTLSFromFile(certFile, keyFile)
|
||||
if err != nil {
|
||||
grpclog.Fatalf("Failed to generate credentials %v", err)
|
||||
}
|
||||
|
||||
s = grpc.NewServer(grpc.Creds(creds))
|
||||
pb.RegisterTaskServer(s, server)
|
||||
grpclog.Printf("listen %s with TLS", addr)
|
||||
} else {
|
||||
s = grpc.NewServer()
|
||||
pb.RegisterTaskServer(s, server)
|
||||
grpclog.Println("listen ", addr)
|
||||
}
|
||||
err = s.Serve(l)
|
||||
if err != nil {
|
||||
grpclog.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,10 +19,13 @@ func Read(filename string) (*ini.Section,error) {
|
||||
}
|
||||
|
||||
// 写入配置
|
||||
func Write(config map[string]string, filename string) error {
|
||||
func Write(config []string, filename string) error {
|
||||
if len(config) == 0 {
|
||||
return errors.New("参数不能为空")
|
||||
}
|
||||
if len(config) % 2 != 0 {
|
||||
return errors.New("参数不匹配")
|
||||
}
|
||||
|
||||
file := ini.Empty()
|
||||
|
||||
@@ -30,14 +33,12 @@ func Write(config map[string]string, filename string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for key, value := range config {
|
||||
if key == "" {
|
||||
continue
|
||||
}
|
||||
_, err = section.NewKey(key, value)
|
||||
for i := 0 ;i < len(config); {
|
||||
_, err = section.NewKey(config[i], config[i+1])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
i += 2
|
||||
}
|
||||
err = file.SaveTo(filename)
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ const ResponseFailure = 1
|
||||
const NotFound = 2
|
||||
const AuthError = 3
|
||||
const ServerError = 4
|
||||
const CaptchaError = 5
|
||||
|
||||
const SuccessContent = "操作成功"
|
||||
const FailureContent = "操作失败"
|
||||
|
||||
+21
-28
@@ -5,40 +5,33 @@ package utils
|
||||
import (
|
||||
"os/exec"
|
||||
"syscall"
|
||||
"time"
|
||||
"fmt"
|
||||
"golang.org/x/net/context"
|
||||
"errors"
|
||||
)
|
||||
|
||||
type Result struct {
|
||||
output string
|
||||
err error
|
||||
}
|
||||
|
||||
// 执行shell命令,可设置执行超时时间
|
||||
func ExecShellWithTimeout(timeout int, command string, args... string) (string, error) {
|
||||
cmd := exec.Command(command, args...)
|
||||
func ExecShell(ctx context.Context, command string) (string, error) {
|
||||
cmd := exec.Command("/bin/bash", "-c", command)
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||
Setpgid: true,
|
||||
}
|
||||
|
||||
// 后台运行
|
||||
if timeout == -1 {
|
||||
go cmd.CombinedOutput()
|
||||
return "", nil
|
||||
}
|
||||
// 不限制超时
|
||||
if timeout == 0 {
|
||||
var resultChan chan Result = make(chan Result)
|
||||
go func() {
|
||||
output ,err := cmd.CombinedOutput()
|
||||
return string(output), err
|
||||
resultChan <- Result{string(output), err}
|
||||
}()
|
||||
select {
|
||||
case <- ctx.Done():
|
||||
if cmd.Process.Pid > 0 {
|
||||
syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
|
||||
}
|
||||
return "", errors.New("timeout killed")
|
||||
case result := <- resultChan:
|
||||
return result.output, result.err
|
||||
}
|
||||
|
||||
d := time.Duration(timeout) * time.Second
|
||||
timer := time.AfterFunc(d, func() {
|
||||
// 超时kill进程
|
||||
syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
|
||||
})
|
||||
output ,err := cmd.CombinedOutput()
|
||||
timer.Stop()
|
||||
|
||||
return string(output), err
|
||||
}
|
||||
|
||||
// 格式化环境变量
|
||||
func FormatEnv(key, value string) string {
|
||||
return fmt.Sprintf("export %s=%s;", key, value)
|
||||
}
|
||||
@@ -4,41 +4,50 @@ package utils
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"time"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"fmt"
|
||||
"golang.org/x/net/context"
|
||||
"errors"
|
||||
)
|
||||
|
||||
// 执行shell命令,可设置执行超时时间
|
||||
func ExecShellWithTimeout(timeout int, command string, args... string) (string, error) {
|
||||
cmd := exec.Command(command, args...)
|
||||
// 隐藏cmd窗口
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
|
||||
// 后台运行
|
||||
if timeout == -1 {
|
||||
go cmd.CombinedOutput()
|
||||
return "", nil
|
||||
}
|
||||
// 不限制超时
|
||||
if timeout <= 0 {
|
||||
output ,err := cmd.CombinedOutput()
|
||||
return string(output), err
|
||||
}
|
||||
|
||||
d := time.Duration(timeout) * time.Second
|
||||
timer := time.AfterFunc(d, func() {
|
||||
// 超时kill进程
|
||||
exec.Command("taskkill", "/F", "/T", "/PID", strconv.Itoa(cmd.Process.Pid)).Run()
|
||||
cmd.Process.Kill()
|
||||
})
|
||||
output ,err := cmd.CombinedOutput()
|
||||
timer.Stop()
|
||||
|
||||
return string(output), err
|
||||
type Result struct {
|
||||
output string
|
||||
err error
|
||||
}
|
||||
|
||||
// 格式化环境变量
|
||||
func FormatEnv(key, value string) string {
|
||||
return fmt.Sprintf("set %s=%s & ", key, value)
|
||||
// 执行shell命令,可设置执行超时时间
|
||||
func ExecShell(ctx context.Context, command string) (string, error) {
|
||||
cmd := exec.Command("cmd", "/C", command)
|
||||
// 隐藏cmd窗口
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||
HideWindow: true,
|
||||
}
|
||||
var resultChan chan Result = make(chan Result)
|
||||
go func() {
|
||||
output ,err := cmd.CombinedOutput()
|
||||
resultChan <- Result{string(output), err}
|
||||
}()
|
||||
select {
|
||||
case <- ctx.Done():
|
||||
if cmd.Process.Pid > 0 {
|
||||
exec.Command("taskkill", "/F", "/T", "/PID", strconv.Itoa(cmd.Process.Pid)).Run()
|
||||
cmd.Process.Kill()
|
||||
}
|
||||
return "", errors.New("timeout killed")
|
||||
case result := <- resultChan:
|
||||
return ConvertEncoding(result.output), result.err
|
||||
}
|
||||
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func ConvertEncoding(outputGBK string) (string) {
|
||||
// windows平台编码为gbk,需转换为utf8才能入库
|
||||
outputUTF8, ok := GBK2UTF8(outputGBK)
|
||||
if ok {
|
||||
return outputUTF8
|
||||
}
|
||||
|
||||
return "命令输出转换编码失败(gbk to utf8)"
|
||||
}
|
||||
@@ -10,13 +10,16 @@ function Util() {
|
||||
swal("操作成功", '保存成功', 'success');
|
||||
};
|
||||
// ajax成功处理
|
||||
util.ajaxSuccess = function(response, callback) {
|
||||
util.ajaxSuccess = function(response, callback, failureCallback) {
|
||||
if (response.code === undefined) {
|
||||
swal(FAILURE_MESSAGE, '服务端返回值无法解析', 'error');
|
||||
return;
|
||||
}
|
||||
if (response.code != SUCCESS) {
|
||||
swal(FAILURE_MESSAGE, response.message ,'error');
|
||||
if (failureCallback !== undefined) {
|
||||
failureCallback(response.code, response.message);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (callback !== undefined) {
|
||||
@@ -26,7 +29,7 @@ function Util() {
|
||||
// ajax错误处理
|
||||
util.ajaxFailure = function() {
|
||||
// todo 错误处理
|
||||
swal(FAILURE_MESSAGE, '未知错误', 'error');
|
||||
swal(FAILURE_MESSAGE, '操作失败', 'error');
|
||||
};
|
||||
// get请求
|
||||
util.get = function(url, callback) {
|
||||
@@ -39,12 +42,12 @@ function Util() {
|
||||
).error(util.ajaxFailure);
|
||||
};
|
||||
// post请求
|
||||
util.post = function(url, params, callback) {
|
||||
util.post = function(url, params, callback, failureCallback) {
|
||||
$.post(
|
||||
url,
|
||||
util.objectTrim(params),
|
||||
function(response) {
|
||||
util.ajaxSuccess(response, callback);
|
||||
util.ajaxSuccess(response, callback, failureCallback);
|
||||
},
|
||||
'json'
|
||||
).error(util.ajaxFailure);
|
||||
@@ -57,7 +60,7 @@ function Util() {
|
||||
type: 'warning',
|
||||
showCancelButton: true,
|
||||
confirmButtonColor: '#3085d6',
|
||||
confirmButtonText: '删除',
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonColor: '#d33',
|
||||
cancelButtonText: "取消",
|
||||
closeOnConfirm: false,
|
||||
|
||||
@@ -1,110 +0,0 @@
|
||||
package delaytask
|
||||
|
||||
import (
|
||||
"gopkg.in/macaron.v1"
|
||||
"github.com/ouqiang/gocron/models"
|
||||
"github.com/ouqiang/gocron/modules/utils"
|
||||
"strings"
|
||||
"github.com/ouqiang/gocron/service"
|
||||
"github.com/ouqiang/gocron/modules/logger"
|
||||
"github.com/Unknwon/paginater"
|
||||
"fmt"
|
||||
"github.com/ouqiang/gocron/routers/base"
|
||||
"html/template"
|
||||
"github.com/ouqiang/gocron/modules/app"
|
||||
)
|
||||
|
||||
func Index(ctx *macaron.Context) {
|
||||
delayTaskModel := new(models.DelayTask)
|
||||
queryParams := parseQueryParams(ctx)
|
||||
total, err := delayTaskModel.Total(queryParams)
|
||||
tasks, err := delayTaskModel.List(queryParams)
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
}
|
||||
PageParams := fmt.Sprintf("status=%d&page_size=%d",
|
||||
queryParams["Status"], queryParams["PageSize"]);
|
||||
queryParams["PageParams"] = template.URL(PageParams)
|
||||
p := paginater.New(int(total), queryParams["PageSize"].(int), queryParams["Page"].(int), 5)
|
||||
ctx.Data["Pagination"] = p
|
||||
ctx.Data["Title"] = "延时任务列表"
|
||||
ctx.Data["Tasks"] = tasks
|
||||
ctx.Data["Params"] = queryParams
|
||||
ctx.HTML(200, "task/delay_task")
|
||||
}
|
||||
|
||||
func Create(ctx *macaron.Context) string {
|
||||
url := ctx.QueryTrim("url")
|
||||
params := ctx.QueryTrim("params")
|
||||
delay := ctx.QueryInt("delay")
|
||||
json := utils.JsonResponse{}
|
||||
delayTaskEnabled, _ := app.Setting.Key("delay.task.enable").Bool()
|
||||
if !delayTaskEnabled {
|
||||
return json.CommonFailure("系统未开启延时任务")
|
||||
}
|
||||
if url == "" {
|
||||
return json.CommonFailure("url地址不能为空")
|
||||
}
|
||||
lowerUrl := strings.ToLower(url)
|
||||
if !strings.HasPrefix(lowerUrl, "http") &&
|
||||
!strings.HasPrefix(lowerUrl, "https") {
|
||||
return json.CommonFailure("无效的url地址")
|
||||
}
|
||||
if len(url) > 128 {
|
||||
return json.CommonFailure("url长度不能超过128")
|
||||
}
|
||||
maxDelay := 1 << 31
|
||||
if delay <= 0 || delay > maxDelay {
|
||||
return json.CommonFailure("无效的delay, 取值范围1-(2^31-1)")
|
||||
}
|
||||
if len(params) > 256 {
|
||||
return json.CommonFailure("params长度不能超过256")
|
||||
}
|
||||
|
||||
delayTask := new(models.DelayTask)
|
||||
delayTask.Url = url
|
||||
delayTask.Params = params
|
||||
delayTask.Delay = delay
|
||||
delayTask.Status = models.Waiting
|
||||
_, err := delayTask.Create()
|
||||
|
||||
if err != nil {
|
||||
return json.CommonFailure("添加失败", err)
|
||||
}
|
||||
|
||||
logger.Infof("新增延时任务#id-%d#url-%s#params-%s#delay-%d",
|
||||
delayTask.Id, delayTask.Url, delayTask.Params, delayTask.Delay)
|
||||
delayTaskService := new(service.DelayTask)
|
||||
delayTaskService.Add(*delayTask)
|
||||
|
||||
return json.Success("添加成功", nil)
|
||||
}
|
||||
|
||||
// 删除N个月前的日志
|
||||
func Remove(ctx *macaron.Context) string {
|
||||
month := ctx.ParamsInt(":id")
|
||||
json := utils.JsonResponse{}
|
||||
if month < 1 || month > 12 {
|
||||
return json.CommonFailure("参数取值范围1-12")
|
||||
}
|
||||
delayTaskModel := new(models.DelayTask)
|
||||
_, err := delayTaskModel.Remove(month)
|
||||
if err != nil {
|
||||
return json.CommonFailure("删除失败", err)
|
||||
}
|
||||
|
||||
return json.Success("删除成功", nil)
|
||||
}
|
||||
|
||||
// 解析查询参数
|
||||
func parseQueryParams(ctx *macaron.Context) (models.CommonMap) {
|
||||
var params models.CommonMap = models.CommonMap{}
|
||||
status := ctx.QueryInt("status")
|
||||
if status >=0 {
|
||||
status -= 1
|
||||
}
|
||||
params["Status"] = status
|
||||
base.ParsePageAndPageSize(ctx, params)
|
||||
|
||||
return params
|
||||
}
|
||||
+68
-55
@@ -6,13 +6,16 @@ import (
|
||||
"github.com/ouqiang/gocron/modules/utils"
|
||||
"github.com/ouqiang/gocron/modules/logger"
|
||||
"strconv"
|
||||
"github.com/ouqiang/gocron/modules/ssh"
|
||||
"github.com/ouqiang/gocron/service"
|
||||
"github.com/Unknwon/paginater"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"github.com/ouqiang/gocron/routers/base"
|
||||
"github.com/go-macaron/binding"
|
||||
"github.com/ouqiang/gocron/modules/rpc/grpcpool"
|
||||
"strings"
|
||||
"github.com/ouqiang/gocron/modules/rpc/client"
|
||||
"github.com/ouqiang/gocron/modules/rpc/proto"
|
||||
)
|
||||
|
||||
func Index(ctx *macaron.Context) {
|
||||
@@ -56,52 +59,13 @@ func Edit(ctx *macaron.Context) {
|
||||
ctx.HTML(200, "host/host_form")
|
||||
}
|
||||
|
||||
func Ping(ctx *macaron.Context) string {
|
||||
id := ctx.ParamsInt(":id")
|
||||
hostModel := new(models.Host)
|
||||
err := hostModel.Find(id)
|
||||
json := utils.JsonResponse{}
|
||||
if err != nil || hostModel.Id <= 0{
|
||||
return json.CommonFailure("主机不存在", err)
|
||||
}
|
||||
|
||||
sshConfig := ssh.SSHConfig{}
|
||||
sshConfig.User = hostModel.Username
|
||||
sshConfig.Host = hostModel.Name
|
||||
sshConfig.Port = hostModel.Port
|
||||
sshConfig.ExecTimeout = 5
|
||||
sshConfig.AuthType = hostModel.AuthType
|
||||
var password string
|
||||
var privateKey string
|
||||
if hostModel.AuthType == ssh.HostPassword {
|
||||
password, err = hostModel.GetPasswordByHost(hostModel.Name)
|
||||
if err != nil {
|
||||
return json.CommonFailure(err.Error(), err)
|
||||
}
|
||||
sshConfig.Password = password
|
||||
} else {
|
||||
privateKey, err = hostModel.GetPrivateKeyByHost(hostModel.Name)
|
||||
if err != nil {
|
||||
return json.CommonFailure(err.Error(), err)
|
||||
}
|
||||
sshConfig.PrivateKey = privateKey
|
||||
}
|
||||
|
||||
_, err = ssh.Exec(sshConfig, "pwd")
|
||||
if err != nil {
|
||||
return json.CommonFailure("连接失败-" + err.Error(), err)
|
||||
}
|
||||
|
||||
return json.Success("连接成功", nil)
|
||||
}
|
||||
|
||||
type HostForm struct {
|
||||
Id int16
|
||||
Name string `binding:"Required;MaxSize(64)"`
|
||||
Alias string `binding:"Required;MaxSize(32)"`
|
||||
Username string `binding:"Required;MaxSize(32)"`
|
||||
Port int `binding:"Required;Range(1-65535)"`
|
||||
AuthType ssh.HostAuthType `binding:"Required:Range(1,2)"`
|
||||
CertFile string
|
||||
Token string
|
||||
Remark string
|
||||
}
|
||||
|
||||
@@ -127,13 +91,25 @@ func Store(ctx *macaron.Context, form HostForm) string {
|
||||
return json.CommonFailure("主机名已存在")
|
||||
}
|
||||
|
||||
hostModel.Name = form.Name
|
||||
hostModel.Alias = form.Alias
|
||||
hostModel.Username = form.Username
|
||||
hostModel.Name = strings.TrimSpace(form.Name)
|
||||
hostModel.Alias = strings.TrimSpace(form.Alias)
|
||||
hostModel.Port = form.Port
|
||||
hostModel.Remark = form.Remark
|
||||
hostModel.AuthType = form.AuthType
|
||||
hostModel.Remark = strings.TrimSpace(form.Remark)
|
||||
hostModel.CertFile = strings.TrimSpace(form.CertFile)
|
||||
hostModel.Token = strings.TrimSpace(form.Token)
|
||||
|
||||
if hostModel.CertFile != "" && !utils.FileExist(hostModel.CertFile) {
|
||||
return json.CommonFailure("证书文件不存在或无权限访问")
|
||||
}
|
||||
|
||||
isCreate := false
|
||||
oldHostModel := new(models.Host)
|
||||
err = oldHostModel.Find(int(id))
|
||||
if err != nil {
|
||||
return json.CommonFailure("主机不存在")
|
||||
}
|
||||
|
||||
|
||||
if id > 0 {
|
||||
_, err = hostModel.UpdateBean(id)
|
||||
} else {
|
||||
@@ -144,12 +120,15 @@ func Store(ctx *macaron.Context, form HostForm) string {
|
||||
return json.CommonFailure("保存失败", err)
|
||||
}
|
||||
|
||||
taskModel := new(models.TaskHost)
|
||||
tasks, err := taskModel.ActiveListByHostId(id)
|
||||
if err != nil {
|
||||
return json.CommonFailure("刷新任务主机信息失败", err)
|
||||
}
|
||||
if !isCreate && len(tasks) > 0 {
|
||||
if !isCreate {
|
||||
oldAddr := fmt.Sprintf("%s:%d", oldHostModel.Name, oldHostModel.Port)
|
||||
grpcpool.Pool.Release(oldAddr)
|
||||
|
||||
taskModel := new(models.Task)
|
||||
tasks, err := taskModel.ActiveListByHostId(id)
|
||||
if err != nil {
|
||||
return json.CommonFailure("刷新任务主机信息失败", err)
|
||||
}
|
||||
serviceTask := new(service.Task)
|
||||
serviceTask.BatchAdd(tasks)
|
||||
}
|
||||
@@ -163,8 +142,8 @@ func Remove(ctx *macaron.Context) string {
|
||||
if err != nil {
|
||||
return json.CommonFailure("参数错误", err)
|
||||
}
|
||||
taskModel := new(models.Task)
|
||||
exist,err := taskModel.HostIdExist(int16(id))
|
||||
taskHostModel := new(models.TaskHost)
|
||||
exist,err := taskHostModel.HostIdExist(int16(id))
|
||||
if err != nil {
|
||||
return json.CommonFailure("操作失败", err)
|
||||
}
|
||||
@@ -173,14 +152,48 @@ func Remove(ctx *macaron.Context) string {
|
||||
}
|
||||
|
||||
hostModel := new(models.Host)
|
||||
err = hostModel.Find(int(id))
|
||||
if err != nil {
|
||||
return json.CommonFailure("主机不存在")
|
||||
}
|
||||
|
||||
_, err =hostModel.Delete(id)
|
||||
if err != nil {
|
||||
return json.CommonFailure("操作失败", err)
|
||||
}
|
||||
|
||||
addr := fmt.Sprintf("%s:%d", hostModel.Name, hostModel.Port)
|
||||
grpcpool.Pool.Release(addr)
|
||||
|
||||
|
||||
return json.Success("操作成功", nil)
|
||||
}
|
||||
|
||||
func Ping(ctx *macaron.Context) string {
|
||||
id := ctx.ParamsInt(":id")
|
||||
hostModel := new(models.Host)
|
||||
err := hostModel.Find(id)
|
||||
json := utils.JsonResponse{}
|
||||
if err != nil || hostModel.Id <= 0{
|
||||
return json.CommonFailure("主机不存在", err)
|
||||
}
|
||||
|
||||
|
||||
taskReq := &rpc.TaskRequest{}
|
||||
taskReq.Command = "echo hello"
|
||||
taskReq.Timeout = 10
|
||||
output, err := client.Exec(hostModel.Name,
|
||||
hostModel.Port,
|
||||
hostModel.CertFile,
|
||||
hostModel.Token,
|
||||
taskReq)
|
||||
if err != nil {
|
||||
return json.CommonFailure("连接失败-" + err.Error() + " " + output, err)
|
||||
}
|
||||
|
||||
return json.Success("连接成功", nil)
|
||||
}
|
||||
|
||||
// 解析查询参数
|
||||
func parseQueryParams(ctx *macaron.Context) (models.CommonMap) {
|
||||
var params models.CommonMap = models.CommonMap{}
|
||||
|
||||
+19
-15
@@ -75,7 +75,7 @@ func Store(ctx *macaron.Context, form InstallForm) string {
|
||||
models.Db = models.CreateDb()
|
||||
// 创建数据库表
|
||||
migration := new(models.Migration)
|
||||
err = migration.Exec(form.DbName)
|
||||
err = migration.Install(form.DbName)
|
||||
if err != nil {
|
||||
return json.CommonFailure(fmt.Sprintf("创建数据库表失败-%s", err.Error()), err)
|
||||
}
|
||||
@@ -92,6 +92,9 @@ func Store(ctx *macaron.Context, form InstallForm) string {
|
||||
return json.CommonFailure("创建文件安装锁失败", err)
|
||||
}
|
||||
|
||||
// 更新版本号文件
|
||||
app.UpdateVersionFile()
|
||||
|
||||
app.Installed = true
|
||||
// 初始化定时任务
|
||||
serviceTask := new(service.Task)
|
||||
@@ -102,20 +105,21 @@ func Store(ctx *macaron.Context, form InstallForm) string {
|
||||
|
||||
// 配置写入文件
|
||||
func writeConfig(form InstallForm) error {
|
||||
dbConfig := map[string]string{
|
||||
"db.engine": form.DbType,
|
||||
"db.host": form.DbHost,
|
||||
"db.port": strconv.Itoa(form.DbPort),
|
||||
"db.user": form.DbUsername,
|
||||
"db.password": form.DbPassword,
|
||||
"db.database": form.DbName,
|
||||
"db.prefix": form.DbTablePrefix,
|
||||
"db.charset": "utf8",
|
||||
"allow_ips" : "",
|
||||
"app.name": "定时任务管理系统", // 应用名称
|
||||
"delay.task.enable": "false", // 是否开启延时任务
|
||||
"delay.task.slots": "3600", // 时间轮槽数量
|
||||
"delay.task.tick": "1s", // 时间轮每次转动的时间
|
||||
dbConfig := []string{
|
||||
"db.engine", form.DbType,
|
||||
"db.host", form.DbHost,
|
||||
"db.port", strconv.Itoa(form.DbPort),
|
||||
"db.user", form.DbUsername,
|
||||
"db.password",form.DbPassword,
|
||||
"db.database", form.DbName,
|
||||
"db.prefix", form.DbTablePrefix,
|
||||
"db.charset", "utf8",
|
||||
"db.max.idle.conns", "30",
|
||||
"db.max.open.conns", "100",
|
||||
"allow_ips", "",
|
||||
"app.name", "定时任务管理系统", // 应用名称
|
||||
"api.key", "",
|
||||
"api.secret", "",
|
||||
}
|
||||
|
||||
return setting.Write(dbConfig, app.AppConfig)
|
||||
|
||||
+65
-13
@@ -17,7 +17,11 @@ import (
|
||||
"github.com/go-macaron/gzip"
|
||||
"github.com/ouqiang/gocron/routers/manage"
|
||||
"github.com/ouqiang/gocron/routers/loginlog"
|
||||
"github.com/ouqiang/gocron/routers/delaytask"
|
||||
"time"
|
||||
"strconv"
|
||||
"html/template"
|
||||
"github.com/go-macaron/cache"
|
||||
"github.com/go-macaron/captcha"
|
||||
)
|
||||
|
||||
// 静态文件目录
|
||||
@@ -58,18 +62,13 @@ func Register(m *macaron.Macaron) {
|
||||
m.Get("/run/:id", task.Run)
|
||||
})
|
||||
|
||||
// 延时任务
|
||||
m.Group("/delaytask", func() {
|
||||
m.Get("", delaytask.Index)
|
||||
})
|
||||
|
||||
// 主机
|
||||
m.Group("/host", func() {
|
||||
m.Get("/create", host.Create)
|
||||
m.Get("/edit/:id", host.Edit)
|
||||
m.Get("/ping/:id", host.Ping)
|
||||
m.Post("/store", binding.Bind(host.HostForm{}), host.Store)
|
||||
m.Get("", host.Index)
|
||||
m.Get("/ping/:id", host.Ping)
|
||||
m.Post("/remove/:id", host.Remove)
|
||||
})
|
||||
|
||||
@@ -95,11 +94,10 @@ func Register(m *macaron.Macaron) {
|
||||
|
||||
// API
|
||||
m.Group("/api/v1", func() {
|
||||
m.Route("/tasklog/update-status", "GET,POST", tasklog.UpdateStatus)
|
||||
m.Post("/tasklog/remove/:id", tasklog.Remove)
|
||||
m.Post("/delaytask/push", delaytask.Create)
|
||||
m.Post("/delaytask/log/remove/:id", delaytask.Remove)
|
||||
});
|
||||
m.Post("/task/enable/:id", task.Enable)
|
||||
m.Post("/task/disable/:id", task.Disable)
|
||||
}, apiAuth);
|
||||
|
||||
// 404错误
|
||||
m.NotFound(func(ctx *macaron.Context) {
|
||||
@@ -143,7 +141,17 @@ func RegisterMiddleware(m *macaron.Macaron) {
|
||||
IndentJSON: true,
|
||||
// 渲染具有缩进格式的 XML,默认为不缩进
|
||||
IndentXML: true,
|
||||
Funcs: []template.FuncMap{map[string]interface{} {
|
||||
"HostFormat": func(index int) bool {
|
||||
return (index + 1) % 3 == 0
|
||||
},
|
||||
"unescape": func(str string) template.HTML {
|
||||
return template.HTML(str)
|
||||
},
|
||||
}},
|
||||
}))
|
||||
m.Use(cache.Cacher())
|
||||
m.Use(captcha.Captchaer())
|
||||
m.Use(session.Sessioner(session.Options{
|
||||
Provider: "file",
|
||||
ProviderConfig: app.DataDir + "/sessions",
|
||||
@@ -207,8 +215,6 @@ func userAuth(ctx *macaron.Context, sess session.Store) {
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
/** 设置共享数据 **/
|
||||
func setShareData(ctx *macaron.Context, sess session.Store) {
|
||||
ctx.Data["URI"] = ctx.Req.URL.Path
|
||||
@@ -227,6 +233,52 @@ func setShareData(ctx *macaron.Context, sess session.Store) {
|
||||
ctx.Data["AppName"] = app.Setting.Key("app.name").String()
|
||||
}
|
||||
|
||||
/** API接口签名验证 **/
|
||||
func apiAuth(ctx *macaron.Context) {
|
||||
apiSignEnable := app.Setting.Key("api.sign.enable").String()
|
||||
apiSignEnable = strings.TrimSpace(apiSignEnable)
|
||||
if apiSignEnable == "false" {
|
||||
return
|
||||
}
|
||||
apiKey := app.Setting.Key("api.key").String()
|
||||
apiSecret := app.Setting.Key("api.secret").String()
|
||||
apiKey = strings.TrimSpace(apiKey)
|
||||
apiSecret = strings.TrimSpace(apiSecret)
|
||||
json := utils.JsonResponse{}
|
||||
if apiKey == "" || apiSecret == "" {
|
||||
msg := json.CommonFailure("使用API前, 请先配置密钥")
|
||||
ctx.Write([]byte(msg))
|
||||
return
|
||||
}
|
||||
currentTimestamp := time.Now().Unix()
|
||||
time := ctx.QueryInt64("time")
|
||||
if time <= 0 {
|
||||
msg := json.CommonFailure("参数time不能为空")
|
||||
ctx.Write([]byte(msg))
|
||||
return
|
||||
}
|
||||
if time < (currentTimestamp - 1800) {
|
||||
msg := json.CommonFailure("time无效")
|
||||
ctx.Write([]byte(msg))
|
||||
return
|
||||
}
|
||||
sign := ctx.QueryTrim("sign")
|
||||
if sign == "" {
|
||||
msg := json.CommonFailure("参数sign不能为空")
|
||||
ctx.Write([]byte(msg))
|
||||
return
|
||||
}
|
||||
raw := apiKey + strconv.FormatInt(time, 10) + strings.TrimSpace(ctx.Req.URL.Path) + apiSecret
|
||||
realSign := utils.Md5(raw)
|
||||
if sign != realSign {
|
||||
msg := json.CommonFailure("签名验证失败")
|
||||
ctx.Write([]byte(msg))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
func isAjaxRequest(ctx *macaron.Context) bool {
|
||||
req := ctx.Req.Header.Get("X-Requested-With")
|
||||
if req == "XMLHttpRequest" {
|
||||
|
||||
+81
-23
@@ -18,17 +18,20 @@ import (
|
||||
|
||||
type TaskForm struct {
|
||||
Id int
|
||||
Level models.TaskLevel `binding:"Required;In(1,2)"`
|
||||
DependencyStatus models.TaskDependencyStatus
|
||||
DependencyTaskId string
|
||||
Name string `binding:"Required;MaxSize(32)"`
|
||||
Spec string `binding:"Required;MaxSize(64)"`
|
||||
Protocol models.TaskProtocol `binding:"In(1,2,3)"`
|
||||
Spec string
|
||||
Protocol models.TaskProtocol `binding:"In(1,2)"`
|
||||
Command string `binding:"Required;MaxSize(256)"`
|
||||
Timeout int `binding:"Range(-1,86400)"`
|
||||
Timeout int `binding:"Range(0,86400)"`
|
||||
Multi int8 `binding:"In(1,2)"`
|
||||
RetryTimes int8
|
||||
HostId int16
|
||||
HostId string
|
||||
Remark string
|
||||
NotifyStatus int8 `binding:In(1,2,3)`
|
||||
NotifyType int8 `binding:In(1,2)`
|
||||
NotifyStatus int8 `binding:"In(1,2,3)"`
|
||||
NotifyType int8 `binding:"In(1,2,3)"`
|
||||
NotifyReceiverId string
|
||||
}
|
||||
|
||||
@@ -88,9 +91,22 @@ func Edit(ctx *macaron.Context) {
|
||||
logger.Errorf("编辑任务#获取任务详情失败#任务ID-%d#%s", id, err.Error())
|
||||
ctx.Redirect("/task")
|
||||
}
|
||||
hostModel := new(models.Host)
|
||||
hostModel.PageSize = -1
|
||||
hosts, err := hostModel.List(models.CommonMap{})
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
} else {
|
||||
for i, host := range(hosts) {
|
||||
if inHosts(task.Hosts, host.Id) {
|
||||
hosts[i].Selected = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ctx.Data["Task"] = task
|
||||
ctx.Data["Hosts"] = hosts
|
||||
ctx.Data["Title"] = "编辑"
|
||||
setHostsToTemplate(ctx)
|
||||
ctx.HTML(200, "task/task_form")
|
||||
}
|
||||
|
||||
@@ -99,10 +115,6 @@ func Store(ctx *macaron.Context, form TaskForm) string {
|
||||
json := utils.JsonResponse{}
|
||||
taskModel := models.Task{}
|
||||
var id int = form.Id
|
||||
_, err := cron.Parse(form.Spec)
|
||||
if err != nil {
|
||||
return json.CommonFailure("crontab表达式解析失败", err)
|
||||
}
|
||||
nameExists, err := taskModel.NameExist(form.Name, form.Id)
|
||||
if err != nil {
|
||||
return json.CommonFailure(utils.FailureContent, err)
|
||||
@@ -111,15 +123,10 @@ func Store(ctx *macaron.Context, form TaskForm) string {
|
||||
return json.CommonFailure("任务名称已存在")
|
||||
}
|
||||
|
||||
if form.Protocol == models.TaskSSH && form.HostId <= 0 {
|
||||
if form.Protocol == models.TaskRPC && form.HostId == "" {
|
||||
return json.CommonFailure("请选择主机名")
|
||||
}
|
||||
|
||||
if form.Protocol != models.TaskHTTP {
|
||||
taskModel.HostId = form.HostId
|
||||
} else {
|
||||
taskModel.HostId = 0
|
||||
}
|
||||
taskModel.Name = form.Name
|
||||
taskModel.Protocol = form.Protocol
|
||||
taskModel.Command = form.Command
|
||||
@@ -134,17 +141,17 @@ func Store(ctx *macaron.Context, form TaskForm) string {
|
||||
taskModel.NotifyType = form.NotifyType - 1
|
||||
taskModel.NotifyReceiverId = form.NotifyReceiverId
|
||||
taskModel.Spec = form.Spec
|
||||
taskModel.Level = form.Level
|
||||
taskModel.DependencyStatus = form.DependencyStatus
|
||||
taskModel.DependencyTaskId = strings.TrimSpace(form.DependencyTaskId)
|
||||
if taskModel.NotifyStatus > 0 && taskModel.NotifyReceiverId == "" {
|
||||
return json.CommonFailure("请至少选择一个接收者")
|
||||
return json.CommonFailure("至少选择一个通知接收者")
|
||||
}
|
||||
if taskModel.Protocol == models.TaskHTTP {
|
||||
command := strings.ToLower(taskModel.Command)
|
||||
if !strings.HasPrefix(command, "http://") && !strings.HasPrefix(command, "https://") {
|
||||
return json.CommonFailure("请输入正确的URL地址")
|
||||
}
|
||||
if taskModel.Timeout == -1 {
|
||||
return json.CommonFailure("HTTP任务不支持后台运行")
|
||||
}
|
||||
if taskModel.Timeout > 300 {
|
||||
return json.CommonFailure("HTTP任务超时时间不能超过300秒")
|
||||
}
|
||||
@@ -154,20 +161,57 @@ func Store(ctx *macaron.Context, form TaskForm) string {
|
||||
return json.CommonFailure("任务重试次数取值0-10")
|
||||
}
|
||||
|
||||
if (taskModel.DependencyStatus != models.TaskDependencyStatusStrong &&
|
||||
taskModel.DependencyStatus != models.TaskDependencyStatusWeak) {
|
||||
return json.CommonFailure("请选择依赖关系")
|
||||
}
|
||||
|
||||
if taskModel.Protocol != models.TaskSSH {
|
||||
taskModel.HostId = 0
|
||||
if taskModel.Level == models.TaskLevelParent {
|
||||
_, err = cron.Parse(form.Spec)
|
||||
if err != nil {
|
||||
return json.CommonFailure("crontab表达式解析失败", err)
|
||||
}
|
||||
} else {
|
||||
taskModel.DependencyTaskId = ""
|
||||
taskModel.Spec = ""
|
||||
}
|
||||
|
||||
if id > 0 && taskModel.DependencyTaskId != "" {
|
||||
dependencyTaskIds := strings.Split(taskModel.DependencyTaskId, ",")
|
||||
if utils.InStringSlice(dependencyTaskIds, strconv.Itoa(id)) {
|
||||
return json.CommonFailure("不允许设置当前任务为子任务")
|
||||
}
|
||||
}
|
||||
|
||||
if id == 0 {
|
||||
// 任务添加后开始调度执行
|
||||
taskModel.Status = models.Running
|
||||
id, err = taskModel.Create()
|
||||
} else {
|
||||
_, err = taskModel.UpdateBean(id)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return json.CommonFailure("保存失败", err)
|
||||
}
|
||||
|
||||
taskHostModel := new(models.TaskHost)
|
||||
if form.Protocol == models.TaskRPC {
|
||||
hostIdStrList := strings.Split(form.HostId, ",")
|
||||
hostIds := make([]int, len(hostIdStrList))
|
||||
for i, hostIdStr := range hostIdStrList {
|
||||
hostIds[i], _ = strconv.Atoi(hostIdStr)
|
||||
}
|
||||
taskHostModel.Add(id, hostIds)
|
||||
} else {
|
||||
taskHostModel.Remove(id)
|
||||
}
|
||||
|
||||
status, err := taskModel.GetStatus(id)
|
||||
if status == models.Enabled && taskModel.Level == models.TaskLevelParent {
|
||||
addTaskToTimer(id)
|
||||
}
|
||||
|
||||
return json.Success("保存成功", nil)
|
||||
}
|
||||
|
||||
@@ -181,6 +225,9 @@ func Remove(ctx *macaron.Context) string {
|
||||
return json.CommonFailure(utils.FailureContent, err)
|
||||
}
|
||||
|
||||
taskHostModel := new(models.TaskHost)
|
||||
taskHostModel.Remove(id)
|
||||
|
||||
service.Cron.RemoveJob(strconv.Itoa(id))
|
||||
|
||||
return json.Success(utils.SuccessContent, nil)
|
||||
@@ -266,9 +313,20 @@ func parseQueryParams(ctx *macaron.Context) (models.CommonMap) {
|
||||
|
||||
func setHostsToTemplate(ctx *macaron.Context) {
|
||||
hostModel := new(models.Host)
|
||||
hostModel.PageSize = -1
|
||||
hosts, err := hostModel.List(models.CommonMap{})
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
}
|
||||
ctx.Data["Hosts"] = hosts
|
||||
}
|
||||
|
||||
func inHosts(slice []models.TaskHostDetail, element int16) bool {
|
||||
for _, v := range slice {
|
||||
if v.HostId == element {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
@@ -11,8 +11,6 @@ import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"github.com/ouqiang/gocron/routers/base"
|
||||
"github.com/ouqiang/gocron/service"
|
||||
"errors"
|
||||
)
|
||||
|
||||
func Index(ctx *macaron.Context) {
|
||||
@@ -66,51 +64,6 @@ func Remove(ctx *macaron.Context) string {
|
||||
return json.Success("删除成功", nil)
|
||||
}
|
||||
|
||||
// 更新任务状态
|
||||
func UpdateStatus(ctx *macaron.Context) string {
|
||||
id := ctx.QueryTrim("id")
|
||||
status := ctx.QueryInt("status")
|
||||
result := ctx.QueryTrim("result")
|
||||
json := utils.JsonResponse{}
|
||||
|
||||
if id == "" {
|
||||
return json.CommonFailure("任务ID不能为空")
|
||||
}
|
||||
if status != 1 && status != 2 {
|
||||
return json.CommonFailure("status值错误")
|
||||
}
|
||||
if status == 1 {
|
||||
status -= 1
|
||||
}
|
||||
taskLogModel := new(models.TaskLog)
|
||||
affectRows, err := taskLogModel.UpdateStatus(id, models.Status(status), result)
|
||||
if err != nil || affectRows == 0 {
|
||||
return json.CommonFailure("更新任务状态失败")
|
||||
}
|
||||
|
||||
// 发送通知
|
||||
taskId, err := taskLogModel.GetTaskIdByNotifyId(id)
|
||||
if err != nil || taskId <= 0 {
|
||||
logger.Error("异步任务回调#根据notify-id获取taskId失败", err)
|
||||
return json.Success("success", nil)
|
||||
}
|
||||
taskModel := new(models.Task)
|
||||
task, err := taskModel.Detail(taskId)
|
||||
if err != nil || task.Id <= 0 {
|
||||
logger.Error("异步任务回调#根据获取任务详情失败", err)
|
||||
return json.Success("success", nil)
|
||||
}
|
||||
|
||||
taskResult := service.TaskResult{}
|
||||
taskResult.Result = result
|
||||
if status == 0 {
|
||||
taskResult.Err = errors.New("error")
|
||||
}
|
||||
service.SendNotification(task, taskResult)
|
||||
|
||||
return json.Success("success", nil)
|
||||
}
|
||||
|
||||
// 解析查询参数
|
||||
func parseQueryParams(ctx *macaron.Context) (models.CommonMap) {
|
||||
var params models.CommonMap = models.CommonMap{}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"github.com/ouqiang/gocron/models"
|
||||
"github.com/go-macaron/session"
|
||||
"github.com/ouqiang/gocron/modules/logger"
|
||||
"github.com/go-macaron/captcha"
|
||||
)
|
||||
|
||||
// @author qiang.ou<qingqianludao@gmail.com>
|
||||
@@ -47,7 +48,7 @@ func UpdatePassword(ctx *macaron.Context, sess session.Store) string {
|
||||
return json.Success("修改成功", nil)
|
||||
}
|
||||
|
||||
func ValidateLogin(ctx *macaron.Context, sess session.Store) string {
|
||||
func ValidateLogin(ctx *macaron.Context, sess session.Store, cpt *captcha.Captcha) string {
|
||||
username := ctx.QueryTrim("username")
|
||||
password := ctx.QueryTrim("password")
|
||||
json := utils.JsonResponse{}
|
||||
@@ -58,6 +59,9 @@ func ValidateLogin(ctx *macaron.Context, sess session.Store) string {
|
||||
if !userModel.Match(username, password) {
|
||||
return json.CommonFailure("用户名或密码错误")
|
||||
}
|
||||
if !cpt.VerifyReq(ctx.Req) {
|
||||
return json.Failure(utils.CaptchaError, "验证码错误")
|
||||
}
|
||||
|
||||
loginLogModel := new(models.LoginLog)
|
||||
loginLogModel.Username = userModel.Name
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 221 KiB After Width: | Height: | Size: 108 KiB |
@@ -1,110 +0,0 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"github.com/ouqiang/gocron/models"
|
||||
"time"
|
||||
"github.com/ouqiang/gocron/modules/logger"
|
||||
"math"
|
||||
"github.com/ouqiang/gocron/modules/httpclient"
|
||||
"strings"
|
||||
"github.com/ouqiang/timewheel"
|
||||
)
|
||||
|
||||
var tw *timewheel.TimeWheel
|
||||
|
||||
type DelayTask struct {}
|
||||
|
||||
// 从数据库中取出所有延迟任务
|
||||
func (task *DelayTask) Initialize(tick time.Duration, slots int) {
|
||||
tw = timewheel.New(tick, slots)
|
||||
tw.Start()
|
||||
taskModel := new(models.DelayTask)
|
||||
currentTime := time.Now()
|
||||
taskNum, err := taskModel.ActiveNum(currentTime)
|
||||
if err != nil {
|
||||
logger.Error("延迟任务初始化#获取待执行的任务失败", err)
|
||||
return
|
||||
}
|
||||
if taskNum == 0 {
|
||||
logger.Debugf("延迟任务初始化#待执行的任务数量为0")
|
||||
return
|
||||
}
|
||||
pageSize := 100
|
||||
totalPage := int( math.Ceil(float64(taskNum) / float64(pageSize)) )
|
||||
logger.Infof("延迟任务初始化#待执行的任务数量-%d#共%d页#每页取%d条", taskNum, totalPage, pageSize)
|
||||
taskModel.PageSize = pageSize
|
||||
for page := 1; page <= totalPage; page++ {
|
||||
taskModel.Page = page
|
||||
logger.Debugf("延迟任务初始化#取出任务列表#第%d页", page)
|
||||
taskList, err := taskModel.ActiveList(currentTime)
|
||||
if err != nil {
|
||||
logger.Error("延迟任务初始化#获取任务列表失败", err)
|
||||
}
|
||||
task.BatchAdd(taskList)
|
||||
}
|
||||
logger.Info("延迟任务初始化完成")
|
||||
}
|
||||
|
||||
// 批量添加任务
|
||||
func (task *DelayTask) BatchAdd(taskList []models.DelayTask) {
|
||||
for _, item := range(taskList) {
|
||||
task.Add(item)
|
||||
}
|
||||
}
|
||||
|
||||
// 添加任务
|
||||
func (task *DelayTask) Add(taskModel models.DelayTask) {
|
||||
currentTimestamp := time.Now().Unix()
|
||||
execTimestamp := taskModel.Created.Unix() + int64(taskModel.Delay)
|
||||
// 时间过期, 立即执行任务
|
||||
if execTimestamp <= currentTimestamp {
|
||||
go task.Run(taskModel.Id, taskModel.Url, taskModel.Params)
|
||||
return
|
||||
}
|
||||
delay := execTimestamp - currentTimestamp
|
||||
tw.Add(time.Duration(delay) * time.Second, func() {
|
||||
task.Run(taskModel.Id, taskModel.Url, taskModel.Params)
|
||||
})
|
||||
}
|
||||
|
||||
// 运行任务
|
||||
func (task *DelayTask) Run(id int64, url, params string) {
|
||||
taskModel := new(models.DelayTask)
|
||||
_, err := taskModel.UpdateStatus(id, models.Running)
|
||||
if err != nil {
|
||||
logger.Error("延迟任务开始执行#更新任务状态失败", err)
|
||||
return
|
||||
}
|
||||
timeout := 300
|
||||
tryTimes := 3
|
||||
success := false
|
||||
logger.Infof("延迟任务开始执行#id-%d#url-%s#params-%s", id, url, params)
|
||||
for i := 0; i < tryTimes; {
|
||||
response := httpclient.PostParams(url, params, timeout)
|
||||
if response.StatusCode == 200 && strings.TrimSpace(response.Body) == "success"{
|
||||
success = true
|
||||
break;
|
||||
}
|
||||
i++
|
||||
if i < tryTimes {
|
||||
logger.Errorf("延迟任务执行失败#重试第%d次#任务Id-%d#HTTP状态码-%d#HTTP-BODY-%s",
|
||||
i,id,response.StatusCode,response.Body)
|
||||
time.Sleep(30 * time.Second)
|
||||
}
|
||||
}
|
||||
logger.Infof("延迟任务执行完成#id-%d", id)
|
||||
var status models.Status
|
||||
if success {
|
||||
status = models.Finish
|
||||
} else {
|
||||
status = models.Failure
|
||||
}
|
||||
_ ,err = taskModel.UpdateStatus(id, status)
|
||||
if err != nil {
|
||||
logger.Error("延迟任务执行完成#更新任务状态失败", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (task *DelayTask) Stop() {
|
||||
tw.Stop()
|
||||
}
|
||||
+158
-116
@@ -5,14 +5,18 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
"github.com/ouqiang/gocron/modules/logger"
|
||||
"github.com/ouqiang/gocron/modules/ssh"
|
||||
"github.com/jakecoffman/cron"
|
||||
"github.com/ouqiang/gocron/modules/utils"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/ouqiang/gocron/modules/httpclient"
|
||||
"github.com/ouqiang/gocron/modules/notify"
|
||||
"sync"
|
||||
rpcClient "github.com/ouqiang/gocron/modules/rpc/client"
|
||||
pb "github.com/ouqiang/gocron/modules/rpc/proto"
|
||||
"strings"
|
||||
"text/template"
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
)
|
||||
|
||||
// 定时任务调度管理器
|
||||
@@ -47,13 +51,16 @@ func (c *TaskCount) Num() int {
|
||||
return c.num
|
||||
}
|
||||
|
||||
// 任务ID作为Key, 不会出现并发写, 不加锁
|
||||
// 任务ID作为Key
|
||||
type Instance struct {
|
||||
Status map[int]bool
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
// 是否有任务处于运行中
|
||||
func (i *Instance) has(key int) bool {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
running, ok := i.Status[key]
|
||||
if ok && running {
|
||||
return true
|
||||
@@ -63,11 +70,15 @@ func (i *Instance) has(key int) bool {
|
||||
}
|
||||
|
||||
func (i *Instance) add(key int) {
|
||||
i.Lock()
|
||||
defer i.Unlock()
|
||||
i.Status[key] = true
|
||||
}
|
||||
|
||||
func (i *Instance) done(key int) {
|
||||
i.Status[key] = false
|
||||
i.Lock()
|
||||
defer i.Unlock()
|
||||
delete(i.Status, key)
|
||||
}
|
||||
|
||||
type Task struct{}
|
||||
@@ -76,14 +87,13 @@ type TaskResult struct {
|
||||
Result string
|
||||
Err error
|
||||
RetryTimes int8
|
||||
IsAsync bool
|
||||
}
|
||||
|
||||
// 初始化任务, 从数据库取出所有任务, 添加到定时任务并运行
|
||||
func (task *Task) Initialize() {
|
||||
Cron = cron.New()
|
||||
Cron.Start()
|
||||
runInstance = Instance{make(map[int]bool)}
|
||||
runInstance = Instance{make(map[int]bool), sync.RWMutex{}}
|
||||
TaskNum = TaskCount{0, sync.RWMutex{}}
|
||||
|
||||
taskModel := new(models.Task)
|
||||
@@ -100,14 +110,18 @@ func (task *Task) Initialize() {
|
||||
}
|
||||
|
||||
// 批量添加任务
|
||||
func (task *Task) BatchAdd(tasks []models.TaskHost) {
|
||||
func (task *Task) BatchAdd(tasks []models.Task) {
|
||||
for _, item := range tasks {
|
||||
task.Add(item)
|
||||
}
|
||||
}
|
||||
|
||||
// 添加任务
|
||||
func (task *Task) Add(taskModel models.TaskHost) {
|
||||
func (task *Task) Add(taskModel models.Task) {
|
||||
if taskModel.Level == models.TaskLevelChild {
|
||||
logger.Errorf("添加任务失败#不允许添加子任务到调度器#任务Id-%d", taskModel.Id);
|
||||
return
|
||||
}
|
||||
taskFunc := createJob(taskModel)
|
||||
if taskFunc == nil {
|
||||
logger.Error("创建任务处理Job失败,不支持的任务协议#", taskModel.Protocol)
|
||||
@@ -129,46 +143,14 @@ func (task *Task) StopAll() {
|
||||
}
|
||||
|
||||
// 直接运行任务
|
||||
func (task *Task) Run(taskModel models.TaskHost) {
|
||||
func (task *Task) Run(taskModel models.Task) {
|
||||
go createJob(taskModel)()
|
||||
}
|
||||
|
||||
type Handler interface {
|
||||
Run(taskModel models.TaskHost) (string, error)
|
||||
Run(taskModel models.Task) (string, error)
|
||||
}
|
||||
|
||||
// 本地命令
|
||||
type LocalCommandHandler struct {}
|
||||
|
||||
// 运行本地命令
|
||||
func (h *LocalCommandHandler) Run(taskModel models.TaskHost) (string, error) {
|
||||
if taskModel.Command == "" {
|
||||
return "", errors.New("invalid command")
|
||||
}
|
||||
|
||||
if utils.IsWindows() {
|
||||
return h.runOnWindows(taskModel)
|
||||
}
|
||||
|
||||
return h.runOnUnix(taskModel)
|
||||
}
|
||||
|
||||
// 执行Windows命令
|
||||
func (h *LocalCommandHandler) runOnWindows(taskModel models.TaskHost) (string, error) {
|
||||
outputGBK, err := utils.ExecShellWithTimeout(taskModel.Timeout, "cmd", "/C", taskModel.Command)
|
||||
// windows平台编码为gbk,需转换为utf8才能入库
|
||||
outputUTF8, ok := utils.GBK2UTF8(outputGBK)
|
||||
if ok {
|
||||
return outputUTF8, err
|
||||
}
|
||||
|
||||
return "命令输出转换编码失败(gbk to utf8)", err
|
||||
}
|
||||
|
||||
// 执行Unix命令
|
||||
func (h *LocalCommandHandler) runOnUnix(taskModel models.TaskHost) (string, error) {
|
||||
return utils.ExecShellWithTimeout(taskModel.Timeout, "/bin/bash", "-c", taskModel.Command)
|
||||
}
|
||||
|
||||
// HTTP任务
|
||||
type HTTPHandler struct{}
|
||||
@@ -176,7 +158,7 @@ type HTTPHandler struct{}
|
||||
// http任务执行时间不超过300秒
|
||||
const HttpExecTimeout = 300
|
||||
|
||||
func (h *HTTPHandler) Run(taskModel models.TaskHost) (result string, err error) {
|
||||
func (h *HTTPHandler) Run(taskModel models.Task) (result string, err error) {
|
||||
if taskModel.Timeout <= 0 || taskModel.Timeout > HttpExecTimeout {
|
||||
taskModel.Timeout = HttpExecTimeout
|
||||
}
|
||||
@@ -189,63 +171,67 @@ func (h *HTTPHandler) Run(taskModel models.TaskHost) (result string, err error)
|
||||
return resp.Body, err
|
||||
}
|
||||
|
||||
// SSH-command任务
|
||||
type SSHCommandHandler struct{}
|
||||
// RPC调用执行任务
|
||||
type RPCHandler struct {}
|
||||
|
||||
func (h *SSHCommandHandler) Run(taskModel models.TaskHost) (string, error) {
|
||||
hostModel := new(models.Host)
|
||||
err := hostModel.Find(int(taskModel.HostId))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
sshConfig := ssh.SSHConfig{}
|
||||
sshConfig.User = hostModel.Username
|
||||
sshConfig.Host = hostModel.Name
|
||||
sshConfig.Port = hostModel.Port
|
||||
sshConfig.ExecTimeout = taskModel.Timeout
|
||||
sshConfig.AuthType = hostModel.AuthType
|
||||
var password string
|
||||
var privateKey string
|
||||
if hostModel.AuthType == ssh.HostPassword {
|
||||
password, err = hostModel.GetPasswordByHost(hostModel.Name)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
sshConfig.Password = password
|
||||
} else {
|
||||
privateKey, err = hostModel.GetPrivateKeyByHost(hostModel.Name)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
sshConfig.PrivateKey = privateKey
|
||||
func (h *RPCHandler) Run(taskModel models.Task) (result string, err error) {
|
||||
taskRequest := new(pb.TaskRequest)
|
||||
taskRequest.Timeout = int32(taskModel.Timeout)
|
||||
taskRequest.Command = taskModel.Command
|
||||
var resultChan chan TaskResult = make(chan TaskResult, len(taskModel.Hosts))
|
||||
for _, taskHost := range taskModel.Hosts {
|
||||
go func(th models.TaskHostDetail) {
|
||||
output, err := rpcClient.ExecWithRetry(th.Name,
|
||||
th.Port,
|
||||
th.CertFile,
|
||||
th.Token,
|
||||
taskRequest)
|
||||
var errorMessage string = ""
|
||||
if err != nil {
|
||||
errorMessage = err.Error()
|
||||
}
|
||||
outputMessage := fmt.Sprintf("主机: [%s-%s]\n%s\n%s\n\n",
|
||||
th.Alias, th.Name, errorMessage, output,
|
||||
)
|
||||
resultChan <- TaskResult{Err:err, Result: outputMessage}
|
||||
}(taskHost)
|
||||
}
|
||||
|
||||
return ssh.Exec(sshConfig, taskModel.Command)
|
||||
var aggregationErr error = nil
|
||||
var aggregationResult string = ""
|
||||
for i := 0; i < len(taskModel.Hosts); i++ {
|
||||
taskResult := <- resultChan
|
||||
aggregationResult += taskResult.Result
|
||||
if taskResult.Err != nil {
|
||||
aggregationErr = taskResult.Err
|
||||
}
|
||||
}
|
||||
|
||||
return aggregationResult, aggregationErr
|
||||
}
|
||||
|
||||
|
||||
// 创建任务日志
|
||||
func createTaskLog(taskModel models.TaskHost, status models.Status) (int64, string, error) {
|
||||
func createTaskLog(taskModel models.Task, status models.Status) (int64, error) {
|
||||
taskLogModel := new(models.TaskLog)
|
||||
taskLogModel.TaskId = taskModel.Id
|
||||
taskLogModel.Name = taskModel.Task.Name
|
||||
taskLogModel.Name = taskModel.Name
|
||||
taskLogModel.Spec = taskModel.Spec
|
||||
taskLogModel.Protocol = taskModel.Protocol
|
||||
taskLogModel.Command = taskModel.Command
|
||||
taskLogModel.Timeout = taskModel.Timeout
|
||||
if taskModel.Protocol == models.TaskSSH {
|
||||
taskLogModel.Hostname = taskModel.Alias + "-" + taskModel.Name
|
||||
if taskModel.Protocol == models.TaskRPC {
|
||||
var aggregationHost string = ""
|
||||
for _, host := range taskModel.Hosts {
|
||||
aggregationHost += fmt.Sprintf("%s-%s<br>", host.Alias, host.Name)
|
||||
}
|
||||
taskLogModel.Hostname = aggregationHost
|
||||
}
|
||||
taskLogModel.StartTime = time.Now()
|
||||
taskLogModel.Status = status
|
||||
// SSH执行远程命令,后台运行
|
||||
var notifyId string = ""
|
||||
if taskModel.Timeout == -1 {
|
||||
notifyId = utils.RandString(32);
|
||||
taskLogModel.NotifyId = notifyId;
|
||||
}
|
||||
insertId, err := taskLogModel.Create()
|
||||
|
||||
return insertId, notifyId, err
|
||||
return insertId, err
|
||||
}
|
||||
|
||||
// 更新任务日志
|
||||
@@ -255,9 +241,7 @@ func updateTaskLog(taskLogId int64, taskResult TaskResult) (int64, error) {
|
||||
var result string = taskResult.Result
|
||||
if taskResult.Err != nil {
|
||||
status = models.Failure
|
||||
} else if taskResult.IsAsync {
|
||||
status = models.Background
|
||||
} else {
|
||||
} else {
|
||||
status = models.Finish
|
||||
}
|
||||
return taskLogModel.Update(taskLogId, models.CommonMap{
|
||||
@@ -268,7 +252,7 @@ func updateTaskLog(taskLogId int64, taskResult TaskResult) (int64, error) {
|
||||
|
||||
}
|
||||
|
||||
func createJob(taskModel models.TaskHost) cron.FuncJob {
|
||||
func createJob(taskModel models.Task) cron.FuncJob {
|
||||
var handler Handler = createHandler(taskModel)
|
||||
if handler == nil {
|
||||
return nil
|
||||
@@ -276,81 +260,134 @@ func createJob(taskModel models.TaskHost) cron.FuncJob {
|
||||
taskFunc := func() {
|
||||
TaskNum.Add()
|
||||
defer TaskNum.Done()
|
||||
taskLogId := beforeExecJob(&taskModel)
|
||||
taskLogId := beforeExecJob(taskModel)
|
||||
if taskLogId <= 0 {
|
||||
return
|
||||
}
|
||||
logger.Infof("开始执行任务#%s#命令-%s", taskModel.Task.Name, taskModel.Command)
|
||||
logger.Infof("开始执行任务#%s#命令-%s", taskModel.Name, taskModel.Command)
|
||||
taskResult := execJob(handler, taskModel)
|
||||
logger.Infof("任务完成#%s#命令-%s", taskModel.Task.Name, taskModel.Command)
|
||||
logger.Infof("任务完成#%s#命令-%s", taskModel.Name, taskModel.Command)
|
||||
afterExecJob(taskModel, taskResult, taskLogId)
|
||||
}
|
||||
|
||||
return taskFunc
|
||||
}
|
||||
|
||||
func createHandler(taskModel models.TaskHost) Handler {
|
||||
func createHandler(taskModel models.Task) Handler {
|
||||
var handler Handler = nil
|
||||
switch taskModel.Protocol {
|
||||
case models.TaskHTTP:
|
||||
handler = new(HTTPHandler)
|
||||
case models.TaskSSH:
|
||||
handler = new(SSHCommandHandler)
|
||||
case models.TaskLocalCommand:
|
||||
handler = new(LocalCommandHandler)
|
||||
case models.TaskRPC:
|
||||
handler = new(RPCHandler)
|
||||
}
|
||||
|
||||
|
||||
return handler;
|
||||
}
|
||||
|
||||
func beforeExecJob(taskModel *models.TaskHost) (taskLogId int64) {
|
||||
// 任务前置操作
|
||||
func beforeExecJob(taskModel models.Task) (taskLogId int64) {
|
||||
if taskModel.Multi == 0 && runInstance.has(taskModel.Id) {
|
||||
createTaskLog(*taskModel, models.Cancel)
|
||||
createTaskLog(taskModel, models.Cancel)
|
||||
return
|
||||
}
|
||||
if taskModel.Multi == 0 {
|
||||
runInstance.add(taskModel.Id)
|
||||
}
|
||||
taskLogId, notifyId, err := createTaskLog(*taskModel, models.Running)
|
||||
taskLogId, err := createTaskLog(taskModel, models.Running)
|
||||
if err != nil {
|
||||
logger.Error("任务开始执行#写入任务日志失败-", err)
|
||||
return
|
||||
}
|
||||
// 设置notifyId到环境变量中
|
||||
if notifyId != "" {
|
||||
envName := "GOCRON_TASK_ID"
|
||||
if taskModel.Protocol == models.TaskSSH {
|
||||
taskModel.Command = fmt.Sprintf("%s%s", utils.FormatUnixEnv(envName, notifyId), taskModel.Command)
|
||||
} else {
|
||||
taskModel.Command = fmt.Sprintf("%s%s", utils.FormatEnv(envName, notifyId), taskModel.Command)
|
||||
}
|
||||
}
|
||||
|
||||
logger.Debugf("任务命令-%s", taskModel.Command)
|
||||
|
||||
return taskLogId
|
||||
}
|
||||
|
||||
func afterExecJob(taskModel models.TaskHost, taskResult TaskResult, taskLogId int64) {
|
||||
// 任务执行后置操作
|
||||
func afterExecJob(taskModel models.Task, taskResult TaskResult, taskLogId int64) {
|
||||
if taskResult.Err != nil {
|
||||
taskResult.Result = taskResult.Err.Error() + "\n" + taskResult.Result
|
||||
}
|
||||
if taskModel.Timeout == -1 {
|
||||
taskResult.IsAsync = true
|
||||
}
|
||||
_, err := updateTaskLog(taskLogId, taskResult)
|
||||
if err != nil {
|
||||
logger.Error("任务结束#更新任务日志失败-", err)
|
||||
}
|
||||
if taskResult.IsAsync {
|
||||
|
||||
// 发送邮件
|
||||
go SendNotification(taskModel, taskResult)
|
||||
// 执行依赖任务
|
||||
go execDependencyTask(taskModel, taskResult)
|
||||
}
|
||||
|
||||
// 执行依赖任务, 多个任务并发执行
|
||||
func execDependencyTask(taskModel models.Task, taskResult TaskResult) {
|
||||
// 父任务才能执行子任务
|
||||
if taskModel.Level != models.TaskLevelParent {
|
||||
return
|
||||
}
|
||||
|
||||
SendNotification(taskModel, taskResult)
|
||||
// 是否存在子任务
|
||||
dependencyTaskId := strings.TrimSpace(taskModel.DependencyTaskId)
|
||||
if dependencyTaskId == "" {
|
||||
return
|
||||
}
|
||||
|
||||
// 父子任务关系为强依赖, 父任务执行失败, 不执行依赖任务
|
||||
if taskModel.DependencyStatus == models.TaskDependencyStatusStrong && taskResult.Err != nil {
|
||||
logger.Infof("父子任务为强依赖关系, 父任务执行失败, 不运行依赖任务#主任务ID-%d", taskModel.Id)
|
||||
return
|
||||
}
|
||||
|
||||
// 获取子任务
|
||||
model := new(models.Task)
|
||||
tasks , err := model.GetDependencyTaskList(dependencyTaskId)
|
||||
if err != nil {
|
||||
logger.Errorf("获取依赖任务失败#主任务ID-%d#%s", taskModel.Id, err.Error())
|
||||
return
|
||||
}
|
||||
if len(tasks) == 0 {
|
||||
logger.Errorf("依赖任务列表为空#主任务ID-%d", taskModel.Id)
|
||||
}
|
||||
serviceTask := new(Task)
|
||||
for _, task := range tasks {
|
||||
task.Command = appendResultToCommand(task.Command, taskResult)
|
||||
task.Spec = fmt.Sprintf("依赖任务(主任务ID-%d)", taskModel.Id)
|
||||
serviceTask.Run(task)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加主任务执行结果到子任务命令中, 占位符{{.Code}} {{.Message}}
|
||||
*/
|
||||
func appendResultToCommand(command string, taskResult TaskResult) string {
|
||||
var code int8 = 0
|
||||
if taskResult.Err != nil {
|
||||
code = 1
|
||||
}
|
||||
data := map[string]interface{} {
|
||||
"Code": code,
|
||||
"Message": base64.StdEncoding.EncodeToString([]byte(taskResult.Result)),
|
||||
}
|
||||
var buf *bytes.Buffer = new(bytes.Buffer)
|
||||
tmpl, err := template.New("command").Parse(command)
|
||||
if err != nil {
|
||||
logger.Errorf("替换子任务命令占位符失败#%s", err.Error())
|
||||
return command
|
||||
}
|
||||
err = tmpl.Execute(buf, data)
|
||||
if err != nil {
|
||||
logger.Errorf("替换子任务命令占位符失败#%s", err.Error())
|
||||
return command
|
||||
}
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// 发送任务结果通知
|
||||
func SendNotification(taskModel models.TaskHost, taskResult TaskResult) {
|
||||
func SendNotification(taskModel models.Task, taskResult TaskResult) {
|
||||
var statusName string
|
||||
// 未开启通知
|
||||
if taskModel.NotifyStatus == 0 {
|
||||
@@ -372,7 +409,7 @@ func SendNotification(taskModel models.TaskHost, taskResult TaskResult) {
|
||||
msg := notify.Message{
|
||||
"task_type": taskModel.NotifyType,
|
||||
"task_receiver_id": taskModel.NotifyReceiverId,
|
||||
"name": taskModel.Task.Name,
|
||||
"name": taskModel.Name,
|
||||
"output": taskResult.Result,
|
||||
"status": statusName,
|
||||
"taskId": taskModel.Id,
|
||||
@@ -381,7 +418,12 @@ func SendNotification(taskModel models.TaskHost, taskResult TaskResult) {
|
||||
}
|
||||
|
||||
// 执行具体任务
|
||||
func execJob(handler Handler, taskModel models.TaskHost) TaskResult {
|
||||
func execJob(handler Handler, taskModel models.Task) TaskResult {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
logger.Error("panic#service/task.go:execJob#", err)
|
||||
}
|
||||
} ()
|
||||
if taskModel.Multi == 0 {
|
||||
defer runInstance.done(taskModel.Id)
|
||||
}
|
||||
|
||||
@@ -60,13 +60,12 @@
|
||||
<div class="ui teal inverted menu">
|
||||
<div class="bigcontainer">
|
||||
<div class="right menu">
|
||||
<a class="item {{{if eq .Controller "task"}}}active{{{end}}}" href="/task"><i class="tasks icon"></i>任务</a>
|
||||
<a class="item {{{if eq .Controller "host"}}}active{{{end}}}" href="/host"><i class="linux icon"></i>主机</a>
|
||||
<a class="item {{{if or (eq .Controller "task") (eq .Controller "delaytask")}}}active{{{end}}}" href="/task"><i class="tasks icon"></i>任务</a>
|
||||
<a class="item {{{if eq .Controller "host"}}}active{{{end}}}" href="/host"><i class="linux icon"></i>任务节点</a>
|
||||
<!-- <a class="item {{{if eq .Controller "user"}}}active{{{end}}}" href="/user"><i class="user icon"></i>账户</a> -->
|
||||
{{{if gt .LoginUid 0}}}
|
||||
<a class="item {{{if eq .Controller "manage"}}}active{{{end}}}" href="/manage/slack/edit"><i class="settings icon"></i>管理</a>
|
||||
{{{end}}}
|
||||
<a class="item" href="https://github.com/ouqiang/gocron/wiki" target="_blank"><i class="file text icon"></i>查看文档</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -17,45 +17,40 @@
|
||||
<input type="hidden" name="id" value="{{{.Host.Id}}}">
|
||||
<div class="four fields">
|
||||
<div class="field">
|
||||
<label>主机名 (域名或IP)</label>
|
||||
<label>主机名</label>
|
||||
<div class="ui small input">
|
||||
<input type="text" name="name" value="{{{.Host.Name}}}">
|
||||
<input type="text" name="name" value="{{{.Host.Name}}}" placeholder="127.0.0.1">
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>主机别名 (方便记忆和引用)</label>
|
||||
<label>端口</label>
|
||||
<div class="ui small input">
|
||||
<input type="text" name="alias" value="{{{.Host.Alias}}}">
|
||||
<input type="text" name="port" value="{{{.Host.Port}}}" placeholder="5921">
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>节点名称</label>
|
||||
<div class="ui small input">
|
||||
<input type="text" name="alias" value="{{{.Host.Alias}}}"
|
||||
placeholder="节点名称如web">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="four fields">
|
||||
<div class="two fields">
|
||||
<div class="field">
|
||||
<label>SSH用户名</label>
|
||||
<label>证书路径</label>
|
||||
<div class="ui small input">
|
||||
<input type="text" name="username" value="{{{.Host.Username}}}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>SSH端口</label>
|
||||
<div class="ui small input">
|
||||
<input type="text" name="port" value="{{{.Host.Port}}}">
|
||||
<input type="text" name="cert_file" value="{{{.Host.CertFile}}}"
|
||||
placeholder="data/certs/server.pem">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="fields">
|
||||
<div class="two fields">
|
||||
<div class="field">
|
||||
<div class="ui blue message">
|
||||
密码认证: 把密码写入文件 gocron根目录/data/ssh/password/主机名<br>
|
||||
例: echo '12345678' > data/ssh/password/127.0.0.1<br><br>
|
||||
公钥认证: 把私钥写入文件 gocron根目录/data/ssh/private_key/主机名<br>
|
||||
例: cp ~/.ssh/id_rsa data/ssh/private_key/127.0.0.1
|
||||
<label>Token</label>
|
||||
<div class="ui small input">
|
||||
<textarea rows="4" name="token" placeholder="gocron-node中配置的token">{{{.Host.Token}}}</textarea>
|
||||
</div>
|
||||
<label>认证方式</label>
|
||||
<select name="auth_type" id="authType">
|
||||
<option value="2" {{{if .Host}}} {{{if eq .Host.AuthType 2}}}selected {{{end}}} {{{end}}} data-validate-type="selectPrivateKey" data-match="private_key">公钥</option>
|
||||
<option value="1" {{{if .Host}}} {{{if eq .Host.AuthType 1}}}selected {{{end}}} {{{end}}} data-validate-type="selectPassword" data-match="password">密码</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="two fields">
|
||||
@@ -112,19 +107,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
username: {
|
||||
identifier : 'username',
|
||||
rules: [
|
||||
{
|
||||
type : 'empty',
|
||||
prompt : '请输入SSH用户名'
|
||||
},
|
||||
{
|
||||
type : 'maxLength[32]',
|
||||
prompt : '长度不能超过32'
|
||||
}
|
||||
]
|
||||
},
|
||||
port: {
|
||||
identifier : 'port',
|
||||
rules: [
|
||||
|
||||
+12
-18
@@ -10,7 +10,7 @@
|
||||
<a href="/host/create">
|
||||
<i class="large add icon"></i>
|
||||
<div class="content">
|
||||
添加主机
|
||||
添加节点
|
||||
</div>
|
||||
</a>
|
||||
</h3>
|
||||
@@ -29,14 +29,14 @@
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<table class="ui striped table">
|
||||
<table class="ui celled table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>主机名</th>
|
||||
<th>别名</th>
|
||||
<th>用户名</th>
|
||||
<th>端口</th>
|
||||
<th>证书</th>
|
||||
<th>备注</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
@@ -47,8 +47,8 @@
|
||||
<td>{{{.Id}}}</td>
|
||||
<td>{{{.Name}}}</td>
|
||||
<td>{{{.Alias}}}</td>
|
||||
<td>{{{.Username}}}</td>
|
||||
<td>{{{.Port}}}</td>
|
||||
<td>{{{.CertFile}}}</td>
|
||||
<td>{{{.Remark}}}</td>
|
||||
<td class="operation">
|
||||
<a class="ui purple button" href="/host/edit/{{{.Id}}}">编辑</a>
|
||||
@@ -68,20 +68,14 @@
|
||||
|
||||
<script type="text/javascript">
|
||||
var Vue = new Vue({
|
||||
el: '.ui.striped.table',
|
||||
methods: {
|
||||
ping: function(id) {
|
||||
swal({
|
||||
title: '',
|
||||
text: "连接中.......",
|
||||
type: 'info',
|
||||
showConfirmButton: false
|
||||
});
|
||||
util.get("/host/ping/" + id, function(code, message) {
|
||||
swal('操作成功', '连接成功', 'success');
|
||||
})
|
||||
}
|
||||
}
|
||||
el: '.ui.celled.table',
|
||||
methods: {
|
||||
ping: function(id) {
|
||||
util.get("/host/ping/" + id, function(code, message) {
|
||||
swal('操作成功', '连接成功', 'success');
|
||||
})
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="verticalMenu">
|
||||
<div class="ui vertical pointing menu fluid">
|
||||
<a class="{{{if eq .URI "/host"}}}active teal{{{end}}} item" href="/host">
|
||||
<i class="linux icon"></i> 主机列表
|
||||
<i class="linux icon"></i> 节点列表
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -90,12 +90,6 @@
|
||||
$('.ui.form').form(
|
||||
{
|
||||
onSuccess: function(event, fields) {
|
||||
swal({
|
||||
title: '',
|
||||
text: "系统安装中.......",
|
||||
type: 'info',
|
||||
showConfirmButton: false
|
||||
});
|
||||
util.post('/install/store', fields, function(code, message) {
|
||||
swal('安装成功');
|
||||
setTimeout(function() {
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
{{{ template "common/header" . }}}
|
||||
<style type="text/css">
|
||||
pre {
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
padding:10px;
|
||||
background-color: #4C4C4C;
|
||||
color: white;
|
||||
}
|
||||
</style>
|
||||
<div class="ui grid">
|
||||
<!--the vertical menu-->
|
||||
{{{ template "task/menu" . }}}
|
||||
|
||||
<div class="twelve wide column">
|
||||
<div class="pageHeader">
|
||||
<div class="segment">
|
||||
<h3 class="ui dividing header">
|
||||
<div class="content">
|
||||
</div>
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
<form class="ui form">
|
||||
<div class="fields search">
|
||||
<div class="field">
|
||||
<select name="status">
|
||||
<option value="0">任务状态</option>
|
||||
<option value="1" {{{if eq .Params.Status 0}}}selected{{{end}}} >失败</option>
|
||||
<option value="2" {{{if eq .Params.Status 1}}}selected{{{end}}}>执行中</option>
|
||||
<option value="3" {{{if eq .Params.Status 2}}}selected{{{end}}}>成功</option>
|
||||
<option value="6" {{{if eq .Params.Status 5}}}selected{{{end}}}>待执行</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<button class="ui linkedin submit button">搜索</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<table class="ui pink table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>任务ID</th>
|
||||
<th>URL</th>
|
||||
<th>参数</th>
|
||||
<th>延迟时间</th>
|
||||
<th>创建时间</th>
|
||||
<th>完成时间</th>
|
||||
<th>状态</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{{range $i, $v := .Tasks}}}
|
||||
<tr>
|
||||
<td>{{{.Id}}}</td>
|
||||
<td>{{{.Url}}}</td>
|
||||
<td>{{{.Params}}}</td>
|
||||
<td>{{{.Delay}}}秒</td>
|
||||
<td>{{{.Created.Format "2006-01-02 15:04:05" }}}</td>
|
||||
<td>
|
||||
{{{if or (eq .Status 0) (eq .Status 2) }}}
|
||||
{{{.Updated.Format "2006-01-02 15:04:05" }}}
|
||||
{{{end}}}
|
||||
</td>
|
||||
<td>
|
||||
{{{if eq .Status 2}}}
|
||||
成功
|
||||
{{{else if eq .Status 1}}}
|
||||
<span style="color:green">执行中</span>
|
||||
{{{else if eq .Status 0}}}
|
||||
<span style="color:red">失败</span>
|
||||
{{{else if eq .Status 5}}}
|
||||
<span style="color:#43A102">待执行</span>
|
||||
{{{end}}}
|
||||
</td>
|
||||
</tr>
|
||||
{{{end}}}
|
||||
</tbody>
|
||||
</table>
|
||||
{{{ template "common/pagination" .}}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{{ template "common/footer" . }}}
|
||||
+146
-58
@@ -24,7 +24,7 @@
|
||||
</div>
|
||||
<div class="field">
|
||||
<select name="host_id" id="hostId">
|
||||
<option value="">选择主机</option>
|
||||
<option value="">选择节点</option>
|
||||
{{{range $i, $v := .Hosts}}}
|
||||
<option value="{{{.Id}}}" {{{if eq $.Params.HostId .Id }}} selected {{{end}}} >{{{.Alias}}}-{{{.Name}}}</option>
|
||||
{{{end}}}
|
||||
@@ -33,15 +33,14 @@
|
||||
<div class="field">
|
||||
<select name="protocol" id="protocol">
|
||||
<option value="0">执行方式</option>
|
||||
<option value="3" {{{if eq .Params.Protocol 3}}}selected{{{end}}}>系统命令</option>
|
||||
<option value="2" {{{if eq .Params.Protocol 2}}}selected{{{end}}} data-match="host_id" data-validate-type="selectProtocol">SSH</option>
|
||||
<option value="2" {{{if eq .Params.Protocol 2}}}selected{{{end}}} data-match="host_id" data-validate-type="selectProtocol">SHELL</option>
|
||||
<option value="1" {{{if eq .Params.Protocol 1}}}selected{{{end}}}>HTTP</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<select name="status">
|
||||
<option value="0">状态</option>
|
||||
<option value="1" {{{if eq .Params.Status 0}}}selected{{{end}}} >暂停</option>
|
||||
<option value="1" {{{if eq .Params.Status 0}}}selected{{{end}}} >停止</option>
|
||||
<option value="2" {{{if eq .Params.Status 1}}}selected{{{end}}}>激活</option>
|
||||
</select>
|
||||
</div>
|
||||
@@ -50,70 +49,132 @@
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<div class="task-list">
|
||||
{{{range $i, $v := .Tasks}}}
|
||||
<div class="ui device two column middle aligned vertical grid segment">
|
||||
<div class="column verborder">
|
||||
<div class="ui info segment">
|
||||
<h5 class="ui header">{{{.Task.Name}}} {{{if eq .Status 1}}}<i class="large checkmark blue icon"></i> {{{else}}} <i class="large red minus icon"></i> {{{end}}}
|
||||
</h5>
|
||||
<p>任务ID: <span class="stress">{{{.Id}}}</span></p>
|
||||
<p>状态: <span class="stress">{{{if eq .Status 1}}}激活{{{else}}}停止{{{end}}}</span></p>
|
||||
<p>cron表达式: {{{.Spec}}}</p>
|
||||
<p>执行方式: {{{if eq .Protocol 1}}} HTTP {{{else if eq .Protocol 2}}} SSH {{{else if eq .Protocol 3}}}本地命令{{{end}}}</p>
|
||||
<p class="sensorStatus">命令:{{{.Command}}}</p>
|
||||
<p class="sensorStatus">超时时间:{{{if eq .Timeout -1}}}后台运行{{{else if gt .Timeout 0}}}{{{.Timeout}}}秒{{{else}}}不限制{{{end}}}</p>
|
||||
<p>重试次数: {{{.RetryTimes}}}</p>
|
||||
<p class="sensorStatus">是否允许多实例运行:{{{if gt .Multi 0}}}是{{{else}}}否{{{end}}}</p>
|
||||
{{{if eq .Protocol 2}}}
|
||||
<p>主机: {{{.Alias}}}-{{{.Name}}}</p>
|
||||
{{{end}}}
|
||||
<p>备注: {{{.Remark}}}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="center aligned column">
|
||||
<div class="ui buttons operation">
|
||||
<a class="ui purple button" href="/task/edit/{{{.Id}}}">编辑</a>
|
||||
{{{if eq .Status 1}}}
|
||||
<button class="ui primary button" @click="changeStatus({{{.Id}}},{{{.Status}}})">停止</button>
|
||||
{{{else}}}
|
||||
<button class="ui blue button" @click="changeStatus({{{.Id}}},{{{.Status}}})">激活 </button>
|
||||
{{{end}}}
|
||||
<button class="ui positive button" @click="remove({{{.Id}}})">删除</button> <br>
|
||||
<button class="ui twitter button" @click="run({{{.Id}}})">手动运行</button>
|
||||
<a class="ui instagram button" href="/task/log?task_id={{{.Id}}}">查看日志</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{{end}}}
|
||||
</div>
|
||||
<div class="field">
|
||||
<select id="batch-operation">
|
||||
<option value="0">批量操作</option>
|
||||
<option value="1">激活</option>
|
||||
<option value="2">停止</option>
|
||||
<option value="3">删除</option>
|
||||
</select>
|
||||
</div>
|
||||
<br>
|
||||
<table class="ui celled table task-list">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<input type="checkbox" onclick="checkAll(this)" style="width:25px;height: 25px;">
|
||||
</th>
|
||||
<th>任务ID</th>
|
||||
<th>任务名称</th>
|
||||
<th>任务类型</th>
|
||||
<th>cron表达式</th>
|
||||
<th>执行方式</th>
|
||||
<th>超时时间</th>
|
||||
<th>重试次数</th>
|
||||
<th>单实例运行</th>
|
||||
<th>任务节点</th>
|
||||
<th>状态</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{{range $i, $v := .Tasks}}}
|
||||
<tr>
|
||||
<td>
|
||||
<input type="checkbox"
|
||||
class="sub-check"
|
||||
data-id="{{{.Id}}}"
|
||||
style="width:25px;height: 25px;">
|
||||
</td>
|
||||
<td>{{{.Id}}}</td>
|
||||
<td>{{{.Name}}}</td>
|
||||
<td>{{{if eq .Level 1}}}主任务{{{else}}}子任务{{{end}}}</td>
|
||||
<td>{{{.Spec}}}</td>
|
||||
<td>{{{if eq .Protocol 1}}} HTTP {{{else if eq .Protocol 2}}} SHELL {{{end}}}</td>
|
||||
<td>{{{if eq .Timeout -1}}}后台运行{{{else if gt .Timeout 0}}}{{{.Timeout}}}秒{{{else}}}不限制{{{end}}}</td>
|
||||
<td>{{{.RetryTimes}}}</td>
|
||||
<td>{{{if gt .Multi 0}}}否{{{else}}}是{{{end}}}</td>
|
||||
<td>
|
||||
{{{range $k, $h := .Hosts}}}
|
||||
{{{$h.Alias}}}<br>
|
||||
{{{end}}}
|
||||
</td>
|
||||
<td>
|
||||
{{{if eq .Level 1}}}
|
||||
{{{if eq .Status 1}}}<span style="color: green;">激活</span>{{{else}}}<span style="color: red;">停止<span>{{{end}}}
|
||||
{{{end}}}
|
||||
</td>
|
||||
<td>
|
||||
<div class="ui buttons operation">
|
||||
<a class="ui purple button" href="/task/edit/{{{.Id}}}">编辑</a>
|
||||
{{{if eq .Level 1}}}
|
||||
{{{if eq .Status 1}}}
|
||||
<button class="ui primary button" @click="changeStatus({{{.Id}}},{{{.Status}}})">停止</button>
|
||||
{{{else}}}
|
||||
<button class="ui blue button" @click="changeStatus({{{.Id}}},{{{.Status}}})">激活 </button>
|
||||
{{{end}}}
|
||||
{{{end}}}
|
||||
<button class="ui positive button" @click="remove({{{.Id}}})">删除</button> <br>
|
||||
<button class="ui twitter button" @click="run({{{.Id}}})">手动运行</button>
|
||||
<a class="ui instagram button" href="/task/log?task_id={{{.Id}}}">查看日志</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{{{end}}}
|
||||
</tbody>
|
||||
</table>
|
||||
{{{ template "common/pagination" .}}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<script type="text/javascript">
|
||||
$('.ui.checkbox').checkbox();
|
||||
|
||||
$('#batch-operation').change(function() {
|
||||
var type = $(this).val();
|
||||
if (type == 0) {
|
||||
return;
|
||||
}
|
||||
var ids = [];
|
||||
$('.sub-check:checked').each(function() {
|
||||
ids.push($(this).data('id'));
|
||||
});
|
||||
if (ids.length == 0) {
|
||||
swal('错误提示', '至少选择一个任务', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
util.confirm("确定要执行此操作吗", function () {
|
||||
$.ajaxSetup({
|
||||
async: false
|
||||
});
|
||||
switch (type) {
|
||||
case "1":
|
||||
for (i in ids) {
|
||||
changeStatus(ids[i], false, true);
|
||||
}
|
||||
break;
|
||||
case "2":
|
||||
for (i in ids) {
|
||||
changeStatus(ids[i], true, true);
|
||||
}
|
||||
break;
|
||||
case "3":
|
||||
for (i in ids) {
|
||||
remove(ids[i], true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
location.reload();
|
||||
});
|
||||
});
|
||||
|
||||
var vue = new Vue(
|
||||
{
|
||||
el: '.task-list',
|
||||
methods: {
|
||||
changeStatus: function (id ,status) {
|
||||
var url = '';
|
||||
if (status) {
|
||||
url = '/task/disable';
|
||||
} else {
|
||||
url = '/task/enable';
|
||||
}
|
||||
url += '/' + id;
|
||||
util.post(url,{}, function() {
|
||||
location.reload();
|
||||
});
|
||||
},
|
||||
remove: function(id) {
|
||||
util.removeConfirm('/task/remove/' + id);
|
||||
},
|
||||
changeStatus: changeStatus,
|
||||
remove: remove,
|
||||
run: function(id) {
|
||||
util.get("/task/run/" + id, function(code, message) {
|
||||
swal('操作成功', message, 'success');
|
||||
@@ -123,9 +184,36 @@
|
||||
}
|
||||
);
|
||||
|
||||
function checkAll(ele) {
|
||||
if ($(ele).is(":checked")) {
|
||||
$('.sub-check').prop("checked", true);
|
||||
} else {
|
||||
$('.sub-check').prop("checked", false);
|
||||
}
|
||||
}
|
||||
|
||||
function remove(id, stopReload) {
|
||||
util.post('/task/remove/' + id, {}, function () {
|
||||
if (stopReload === undefined) {
|
||||
location.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function changeStatus(id ,status, stopReload) {
|
||||
var url = '';
|
||||
if (status) {
|
||||
url = '/task/disable';
|
||||
} else {
|
||||
url = '/task/enable';
|
||||
}
|
||||
url += '/' + id;
|
||||
util.post(url,{}, function() {
|
||||
if (stopReload === undefined) {
|
||||
location.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
{{{ template "common/footer" . }}}
|
||||
|
||||
+6
-16
@@ -30,8 +30,7 @@
|
||||
<div class="field">
|
||||
<select name="protocol" id="protocol">
|
||||
<option value="0">执行方式</option>
|
||||
<option value="3" {{{if eq .Params.Protocol 3}}}selected{{{end}}}>系统命令</option>
|
||||
<option value="2" {{{if eq .Params.Protocol 2}}}selected{{{end}}} data-match="host_id" data-validate-type="selectProtocol">SSH</option>
|
||||
<option value="2" {{{if eq .Params.Protocol 2}}}selected{{{end}}} data-match="host_id" data-validate-type="selectProtocol">SHELL</option>
|
||||
<option value="1" {{{if eq .Params.Protocol 1}}}selected{{{end}}}>HTTP</option>
|
||||
</select>
|
||||
</div>
|
||||
@@ -42,7 +41,6 @@
|
||||
<option value="2" {{{if eq .Params.Status 1}}}selected{{{end}}}>执行中</option>
|
||||
<option value="3" {{{if eq .Params.Status 2}}}selected{{{end}}}>成功</option>
|
||||
<option value="4" {{{if eq .Params.Status 3}}}selected{{{end}}}>取消</option>
|
||||
<option value="5" {{{if eq .Params.Status 4}}}selected{{{end}}}>后台运行</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
@@ -50,16 +48,15 @@
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<table class="ui pink table">
|
||||
<table class="ui celled table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>任务ID</th>
|
||||
<th>任务名称</th>
|
||||
<th>cron表达式</th>
|
||||
<th>协议</th>
|
||||
<th>超时时间</th>
|
||||
<th>执行方式</th>
|
||||
<th>重试次数</th>
|
||||
<th>主机</th>
|
||||
<th>任务节点</th>
|
||||
<th>执行时长</th>
|
||||
<th>状态</th>
|
||||
<th>执行结果</th>
|
||||
@@ -71,10 +68,9 @@
|
||||
<td><a href="/task?id={{{.TaskId}}}">{{{.TaskId}}}</a></td>
|
||||
<td>{{{.Name}}}</td>
|
||||
<td>{{{.Spec}}}</td>
|
||||
<td>{{{if eq .Protocol 1}}} HTTP {{{else if eq .Protocol 2}}} SSH {{{else}}} 系统命令 {{{end}}}</td>
|
||||
<td>{{{if eq .Timeout -1}}}后台运行{{{else if gt .Timeout 0}}}{{{.Timeout}}}秒{{{else}}}不限制{{{end}}}</td>
|
||||
<td>{{{if eq .Protocol 1}}} HTTP {{{else if eq .Protocol 2}}} SHELL {{{end}}}</td>
|
||||
<td>{{{.RetryTimes}}}</td>
|
||||
<td>{{{.Hostname}}}</td>
|
||||
<td>{{{unescape .Hostname}}}</td>
|
||||
<td>
|
||||
{{{if and (ne .Status 3) (ne .Status 4)}}}
|
||||
{{{if gt .TotalTime 0}}}{{{.TotalTime}}}秒{{{else}}}1秒{{{end}}}<br>
|
||||
@@ -93,8 +89,6 @@
|
||||
<span style="color:red">失败</span>
|
||||
{{{else if eq .Status 3}}}
|
||||
<span style="color:#4499EE">取消</span>
|
||||
{{{else if eq .Status 4}}}
|
||||
<span style="color:#43A102">后台运行</span>
|
||||
{{{end}}}
|
||||
</td>
|
||||
<td>
|
||||
@@ -133,10 +127,6 @@
|
||||
</script>
|
||||
|
||||
<script type="text/javascript">
|
||||
function showTime(startTime, endTime, status) {
|
||||
|
||||
}
|
||||
|
||||
function showResult(name, command,result) {
|
||||
$('.message').html($('#task-result').html());
|
||||
new Vue(
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
<div class="verticalMenu">
|
||||
<div class="ui vertical pointing menu fluid">
|
||||
<a class="{{{if eq .URI "/task"}}}active teal{{{end}}} item" href="/task">
|
||||
<i class="tasks icon"></i> 任务列表
|
||||
<i class="tasks icon"></i> 定时任务列表
|
||||
</a>
|
||||
<a class="item {{{if eq .URI "/task/log"}}}active teal{{{end}}} " href="/task/log">
|
||||
<i class="bar chart icon"></i> 任务日志
|
||||
<i class="bar chart icon"></i> 定时任务日志
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+120
-47
@@ -19,19 +19,63 @@
|
||||
<div class="content">任务名称</div>
|
||||
</label>
|
||||
<div class="ui small input">
|
||||
<input type="text" name="name" value="{{{.Task.Task.Name}}}">
|
||||
<input type="text" name="name" placeholder="任务名称" value="{{{.Task.Name}}}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="two fields">
|
||||
<div class="field">
|
||||
<label>
|
||||
<div class="content">
|
||||
crontab表达式
|
||||
<div class="content">任务类型</div>
|
||||
<div class="ui message">
|
||||
主任务可以配置多个子任务, 当主任务执行完成后,自动执行子任务<br>
|
||||
任务类型新增后不能变更
|
||||
</div>
|
||||
</label>
|
||||
<div class="ui small input">
|
||||
<input type="text" name="spec" value="{{{.Task.Spec}}}" placeholder="秒 分 时 天 月 周"/>
|
||||
<select name="level" id="level" {{{if .Task}}}disabled="disabled"{{{end}}}>
|
||||
<option value="1" {{{if .Task}}} {{{if eq .Task.Level 1}}}selected{{{end}}} {{{end}}}>主任务</option>
|
||||
<option value="2" {{{if .Task}}} {{{if eq .Task.Level 2}}}selected{{{end}}} {{{end}}}>子任务</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div id="parent-task">
|
||||
<div class="two fields">
|
||||
<div class="field">
|
||||
<label>
|
||||
<div class="content">依赖关系</div>
|
||||
<div class="ui message">
|
||||
强依赖: 主任务执行成功,才会运行子任务 <br>
|
||||
弱依赖: 无论主任务执行是否成功,都会运行子任务
|
||||
</div>
|
||||
</label>
|
||||
<select name="dependency_status" id="dependency_status">
|
||||
<option value="1" {{{if .Task}}} {{{if eq .Task.DependencyStatus 1}}}selected{{{end}}} {{{end}}}>强依赖</option>
|
||||
<option value="2" {{{if .Task}}} {{{if eq .Task.DependencyStatus 2}}}selected{{{end}}} {{{end}}}>弱依赖</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>
|
||||
<div class="content">子任务ID</div>
|
||||
<div class="ui message">
|
||||
多个任务ID逗号分隔 <br>
|
||||
子任务并发执行
|
||||
</div>
|
||||
</label>
|
||||
<div class="ui small input">
|
||||
<input type="text" name="dependency_task_id" placeholder="可选" value="{{{.Task.DependencyTaskId}}}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="two fields">
|
||||
<div class="field">
|
||||
<label>
|
||||
<div class="content">
|
||||
crontab表达式
|
||||
</div>
|
||||
</label>
|
||||
<div class="ui small input">
|
||||
<input type="text" name="spec" value="{{{.Task.Spec}}}" placeholder="秒 分 时 天 月 周"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -39,24 +83,23 @@
|
||||
<div class="field">
|
||||
<label>执行方式</label>
|
||||
<select name="protocol" id="protocol">
|
||||
<option value="3" {{{if .Task}}} {{{if eq .Task.Protocol 3}}}selected{{{end}}} {{{end}}}>系统命令</option>
|
||||
<option value="2" {{{if .Task}}} {{{if eq .Task.Protocol 2}}}selected{{{end}}} {{{end}}} data-match="host_id" data-validate-type="selectProtocol">SSH</option>
|
||||
<option value="2" {{{if .Task}}} {{{if eq .Task.Protocol 2}}}selected{{{end}}} {{{end}}}
|
||||
data-validate-type="selectProtocol">SHELL</option>
|
||||
<option value="1" {{{if .Task}}} {{{if eq .Task.Protocol 1}}}selected{{{end}}} {{{end}}}>HTTP</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="three fields" id="hostField">
|
||||
<div class="fields" id="hostField">
|
||||
<div class="field">
|
||||
<label>主机</label>
|
||||
<div class="ui blue message">
|
||||
<pre>选择SSH协议时,需选择执行主机</pre>
|
||||
</div>
|
||||
<select name="host_id" id="hostId">
|
||||
<option value="">选择主机</option>
|
||||
<label>选择任务节点</label>
|
||||
<div id="hostId">
|
||||
{{{range $i, $v := .Hosts}}}
|
||||
<option value="{{{.Id}}}" {{{if $.Task}}}{{{if eq $.Task.HostId .Id }}} selected {{{end}}} {{{end}}}>{{{.Alias}}}-{{{.Name}}}</option>
|
||||
<label>
|
||||
<input type="checkbox" value="{{{.Id}}}" {{{if $.Task}}}{{{if $v.Selected}}} checked {{{end}}}{{{end}}} style="width:25px;height: 25px;">{{{.Alias}}}-{{{.Name}}}
|
||||
{{{if (HostFormat $i) }}}<br>{{{end}}}
|
||||
</label>
|
||||
{{{end}}}
|
||||
</select> <a class="ui blue button" href="/host/create" target="_blank">添加主机</a>
|
||||
</div> <br> <a class="ui blue button" href="/host/create" target="_blank">添加节点</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@@ -66,26 +109,23 @@
|
||||
<textarea rows="5" name="command" placeholder="请输入系统命令" id="command">{{{.Task.Command}}}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="six fields">
|
||||
<div class="field">
|
||||
<label>任务超时时间(秒)</label>
|
||||
<input type="text" name="timeout" placeholder="默认0, 不限制" value="{{{.Task.Timeout}}}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="six fields">
|
||||
<div class="field">
|
||||
<label>任务失败重试次数</label>
|
||||
<input type="text" name="retry_times" placeholder="默认0, 不重试" value="{{{.Task.RetryTimes}}}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="three fields">
|
||||
<div class="field">
|
||||
<label>任务超时时间(秒, 0-86400)</label>
|
||||
<input type="text" name="timeout" placeholder="默认0, 不限制" value="{{{if .Task}}} {{{.Task.Timeout}}} {{{else}}}0{{{end}}}">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>任务失败重试次数 (0-10)</label>
|
||||
<input type="text" name="retry_times" placeholder="默认0, 不重试" value="{{{if .Task}}} {{{.Task.RetryTimes}}} {{{else}}}0{{{end}}}">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>允许多实例同时运行</label>
|
||||
<select name="multi">
|
||||
<option value="1"{{{if .Task}}} {{{if eq .Task.Multi 1}}}selected{{{end}}} {{{end}}}>是</option>
|
||||
<option value="2" {{{if .Task}}} {{{if eq .Task.Multi 0}}}selected{{{end}}} {{{end}}}>否</option>
|
||||
<option value="1"{{{if .Task}}} {{{if eq .Task.Multi 1}}}selected{{{end}}} {{{end}}}>是</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="three fields">
|
||||
<div class="field">
|
||||
@@ -111,7 +151,7 @@
|
||||
<div class="two fields">
|
||||
<div class="field">
|
||||
<label>备注</label>
|
||||
<textarea rows="5" name="remark">{{{.Task.Remark}}}</textarea>
|
||||
<textarea rows="5" name="remark" placeholder="任务备注信息">{{{.Task.Remark}}}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui primary submit button">保存</div> <a class="ui button" onclick="location.href='/task';">取消</a>
|
||||
@@ -148,6 +188,7 @@
|
||||
<script type="text/javascript">
|
||||
$(function() {
|
||||
changeCommandPlaceholder();
|
||||
changeLevel();
|
||||
changeProtocol();
|
||||
showNotify();
|
||||
});
|
||||
@@ -157,7 +198,9 @@
|
||||
changeProtocol();
|
||||
});
|
||||
|
||||
|
||||
$('#level').change(function() {
|
||||
changeLevel();
|
||||
});
|
||||
|
||||
$('#task-status').change(function() {
|
||||
var selected = $(this).val();
|
||||
@@ -183,9 +226,6 @@
|
||||
case '2':
|
||||
$('#command').attr('placeholder', '请输入shell命令');
|
||||
break;
|
||||
case '3':
|
||||
$('#command').attr('placeholder', '请输入系统命令');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,6 +316,28 @@
|
||||
return receivers.join(",");
|
||||
}
|
||||
|
||||
function parseHostId() {
|
||||
var hostIds = [];
|
||||
$('#hostId input:checked').each(function () {
|
||||
hostIds.push($(this).val());
|
||||
});
|
||||
|
||||
return hostIds.join(",");
|
||||
}
|
||||
|
||||
function changeLevel() {
|
||||
var selected = $('#level').val();
|
||||
if (selected == 1) {
|
||||
// 主任务
|
||||
$('#parent-task').show();
|
||||
$('#child-task').hide();
|
||||
} else {
|
||||
// 子任务
|
||||
$('#parent-task').hide();
|
||||
$('#child-task').show();
|
||||
}
|
||||
}
|
||||
|
||||
var $uiForm = $('.ui.form');
|
||||
registerSelectFormValidation("selectProtocol", $uiForm, $('#protocol'), 'protocol');
|
||||
$($uiForm).form(
|
||||
@@ -286,6 +348,12 @@
|
||||
return false;
|
||||
}
|
||||
fields.notify_receiver_id = parseNotifyReceiver();
|
||||
fields.host_id = parseHostId();
|
||||
if (fields.protocol == 2 && fields.host_id == "") {
|
||||
swal('错误提示', '请选择任务节点');
|
||||
return false;
|
||||
}
|
||||
|
||||
util.post('/task/store', fields, function(code, message) {
|
||||
location.href = "/task"
|
||||
});
|
||||
@@ -306,19 +374,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
spec: {
|
||||
identifier : 'spec',
|
||||
rules: [
|
||||
{
|
||||
type : 'empty',
|
||||
prompt : '请输入crontab格式表达式'
|
||||
},
|
||||
{
|
||||
type : 'maxLength[64]',
|
||||
prompt : '长度不能超过64'
|
||||
}
|
||||
]
|
||||
},
|
||||
command: {
|
||||
identifier : 'command',
|
||||
rules: [
|
||||
@@ -341,6 +396,24 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
timeout: {
|
||||
identifier : 'timeout',
|
||||
rules: [
|
||||
{
|
||||
type : 'integer[0..86400]',
|
||||
prompt : '超时范围0-86400'
|
||||
}
|
||||
]
|
||||
},
|
||||
retryTimes: {
|
||||
identifier : 'retry_times',
|
||||
rules: [
|
||||
{
|
||||
type : 'integer[0..10]',
|
||||
prompt : '重试次数0-10'
|
||||
}
|
||||
]
|
||||
},
|
||||
remark: {
|
||||
identifier : 'remark',
|
||||
rules: [
|
||||
|
||||
@@ -13,6 +13,12 @@
|
||||
<input type="password" name="password" placeholder="密码">
|
||||
</div>
|
||||
</div>
|
||||
<div class="two fields">
|
||||
<div class="field">
|
||||
{{{.Captcha.CreateHtml}}}
|
||||
<input type="text" name="captcha" placeholder="验证码">
|
||||
</div>
|
||||
</div>
|
||||
<button class="ui button primary button" >登录</button>
|
||||
</form>
|
||||
</div>
|
||||
@@ -25,6 +31,10 @@
|
||||
onSuccess: function(event, fields) {
|
||||
util.post('/user/login', fields, function(code, message) {
|
||||
location.href = "/"
|
||||
}, function (code, message) {
|
||||
if (code == 5) {
|
||||
$('.captcha-img').trigger('click');
|
||||
}
|
||||
});
|
||||
|
||||
return false;
|
||||
@@ -47,6 +57,15 @@
|
||||
prompt : '请输入密码'
|
||||
}
|
||||
]
|
||||
},
|
||||
Captcha: {
|
||||
identifier : 'captcha',
|
||||
rules: [
|
||||
{
|
||||
type : 'empty',
|
||||
prompt : '请输入验证码'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
inline : true
|
||||
|
||||
@@ -11,7 +11,11 @@ fi
|
||||
for i in linux darwin windows
|
||||
do
|
||||
./build.sh -p $i
|
||||
if [[ ! $? ]];then
|
||||
if [[ $? != 0 ]];then
|
||||
break
|
||||
fi
|
||||
./build_node.sh -p $i
|
||||
if [[ $? != 0 ]];then
|
||||
break
|
||||
fi
|
||||
done
|
||||
@@ -25,7 +29,7 @@ do
|
||||
# 上传文件 qrsctl put bucket key srcFile
|
||||
KEY=gocron/$i
|
||||
qrsctl put github $KEY $i
|
||||
if [[ ! $? ]];then
|
||||
if [[ $? != 0 ]];then
|
||||
break
|
||||
fi
|
||||
echo "刷新七牛CDN-" $QINIU_URL/$KEY
|
||||
|
||||
+191
@@ -0,0 +1,191 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction, and
|
||||
distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by the copyright
|
||||
owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all other entities
|
||||
that control, are controlled by, or are under common control with that entity.
|
||||
For the purposes of this definition, "control" means (i) the power, direct or
|
||||
indirect, to cause the direction or management of such entity, whether by
|
||||
contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity exercising
|
||||
permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications, including
|
||||
but not limited to software source code, documentation source, and configuration
|
||||
files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical transformation or
|
||||
translation of a Source form, including but not limited to compiled object code,
|
||||
generated documentation, and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or Object form, made
|
||||
available under the License, as indicated by a copyright notice that is included
|
||||
in or attached to the work (an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object form, that
|
||||
is based on (or derived from) the Work and for which the editorial revisions,
|
||||
annotations, elaborations, or other modifications represent, as a whole, an
|
||||
original work of authorship. For the purposes of this License, Derivative Works
|
||||
shall not include works that remain separable from, or merely link (or bind by
|
||||
name) to the interfaces of, the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including the original version
|
||||
of the Work and any modifications or additions to that Work or Derivative Works
|
||||
thereof, that is intentionally submitted to Licensor for inclusion in the Work
|
||||
by the copyright owner or by an individual or Legal Entity authorized to submit
|
||||
on behalf of the copyright owner. For the purposes of this definition,
|
||||
"submitted" means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems, and
|
||||
issue tracking systems that are managed by, or on behalf of, the Licensor for
|
||||
the purpose of discussing and improving the Work, but excluding communication
|
||||
that is conspicuously marked or otherwise designated in writing by the copyright
|
||||
owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
|
||||
of whom a Contribution has been received by Licensor and subsequently
|
||||
incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License.
|
||||
|
||||
Subject to the terms and conditions of this License, each Contributor hereby
|
||||
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
|
||||
irrevocable copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the Work and such
|
||||
Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License.
|
||||
|
||||
Subject to the terms and conditions of this License, each Contributor hereby
|
||||
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
|
||||
irrevocable (except as stated in this section) patent license to make, have
|
||||
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
|
||||
such license applies only to those patent claims licensable by such Contributor
|
||||
that are necessarily infringed by their Contribution(s) alone or by combination
|
||||
of their Contribution(s) with the Work to which such Contribution(s) was
|
||||
submitted. If You institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
|
||||
Contribution incorporated within the Work constitutes direct or contributory
|
||||
patent infringement, then any patent licenses granted to You under this License
|
||||
for that Work shall terminate as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution.
|
||||
|
||||
You may reproduce and distribute copies of the Work or Derivative Works thereof
|
||||
in any medium, with or without modifications, and in Source or Object form,
|
||||
provided that You meet the following conditions:
|
||||
|
||||
You must give any other recipients of the Work or Derivative Works a copy of
|
||||
this License; and
|
||||
You must cause any modified files to carry prominent notices stating that You
|
||||
changed the files; and
|
||||
You must retain, in the Source form of any Derivative Works that You distribute,
|
||||
all copyright, patent, trademark, and attribution notices from the Source form
|
||||
of the Work, excluding those notices that do not pertain to any part of the
|
||||
Derivative Works; and
|
||||
If the Work includes a "NOTICE" text file as part of its distribution, then any
|
||||
Derivative Works that You distribute must include a readable copy of the
|
||||
attribution notices contained within such NOTICE file, excluding those notices
|
||||
that do not pertain to any part of the Derivative Works, in at least one of the
|
||||
following places: within a NOTICE text file distributed as part of the
|
||||
Derivative Works; within the Source form or documentation, if provided along
|
||||
with the Derivative Works; or, within a display generated by the Derivative
|
||||
Works, if and wherever such third-party notices normally appear. The contents of
|
||||
the NOTICE file are for informational purposes only and do not modify the
|
||||
License. You may add Your own attribution notices within Derivative Works that
|
||||
You distribute, alongside or as an addendum to the NOTICE text from the Work,
|
||||
provided that such additional attribution notices cannot be construed as
|
||||
modifying the License.
|
||||
You may add Your own copyright statement to Your modifications and may provide
|
||||
additional or different license terms and conditions for use, reproduction, or
|
||||
distribution of Your modifications, or for any such Derivative Works as a whole,
|
||||
provided Your use, reproduction, and distribution of the Work otherwise complies
|
||||
with the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions.
|
||||
|
||||
Unless You explicitly state otherwise, any Contribution intentionally submitted
|
||||
for inclusion in the Work by You to the Licensor shall be under the terms and
|
||||
conditions of this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify the terms of
|
||||
any separate license agreement you may have executed with Licensor regarding
|
||||
such Contributions.
|
||||
|
||||
6. Trademarks.
|
||||
|
||||
This License does not grant permission to use the trade names, trademarks,
|
||||
service marks, or product names of the Licensor, except as required for
|
||||
reasonable and customary use in describing the origin of the Work and
|
||||
reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty.
|
||||
|
||||
Unless required by applicable law or agreed to in writing, Licensor provides the
|
||||
Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
|
||||
including, without limitation, any warranties or conditions of TITLE,
|
||||
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
|
||||
solely responsible for determining the appropriateness of using or
|
||||
redistributing the Work and assume any risks associated with Your exercise of
|
||||
permissions under this License.
|
||||
|
||||
8. Limitation of Liability.
|
||||
|
||||
In no event and under no legal theory, whether in tort (including negligence),
|
||||
contract, or otherwise, unless required by applicable law (such as deliberate
|
||||
and grossly negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special, incidental,
|
||||
or consequential damages of any character arising as a result of this License or
|
||||
out of the use or inability to use the Work (including but not limited to
|
||||
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
|
||||
any and all other commercial damages or losses), even if such Contributor has
|
||||
been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability.
|
||||
|
||||
While redistributing the Work or Derivative Works thereof, You may choose to
|
||||
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
|
||||
other liability obligations and/or rights consistent with this License. However,
|
||||
in accepting such obligations, You may act only on Your own behalf and on Your
|
||||
sole responsibility, not on behalf of any other Contributor, and only if You
|
||||
agree to indemnify, defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason of your
|
||||
accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work
|
||||
|
||||
To apply the Apache License to your work, attach the following boilerplate
|
||||
notice, with the fields enclosed by brackets "[]" replaced with your own
|
||||
identifying information. (Don't include the brackets!) The text should be
|
||||
enclosed in the appropriate comment syntax for the file format. We also
|
||||
recommend that a file or class name and description of purpose be included on
|
||||
the same "printed page" as the copyright notice for easier identification within
|
||||
third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
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.
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
# cache [](https://travis-ci.org/go-macaron/cache) [](http://gocover.io/github.com/go-macaron/cache)
|
||||
|
||||
Middleware cache provides cache management for [Macaron](https://github.com/go-macaron/macaron). It can use many cache adapters, including memory, file, Redis, Memcache, PostgreSQL, MySQL, Ledis and Nodb.
|
||||
|
||||
### Installation
|
||||
|
||||
go get github.com/go-macaron/cache
|
||||
|
||||
## Getting Help
|
||||
|
||||
- [API Reference](https://gowalker.org/github.com/go-macaron/cache)
|
||||
- [Documentation](http://go-macaron.com/docs/middlewares/cache)
|
||||
|
||||
## Credits
|
||||
|
||||
This package is a modified version of [beego/cache](https://github.com/astaxie/beego/tree/master/cache).
|
||||
|
||||
## License
|
||||
|
||||
This project is under the Apache License, Version 2.0. See the [LICENSE](LICENSE) file for the full license text.
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
// Copyright 2013 Beego Authors
|
||||
// Copyright 2014 The Macaron Authors
|
||||
//
|
||||
// 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 is a middleware that provides the cache management of Macaron.
|
||||
package cache
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gopkg.in/macaron.v1"
|
||||
)
|
||||
|
||||
const _VERSION = "0.3.0"
|
||||
|
||||
func Version() string {
|
||||
return _VERSION
|
||||
}
|
||||
|
||||
// Cache is the interface that operates the cache data.
|
||||
type Cache interface {
|
||||
// Put puts value into cache with key and expire time.
|
||||
Put(key string, val interface{}, timeout int64) error
|
||||
// Get gets cached value by given key.
|
||||
Get(key string) interface{}
|
||||
// Delete deletes cached value by given key.
|
||||
Delete(key string) error
|
||||
// Incr increases cached int-type value by given key as a counter.
|
||||
Incr(key string) error
|
||||
// Decr decreases cached int-type value by given key as a counter.
|
||||
Decr(key string) error
|
||||
// IsExist returns true if cached value exists.
|
||||
IsExist(key string) bool
|
||||
// Flush deletes all cached data.
|
||||
Flush() error
|
||||
// StartAndGC starts GC routine based on config string settings.
|
||||
StartAndGC(opt Options) error
|
||||
}
|
||||
|
||||
// Options represents a struct for specifying configuration options for the cache middleware.
|
||||
type Options struct {
|
||||
// Name of adapter. Default is "memory".
|
||||
Adapter string
|
||||
// Adapter configuration, it's corresponding to adapter.
|
||||
AdapterConfig string
|
||||
// GC interval time in seconds. Default is 60.
|
||||
Interval int
|
||||
// Occupy entire database. Default is false.
|
||||
OccupyMode bool
|
||||
// Configuration section name. Default is "cache".
|
||||
Section string
|
||||
}
|
||||
|
||||
func prepareOptions(options []Options) Options {
|
||||
var opt Options
|
||||
if len(options) > 0 {
|
||||
opt = options[0]
|
||||
}
|
||||
if len(opt.Section) == 0 {
|
||||
opt.Section = "cache"
|
||||
}
|
||||
sec := macaron.Config().Section(opt.Section)
|
||||
|
||||
if len(opt.Adapter) == 0 {
|
||||
opt.Adapter = sec.Key("ADAPTER").MustString("memory")
|
||||
}
|
||||
if opt.Interval == 0 {
|
||||
opt.Interval = sec.Key("INTERVAL").MustInt(60)
|
||||
}
|
||||
if len(opt.AdapterConfig) == 0 {
|
||||
opt.AdapterConfig = sec.Key("ADAPTER_CONFIG").MustString("data/caches")
|
||||
}
|
||||
|
||||
return opt
|
||||
}
|
||||
|
||||
// NewCacher creates and returns a new cacher by given adapter name and configuration.
|
||||
// It panics when given adapter isn't registered and starts GC automatically.
|
||||
func NewCacher(name string, opt Options) (Cache, error) {
|
||||
adapter, ok := adapters[name]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("cache: unknown adapter '%s'(forgot to import?)", name)
|
||||
}
|
||||
return adapter, adapter.StartAndGC(opt)
|
||||
}
|
||||
|
||||
// Cacher is a middleware that maps a cache.Cache service into the Macaron handler chain.
|
||||
// An single variadic cache.Options struct can be optionally provided to configure.
|
||||
func Cacher(options ...Options) macaron.Handler {
|
||||
opt := prepareOptions(options)
|
||||
cache, err := NewCacher(opt.Adapter, opt)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return func(ctx *macaron.Context) {
|
||||
ctx.Map(cache)
|
||||
}
|
||||
}
|
||||
|
||||
var adapters = make(map[string]Cache)
|
||||
|
||||
// Register registers a adapter.
|
||||
func Register(name string, adapter Cache) {
|
||||
if adapter == nil {
|
||||
panic("cache: cannot register adapter with nil value")
|
||||
}
|
||||
if _, dup := adapters[name]; dup {
|
||||
panic(fmt.Errorf("cache: cannot register adapter '%s' twice", name))
|
||||
}
|
||||
adapters[name] = adapter
|
||||
}
|
||||
+208
@@ -0,0 +1,208 @@
|
||||
// Copyright 2013 Beego Authors
|
||||
// Copyright 2014 The Macaron Authors
|
||||
//
|
||||
// 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 (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
"gopkg.in/macaron.v1"
|
||||
)
|
||||
|
||||
// Item represents a cache item.
|
||||
type Item struct {
|
||||
Val interface{}
|
||||
Created int64
|
||||
Expire int64
|
||||
}
|
||||
|
||||
func (item *Item) hasExpired() bool {
|
||||
return item.Expire > 0 &&
|
||||
(time.Now().Unix()-item.Created) >= item.Expire
|
||||
}
|
||||
|
||||
// FileCacher represents a file cache adapter implementation.
|
||||
type FileCacher struct {
|
||||
lock sync.Mutex
|
||||
rootPath string
|
||||
interval int // GC interval.
|
||||
}
|
||||
|
||||
// NewFileCacher creates and returns a new file cacher.
|
||||
func NewFileCacher() *FileCacher {
|
||||
return &FileCacher{}
|
||||
}
|
||||
|
||||
func (c *FileCacher) filepath(key string) string {
|
||||
m := md5.Sum([]byte(key))
|
||||
hash := hex.EncodeToString(m[:])
|
||||
return filepath.Join(c.rootPath, string(hash[0]), string(hash[1]), hash)
|
||||
}
|
||||
|
||||
// Put puts value into cache with key and expire time.
|
||||
// If expired is 0, it will be deleted by next GC operation.
|
||||
func (c *FileCacher) Put(key string, val interface{}, expire int64) error {
|
||||
filename := c.filepath(key)
|
||||
item := &Item{val, time.Now().Unix(), expire}
|
||||
data, err := EncodeGob(item)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
os.MkdirAll(filepath.Dir(filename), os.ModePerm)
|
||||
return ioutil.WriteFile(filename, data, os.ModePerm)
|
||||
}
|
||||
|
||||
func (c *FileCacher) read(key string) (*Item, error) {
|
||||
filename := c.filepath(key)
|
||||
|
||||
data, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
item := new(Item)
|
||||
return item, DecodeGob(data, item)
|
||||
}
|
||||
|
||||
// Get gets cached value by given key.
|
||||
func (c *FileCacher) Get(key string) interface{} {
|
||||
item, err := c.read(key)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if item.hasExpired() {
|
||||
os.Remove(c.filepath(key))
|
||||
return nil
|
||||
}
|
||||
return item.Val
|
||||
}
|
||||
|
||||
// Delete deletes cached value by given key.
|
||||
func (c *FileCacher) Delete(key string) error {
|
||||
return os.Remove(c.filepath(key))
|
||||
}
|
||||
|
||||
// Incr increases cached int-type value by given key as a counter.
|
||||
func (c *FileCacher) Incr(key string) error {
|
||||
item, err := c.read(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
item.Val, err = Incr(item.Val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.Put(key, item.Val, item.Expire)
|
||||
}
|
||||
|
||||
// Decrease cached int value.
|
||||
func (c *FileCacher) Decr(key string) error {
|
||||
item, err := c.read(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
item.Val, err = Decr(item.Val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.Put(key, item.Val, item.Expire)
|
||||
}
|
||||
|
||||
// IsExist returns true if cached value exists.
|
||||
func (c *FileCacher) IsExist(key string) bool {
|
||||
return com.IsExist(c.filepath(key))
|
||||
}
|
||||
|
||||
// Flush deletes all cached data.
|
||||
func (c *FileCacher) Flush() error {
|
||||
return os.RemoveAll(c.rootPath)
|
||||
}
|
||||
|
||||
func (c *FileCacher) startGC() {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
if c.interval < 1 {
|
||||
return
|
||||
}
|
||||
|
||||
if err := filepath.Walk(c.rootPath, func(path string, fi os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("Walk: %v", err)
|
||||
}
|
||||
|
||||
if fi.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadFile(path)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
fmt.Errorf("ReadFile: %v", err)
|
||||
}
|
||||
|
||||
item := new(Item)
|
||||
if err = DecodeGob(data, item); err != nil {
|
||||
return err
|
||||
}
|
||||
if item.hasExpired() {
|
||||
if err = os.Remove(path); err != nil && !os.IsNotExist(err) {
|
||||
return fmt.Errorf("Remove: %v", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
log.Printf("error garbage collecting cache files: %v", err)
|
||||
}
|
||||
|
||||
time.AfterFunc(time.Duration(c.interval)*time.Second, func() { c.startGC() })
|
||||
}
|
||||
|
||||
// StartAndGC starts GC routine based on config string settings.
|
||||
func (c *FileCacher) StartAndGC(opt Options) error {
|
||||
c.lock.Lock()
|
||||
c.rootPath = opt.AdapterConfig
|
||||
c.interval = opt.Interval
|
||||
|
||||
if !filepath.IsAbs(c.rootPath) {
|
||||
c.rootPath = filepath.Join(macaron.Root, c.rootPath)
|
||||
}
|
||||
c.lock.Unlock()
|
||||
|
||||
if err := os.MkdirAll(c.rootPath, os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go c.startGC()
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
Register("file", NewFileCacher())
|
||||
}
|
||||
+179
@@ -0,0 +1,179 @@
|
||||
// Copyright 2013 Beego Authors
|
||||
// Copyright 2014 The Macaron Authors
|
||||
//
|
||||
// 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 (
|
||||
"errors"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// MemoryItem represents a memory cache item.
|
||||
type MemoryItem struct {
|
||||
val interface{}
|
||||
created int64
|
||||
expire int64
|
||||
}
|
||||
|
||||
func (item *MemoryItem) hasExpired() bool {
|
||||
return item.expire > 0 &&
|
||||
(time.Now().Unix()-item.created) >= item.expire
|
||||
}
|
||||
|
||||
// MemoryCacher represents a memory cache adapter implementation.
|
||||
type MemoryCacher struct {
|
||||
lock sync.RWMutex
|
||||
items map[string]*MemoryItem
|
||||
interval int // GC interval.
|
||||
}
|
||||
|
||||
// NewMemoryCacher creates and returns a new memory cacher.
|
||||
func NewMemoryCacher() *MemoryCacher {
|
||||
return &MemoryCacher{items: make(map[string]*MemoryItem)}
|
||||
}
|
||||
|
||||
// Put puts value into cache with key and expire time.
|
||||
// If expired is 0, it will be deleted by next GC operation.
|
||||
func (c *MemoryCacher) Put(key string, val interface{}, expire int64) error {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
c.items[key] = &MemoryItem{
|
||||
val: val,
|
||||
created: time.Now().Unix(),
|
||||
expire: expire,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get gets cached value by given key.
|
||||
func (c *MemoryCacher) Get(key string) interface{} {
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
|
||||
item, ok := c.items[key]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
if item.hasExpired() {
|
||||
go c.Delete(key)
|
||||
return nil
|
||||
}
|
||||
return item.val
|
||||
}
|
||||
|
||||
// Delete deletes cached value by given key.
|
||||
func (c *MemoryCacher) Delete(key string) error {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
delete(c.items, key)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Incr increases cached int-type value by given key as a counter.
|
||||
func (c *MemoryCacher) Incr(key string) (err error) {
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
|
||||
item, ok := c.items[key]
|
||||
if !ok {
|
||||
return errors.New("key not exist")
|
||||
}
|
||||
item.val, err = Incr(item.val)
|
||||
return err
|
||||
}
|
||||
|
||||
// Decr decreases cached int-type value by given key as a counter.
|
||||
func (c *MemoryCacher) Decr(key string) (err error) {
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
|
||||
item, ok := c.items[key]
|
||||
if !ok {
|
||||
return errors.New("key not exist")
|
||||
}
|
||||
|
||||
item.val, err = Decr(item.val)
|
||||
return err
|
||||
}
|
||||
|
||||
// IsExist returns true if cached value exists.
|
||||
func (c *MemoryCacher) IsExist(key string) bool {
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
|
||||
_, ok := c.items[key]
|
||||
return ok
|
||||
}
|
||||
|
||||
// Flush deletes all cached data.
|
||||
func (c *MemoryCacher) Flush() error {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
c.items = make(map[string]*MemoryItem)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *MemoryCacher) checkRawExpiration(key string) {
|
||||
item, ok := c.items[key]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
if item.hasExpired() {
|
||||
delete(c.items, key)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *MemoryCacher) checkExpiration(key string) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
c.checkRawExpiration(key)
|
||||
}
|
||||
|
||||
func (c *MemoryCacher) startGC() {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
if c.interval < 1 {
|
||||
return
|
||||
}
|
||||
|
||||
if c.items != nil {
|
||||
for key, _ := range c.items {
|
||||
c.checkRawExpiration(key)
|
||||
}
|
||||
}
|
||||
|
||||
time.AfterFunc(time.Duration(c.interval)*time.Second, func() { c.startGC() })
|
||||
}
|
||||
|
||||
// StartAndGC starts GC routine based on config string settings.
|
||||
func (c *MemoryCacher) StartAndGC(opt Options) error {
|
||||
c.lock.Lock()
|
||||
c.interval = opt.Interval
|
||||
c.lock.Unlock()
|
||||
|
||||
go c.startGC()
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
Register("memory", NewMemoryCacher())
|
||||
}
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
// Copyright 2014 The Macaron Authors
|
||||
//
|
||||
// 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"
|
||||
"encoding/gob"
|
||||
"errors"
|
||||
)
|
||||
|
||||
func EncodeGob(item *Item) ([]byte, error) {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
err := gob.NewEncoder(buf).Encode(item)
|
||||
return buf.Bytes(), err
|
||||
}
|
||||
|
||||
func DecodeGob(data []byte, out *Item) error {
|
||||
buf := bytes.NewBuffer(data)
|
||||
return gob.NewDecoder(buf).Decode(&out)
|
||||
}
|
||||
|
||||
func Incr(val interface{}) (interface{}, error) {
|
||||
switch val.(type) {
|
||||
case int:
|
||||
val = val.(int) + 1
|
||||
case int32:
|
||||
val = val.(int32) + 1
|
||||
case int64:
|
||||
val = val.(int64) + 1
|
||||
case uint:
|
||||
val = val.(uint) + 1
|
||||
case uint32:
|
||||
val = val.(uint32) + 1
|
||||
case uint64:
|
||||
val = val.(uint64) + 1
|
||||
default:
|
||||
return val, errors.New("item value is not int-type")
|
||||
}
|
||||
return val, nil
|
||||
}
|
||||
|
||||
func Decr(val interface{}) (interface{}, error) {
|
||||
switch val.(type) {
|
||||
case int:
|
||||
val = val.(int) - 1
|
||||
case int32:
|
||||
val = val.(int32) - 1
|
||||
case int64:
|
||||
val = val.(int64) - 1
|
||||
case uint:
|
||||
if val.(uint) > 0 {
|
||||
val = val.(uint) - 1
|
||||
} else {
|
||||
return val, errors.New("item value is less than 0")
|
||||
}
|
||||
case uint32:
|
||||
if val.(uint32) > 0 {
|
||||
val = val.(uint32) - 1
|
||||
} else {
|
||||
return val, errors.New("item value is less than 0")
|
||||
}
|
||||
case uint64:
|
||||
if val.(uint64) > 0 {
|
||||
val = val.(uint64) - 1
|
||||
} else {
|
||||
return val, errors.New("item value is less than 0")
|
||||
}
|
||||
default:
|
||||
return val, errors.New("item value is not int-type")
|
||||
}
|
||||
return val, nil
|
||||
}
|
||||
+191
@@ -0,0 +1,191 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction, and
|
||||
distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by the copyright
|
||||
owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all other entities
|
||||
that control, are controlled by, or are under common control with that entity.
|
||||
For the purposes of this definition, "control" means (i) the power, direct or
|
||||
indirect, to cause the direction or management of such entity, whether by
|
||||
contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity exercising
|
||||
permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications, including
|
||||
but not limited to software source code, documentation source, and configuration
|
||||
files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical transformation or
|
||||
translation of a Source form, including but not limited to compiled object code,
|
||||
generated documentation, and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or Object form, made
|
||||
available under the License, as indicated by a copyright notice that is included
|
||||
in or attached to the work (an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object form, that
|
||||
is based on (or derived from) the Work and for which the editorial revisions,
|
||||
annotations, elaborations, or other modifications represent, as a whole, an
|
||||
original work of authorship. For the purposes of this License, Derivative Works
|
||||
shall not include works that remain separable from, or merely link (or bind by
|
||||
name) to the interfaces of, the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including the original version
|
||||
of the Work and any modifications or additions to that Work or Derivative Works
|
||||
thereof, that is intentionally submitted to Licensor for inclusion in the Work
|
||||
by the copyright owner or by an individual or Legal Entity authorized to submit
|
||||
on behalf of the copyright owner. For the purposes of this definition,
|
||||
"submitted" means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems, and
|
||||
issue tracking systems that are managed by, or on behalf of, the Licensor for
|
||||
the purpose of discussing and improving the Work, but excluding communication
|
||||
that is conspicuously marked or otherwise designated in writing by the copyright
|
||||
owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
|
||||
of whom a Contribution has been received by Licensor and subsequently
|
||||
incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License.
|
||||
|
||||
Subject to the terms and conditions of this License, each Contributor hereby
|
||||
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
|
||||
irrevocable copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the Work and such
|
||||
Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License.
|
||||
|
||||
Subject to the terms and conditions of this License, each Contributor hereby
|
||||
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
|
||||
irrevocable (except as stated in this section) patent license to make, have
|
||||
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
|
||||
such license applies only to those patent claims licensable by such Contributor
|
||||
that are necessarily infringed by their Contribution(s) alone or by combination
|
||||
of their Contribution(s) with the Work to which such Contribution(s) was
|
||||
submitted. If You institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
|
||||
Contribution incorporated within the Work constitutes direct or contributory
|
||||
patent infringement, then any patent licenses granted to You under this License
|
||||
for that Work shall terminate as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution.
|
||||
|
||||
You may reproduce and distribute copies of the Work or Derivative Works thereof
|
||||
in any medium, with or without modifications, and in Source or Object form,
|
||||
provided that You meet the following conditions:
|
||||
|
||||
You must give any other recipients of the Work or Derivative Works a copy of
|
||||
this License; and
|
||||
You must cause any modified files to carry prominent notices stating that You
|
||||
changed the files; and
|
||||
You must retain, in the Source form of any Derivative Works that You distribute,
|
||||
all copyright, patent, trademark, and attribution notices from the Source form
|
||||
of the Work, excluding those notices that do not pertain to any part of the
|
||||
Derivative Works; and
|
||||
If the Work includes a "NOTICE" text file as part of its distribution, then any
|
||||
Derivative Works that You distribute must include a readable copy of the
|
||||
attribution notices contained within such NOTICE file, excluding those notices
|
||||
that do not pertain to any part of the Derivative Works, in at least one of the
|
||||
following places: within a NOTICE text file distributed as part of the
|
||||
Derivative Works; within the Source form or documentation, if provided along
|
||||
with the Derivative Works; or, within a display generated by the Derivative
|
||||
Works, if and wherever such third-party notices normally appear. The contents of
|
||||
the NOTICE file are for informational purposes only and do not modify the
|
||||
License. You may add Your own attribution notices within Derivative Works that
|
||||
You distribute, alongside or as an addendum to the NOTICE text from the Work,
|
||||
provided that such additional attribution notices cannot be construed as
|
||||
modifying the License.
|
||||
You may add Your own copyright statement to Your modifications and may provide
|
||||
additional or different license terms and conditions for use, reproduction, or
|
||||
distribution of Your modifications, or for any such Derivative Works as a whole,
|
||||
provided Your use, reproduction, and distribution of the Work otherwise complies
|
||||
with the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions.
|
||||
|
||||
Unless You explicitly state otherwise, any Contribution intentionally submitted
|
||||
for inclusion in the Work by You to the Licensor shall be under the terms and
|
||||
conditions of this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify the terms of
|
||||
any separate license agreement you may have executed with Licensor regarding
|
||||
such Contributions.
|
||||
|
||||
6. Trademarks.
|
||||
|
||||
This License does not grant permission to use the trade names, trademarks,
|
||||
service marks, or product names of the Licensor, except as required for
|
||||
reasonable and customary use in describing the origin of the Work and
|
||||
reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty.
|
||||
|
||||
Unless required by applicable law or agreed to in writing, Licensor provides the
|
||||
Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
|
||||
including, without limitation, any warranties or conditions of TITLE,
|
||||
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
|
||||
solely responsible for determining the appropriateness of using or
|
||||
redistributing the Work and assume any risks associated with Your exercise of
|
||||
permissions under this License.
|
||||
|
||||
8. Limitation of Liability.
|
||||
|
||||
In no event and under no legal theory, whether in tort (including negligence),
|
||||
contract, or otherwise, unless required by applicable law (such as deliberate
|
||||
and grossly negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special, incidental,
|
||||
or consequential damages of any character arising as a result of this License or
|
||||
out of the use or inability to use the Work (including but not limited to
|
||||
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
|
||||
any and all other commercial damages or losses), even if such Contributor has
|
||||
been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability.
|
||||
|
||||
While redistributing the Work or Derivative Works thereof, You may choose to
|
||||
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
|
||||
other liability obligations and/or rights consistent with this License. However,
|
||||
in accepting such obligations, You may act only on Your own behalf and on Your
|
||||
sole responsibility, not on behalf of any other Contributor, and only if You
|
||||
agree to indemnify, defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason of your
|
||||
accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work
|
||||
|
||||
To apply the Apache License to your work, attach the following boilerplate
|
||||
notice, with the fields enclosed by brackets "[]" replaced with your own
|
||||
identifying information. (Don't include the brackets!) The text should be
|
||||
enclosed in the appropriate comment syntax for the file format. We also
|
||||
recommend that a file or class name and description of purpose be included on
|
||||
the same "printed page" as the copyright notice for easier identification within
|
||||
third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
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.
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
# captcha [](https://travis-ci.org/go-macaron/captcha)
|
||||
|
||||
Middleware captcha provides captcha service for [Macaron](https://github.com/go-macaron/macaron).
|
||||
|
||||
### Installation
|
||||
|
||||
go get github.com/go-macaron/captcha
|
||||
|
||||
## Getting Help
|
||||
|
||||
- [API Reference](https://gowalker.org/github.com/go-macaron/captcha)
|
||||
- [Documentation](http://go-macaron.com/docs/middlewares/captcha)
|
||||
|
||||
## License
|
||||
|
||||
This project is under the Apache License, Version 2.0. See the [LICENSE](LICENSE) file for the full license text.
|
||||
+246
@@ -0,0 +1,246 @@
|
||||
// Copyright 2013 Beego Authors
|
||||
// Copyright 2014 The Macaron Authors
|
||||
//
|
||||
// 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 captcha a middleware that provides captcha service for Macaron.
|
||||
package captcha
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
"github.com/go-macaron/cache"
|
||||
"gopkg.in/macaron.v1"
|
||||
)
|
||||
|
||||
const _VERSION = "0.1.0"
|
||||
|
||||
func Version() string {
|
||||
return _VERSION
|
||||
}
|
||||
|
||||
var (
|
||||
defaultChars = []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
|
||||
)
|
||||
|
||||
// Captcha represents a captcha service.
|
||||
type Captcha struct {
|
||||
store cache.Cache
|
||||
SubURL string
|
||||
URLPrefix string
|
||||
FieldIdName string
|
||||
FieldCaptchaName string
|
||||
StdWidth int
|
||||
StdHeight int
|
||||
ChallengeNums int
|
||||
Expiration int64
|
||||
CachePrefix string
|
||||
}
|
||||
|
||||
// generate key string
|
||||
func (c *Captcha) key(id string) string {
|
||||
return c.CachePrefix + id
|
||||
}
|
||||
|
||||
// generate rand chars with default chars
|
||||
func (c *Captcha) genRandChars() string {
|
||||
return string(com.RandomCreateBytes(c.ChallengeNums, defaultChars...))
|
||||
}
|
||||
|
||||
// CreateHTML outputs HTML for display and fetch new captcha images.
|
||||
func (c *Captcha) CreateHTML() template.HTML {
|
||||
value, err := c.CreateCaptcha()
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("fail to create captcha: %v", err))
|
||||
}
|
||||
return template.HTML(fmt.Sprintf(`<input type="hidden" name="%[1]s" value="%[2]s">
|
||||
<a class="captcha" href="javascript:" tabindex="-1">
|
||||
<img onclick="this.src=('%[3]s%[4]s%[2]s.png?reload='+(new Date()).getTime())" class="captcha-img" src="%[3]s%[4]s%[2]s.png">
|
||||
</a>`, c.FieldIdName, value, c.SubURL, c.URLPrefix))
|
||||
}
|
||||
|
||||
// DEPRECATED
|
||||
func (c *Captcha) CreateHtml() template.HTML {
|
||||
return c.CreateHTML()
|
||||
}
|
||||
|
||||
// create a new captcha id
|
||||
func (c *Captcha) CreateCaptcha() (string, error) {
|
||||
id := string(com.RandomCreateBytes(15))
|
||||
if err := c.store.Put(c.key(id), c.genRandChars(), c.Expiration); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return id, nil
|
||||
}
|
||||
|
||||
// verify from a request
|
||||
func (c *Captcha) VerifyReq(req macaron.Request) bool {
|
||||
req.ParseForm()
|
||||
return c.Verify(req.Form.Get(c.FieldIdName), req.Form.Get(c.FieldCaptchaName))
|
||||
}
|
||||
|
||||
// direct verify id and challenge string
|
||||
func (c *Captcha) Verify(id string, challenge string) bool {
|
||||
if len(challenge) == 0 || len(id) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
var chars string
|
||||
|
||||
key := c.key(id)
|
||||
|
||||
if v, ok := c.store.Get(key).(string); ok {
|
||||
chars = v
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
|
||||
defer c.store.Delete(key)
|
||||
|
||||
if len(chars) != len(challenge) {
|
||||
return false
|
||||
}
|
||||
|
||||
// verify challenge
|
||||
for i, c := range []byte(chars) {
|
||||
if c != challenge[i]-48 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
type Options struct {
|
||||
// Suburl path. Default is empty.
|
||||
SubURL string
|
||||
// URL prefix of getting captcha pictures. Default is "/captcha/".
|
||||
URLPrefix string
|
||||
// Hidden input element ID. Default is "captcha_id".
|
||||
FieldIdName string
|
||||
// User input value element name in request form. Default is "captcha".
|
||||
FieldCaptchaName string
|
||||
// Challenge number. Default is 6.
|
||||
ChallengeNums int
|
||||
// Captcha image width. Default is 240.
|
||||
Width int
|
||||
// Captcha image height. Default is 80.
|
||||
Height int
|
||||
// Captcha expiration time in seconds. Default is 600.
|
||||
Expiration int64
|
||||
// Cache key prefix captcha characters. Default is "captcha_".
|
||||
CachePrefix string
|
||||
}
|
||||
|
||||
func prepareOptions(options []Options) Options {
|
||||
var opt Options
|
||||
if len(options) > 0 {
|
||||
opt = options[0]
|
||||
}
|
||||
|
||||
opt.SubURL = strings.TrimSuffix(opt.SubURL, "/")
|
||||
|
||||
// Defaults.
|
||||
if len(opt.URLPrefix) == 0 {
|
||||
opt.URLPrefix = "/captcha/"
|
||||
} else if opt.URLPrefix[len(opt.URLPrefix)-1] != '/' {
|
||||
opt.URLPrefix += "/"
|
||||
}
|
||||
if len(opt.FieldIdName) == 0 {
|
||||
opt.FieldIdName = "captcha_id"
|
||||
}
|
||||
if len(opt.FieldCaptchaName) == 0 {
|
||||
opt.FieldCaptchaName = "captcha"
|
||||
}
|
||||
if opt.ChallengeNums == 0 {
|
||||
opt.ChallengeNums = 6
|
||||
}
|
||||
if opt.Width == 0 {
|
||||
opt.Width = stdWidth
|
||||
}
|
||||
if opt.Height == 0 {
|
||||
opt.Height = stdHeight
|
||||
}
|
||||
if opt.Expiration == 0 {
|
||||
opt.Expiration = 600
|
||||
}
|
||||
if len(opt.CachePrefix) == 0 {
|
||||
opt.CachePrefix = "captcha_"
|
||||
}
|
||||
|
||||
return opt
|
||||
}
|
||||
|
||||
// NewCaptcha initializes and returns a captcha with given options.
|
||||
func NewCaptcha(opt Options) *Captcha {
|
||||
return &Captcha{
|
||||
SubURL: opt.SubURL,
|
||||
URLPrefix: opt.URLPrefix,
|
||||
FieldIdName: opt.FieldIdName,
|
||||
FieldCaptchaName: opt.FieldCaptchaName,
|
||||
StdWidth: opt.Width,
|
||||
StdHeight: opt.Height,
|
||||
ChallengeNums: opt.ChallengeNums,
|
||||
Expiration: opt.Expiration,
|
||||
CachePrefix: opt.CachePrefix,
|
||||
}
|
||||
}
|
||||
|
||||
// Captchaer is a middleware that maps a captcha.Captcha service into the Macaron handler chain.
|
||||
// An single variadic captcha.Options struct can be optionally provided to configure.
|
||||
// This should be register after cache.Cacher.
|
||||
func Captchaer(options ...Options) macaron.Handler {
|
||||
return func(ctx *macaron.Context, cache cache.Cache) {
|
||||
cpt := NewCaptcha(prepareOptions(options))
|
||||
cpt.store = cache
|
||||
|
||||
if strings.HasPrefix(ctx.Req.URL.Path, cpt.URLPrefix) {
|
||||
var chars string
|
||||
id := path.Base(ctx.Req.URL.Path)
|
||||
if i := strings.Index(id, "."); i > -1 {
|
||||
id = id[:i]
|
||||
}
|
||||
key := cpt.key(id)
|
||||
|
||||
// Reload captcha.
|
||||
if len(ctx.Query("reload")) > 0 {
|
||||
chars = cpt.genRandChars()
|
||||
if err := cpt.store.Put(key, chars, cpt.Expiration); err != nil {
|
||||
ctx.Status(500)
|
||||
ctx.Write([]byte("captcha reload error"))
|
||||
panic(fmt.Errorf("reload captcha: %v", err))
|
||||
}
|
||||
} else {
|
||||
if v, ok := cpt.store.Get(key).(string); ok {
|
||||
chars = v
|
||||
} else {
|
||||
ctx.Status(404)
|
||||
ctx.Write([]byte("captcha not found"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := NewImage([]byte(chars), cpt.StdWidth, cpt.StdHeight).WriteTo(ctx.Resp); err != nil {
|
||||
panic(fmt.Errorf("write captcha: %v", err))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Data["Captcha"] = cpt
|
||||
ctx.Map(cpt)
|
||||
}
|
||||
}
|
||||
+498
@@ -0,0 +1,498 @@
|
||||
// Copyright 2013 Beego Authors
|
||||
//
|
||||
// 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 captcha
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"image"
|
||||
"image/color"
|
||||
"image/png"
|
||||
"io"
|
||||
"math"
|
||||
)
|
||||
|
||||
const (
|
||||
fontWidth = 11
|
||||
fontHeight = 18
|
||||
blackChar = 1
|
||||
|
||||
// Standard width and height of a captcha image.
|
||||
stdWidth = 240
|
||||
stdHeight = 80
|
||||
|
||||
// Maximum absolute skew factor of a single digit.
|
||||
maxSkew = 0.7
|
||||
// Number of background circles.
|
||||
circleCount = 20
|
||||
)
|
||||
|
||||
var font = [][]byte{
|
||||
{ // 0
|
||||
0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0,
|
||||
0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0,
|
||||
0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0,
|
||||
0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0,
|
||||
1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0,
|
||||
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
|
||||
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
|
||||
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
|
||||
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
|
||||
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
|
||||
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
|
||||
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
|
||||
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
|
||||
1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1,
|
||||
0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0,
|
||||
0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0,
|
||||
0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0,
|
||||
0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0,
|
||||
},
|
||||
{ // 1
|
||||
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0,
|
||||
0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0,
|
||||
0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0,
|
||||
0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0,
|
||||
0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
|
||||
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
},
|
||||
{ // 2
|
||||
0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0,
|
||||
0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
|
||||
1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0,
|
||||
0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0,
|
||||
0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0,
|
||||
0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0,
|
||||
0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
},
|
||||
{ // 3
|
||||
0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
|
||||
1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
|
||||
0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0,
|
||||
0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0,
|
||||
0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
|
||||
1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
|
||||
0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0,
|
||||
},
|
||||
{ // 4
|
||||
0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0,
|
||||
0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0,
|
||||
0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 0,
|
||||
0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0,
|
||||
0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0,
|
||||
0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0,
|
||||
0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0,
|
||||
0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0,
|
||||
0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0,
|
||||
1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0,
|
||||
1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
|
||||
},
|
||||
{ // 5
|
||||
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
|
||||
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
|
||||
0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0,
|
||||
0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
|
||||
1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
|
||||
0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0,
|
||||
},
|
||||
{ // 6
|
||||
0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0,
|
||||
0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0,
|
||||
0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0,
|
||||
0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0,
|
||||
1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0,
|
||||
1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0,
|
||||
1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1,
|
||||
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
|
||||
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
|
||||
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
|
||||
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
|
||||
0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1,
|
||||
0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0,
|
||||
0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0,
|
||||
0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0,
|
||||
},
|
||||
{ // 7
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1,
|
||||
1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0,
|
||||
},
|
||||
{ // 8
|
||||
0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0,
|
||||
0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0,
|
||||
0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1,
|
||||
0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1,
|
||||
0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1,
|
||||
0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1,
|
||||
0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0,
|
||||
0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0,
|
||||
0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0,
|
||||
0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0,
|
||||
0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0,
|
||||
1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1,
|
||||
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
|
||||
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
|
||||
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
|
||||
1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0,
|
||||
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
|
||||
0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0,
|
||||
},
|
||||
{ // 9
|
||||
0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0,
|
||||
0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
|
||||
0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0,
|
||||
1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0,
|
||||
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
|
||||
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
|
||||
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1,
|
||||
1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1,
|
||||
0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1,
|
||||
0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1,
|
||||
0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0,
|
||||
0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0,
|
||||
0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0,
|
||||
0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
|
||||
},
|
||||
}
|
||||
|
||||
type Image struct {
|
||||
*image.Paletted
|
||||
numWidth int
|
||||
numHeight int
|
||||
dotSize int
|
||||
}
|
||||
|
||||
var prng = &siprng{}
|
||||
|
||||
// randIntn returns a pseudorandom non-negative int in range [0, n).
|
||||
func randIntn(n int) int {
|
||||
return prng.Intn(n)
|
||||
}
|
||||
|
||||
// randInt returns a pseudorandom int in range [from, to].
|
||||
func randInt(from, to int) int {
|
||||
return prng.Intn(to+1-from) + from
|
||||
}
|
||||
|
||||
// randFloat returns a pseudorandom float64 in range [from, to].
|
||||
func randFloat(from, to float64) float64 {
|
||||
return (to-from)*prng.Float64() + from
|
||||
}
|
||||
|
||||
func randomPalette() color.Palette {
|
||||
p := make([]color.Color, circleCount+1)
|
||||
// Transparent color.
|
||||
p[0] = color.RGBA{0xFF, 0xFF, 0xFF, 0x00}
|
||||
// Primary color.
|
||||
prim := color.RGBA{
|
||||
uint8(randIntn(129)),
|
||||
uint8(randIntn(129)),
|
||||
uint8(randIntn(129)),
|
||||
0xFF,
|
||||
}
|
||||
p[1] = prim
|
||||
// Circle colors.
|
||||
for i := 2; i <= circleCount; i++ {
|
||||
p[i] = randomBrightness(prim, 255)
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
// NewImage returns a new captcha image of the given width and height with the
|
||||
// given digits, where each digit must be in range 0-9.
|
||||
func NewImage(digits []byte, width, height int) *Image {
|
||||
m := new(Image)
|
||||
m.Paletted = image.NewPaletted(image.Rect(0, 0, width, height), randomPalette())
|
||||
m.calculateSizes(width, height, len(digits))
|
||||
// Randomly position captcha inside the image.
|
||||
maxx := width - (m.numWidth+m.dotSize)*len(digits) - m.dotSize
|
||||
maxy := height - m.numHeight - m.dotSize*2
|
||||
var border int
|
||||
if width > height {
|
||||
border = height / 5
|
||||
} else {
|
||||
border = width / 5
|
||||
}
|
||||
x := randInt(border, maxx-border)
|
||||
y := randInt(border, maxy-border)
|
||||
// Draw digits.
|
||||
for _, n := range digits {
|
||||
m.drawDigit(font[n], x, y)
|
||||
x += m.numWidth + m.dotSize
|
||||
}
|
||||
// Draw strike-through line.
|
||||
m.strikeThrough()
|
||||
// Apply wave distortion.
|
||||
m.distort(randFloat(5, 10), randFloat(100, 200))
|
||||
// Fill image with random circles.
|
||||
m.fillWithCircles(circleCount, m.dotSize)
|
||||
return m
|
||||
}
|
||||
|
||||
// encodedPNG encodes an image to PNG and returns
|
||||
// the result as a byte slice.
|
||||
func (m *Image) encodedPNG() []byte {
|
||||
var buf bytes.Buffer
|
||||
if err := png.Encode(&buf, m.Paletted); err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
// WriteTo writes captcha image in PNG format into the given writer.
|
||||
func (m *Image) WriteTo(w io.Writer) (int64, error) {
|
||||
n, err := w.Write(m.encodedPNG())
|
||||
return int64(n), err
|
||||
}
|
||||
|
||||
func (m *Image) calculateSizes(width, height, ncount int) {
|
||||
// Goal: fit all digits inside the image.
|
||||
var border int
|
||||
if width > height {
|
||||
border = height / 4
|
||||
} else {
|
||||
border = width / 4
|
||||
}
|
||||
// Convert everything to floats for calculations.
|
||||
w := float64(width - border*2)
|
||||
h := float64(height - border*2)
|
||||
// fw takes into account 1-dot spacing between digits.
|
||||
fw := float64(fontWidth + 1)
|
||||
fh := float64(fontHeight)
|
||||
nc := float64(ncount)
|
||||
// Calculate the width of a single digit taking into account only the
|
||||
// width of the image.
|
||||
nw := w / nc
|
||||
// Calculate the height of a digit from this width.
|
||||
nh := nw * fh / fw
|
||||
// Digit too high?
|
||||
if nh > h {
|
||||
// Fit digits based on height.
|
||||
nh = h
|
||||
nw = fw / fh * nh
|
||||
}
|
||||
// Calculate dot size.
|
||||
m.dotSize = int(nh / fh)
|
||||
// Save everything, making the actual width smaller by 1 dot to account
|
||||
// for spacing between digits.
|
||||
m.numWidth = int(nw) - m.dotSize
|
||||
m.numHeight = int(nh)
|
||||
}
|
||||
|
||||
func (m *Image) drawHorizLine(fromX, toX, y int, colorIdx uint8) {
|
||||
for x := fromX; x <= toX; x++ {
|
||||
m.SetColorIndex(x, y, colorIdx)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Image) drawCircle(x, y, radius int, colorIdx uint8) {
|
||||
f := 1 - radius
|
||||
dfx := 1
|
||||
dfy := -2 * radius
|
||||
xo := 0
|
||||
yo := radius
|
||||
|
||||
m.SetColorIndex(x, y+radius, colorIdx)
|
||||
m.SetColorIndex(x, y-radius, colorIdx)
|
||||
m.drawHorizLine(x-radius, x+radius, y, colorIdx)
|
||||
|
||||
for xo < yo {
|
||||
if f >= 0 {
|
||||
yo--
|
||||
dfy += 2
|
||||
f += dfy
|
||||
}
|
||||
xo++
|
||||
dfx += 2
|
||||
f += dfx
|
||||
m.drawHorizLine(x-xo, x+xo, y+yo, colorIdx)
|
||||
m.drawHorizLine(x-xo, x+xo, y-yo, colorIdx)
|
||||
m.drawHorizLine(x-yo, x+yo, y+xo, colorIdx)
|
||||
m.drawHorizLine(x-yo, x+yo, y-xo, colorIdx)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Image) fillWithCircles(n, maxradius int) {
|
||||
maxx := m.Bounds().Max.X
|
||||
maxy := m.Bounds().Max.Y
|
||||
for i := 0; i < n; i++ {
|
||||
colorIdx := uint8(randInt(1, circleCount-1))
|
||||
r := randInt(1, maxradius)
|
||||
m.drawCircle(randInt(r, maxx-r), randInt(r, maxy-r), r, colorIdx)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Image) strikeThrough() {
|
||||
maxx := m.Bounds().Max.X
|
||||
maxy := m.Bounds().Max.Y
|
||||
y := randInt(maxy/3, maxy-maxy/3)
|
||||
amplitude := randFloat(5, 20)
|
||||
period := randFloat(80, 180)
|
||||
dx := 2.0 * math.Pi / period
|
||||
for x := 0; x < maxx; x++ {
|
||||
xo := amplitude * math.Cos(float64(y)*dx)
|
||||
yo := amplitude * math.Sin(float64(x)*dx)
|
||||
for yn := 0; yn < m.dotSize; yn++ {
|
||||
r := randInt(0, m.dotSize)
|
||||
m.drawCircle(x+int(xo), y+int(yo)+(yn*m.dotSize), r/2, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Image) drawDigit(digit []byte, x, y int) {
|
||||
skf := randFloat(-maxSkew, maxSkew)
|
||||
xs := float64(x)
|
||||
r := m.dotSize / 2
|
||||
y += randInt(-r, r)
|
||||
for yo := 0; yo < fontHeight; yo++ {
|
||||
for xo := 0; xo < fontWidth; xo++ {
|
||||
if digit[yo*fontWidth+xo] != blackChar {
|
||||
continue
|
||||
}
|
||||
m.drawCircle(x+xo*m.dotSize, y+yo*m.dotSize, r, 1)
|
||||
}
|
||||
xs += skf
|
||||
x = int(xs)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Image) distort(amplude float64, period float64) {
|
||||
w := m.Bounds().Max.X
|
||||
h := m.Bounds().Max.Y
|
||||
|
||||
oldm := m.Paletted
|
||||
newm := image.NewPaletted(image.Rect(0, 0, w, h), oldm.Palette)
|
||||
|
||||
dx := 2.0 * math.Pi / period
|
||||
for x := 0; x < w; x++ {
|
||||
for y := 0; y < h; y++ {
|
||||
xo := amplude * math.Sin(float64(y)*dx)
|
||||
yo := amplude * math.Cos(float64(x)*dx)
|
||||
newm.SetColorIndex(x, y, oldm.ColorIndexAt(x+int(xo), y+int(yo)))
|
||||
}
|
||||
}
|
||||
m.Paletted = newm
|
||||
}
|
||||
|
||||
func randomBrightness(c color.RGBA, max uint8) color.RGBA {
|
||||
minc := min3(c.R, c.G, c.B)
|
||||
maxc := max3(c.R, c.G, c.B)
|
||||
if maxc > max {
|
||||
return c
|
||||
}
|
||||
n := randIntn(int(max-maxc)) - int(minc)
|
||||
return color.RGBA{
|
||||
uint8(int(c.R) + n),
|
||||
uint8(int(c.G) + n),
|
||||
uint8(int(c.B) + n),
|
||||
uint8(c.A),
|
||||
}
|
||||
}
|
||||
|
||||
func min3(x, y, z uint8) (m uint8) {
|
||||
m = x
|
||||
if y < m {
|
||||
m = y
|
||||
}
|
||||
if z < m {
|
||||
m = z
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func max3(x, y, z uint8) (m uint8) {
|
||||
m = x
|
||||
if y > m {
|
||||
m = y
|
||||
}
|
||||
if z > m {
|
||||
m = z
|
||||
}
|
||||
return
|
||||
}
|
||||
+277
@@ -0,0 +1,277 @@
|
||||
// Copyright 2013 Beego Authors
|
||||
//
|
||||
// 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 captcha
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// siprng is PRNG based on SipHash-2-4.
|
||||
type siprng struct {
|
||||
mu sync.Mutex
|
||||
k0, k1, ctr uint64
|
||||
}
|
||||
|
||||
// siphash implements SipHash-2-4, accepting a uint64 as a message.
|
||||
func siphash(k0, k1, m uint64) uint64 {
|
||||
// Initialization.
|
||||
v0 := k0 ^ 0x736f6d6570736575
|
||||
v1 := k1 ^ 0x646f72616e646f6d
|
||||
v2 := k0 ^ 0x6c7967656e657261
|
||||
v3 := k1 ^ 0x7465646279746573
|
||||
t := uint64(8) << 56
|
||||
|
||||
// Compression.
|
||||
v3 ^= m
|
||||
|
||||
// Round 1.
|
||||
v0 += v1
|
||||
v1 = v1<<13 | v1>>(64-13)
|
||||
v1 ^= v0
|
||||
v0 = v0<<32 | v0>>(64-32)
|
||||
|
||||
v2 += v3
|
||||
v3 = v3<<16 | v3>>(64-16)
|
||||
v3 ^= v2
|
||||
|
||||
v0 += v3
|
||||
v3 = v3<<21 | v3>>(64-21)
|
||||
v3 ^= v0
|
||||
|
||||
v2 += v1
|
||||
v1 = v1<<17 | v1>>(64-17)
|
||||
v1 ^= v2
|
||||
v2 = v2<<32 | v2>>(64-32)
|
||||
|
||||
// Round 2.
|
||||
v0 += v1
|
||||
v1 = v1<<13 | v1>>(64-13)
|
||||
v1 ^= v0
|
||||
v0 = v0<<32 | v0>>(64-32)
|
||||
|
||||
v2 += v3
|
||||
v3 = v3<<16 | v3>>(64-16)
|
||||
v3 ^= v2
|
||||
|
||||
v0 += v3
|
||||
v3 = v3<<21 | v3>>(64-21)
|
||||
v3 ^= v0
|
||||
|
||||
v2 += v1
|
||||
v1 = v1<<17 | v1>>(64-17)
|
||||
v1 ^= v2
|
||||
v2 = v2<<32 | v2>>(64-32)
|
||||
|
||||
v0 ^= m
|
||||
|
||||
// Compress last block.
|
||||
v3 ^= t
|
||||
|
||||
// Round 1.
|
||||
v0 += v1
|
||||
v1 = v1<<13 | v1>>(64-13)
|
||||
v1 ^= v0
|
||||
v0 = v0<<32 | v0>>(64-32)
|
||||
|
||||
v2 += v3
|
||||
v3 = v3<<16 | v3>>(64-16)
|
||||
v3 ^= v2
|
||||
|
||||
v0 += v3
|
||||
v3 = v3<<21 | v3>>(64-21)
|
||||
v3 ^= v0
|
||||
|
||||
v2 += v1
|
||||
v1 = v1<<17 | v1>>(64-17)
|
||||
v1 ^= v2
|
||||
v2 = v2<<32 | v2>>(64-32)
|
||||
|
||||
// Round 2.
|
||||
v0 += v1
|
||||
v1 = v1<<13 | v1>>(64-13)
|
||||
v1 ^= v0
|
||||
v0 = v0<<32 | v0>>(64-32)
|
||||
|
||||
v2 += v3
|
||||
v3 = v3<<16 | v3>>(64-16)
|
||||
v3 ^= v2
|
||||
|
||||
v0 += v3
|
||||
v3 = v3<<21 | v3>>(64-21)
|
||||
v3 ^= v0
|
||||
|
||||
v2 += v1
|
||||
v1 = v1<<17 | v1>>(64-17)
|
||||
v1 ^= v2
|
||||
v2 = v2<<32 | v2>>(64-32)
|
||||
|
||||
v0 ^= t
|
||||
|
||||
// Finalization.
|
||||
v2 ^= 0xff
|
||||
|
||||
// Round 1.
|
||||
v0 += v1
|
||||
v1 = v1<<13 | v1>>(64-13)
|
||||
v1 ^= v0
|
||||
v0 = v0<<32 | v0>>(64-32)
|
||||
|
||||
v2 += v3
|
||||
v3 = v3<<16 | v3>>(64-16)
|
||||
v3 ^= v2
|
||||
|
||||
v0 += v3
|
||||
v3 = v3<<21 | v3>>(64-21)
|
||||
v3 ^= v0
|
||||
|
||||
v2 += v1
|
||||
v1 = v1<<17 | v1>>(64-17)
|
||||
v1 ^= v2
|
||||
v2 = v2<<32 | v2>>(64-32)
|
||||
|
||||
// Round 2.
|
||||
v0 += v1
|
||||
v1 = v1<<13 | v1>>(64-13)
|
||||
v1 ^= v0
|
||||
v0 = v0<<32 | v0>>(64-32)
|
||||
|
||||
v2 += v3
|
||||
v3 = v3<<16 | v3>>(64-16)
|
||||
v3 ^= v2
|
||||
|
||||
v0 += v3
|
||||
v3 = v3<<21 | v3>>(64-21)
|
||||
v3 ^= v0
|
||||
|
||||
v2 += v1
|
||||
v1 = v1<<17 | v1>>(64-17)
|
||||
v1 ^= v2
|
||||
v2 = v2<<32 | v2>>(64-32)
|
||||
|
||||
// Round 3.
|
||||
v0 += v1
|
||||
v1 = v1<<13 | v1>>(64-13)
|
||||
v1 ^= v0
|
||||
v0 = v0<<32 | v0>>(64-32)
|
||||
|
||||
v2 += v3
|
||||
v3 = v3<<16 | v3>>(64-16)
|
||||
v3 ^= v2
|
||||
|
||||
v0 += v3
|
||||
v3 = v3<<21 | v3>>(64-21)
|
||||
v3 ^= v0
|
||||
|
||||
v2 += v1
|
||||
v1 = v1<<17 | v1>>(64-17)
|
||||
v1 ^= v2
|
||||
v2 = v2<<32 | v2>>(64-32)
|
||||
|
||||
// Round 4.
|
||||
v0 += v1
|
||||
v1 = v1<<13 | v1>>(64-13)
|
||||
v1 ^= v0
|
||||
v0 = v0<<32 | v0>>(64-32)
|
||||
|
||||
v2 += v3
|
||||
v3 = v3<<16 | v3>>(64-16)
|
||||
v3 ^= v2
|
||||
|
||||
v0 += v3
|
||||
v3 = v3<<21 | v3>>(64-21)
|
||||
v3 ^= v0
|
||||
|
||||
v2 += v1
|
||||
v1 = v1<<17 | v1>>(64-17)
|
||||
v1 ^= v2
|
||||
v2 = v2<<32 | v2>>(64-32)
|
||||
|
||||
return v0 ^ v1 ^ v2 ^ v3
|
||||
}
|
||||
|
||||
// rekey sets a new PRNG key, which is read from crypto/rand.
|
||||
func (p *siprng) rekey() {
|
||||
var k [16]byte
|
||||
if _, err := io.ReadFull(rand.Reader, k[:]); err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
p.k0 = binary.LittleEndian.Uint64(k[0:8])
|
||||
p.k1 = binary.LittleEndian.Uint64(k[8:16])
|
||||
p.ctr = 1
|
||||
}
|
||||
|
||||
// Uint64 returns a new pseudorandom uint64.
|
||||
// It rekeys PRNG on the first call and every 64 MB of generated data.
|
||||
func (p *siprng) Uint64() uint64 {
|
||||
p.mu.Lock()
|
||||
if p.ctr == 0 || p.ctr > 8*1024*1024 {
|
||||
p.rekey()
|
||||
}
|
||||
v := siphash(p.k0, p.k1, p.ctr)
|
||||
p.ctr++
|
||||
p.mu.Unlock()
|
||||
return v
|
||||
}
|
||||
|
||||
func (p *siprng) Int63() int64 {
|
||||
return int64(p.Uint64() & 0x7fffffffffffffff)
|
||||
}
|
||||
|
||||
func (p *siprng) Uint32() uint32 {
|
||||
return uint32(p.Uint64())
|
||||
}
|
||||
|
||||
func (p *siprng) Int31() int32 {
|
||||
return int32(p.Uint32() & 0x7fffffff)
|
||||
}
|
||||
|
||||
func (p *siprng) Intn(n int) int {
|
||||
if n <= 0 {
|
||||
panic("invalid argument to Intn")
|
||||
}
|
||||
if n <= 1<<31-1 {
|
||||
return int(p.Int31n(int32(n)))
|
||||
}
|
||||
return int(p.Int63n(int64(n)))
|
||||
}
|
||||
|
||||
func (p *siprng) Int63n(n int64) int64 {
|
||||
if n <= 0 {
|
||||
panic("invalid argument to Int63n")
|
||||
}
|
||||
max := int64((1 << 63) - 1 - (1<<63)%uint64(n))
|
||||
v := p.Int63()
|
||||
for v > max {
|
||||
v = p.Int63()
|
||||
}
|
||||
return v % n
|
||||
}
|
||||
|
||||
func (p *siprng) Int31n(n int32) int32 {
|
||||
if n <= 0 {
|
||||
panic("invalid argument to Int31n")
|
||||
}
|
||||
max := int32((1 << 31) - 1 - (1<<31)%uint32(n))
|
||||
v := p.Int31()
|
||||
for v > max {
|
||||
v = p.Int31()
|
||||
}
|
||||
return v % n
|
||||
}
|
||||
|
||||
func (p *siprng) Float64() float64 { return float64(p.Int63()) / (1 << 63) }
|
||||
+3
-1
@@ -252,7 +252,9 @@ func (engine *Engine) Close() error {
|
||||
func (engine *Engine) Ping() error {
|
||||
session := engine.NewSession()
|
||||
defer session.Close()
|
||||
engine.logger.Infof("PING DATABASE %v", engine.DriverName())
|
||||
if engine.showSQL {
|
||||
engine.logger.Infof("PING DATABASE %v", engine.DriverName())
|
||||
}
|
||||
return session.Ping()
|
||||
}
|
||||
|
||||
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
Go support for Protocol Buffers - Google's data interchange format
|
||||
|
||||
Copyright 2010 The Go Authors. All rights reserved.
|
||||
https://github.com/golang/protobuf
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
# Go support for Protocol Buffers - Google's data interchange format
|
||||
#
|
||||
# Copyright 2010 The Go Authors. All rights reserved.
|
||||
# https://github.com/golang/protobuf
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are
|
||||
# met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above
|
||||
# copyright notice, this list of conditions and the following disclaimer
|
||||
# in the documentation and/or other materials provided with the
|
||||
# distribution.
|
||||
# * Neither the name of Google Inc. nor the names of its
|
||||
# contributors may be used to endorse or promote products derived from
|
||||
# this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
install:
|
||||
go install
|
||||
|
||||
test: install generate-test-pbs
|
||||
go test
|
||||
|
||||
|
||||
generate-test-pbs:
|
||||
make install
|
||||
make -C testdata
|
||||
protoc --go_out=Mtestdata/test.proto=github.com/golang/protobuf/proto/testdata,Mgoogle/protobuf/any.proto=github.com/golang/protobuf/ptypes/any:. proto3_proto/proto3.proto
|
||||
make
|
||||
+229
@@ -0,0 +1,229 @@
|
||||
// Go support for Protocol Buffers - Google's data interchange format
|
||||
//
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// https://github.com/golang/protobuf
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// Protocol buffer deep copy and merge.
|
||||
// TODO: RawMessage.
|
||||
|
||||
package proto
|
||||
|
||||
import (
|
||||
"log"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Clone returns a deep copy of a protocol buffer.
|
||||
func Clone(pb Message) Message {
|
||||
in := reflect.ValueOf(pb)
|
||||
if in.IsNil() {
|
||||
return pb
|
||||
}
|
||||
|
||||
out := reflect.New(in.Type().Elem())
|
||||
// out is empty so a merge is a deep copy.
|
||||
mergeStruct(out.Elem(), in.Elem())
|
||||
return out.Interface().(Message)
|
||||
}
|
||||
|
||||
// Merge merges src into dst.
|
||||
// Required and optional fields that are set in src will be set to that value in dst.
|
||||
// Elements of repeated fields will be appended.
|
||||
// Merge panics if src and dst are not the same type, or if dst is nil.
|
||||
func Merge(dst, src Message) {
|
||||
in := reflect.ValueOf(src)
|
||||
out := reflect.ValueOf(dst)
|
||||
if out.IsNil() {
|
||||
panic("proto: nil destination")
|
||||
}
|
||||
if in.Type() != out.Type() {
|
||||
// Explicit test prior to mergeStruct so that mistyped nils will fail
|
||||
panic("proto: type mismatch")
|
||||
}
|
||||
if in.IsNil() {
|
||||
// Merging nil into non-nil is a quiet no-op
|
||||
return
|
||||
}
|
||||
mergeStruct(out.Elem(), in.Elem())
|
||||
}
|
||||
|
||||
func mergeStruct(out, in reflect.Value) {
|
||||
sprop := GetProperties(in.Type())
|
||||
for i := 0; i < in.NumField(); i++ {
|
||||
f := in.Type().Field(i)
|
||||
if strings.HasPrefix(f.Name, "XXX_") {
|
||||
continue
|
||||
}
|
||||
mergeAny(out.Field(i), in.Field(i), false, sprop.Prop[i])
|
||||
}
|
||||
|
||||
if emIn, ok := extendable(in.Addr().Interface()); ok {
|
||||
emOut, _ := extendable(out.Addr().Interface())
|
||||
mIn, muIn := emIn.extensionsRead()
|
||||
if mIn != nil {
|
||||
mOut := emOut.extensionsWrite()
|
||||
muIn.Lock()
|
||||
mergeExtension(mOut, mIn)
|
||||
muIn.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
uf := in.FieldByName("XXX_unrecognized")
|
||||
if !uf.IsValid() {
|
||||
return
|
||||
}
|
||||
uin := uf.Bytes()
|
||||
if len(uin) > 0 {
|
||||
out.FieldByName("XXX_unrecognized").SetBytes(append([]byte(nil), uin...))
|
||||
}
|
||||
}
|
||||
|
||||
// mergeAny performs a merge between two values of the same type.
|
||||
// viaPtr indicates whether the values were indirected through a pointer (implying proto2).
|
||||
// prop is set if this is a struct field (it may be nil).
|
||||
func mergeAny(out, in reflect.Value, viaPtr bool, prop *Properties) {
|
||||
if in.Type() == protoMessageType {
|
||||
if !in.IsNil() {
|
||||
if out.IsNil() {
|
||||
out.Set(reflect.ValueOf(Clone(in.Interface().(Message))))
|
||||
} else {
|
||||
Merge(out.Interface().(Message), in.Interface().(Message))
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
switch in.Kind() {
|
||||
case reflect.Bool, reflect.Float32, reflect.Float64, reflect.Int32, reflect.Int64,
|
||||
reflect.String, reflect.Uint32, reflect.Uint64:
|
||||
if !viaPtr && isProto3Zero(in) {
|
||||
return
|
||||
}
|
||||
out.Set(in)
|
||||
case reflect.Interface:
|
||||
// Probably a oneof field; copy non-nil values.
|
||||
if in.IsNil() {
|
||||
return
|
||||
}
|
||||
// Allocate destination if it is not set, or set to a different type.
|
||||
// Otherwise we will merge as normal.
|
||||
if out.IsNil() || out.Elem().Type() != in.Elem().Type() {
|
||||
out.Set(reflect.New(in.Elem().Elem().Type())) // interface -> *T -> T -> new(T)
|
||||
}
|
||||
mergeAny(out.Elem(), in.Elem(), false, nil)
|
||||
case reflect.Map:
|
||||
if in.Len() == 0 {
|
||||
return
|
||||
}
|
||||
if out.IsNil() {
|
||||
out.Set(reflect.MakeMap(in.Type()))
|
||||
}
|
||||
// For maps with value types of *T or []byte we need to deep copy each value.
|
||||
elemKind := in.Type().Elem().Kind()
|
||||
for _, key := range in.MapKeys() {
|
||||
var val reflect.Value
|
||||
switch elemKind {
|
||||
case reflect.Ptr:
|
||||
val = reflect.New(in.Type().Elem().Elem())
|
||||
mergeAny(val, in.MapIndex(key), false, nil)
|
||||
case reflect.Slice:
|
||||
val = in.MapIndex(key)
|
||||
val = reflect.ValueOf(append([]byte{}, val.Bytes()...))
|
||||
default:
|
||||
val = in.MapIndex(key)
|
||||
}
|
||||
out.SetMapIndex(key, val)
|
||||
}
|
||||
case reflect.Ptr:
|
||||
if in.IsNil() {
|
||||
return
|
||||
}
|
||||
if out.IsNil() {
|
||||
out.Set(reflect.New(in.Elem().Type()))
|
||||
}
|
||||
mergeAny(out.Elem(), in.Elem(), true, nil)
|
||||
case reflect.Slice:
|
||||
if in.IsNil() {
|
||||
return
|
||||
}
|
||||
if in.Type().Elem().Kind() == reflect.Uint8 {
|
||||
// []byte is a scalar bytes field, not a repeated field.
|
||||
|
||||
// Edge case: if this is in a proto3 message, a zero length
|
||||
// bytes field is considered the zero value, and should not
|
||||
// be merged.
|
||||
if prop != nil && prop.proto3 && in.Len() == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Make a deep copy.
|
||||
// Append to []byte{} instead of []byte(nil) so that we never end up
|
||||
// with a nil result.
|
||||
out.SetBytes(append([]byte{}, in.Bytes()...))
|
||||
return
|
||||
}
|
||||
n := in.Len()
|
||||
if out.IsNil() {
|
||||
out.Set(reflect.MakeSlice(in.Type(), 0, n))
|
||||
}
|
||||
switch in.Type().Elem().Kind() {
|
||||
case reflect.Bool, reflect.Float32, reflect.Float64, reflect.Int32, reflect.Int64,
|
||||
reflect.String, reflect.Uint32, reflect.Uint64:
|
||||
out.Set(reflect.AppendSlice(out, in))
|
||||
default:
|
||||
for i := 0; i < n; i++ {
|
||||
x := reflect.Indirect(reflect.New(in.Type().Elem()))
|
||||
mergeAny(x, in.Index(i), false, nil)
|
||||
out.Set(reflect.Append(out, x))
|
||||
}
|
||||
}
|
||||
case reflect.Struct:
|
||||
mergeStruct(out, in)
|
||||
default:
|
||||
// unknown type, so not a protocol buffer
|
||||
log.Printf("proto: don't know how to copy %v", in)
|
||||
}
|
||||
}
|
||||
|
||||
func mergeExtension(out, in map[int32]Extension) {
|
||||
for extNum, eIn := range in {
|
||||
eOut := Extension{desc: eIn.desc}
|
||||
if eIn.value != nil {
|
||||
v := reflect.New(reflect.TypeOf(eIn.value)).Elem()
|
||||
mergeAny(v, reflect.ValueOf(eIn.value), false, nil)
|
||||
eOut.value = v.Interface()
|
||||
}
|
||||
if eIn.enc != nil {
|
||||
eOut.enc = make([]byte, len(eIn.enc))
|
||||
copy(eOut.enc, eIn.enc)
|
||||
}
|
||||
|
||||
out[extNum] = eOut
|
||||
}
|
||||
}
|
||||
+970
@@ -0,0 +1,970 @@
|
||||
// Go support for Protocol Buffers - Google's data interchange format
|
||||
//
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// https://github.com/golang/protobuf
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package proto
|
||||
|
||||
/*
|
||||
* Routines for decoding protocol buffer data to construct in-memory representations.
|
||||
*/
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// errOverflow is returned when an integer is too large to be represented.
|
||||
var errOverflow = errors.New("proto: integer overflow")
|
||||
|
||||
// ErrInternalBadWireType is returned by generated code when an incorrect
|
||||
// wire type is encountered. It does not get returned to user code.
|
||||
var ErrInternalBadWireType = errors.New("proto: internal error: bad wiretype for oneof")
|
||||
|
||||
// The fundamental decoders that interpret bytes on the wire.
|
||||
// Those that take integer types all return uint64 and are
|
||||
// therefore of type valueDecoder.
|
||||
|
||||
// DecodeVarint reads a varint-encoded integer from the slice.
|
||||
// It returns the integer and the number of bytes consumed, or
|
||||
// zero if there is not enough.
|
||||
// This is the format for the
|
||||
// int32, int64, uint32, uint64, bool, and enum
|
||||
// protocol buffer types.
|
||||
func DecodeVarint(buf []byte) (x uint64, n int) {
|
||||
for shift := uint(0); shift < 64; shift += 7 {
|
||||
if n >= len(buf) {
|
||||
return 0, 0
|
||||
}
|
||||
b := uint64(buf[n])
|
||||
n++
|
||||
x |= (b & 0x7F) << shift
|
||||
if (b & 0x80) == 0 {
|
||||
return x, n
|
||||
}
|
||||
}
|
||||
|
||||
// The number is too large to represent in a 64-bit value.
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
func (p *Buffer) decodeVarintSlow() (x uint64, err error) {
|
||||
i := p.index
|
||||
l := len(p.buf)
|
||||
|
||||
for shift := uint(0); shift < 64; shift += 7 {
|
||||
if i >= l {
|
||||
err = io.ErrUnexpectedEOF
|
||||
return
|
||||
}
|
||||
b := p.buf[i]
|
||||
i++
|
||||
x |= (uint64(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
p.index = i
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// The number is too large to represent in a 64-bit value.
|
||||
err = errOverflow
|
||||
return
|
||||
}
|
||||
|
||||
// DecodeVarint reads a varint-encoded integer from the Buffer.
|
||||
// This is the format for the
|
||||
// int32, int64, uint32, uint64, bool, and enum
|
||||
// protocol buffer types.
|
||||
func (p *Buffer) DecodeVarint() (x uint64, err error) {
|
||||
i := p.index
|
||||
buf := p.buf
|
||||
|
||||
if i >= len(buf) {
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
} else if buf[i] < 0x80 {
|
||||
p.index++
|
||||
return uint64(buf[i]), nil
|
||||
} else if len(buf)-i < 10 {
|
||||
return p.decodeVarintSlow()
|
||||
}
|
||||
|
||||
var b uint64
|
||||
// we already checked the first byte
|
||||
x = uint64(buf[i]) - 0x80
|
||||
i++
|
||||
|
||||
b = uint64(buf[i])
|
||||
i++
|
||||
x += b << 7
|
||||
if b&0x80 == 0 {
|
||||
goto done
|
||||
}
|
||||
x -= 0x80 << 7
|
||||
|
||||
b = uint64(buf[i])
|
||||
i++
|
||||
x += b << 14
|
||||
if b&0x80 == 0 {
|
||||
goto done
|
||||
}
|
||||
x -= 0x80 << 14
|
||||
|
||||
b = uint64(buf[i])
|
||||
i++
|
||||
x += b << 21
|
||||
if b&0x80 == 0 {
|
||||
goto done
|
||||
}
|
||||
x -= 0x80 << 21
|
||||
|
||||
b = uint64(buf[i])
|
||||
i++
|
||||
x += b << 28
|
||||
if b&0x80 == 0 {
|
||||
goto done
|
||||
}
|
||||
x -= 0x80 << 28
|
||||
|
||||
b = uint64(buf[i])
|
||||
i++
|
||||
x += b << 35
|
||||
if b&0x80 == 0 {
|
||||
goto done
|
||||
}
|
||||
x -= 0x80 << 35
|
||||
|
||||
b = uint64(buf[i])
|
||||
i++
|
||||
x += b << 42
|
||||
if b&0x80 == 0 {
|
||||
goto done
|
||||
}
|
||||
x -= 0x80 << 42
|
||||
|
||||
b = uint64(buf[i])
|
||||
i++
|
||||
x += b << 49
|
||||
if b&0x80 == 0 {
|
||||
goto done
|
||||
}
|
||||
x -= 0x80 << 49
|
||||
|
||||
b = uint64(buf[i])
|
||||
i++
|
||||
x += b << 56
|
||||
if b&0x80 == 0 {
|
||||
goto done
|
||||
}
|
||||
x -= 0x80 << 56
|
||||
|
||||
b = uint64(buf[i])
|
||||
i++
|
||||
x += b << 63
|
||||
if b&0x80 == 0 {
|
||||
goto done
|
||||
}
|
||||
// x -= 0x80 << 63 // Always zero.
|
||||
|
||||
return 0, errOverflow
|
||||
|
||||
done:
|
||||
p.index = i
|
||||
return x, nil
|
||||
}
|
||||
|
||||
// DecodeFixed64 reads a 64-bit integer from the Buffer.
|
||||
// This is the format for the
|
||||
// fixed64, sfixed64, and double protocol buffer types.
|
||||
func (p *Buffer) DecodeFixed64() (x uint64, err error) {
|
||||
// x, err already 0
|
||||
i := p.index + 8
|
||||
if i < 0 || i > len(p.buf) {
|
||||
err = io.ErrUnexpectedEOF
|
||||
return
|
||||
}
|
||||
p.index = i
|
||||
|
||||
x = uint64(p.buf[i-8])
|
||||
x |= uint64(p.buf[i-7]) << 8
|
||||
x |= uint64(p.buf[i-6]) << 16
|
||||
x |= uint64(p.buf[i-5]) << 24
|
||||
x |= uint64(p.buf[i-4]) << 32
|
||||
x |= uint64(p.buf[i-3]) << 40
|
||||
x |= uint64(p.buf[i-2]) << 48
|
||||
x |= uint64(p.buf[i-1]) << 56
|
||||
return
|
||||
}
|
||||
|
||||
// DecodeFixed32 reads a 32-bit integer from the Buffer.
|
||||
// This is the format for the
|
||||
// fixed32, sfixed32, and float protocol buffer types.
|
||||
func (p *Buffer) DecodeFixed32() (x uint64, err error) {
|
||||
// x, err already 0
|
||||
i := p.index + 4
|
||||
if i < 0 || i > len(p.buf) {
|
||||
err = io.ErrUnexpectedEOF
|
||||
return
|
||||
}
|
||||
p.index = i
|
||||
|
||||
x = uint64(p.buf[i-4])
|
||||
x |= uint64(p.buf[i-3]) << 8
|
||||
x |= uint64(p.buf[i-2]) << 16
|
||||
x |= uint64(p.buf[i-1]) << 24
|
||||
return
|
||||
}
|
||||
|
||||
// DecodeZigzag64 reads a zigzag-encoded 64-bit integer
|
||||
// from the Buffer.
|
||||
// This is the format used for the sint64 protocol buffer type.
|
||||
func (p *Buffer) DecodeZigzag64() (x uint64, err error) {
|
||||
x, err = p.DecodeVarint()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
x = (x >> 1) ^ uint64((int64(x&1)<<63)>>63)
|
||||
return
|
||||
}
|
||||
|
||||
// DecodeZigzag32 reads a zigzag-encoded 32-bit integer
|
||||
// from the Buffer.
|
||||
// This is the format used for the sint32 protocol buffer type.
|
||||
func (p *Buffer) DecodeZigzag32() (x uint64, err error) {
|
||||
x, err = p.DecodeVarint()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
x = uint64((uint32(x) >> 1) ^ uint32((int32(x&1)<<31)>>31))
|
||||
return
|
||||
}
|
||||
|
||||
// These are not ValueDecoders: they produce an array of bytes or a string.
|
||||
// bytes, embedded messages
|
||||
|
||||
// DecodeRawBytes reads a count-delimited byte buffer from the Buffer.
|
||||
// This is the format used for the bytes protocol buffer
|
||||
// type and for embedded messages.
|
||||
func (p *Buffer) DecodeRawBytes(alloc bool) (buf []byte, err error) {
|
||||
n, err := p.DecodeVarint()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nb := int(n)
|
||||
if nb < 0 {
|
||||
return nil, fmt.Errorf("proto: bad byte length %d", nb)
|
||||
}
|
||||
end := p.index + nb
|
||||
if end < p.index || end > len(p.buf) {
|
||||
return nil, io.ErrUnexpectedEOF
|
||||
}
|
||||
|
||||
if !alloc {
|
||||
// todo: check if can get more uses of alloc=false
|
||||
buf = p.buf[p.index:end]
|
||||
p.index += nb
|
||||
return
|
||||
}
|
||||
|
||||
buf = make([]byte, nb)
|
||||
copy(buf, p.buf[p.index:])
|
||||
p.index += nb
|
||||
return
|
||||
}
|
||||
|
||||
// DecodeStringBytes reads an encoded string from the Buffer.
|
||||
// This is the format used for the proto2 string type.
|
||||
func (p *Buffer) DecodeStringBytes() (s string, err error) {
|
||||
buf, err := p.DecodeRawBytes(false)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return string(buf), nil
|
||||
}
|
||||
|
||||
// Skip the next item in the buffer. Its wire type is decoded and presented as an argument.
|
||||
// If the protocol buffer has extensions, and the field matches, add it as an extension.
|
||||
// Otherwise, if the XXX_unrecognized field exists, append the skipped data there.
|
||||
func (o *Buffer) skipAndSave(t reflect.Type, tag, wire int, base structPointer, unrecField field) error {
|
||||
oi := o.index
|
||||
|
||||
err := o.skip(t, tag, wire)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !unrecField.IsValid() {
|
||||
return nil
|
||||
}
|
||||
|
||||
ptr := structPointer_Bytes(base, unrecField)
|
||||
|
||||
// Add the skipped field to struct field
|
||||
obuf := o.buf
|
||||
|
||||
o.buf = *ptr
|
||||
o.EncodeVarint(uint64(tag<<3 | wire))
|
||||
*ptr = append(o.buf, obuf[oi:o.index]...)
|
||||
|
||||
o.buf = obuf
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Skip the next item in the buffer. Its wire type is decoded and presented as an argument.
|
||||
func (o *Buffer) skip(t reflect.Type, tag, wire int) error {
|
||||
|
||||
var u uint64
|
||||
var err error
|
||||
|
||||
switch wire {
|
||||
case WireVarint:
|
||||
_, err = o.DecodeVarint()
|
||||
case WireFixed64:
|
||||
_, err = o.DecodeFixed64()
|
||||
case WireBytes:
|
||||
_, err = o.DecodeRawBytes(false)
|
||||
case WireFixed32:
|
||||
_, err = o.DecodeFixed32()
|
||||
case WireStartGroup:
|
||||
for {
|
||||
u, err = o.DecodeVarint()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
fwire := int(u & 0x7)
|
||||
if fwire == WireEndGroup {
|
||||
break
|
||||
}
|
||||
ftag := int(u >> 3)
|
||||
err = o.skip(t, ftag, fwire)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
default:
|
||||
err = fmt.Errorf("proto: can't skip unknown wire type %d for %s", wire, t)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Unmarshaler is the interface representing objects that can
|
||||
// unmarshal themselves. The method should reset the receiver before
|
||||
// decoding starts. The argument points to data that may be
|
||||
// overwritten, so implementations should not keep references to the
|
||||
// buffer.
|
||||
type Unmarshaler interface {
|
||||
Unmarshal([]byte) error
|
||||
}
|
||||
|
||||
// Unmarshal parses the protocol buffer representation in buf and places the
|
||||
// decoded result in pb. If the struct underlying pb does not match
|
||||
// the data in buf, the results can be unpredictable.
|
||||
//
|
||||
// Unmarshal resets pb before starting to unmarshal, so any
|
||||
// existing data in pb is always removed. Use UnmarshalMerge
|
||||
// to preserve and append to existing data.
|
||||
func Unmarshal(buf []byte, pb Message) error {
|
||||
pb.Reset()
|
||||
return UnmarshalMerge(buf, pb)
|
||||
}
|
||||
|
||||
// UnmarshalMerge parses the protocol buffer representation in buf and
|
||||
// writes the decoded result to pb. If the struct underlying pb does not match
|
||||
// the data in buf, the results can be unpredictable.
|
||||
//
|
||||
// UnmarshalMerge merges into existing data in pb.
|
||||
// Most code should use Unmarshal instead.
|
||||
func UnmarshalMerge(buf []byte, pb Message) error {
|
||||
// If the object can unmarshal itself, let it.
|
||||
if u, ok := pb.(Unmarshaler); ok {
|
||||
return u.Unmarshal(buf)
|
||||
}
|
||||
return NewBuffer(buf).Unmarshal(pb)
|
||||
}
|
||||
|
||||
// DecodeMessage reads a count-delimited message from the Buffer.
|
||||
func (p *Buffer) DecodeMessage(pb Message) error {
|
||||
enc, err := p.DecodeRawBytes(false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return NewBuffer(enc).Unmarshal(pb)
|
||||
}
|
||||
|
||||
// DecodeGroup reads a tag-delimited group from the Buffer.
|
||||
func (p *Buffer) DecodeGroup(pb Message) error {
|
||||
typ, base, err := getbase(pb)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return p.unmarshalType(typ.Elem(), GetProperties(typ.Elem()), true, base)
|
||||
}
|
||||
|
||||
// Unmarshal parses the protocol buffer representation in the
|
||||
// Buffer and places the decoded result in pb. If the struct
|
||||
// underlying pb does not match the data in the buffer, the results can be
|
||||
// unpredictable.
|
||||
//
|
||||
// Unlike proto.Unmarshal, this does not reset pb before starting to unmarshal.
|
||||
func (p *Buffer) Unmarshal(pb Message) error {
|
||||
// If the object can unmarshal itself, let it.
|
||||
if u, ok := pb.(Unmarshaler); ok {
|
||||
err := u.Unmarshal(p.buf[p.index:])
|
||||
p.index = len(p.buf)
|
||||
return err
|
||||
}
|
||||
|
||||
typ, base, err := getbase(pb)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = p.unmarshalType(typ.Elem(), GetProperties(typ.Elem()), false, base)
|
||||
|
||||
if collectStats {
|
||||
stats.Decode++
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// unmarshalType does the work of unmarshaling a structure.
|
||||
func (o *Buffer) unmarshalType(st reflect.Type, prop *StructProperties, is_group bool, base structPointer) error {
|
||||
var state errorState
|
||||
required, reqFields := prop.reqCount, uint64(0)
|
||||
|
||||
var err error
|
||||
for err == nil && o.index < len(o.buf) {
|
||||
oi := o.index
|
||||
var u uint64
|
||||
u, err = o.DecodeVarint()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
wire := int(u & 0x7)
|
||||
if wire == WireEndGroup {
|
||||
if is_group {
|
||||
if required > 0 {
|
||||
// Not enough information to determine the exact field.
|
||||
// (See below.)
|
||||
return &RequiredNotSetError{"{Unknown}"}
|
||||
}
|
||||
return nil // input is satisfied
|
||||
}
|
||||
return fmt.Errorf("proto: %s: wiretype end group for non-group", st)
|
||||
}
|
||||
tag := int(u >> 3)
|
||||
if tag <= 0 {
|
||||
return fmt.Errorf("proto: %s: illegal tag %d (wire type %d)", st, tag, wire)
|
||||
}
|
||||
fieldnum, ok := prop.decoderTags.get(tag)
|
||||
if !ok {
|
||||
// Maybe it's an extension?
|
||||
if prop.extendable {
|
||||
if e, _ := extendable(structPointer_Interface(base, st)); isExtensionField(e, int32(tag)) {
|
||||
if err = o.skip(st, tag, wire); err == nil {
|
||||
extmap := e.extensionsWrite()
|
||||
ext := extmap[int32(tag)] // may be missing
|
||||
ext.enc = append(ext.enc, o.buf[oi:o.index]...)
|
||||
extmap[int32(tag)] = ext
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
// Maybe it's a oneof?
|
||||
if prop.oneofUnmarshaler != nil {
|
||||
m := structPointer_Interface(base, st).(Message)
|
||||
// First return value indicates whether tag is a oneof field.
|
||||
ok, err = prop.oneofUnmarshaler(m, tag, wire, o)
|
||||
if err == ErrInternalBadWireType {
|
||||
// Map the error to something more descriptive.
|
||||
// Do the formatting here to save generated code space.
|
||||
err = fmt.Errorf("bad wiretype for oneof field in %T", m)
|
||||
}
|
||||
if ok {
|
||||
continue
|
||||
}
|
||||
}
|
||||
err = o.skipAndSave(st, tag, wire, base, prop.unrecField)
|
||||
continue
|
||||
}
|
||||
p := prop.Prop[fieldnum]
|
||||
|
||||
if p.dec == nil {
|
||||
fmt.Fprintf(os.Stderr, "proto: no protobuf decoder for %s.%s\n", st, st.Field(fieldnum).Name)
|
||||
continue
|
||||
}
|
||||
dec := p.dec
|
||||
if wire != WireStartGroup && wire != p.WireType {
|
||||
if wire == WireBytes && p.packedDec != nil {
|
||||
// a packable field
|
||||
dec = p.packedDec
|
||||
} else {
|
||||
err = fmt.Errorf("proto: bad wiretype for field %s.%s: got wiretype %d, want %d", st, st.Field(fieldnum).Name, wire, p.WireType)
|
||||
continue
|
||||
}
|
||||
}
|
||||
decErr := dec(o, p, base)
|
||||
if decErr != nil && !state.shouldContinue(decErr, p) {
|
||||
err = decErr
|
||||
}
|
||||
if err == nil && p.Required {
|
||||
// Successfully decoded a required field.
|
||||
if tag <= 64 {
|
||||
// use bitmap for fields 1-64 to catch field reuse.
|
||||
var mask uint64 = 1 << uint64(tag-1)
|
||||
if reqFields&mask == 0 {
|
||||
// new required field
|
||||
reqFields |= mask
|
||||
required--
|
||||
}
|
||||
} else {
|
||||
// This is imprecise. It can be fooled by a required field
|
||||
// with a tag > 64 that is encoded twice; that's very rare.
|
||||
// A fully correct implementation would require allocating
|
||||
// a data structure, which we would like to avoid.
|
||||
required--
|
||||
}
|
||||
}
|
||||
}
|
||||
if err == nil {
|
||||
if is_group {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
if state.err != nil {
|
||||
return state.err
|
||||
}
|
||||
if required > 0 {
|
||||
// Not enough information to determine the exact field. If we use extra
|
||||
// CPU, we could determine the field only if the missing required field
|
||||
// has a tag <= 64 and we check reqFields.
|
||||
return &RequiredNotSetError{"{Unknown}"}
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Individual type decoders
|
||||
// For each,
|
||||
// u is the decoded value,
|
||||
// v is a pointer to the field (pointer) in the struct
|
||||
|
||||
// Sizes of the pools to allocate inside the Buffer.
|
||||
// The goal is modest amortization and allocation
|
||||
// on at least 16-byte boundaries.
|
||||
const (
|
||||
boolPoolSize = 16
|
||||
uint32PoolSize = 8
|
||||
uint64PoolSize = 4
|
||||
)
|
||||
|
||||
// Decode a bool.
|
||||
func (o *Buffer) dec_bool(p *Properties, base structPointer) error {
|
||||
u, err := p.valDec(o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(o.bools) == 0 {
|
||||
o.bools = make([]bool, boolPoolSize)
|
||||
}
|
||||
o.bools[0] = u != 0
|
||||
*structPointer_Bool(base, p.field) = &o.bools[0]
|
||||
o.bools = o.bools[1:]
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *Buffer) dec_proto3_bool(p *Properties, base structPointer) error {
|
||||
u, err := p.valDec(o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*structPointer_BoolVal(base, p.field) = u != 0
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode an int32.
|
||||
func (o *Buffer) dec_int32(p *Properties, base structPointer) error {
|
||||
u, err := p.valDec(o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
word32_Set(structPointer_Word32(base, p.field), o, uint32(u))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *Buffer) dec_proto3_int32(p *Properties, base structPointer) error {
|
||||
u, err := p.valDec(o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
word32Val_Set(structPointer_Word32Val(base, p.field), uint32(u))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode an int64.
|
||||
func (o *Buffer) dec_int64(p *Properties, base structPointer) error {
|
||||
u, err := p.valDec(o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
word64_Set(structPointer_Word64(base, p.field), o, u)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *Buffer) dec_proto3_int64(p *Properties, base structPointer) error {
|
||||
u, err := p.valDec(o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
word64Val_Set(structPointer_Word64Val(base, p.field), o, u)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode a string.
|
||||
func (o *Buffer) dec_string(p *Properties, base structPointer) error {
|
||||
s, err := o.DecodeStringBytes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*structPointer_String(base, p.field) = &s
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *Buffer) dec_proto3_string(p *Properties, base structPointer) error {
|
||||
s, err := o.DecodeStringBytes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*structPointer_StringVal(base, p.field) = s
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode a slice of bytes ([]byte).
|
||||
func (o *Buffer) dec_slice_byte(p *Properties, base structPointer) error {
|
||||
b, err := o.DecodeRawBytes(true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*structPointer_Bytes(base, p.field) = b
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode a slice of bools ([]bool).
|
||||
func (o *Buffer) dec_slice_bool(p *Properties, base structPointer) error {
|
||||
u, err := p.valDec(o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v := structPointer_BoolSlice(base, p.field)
|
||||
*v = append(*v, u != 0)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode a slice of bools ([]bool) in packed format.
|
||||
func (o *Buffer) dec_slice_packed_bool(p *Properties, base structPointer) error {
|
||||
v := structPointer_BoolSlice(base, p.field)
|
||||
|
||||
nn, err := o.DecodeVarint()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nb := int(nn) // number of bytes of encoded bools
|
||||
fin := o.index + nb
|
||||
if fin < o.index {
|
||||
return errOverflow
|
||||
}
|
||||
|
||||
y := *v
|
||||
for o.index < fin {
|
||||
u, err := p.valDec(o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
y = append(y, u != 0)
|
||||
}
|
||||
|
||||
*v = y
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode a slice of int32s ([]int32).
|
||||
func (o *Buffer) dec_slice_int32(p *Properties, base structPointer) error {
|
||||
u, err := p.valDec(o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
structPointer_Word32Slice(base, p.field).Append(uint32(u))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode a slice of int32s ([]int32) in packed format.
|
||||
func (o *Buffer) dec_slice_packed_int32(p *Properties, base structPointer) error {
|
||||
v := structPointer_Word32Slice(base, p.field)
|
||||
|
||||
nn, err := o.DecodeVarint()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nb := int(nn) // number of bytes of encoded int32s
|
||||
|
||||
fin := o.index + nb
|
||||
if fin < o.index {
|
||||
return errOverflow
|
||||
}
|
||||
for o.index < fin {
|
||||
u, err := p.valDec(o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.Append(uint32(u))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode a slice of int64s ([]int64).
|
||||
func (o *Buffer) dec_slice_int64(p *Properties, base structPointer) error {
|
||||
u, err := p.valDec(o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
structPointer_Word64Slice(base, p.field).Append(u)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode a slice of int64s ([]int64) in packed format.
|
||||
func (o *Buffer) dec_slice_packed_int64(p *Properties, base structPointer) error {
|
||||
v := structPointer_Word64Slice(base, p.field)
|
||||
|
||||
nn, err := o.DecodeVarint()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nb := int(nn) // number of bytes of encoded int64s
|
||||
|
||||
fin := o.index + nb
|
||||
if fin < o.index {
|
||||
return errOverflow
|
||||
}
|
||||
for o.index < fin {
|
||||
u, err := p.valDec(o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.Append(u)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode a slice of strings ([]string).
|
||||
func (o *Buffer) dec_slice_string(p *Properties, base structPointer) error {
|
||||
s, err := o.DecodeStringBytes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v := structPointer_StringSlice(base, p.field)
|
||||
*v = append(*v, s)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode a slice of slice of bytes ([][]byte).
|
||||
func (o *Buffer) dec_slice_slice_byte(p *Properties, base structPointer) error {
|
||||
b, err := o.DecodeRawBytes(true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v := structPointer_BytesSlice(base, p.field)
|
||||
*v = append(*v, b)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode a map field.
|
||||
func (o *Buffer) dec_new_map(p *Properties, base structPointer) error {
|
||||
raw, err := o.DecodeRawBytes(false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
oi := o.index // index at the end of this map entry
|
||||
o.index -= len(raw) // move buffer back to start of map entry
|
||||
|
||||
mptr := structPointer_NewAt(base, p.field, p.mtype) // *map[K]V
|
||||
if mptr.Elem().IsNil() {
|
||||
mptr.Elem().Set(reflect.MakeMap(mptr.Type().Elem()))
|
||||
}
|
||||
v := mptr.Elem() // map[K]V
|
||||
|
||||
// Prepare addressable doubly-indirect placeholders for the key and value types.
|
||||
// See enc_new_map for why.
|
||||
keyptr := reflect.New(reflect.PtrTo(p.mtype.Key())).Elem() // addressable *K
|
||||
keybase := toStructPointer(keyptr.Addr()) // **K
|
||||
|
||||
var valbase structPointer
|
||||
var valptr reflect.Value
|
||||
switch p.mtype.Elem().Kind() {
|
||||
case reflect.Slice:
|
||||
// []byte
|
||||
var dummy []byte
|
||||
valptr = reflect.ValueOf(&dummy) // *[]byte
|
||||
valbase = toStructPointer(valptr) // *[]byte
|
||||
case reflect.Ptr:
|
||||
// message; valptr is **Msg; need to allocate the intermediate pointer
|
||||
valptr = reflect.New(reflect.PtrTo(p.mtype.Elem())).Elem() // addressable *V
|
||||
valptr.Set(reflect.New(valptr.Type().Elem()))
|
||||
valbase = toStructPointer(valptr)
|
||||
default:
|
||||
// everything else
|
||||
valptr = reflect.New(reflect.PtrTo(p.mtype.Elem())).Elem() // addressable *V
|
||||
valbase = toStructPointer(valptr.Addr()) // **V
|
||||
}
|
||||
|
||||
// Decode.
|
||||
// This parses a restricted wire format, namely the encoding of a message
|
||||
// with two fields. See enc_new_map for the format.
|
||||
for o.index < oi {
|
||||
// tagcode for key and value properties are always a single byte
|
||||
// because they have tags 1 and 2.
|
||||
tagcode := o.buf[o.index]
|
||||
o.index++
|
||||
switch tagcode {
|
||||
case p.mkeyprop.tagcode[0]:
|
||||
if err := p.mkeyprop.dec(o, p.mkeyprop, keybase); err != nil {
|
||||
return err
|
||||
}
|
||||
case p.mvalprop.tagcode[0]:
|
||||
if err := p.mvalprop.dec(o, p.mvalprop, valbase); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
// TODO: Should we silently skip this instead?
|
||||
return fmt.Errorf("proto: bad map data tag %d", raw[0])
|
||||
}
|
||||
}
|
||||
keyelem, valelem := keyptr.Elem(), valptr.Elem()
|
||||
if !keyelem.IsValid() {
|
||||
keyelem = reflect.Zero(p.mtype.Key())
|
||||
}
|
||||
if !valelem.IsValid() {
|
||||
valelem = reflect.Zero(p.mtype.Elem())
|
||||
}
|
||||
|
||||
v.SetMapIndex(keyelem, valelem)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decode a group.
|
||||
func (o *Buffer) dec_struct_group(p *Properties, base structPointer) error {
|
||||
bas := structPointer_GetStructPointer(base, p.field)
|
||||
if structPointer_IsNil(bas) {
|
||||
// allocate new nested message
|
||||
bas = toStructPointer(reflect.New(p.stype))
|
||||
structPointer_SetStructPointer(base, p.field, bas)
|
||||
}
|
||||
return o.unmarshalType(p.stype, p.sprop, true, bas)
|
||||
}
|
||||
|
||||
// Decode an embedded message.
|
||||
func (o *Buffer) dec_struct_message(p *Properties, base structPointer) (err error) {
|
||||
raw, e := o.DecodeRawBytes(false)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
|
||||
bas := structPointer_GetStructPointer(base, p.field)
|
||||
if structPointer_IsNil(bas) {
|
||||
// allocate new nested message
|
||||
bas = toStructPointer(reflect.New(p.stype))
|
||||
structPointer_SetStructPointer(base, p.field, bas)
|
||||
}
|
||||
|
||||
// If the object can unmarshal itself, let it.
|
||||
if p.isUnmarshaler {
|
||||
iv := structPointer_Interface(bas, p.stype)
|
||||
return iv.(Unmarshaler).Unmarshal(raw)
|
||||
}
|
||||
|
||||
obuf := o.buf
|
||||
oi := o.index
|
||||
o.buf = raw
|
||||
o.index = 0
|
||||
|
||||
err = o.unmarshalType(p.stype, p.sprop, false, bas)
|
||||
o.buf = obuf
|
||||
o.index = oi
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Decode a slice of embedded messages.
|
||||
func (o *Buffer) dec_slice_struct_message(p *Properties, base structPointer) error {
|
||||
return o.dec_slice_struct(p, false, base)
|
||||
}
|
||||
|
||||
// Decode a slice of embedded groups.
|
||||
func (o *Buffer) dec_slice_struct_group(p *Properties, base structPointer) error {
|
||||
return o.dec_slice_struct(p, true, base)
|
||||
}
|
||||
|
||||
// Decode a slice of structs ([]*struct).
|
||||
func (o *Buffer) dec_slice_struct(p *Properties, is_group bool, base structPointer) error {
|
||||
v := reflect.New(p.stype)
|
||||
bas := toStructPointer(v)
|
||||
structPointer_StructPointerSlice(base, p.field).Append(bas)
|
||||
|
||||
if is_group {
|
||||
err := o.unmarshalType(p.stype, p.sprop, is_group, bas)
|
||||
return err
|
||||
}
|
||||
|
||||
raw, err := o.DecodeRawBytes(false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If the object can unmarshal itself, let it.
|
||||
if p.isUnmarshaler {
|
||||
iv := v.Interface()
|
||||
return iv.(Unmarshaler).Unmarshal(raw)
|
||||
}
|
||||
|
||||
obuf := o.buf
|
||||
oi := o.index
|
||||
o.buf = raw
|
||||
o.index = 0
|
||||
|
||||
err = o.unmarshalType(p.stype, p.sprop, is_group, bas)
|
||||
|
||||
o.buf = obuf
|
||||
o.index = oi
|
||||
|
||||
return err
|
||||
}
|
||||
+1362
File diff suppressed because it is too large
Load Diff
+300
@@ -0,0 +1,300 @@
|
||||
// Go support for Protocol Buffers - Google's data interchange format
|
||||
//
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// https://github.com/golang/protobuf
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// Protocol buffer comparison.
|
||||
|
||||
package proto
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"log"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
/*
|
||||
Equal returns true iff protocol buffers a and b are equal.
|
||||
The arguments must both be pointers to protocol buffer structs.
|
||||
|
||||
Equality is defined in this way:
|
||||
- Two messages are equal iff they are the same type,
|
||||
corresponding fields are equal, unknown field sets
|
||||
are equal, and extensions sets are equal.
|
||||
- Two set scalar fields are equal iff their values are equal.
|
||||
If the fields are of a floating-point type, remember that
|
||||
NaN != x for all x, including NaN. If the message is defined
|
||||
in a proto3 .proto file, fields are not "set"; specifically,
|
||||
zero length proto3 "bytes" fields are equal (nil == {}).
|
||||
- Two repeated fields are equal iff their lengths are the same,
|
||||
and their corresponding elements are equal. Note a "bytes" field,
|
||||
although represented by []byte, is not a repeated field and the
|
||||
rule for the scalar fields described above applies.
|
||||
- Two unset fields are equal.
|
||||
- Two unknown field sets are equal if their current
|
||||
encoded state is equal.
|
||||
- Two extension sets are equal iff they have corresponding
|
||||
elements that are pairwise equal.
|
||||
- Two map fields are equal iff their lengths are the same,
|
||||
and they contain the same set of elements. Zero-length map
|
||||
fields are equal.
|
||||
- Every other combination of things are not equal.
|
||||
|
||||
The return value is undefined if a and b are not protocol buffers.
|
||||
*/
|
||||
func Equal(a, b Message) bool {
|
||||
if a == nil || b == nil {
|
||||
return a == b
|
||||
}
|
||||
v1, v2 := reflect.ValueOf(a), reflect.ValueOf(b)
|
||||
if v1.Type() != v2.Type() {
|
||||
return false
|
||||
}
|
||||
if v1.Kind() == reflect.Ptr {
|
||||
if v1.IsNil() {
|
||||
return v2.IsNil()
|
||||
}
|
||||
if v2.IsNil() {
|
||||
return false
|
||||
}
|
||||
v1, v2 = v1.Elem(), v2.Elem()
|
||||
}
|
||||
if v1.Kind() != reflect.Struct {
|
||||
return false
|
||||
}
|
||||
return equalStruct(v1, v2)
|
||||
}
|
||||
|
||||
// v1 and v2 are known to have the same type.
|
||||
func equalStruct(v1, v2 reflect.Value) bool {
|
||||
sprop := GetProperties(v1.Type())
|
||||
for i := 0; i < v1.NumField(); i++ {
|
||||
f := v1.Type().Field(i)
|
||||
if strings.HasPrefix(f.Name, "XXX_") {
|
||||
continue
|
||||
}
|
||||
f1, f2 := v1.Field(i), v2.Field(i)
|
||||
if f.Type.Kind() == reflect.Ptr {
|
||||
if n1, n2 := f1.IsNil(), f2.IsNil(); n1 && n2 {
|
||||
// both unset
|
||||
continue
|
||||
} else if n1 != n2 {
|
||||
// set/unset mismatch
|
||||
return false
|
||||
}
|
||||
b1, ok := f1.Interface().(raw)
|
||||
if ok {
|
||||
b2 := f2.Interface().(raw)
|
||||
// RawMessage
|
||||
if !bytes.Equal(b1.Bytes(), b2.Bytes()) {
|
||||
return false
|
||||
}
|
||||
continue
|
||||
}
|
||||
f1, f2 = f1.Elem(), f2.Elem()
|
||||
}
|
||||
if !equalAny(f1, f2, sprop.Prop[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if em1 := v1.FieldByName("XXX_InternalExtensions"); em1.IsValid() {
|
||||
em2 := v2.FieldByName("XXX_InternalExtensions")
|
||||
if !equalExtensions(v1.Type(), em1.Interface().(XXX_InternalExtensions), em2.Interface().(XXX_InternalExtensions)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if em1 := v1.FieldByName("XXX_extensions"); em1.IsValid() {
|
||||
em2 := v2.FieldByName("XXX_extensions")
|
||||
if !equalExtMap(v1.Type(), em1.Interface().(map[int32]Extension), em2.Interface().(map[int32]Extension)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
uf := v1.FieldByName("XXX_unrecognized")
|
||||
if !uf.IsValid() {
|
||||
return true
|
||||
}
|
||||
|
||||
u1 := uf.Bytes()
|
||||
u2 := v2.FieldByName("XXX_unrecognized").Bytes()
|
||||
if !bytes.Equal(u1, u2) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// v1 and v2 are known to have the same type.
|
||||
// prop may be nil.
|
||||
func equalAny(v1, v2 reflect.Value, prop *Properties) bool {
|
||||
if v1.Type() == protoMessageType {
|
||||
m1, _ := v1.Interface().(Message)
|
||||
m2, _ := v2.Interface().(Message)
|
||||
return Equal(m1, m2)
|
||||
}
|
||||
switch v1.Kind() {
|
||||
case reflect.Bool:
|
||||
return v1.Bool() == v2.Bool()
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return v1.Float() == v2.Float()
|
||||
case reflect.Int32, reflect.Int64:
|
||||
return v1.Int() == v2.Int()
|
||||
case reflect.Interface:
|
||||
// Probably a oneof field; compare the inner values.
|
||||
n1, n2 := v1.IsNil(), v2.IsNil()
|
||||
if n1 || n2 {
|
||||
return n1 == n2
|
||||
}
|
||||
e1, e2 := v1.Elem(), v2.Elem()
|
||||
if e1.Type() != e2.Type() {
|
||||
return false
|
||||
}
|
||||
return equalAny(e1, e2, nil)
|
||||
case reflect.Map:
|
||||
if v1.Len() != v2.Len() {
|
||||
return false
|
||||
}
|
||||
for _, key := range v1.MapKeys() {
|
||||
val2 := v2.MapIndex(key)
|
||||
if !val2.IsValid() {
|
||||
// This key was not found in the second map.
|
||||
return false
|
||||
}
|
||||
if !equalAny(v1.MapIndex(key), val2, nil) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
case reflect.Ptr:
|
||||
// Maps may have nil values in them, so check for nil.
|
||||
if v1.IsNil() && v2.IsNil() {
|
||||
return true
|
||||
}
|
||||
if v1.IsNil() != v2.IsNil() {
|
||||
return false
|
||||
}
|
||||
return equalAny(v1.Elem(), v2.Elem(), prop)
|
||||
case reflect.Slice:
|
||||
if v1.Type().Elem().Kind() == reflect.Uint8 {
|
||||
// short circuit: []byte
|
||||
|
||||
// Edge case: if this is in a proto3 message, a zero length
|
||||
// bytes field is considered the zero value.
|
||||
if prop != nil && prop.proto3 && v1.Len() == 0 && v2.Len() == 0 {
|
||||
return true
|
||||
}
|
||||
if v1.IsNil() != v2.IsNil() {
|
||||
return false
|
||||
}
|
||||
return bytes.Equal(v1.Interface().([]byte), v2.Interface().([]byte))
|
||||
}
|
||||
|
||||
if v1.Len() != v2.Len() {
|
||||
return false
|
||||
}
|
||||
for i := 0; i < v1.Len(); i++ {
|
||||
if !equalAny(v1.Index(i), v2.Index(i), prop) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
case reflect.String:
|
||||
return v1.Interface().(string) == v2.Interface().(string)
|
||||
case reflect.Struct:
|
||||
return equalStruct(v1, v2)
|
||||
case reflect.Uint32, reflect.Uint64:
|
||||
return v1.Uint() == v2.Uint()
|
||||
}
|
||||
|
||||
// unknown type, so not a protocol buffer
|
||||
log.Printf("proto: don't know how to compare %v", v1)
|
||||
return false
|
||||
}
|
||||
|
||||
// base is the struct type that the extensions are based on.
|
||||
// x1 and x2 are InternalExtensions.
|
||||
func equalExtensions(base reflect.Type, x1, x2 XXX_InternalExtensions) bool {
|
||||
em1, _ := x1.extensionsRead()
|
||||
em2, _ := x2.extensionsRead()
|
||||
return equalExtMap(base, em1, em2)
|
||||
}
|
||||
|
||||
func equalExtMap(base reflect.Type, em1, em2 map[int32]Extension) bool {
|
||||
if len(em1) != len(em2) {
|
||||
return false
|
||||
}
|
||||
|
||||
for extNum, e1 := range em1 {
|
||||
e2, ok := em2[extNum]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
m1, m2 := e1.value, e2.value
|
||||
|
||||
if m1 != nil && m2 != nil {
|
||||
// Both are unencoded.
|
||||
if !equalAny(reflect.ValueOf(m1), reflect.ValueOf(m2), nil) {
|
||||
return false
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// At least one is encoded. To do a semantically correct comparison
|
||||
// we need to unmarshal them first.
|
||||
var desc *ExtensionDesc
|
||||
if m := extensionMaps[base]; m != nil {
|
||||
desc = m[extNum]
|
||||
}
|
||||
if desc == nil {
|
||||
log.Printf("proto: don't know how to compare extension %d of %v", extNum, base)
|
||||
continue
|
||||
}
|
||||
var err error
|
||||
if m1 == nil {
|
||||
m1, err = decodeExtension(e1.enc, desc)
|
||||
}
|
||||
if m2 == nil && err == nil {
|
||||
m2, err = decodeExtension(e2.enc, desc)
|
||||
}
|
||||
if err != nil {
|
||||
// The encoded form is invalid.
|
||||
log.Printf("proto: badly encoded extension %d of %v: %v", extNum, base, err)
|
||||
return false
|
||||
}
|
||||
if !equalAny(reflect.ValueOf(m1), reflect.ValueOf(m2), nil) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
+587
@@ -0,0 +1,587 @@
|
||||
// Go support for Protocol Buffers - Google's data interchange format
|
||||
//
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// https://github.com/golang/protobuf
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package proto
|
||||
|
||||
/*
|
||||
* Types and routines for supporting protocol buffer extensions.
|
||||
*/
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// ErrMissingExtension is the error returned by GetExtension if the named extension is not in the message.
|
||||
var ErrMissingExtension = errors.New("proto: missing extension")
|
||||
|
||||
// ExtensionRange represents a range of message extensions for a protocol buffer.
|
||||
// Used in code generated by the protocol compiler.
|
||||
type ExtensionRange struct {
|
||||
Start, End int32 // both inclusive
|
||||
}
|
||||
|
||||
// extendableProto is an interface implemented by any protocol buffer generated by the current
|
||||
// proto compiler that may be extended.
|
||||
type extendableProto interface {
|
||||
Message
|
||||
ExtensionRangeArray() []ExtensionRange
|
||||
extensionsWrite() map[int32]Extension
|
||||
extensionsRead() (map[int32]Extension, sync.Locker)
|
||||
}
|
||||
|
||||
// extendableProtoV1 is an interface implemented by a protocol buffer generated by the previous
|
||||
// version of the proto compiler that may be extended.
|
||||
type extendableProtoV1 interface {
|
||||
Message
|
||||
ExtensionRangeArray() []ExtensionRange
|
||||
ExtensionMap() map[int32]Extension
|
||||
}
|
||||
|
||||
// extensionAdapter is a wrapper around extendableProtoV1 that implements extendableProto.
|
||||
type extensionAdapter struct {
|
||||
extendableProtoV1
|
||||
}
|
||||
|
||||
func (e extensionAdapter) extensionsWrite() map[int32]Extension {
|
||||
return e.ExtensionMap()
|
||||
}
|
||||
|
||||
func (e extensionAdapter) extensionsRead() (map[int32]Extension, sync.Locker) {
|
||||
return e.ExtensionMap(), notLocker{}
|
||||
}
|
||||
|
||||
// notLocker is a sync.Locker whose Lock and Unlock methods are nops.
|
||||
type notLocker struct{}
|
||||
|
||||
func (n notLocker) Lock() {}
|
||||
func (n notLocker) Unlock() {}
|
||||
|
||||
// extendable returns the extendableProto interface for the given generated proto message.
|
||||
// If the proto message has the old extension format, it returns a wrapper that implements
|
||||
// the extendableProto interface.
|
||||
func extendable(p interface{}) (extendableProto, bool) {
|
||||
if ep, ok := p.(extendableProto); ok {
|
||||
return ep, ok
|
||||
}
|
||||
if ep, ok := p.(extendableProtoV1); ok {
|
||||
return extensionAdapter{ep}, ok
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// XXX_InternalExtensions is an internal representation of proto extensions.
|
||||
//
|
||||
// Each generated message struct type embeds an anonymous XXX_InternalExtensions field,
|
||||
// thus gaining the unexported 'extensions' method, which can be called only from the proto package.
|
||||
//
|
||||
// The methods of XXX_InternalExtensions are not concurrency safe in general,
|
||||
// but calls to logically read-only methods such as has and get may be executed concurrently.
|
||||
type XXX_InternalExtensions struct {
|
||||
// The struct must be indirect so that if a user inadvertently copies a
|
||||
// generated message and its embedded XXX_InternalExtensions, they
|
||||
// avoid the mayhem of a copied mutex.
|
||||
//
|
||||
// The mutex serializes all logically read-only operations to p.extensionMap.
|
||||
// It is up to the client to ensure that write operations to p.extensionMap are
|
||||
// mutually exclusive with other accesses.
|
||||
p *struct {
|
||||
mu sync.Mutex
|
||||
extensionMap map[int32]Extension
|
||||
}
|
||||
}
|
||||
|
||||
// extensionsWrite returns the extension map, creating it on first use.
|
||||
func (e *XXX_InternalExtensions) extensionsWrite() map[int32]Extension {
|
||||
if e.p == nil {
|
||||
e.p = new(struct {
|
||||
mu sync.Mutex
|
||||
extensionMap map[int32]Extension
|
||||
})
|
||||
e.p.extensionMap = make(map[int32]Extension)
|
||||
}
|
||||
return e.p.extensionMap
|
||||
}
|
||||
|
||||
// extensionsRead returns the extensions map for read-only use. It may be nil.
|
||||
// The caller must hold the returned mutex's lock when accessing Elements within the map.
|
||||
func (e *XXX_InternalExtensions) extensionsRead() (map[int32]Extension, sync.Locker) {
|
||||
if e.p == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return e.p.extensionMap, &e.p.mu
|
||||
}
|
||||
|
||||
var extendableProtoType = reflect.TypeOf((*extendableProto)(nil)).Elem()
|
||||
var extendableProtoV1Type = reflect.TypeOf((*extendableProtoV1)(nil)).Elem()
|
||||
|
||||
// ExtensionDesc represents an extension specification.
|
||||
// Used in generated code from the protocol compiler.
|
||||
type ExtensionDesc struct {
|
||||
ExtendedType Message // nil pointer to the type that is being extended
|
||||
ExtensionType interface{} // nil pointer to the extension type
|
||||
Field int32 // field number
|
||||
Name string // fully-qualified name of extension, for text formatting
|
||||
Tag string // protobuf tag style
|
||||
Filename string // name of the file in which the extension is defined
|
||||
}
|
||||
|
||||
func (ed *ExtensionDesc) repeated() bool {
|
||||
t := reflect.TypeOf(ed.ExtensionType)
|
||||
return t.Kind() == reflect.Slice && t.Elem().Kind() != reflect.Uint8
|
||||
}
|
||||
|
||||
// Extension represents an extension in a message.
|
||||
type Extension struct {
|
||||
// When an extension is stored in a message using SetExtension
|
||||
// only desc and value are set. When the message is marshaled
|
||||
// enc will be set to the encoded form of the message.
|
||||
//
|
||||
// When a message is unmarshaled and contains extensions, each
|
||||
// extension will have only enc set. When such an extension is
|
||||
// accessed using GetExtension (or GetExtensions) desc and value
|
||||
// will be set.
|
||||
desc *ExtensionDesc
|
||||
value interface{}
|
||||
enc []byte
|
||||
}
|
||||
|
||||
// SetRawExtension is for testing only.
|
||||
func SetRawExtension(base Message, id int32, b []byte) {
|
||||
epb, ok := extendable(base)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
extmap := epb.extensionsWrite()
|
||||
extmap[id] = Extension{enc: b}
|
||||
}
|
||||
|
||||
// isExtensionField returns true iff the given field number is in an extension range.
|
||||
func isExtensionField(pb extendableProto, field int32) bool {
|
||||
for _, er := range pb.ExtensionRangeArray() {
|
||||
if er.Start <= field && field <= er.End {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// checkExtensionTypes checks that the given extension is valid for pb.
|
||||
func checkExtensionTypes(pb extendableProto, extension *ExtensionDesc) error {
|
||||
var pbi interface{} = pb
|
||||
// Check the extended type.
|
||||
if ea, ok := pbi.(extensionAdapter); ok {
|
||||
pbi = ea.extendableProtoV1
|
||||
}
|
||||
if a, b := reflect.TypeOf(pbi), reflect.TypeOf(extension.ExtendedType); a != b {
|
||||
return errors.New("proto: bad extended type; " + b.String() + " does not extend " + a.String())
|
||||
}
|
||||
// Check the range.
|
||||
if !isExtensionField(pb, extension.Field) {
|
||||
return errors.New("proto: bad extension number; not in declared ranges")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// extPropKey is sufficient to uniquely identify an extension.
|
||||
type extPropKey struct {
|
||||
base reflect.Type
|
||||
field int32
|
||||
}
|
||||
|
||||
var extProp = struct {
|
||||
sync.RWMutex
|
||||
m map[extPropKey]*Properties
|
||||
}{
|
||||
m: make(map[extPropKey]*Properties),
|
||||
}
|
||||
|
||||
func extensionProperties(ed *ExtensionDesc) *Properties {
|
||||
key := extPropKey{base: reflect.TypeOf(ed.ExtendedType), field: ed.Field}
|
||||
|
||||
extProp.RLock()
|
||||
if prop, ok := extProp.m[key]; ok {
|
||||
extProp.RUnlock()
|
||||
return prop
|
||||
}
|
||||
extProp.RUnlock()
|
||||
|
||||
extProp.Lock()
|
||||
defer extProp.Unlock()
|
||||
// Check again.
|
||||
if prop, ok := extProp.m[key]; ok {
|
||||
return prop
|
||||
}
|
||||
|
||||
prop := new(Properties)
|
||||
prop.Init(reflect.TypeOf(ed.ExtensionType), "unknown_name", ed.Tag, nil)
|
||||
extProp.m[key] = prop
|
||||
return prop
|
||||
}
|
||||
|
||||
// encode encodes any unmarshaled (unencoded) extensions in e.
|
||||
func encodeExtensions(e *XXX_InternalExtensions) error {
|
||||
m, mu := e.extensionsRead()
|
||||
if m == nil {
|
||||
return nil // fast path
|
||||
}
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
return encodeExtensionsMap(m)
|
||||
}
|
||||
|
||||
// encode encodes any unmarshaled (unencoded) extensions in e.
|
||||
func encodeExtensionsMap(m map[int32]Extension) error {
|
||||
for k, e := range m {
|
||||
if e.value == nil || e.desc == nil {
|
||||
// Extension is only in its encoded form.
|
||||
continue
|
||||
}
|
||||
|
||||
// We don't skip extensions that have an encoded form set,
|
||||
// because the extension value may have been mutated after
|
||||
// the last time this function was called.
|
||||
|
||||
et := reflect.TypeOf(e.desc.ExtensionType)
|
||||
props := extensionProperties(e.desc)
|
||||
|
||||
p := NewBuffer(nil)
|
||||
// If e.value has type T, the encoder expects a *struct{ X T }.
|
||||
// Pass a *T with a zero field and hope it all works out.
|
||||
x := reflect.New(et)
|
||||
x.Elem().Set(reflect.ValueOf(e.value))
|
||||
if err := props.enc(p, props, toStructPointer(x)); err != nil {
|
||||
return err
|
||||
}
|
||||
e.enc = p.buf
|
||||
m[k] = e
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func extensionsSize(e *XXX_InternalExtensions) (n int) {
|
||||
m, mu := e.extensionsRead()
|
||||
if m == nil {
|
||||
return 0
|
||||
}
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
return extensionsMapSize(m)
|
||||
}
|
||||
|
||||
func extensionsMapSize(m map[int32]Extension) (n int) {
|
||||
for _, e := range m {
|
||||
if e.value == nil || e.desc == nil {
|
||||
// Extension is only in its encoded form.
|
||||
n += len(e.enc)
|
||||
continue
|
||||
}
|
||||
|
||||
// We don't skip extensions that have an encoded form set,
|
||||
// because the extension value may have been mutated after
|
||||
// the last time this function was called.
|
||||
|
||||
et := reflect.TypeOf(e.desc.ExtensionType)
|
||||
props := extensionProperties(e.desc)
|
||||
|
||||
// If e.value has type T, the encoder expects a *struct{ X T }.
|
||||
// Pass a *T with a zero field and hope it all works out.
|
||||
x := reflect.New(et)
|
||||
x.Elem().Set(reflect.ValueOf(e.value))
|
||||
n += props.size(props, toStructPointer(x))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// HasExtension returns whether the given extension is present in pb.
|
||||
func HasExtension(pb Message, extension *ExtensionDesc) bool {
|
||||
// TODO: Check types, field numbers, etc.?
|
||||
epb, ok := extendable(pb)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
extmap, mu := epb.extensionsRead()
|
||||
if extmap == nil {
|
||||
return false
|
||||
}
|
||||
mu.Lock()
|
||||
_, ok = extmap[extension.Field]
|
||||
mu.Unlock()
|
||||
return ok
|
||||
}
|
||||
|
||||
// ClearExtension removes the given extension from pb.
|
||||
func ClearExtension(pb Message, extension *ExtensionDesc) {
|
||||
epb, ok := extendable(pb)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
// TODO: Check types, field numbers, etc.?
|
||||
extmap := epb.extensionsWrite()
|
||||
delete(extmap, extension.Field)
|
||||
}
|
||||
|
||||
// GetExtension parses and returns the given extension of pb.
|
||||
// If the extension is not present and has no default value it returns ErrMissingExtension.
|
||||
func GetExtension(pb Message, extension *ExtensionDesc) (interface{}, error) {
|
||||
epb, ok := extendable(pb)
|
||||
if !ok {
|
||||
return nil, errors.New("proto: not an extendable proto")
|
||||
}
|
||||
|
||||
if err := checkExtensionTypes(epb, extension); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
emap, mu := epb.extensionsRead()
|
||||
if emap == nil {
|
||||
return defaultExtensionValue(extension)
|
||||
}
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
e, ok := emap[extension.Field]
|
||||
if !ok {
|
||||
// defaultExtensionValue returns the default value or
|
||||
// ErrMissingExtension if there is no default.
|
||||
return defaultExtensionValue(extension)
|
||||
}
|
||||
|
||||
if e.value != nil {
|
||||
// Already decoded. Check the descriptor, though.
|
||||
if e.desc != extension {
|
||||
// This shouldn't happen. If it does, it means that
|
||||
// GetExtension was called twice with two different
|
||||
// descriptors with the same field number.
|
||||
return nil, errors.New("proto: descriptor conflict")
|
||||
}
|
||||
return e.value, nil
|
||||
}
|
||||
|
||||
v, err := decodeExtension(e.enc, extension)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Remember the decoded version and drop the encoded version.
|
||||
// That way it is safe to mutate what we return.
|
||||
e.value = v
|
||||
e.desc = extension
|
||||
e.enc = nil
|
||||
emap[extension.Field] = e
|
||||
return e.value, nil
|
||||
}
|
||||
|
||||
// defaultExtensionValue returns the default value for extension.
|
||||
// If no default for an extension is defined ErrMissingExtension is returned.
|
||||
func defaultExtensionValue(extension *ExtensionDesc) (interface{}, error) {
|
||||
t := reflect.TypeOf(extension.ExtensionType)
|
||||
props := extensionProperties(extension)
|
||||
|
||||
sf, _, err := fieldDefault(t, props)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if sf == nil || sf.value == nil {
|
||||
// There is no default value.
|
||||
return nil, ErrMissingExtension
|
||||
}
|
||||
|
||||
if t.Kind() != reflect.Ptr {
|
||||
// We do not need to return a Ptr, we can directly return sf.value.
|
||||
return sf.value, nil
|
||||
}
|
||||
|
||||
// We need to return an interface{} that is a pointer to sf.value.
|
||||
value := reflect.New(t).Elem()
|
||||
value.Set(reflect.New(value.Type().Elem()))
|
||||
if sf.kind == reflect.Int32 {
|
||||
// We may have an int32 or an enum, but the underlying data is int32.
|
||||
// Since we can't set an int32 into a non int32 reflect.value directly
|
||||
// set it as a int32.
|
||||
value.Elem().SetInt(int64(sf.value.(int32)))
|
||||
} else {
|
||||
value.Elem().Set(reflect.ValueOf(sf.value))
|
||||
}
|
||||
return value.Interface(), nil
|
||||
}
|
||||
|
||||
// decodeExtension decodes an extension encoded in b.
|
||||
func decodeExtension(b []byte, extension *ExtensionDesc) (interface{}, error) {
|
||||
o := NewBuffer(b)
|
||||
|
||||
t := reflect.TypeOf(extension.ExtensionType)
|
||||
|
||||
props := extensionProperties(extension)
|
||||
|
||||
// t is a pointer to a struct, pointer to basic type or a slice.
|
||||
// Allocate a "field" to store the pointer/slice itself; the
|
||||
// pointer/slice will be stored here. We pass
|
||||
// the address of this field to props.dec.
|
||||
// This passes a zero field and a *t and lets props.dec
|
||||
// interpret it as a *struct{ x t }.
|
||||
value := reflect.New(t).Elem()
|
||||
|
||||
for {
|
||||
// Discard wire type and field number varint. It isn't needed.
|
||||
if _, err := o.DecodeVarint(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := props.dec(o, props, toStructPointer(value.Addr())); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if o.index >= len(o.buf) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return value.Interface(), nil
|
||||
}
|
||||
|
||||
// GetExtensions returns a slice of the extensions present in pb that are also listed in es.
|
||||
// The returned slice has the same length as es; missing extensions will appear as nil elements.
|
||||
func GetExtensions(pb Message, es []*ExtensionDesc) (extensions []interface{}, err error) {
|
||||
epb, ok := extendable(pb)
|
||||
if !ok {
|
||||
return nil, errors.New("proto: not an extendable proto")
|
||||
}
|
||||
extensions = make([]interface{}, len(es))
|
||||
for i, e := range es {
|
||||
extensions[i], err = GetExtension(epb, e)
|
||||
if err == ErrMissingExtension {
|
||||
err = nil
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ExtensionDescs returns a new slice containing pb's extension descriptors, in undefined order.
|
||||
// For non-registered extensions, ExtensionDescs returns an incomplete descriptor containing
|
||||
// just the Field field, which defines the extension's field number.
|
||||
func ExtensionDescs(pb Message) ([]*ExtensionDesc, error) {
|
||||
epb, ok := extendable(pb)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("proto: %T is not an extendable proto.Message", pb)
|
||||
}
|
||||
registeredExtensions := RegisteredExtensions(pb)
|
||||
|
||||
emap, mu := epb.extensionsRead()
|
||||
if emap == nil {
|
||||
return nil, nil
|
||||
}
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
extensions := make([]*ExtensionDesc, 0, len(emap))
|
||||
for extid, e := range emap {
|
||||
desc := e.desc
|
||||
if desc == nil {
|
||||
desc = registeredExtensions[extid]
|
||||
if desc == nil {
|
||||
desc = &ExtensionDesc{Field: extid}
|
||||
}
|
||||
}
|
||||
|
||||
extensions = append(extensions, desc)
|
||||
}
|
||||
return extensions, nil
|
||||
}
|
||||
|
||||
// SetExtension sets the specified extension of pb to the specified value.
|
||||
func SetExtension(pb Message, extension *ExtensionDesc, value interface{}) error {
|
||||
epb, ok := extendable(pb)
|
||||
if !ok {
|
||||
return errors.New("proto: not an extendable proto")
|
||||
}
|
||||
if err := checkExtensionTypes(epb, extension); err != nil {
|
||||
return err
|
||||
}
|
||||
typ := reflect.TypeOf(extension.ExtensionType)
|
||||
if typ != reflect.TypeOf(value) {
|
||||
return errors.New("proto: bad extension value type")
|
||||
}
|
||||
// nil extension values need to be caught early, because the
|
||||
// encoder can't distinguish an ErrNil due to a nil extension
|
||||
// from an ErrNil due to a missing field. Extensions are
|
||||
// always optional, so the encoder would just swallow the error
|
||||
// and drop all the extensions from the encoded message.
|
||||
if reflect.ValueOf(value).IsNil() {
|
||||
return fmt.Errorf("proto: SetExtension called with nil value of type %T", value)
|
||||
}
|
||||
|
||||
extmap := epb.extensionsWrite()
|
||||
extmap[extension.Field] = Extension{desc: extension, value: value}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ClearAllExtensions clears all extensions from pb.
|
||||
func ClearAllExtensions(pb Message) {
|
||||
epb, ok := extendable(pb)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
m := epb.extensionsWrite()
|
||||
for k := range m {
|
||||
delete(m, k)
|
||||
}
|
||||
}
|
||||
|
||||
// A global registry of extensions.
|
||||
// The generated code will register the generated descriptors by calling RegisterExtension.
|
||||
|
||||
var extensionMaps = make(map[reflect.Type]map[int32]*ExtensionDesc)
|
||||
|
||||
// RegisterExtension is called from the generated code.
|
||||
func RegisterExtension(desc *ExtensionDesc) {
|
||||
st := reflect.TypeOf(desc.ExtendedType).Elem()
|
||||
m := extensionMaps[st]
|
||||
if m == nil {
|
||||
m = make(map[int32]*ExtensionDesc)
|
||||
extensionMaps[st] = m
|
||||
}
|
||||
if _, ok := m[desc.Field]; ok {
|
||||
panic("proto: duplicate extension registered: " + st.String() + " " + strconv.Itoa(int(desc.Field)))
|
||||
}
|
||||
m[desc.Field] = desc
|
||||
}
|
||||
|
||||
// RegisteredExtensions returns a map of the registered extensions of a
|
||||
// protocol buffer struct, indexed by the extension number.
|
||||
// The argument pb should be a nil pointer to the struct type.
|
||||
func RegisteredExtensions(pb Message) map[int32]*ExtensionDesc {
|
||||
return extensionMaps[reflect.TypeOf(pb).Elem()]
|
||||
}
|
||||
+898
@@ -0,0 +1,898 @@
|
||||
// Go support for Protocol Buffers - Google's data interchange format
|
||||
//
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// https://github.com/golang/protobuf
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
/*
|
||||
Package proto converts data structures to and from the wire format of
|
||||
protocol buffers. It works in concert with the Go source code generated
|
||||
for .proto files by the protocol compiler.
|
||||
|
||||
A summary of the properties of the protocol buffer interface
|
||||
for a protocol buffer variable v:
|
||||
|
||||
- Names are turned from camel_case to CamelCase for export.
|
||||
- There are no methods on v to set fields; just treat
|
||||
them as structure fields.
|
||||
- There are getters that return a field's value if set,
|
||||
and return the field's default value if unset.
|
||||
The getters work even if the receiver is a nil message.
|
||||
- The zero value for a struct is its correct initialization state.
|
||||
All desired fields must be set before marshaling.
|
||||
- A Reset() method will restore a protobuf struct to its zero state.
|
||||
- Non-repeated fields are pointers to the values; nil means unset.
|
||||
That is, optional or required field int32 f becomes F *int32.
|
||||
- Repeated fields are slices.
|
||||
- Helper functions are available to aid the setting of fields.
|
||||
msg.Foo = proto.String("hello") // set field
|
||||
- Constants are defined to hold the default values of all fields that
|
||||
have them. They have the form Default_StructName_FieldName.
|
||||
Because the getter methods handle defaulted values,
|
||||
direct use of these constants should be rare.
|
||||
- Enums are given type names and maps from names to values.
|
||||
Enum values are prefixed by the enclosing message's name, or by the
|
||||
enum's type name if it is a top-level enum. Enum types have a String
|
||||
method, and a Enum method to assist in message construction.
|
||||
- Nested messages, groups and enums have type names prefixed with the name of
|
||||
the surrounding message type.
|
||||
- Extensions are given descriptor names that start with E_,
|
||||
followed by an underscore-delimited list of the nested messages
|
||||
that contain it (if any) followed by the CamelCased name of the
|
||||
extension field itself. HasExtension, ClearExtension, GetExtension
|
||||
and SetExtension are functions for manipulating extensions.
|
||||
- Oneof field sets are given a single field in their message,
|
||||
with distinguished wrapper types for each possible field value.
|
||||
- Marshal and Unmarshal are functions to encode and decode the wire format.
|
||||
|
||||
When the .proto file specifies `syntax="proto3"`, there are some differences:
|
||||
|
||||
- Non-repeated fields of non-message type are values instead of pointers.
|
||||
- Getters are only generated for message and oneof fields.
|
||||
- Enum types do not get an Enum method.
|
||||
|
||||
The simplest way to describe this is to see an example.
|
||||
Given file test.proto, containing
|
||||
|
||||
package example;
|
||||
|
||||
enum FOO { X = 17; }
|
||||
|
||||
message Test {
|
||||
required string label = 1;
|
||||
optional int32 type = 2 [default=77];
|
||||
repeated int64 reps = 3;
|
||||
optional group OptionalGroup = 4 {
|
||||
required string RequiredField = 5;
|
||||
}
|
||||
oneof union {
|
||||
int32 number = 6;
|
||||
string name = 7;
|
||||
}
|
||||
}
|
||||
|
||||
The resulting file, test.pb.go, is:
|
||||
|
||||
package example
|
||||
|
||||
import proto "github.com/golang/protobuf/proto"
|
||||
import math "math"
|
||||
|
||||
type FOO int32
|
||||
const (
|
||||
FOO_X FOO = 17
|
||||
)
|
||||
var FOO_name = map[int32]string{
|
||||
17: "X",
|
||||
}
|
||||
var FOO_value = map[string]int32{
|
||||
"X": 17,
|
||||
}
|
||||
|
||||
func (x FOO) Enum() *FOO {
|
||||
p := new(FOO)
|
||||
*p = x
|
||||
return p
|
||||
}
|
||||
func (x FOO) String() string {
|
||||
return proto.EnumName(FOO_name, int32(x))
|
||||
}
|
||||
func (x *FOO) UnmarshalJSON(data []byte) error {
|
||||
value, err := proto.UnmarshalJSONEnum(FOO_value, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*x = FOO(value)
|
||||
return nil
|
||||
}
|
||||
|
||||
type Test struct {
|
||||
Label *string `protobuf:"bytes,1,req,name=label" json:"label,omitempty"`
|
||||
Type *int32 `protobuf:"varint,2,opt,name=type,def=77" json:"type,omitempty"`
|
||||
Reps []int64 `protobuf:"varint,3,rep,name=reps" json:"reps,omitempty"`
|
||||
Optionalgroup *Test_OptionalGroup `protobuf:"group,4,opt,name=OptionalGroup" json:"optionalgroup,omitempty"`
|
||||
// Types that are valid to be assigned to Union:
|
||||
// *Test_Number
|
||||
// *Test_Name
|
||||
Union isTest_Union `protobuf_oneof:"union"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
}
|
||||
func (m *Test) Reset() { *m = Test{} }
|
||||
func (m *Test) String() string { return proto.CompactTextString(m) }
|
||||
func (*Test) ProtoMessage() {}
|
||||
|
||||
type isTest_Union interface {
|
||||
isTest_Union()
|
||||
}
|
||||
|
||||
type Test_Number struct {
|
||||
Number int32 `protobuf:"varint,6,opt,name=number"`
|
||||
}
|
||||
type Test_Name struct {
|
||||
Name string `protobuf:"bytes,7,opt,name=name"`
|
||||
}
|
||||
|
||||
func (*Test_Number) isTest_Union() {}
|
||||
func (*Test_Name) isTest_Union() {}
|
||||
|
||||
func (m *Test) GetUnion() isTest_Union {
|
||||
if m != nil {
|
||||
return m.Union
|
||||
}
|
||||
return nil
|
||||
}
|
||||
const Default_Test_Type int32 = 77
|
||||
|
||||
func (m *Test) GetLabel() string {
|
||||
if m != nil && m.Label != nil {
|
||||
return *m.Label
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Test) GetType() int32 {
|
||||
if m != nil && m.Type != nil {
|
||||
return *m.Type
|
||||
}
|
||||
return Default_Test_Type
|
||||
}
|
||||
|
||||
func (m *Test) GetOptionalgroup() *Test_OptionalGroup {
|
||||
if m != nil {
|
||||
return m.Optionalgroup
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Test_OptionalGroup struct {
|
||||
RequiredField *string `protobuf:"bytes,5,req" json:"RequiredField,omitempty"`
|
||||
}
|
||||
func (m *Test_OptionalGroup) Reset() { *m = Test_OptionalGroup{} }
|
||||
func (m *Test_OptionalGroup) String() string { return proto.CompactTextString(m) }
|
||||
|
||||
func (m *Test_OptionalGroup) GetRequiredField() string {
|
||||
if m != nil && m.RequiredField != nil {
|
||||
return *m.RequiredField
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Test) GetNumber() int32 {
|
||||
if x, ok := m.GetUnion().(*Test_Number); ok {
|
||||
return x.Number
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *Test) GetName() string {
|
||||
if x, ok := m.GetUnion().(*Test_Name); ok {
|
||||
return x.Name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterEnum("example.FOO", FOO_name, FOO_value)
|
||||
}
|
||||
|
||||
To create and play with a Test object:
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
pb "./example.pb"
|
||||
)
|
||||
|
||||
func main() {
|
||||
test := &pb.Test{
|
||||
Label: proto.String("hello"),
|
||||
Type: proto.Int32(17),
|
||||
Reps: []int64{1, 2, 3},
|
||||
Optionalgroup: &pb.Test_OptionalGroup{
|
||||
RequiredField: proto.String("good bye"),
|
||||
},
|
||||
Union: &pb.Test_Name{"fred"},
|
||||
}
|
||||
data, err := proto.Marshal(test)
|
||||
if err != nil {
|
||||
log.Fatal("marshaling error: ", err)
|
||||
}
|
||||
newTest := &pb.Test{}
|
||||
err = proto.Unmarshal(data, newTest)
|
||||
if err != nil {
|
||||
log.Fatal("unmarshaling error: ", err)
|
||||
}
|
||||
// Now test and newTest contain the same data.
|
||||
if test.GetLabel() != newTest.GetLabel() {
|
||||
log.Fatalf("data mismatch %q != %q", test.GetLabel(), newTest.GetLabel())
|
||||
}
|
||||
// Use a type switch to determine which oneof was set.
|
||||
switch u := test.Union.(type) {
|
||||
case *pb.Test_Number: // u.Number contains the number.
|
||||
case *pb.Test_Name: // u.Name contains the string.
|
||||
}
|
||||
// etc.
|
||||
}
|
||||
*/
|
||||
package proto
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Message is implemented by generated protocol buffer messages.
|
||||
type Message interface {
|
||||
Reset()
|
||||
String() string
|
||||
ProtoMessage()
|
||||
}
|
||||
|
||||
// Stats records allocation details about the protocol buffer encoders
|
||||
// and decoders. Useful for tuning the library itself.
|
||||
type Stats struct {
|
||||
Emalloc uint64 // mallocs in encode
|
||||
Dmalloc uint64 // mallocs in decode
|
||||
Encode uint64 // number of encodes
|
||||
Decode uint64 // number of decodes
|
||||
Chit uint64 // number of cache hits
|
||||
Cmiss uint64 // number of cache misses
|
||||
Size uint64 // number of sizes
|
||||
}
|
||||
|
||||
// Set to true to enable stats collection.
|
||||
const collectStats = false
|
||||
|
||||
var stats Stats
|
||||
|
||||
// GetStats returns a copy of the global Stats structure.
|
||||
func GetStats() Stats { return stats }
|
||||
|
||||
// A Buffer is a buffer manager for marshaling and unmarshaling
|
||||
// protocol buffers. It may be reused between invocations to
|
||||
// reduce memory usage. It is not necessary to use a Buffer;
|
||||
// the global functions Marshal and Unmarshal create a
|
||||
// temporary Buffer and are fine for most applications.
|
||||
type Buffer struct {
|
||||
buf []byte // encode/decode byte stream
|
||||
index int // read point
|
||||
|
||||
// pools of basic types to amortize allocation.
|
||||
bools []bool
|
||||
uint32s []uint32
|
||||
uint64s []uint64
|
||||
|
||||
// extra pools, only used with pointer_reflect.go
|
||||
int32s []int32
|
||||
int64s []int64
|
||||
float32s []float32
|
||||
float64s []float64
|
||||
}
|
||||
|
||||
// NewBuffer allocates a new Buffer and initializes its internal data to
|
||||
// the contents of the argument slice.
|
||||
func NewBuffer(e []byte) *Buffer {
|
||||
return &Buffer{buf: e}
|
||||
}
|
||||
|
||||
// Reset resets the Buffer, ready for marshaling a new protocol buffer.
|
||||
func (p *Buffer) Reset() {
|
||||
p.buf = p.buf[0:0] // for reading/writing
|
||||
p.index = 0 // for reading
|
||||
}
|
||||
|
||||
// SetBuf replaces the internal buffer with the slice,
|
||||
// ready for unmarshaling the contents of the slice.
|
||||
func (p *Buffer) SetBuf(s []byte) {
|
||||
p.buf = s
|
||||
p.index = 0
|
||||
}
|
||||
|
||||
// Bytes returns the contents of the Buffer.
|
||||
func (p *Buffer) Bytes() []byte { return p.buf }
|
||||
|
||||
/*
|
||||
* Helper routines for simplifying the creation of optional fields of basic type.
|
||||
*/
|
||||
|
||||
// Bool is a helper routine that allocates a new bool value
|
||||
// to store v and returns a pointer to it.
|
||||
func Bool(v bool) *bool {
|
||||
return &v
|
||||
}
|
||||
|
||||
// Int32 is a helper routine that allocates a new int32 value
|
||||
// to store v and returns a pointer to it.
|
||||
func Int32(v int32) *int32 {
|
||||
return &v
|
||||
}
|
||||
|
||||
// Int is a helper routine that allocates a new int32 value
|
||||
// to store v and returns a pointer to it, but unlike Int32
|
||||
// its argument value is an int.
|
||||
func Int(v int) *int32 {
|
||||
p := new(int32)
|
||||
*p = int32(v)
|
||||
return p
|
||||
}
|
||||
|
||||
// Int64 is a helper routine that allocates a new int64 value
|
||||
// to store v and returns a pointer to it.
|
||||
func Int64(v int64) *int64 {
|
||||
return &v
|
||||
}
|
||||
|
||||
// Float32 is a helper routine that allocates a new float32 value
|
||||
// to store v and returns a pointer to it.
|
||||
func Float32(v float32) *float32 {
|
||||
return &v
|
||||
}
|
||||
|
||||
// Float64 is a helper routine that allocates a new float64 value
|
||||
// to store v and returns a pointer to it.
|
||||
func Float64(v float64) *float64 {
|
||||
return &v
|
||||
}
|
||||
|
||||
// Uint32 is a helper routine that allocates a new uint32 value
|
||||
// to store v and returns a pointer to it.
|
||||
func Uint32(v uint32) *uint32 {
|
||||
return &v
|
||||
}
|
||||
|
||||
// Uint64 is a helper routine that allocates a new uint64 value
|
||||
// to store v and returns a pointer to it.
|
||||
func Uint64(v uint64) *uint64 {
|
||||
return &v
|
||||
}
|
||||
|
||||
// String is a helper routine that allocates a new string value
|
||||
// to store v and returns a pointer to it.
|
||||
func String(v string) *string {
|
||||
return &v
|
||||
}
|
||||
|
||||
// EnumName is a helper function to simplify printing protocol buffer enums
|
||||
// by name. Given an enum map and a value, it returns a useful string.
|
||||
func EnumName(m map[int32]string, v int32) string {
|
||||
s, ok := m[v]
|
||||
if ok {
|
||||
return s
|
||||
}
|
||||
return strconv.Itoa(int(v))
|
||||
}
|
||||
|
||||
// UnmarshalJSONEnum is a helper function to simplify recovering enum int values
|
||||
// from their JSON-encoded representation. Given a map from the enum's symbolic
|
||||
// names to its int values, and a byte buffer containing the JSON-encoded
|
||||
// value, it returns an int32 that can be cast to the enum type by the caller.
|
||||
//
|
||||
// The function can deal with both JSON representations, numeric and symbolic.
|
||||
func UnmarshalJSONEnum(m map[string]int32, data []byte, enumName string) (int32, error) {
|
||||
if data[0] == '"' {
|
||||
// New style: enums are strings.
|
||||
var repr string
|
||||
if err := json.Unmarshal(data, &repr); err != nil {
|
||||
return -1, err
|
||||
}
|
||||
val, ok := m[repr]
|
||||
if !ok {
|
||||
return 0, fmt.Errorf("unrecognized enum %s value %q", enumName, repr)
|
||||
}
|
||||
return val, nil
|
||||
}
|
||||
// Old style: enums are ints.
|
||||
var val int32
|
||||
if err := json.Unmarshal(data, &val); err != nil {
|
||||
return 0, fmt.Errorf("cannot unmarshal %#q into enum %s", data, enumName)
|
||||
}
|
||||
return val, nil
|
||||
}
|
||||
|
||||
// DebugPrint dumps the encoded data in b in a debugging format with a header
|
||||
// including the string s. Used in testing but made available for general debugging.
|
||||
func (p *Buffer) DebugPrint(s string, b []byte) {
|
||||
var u uint64
|
||||
|
||||
obuf := p.buf
|
||||
index := p.index
|
||||
p.buf = b
|
||||
p.index = 0
|
||||
depth := 0
|
||||
|
||||
fmt.Printf("\n--- %s ---\n", s)
|
||||
|
||||
out:
|
||||
for {
|
||||
for i := 0; i < depth; i++ {
|
||||
fmt.Print(" ")
|
||||
}
|
||||
|
||||
index := p.index
|
||||
if index == len(p.buf) {
|
||||
break
|
||||
}
|
||||
|
||||
op, err := p.DecodeVarint()
|
||||
if err != nil {
|
||||
fmt.Printf("%3d: fetching op err %v\n", index, err)
|
||||
break out
|
||||
}
|
||||
tag := op >> 3
|
||||
wire := op & 7
|
||||
|
||||
switch wire {
|
||||
default:
|
||||
fmt.Printf("%3d: t=%3d unknown wire=%d\n",
|
||||
index, tag, wire)
|
||||
break out
|
||||
|
||||
case WireBytes:
|
||||
var r []byte
|
||||
|
||||
r, err = p.DecodeRawBytes(false)
|
||||
if err != nil {
|
||||
break out
|
||||
}
|
||||
fmt.Printf("%3d: t=%3d bytes [%d]", index, tag, len(r))
|
||||
if len(r) <= 6 {
|
||||
for i := 0; i < len(r); i++ {
|
||||
fmt.Printf(" %.2x", r[i])
|
||||
}
|
||||
} else {
|
||||
for i := 0; i < 3; i++ {
|
||||
fmt.Printf(" %.2x", r[i])
|
||||
}
|
||||
fmt.Printf(" ..")
|
||||
for i := len(r) - 3; i < len(r); i++ {
|
||||
fmt.Printf(" %.2x", r[i])
|
||||
}
|
||||
}
|
||||
fmt.Printf("\n")
|
||||
|
||||
case WireFixed32:
|
||||
u, err = p.DecodeFixed32()
|
||||
if err != nil {
|
||||
fmt.Printf("%3d: t=%3d fix32 err %v\n", index, tag, err)
|
||||
break out
|
||||
}
|
||||
fmt.Printf("%3d: t=%3d fix32 %d\n", index, tag, u)
|
||||
|
||||
case WireFixed64:
|
||||
u, err = p.DecodeFixed64()
|
||||
if err != nil {
|
||||
fmt.Printf("%3d: t=%3d fix64 err %v\n", index, tag, err)
|
||||
break out
|
||||
}
|
||||
fmt.Printf("%3d: t=%3d fix64 %d\n", index, tag, u)
|
||||
|
||||
case WireVarint:
|
||||
u, err = p.DecodeVarint()
|
||||
if err != nil {
|
||||
fmt.Printf("%3d: t=%3d varint err %v\n", index, tag, err)
|
||||
break out
|
||||
}
|
||||
fmt.Printf("%3d: t=%3d varint %d\n", index, tag, u)
|
||||
|
||||
case WireStartGroup:
|
||||
fmt.Printf("%3d: t=%3d start\n", index, tag)
|
||||
depth++
|
||||
|
||||
case WireEndGroup:
|
||||
depth--
|
||||
fmt.Printf("%3d: t=%3d end\n", index, tag)
|
||||
}
|
||||
}
|
||||
|
||||
if depth != 0 {
|
||||
fmt.Printf("%3d: start-end not balanced %d\n", p.index, depth)
|
||||
}
|
||||
fmt.Printf("\n")
|
||||
|
||||
p.buf = obuf
|
||||
p.index = index
|
||||
}
|
||||
|
||||
// SetDefaults sets unset protocol buffer fields to their default values.
|
||||
// It only modifies fields that are both unset and have defined defaults.
|
||||
// It recursively sets default values in any non-nil sub-messages.
|
||||
func SetDefaults(pb Message) {
|
||||
setDefaults(reflect.ValueOf(pb), true, false)
|
||||
}
|
||||
|
||||
// v is a pointer to a struct.
|
||||
func setDefaults(v reflect.Value, recur, zeros bool) {
|
||||
v = v.Elem()
|
||||
|
||||
defaultMu.RLock()
|
||||
dm, ok := defaults[v.Type()]
|
||||
defaultMu.RUnlock()
|
||||
if !ok {
|
||||
dm = buildDefaultMessage(v.Type())
|
||||
defaultMu.Lock()
|
||||
defaults[v.Type()] = dm
|
||||
defaultMu.Unlock()
|
||||
}
|
||||
|
||||
for _, sf := range dm.scalars {
|
||||
f := v.Field(sf.index)
|
||||
if !f.IsNil() {
|
||||
// field already set
|
||||
continue
|
||||
}
|
||||
dv := sf.value
|
||||
if dv == nil && !zeros {
|
||||
// no explicit default, and don't want to set zeros
|
||||
continue
|
||||
}
|
||||
fptr := f.Addr().Interface() // **T
|
||||
// TODO: Consider batching the allocations we do here.
|
||||
switch sf.kind {
|
||||
case reflect.Bool:
|
||||
b := new(bool)
|
||||
if dv != nil {
|
||||
*b = dv.(bool)
|
||||
}
|
||||
*(fptr.(**bool)) = b
|
||||
case reflect.Float32:
|
||||
f := new(float32)
|
||||
if dv != nil {
|
||||
*f = dv.(float32)
|
||||
}
|
||||
*(fptr.(**float32)) = f
|
||||
case reflect.Float64:
|
||||
f := new(float64)
|
||||
if dv != nil {
|
||||
*f = dv.(float64)
|
||||
}
|
||||
*(fptr.(**float64)) = f
|
||||
case reflect.Int32:
|
||||
// might be an enum
|
||||
if ft := f.Type(); ft != int32PtrType {
|
||||
// enum
|
||||
f.Set(reflect.New(ft.Elem()))
|
||||
if dv != nil {
|
||||
f.Elem().SetInt(int64(dv.(int32)))
|
||||
}
|
||||
} else {
|
||||
// int32 field
|
||||
i := new(int32)
|
||||
if dv != nil {
|
||||
*i = dv.(int32)
|
||||
}
|
||||
*(fptr.(**int32)) = i
|
||||
}
|
||||
case reflect.Int64:
|
||||
i := new(int64)
|
||||
if dv != nil {
|
||||
*i = dv.(int64)
|
||||
}
|
||||
*(fptr.(**int64)) = i
|
||||
case reflect.String:
|
||||
s := new(string)
|
||||
if dv != nil {
|
||||
*s = dv.(string)
|
||||
}
|
||||
*(fptr.(**string)) = s
|
||||
case reflect.Uint8:
|
||||
// exceptional case: []byte
|
||||
var b []byte
|
||||
if dv != nil {
|
||||
db := dv.([]byte)
|
||||
b = make([]byte, len(db))
|
||||
copy(b, db)
|
||||
} else {
|
||||
b = []byte{}
|
||||
}
|
||||
*(fptr.(*[]byte)) = b
|
||||
case reflect.Uint32:
|
||||
u := new(uint32)
|
||||
if dv != nil {
|
||||
*u = dv.(uint32)
|
||||
}
|
||||
*(fptr.(**uint32)) = u
|
||||
case reflect.Uint64:
|
||||
u := new(uint64)
|
||||
if dv != nil {
|
||||
*u = dv.(uint64)
|
||||
}
|
||||
*(fptr.(**uint64)) = u
|
||||
default:
|
||||
log.Printf("proto: can't set default for field %v (sf.kind=%v)", f, sf.kind)
|
||||
}
|
||||
}
|
||||
|
||||
for _, ni := range dm.nested {
|
||||
f := v.Field(ni)
|
||||
// f is *T or []*T or map[T]*T
|
||||
switch f.Kind() {
|
||||
case reflect.Ptr:
|
||||
if f.IsNil() {
|
||||
continue
|
||||
}
|
||||
setDefaults(f, recur, zeros)
|
||||
|
||||
case reflect.Slice:
|
||||
for i := 0; i < f.Len(); i++ {
|
||||
e := f.Index(i)
|
||||
if e.IsNil() {
|
||||
continue
|
||||
}
|
||||
setDefaults(e, recur, zeros)
|
||||
}
|
||||
|
||||
case reflect.Map:
|
||||
for _, k := range f.MapKeys() {
|
||||
e := f.MapIndex(k)
|
||||
if e.IsNil() {
|
||||
continue
|
||||
}
|
||||
setDefaults(e, recur, zeros)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
// defaults maps a protocol buffer struct type to a slice of the fields,
|
||||
// with its scalar fields set to their proto-declared non-zero default values.
|
||||
defaultMu sync.RWMutex
|
||||
defaults = make(map[reflect.Type]defaultMessage)
|
||||
|
||||
int32PtrType = reflect.TypeOf((*int32)(nil))
|
||||
)
|
||||
|
||||
// defaultMessage represents information about the default values of a message.
|
||||
type defaultMessage struct {
|
||||
scalars []scalarField
|
||||
nested []int // struct field index of nested messages
|
||||
}
|
||||
|
||||
type scalarField struct {
|
||||
index int // struct field index
|
||||
kind reflect.Kind // element type (the T in *T or []T)
|
||||
value interface{} // the proto-declared default value, or nil
|
||||
}
|
||||
|
||||
// t is a struct type.
|
||||
func buildDefaultMessage(t reflect.Type) (dm defaultMessage) {
|
||||
sprop := GetProperties(t)
|
||||
for _, prop := range sprop.Prop {
|
||||
fi, ok := sprop.decoderTags.get(prop.Tag)
|
||||
if !ok {
|
||||
// XXX_unrecognized
|
||||
continue
|
||||
}
|
||||
ft := t.Field(fi).Type
|
||||
|
||||
sf, nested, err := fieldDefault(ft, prop)
|
||||
switch {
|
||||
case err != nil:
|
||||
log.Print(err)
|
||||
case nested:
|
||||
dm.nested = append(dm.nested, fi)
|
||||
case sf != nil:
|
||||
sf.index = fi
|
||||
dm.scalars = append(dm.scalars, *sf)
|
||||
}
|
||||
}
|
||||
|
||||
return dm
|
||||
}
|
||||
|
||||
// fieldDefault returns the scalarField for field type ft.
|
||||
// sf will be nil if the field can not have a default.
|
||||
// nestedMessage will be true if this is a nested message.
|
||||
// Note that sf.index is not set on return.
|
||||
func fieldDefault(ft reflect.Type, prop *Properties) (sf *scalarField, nestedMessage bool, err error) {
|
||||
var canHaveDefault bool
|
||||
switch ft.Kind() {
|
||||
case reflect.Ptr:
|
||||
if ft.Elem().Kind() == reflect.Struct {
|
||||
nestedMessage = true
|
||||
} else {
|
||||
canHaveDefault = true // proto2 scalar field
|
||||
}
|
||||
|
||||
case reflect.Slice:
|
||||
switch ft.Elem().Kind() {
|
||||
case reflect.Ptr:
|
||||
nestedMessage = true // repeated message
|
||||
case reflect.Uint8:
|
||||
canHaveDefault = true // bytes field
|
||||
}
|
||||
|
||||
case reflect.Map:
|
||||
if ft.Elem().Kind() == reflect.Ptr {
|
||||
nestedMessage = true // map with message values
|
||||
}
|
||||
}
|
||||
|
||||
if !canHaveDefault {
|
||||
if nestedMessage {
|
||||
return nil, true, nil
|
||||
}
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
// We now know that ft is a pointer or slice.
|
||||
sf = &scalarField{kind: ft.Elem().Kind()}
|
||||
|
||||
// scalar fields without defaults
|
||||
if !prop.HasDefault {
|
||||
return sf, false, nil
|
||||
}
|
||||
|
||||
// a scalar field: either *T or []byte
|
||||
switch ft.Elem().Kind() {
|
||||
case reflect.Bool:
|
||||
x, err := strconv.ParseBool(prop.Default)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("proto: bad default bool %q: %v", prop.Default, err)
|
||||
}
|
||||
sf.value = x
|
||||
case reflect.Float32:
|
||||
x, err := strconv.ParseFloat(prop.Default, 32)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("proto: bad default float32 %q: %v", prop.Default, err)
|
||||
}
|
||||
sf.value = float32(x)
|
||||
case reflect.Float64:
|
||||
x, err := strconv.ParseFloat(prop.Default, 64)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("proto: bad default float64 %q: %v", prop.Default, err)
|
||||
}
|
||||
sf.value = x
|
||||
case reflect.Int32:
|
||||
x, err := strconv.ParseInt(prop.Default, 10, 32)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("proto: bad default int32 %q: %v", prop.Default, err)
|
||||
}
|
||||
sf.value = int32(x)
|
||||
case reflect.Int64:
|
||||
x, err := strconv.ParseInt(prop.Default, 10, 64)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("proto: bad default int64 %q: %v", prop.Default, err)
|
||||
}
|
||||
sf.value = x
|
||||
case reflect.String:
|
||||
sf.value = prop.Default
|
||||
case reflect.Uint8:
|
||||
// []byte (not *uint8)
|
||||
sf.value = []byte(prop.Default)
|
||||
case reflect.Uint32:
|
||||
x, err := strconv.ParseUint(prop.Default, 10, 32)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("proto: bad default uint32 %q: %v", prop.Default, err)
|
||||
}
|
||||
sf.value = uint32(x)
|
||||
case reflect.Uint64:
|
||||
x, err := strconv.ParseUint(prop.Default, 10, 64)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("proto: bad default uint64 %q: %v", prop.Default, err)
|
||||
}
|
||||
sf.value = x
|
||||
default:
|
||||
return nil, false, fmt.Errorf("proto: unhandled def kind %v", ft.Elem().Kind())
|
||||
}
|
||||
|
||||
return sf, false, nil
|
||||
}
|
||||
|
||||
// Map fields may have key types of non-float scalars, strings and enums.
|
||||
// The easiest way to sort them in some deterministic order is to use fmt.
|
||||
// If this turns out to be inefficient we can always consider other options,
|
||||
// such as doing a Schwartzian transform.
|
||||
|
||||
func mapKeys(vs []reflect.Value) sort.Interface {
|
||||
s := mapKeySorter{
|
||||
vs: vs,
|
||||
// default Less function: textual comparison
|
||||
less: func(a, b reflect.Value) bool {
|
||||
return fmt.Sprint(a.Interface()) < fmt.Sprint(b.Interface())
|
||||
},
|
||||
}
|
||||
|
||||
// Type specialization per https://developers.google.com/protocol-buffers/docs/proto#maps;
|
||||
// numeric keys are sorted numerically.
|
||||
if len(vs) == 0 {
|
||||
return s
|
||||
}
|
||||
switch vs[0].Kind() {
|
||||
case reflect.Int32, reflect.Int64:
|
||||
s.less = func(a, b reflect.Value) bool { return a.Int() < b.Int() }
|
||||
case reflect.Uint32, reflect.Uint64:
|
||||
s.less = func(a, b reflect.Value) bool { return a.Uint() < b.Uint() }
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
type mapKeySorter struct {
|
||||
vs []reflect.Value
|
||||
less func(a, b reflect.Value) bool
|
||||
}
|
||||
|
||||
func (s mapKeySorter) Len() int { return len(s.vs) }
|
||||
func (s mapKeySorter) Swap(i, j int) { s.vs[i], s.vs[j] = s.vs[j], s.vs[i] }
|
||||
func (s mapKeySorter) Less(i, j int) bool {
|
||||
return s.less(s.vs[i], s.vs[j])
|
||||
}
|
||||
|
||||
// isProto3Zero reports whether v is a zero proto3 value.
|
||||
func isProto3Zero(v reflect.Value) bool {
|
||||
switch v.Kind() {
|
||||
case reflect.Bool:
|
||||
return !v.Bool()
|
||||
case reflect.Int32, reflect.Int64:
|
||||
return v.Int() == 0
|
||||
case reflect.Uint32, reflect.Uint64:
|
||||
return v.Uint() == 0
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return v.Float() == 0
|
||||
case reflect.String:
|
||||
return v.String() == ""
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ProtoPackageIsVersion2 is referenced from generated protocol buffer files
|
||||
// to assert that that code is compatible with this version of the proto package.
|
||||
const ProtoPackageIsVersion2 = true
|
||||
|
||||
// ProtoPackageIsVersion1 is referenced from generated protocol buffer files
|
||||
// to assert that that code is compatible with this version of the proto package.
|
||||
const ProtoPackageIsVersion1 = true
|
||||
+311
@@ -0,0 +1,311 @@
|
||||
// Go support for Protocol Buffers - Google's data interchange format
|
||||
//
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// https://github.com/golang/protobuf
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package proto
|
||||
|
||||
/*
|
||||
* Support for message sets.
|
||||
*/
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// errNoMessageTypeID occurs when a protocol buffer does not have a message type ID.
|
||||
// A message type ID is required for storing a protocol buffer in a message set.
|
||||
var errNoMessageTypeID = errors.New("proto does not have a message type ID")
|
||||
|
||||
// The first two types (_MessageSet_Item and messageSet)
|
||||
// model what the protocol compiler produces for the following protocol message:
|
||||
// message MessageSet {
|
||||
// repeated group Item = 1 {
|
||||
// required int32 type_id = 2;
|
||||
// required string message = 3;
|
||||
// };
|
||||
// }
|
||||
// That is the MessageSet wire format. We can't use a proto to generate these
|
||||
// because that would introduce a circular dependency between it and this package.
|
||||
|
||||
type _MessageSet_Item struct {
|
||||
TypeId *int32 `protobuf:"varint,2,req,name=type_id"`
|
||||
Message []byte `protobuf:"bytes,3,req,name=message"`
|
||||
}
|
||||
|
||||
type messageSet struct {
|
||||
Item []*_MessageSet_Item `protobuf:"group,1,rep"`
|
||||
XXX_unrecognized []byte
|
||||
// TODO: caching?
|
||||
}
|
||||
|
||||
// Make sure messageSet is a Message.
|
||||
var _ Message = (*messageSet)(nil)
|
||||
|
||||
// messageTypeIder is an interface satisfied by a protocol buffer type
|
||||
// that may be stored in a MessageSet.
|
||||
type messageTypeIder interface {
|
||||
MessageTypeId() int32
|
||||
}
|
||||
|
||||
func (ms *messageSet) find(pb Message) *_MessageSet_Item {
|
||||
mti, ok := pb.(messageTypeIder)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
id := mti.MessageTypeId()
|
||||
for _, item := range ms.Item {
|
||||
if *item.TypeId == id {
|
||||
return item
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ms *messageSet) Has(pb Message) bool {
|
||||
if ms.find(pb) != nil {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (ms *messageSet) Unmarshal(pb Message) error {
|
||||
if item := ms.find(pb); item != nil {
|
||||
return Unmarshal(item.Message, pb)
|
||||
}
|
||||
if _, ok := pb.(messageTypeIder); !ok {
|
||||
return errNoMessageTypeID
|
||||
}
|
||||
return nil // TODO: return error instead?
|
||||
}
|
||||
|
||||
func (ms *messageSet) Marshal(pb Message) error {
|
||||
msg, err := Marshal(pb)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if item := ms.find(pb); item != nil {
|
||||
// reuse existing item
|
||||
item.Message = msg
|
||||
return nil
|
||||
}
|
||||
|
||||
mti, ok := pb.(messageTypeIder)
|
||||
if !ok {
|
||||
return errNoMessageTypeID
|
||||
}
|
||||
|
||||
mtid := mti.MessageTypeId()
|
||||
ms.Item = append(ms.Item, &_MessageSet_Item{
|
||||
TypeId: &mtid,
|
||||
Message: msg,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ms *messageSet) Reset() { *ms = messageSet{} }
|
||||
func (ms *messageSet) String() string { return CompactTextString(ms) }
|
||||
func (*messageSet) ProtoMessage() {}
|
||||
|
||||
// Support for the message_set_wire_format message option.
|
||||
|
||||
func skipVarint(buf []byte) []byte {
|
||||
i := 0
|
||||
for ; buf[i]&0x80 != 0; i++ {
|
||||
}
|
||||
return buf[i+1:]
|
||||
}
|
||||
|
||||
// MarshalMessageSet encodes the extension map represented by m in the message set wire format.
|
||||
// It is called by generated Marshal methods on protocol buffer messages with the message_set_wire_format option.
|
||||
func MarshalMessageSet(exts interface{}) ([]byte, error) {
|
||||
var m map[int32]Extension
|
||||
switch exts := exts.(type) {
|
||||
case *XXX_InternalExtensions:
|
||||
if err := encodeExtensions(exts); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m, _ = exts.extensionsRead()
|
||||
case map[int32]Extension:
|
||||
if err := encodeExtensionsMap(exts); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m = exts
|
||||
default:
|
||||
return nil, errors.New("proto: not an extension map")
|
||||
}
|
||||
|
||||
// Sort extension IDs to provide a deterministic encoding.
|
||||
// See also enc_map in encode.go.
|
||||
ids := make([]int, 0, len(m))
|
||||
for id := range m {
|
||||
ids = append(ids, int(id))
|
||||
}
|
||||
sort.Ints(ids)
|
||||
|
||||
ms := &messageSet{Item: make([]*_MessageSet_Item, 0, len(m))}
|
||||
for _, id := range ids {
|
||||
e := m[int32(id)]
|
||||
// Remove the wire type and field number varint, as well as the length varint.
|
||||
msg := skipVarint(skipVarint(e.enc))
|
||||
|
||||
ms.Item = append(ms.Item, &_MessageSet_Item{
|
||||
TypeId: Int32(int32(id)),
|
||||
Message: msg,
|
||||
})
|
||||
}
|
||||
return Marshal(ms)
|
||||
}
|
||||
|
||||
// UnmarshalMessageSet decodes the extension map encoded in buf in the message set wire format.
|
||||
// It is called by generated Unmarshal methods on protocol buffer messages with the message_set_wire_format option.
|
||||
func UnmarshalMessageSet(buf []byte, exts interface{}) error {
|
||||
var m map[int32]Extension
|
||||
switch exts := exts.(type) {
|
||||
case *XXX_InternalExtensions:
|
||||
m = exts.extensionsWrite()
|
||||
case map[int32]Extension:
|
||||
m = exts
|
||||
default:
|
||||
return errors.New("proto: not an extension map")
|
||||
}
|
||||
|
||||
ms := new(messageSet)
|
||||
if err := Unmarshal(buf, ms); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, item := range ms.Item {
|
||||
id := *item.TypeId
|
||||
msg := item.Message
|
||||
|
||||
// Restore wire type and field number varint, plus length varint.
|
||||
// Be careful to preserve duplicate items.
|
||||
b := EncodeVarint(uint64(id)<<3 | WireBytes)
|
||||
if ext, ok := m[id]; ok {
|
||||
// Existing data; rip off the tag and length varint
|
||||
// so we join the new data correctly.
|
||||
// We can assume that ext.enc is set because we are unmarshaling.
|
||||
o := ext.enc[len(b):] // skip wire type and field number
|
||||
_, n := DecodeVarint(o) // calculate length of length varint
|
||||
o = o[n:] // skip length varint
|
||||
msg = append(o, msg...) // join old data and new data
|
||||
}
|
||||
b = append(b, EncodeVarint(uint64(len(msg)))...)
|
||||
b = append(b, msg...)
|
||||
|
||||
m[id] = Extension{enc: b}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalMessageSetJSON encodes the extension map represented by m in JSON format.
|
||||
// It is called by generated MarshalJSON methods on protocol buffer messages with the message_set_wire_format option.
|
||||
func MarshalMessageSetJSON(exts interface{}) ([]byte, error) {
|
||||
var m map[int32]Extension
|
||||
switch exts := exts.(type) {
|
||||
case *XXX_InternalExtensions:
|
||||
m, _ = exts.extensionsRead()
|
||||
case map[int32]Extension:
|
||||
m = exts
|
||||
default:
|
||||
return nil, errors.New("proto: not an extension map")
|
||||
}
|
||||
var b bytes.Buffer
|
||||
b.WriteByte('{')
|
||||
|
||||
// Process the map in key order for deterministic output.
|
||||
ids := make([]int32, 0, len(m))
|
||||
for id := range m {
|
||||
ids = append(ids, id)
|
||||
}
|
||||
sort.Sort(int32Slice(ids)) // int32Slice defined in text.go
|
||||
|
||||
for i, id := range ids {
|
||||
ext := m[id]
|
||||
if i > 0 {
|
||||
b.WriteByte(',')
|
||||
}
|
||||
|
||||
msd, ok := messageSetMap[id]
|
||||
if !ok {
|
||||
// Unknown type; we can't render it, so skip it.
|
||||
continue
|
||||
}
|
||||
fmt.Fprintf(&b, `"[%s]":`, msd.name)
|
||||
|
||||
x := ext.value
|
||||
if x == nil {
|
||||
x = reflect.New(msd.t.Elem()).Interface()
|
||||
if err := Unmarshal(ext.enc, x.(Message)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
d, err := json.Marshal(x)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b.Write(d)
|
||||
}
|
||||
b.WriteByte('}')
|
||||
return b.Bytes(), nil
|
||||
}
|
||||
|
||||
// UnmarshalMessageSetJSON decodes the extension map encoded in buf in JSON format.
|
||||
// It is called by generated UnmarshalJSON methods on protocol buffer messages with the message_set_wire_format option.
|
||||
func UnmarshalMessageSetJSON(buf []byte, exts interface{}) error {
|
||||
// Common-case fast path.
|
||||
if len(buf) == 0 || bytes.Equal(buf, []byte("{}")) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// This is fairly tricky, and it's not clear that it is needed.
|
||||
return errors.New("TODO: UnmarshalMessageSetJSON not yet implemented")
|
||||
}
|
||||
|
||||
// A global registry of types that can be used in a MessageSet.
|
||||
|
||||
var messageSetMap = make(map[int32]messageSetDesc)
|
||||
|
||||
type messageSetDesc struct {
|
||||
t reflect.Type // pointer to struct
|
||||
name string
|
||||
}
|
||||
|
||||
// RegisterMessageSetType is called from the generated code.
|
||||
func RegisterMessageSetType(m Message, fieldNum int32, name string) {
|
||||
messageSetMap[fieldNum] = messageSetDesc{
|
||||
t: reflect.TypeOf(m),
|
||||
name: name,
|
||||
}
|
||||
}
|
||||
+484
@@ -0,0 +1,484 @@
|
||||
// Go support for Protocol Buffers - Google's data interchange format
|
||||
//
|
||||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// https://github.com/golang/protobuf
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// +build appengine js
|
||||
|
||||
// This file contains an implementation of proto field accesses using package reflect.
|
||||
// It is slower than the code in pointer_unsafe.go but it avoids package unsafe and can
|
||||
// be used on App Engine.
|
||||
|
||||
package proto
|
||||
|
||||
import (
|
||||
"math"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// A structPointer is a pointer to a struct.
|
||||
type structPointer struct {
|
||||
v reflect.Value
|
||||
}
|
||||
|
||||
// toStructPointer returns a structPointer equivalent to the given reflect value.
|
||||
// The reflect value must itself be a pointer to a struct.
|
||||
func toStructPointer(v reflect.Value) structPointer {
|
||||
return structPointer{v}
|
||||
}
|
||||
|
||||
// IsNil reports whether p is nil.
|
||||
func structPointer_IsNil(p structPointer) bool {
|
||||
return p.v.IsNil()
|
||||
}
|
||||
|
||||
// Interface returns the struct pointer as an interface value.
|
||||
func structPointer_Interface(p structPointer, _ reflect.Type) interface{} {
|
||||
return p.v.Interface()
|
||||
}
|
||||
|
||||
// A field identifies a field in a struct, accessible from a structPointer.
|
||||
// In this implementation, a field is identified by the sequence of field indices
|
||||
// passed to reflect's FieldByIndex.
|
||||
type field []int
|
||||
|
||||
// toField returns a field equivalent to the given reflect field.
|
||||
func toField(f *reflect.StructField) field {
|
||||
return f.Index
|
||||
}
|
||||
|
||||
// invalidField is an invalid field identifier.
|
||||
var invalidField = field(nil)
|
||||
|
||||
// IsValid reports whether the field identifier is valid.
|
||||
func (f field) IsValid() bool { return f != nil }
|
||||
|
||||
// field returns the given field in the struct as a reflect value.
|
||||
func structPointer_field(p structPointer, f field) reflect.Value {
|
||||
// Special case: an extension map entry with a value of type T
|
||||
// passes a *T to the struct-handling code with a zero field,
|
||||
// expecting that it will be treated as equivalent to *struct{ X T },
|
||||
// which has the same memory layout. We have to handle that case
|
||||
// specially, because reflect will panic if we call FieldByIndex on a
|
||||
// non-struct.
|
||||
if f == nil {
|
||||
return p.v.Elem()
|
||||
}
|
||||
|
||||
return p.v.Elem().FieldByIndex(f)
|
||||
}
|
||||
|
||||
// ifield returns the given field in the struct as an interface value.
|
||||
func structPointer_ifield(p structPointer, f field) interface{} {
|
||||
return structPointer_field(p, f).Addr().Interface()
|
||||
}
|
||||
|
||||
// Bytes returns the address of a []byte field in the struct.
|
||||
func structPointer_Bytes(p structPointer, f field) *[]byte {
|
||||
return structPointer_ifield(p, f).(*[]byte)
|
||||
}
|
||||
|
||||
// BytesSlice returns the address of a [][]byte field in the struct.
|
||||
func structPointer_BytesSlice(p structPointer, f field) *[][]byte {
|
||||
return structPointer_ifield(p, f).(*[][]byte)
|
||||
}
|
||||
|
||||
// Bool returns the address of a *bool field in the struct.
|
||||
func structPointer_Bool(p structPointer, f field) **bool {
|
||||
return structPointer_ifield(p, f).(**bool)
|
||||
}
|
||||
|
||||
// BoolVal returns the address of a bool field in the struct.
|
||||
func structPointer_BoolVal(p structPointer, f field) *bool {
|
||||
return structPointer_ifield(p, f).(*bool)
|
||||
}
|
||||
|
||||
// BoolSlice returns the address of a []bool field in the struct.
|
||||
func structPointer_BoolSlice(p structPointer, f field) *[]bool {
|
||||
return structPointer_ifield(p, f).(*[]bool)
|
||||
}
|
||||
|
||||
// String returns the address of a *string field in the struct.
|
||||
func structPointer_String(p structPointer, f field) **string {
|
||||
return structPointer_ifield(p, f).(**string)
|
||||
}
|
||||
|
||||
// StringVal returns the address of a string field in the struct.
|
||||
func structPointer_StringVal(p structPointer, f field) *string {
|
||||
return structPointer_ifield(p, f).(*string)
|
||||
}
|
||||
|
||||
// StringSlice returns the address of a []string field in the struct.
|
||||
func structPointer_StringSlice(p structPointer, f field) *[]string {
|
||||
return structPointer_ifield(p, f).(*[]string)
|
||||
}
|
||||
|
||||
// Extensions returns the address of an extension map field in the struct.
|
||||
func structPointer_Extensions(p structPointer, f field) *XXX_InternalExtensions {
|
||||
return structPointer_ifield(p, f).(*XXX_InternalExtensions)
|
||||
}
|
||||
|
||||
// ExtMap returns the address of an extension map field in the struct.
|
||||
func structPointer_ExtMap(p structPointer, f field) *map[int32]Extension {
|
||||
return structPointer_ifield(p, f).(*map[int32]Extension)
|
||||
}
|
||||
|
||||
// NewAt returns the reflect.Value for a pointer to a field in the struct.
|
||||
func structPointer_NewAt(p structPointer, f field, typ reflect.Type) reflect.Value {
|
||||
return structPointer_field(p, f).Addr()
|
||||
}
|
||||
|
||||
// SetStructPointer writes a *struct field in the struct.
|
||||
func structPointer_SetStructPointer(p structPointer, f field, q structPointer) {
|
||||
structPointer_field(p, f).Set(q.v)
|
||||
}
|
||||
|
||||
// GetStructPointer reads a *struct field in the struct.
|
||||
func structPointer_GetStructPointer(p structPointer, f field) structPointer {
|
||||
return structPointer{structPointer_field(p, f)}
|
||||
}
|
||||
|
||||
// StructPointerSlice the address of a []*struct field in the struct.
|
||||
func structPointer_StructPointerSlice(p structPointer, f field) structPointerSlice {
|
||||
return structPointerSlice{structPointer_field(p, f)}
|
||||
}
|
||||
|
||||
// A structPointerSlice represents the address of a slice of pointers to structs
|
||||
// (themselves messages or groups). That is, v.Type() is *[]*struct{...}.
|
||||
type structPointerSlice struct {
|
||||
v reflect.Value
|
||||
}
|
||||
|
||||
func (p structPointerSlice) Len() int { return p.v.Len() }
|
||||
func (p structPointerSlice) Index(i int) structPointer { return structPointer{p.v.Index(i)} }
|
||||
func (p structPointerSlice) Append(q structPointer) {
|
||||
p.v.Set(reflect.Append(p.v, q.v))
|
||||
}
|
||||
|
||||
var (
|
||||
int32Type = reflect.TypeOf(int32(0))
|
||||
uint32Type = reflect.TypeOf(uint32(0))
|
||||
float32Type = reflect.TypeOf(float32(0))
|
||||
int64Type = reflect.TypeOf(int64(0))
|
||||
uint64Type = reflect.TypeOf(uint64(0))
|
||||
float64Type = reflect.TypeOf(float64(0))
|
||||
)
|
||||
|
||||
// A word32 represents a field of type *int32, *uint32, *float32, or *enum.
|
||||
// That is, v.Type() is *int32, *uint32, *float32, or *enum and v is assignable.
|
||||
type word32 struct {
|
||||
v reflect.Value
|
||||
}
|
||||
|
||||
// IsNil reports whether p is nil.
|
||||
func word32_IsNil(p word32) bool {
|
||||
return p.v.IsNil()
|
||||
}
|
||||
|
||||
// Set sets p to point at a newly allocated word with bits set to x.
|
||||
func word32_Set(p word32, o *Buffer, x uint32) {
|
||||
t := p.v.Type().Elem()
|
||||
switch t {
|
||||
case int32Type:
|
||||
if len(o.int32s) == 0 {
|
||||
o.int32s = make([]int32, uint32PoolSize)
|
||||
}
|
||||
o.int32s[0] = int32(x)
|
||||
p.v.Set(reflect.ValueOf(&o.int32s[0]))
|
||||
o.int32s = o.int32s[1:]
|
||||
return
|
||||
case uint32Type:
|
||||
if len(o.uint32s) == 0 {
|
||||
o.uint32s = make([]uint32, uint32PoolSize)
|
||||
}
|
||||
o.uint32s[0] = x
|
||||
p.v.Set(reflect.ValueOf(&o.uint32s[0]))
|
||||
o.uint32s = o.uint32s[1:]
|
||||
return
|
||||
case float32Type:
|
||||
if len(o.float32s) == 0 {
|
||||
o.float32s = make([]float32, uint32PoolSize)
|
||||
}
|
||||
o.float32s[0] = math.Float32frombits(x)
|
||||
p.v.Set(reflect.ValueOf(&o.float32s[0]))
|
||||
o.float32s = o.float32s[1:]
|
||||
return
|
||||
}
|
||||
|
||||
// must be enum
|
||||
p.v.Set(reflect.New(t))
|
||||
p.v.Elem().SetInt(int64(int32(x)))
|
||||
}
|
||||
|
||||
// Get gets the bits pointed at by p, as a uint32.
|
||||
func word32_Get(p word32) uint32 {
|
||||
elem := p.v.Elem()
|
||||
switch elem.Kind() {
|
||||
case reflect.Int32:
|
||||
return uint32(elem.Int())
|
||||
case reflect.Uint32:
|
||||
return uint32(elem.Uint())
|
||||
case reflect.Float32:
|
||||
return math.Float32bits(float32(elem.Float()))
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
// Word32 returns a reference to a *int32, *uint32, *float32, or *enum field in the struct.
|
||||
func structPointer_Word32(p structPointer, f field) word32 {
|
||||
return word32{structPointer_field(p, f)}
|
||||
}
|
||||
|
||||
// A word32Val represents a field of type int32, uint32, float32, or enum.
|
||||
// That is, v.Type() is int32, uint32, float32, or enum and v is assignable.
|
||||
type word32Val struct {
|
||||
v reflect.Value
|
||||
}
|
||||
|
||||
// Set sets *p to x.
|
||||
func word32Val_Set(p word32Val, x uint32) {
|
||||
switch p.v.Type() {
|
||||
case int32Type:
|
||||
p.v.SetInt(int64(x))
|
||||
return
|
||||
case uint32Type:
|
||||
p.v.SetUint(uint64(x))
|
||||
return
|
||||
case float32Type:
|
||||
p.v.SetFloat(float64(math.Float32frombits(x)))
|
||||
return
|
||||
}
|
||||
|
||||
// must be enum
|
||||
p.v.SetInt(int64(int32(x)))
|
||||
}
|
||||
|
||||
// Get gets the bits pointed at by p, as a uint32.
|
||||
func word32Val_Get(p word32Val) uint32 {
|
||||
elem := p.v
|
||||
switch elem.Kind() {
|
||||
case reflect.Int32:
|
||||
return uint32(elem.Int())
|
||||
case reflect.Uint32:
|
||||
return uint32(elem.Uint())
|
||||
case reflect.Float32:
|
||||
return math.Float32bits(float32(elem.Float()))
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
// Word32Val returns a reference to a int32, uint32, float32, or enum field in the struct.
|
||||
func structPointer_Word32Val(p structPointer, f field) word32Val {
|
||||
return word32Val{structPointer_field(p, f)}
|
||||
}
|
||||
|
||||
// A word32Slice is a slice of 32-bit values.
|
||||
// That is, v.Type() is []int32, []uint32, []float32, or []enum.
|
||||
type word32Slice struct {
|
||||
v reflect.Value
|
||||
}
|
||||
|
||||
func (p word32Slice) Append(x uint32) {
|
||||
n, m := p.v.Len(), p.v.Cap()
|
||||
if n < m {
|
||||
p.v.SetLen(n + 1)
|
||||
} else {
|
||||
t := p.v.Type().Elem()
|
||||
p.v.Set(reflect.Append(p.v, reflect.Zero(t)))
|
||||
}
|
||||
elem := p.v.Index(n)
|
||||
switch elem.Kind() {
|
||||
case reflect.Int32:
|
||||
elem.SetInt(int64(int32(x)))
|
||||
case reflect.Uint32:
|
||||
elem.SetUint(uint64(x))
|
||||
case reflect.Float32:
|
||||
elem.SetFloat(float64(math.Float32frombits(x)))
|
||||
}
|
||||
}
|
||||
|
||||
func (p word32Slice) Len() int {
|
||||
return p.v.Len()
|
||||
}
|
||||
|
||||
func (p word32Slice) Index(i int) uint32 {
|
||||
elem := p.v.Index(i)
|
||||
switch elem.Kind() {
|
||||
case reflect.Int32:
|
||||
return uint32(elem.Int())
|
||||
case reflect.Uint32:
|
||||
return uint32(elem.Uint())
|
||||
case reflect.Float32:
|
||||
return math.Float32bits(float32(elem.Float()))
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
// Word32Slice returns a reference to a []int32, []uint32, []float32, or []enum field in the struct.
|
||||
func structPointer_Word32Slice(p structPointer, f field) word32Slice {
|
||||
return word32Slice{structPointer_field(p, f)}
|
||||
}
|
||||
|
||||
// word64 is like word32 but for 64-bit values.
|
||||
type word64 struct {
|
||||
v reflect.Value
|
||||
}
|
||||
|
||||
func word64_Set(p word64, o *Buffer, x uint64) {
|
||||
t := p.v.Type().Elem()
|
||||
switch t {
|
||||
case int64Type:
|
||||
if len(o.int64s) == 0 {
|
||||
o.int64s = make([]int64, uint64PoolSize)
|
||||
}
|
||||
o.int64s[0] = int64(x)
|
||||
p.v.Set(reflect.ValueOf(&o.int64s[0]))
|
||||
o.int64s = o.int64s[1:]
|
||||
return
|
||||
case uint64Type:
|
||||
if len(o.uint64s) == 0 {
|
||||
o.uint64s = make([]uint64, uint64PoolSize)
|
||||
}
|
||||
o.uint64s[0] = x
|
||||
p.v.Set(reflect.ValueOf(&o.uint64s[0]))
|
||||
o.uint64s = o.uint64s[1:]
|
||||
return
|
||||
case float64Type:
|
||||
if len(o.float64s) == 0 {
|
||||
o.float64s = make([]float64, uint64PoolSize)
|
||||
}
|
||||
o.float64s[0] = math.Float64frombits(x)
|
||||
p.v.Set(reflect.ValueOf(&o.float64s[0]))
|
||||
o.float64s = o.float64s[1:]
|
||||
return
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
func word64_IsNil(p word64) bool {
|
||||
return p.v.IsNil()
|
||||
}
|
||||
|
||||
func word64_Get(p word64) uint64 {
|
||||
elem := p.v.Elem()
|
||||
switch elem.Kind() {
|
||||
case reflect.Int64:
|
||||
return uint64(elem.Int())
|
||||
case reflect.Uint64:
|
||||
return elem.Uint()
|
||||
case reflect.Float64:
|
||||
return math.Float64bits(elem.Float())
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
func structPointer_Word64(p structPointer, f field) word64 {
|
||||
return word64{structPointer_field(p, f)}
|
||||
}
|
||||
|
||||
// word64Val is like word32Val but for 64-bit values.
|
||||
type word64Val struct {
|
||||
v reflect.Value
|
||||
}
|
||||
|
||||
func word64Val_Set(p word64Val, o *Buffer, x uint64) {
|
||||
switch p.v.Type() {
|
||||
case int64Type:
|
||||
p.v.SetInt(int64(x))
|
||||
return
|
||||
case uint64Type:
|
||||
p.v.SetUint(x)
|
||||
return
|
||||
case float64Type:
|
||||
p.v.SetFloat(math.Float64frombits(x))
|
||||
return
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
func word64Val_Get(p word64Val) uint64 {
|
||||
elem := p.v
|
||||
switch elem.Kind() {
|
||||
case reflect.Int64:
|
||||
return uint64(elem.Int())
|
||||
case reflect.Uint64:
|
||||
return elem.Uint()
|
||||
case reflect.Float64:
|
||||
return math.Float64bits(elem.Float())
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
func structPointer_Word64Val(p structPointer, f field) word64Val {
|
||||
return word64Val{structPointer_field(p, f)}
|
||||
}
|
||||
|
||||
type word64Slice struct {
|
||||
v reflect.Value
|
||||
}
|
||||
|
||||
func (p word64Slice) Append(x uint64) {
|
||||
n, m := p.v.Len(), p.v.Cap()
|
||||
if n < m {
|
||||
p.v.SetLen(n + 1)
|
||||
} else {
|
||||
t := p.v.Type().Elem()
|
||||
p.v.Set(reflect.Append(p.v, reflect.Zero(t)))
|
||||
}
|
||||
elem := p.v.Index(n)
|
||||
switch elem.Kind() {
|
||||
case reflect.Int64:
|
||||
elem.SetInt(int64(int64(x)))
|
||||
case reflect.Uint64:
|
||||
elem.SetUint(uint64(x))
|
||||
case reflect.Float64:
|
||||
elem.SetFloat(float64(math.Float64frombits(x)))
|
||||
}
|
||||
}
|
||||
|
||||
func (p word64Slice) Len() int {
|
||||
return p.v.Len()
|
||||
}
|
||||
|
||||
func (p word64Slice) Index(i int) uint64 {
|
||||
elem := p.v.Index(i)
|
||||
switch elem.Kind() {
|
||||
case reflect.Int64:
|
||||
return uint64(elem.Int())
|
||||
case reflect.Uint64:
|
||||
return uint64(elem.Uint())
|
||||
case reflect.Float64:
|
||||
return math.Float64bits(float64(elem.Float()))
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
func structPointer_Word64Slice(p structPointer, f field) word64Slice {
|
||||
return word64Slice{structPointer_field(p, f)}
|
||||
}
|
||||
+270
@@ -0,0 +1,270 @@
|
||||
// Go support for Protocol Buffers - Google's data interchange format
|
||||
//
|
||||
// Copyright 2012 The Go Authors. All rights reserved.
|
||||
// https://github.com/golang/protobuf
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// +build !appengine,!js
|
||||
|
||||
// This file contains the implementation of the proto field accesses using package unsafe.
|
||||
|
||||
package proto
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// NOTE: These type_Foo functions would more idiomatically be methods,
|
||||
// but Go does not allow methods on pointer types, and we must preserve
|
||||
// some pointer type for the garbage collector. We use these
|
||||
// funcs with clunky names as our poor approximation to methods.
|
||||
//
|
||||
// An alternative would be
|
||||
// type structPointer struct { p unsafe.Pointer }
|
||||
// but that does not registerize as well.
|
||||
|
||||
// A structPointer is a pointer to a struct.
|
||||
type structPointer unsafe.Pointer
|
||||
|
||||
// toStructPointer returns a structPointer equivalent to the given reflect value.
|
||||
func toStructPointer(v reflect.Value) structPointer {
|
||||
return structPointer(unsafe.Pointer(v.Pointer()))
|
||||
}
|
||||
|
||||
// IsNil reports whether p is nil.
|
||||
func structPointer_IsNil(p structPointer) bool {
|
||||
return p == nil
|
||||
}
|
||||
|
||||
// Interface returns the struct pointer, assumed to have element type t,
|
||||
// as an interface value.
|
||||
func structPointer_Interface(p structPointer, t reflect.Type) interface{} {
|
||||
return reflect.NewAt(t, unsafe.Pointer(p)).Interface()
|
||||
}
|
||||
|
||||
// A field identifies a field in a struct, accessible from a structPointer.
|
||||
// In this implementation, a field is identified by its byte offset from the start of the struct.
|
||||
type field uintptr
|
||||
|
||||
// toField returns a field equivalent to the given reflect field.
|
||||
func toField(f *reflect.StructField) field {
|
||||
return field(f.Offset)
|
||||
}
|
||||
|
||||
// invalidField is an invalid field identifier.
|
||||
const invalidField = ^field(0)
|
||||
|
||||
// IsValid reports whether the field identifier is valid.
|
||||
func (f field) IsValid() bool {
|
||||
return f != ^field(0)
|
||||
}
|
||||
|
||||
// Bytes returns the address of a []byte field in the struct.
|
||||
func structPointer_Bytes(p structPointer, f field) *[]byte {
|
||||
return (*[]byte)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||
}
|
||||
|
||||
// BytesSlice returns the address of a [][]byte field in the struct.
|
||||
func structPointer_BytesSlice(p structPointer, f field) *[][]byte {
|
||||
return (*[][]byte)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||
}
|
||||
|
||||
// Bool returns the address of a *bool field in the struct.
|
||||
func structPointer_Bool(p structPointer, f field) **bool {
|
||||
return (**bool)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||
}
|
||||
|
||||
// BoolVal returns the address of a bool field in the struct.
|
||||
func structPointer_BoolVal(p structPointer, f field) *bool {
|
||||
return (*bool)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||
}
|
||||
|
||||
// BoolSlice returns the address of a []bool field in the struct.
|
||||
func structPointer_BoolSlice(p structPointer, f field) *[]bool {
|
||||
return (*[]bool)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||
}
|
||||
|
||||
// String returns the address of a *string field in the struct.
|
||||
func structPointer_String(p structPointer, f field) **string {
|
||||
return (**string)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||
}
|
||||
|
||||
// StringVal returns the address of a string field in the struct.
|
||||
func structPointer_StringVal(p structPointer, f field) *string {
|
||||
return (*string)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||
}
|
||||
|
||||
// StringSlice returns the address of a []string field in the struct.
|
||||
func structPointer_StringSlice(p structPointer, f field) *[]string {
|
||||
return (*[]string)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||
}
|
||||
|
||||
// ExtMap returns the address of an extension map field in the struct.
|
||||
func structPointer_Extensions(p structPointer, f field) *XXX_InternalExtensions {
|
||||
return (*XXX_InternalExtensions)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||
}
|
||||
|
||||
func structPointer_ExtMap(p structPointer, f field) *map[int32]Extension {
|
||||
return (*map[int32]Extension)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||
}
|
||||
|
||||
// NewAt returns the reflect.Value for a pointer to a field in the struct.
|
||||
func structPointer_NewAt(p structPointer, f field, typ reflect.Type) reflect.Value {
|
||||
return reflect.NewAt(typ, unsafe.Pointer(uintptr(p)+uintptr(f)))
|
||||
}
|
||||
|
||||
// SetStructPointer writes a *struct field in the struct.
|
||||
func structPointer_SetStructPointer(p structPointer, f field, q structPointer) {
|
||||
*(*structPointer)(unsafe.Pointer(uintptr(p) + uintptr(f))) = q
|
||||
}
|
||||
|
||||
// GetStructPointer reads a *struct field in the struct.
|
||||
func structPointer_GetStructPointer(p structPointer, f field) structPointer {
|
||||
return *(*structPointer)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||
}
|
||||
|
||||
// StructPointerSlice the address of a []*struct field in the struct.
|
||||
func structPointer_StructPointerSlice(p structPointer, f field) *structPointerSlice {
|
||||
return (*structPointerSlice)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||
}
|
||||
|
||||
// A structPointerSlice represents a slice of pointers to structs (themselves submessages or groups).
|
||||
type structPointerSlice []structPointer
|
||||
|
||||
func (v *structPointerSlice) Len() int { return len(*v) }
|
||||
func (v *structPointerSlice) Index(i int) structPointer { return (*v)[i] }
|
||||
func (v *structPointerSlice) Append(p structPointer) { *v = append(*v, p) }
|
||||
|
||||
// A word32 is the address of a "pointer to 32-bit value" field.
|
||||
type word32 **uint32
|
||||
|
||||
// IsNil reports whether *v is nil.
|
||||
func word32_IsNil(p word32) bool {
|
||||
return *p == nil
|
||||
}
|
||||
|
||||
// Set sets *v to point at a newly allocated word set to x.
|
||||
func word32_Set(p word32, o *Buffer, x uint32) {
|
||||
if len(o.uint32s) == 0 {
|
||||
o.uint32s = make([]uint32, uint32PoolSize)
|
||||
}
|
||||
o.uint32s[0] = x
|
||||
*p = &o.uint32s[0]
|
||||
o.uint32s = o.uint32s[1:]
|
||||
}
|
||||
|
||||
// Get gets the value pointed at by *v.
|
||||
func word32_Get(p word32) uint32 {
|
||||
return **p
|
||||
}
|
||||
|
||||
// Word32 returns the address of a *int32, *uint32, *float32, or *enum field in the struct.
|
||||
func structPointer_Word32(p structPointer, f field) word32 {
|
||||
return word32((**uint32)(unsafe.Pointer(uintptr(p) + uintptr(f))))
|
||||
}
|
||||
|
||||
// A word32Val is the address of a 32-bit value field.
|
||||
type word32Val *uint32
|
||||
|
||||
// Set sets *p to x.
|
||||
func word32Val_Set(p word32Val, x uint32) {
|
||||
*p = x
|
||||
}
|
||||
|
||||
// Get gets the value pointed at by p.
|
||||
func word32Val_Get(p word32Val) uint32 {
|
||||
return *p
|
||||
}
|
||||
|
||||
// Word32Val returns the address of a *int32, *uint32, *float32, or *enum field in the struct.
|
||||
func structPointer_Word32Val(p structPointer, f field) word32Val {
|
||||
return word32Val((*uint32)(unsafe.Pointer(uintptr(p) + uintptr(f))))
|
||||
}
|
||||
|
||||
// A word32Slice is a slice of 32-bit values.
|
||||
type word32Slice []uint32
|
||||
|
||||
func (v *word32Slice) Append(x uint32) { *v = append(*v, x) }
|
||||
func (v *word32Slice) Len() int { return len(*v) }
|
||||
func (v *word32Slice) Index(i int) uint32 { return (*v)[i] }
|
||||
|
||||
// Word32Slice returns the address of a []int32, []uint32, []float32, or []enum field in the struct.
|
||||
func structPointer_Word32Slice(p structPointer, f field) *word32Slice {
|
||||
return (*word32Slice)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||
}
|
||||
|
||||
// word64 is like word32 but for 64-bit values.
|
||||
type word64 **uint64
|
||||
|
||||
func word64_Set(p word64, o *Buffer, x uint64) {
|
||||
if len(o.uint64s) == 0 {
|
||||
o.uint64s = make([]uint64, uint64PoolSize)
|
||||
}
|
||||
o.uint64s[0] = x
|
||||
*p = &o.uint64s[0]
|
||||
o.uint64s = o.uint64s[1:]
|
||||
}
|
||||
|
||||
func word64_IsNil(p word64) bool {
|
||||
return *p == nil
|
||||
}
|
||||
|
||||
func word64_Get(p word64) uint64 {
|
||||
return **p
|
||||
}
|
||||
|
||||
func structPointer_Word64(p structPointer, f field) word64 {
|
||||
return word64((**uint64)(unsafe.Pointer(uintptr(p) + uintptr(f))))
|
||||
}
|
||||
|
||||
// word64Val is like word32Val but for 64-bit values.
|
||||
type word64Val *uint64
|
||||
|
||||
func word64Val_Set(p word64Val, o *Buffer, x uint64) {
|
||||
*p = x
|
||||
}
|
||||
|
||||
func word64Val_Get(p word64Val) uint64 {
|
||||
return *p
|
||||
}
|
||||
|
||||
func structPointer_Word64Val(p structPointer, f field) word64Val {
|
||||
return word64Val((*uint64)(unsafe.Pointer(uintptr(p) + uintptr(f))))
|
||||
}
|
||||
|
||||
// word64Slice is like word32Slice but for 64-bit values.
|
||||
type word64Slice []uint64
|
||||
|
||||
func (v *word64Slice) Append(x uint64) { *v = append(*v, x) }
|
||||
func (v *word64Slice) Len() int { return len(*v) }
|
||||
func (v *word64Slice) Index(i int) uint64 { return (*v)[i] }
|
||||
|
||||
func structPointer_Word64Slice(p structPointer, f field) *word64Slice {
|
||||
return (*word64Slice)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||
}
|
||||
+872
@@ -0,0 +1,872 @@
|
||||
// Go support for Protocol Buffers - Google's data interchange format
|
||||
//
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// https://github.com/golang/protobuf
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package proto
|
||||
|
||||
/*
|
||||
* Routines for encoding data into the wire format for protocol buffers.
|
||||
*/
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const debug bool = false
|
||||
|
||||
// Constants that identify the encoding of a value on the wire.
|
||||
const (
|
||||
WireVarint = 0
|
||||
WireFixed64 = 1
|
||||
WireBytes = 2
|
||||
WireStartGroup = 3
|
||||
WireEndGroup = 4
|
||||
WireFixed32 = 5
|
||||
)
|
||||
|
||||
const startSize = 10 // initial slice/string sizes
|
||||
|
||||
// Encoders are defined in encode.go
|
||||
// An encoder outputs the full representation of a field, including its
|
||||
// tag and encoder type.
|
||||
type encoder func(p *Buffer, prop *Properties, base structPointer) error
|
||||
|
||||
// A valueEncoder encodes a single integer in a particular encoding.
|
||||
type valueEncoder func(o *Buffer, x uint64) error
|
||||
|
||||
// Sizers are defined in encode.go
|
||||
// A sizer returns the encoded size of a field, including its tag and encoder
|
||||
// type.
|
||||
type sizer func(prop *Properties, base structPointer) int
|
||||
|
||||
// A valueSizer returns the encoded size of a single integer in a particular
|
||||
// encoding.
|
||||
type valueSizer func(x uint64) int
|
||||
|
||||
// Decoders are defined in decode.go
|
||||
// A decoder creates a value from its wire representation.
|
||||
// Unrecognized subelements are saved in unrec.
|
||||
type decoder func(p *Buffer, prop *Properties, base structPointer) error
|
||||
|
||||
// A valueDecoder decodes a single integer in a particular encoding.
|
||||
type valueDecoder func(o *Buffer) (x uint64, err error)
|
||||
|
||||
// A oneofMarshaler does the marshaling for all oneof fields in a message.
|
||||
type oneofMarshaler func(Message, *Buffer) error
|
||||
|
||||
// A oneofUnmarshaler does the unmarshaling for a oneof field in a message.
|
||||
type oneofUnmarshaler func(Message, int, int, *Buffer) (bool, error)
|
||||
|
||||
// A oneofSizer does the sizing for all oneof fields in a message.
|
||||
type oneofSizer func(Message) int
|
||||
|
||||
// tagMap is an optimization over map[int]int for typical protocol buffer
|
||||
// use-cases. Encoded protocol buffers are often in tag order with small tag
|
||||
// numbers.
|
||||
type tagMap struct {
|
||||
fastTags []int
|
||||
slowTags map[int]int
|
||||
}
|
||||
|
||||
// tagMapFastLimit is the upper bound on the tag number that will be stored in
|
||||
// the tagMap slice rather than its map.
|
||||
const tagMapFastLimit = 1024
|
||||
|
||||
func (p *tagMap) get(t int) (int, bool) {
|
||||
if t > 0 && t < tagMapFastLimit {
|
||||
if t >= len(p.fastTags) {
|
||||
return 0, false
|
||||
}
|
||||
fi := p.fastTags[t]
|
||||
return fi, fi >= 0
|
||||
}
|
||||
fi, ok := p.slowTags[t]
|
||||
return fi, ok
|
||||
}
|
||||
|
||||
func (p *tagMap) put(t int, fi int) {
|
||||
if t > 0 && t < tagMapFastLimit {
|
||||
for len(p.fastTags) < t+1 {
|
||||
p.fastTags = append(p.fastTags, -1)
|
||||
}
|
||||
p.fastTags[t] = fi
|
||||
return
|
||||
}
|
||||
if p.slowTags == nil {
|
||||
p.slowTags = make(map[int]int)
|
||||
}
|
||||
p.slowTags[t] = fi
|
||||
}
|
||||
|
||||
// StructProperties represents properties for all the fields of a struct.
|
||||
// decoderTags and decoderOrigNames should only be used by the decoder.
|
||||
type StructProperties struct {
|
||||
Prop []*Properties // properties for each field
|
||||
reqCount int // required count
|
||||
decoderTags tagMap // map from proto tag to struct field number
|
||||
decoderOrigNames map[string]int // map from original name to struct field number
|
||||
order []int // list of struct field numbers in tag order
|
||||
unrecField field // field id of the XXX_unrecognized []byte field
|
||||
extendable bool // is this an extendable proto
|
||||
|
||||
oneofMarshaler oneofMarshaler
|
||||
oneofUnmarshaler oneofUnmarshaler
|
||||
oneofSizer oneofSizer
|
||||
stype reflect.Type
|
||||
|
||||
// OneofTypes contains information about the oneof fields in this message.
|
||||
// It is keyed by the original name of a field.
|
||||
OneofTypes map[string]*OneofProperties
|
||||
}
|
||||
|
||||
// OneofProperties represents information about a specific field in a oneof.
|
||||
type OneofProperties struct {
|
||||
Type reflect.Type // pointer to generated struct type for this oneof field
|
||||
Field int // struct field number of the containing oneof in the message
|
||||
Prop *Properties
|
||||
}
|
||||
|
||||
// Implement the sorting interface so we can sort the fields in tag order, as recommended by the spec.
|
||||
// See encode.go, (*Buffer).enc_struct.
|
||||
|
||||
func (sp *StructProperties) Len() int { return len(sp.order) }
|
||||
func (sp *StructProperties) Less(i, j int) bool {
|
||||
return sp.Prop[sp.order[i]].Tag < sp.Prop[sp.order[j]].Tag
|
||||
}
|
||||
func (sp *StructProperties) Swap(i, j int) { sp.order[i], sp.order[j] = sp.order[j], sp.order[i] }
|
||||
|
||||
// Properties represents the protocol-specific behavior of a single struct field.
|
||||
type Properties struct {
|
||||
Name string // name of the field, for error messages
|
||||
OrigName string // original name before protocol compiler (always set)
|
||||
JSONName string // name to use for JSON; determined by protoc
|
||||
Wire string
|
||||
WireType int
|
||||
Tag int
|
||||
Required bool
|
||||
Optional bool
|
||||
Repeated bool
|
||||
Packed bool // relevant for repeated primitives only
|
||||
Enum string // set for enum types only
|
||||
proto3 bool // whether this is known to be a proto3 field; set for []byte only
|
||||
oneof bool // whether this is a oneof field
|
||||
|
||||
Default string // default value
|
||||
HasDefault bool // whether an explicit default was provided
|
||||
def_uint64 uint64
|
||||
|
||||
enc encoder
|
||||
valEnc valueEncoder // set for bool and numeric types only
|
||||
field field
|
||||
tagcode []byte // encoding of EncodeVarint((Tag<<3)|WireType)
|
||||
tagbuf [8]byte
|
||||
stype reflect.Type // set for struct types only
|
||||
sprop *StructProperties // set for struct types only
|
||||
isMarshaler bool
|
||||
isUnmarshaler bool
|
||||
|
||||
mtype reflect.Type // set for map types only
|
||||
mkeyprop *Properties // set for map types only
|
||||
mvalprop *Properties // set for map types only
|
||||
|
||||
size sizer
|
||||
valSize valueSizer // set for bool and numeric types only
|
||||
|
||||
dec decoder
|
||||
valDec valueDecoder // set for bool and numeric types only
|
||||
|
||||
// If this is a packable field, this will be the decoder for the packed version of the field.
|
||||
packedDec decoder
|
||||
}
|
||||
|
||||
// String formats the properties in the protobuf struct field tag style.
|
||||
func (p *Properties) String() string {
|
||||
s := p.Wire
|
||||
s = ","
|
||||
s += strconv.Itoa(p.Tag)
|
||||
if p.Required {
|
||||
s += ",req"
|
||||
}
|
||||
if p.Optional {
|
||||
s += ",opt"
|
||||
}
|
||||
if p.Repeated {
|
||||
s += ",rep"
|
||||
}
|
||||
if p.Packed {
|
||||
s += ",packed"
|
||||
}
|
||||
s += ",name=" + p.OrigName
|
||||
if p.JSONName != p.OrigName {
|
||||
s += ",json=" + p.JSONName
|
||||
}
|
||||
if p.proto3 {
|
||||
s += ",proto3"
|
||||
}
|
||||
if p.oneof {
|
||||
s += ",oneof"
|
||||
}
|
||||
if len(p.Enum) > 0 {
|
||||
s += ",enum=" + p.Enum
|
||||
}
|
||||
if p.HasDefault {
|
||||
s += ",def=" + p.Default
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Parse populates p by parsing a string in the protobuf struct field tag style.
|
||||
func (p *Properties) Parse(s string) {
|
||||
// "bytes,49,opt,name=foo,def=hello!"
|
||||
fields := strings.Split(s, ",") // breaks def=, but handled below.
|
||||
if len(fields) < 2 {
|
||||
fmt.Fprintf(os.Stderr, "proto: tag has too few fields: %q\n", s)
|
||||
return
|
||||
}
|
||||
|
||||
p.Wire = fields[0]
|
||||
switch p.Wire {
|
||||
case "varint":
|
||||
p.WireType = WireVarint
|
||||
p.valEnc = (*Buffer).EncodeVarint
|
||||
p.valDec = (*Buffer).DecodeVarint
|
||||
p.valSize = sizeVarint
|
||||
case "fixed32":
|
||||
p.WireType = WireFixed32
|
||||
p.valEnc = (*Buffer).EncodeFixed32
|
||||
p.valDec = (*Buffer).DecodeFixed32
|
||||
p.valSize = sizeFixed32
|
||||
case "fixed64":
|
||||
p.WireType = WireFixed64
|
||||
p.valEnc = (*Buffer).EncodeFixed64
|
||||
p.valDec = (*Buffer).DecodeFixed64
|
||||
p.valSize = sizeFixed64
|
||||
case "zigzag32":
|
||||
p.WireType = WireVarint
|
||||
p.valEnc = (*Buffer).EncodeZigzag32
|
||||
p.valDec = (*Buffer).DecodeZigzag32
|
||||
p.valSize = sizeZigzag32
|
||||
case "zigzag64":
|
||||
p.WireType = WireVarint
|
||||
p.valEnc = (*Buffer).EncodeZigzag64
|
||||
p.valDec = (*Buffer).DecodeZigzag64
|
||||
p.valSize = sizeZigzag64
|
||||
case "bytes", "group":
|
||||
p.WireType = WireBytes
|
||||
// no numeric converter for non-numeric types
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "proto: tag has unknown wire type: %q\n", s)
|
||||
return
|
||||
}
|
||||
|
||||
var err error
|
||||
p.Tag, err = strconv.Atoi(fields[1])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for i := 2; i < len(fields); i++ {
|
||||
f := fields[i]
|
||||
switch {
|
||||
case f == "req":
|
||||
p.Required = true
|
||||
case f == "opt":
|
||||
p.Optional = true
|
||||
case f == "rep":
|
||||
p.Repeated = true
|
||||
case f == "packed":
|
||||
p.Packed = true
|
||||
case strings.HasPrefix(f, "name="):
|
||||
p.OrigName = f[5:]
|
||||
case strings.HasPrefix(f, "json="):
|
||||
p.JSONName = f[5:]
|
||||
case strings.HasPrefix(f, "enum="):
|
||||
p.Enum = f[5:]
|
||||
case f == "proto3":
|
||||
p.proto3 = true
|
||||
case f == "oneof":
|
||||
p.oneof = true
|
||||
case strings.HasPrefix(f, "def="):
|
||||
p.HasDefault = true
|
||||
p.Default = f[4:] // rest of string
|
||||
if i+1 < len(fields) {
|
||||
// Commas aren't escaped, and def is always last.
|
||||
p.Default += "," + strings.Join(fields[i+1:], ",")
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func logNoSliceEnc(t1, t2 reflect.Type) {
|
||||
fmt.Fprintf(os.Stderr, "proto: no slice oenc for %T = []%T\n", t1, t2)
|
||||
}
|
||||
|
||||
var protoMessageType = reflect.TypeOf((*Message)(nil)).Elem()
|
||||
|
||||
// Initialize the fields for encoding and decoding.
|
||||
func (p *Properties) setEncAndDec(typ reflect.Type, f *reflect.StructField, lockGetProp bool) {
|
||||
p.enc = nil
|
||||
p.dec = nil
|
||||
p.size = nil
|
||||
|
||||
switch t1 := typ; t1.Kind() {
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "proto: no coders for %v\n", t1)
|
||||
|
||||
// proto3 scalar types
|
||||
|
||||
case reflect.Bool:
|
||||
p.enc = (*Buffer).enc_proto3_bool
|
||||
p.dec = (*Buffer).dec_proto3_bool
|
||||
p.size = size_proto3_bool
|
||||
case reflect.Int32:
|
||||
p.enc = (*Buffer).enc_proto3_int32
|
||||
p.dec = (*Buffer).dec_proto3_int32
|
||||
p.size = size_proto3_int32
|
||||
case reflect.Uint32:
|
||||
p.enc = (*Buffer).enc_proto3_uint32
|
||||
p.dec = (*Buffer).dec_proto3_int32 // can reuse
|
||||
p.size = size_proto3_uint32
|
||||
case reflect.Int64, reflect.Uint64:
|
||||
p.enc = (*Buffer).enc_proto3_int64
|
||||
p.dec = (*Buffer).dec_proto3_int64
|
||||
p.size = size_proto3_int64
|
||||
case reflect.Float32:
|
||||
p.enc = (*Buffer).enc_proto3_uint32 // can just treat them as bits
|
||||
p.dec = (*Buffer).dec_proto3_int32
|
||||
p.size = size_proto3_uint32
|
||||
case reflect.Float64:
|
||||
p.enc = (*Buffer).enc_proto3_int64 // can just treat them as bits
|
||||
p.dec = (*Buffer).dec_proto3_int64
|
||||
p.size = size_proto3_int64
|
||||
case reflect.String:
|
||||
p.enc = (*Buffer).enc_proto3_string
|
||||
p.dec = (*Buffer).dec_proto3_string
|
||||
p.size = size_proto3_string
|
||||
|
||||
case reflect.Ptr:
|
||||
switch t2 := t1.Elem(); t2.Kind() {
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "proto: no encoder function for %v -> %v\n", t1, t2)
|
||||
break
|
||||
case reflect.Bool:
|
||||
p.enc = (*Buffer).enc_bool
|
||||
p.dec = (*Buffer).dec_bool
|
||||
p.size = size_bool
|
||||
case reflect.Int32:
|
||||
p.enc = (*Buffer).enc_int32
|
||||
p.dec = (*Buffer).dec_int32
|
||||
p.size = size_int32
|
||||
case reflect.Uint32:
|
||||
p.enc = (*Buffer).enc_uint32
|
||||
p.dec = (*Buffer).dec_int32 // can reuse
|
||||
p.size = size_uint32
|
||||
case reflect.Int64, reflect.Uint64:
|
||||
p.enc = (*Buffer).enc_int64
|
||||
p.dec = (*Buffer).dec_int64
|
||||
p.size = size_int64
|
||||
case reflect.Float32:
|
||||
p.enc = (*Buffer).enc_uint32 // can just treat them as bits
|
||||
p.dec = (*Buffer).dec_int32
|
||||
p.size = size_uint32
|
||||
case reflect.Float64:
|
||||
p.enc = (*Buffer).enc_int64 // can just treat them as bits
|
||||
p.dec = (*Buffer).dec_int64
|
||||
p.size = size_int64
|
||||
case reflect.String:
|
||||
p.enc = (*Buffer).enc_string
|
||||
p.dec = (*Buffer).dec_string
|
||||
p.size = size_string
|
||||
case reflect.Struct:
|
||||
p.stype = t1.Elem()
|
||||
p.isMarshaler = isMarshaler(t1)
|
||||
p.isUnmarshaler = isUnmarshaler(t1)
|
||||
if p.Wire == "bytes" {
|
||||
p.enc = (*Buffer).enc_struct_message
|
||||
p.dec = (*Buffer).dec_struct_message
|
||||
p.size = size_struct_message
|
||||
} else {
|
||||
p.enc = (*Buffer).enc_struct_group
|
||||
p.dec = (*Buffer).dec_struct_group
|
||||
p.size = size_struct_group
|
||||
}
|
||||
}
|
||||
|
||||
case reflect.Slice:
|
||||
switch t2 := t1.Elem(); t2.Kind() {
|
||||
default:
|
||||
logNoSliceEnc(t1, t2)
|
||||
break
|
||||
case reflect.Bool:
|
||||
if p.Packed {
|
||||
p.enc = (*Buffer).enc_slice_packed_bool
|
||||
p.size = size_slice_packed_bool
|
||||
} else {
|
||||
p.enc = (*Buffer).enc_slice_bool
|
||||
p.size = size_slice_bool
|
||||
}
|
||||
p.dec = (*Buffer).dec_slice_bool
|
||||
p.packedDec = (*Buffer).dec_slice_packed_bool
|
||||
case reflect.Int32:
|
||||
if p.Packed {
|
||||
p.enc = (*Buffer).enc_slice_packed_int32
|
||||
p.size = size_slice_packed_int32
|
||||
} else {
|
||||
p.enc = (*Buffer).enc_slice_int32
|
||||
p.size = size_slice_int32
|
||||
}
|
||||
p.dec = (*Buffer).dec_slice_int32
|
||||
p.packedDec = (*Buffer).dec_slice_packed_int32
|
||||
case reflect.Uint32:
|
||||
if p.Packed {
|
||||
p.enc = (*Buffer).enc_slice_packed_uint32
|
||||
p.size = size_slice_packed_uint32
|
||||
} else {
|
||||
p.enc = (*Buffer).enc_slice_uint32
|
||||
p.size = size_slice_uint32
|
||||
}
|
||||
p.dec = (*Buffer).dec_slice_int32
|
||||
p.packedDec = (*Buffer).dec_slice_packed_int32
|
||||
case reflect.Int64, reflect.Uint64:
|
||||
if p.Packed {
|
||||
p.enc = (*Buffer).enc_slice_packed_int64
|
||||
p.size = size_slice_packed_int64
|
||||
} else {
|
||||
p.enc = (*Buffer).enc_slice_int64
|
||||
p.size = size_slice_int64
|
||||
}
|
||||
p.dec = (*Buffer).dec_slice_int64
|
||||
p.packedDec = (*Buffer).dec_slice_packed_int64
|
||||
case reflect.Uint8:
|
||||
p.dec = (*Buffer).dec_slice_byte
|
||||
if p.proto3 {
|
||||
p.enc = (*Buffer).enc_proto3_slice_byte
|
||||
p.size = size_proto3_slice_byte
|
||||
} else {
|
||||
p.enc = (*Buffer).enc_slice_byte
|
||||
p.size = size_slice_byte
|
||||
}
|
||||
case reflect.Float32, reflect.Float64:
|
||||
switch t2.Bits() {
|
||||
case 32:
|
||||
// can just treat them as bits
|
||||
if p.Packed {
|
||||
p.enc = (*Buffer).enc_slice_packed_uint32
|
||||
p.size = size_slice_packed_uint32
|
||||
} else {
|
||||
p.enc = (*Buffer).enc_slice_uint32
|
||||
p.size = size_slice_uint32
|
||||
}
|
||||
p.dec = (*Buffer).dec_slice_int32
|
||||
p.packedDec = (*Buffer).dec_slice_packed_int32
|
||||
case 64:
|
||||
// can just treat them as bits
|
||||
if p.Packed {
|
||||
p.enc = (*Buffer).enc_slice_packed_int64
|
||||
p.size = size_slice_packed_int64
|
||||
} else {
|
||||
p.enc = (*Buffer).enc_slice_int64
|
||||
p.size = size_slice_int64
|
||||
}
|
||||
p.dec = (*Buffer).dec_slice_int64
|
||||
p.packedDec = (*Buffer).dec_slice_packed_int64
|
||||
default:
|
||||
logNoSliceEnc(t1, t2)
|
||||
break
|
||||
}
|
||||
case reflect.String:
|
||||
p.enc = (*Buffer).enc_slice_string
|
||||
p.dec = (*Buffer).dec_slice_string
|
||||
p.size = size_slice_string
|
||||
case reflect.Ptr:
|
||||
switch t3 := t2.Elem(); t3.Kind() {
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "proto: no ptr oenc for %T -> %T -> %T\n", t1, t2, t3)
|
||||
break
|
||||
case reflect.Struct:
|
||||
p.stype = t2.Elem()
|
||||
p.isMarshaler = isMarshaler(t2)
|
||||
p.isUnmarshaler = isUnmarshaler(t2)
|
||||
if p.Wire == "bytes" {
|
||||
p.enc = (*Buffer).enc_slice_struct_message
|
||||
p.dec = (*Buffer).dec_slice_struct_message
|
||||
p.size = size_slice_struct_message
|
||||
} else {
|
||||
p.enc = (*Buffer).enc_slice_struct_group
|
||||
p.dec = (*Buffer).dec_slice_struct_group
|
||||
p.size = size_slice_struct_group
|
||||
}
|
||||
}
|
||||
case reflect.Slice:
|
||||
switch t2.Elem().Kind() {
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "proto: no slice elem oenc for %T -> %T -> %T\n", t1, t2, t2.Elem())
|
||||
break
|
||||
case reflect.Uint8:
|
||||
p.enc = (*Buffer).enc_slice_slice_byte
|
||||
p.dec = (*Buffer).dec_slice_slice_byte
|
||||
p.size = size_slice_slice_byte
|
||||
}
|
||||
}
|
||||
|
||||
case reflect.Map:
|
||||
p.enc = (*Buffer).enc_new_map
|
||||
p.dec = (*Buffer).dec_new_map
|
||||
p.size = size_new_map
|
||||
|
||||
p.mtype = t1
|
||||
p.mkeyprop = &Properties{}
|
||||
p.mkeyprop.init(reflect.PtrTo(p.mtype.Key()), "Key", f.Tag.Get("protobuf_key"), nil, lockGetProp)
|
||||
p.mvalprop = &Properties{}
|
||||
vtype := p.mtype.Elem()
|
||||
if vtype.Kind() != reflect.Ptr && vtype.Kind() != reflect.Slice {
|
||||
// The value type is not a message (*T) or bytes ([]byte),
|
||||
// so we need encoders for the pointer to this type.
|
||||
vtype = reflect.PtrTo(vtype)
|
||||
}
|
||||
p.mvalprop.init(vtype, "Value", f.Tag.Get("protobuf_val"), nil, lockGetProp)
|
||||
}
|
||||
|
||||
// precalculate tag code
|
||||
wire := p.WireType
|
||||
if p.Packed {
|
||||
wire = WireBytes
|
||||
}
|
||||
x := uint32(p.Tag)<<3 | uint32(wire)
|
||||
i := 0
|
||||
for i = 0; x > 127; i++ {
|
||||
p.tagbuf[i] = 0x80 | uint8(x&0x7F)
|
||||
x >>= 7
|
||||
}
|
||||
p.tagbuf[i] = uint8(x)
|
||||
p.tagcode = p.tagbuf[0 : i+1]
|
||||
|
||||
if p.stype != nil {
|
||||
if lockGetProp {
|
||||
p.sprop = GetProperties(p.stype)
|
||||
} else {
|
||||
p.sprop = getPropertiesLocked(p.stype)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
marshalerType = reflect.TypeOf((*Marshaler)(nil)).Elem()
|
||||
unmarshalerType = reflect.TypeOf((*Unmarshaler)(nil)).Elem()
|
||||
)
|
||||
|
||||
// isMarshaler reports whether type t implements Marshaler.
|
||||
func isMarshaler(t reflect.Type) bool {
|
||||
// We're checking for (likely) pointer-receiver methods
|
||||
// so if t is not a pointer, something is very wrong.
|
||||
// The calls above only invoke isMarshaler on pointer types.
|
||||
if t.Kind() != reflect.Ptr {
|
||||
panic("proto: misuse of isMarshaler")
|
||||
}
|
||||
return t.Implements(marshalerType)
|
||||
}
|
||||
|
||||
// isUnmarshaler reports whether type t implements Unmarshaler.
|
||||
func isUnmarshaler(t reflect.Type) bool {
|
||||
// We're checking for (likely) pointer-receiver methods
|
||||
// so if t is not a pointer, something is very wrong.
|
||||
// The calls above only invoke isUnmarshaler on pointer types.
|
||||
if t.Kind() != reflect.Ptr {
|
||||
panic("proto: misuse of isUnmarshaler")
|
||||
}
|
||||
return t.Implements(unmarshalerType)
|
||||
}
|
||||
|
||||
// Init populates the properties from a protocol buffer struct tag.
|
||||
func (p *Properties) Init(typ reflect.Type, name, tag string, f *reflect.StructField) {
|
||||
p.init(typ, name, tag, f, true)
|
||||
}
|
||||
|
||||
func (p *Properties) init(typ reflect.Type, name, tag string, f *reflect.StructField, lockGetProp bool) {
|
||||
// "bytes,49,opt,def=hello!"
|
||||
p.Name = name
|
||||
p.OrigName = name
|
||||
if f != nil {
|
||||
p.field = toField(f)
|
||||
}
|
||||
if tag == "" {
|
||||
return
|
||||
}
|
||||
p.Parse(tag)
|
||||
p.setEncAndDec(typ, f, lockGetProp)
|
||||
}
|
||||
|
||||
var (
|
||||
propertiesMu sync.RWMutex
|
||||
propertiesMap = make(map[reflect.Type]*StructProperties)
|
||||
)
|
||||
|
||||
// GetProperties returns the list of properties for the type represented by t.
|
||||
// t must represent a generated struct type of a protocol message.
|
||||
func GetProperties(t reflect.Type) *StructProperties {
|
||||
if t.Kind() != reflect.Struct {
|
||||
panic("proto: type must have kind struct")
|
||||
}
|
||||
|
||||
// Most calls to GetProperties in a long-running program will be
|
||||
// retrieving details for types we have seen before.
|
||||
propertiesMu.RLock()
|
||||
sprop, ok := propertiesMap[t]
|
||||
propertiesMu.RUnlock()
|
||||
if ok {
|
||||
if collectStats {
|
||||
stats.Chit++
|
||||
}
|
||||
return sprop
|
||||
}
|
||||
|
||||
propertiesMu.Lock()
|
||||
sprop = getPropertiesLocked(t)
|
||||
propertiesMu.Unlock()
|
||||
return sprop
|
||||
}
|
||||
|
||||
// getPropertiesLocked requires that propertiesMu is held.
|
||||
func getPropertiesLocked(t reflect.Type) *StructProperties {
|
||||
if prop, ok := propertiesMap[t]; ok {
|
||||
if collectStats {
|
||||
stats.Chit++
|
||||
}
|
||||
return prop
|
||||
}
|
||||
if collectStats {
|
||||
stats.Cmiss++
|
||||
}
|
||||
|
||||
prop := new(StructProperties)
|
||||
// in case of recursive protos, fill this in now.
|
||||
propertiesMap[t] = prop
|
||||
|
||||
// build properties
|
||||
prop.extendable = reflect.PtrTo(t).Implements(extendableProtoType) ||
|
||||
reflect.PtrTo(t).Implements(extendableProtoV1Type)
|
||||
prop.unrecField = invalidField
|
||||
prop.Prop = make([]*Properties, t.NumField())
|
||||
prop.order = make([]int, t.NumField())
|
||||
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
f := t.Field(i)
|
||||
p := new(Properties)
|
||||
name := f.Name
|
||||
p.init(f.Type, name, f.Tag.Get("protobuf"), &f, false)
|
||||
|
||||
if f.Name == "XXX_InternalExtensions" { // special case
|
||||
p.enc = (*Buffer).enc_exts
|
||||
p.dec = nil // not needed
|
||||
p.size = size_exts
|
||||
} else if f.Name == "XXX_extensions" { // special case
|
||||
p.enc = (*Buffer).enc_map
|
||||
p.dec = nil // not needed
|
||||
p.size = size_map
|
||||
} else if f.Name == "XXX_unrecognized" { // special case
|
||||
prop.unrecField = toField(&f)
|
||||
}
|
||||
oneof := f.Tag.Get("protobuf_oneof") // special case
|
||||
if oneof != "" {
|
||||
// Oneof fields don't use the traditional protobuf tag.
|
||||
p.OrigName = oneof
|
||||
}
|
||||
prop.Prop[i] = p
|
||||
prop.order[i] = i
|
||||
if debug {
|
||||
print(i, " ", f.Name, " ", t.String(), " ")
|
||||
if p.Tag > 0 {
|
||||
print(p.String())
|
||||
}
|
||||
print("\n")
|
||||
}
|
||||
if p.enc == nil && !strings.HasPrefix(f.Name, "XXX_") && oneof == "" {
|
||||
fmt.Fprintln(os.Stderr, "proto: no encoder for", f.Name, f.Type.String(), "[GetProperties]")
|
||||
}
|
||||
}
|
||||
|
||||
// Re-order prop.order.
|
||||
sort.Sort(prop)
|
||||
|
||||
type oneofMessage interface {
|
||||
XXX_OneofFuncs() (func(Message, *Buffer) error, func(Message, int, int, *Buffer) (bool, error), func(Message) int, []interface{})
|
||||
}
|
||||
if om, ok := reflect.Zero(reflect.PtrTo(t)).Interface().(oneofMessage); ok {
|
||||
var oots []interface{}
|
||||
prop.oneofMarshaler, prop.oneofUnmarshaler, prop.oneofSizer, oots = om.XXX_OneofFuncs()
|
||||
prop.stype = t
|
||||
|
||||
// Interpret oneof metadata.
|
||||
prop.OneofTypes = make(map[string]*OneofProperties)
|
||||
for _, oot := range oots {
|
||||
oop := &OneofProperties{
|
||||
Type: reflect.ValueOf(oot).Type(), // *T
|
||||
Prop: new(Properties),
|
||||
}
|
||||
sft := oop.Type.Elem().Field(0)
|
||||
oop.Prop.Name = sft.Name
|
||||
oop.Prop.Parse(sft.Tag.Get("protobuf"))
|
||||
// There will be exactly one interface field that
|
||||
// this new value is assignable to.
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
f := t.Field(i)
|
||||
if f.Type.Kind() != reflect.Interface {
|
||||
continue
|
||||
}
|
||||
if !oop.Type.AssignableTo(f.Type) {
|
||||
continue
|
||||
}
|
||||
oop.Field = i
|
||||
break
|
||||
}
|
||||
prop.OneofTypes[oop.Prop.OrigName] = oop
|
||||
}
|
||||
}
|
||||
|
||||
// build required counts
|
||||
// build tags
|
||||
reqCount := 0
|
||||
prop.decoderOrigNames = make(map[string]int)
|
||||
for i, p := range prop.Prop {
|
||||
if strings.HasPrefix(p.Name, "XXX_") {
|
||||
// Internal fields should not appear in tags/origNames maps.
|
||||
// They are handled specially when encoding and decoding.
|
||||
continue
|
||||
}
|
||||
if p.Required {
|
||||
reqCount++
|
||||
}
|
||||
prop.decoderTags.put(p.Tag, i)
|
||||
prop.decoderOrigNames[p.OrigName] = i
|
||||
}
|
||||
prop.reqCount = reqCount
|
||||
|
||||
return prop
|
||||
}
|
||||
|
||||
// Return the Properties object for the x[0]'th field of the structure.
|
||||
func propByIndex(t reflect.Type, x []int) *Properties {
|
||||
if len(x) != 1 {
|
||||
fmt.Fprintf(os.Stderr, "proto: field index dimension %d (not 1) for type %s\n", len(x), t)
|
||||
return nil
|
||||
}
|
||||
prop := GetProperties(t)
|
||||
return prop.Prop[x[0]]
|
||||
}
|
||||
|
||||
// Get the address and type of a pointer to a struct from an interface.
|
||||
func getbase(pb Message) (t reflect.Type, b structPointer, err error) {
|
||||
if pb == nil {
|
||||
err = ErrNil
|
||||
return
|
||||
}
|
||||
// get the reflect type of the pointer to the struct.
|
||||
t = reflect.TypeOf(pb)
|
||||
// get the address of the struct.
|
||||
value := reflect.ValueOf(pb)
|
||||
b = toStructPointer(value)
|
||||
return
|
||||
}
|
||||
|
||||
// A global registry of enum types.
|
||||
// The generated code will register the generated maps by calling RegisterEnum.
|
||||
|
||||
var enumValueMaps = make(map[string]map[string]int32)
|
||||
|
||||
// RegisterEnum is called from the generated code to install the enum descriptor
|
||||
// maps into the global table to aid parsing text format protocol buffers.
|
||||
func RegisterEnum(typeName string, unusedNameMap map[int32]string, valueMap map[string]int32) {
|
||||
if _, ok := enumValueMaps[typeName]; ok {
|
||||
panic("proto: duplicate enum registered: " + typeName)
|
||||
}
|
||||
enumValueMaps[typeName] = valueMap
|
||||
}
|
||||
|
||||
// EnumValueMap returns the mapping from names to integers of the
|
||||
// enum type enumType, or a nil if not found.
|
||||
func EnumValueMap(enumType string) map[string]int32 {
|
||||
return enumValueMaps[enumType]
|
||||
}
|
||||
|
||||
// A registry of all linked message types.
|
||||
// The string is a fully-qualified proto name ("pkg.Message").
|
||||
var (
|
||||
protoTypes = make(map[string]reflect.Type)
|
||||
revProtoTypes = make(map[reflect.Type]string)
|
||||
)
|
||||
|
||||
// RegisterType is called from generated code and maps from the fully qualified
|
||||
// proto name to the type (pointer to struct) of the protocol buffer.
|
||||
func RegisterType(x Message, name string) {
|
||||
if _, ok := protoTypes[name]; ok {
|
||||
// TODO: Some day, make this a panic.
|
||||
log.Printf("proto: duplicate proto type registered: %s", name)
|
||||
return
|
||||
}
|
||||
t := reflect.TypeOf(x)
|
||||
protoTypes[name] = t
|
||||
revProtoTypes[t] = name
|
||||
}
|
||||
|
||||
// MessageName returns the fully-qualified proto name for the given message type.
|
||||
func MessageName(x Message) string {
|
||||
type xname interface {
|
||||
XXX_MessageName() string
|
||||
}
|
||||
if m, ok := x.(xname); ok {
|
||||
return m.XXX_MessageName()
|
||||
}
|
||||
return revProtoTypes[reflect.TypeOf(x)]
|
||||
}
|
||||
|
||||
// MessageType returns the message type (pointer to struct) for a named message.
|
||||
func MessageType(name string) reflect.Type { return protoTypes[name] }
|
||||
|
||||
// A registry of all linked proto files.
|
||||
var (
|
||||
protoFiles = make(map[string][]byte) // file name => fileDescriptor
|
||||
)
|
||||
|
||||
// RegisterFile is called from generated code and maps from the
|
||||
// full file name of a .proto file to its compressed FileDescriptorProto.
|
||||
func RegisterFile(filename string, fileDescriptor []byte) {
|
||||
protoFiles[filename] = fileDescriptor
|
||||
}
|
||||
|
||||
// FileDescriptor returns the compressed FileDescriptorProto for a .proto file.
|
||||
func FileDescriptor(filename string) []byte { return protoFiles[filename] }
|
||||
+854
@@ -0,0 +1,854 @@
|
||||
// Go support for Protocol Buffers - Google's data interchange format
|
||||
//
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// https://github.com/golang/protobuf
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package proto
|
||||
|
||||
// Functions for writing the text protocol buffer format.
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"math"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
newline = []byte("\n")
|
||||
spaces = []byte(" ")
|
||||
gtNewline = []byte(">\n")
|
||||
endBraceNewline = []byte("}\n")
|
||||
backslashN = []byte{'\\', 'n'}
|
||||
backslashR = []byte{'\\', 'r'}
|
||||
backslashT = []byte{'\\', 't'}
|
||||
backslashDQ = []byte{'\\', '"'}
|
||||
backslashBS = []byte{'\\', '\\'}
|
||||
posInf = []byte("inf")
|
||||
negInf = []byte("-inf")
|
||||
nan = []byte("nan")
|
||||
)
|
||||
|
||||
type writer interface {
|
||||
io.Writer
|
||||
WriteByte(byte) error
|
||||
}
|
||||
|
||||
// textWriter is an io.Writer that tracks its indentation level.
|
||||
type textWriter struct {
|
||||
ind int
|
||||
complete bool // if the current position is a complete line
|
||||
compact bool // whether to write out as a one-liner
|
||||
w writer
|
||||
}
|
||||
|
||||
func (w *textWriter) WriteString(s string) (n int, err error) {
|
||||
if !strings.Contains(s, "\n") {
|
||||
if !w.compact && w.complete {
|
||||
w.writeIndent()
|
||||
}
|
||||
w.complete = false
|
||||
return io.WriteString(w.w, s)
|
||||
}
|
||||
// WriteString is typically called without newlines, so this
|
||||
// codepath and its copy are rare. We copy to avoid
|
||||
// duplicating all of Write's logic here.
|
||||
return w.Write([]byte(s))
|
||||
}
|
||||
|
||||
func (w *textWriter) Write(p []byte) (n int, err error) {
|
||||
newlines := bytes.Count(p, newline)
|
||||
if newlines == 0 {
|
||||
if !w.compact && w.complete {
|
||||
w.writeIndent()
|
||||
}
|
||||
n, err = w.w.Write(p)
|
||||
w.complete = false
|
||||
return n, err
|
||||
}
|
||||
|
||||
frags := bytes.SplitN(p, newline, newlines+1)
|
||||
if w.compact {
|
||||
for i, frag := range frags {
|
||||
if i > 0 {
|
||||
if err := w.w.WriteByte(' '); err != nil {
|
||||
return n, err
|
||||
}
|
||||
n++
|
||||
}
|
||||
nn, err := w.w.Write(frag)
|
||||
n += nn
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
for i, frag := range frags {
|
||||
if w.complete {
|
||||
w.writeIndent()
|
||||
}
|
||||
nn, err := w.w.Write(frag)
|
||||
n += nn
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
if i+1 < len(frags) {
|
||||
if err := w.w.WriteByte('\n'); err != nil {
|
||||
return n, err
|
||||
}
|
||||
n++
|
||||
}
|
||||
}
|
||||
w.complete = len(frags[len(frags)-1]) == 0
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (w *textWriter) WriteByte(c byte) error {
|
||||
if w.compact && c == '\n' {
|
||||
c = ' '
|
||||
}
|
||||
if !w.compact && w.complete {
|
||||
w.writeIndent()
|
||||
}
|
||||
err := w.w.WriteByte(c)
|
||||
w.complete = c == '\n'
|
||||
return err
|
||||
}
|
||||
|
||||
func (w *textWriter) indent() { w.ind++ }
|
||||
|
||||
func (w *textWriter) unindent() {
|
||||
if w.ind == 0 {
|
||||
log.Print("proto: textWriter unindented too far")
|
||||
return
|
||||
}
|
||||
w.ind--
|
||||
}
|
||||
|
||||
func writeName(w *textWriter, props *Properties) error {
|
||||
if _, err := w.WriteString(props.OrigName); err != nil {
|
||||
return err
|
||||
}
|
||||
if props.Wire != "group" {
|
||||
return w.WriteByte(':')
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// raw is the interface satisfied by RawMessage.
|
||||
type raw interface {
|
||||
Bytes() []byte
|
||||
}
|
||||
|
||||
func requiresQuotes(u string) bool {
|
||||
// When type URL contains any characters except [0-9A-Za-z./\-]*, it must be quoted.
|
||||
for _, ch := range u {
|
||||
switch {
|
||||
case ch == '.' || ch == '/' || ch == '_':
|
||||
continue
|
||||
case '0' <= ch && ch <= '9':
|
||||
continue
|
||||
case 'A' <= ch && ch <= 'Z':
|
||||
continue
|
||||
case 'a' <= ch && ch <= 'z':
|
||||
continue
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// isAny reports whether sv is a google.protobuf.Any message
|
||||
func isAny(sv reflect.Value) bool {
|
||||
type wkt interface {
|
||||
XXX_WellKnownType() string
|
||||
}
|
||||
t, ok := sv.Addr().Interface().(wkt)
|
||||
return ok && t.XXX_WellKnownType() == "Any"
|
||||
}
|
||||
|
||||
// writeProto3Any writes an expanded google.protobuf.Any message.
|
||||
//
|
||||
// It returns (false, nil) if sv value can't be unmarshaled (e.g. because
|
||||
// required messages are not linked in).
|
||||
//
|
||||
// It returns (true, error) when sv was written in expanded format or an error
|
||||
// was encountered.
|
||||
func (tm *TextMarshaler) writeProto3Any(w *textWriter, sv reflect.Value) (bool, error) {
|
||||
turl := sv.FieldByName("TypeUrl")
|
||||
val := sv.FieldByName("Value")
|
||||
if !turl.IsValid() || !val.IsValid() {
|
||||
return true, errors.New("proto: invalid google.protobuf.Any message")
|
||||
}
|
||||
|
||||
b, ok := val.Interface().([]byte)
|
||||
if !ok {
|
||||
return true, errors.New("proto: invalid google.protobuf.Any message")
|
||||
}
|
||||
|
||||
parts := strings.Split(turl.String(), "/")
|
||||
mt := MessageType(parts[len(parts)-1])
|
||||
if mt == nil {
|
||||
return false, nil
|
||||
}
|
||||
m := reflect.New(mt.Elem())
|
||||
if err := Unmarshal(b, m.Interface().(Message)); err != nil {
|
||||
return false, nil
|
||||
}
|
||||
w.Write([]byte("["))
|
||||
u := turl.String()
|
||||
if requiresQuotes(u) {
|
||||
writeString(w, u)
|
||||
} else {
|
||||
w.Write([]byte(u))
|
||||
}
|
||||
if w.compact {
|
||||
w.Write([]byte("]:<"))
|
||||
} else {
|
||||
w.Write([]byte("]: <\n"))
|
||||
w.ind++
|
||||
}
|
||||
if err := tm.writeStruct(w, m.Elem()); err != nil {
|
||||
return true, err
|
||||
}
|
||||
if w.compact {
|
||||
w.Write([]byte("> "))
|
||||
} else {
|
||||
w.ind--
|
||||
w.Write([]byte(">\n"))
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (tm *TextMarshaler) writeStruct(w *textWriter, sv reflect.Value) error {
|
||||
if tm.ExpandAny && isAny(sv) {
|
||||
if canExpand, err := tm.writeProto3Any(w, sv); canExpand {
|
||||
return err
|
||||
}
|
||||
}
|
||||
st := sv.Type()
|
||||
sprops := GetProperties(st)
|
||||
for i := 0; i < sv.NumField(); i++ {
|
||||
fv := sv.Field(i)
|
||||
props := sprops.Prop[i]
|
||||
name := st.Field(i).Name
|
||||
|
||||
if strings.HasPrefix(name, "XXX_") {
|
||||
// There are two XXX_ fields:
|
||||
// XXX_unrecognized []byte
|
||||
// XXX_extensions map[int32]proto.Extension
|
||||
// The first is handled here;
|
||||
// the second is handled at the bottom of this function.
|
||||
if name == "XXX_unrecognized" && !fv.IsNil() {
|
||||
if err := writeUnknownStruct(w, fv.Interface().([]byte)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
if fv.Kind() == reflect.Ptr && fv.IsNil() {
|
||||
// Field not filled in. This could be an optional field or
|
||||
// a required field that wasn't filled in. Either way, there
|
||||
// isn't anything we can show for it.
|
||||
continue
|
||||
}
|
||||
if fv.Kind() == reflect.Slice && fv.IsNil() {
|
||||
// Repeated field that is empty, or a bytes field that is unused.
|
||||
continue
|
||||
}
|
||||
|
||||
if props.Repeated && fv.Kind() == reflect.Slice {
|
||||
// Repeated field.
|
||||
for j := 0; j < fv.Len(); j++ {
|
||||
if err := writeName(w, props); err != nil {
|
||||
return err
|
||||
}
|
||||
if !w.compact {
|
||||
if err := w.WriteByte(' '); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
v := fv.Index(j)
|
||||
if v.Kind() == reflect.Ptr && v.IsNil() {
|
||||
// A nil message in a repeated field is not valid,
|
||||
// but we can handle that more gracefully than panicking.
|
||||
if _, err := w.Write([]byte("<nil>\n")); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err := tm.writeAny(w, v, props); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := w.WriteByte('\n'); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
if fv.Kind() == reflect.Map {
|
||||
// Map fields are rendered as a repeated struct with key/value fields.
|
||||
keys := fv.MapKeys()
|
||||
sort.Sort(mapKeys(keys))
|
||||
for _, key := range keys {
|
||||
val := fv.MapIndex(key)
|
||||
if err := writeName(w, props); err != nil {
|
||||
return err
|
||||
}
|
||||
if !w.compact {
|
||||
if err := w.WriteByte(' '); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// open struct
|
||||
if err := w.WriteByte('<'); err != nil {
|
||||
return err
|
||||
}
|
||||
if !w.compact {
|
||||
if err := w.WriteByte('\n'); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
w.indent()
|
||||
// key
|
||||
if _, err := w.WriteString("key:"); err != nil {
|
||||
return err
|
||||
}
|
||||
if !w.compact {
|
||||
if err := w.WriteByte(' '); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := tm.writeAny(w, key, props.mkeyprop); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := w.WriteByte('\n'); err != nil {
|
||||
return err
|
||||
}
|
||||
// nil values aren't legal, but we can avoid panicking because of them.
|
||||
if val.Kind() != reflect.Ptr || !val.IsNil() {
|
||||
// value
|
||||
if _, err := w.WriteString("value:"); err != nil {
|
||||
return err
|
||||
}
|
||||
if !w.compact {
|
||||
if err := w.WriteByte(' '); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := tm.writeAny(w, val, props.mvalprop); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := w.WriteByte('\n'); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// close struct
|
||||
w.unindent()
|
||||
if err := w.WriteByte('>'); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := w.WriteByte('\n'); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
if props.proto3 && fv.Kind() == reflect.Slice && fv.Len() == 0 {
|
||||
// empty bytes field
|
||||
continue
|
||||
}
|
||||
if fv.Kind() != reflect.Ptr && fv.Kind() != reflect.Slice {
|
||||
// proto3 non-repeated scalar field; skip if zero value
|
||||
if isProto3Zero(fv) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if fv.Kind() == reflect.Interface {
|
||||
// Check if it is a oneof.
|
||||
if st.Field(i).Tag.Get("protobuf_oneof") != "" {
|
||||
// fv is nil, or holds a pointer to generated struct.
|
||||
// That generated struct has exactly one field,
|
||||
// which has a protobuf struct tag.
|
||||
if fv.IsNil() {
|
||||
continue
|
||||
}
|
||||
inner := fv.Elem().Elem() // interface -> *T -> T
|
||||
tag := inner.Type().Field(0).Tag.Get("protobuf")
|
||||
props = new(Properties) // Overwrite the outer props var, but not its pointee.
|
||||
props.Parse(tag)
|
||||
// Write the value in the oneof, not the oneof itself.
|
||||
fv = inner.Field(0)
|
||||
|
||||
// Special case to cope with malformed messages gracefully:
|
||||
// If the value in the oneof is a nil pointer, don't panic
|
||||
// in writeAny.
|
||||
if fv.Kind() == reflect.Ptr && fv.IsNil() {
|
||||
// Use errors.New so writeAny won't render quotes.
|
||||
msg := errors.New("/* nil */")
|
||||
fv = reflect.ValueOf(&msg).Elem()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := writeName(w, props); err != nil {
|
||||
return err
|
||||
}
|
||||
if !w.compact {
|
||||
if err := w.WriteByte(' '); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if b, ok := fv.Interface().(raw); ok {
|
||||
if err := writeRaw(w, b.Bytes()); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Enums have a String method, so writeAny will work fine.
|
||||
if err := tm.writeAny(w, fv, props); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := w.WriteByte('\n'); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Extensions (the XXX_extensions field).
|
||||
pv := sv.Addr()
|
||||
if _, ok := extendable(pv.Interface()); ok {
|
||||
if err := tm.writeExtensions(w, pv); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// writeRaw writes an uninterpreted raw message.
|
||||
func writeRaw(w *textWriter, b []byte) error {
|
||||
if err := w.WriteByte('<'); err != nil {
|
||||
return err
|
||||
}
|
||||
if !w.compact {
|
||||
if err := w.WriteByte('\n'); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
w.indent()
|
||||
if err := writeUnknownStruct(w, b); err != nil {
|
||||
return err
|
||||
}
|
||||
w.unindent()
|
||||
if err := w.WriteByte('>'); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// writeAny writes an arbitrary field.
|
||||
func (tm *TextMarshaler) writeAny(w *textWriter, v reflect.Value, props *Properties) error {
|
||||
v = reflect.Indirect(v)
|
||||
|
||||
// Floats have special cases.
|
||||
if v.Kind() == reflect.Float32 || v.Kind() == reflect.Float64 {
|
||||
x := v.Float()
|
||||
var b []byte
|
||||
switch {
|
||||
case math.IsInf(x, 1):
|
||||
b = posInf
|
||||
case math.IsInf(x, -1):
|
||||
b = negInf
|
||||
case math.IsNaN(x):
|
||||
b = nan
|
||||
}
|
||||
if b != nil {
|
||||
_, err := w.Write(b)
|
||||
return err
|
||||
}
|
||||
// Other values are handled below.
|
||||
}
|
||||
|
||||
// We don't attempt to serialise every possible value type; only those
|
||||
// that can occur in protocol buffers.
|
||||
switch v.Kind() {
|
||||
case reflect.Slice:
|
||||
// Should only be a []byte; repeated fields are handled in writeStruct.
|
||||
if err := writeString(w, string(v.Bytes())); err != nil {
|
||||
return err
|
||||
}
|
||||
case reflect.String:
|
||||
if err := writeString(w, v.String()); err != nil {
|
||||
return err
|
||||
}
|
||||
case reflect.Struct:
|
||||
// Required/optional group/message.
|
||||
var bra, ket byte = '<', '>'
|
||||
if props != nil && props.Wire == "group" {
|
||||
bra, ket = '{', '}'
|
||||
}
|
||||
if err := w.WriteByte(bra); err != nil {
|
||||
return err
|
||||
}
|
||||
if !w.compact {
|
||||
if err := w.WriteByte('\n'); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
w.indent()
|
||||
if etm, ok := v.Interface().(encoding.TextMarshaler); ok {
|
||||
text, err := etm.MarshalText()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err = w.Write(text); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if err := tm.writeStruct(w, v); err != nil {
|
||||
return err
|
||||
}
|
||||
w.unindent()
|
||||
if err := w.WriteByte(ket); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
_, err := fmt.Fprint(w, v.Interface())
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// equivalent to C's isprint.
|
||||
func isprint(c byte) bool {
|
||||
return c >= 0x20 && c < 0x7f
|
||||
}
|
||||
|
||||
// writeString writes a string in the protocol buffer text format.
|
||||
// It is similar to strconv.Quote except we don't use Go escape sequences,
|
||||
// we treat the string as a byte sequence, and we use octal escapes.
|
||||
// These differences are to maintain interoperability with the other
|
||||
// languages' implementations of the text format.
|
||||
func writeString(w *textWriter, s string) error {
|
||||
// use WriteByte here to get any needed indent
|
||||
if err := w.WriteByte('"'); err != nil {
|
||||
return err
|
||||
}
|
||||
// Loop over the bytes, not the runes.
|
||||
for i := 0; i < len(s); i++ {
|
||||
var err error
|
||||
// Divergence from C++: we don't escape apostrophes.
|
||||
// There's no need to escape them, and the C++ parser
|
||||
// copes with a naked apostrophe.
|
||||
switch c := s[i]; c {
|
||||
case '\n':
|
||||
_, err = w.w.Write(backslashN)
|
||||
case '\r':
|
||||
_, err = w.w.Write(backslashR)
|
||||
case '\t':
|
||||
_, err = w.w.Write(backslashT)
|
||||
case '"':
|
||||
_, err = w.w.Write(backslashDQ)
|
||||
case '\\':
|
||||
_, err = w.w.Write(backslashBS)
|
||||
default:
|
||||
if isprint(c) {
|
||||
err = w.w.WriteByte(c)
|
||||
} else {
|
||||
_, err = fmt.Fprintf(w.w, "\\%03o", c)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return w.WriteByte('"')
|
||||
}
|
||||
|
||||
func writeUnknownStruct(w *textWriter, data []byte) (err error) {
|
||||
if !w.compact {
|
||||
if _, err := fmt.Fprintf(w, "/* %d unknown bytes */\n", len(data)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
b := NewBuffer(data)
|
||||
for b.index < len(b.buf) {
|
||||
x, err := b.DecodeVarint()
|
||||
if err != nil {
|
||||
_, err := fmt.Fprintf(w, "/* %v */\n", err)
|
||||
return err
|
||||
}
|
||||
wire, tag := x&7, x>>3
|
||||
if wire == WireEndGroup {
|
||||
w.unindent()
|
||||
if _, err := w.Write(endBraceNewline); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
if _, err := fmt.Fprint(w, tag); err != nil {
|
||||
return err
|
||||
}
|
||||
if wire != WireStartGroup {
|
||||
if err := w.WriteByte(':'); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if !w.compact || wire == WireStartGroup {
|
||||
if err := w.WriteByte(' '); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
switch wire {
|
||||
case WireBytes:
|
||||
buf, e := b.DecodeRawBytes(false)
|
||||
if e == nil {
|
||||
_, err = fmt.Fprintf(w, "%q", buf)
|
||||
} else {
|
||||
_, err = fmt.Fprintf(w, "/* %v */", e)
|
||||
}
|
||||
case WireFixed32:
|
||||
x, err = b.DecodeFixed32()
|
||||
err = writeUnknownInt(w, x, err)
|
||||
case WireFixed64:
|
||||
x, err = b.DecodeFixed64()
|
||||
err = writeUnknownInt(w, x, err)
|
||||
case WireStartGroup:
|
||||
err = w.WriteByte('{')
|
||||
w.indent()
|
||||
case WireVarint:
|
||||
x, err = b.DecodeVarint()
|
||||
err = writeUnknownInt(w, x, err)
|
||||
default:
|
||||
_, err = fmt.Fprintf(w, "/* unknown wire type %d */", wire)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = w.WriteByte('\n'); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeUnknownInt(w *textWriter, x uint64, err error) error {
|
||||
if err == nil {
|
||||
_, err = fmt.Fprint(w, x)
|
||||
} else {
|
||||
_, err = fmt.Fprintf(w, "/* %v */", err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
type int32Slice []int32
|
||||
|
||||
func (s int32Slice) Len() int { return len(s) }
|
||||
func (s int32Slice) Less(i, j int) bool { return s[i] < s[j] }
|
||||
func (s int32Slice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||
|
||||
// writeExtensions writes all the extensions in pv.
|
||||
// pv is assumed to be a pointer to a protocol message struct that is extendable.
|
||||
func (tm *TextMarshaler) writeExtensions(w *textWriter, pv reflect.Value) error {
|
||||
emap := extensionMaps[pv.Type().Elem()]
|
||||
ep, _ := extendable(pv.Interface())
|
||||
|
||||
// Order the extensions by ID.
|
||||
// This isn't strictly necessary, but it will give us
|
||||
// canonical output, which will also make testing easier.
|
||||
m, mu := ep.extensionsRead()
|
||||
if m == nil {
|
||||
return nil
|
||||
}
|
||||
mu.Lock()
|
||||
ids := make([]int32, 0, len(m))
|
||||
for id := range m {
|
||||
ids = append(ids, id)
|
||||
}
|
||||
sort.Sort(int32Slice(ids))
|
||||
mu.Unlock()
|
||||
|
||||
for _, extNum := range ids {
|
||||
ext := m[extNum]
|
||||
var desc *ExtensionDesc
|
||||
if emap != nil {
|
||||
desc = emap[extNum]
|
||||
}
|
||||
if desc == nil {
|
||||
// Unknown extension.
|
||||
if err := writeUnknownStruct(w, ext.enc); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
pb, err := GetExtension(ep, desc)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed getting extension: %v", err)
|
||||
}
|
||||
|
||||
// Repeated extensions will appear as a slice.
|
||||
if !desc.repeated() {
|
||||
if err := tm.writeExtension(w, desc.Name, pb); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
v := reflect.ValueOf(pb)
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
if err := tm.writeExtension(w, desc.Name, v.Index(i).Interface()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tm *TextMarshaler) writeExtension(w *textWriter, name string, pb interface{}) error {
|
||||
if _, err := fmt.Fprintf(w, "[%s]:", name); err != nil {
|
||||
return err
|
||||
}
|
||||
if !w.compact {
|
||||
if err := w.WriteByte(' '); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := tm.writeAny(w, reflect.ValueOf(pb), nil); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := w.WriteByte('\n'); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *textWriter) writeIndent() {
|
||||
if !w.complete {
|
||||
return
|
||||
}
|
||||
remain := w.ind * 2
|
||||
for remain > 0 {
|
||||
n := remain
|
||||
if n > len(spaces) {
|
||||
n = len(spaces)
|
||||
}
|
||||
w.w.Write(spaces[:n])
|
||||
remain -= n
|
||||
}
|
||||
w.complete = false
|
||||
}
|
||||
|
||||
// TextMarshaler is a configurable text format marshaler.
|
||||
type TextMarshaler struct {
|
||||
Compact bool // use compact text format (one line).
|
||||
ExpandAny bool // expand google.protobuf.Any messages of known types
|
||||
}
|
||||
|
||||
// Marshal writes a given protocol buffer in text format.
|
||||
// The only errors returned are from w.
|
||||
func (tm *TextMarshaler) Marshal(w io.Writer, pb Message) error {
|
||||
val := reflect.ValueOf(pb)
|
||||
if pb == nil || val.IsNil() {
|
||||
w.Write([]byte("<nil>"))
|
||||
return nil
|
||||
}
|
||||
var bw *bufio.Writer
|
||||
ww, ok := w.(writer)
|
||||
if !ok {
|
||||
bw = bufio.NewWriter(w)
|
||||
ww = bw
|
||||
}
|
||||
aw := &textWriter{
|
||||
w: ww,
|
||||
complete: true,
|
||||
compact: tm.Compact,
|
||||
}
|
||||
|
||||
if etm, ok := pb.(encoding.TextMarshaler); ok {
|
||||
text, err := etm.MarshalText()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err = aw.Write(text); err != nil {
|
||||
return err
|
||||
}
|
||||
if bw != nil {
|
||||
return bw.Flush()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
// Dereference the received pointer so we don't have outer < and >.
|
||||
v := reflect.Indirect(val)
|
||||
if err := tm.writeStruct(aw, v); err != nil {
|
||||
return err
|
||||
}
|
||||
if bw != nil {
|
||||
return bw.Flush()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Text is the same as Marshal, but returns the string directly.
|
||||
func (tm *TextMarshaler) Text(pb Message) string {
|
||||
var buf bytes.Buffer
|
||||
tm.Marshal(&buf, pb)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
var (
|
||||
defaultTextMarshaler = TextMarshaler{}
|
||||
compactTextMarshaler = TextMarshaler{Compact: true}
|
||||
)
|
||||
|
||||
// TODO: consider removing some of the Marshal functions below.
|
||||
|
||||
// MarshalText writes a given protocol buffer in text format.
|
||||
// The only errors returned are from w.
|
||||
func MarshalText(w io.Writer, pb Message) error { return defaultTextMarshaler.Marshal(w, pb) }
|
||||
|
||||
// MarshalTextString is the same as MarshalText, but returns the string directly.
|
||||
func MarshalTextString(pb Message) string { return defaultTextMarshaler.Text(pb) }
|
||||
|
||||
// CompactText writes a given protocol buffer in compact text format (one line).
|
||||
func CompactText(w io.Writer, pb Message) error { return compactTextMarshaler.Marshal(w, pb) }
|
||||
|
||||
// CompactTextString is the same as CompactText, but returns the string directly.
|
||||
func CompactTextString(pb Message) string { return compactTextMarshaler.Text(pb) }
|
||||
+895
@@ -0,0 +1,895 @@
|
||||
// Go support for Protocol Buffers - Google's data interchange format
|
||||
//
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// https://github.com/golang/protobuf
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package proto
|
||||
|
||||
// Functions for parsing the Text protocol buffer format.
|
||||
// TODO: message sets.
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// Error string emitted when deserializing Any and fields are already set
|
||||
const anyRepeatedlyUnpacked = "Any message unpacked multiple times, or %q already set"
|
||||
|
||||
type ParseError struct {
|
||||
Message string
|
||||
Line int // 1-based line number
|
||||
Offset int // 0-based byte offset from start of input
|
||||
}
|
||||
|
||||
func (p *ParseError) Error() string {
|
||||
if p.Line == 1 {
|
||||
// show offset only for first line
|
||||
return fmt.Sprintf("line 1.%d: %v", p.Offset, p.Message)
|
||||
}
|
||||
return fmt.Sprintf("line %d: %v", p.Line, p.Message)
|
||||
}
|
||||
|
||||
type token struct {
|
||||
value string
|
||||
err *ParseError
|
||||
line int // line number
|
||||
offset int // byte number from start of input, not start of line
|
||||
unquoted string // the unquoted version of value, if it was a quoted string
|
||||
}
|
||||
|
||||
func (t *token) String() string {
|
||||
if t.err == nil {
|
||||
return fmt.Sprintf("%q (line=%d, offset=%d)", t.value, t.line, t.offset)
|
||||
}
|
||||
return fmt.Sprintf("parse error: %v", t.err)
|
||||
}
|
||||
|
||||
type textParser struct {
|
||||
s string // remaining input
|
||||
done bool // whether the parsing is finished (success or error)
|
||||
backed bool // whether back() was called
|
||||
offset, line int
|
||||
cur token
|
||||
}
|
||||
|
||||
func newTextParser(s string) *textParser {
|
||||
p := new(textParser)
|
||||
p.s = s
|
||||
p.line = 1
|
||||
p.cur.line = 1
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *textParser) errorf(format string, a ...interface{}) *ParseError {
|
||||
pe := &ParseError{fmt.Sprintf(format, a...), p.cur.line, p.cur.offset}
|
||||
p.cur.err = pe
|
||||
p.done = true
|
||||
return pe
|
||||
}
|
||||
|
||||
// Numbers and identifiers are matched by [-+._A-Za-z0-9]
|
||||
func isIdentOrNumberChar(c byte) bool {
|
||||
switch {
|
||||
case 'A' <= c && c <= 'Z', 'a' <= c && c <= 'z':
|
||||
return true
|
||||
case '0' <= c && c <= '9':
|
||||
return true
|
||||
}
|
||||
switch c {
|
||||
case '-', '+', '.', '_':
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isWhitespace(c byte) bool {
|
||||
switch c {
|
||||
case ' ', '\t', '\n', '\r':
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isQuote(c byte) bool {
|
||||
switch c {
|
||||
case '"', '\'':
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *textParser) skipWhitespace() {
|
||||
i := 0
|
||||
for i < len(p.s) && (isWhitespace(p.s[i]) || p.s[i] == '#') {
|
||||
if p.s[i] == '#' {
|
||||
// comment; skip to end of line or input
|
||||
for i < len(p.s) && p.s[i] != '\n' {
|
||||
i++
|
||||
}
|
||||
if i == len(p.s) {
|
||||
break
|
||||
}
|
||||
}
|
||||
if p.s[i] == '\n' {
|
||||
p.line++
|
||||
}
|
||||
i++
|
||||
}
|
||||
p.offset += i
|
||||
p.s = p.s[i:len(p.s)]
|
||||
if len(p.s) == 0 {
|
||||
p.done = true
|
||||
}
|
||||
}
|
||||
|
||||
func (p *textParser) advance() {
|
||||
// Skip whitespace
|
||||
p.skipWhitespace()
|
||||
if p.done {
|
||||
return
|
||||
}
|
||||
|
||||
// Start of non-whitespace
|
||||
p.cur.err = nil
|
||||
p.cur.offset, p.cur.line = p.offset, p.line
|
||||
p.cur.unquoted = ""
|
||||
switch p.s[0] {
|
||||
case '<', '>', '{', '}', ':', '[', ']', ';', ',', '/':
|
||||
// Single symbol
|
||||
p.cur.value, p.s = p.s[0:1], p.s[1:len(p.s)]
|
||||
case '"', '\'':
|
||||
// Quoted string
|
||||
i := 1
|
||||
for i < len(p.s) && p.s[i] != p.s[0] && p.s[i] != '\n' {
|
||||
if p.s[i] == '\\' && i+1 < len(p.s) {
|
||||
// skip escaped char
|
||||
i++
|
||||
}
|
||||
i++
|
||||
}
|
||||
if i >= len(p.s) || p.s[i] != p.s[0] {
|
||||
p.errorf("unmatched quote")
|
||||
return
|
||||
}
|
||||
unq, err := unquoteC(p.s[1:i], rune(p.s[0]))
|
||||
if err != nil {
|
||||
p.errorf("invalid quoted string %s: %v", p.s[0:i+1], err)
|
||||
return
|
||||
}
|
||||
p.cur.value, p.s = p.s[0:i+1], p.s[i+1:len(p.s)]
|
||||
p.cur.unquoted = unq
|
||||
default:
|
||||
i := 0
|
||||
for i < len(p.s) && isIdentOrNumberChar(p.s[i]) {
|
||||
i++
|
||||
}
|
||||
if i == 0 {
|
||||
p.errorf("unexpected byte %#x", p.s[0])
|
||||
return
|
||||
}
|
||||
p.cur.value, p.s = p.s[0:i], p.s[i:len(p.s)]
|
||||
}
|
||||
p.offset += len(p.cur.value)
|
||||
}
|
||||
|
||||
var (
|
||||
errBadUTF8 = errors.New("proto: bad UTF-8")
|
||||
errBadHex = errors.New("proto: bad hexadecimal")
|
||||
)
|
||||
|
||||
func unquoteC(s string, quote rune) (string, error) {
|
||||
// This is based on C++'s tokenizer.cc.
|
||||
// Despite its name, this is *not* parsing C syntax.
|
||||
// For instance, "\0" is an invalid quoted string.
|
||||
|
||||
// Avoid allocation in trivial cases.
|
||||
simple := true
|
||||
for _, r := range s {
|
||||
if r == '\\' || r == quote {
|
||||
simple = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if simple {
|
||||
return s, nil
|
||||
}
|
||||
|
||||
buf := make([]byte, 0, 3*len(s)/2)
|
||||
for len(s) > 0 {
|
||||
r, n := utf8.DecodeRuneInString(s)
|
||||
if r == utf8.RuneError && n == 1 {
|
||||
return "", errBadUTF8
|
||||
}
|
||||
s = s[n:]
|
||||
if r != '\\' {
|
||||
if r < utf8.RuneSelf {
|
||||
buf = append(buf, byte(r))
|
||||
} else {
|
||||
buf = append(buf, string(r)...)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
ch, tail, err := unescape(s)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
buf = append(buf, ch...)
|
||||
s = tail
|
||||
}
|
||||
return string(buf), nil
|
||||
}
|
||||
|
||||
func unescape(s string) (ch string, tail string, err error) {
|
||||
r, n := utf8.DecodeRuneInString(s)
|
||||
if r == utf8.RuneError && n == 1 {
|
||||
return "", "", errBadUTF8
|
||||
}
|
||||
s = s[n:]
|
||||
switch r {
|
||||
case 'a':
|
||||
return "\a", s, nil
|
||||
case 'b':
|
||||
return "\b", s, nil
|
||||
case 'f':
|
||||
return "\f", s, nil
|
||||
case 'n':
|
||||
return "\n", s, nil
|
||||
case 'r':
|
||||
return "\r", s, nil
|
||||
case 't':
|
||||
return "\t", s, nil
|
||||
case 'v':
|
||||
return "\v", s, nil
|
||||
case '?':
|
||||
return "?", s, nil // trigraph workaround
|
||||
case '\'', '"', '\\':
|
||||
return string(r), s, nil
|
||||
case '0', '1', '2', '3', '4', '5', '6', '7', 'x', 'X':
|
||||
if len(s) < 2 {
|
||||
return "", "", fmt.Errorf(`\%c requires 2 following digits`, r)
|
||||
}
|
||||
base := 8
|
||||
ss := s[:2]
|
||||
s = s[2:]
|
||||
if r == 'x' || r == 'X' {
|
||||
base = 16
|
||||
} else {
|
||||
ss = string(r) + ss
|
||||
}
|
||||
i, err := strconv.ParseUint(ss, base, 8)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
return string([]byte{byte(i)}), s, nil
|
||||
case 'u', 'U':
|
||||
n := 4
|
||||
if r == 'U' {
|
||||
n = 8
|
||||
}
|
||||
if len(s) < n {
|
||||
return "", "", fmt.Errorf(`\%c requires %d digits`, r, n)
|
||||
}
|
||||
|
||||
bs := make([]byte, n/2)
|
||||
for i := 0; i < n; i += 2 {
|
||||
a, ok1 := unhex(s[i])
|
||||
b, ok2 := unhex(s[i+1])
|
||||
if !ok1 || !ok2 {
|
||||
return "", "", errBadHex
|
||||
}
|
||||
bs[i/2] = a<<4 | b
|
||||
}
|
||||
s = s[n:]
|
||||
return string(bs), s, nil
|
||||
}
|
||||
return "", "", fmt.Errorf(`unknown escape \%c`, r)
|
||||
}
|
||||
|
||||
// Adapted from src/pkg/strconv/quote.go.
|
||||
func unhex(b byte) (v byte, ok bool) {
|
||||
switch {
|
||||
case '0' <= b && b <= '9':
|
||||
return b - '0', true
|
||||
case 'a' <= b && b <= 'f':
|
||||
return b - 'a' + 10, true
|
||||
case 'A' <= b && b <= 'F':
|
||||
return b - 'A' + 10, true
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// Back off the parser by one token. Can only be done between calls to next().
|
||||
// It makes the next advance() a no-op.
|
||||
func (p *textParser) back() { p.backed = true }
|
||||
|
||||
// Advances the parser and returns the new current token.
|
||||
func (p *textParser) next() *token {
|
||||
if p.backed || p.done {
|
||||
p.backed = false
|
||||
return &p.cur
|
||||
}
|
||||
p.advance()
|
||||
if p.done {
|
||||
p.cur.value = ""
|
||||
} else if len(p.cur.value) > 0 && isQuote(p.cur.value[0]) {
|
||||
// Look for multiple quoted strings separated by whitespace,
|
||||
// and concatenate them.
|
||||
cat := p.cur
|
||||
for {
|
||||
p.skipWhitespace()
|
||||
if p.done || !isQuote(p.s[0]) {
|
||||
break
|
||||
}
|
||||
p.advance()
|
||||
if p.cur.err != nil {
|
||||
return &p.cur
|
||||
}
|
||||
cat.value += " " + p.cur.value
|
||||
cat.unquoted += p.cur.unquoted
|
||||
}
|
||||
p.done = false // parser may have seen EOF, but we want to return cat
|
||||
p.cur = cat
|
||||
}
|
||||
return &p.cur
|
||||
}
|
||||
|
||||
func (p *textParser) consumeToken(s string) error {
|
||||
tok := p.next()
|
||||
if tok.err != nil {
|
||||
return tok.err
|
||||
}
|
||||
if tok.value != s {
|
||||
p.back()
|
||||
return p.errorf("expected %q, found %q", s, tok.value)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Return a RequiredNotSetError indicating which required field was not set.
|
||||
func (p *textParser) missingRequiredFieldError(sv reflect.Value) *RequiredNotSetError {
|
||||
st := sv.Type()
|
||||
sprops := GetProperties(st)
|
||||
for i := 0; i < st.NumField(); i++ {
|
||||
if !isNil(sv.Field(i)) {
|
||||
continue
|
||||
}
|
||||
|
||||
props := sprops.Prop[i]
|
||||
if props.Required {
|
||||
return &RequiredNotSetError{fmt.Sprintf("%v.%v", st, props.OrigName)}
|
||||
}
|
||||
}
|
||||
return &RequiredNotSetError{fmt.Sprintf("%v.<unknown field name>", st)} // should not happen
|
||||
}
|
||||
|
||||
// Returns the index in the struct for the named field, as well as the parsed tag properties.
|
||||
func structFieldByName(sprops *StructProperties, name string) (int, *Properties, bool) {
|
||||
i, ok := sprops.decoderOrigNames[name]
|
||||
if ok {
|
||||
return i, sprops.Prop[i], true
|
||||
}
|
||||
return -1, nil, false
|
||||
}
|
||||
|
||||
// Consume a ':' from the input stream (if the next token is a colon),
|
||||
// returning an error if a colon is needed but not present.
|
||||
func (p *textParser) checkForColon(props *Properties, typ reflect.Type) *ParseError {
|
||||
tok := p.next()
|
||||
if tok.err != nil {
|
||||
return tok.err
|
||||
}
|
||||
if tok.value != ":" {
|
||||
// Colon is optional when the field is a group or message.
|
||||
needColon := true
|
||||
switch props.Wire {
|
||||
case "group":
|
||||
needColon = false
|
||||
case "bytes":
|
||||
// A "bytes" field is either a message, a string, or a repeated field;
|
||||
// those three become *T, *string and []T respectively, so we can check for
|
||||
// this field being a pointer to a non-string.
|
||||
if typ.Kind() == reflect.Ptr {
|
||||
// *T or *string
|
||||
if typ.Elem().Kind() == reflect.String {
|
||||
break
|
||||
}
|
||||
} else if typ.Kind() == reflect.Slice {
|
||||
// []T or []*T
|
||||
if typ.Elem().Kind() != reflect.Ptr {
|
||||
break
|
||||
}
|
||||
} else if typ.Kind() == reflect.String {
|
||||
// The proto3 exception is for a string field,
|
||||
// which requires a colon.
|
||||
break
|
||||
}
|
||||
needColon = false
|
||||
}
|
||||
if needColon {
|
||||
return p.errorf("expected ':', found %q", tok.value)
|
||||
}
|
||||
p.back()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *textParser) readStruct(sv reflect.Value, terminator string) error {
|
||||
st := sv.Type()
|
||||
sprops := GetProperties(st)
|
||||
reqCount := sprops.reqCount
|
||||
var reqFieldErr error
|
||||
fieldSet := make(map[string]bool)
|
||||
// A struct is a sequence of "name: value", terminated by one of
|
||||
// '>' or '}', or the end of the input. A name may also be
|
||||
// "[extension]" or "[type/url]".
|
||||
//
|
||||
// The whole struct can also be an expanded Any message, like:
|
||||
// [type/url] < ... struct contents ... >
|
||||
for {
|
||||
tok := p.next()
|
||||
if tok.err != nil {
|
||||
return tok.err
|
||||
}
|
||||
if tok.value == terminator {
|
||||
break
|
||||
}
|
||||
if tok.value == "[" {
|
||||
// Looks like an extension or an Any.
|
||||
//
|
||||
// TODO: Check whether we need to handle
|
||||
// namespace rooted names (e.g. ".something.Foo").
|
||||
extName, err := p.consumeExtName()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if s := strings.LastIndex(extName, "/"); s >= 0 {
|
||||
// If it contains a slash, it's an Any type URL.
|
||||
messageName := extName[s+1:]
|
||||
mt := MessageType(messageName)
|
||||
if mt == nil {
|
||||
return p.errorf("unrecognized message %q in google.protobuf.Any", messageName)
|
||||
}
|
||||
tok = p.next()
|
||||
if tok.err != nil {
|
||||
return tok.err
|
||||
}
|
||||
// consume an optional colon
|
||||
if tok.value == ":" {
|
||||
tok = p.next()
|
||||
if tok.err != nil {
|
||||
return tok.err
|
||||
}
|
||||
}
|
||||
var terminator string
|
||||
switch tok.value {
|
||||
case "<":
|
||||
terminator = ">"
|
||||
case "{":
|
||||
terminator = "}"
|
||||
default:
|
||||
return p.errorf("expected '{' or '<', found %q", tok.value)
|
||||
}
|
||||
v := reflect.New(mt.Elem())
|
||||
if pe := p.readStruct(v.Elem(), terminator); pe != nil {
|
||||
return pe
|
||||
}
|
||||
b, err := Marshal(v.Interface().(Message))
|
||||
if err != nil {
|
||||
return p.errorf("failed to marshal message of type %q: %v", messageName, err)
|
||||
}
|
||||
if fieldSet["type_url"] {
|
||||
return p.errorf(anyRepeatedlyUnpacked, "type_url")
|
||||
}
|
||||
if fieldSet["value"] {
|
||||
return p.errorf(anyRepeatedlyUnpacked, "value")
|
||||
}
|
||||
sv.FieldByName("TypeUrl").SetString(extName)
|
||||
sv.FieldByName("Value").SetBytes(b)
|
||||
fieldSet["type_url"] = true
|
||||
fieldSet["value"] = true
|
||||
continue
|
||||
}
|
||||
|
||||
var desc *ExtensionDesc
|
||||
// This could be faster, but it's functional.
|
||||
// TODO: Do something smarter than a linear scan.
|
||||
for _, d := range RegisteredExtensions(reflect.New(st).Interface().(Message)) {
|
||||
if d.Name == extName {
|
||||
desc = d
|
||||
break
|
||||
}
|
||||
}
|
||||
if desc == nil {
|
||||
return p.errorf("unrecognized extension %q", extName)
|
||||
}
|
||||
|
||||
props := &Properties{}
|
||||
props.Parse(desc.Tag)
|
||||
|
||||
typ := reflect.TypeOf(desc.ExtensionType)
|
||||
if err := p.checkForColon(props, typ); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rep := desc.repeated()
|
||||
|
||||
// Read the extension structure, and set it in
|
||||
// the value we're constructing.
|
||||
var ext reflect.Value
|
||||
if !rep {
|
||||
ext = reflect.New(typ).Elem()
|
||||
} else {
|
||||
ext = reflect.New(typ.Elem()).Elem()
|
||||
}
|
||||
if err := p.readAny(ext, props); err != nil {
|
||||
if _, ok := err.(*RequiredNotSetError); !ok {
|
||||
return err
|
||||
}
|
||||
reqFieldErr = err
|
||||
}
|
||||
ep := sv.Addr().Interface().(Message)
|
||||
if !rep {
|
||||
SetExtension(ep, desc, ext.Interface())
|
||||
} else {
|
||||
old, err := GetExtension(ep, desc)
|
||||
var sl reflect.Value
|
||||
if err == nil {
|
||||
sl = reflect.ValueOf(old) // existing slice
|
||||
} else {
|
||||
sl = reflect.MakeSlice(typ, 0, 1)
|
||||
}
|
||||
sl = reflect.Append(sl, ext)
|
||||
SetExtension(ep, desc, sl.Interface())
|
||||
}
|
||||
if err := p.consumeOptionalSeparator(); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// This is a normal, non-extension field.
|
||||
name := tok.value
|
||||
var dst reflect.Value
|
||||
fi, props, ok := structFieldByName(sprops, name)
|
||||
if ok {
|
||||
dst = sv.Field(fi)
|
||||
} else if oop, ok := sprops.OneofTypes[name]; ok {
|
||||
// It is a oneof.
|
||||
props = oop.Prop
|
||||
nv := reflect.New(oop.Type.Elem())
|
||||
dst = nv.Elem().Field(0)
|
||||
field := sv.Field(oop.Field)
|
||||
if !field.IsNil() {
|
||||
return p.errorf("field '%s' would overwrite already parsed oneof '%s'", name, sv.Type().Field(oop.Field).Name)
|
||||
}
|
||||
field.Set(nv)
|
||||
}
|
||||
if !dst.IsValid() {
|
||||
return p.errorf("unknown field name %q in %v", name, st)
|
||||
}
|
||||
|
||||
if dst.Kind() == reflect.Map {
|
||||
// Consume any colon.
|
||||
if err := p.checkForColon(props, dst.Type()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Construct the map if it doesn't already exist.
|
||||
if dst.IsNil() {
|
||||
dst.Set(reflect.MakeMap(dst.Type()))
|
||||
}
|
||||
key := reflect.New(dst.Type().Key()).Elem()
|
||||
val := reflect.New(dst.Type().Elem()).Elem()
|
||||
|
||||
// The map entry should be this sequence of tokens:
|
||||
// < key : KEY value : VALUE >
|
||||
// However, implementations may omit key or value, and technically
|
||||
// we should support them in any order. See b/28924776 for a time
|
||||
// this went wrong.
|
||||
|
||||
tok := p.next()
|
||||
var terminator string
|
||||
switch tok.value {
|
||||
case "<":
|
||||
terminator = ">"
|
||||
case "{":
|
||||
terminator = "}"
|
||||
default:
|
||||
return p.errorf("expected '{' or '<', found %q", tok.value)
|
||||
}
|
||||
for {
|
||||
tok := p.next()
|
||||
if tok.err != nil {
|
||||
return tok.err
|
||||
}
|
||||
if tok.value == terminator {
|
||||
break
|
||||
}
|
||||
switch tok.value {
|
||||
case "key":
|
||||
if err := p.consumeToken(":"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := p.readAny(key, props.mkeyprop); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := p.consumeOptionalSeparator(); err != nil {
|
||||
return err
|
||||
}
|
||||
case "value":
|
||||
if err := p.checkForColon(props.mvalprop, dst.Type().Elem()); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := p.readAny(val, props.mvalprop); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := p.consumeOptionalSeparator(); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
p.back()
|
||||
return p.errorf(`expected "key", "value", or %q, found %q`, terminator, tok.value)
|
||||
}
|
||||
}
|
||||
|
||||
dst.SetMapIndex(key, val)
|
||||
continue
|
||||
}
|
||||
|
||||
// Check that it's not already set if it's not a repeated field.
|
||||
if !props.Repeated && fieldSet[name] {
|
||||
return p.errorf("non-repeated field %q was repeated", name)
|
||||
}
|
||||
|
||||
if err := p.checkForColon(props, dst.Type()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Parse into the field.
|
||||
fieldSet[name] = true
|
||||
if err := p.readAny(dst, props); err != nil {
|
||||
if _, ok := err.(*RequiredNotSetError); !ok {
|
||||
return err
|
||||
}
|
||||
reqFieldErr = err
|
||||
}
|
||||
if props.Required {
|
||||
reqCount--
|
||||
}
|
||||
|
||||
if err := p.consumeOptionalSeparator(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if reqCount > 0 {
|
||||
return p.missingRequiredFieldError(sv)
|
||||
}
|
||||
return reqFieldErr
|
||||
}
|
||||
|
||||
// consumeExtName consumes extension name or expanded Any type URL and the
|
||||
// following ']'. It returns the name or URL consumed.
|
||||
func (p *textParser) consumeExtName() (string, error) {
|
||||
tok := p.next()
|
||||
if tok.err != nil {
|
||||
return "", tok.err
|
||||
}
|
||||
|
||||
// If extension name or type url is quoted, it's a single token.
|
||||
if len(tok.value) > 2 && isQuote(tok.value[0]) && tok.value[len(tok.value)-1] == tok.value[0] {
|
||||
name, err := unquoteC(tok.value[1:len(tok.value)-1], rune(tok.value[0]))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return name, p.consumeToken("]")
|
||||
}
|
||||
|
||||
// Consume everything up to "]"
|
||||
var parts []string
|
||||
for tok.value != "]" {
|
||||
parts = append(parts, tok.value)
|
||||
tok = p.next()
|
||||
if tok.err != nil {
|
||||
return "", p.errorf("unrecognized type_url or extension name: %s", tok.err)
|
||||
}
|
||||
}
|
||||
return strings.Join(parts, ""), nil
|
||||
}
|
||||
|
||||
// consumeOptionalSeparator consumes an optional semicolon or comma.
|
||||
// It is used in readStruct to provide backward compatibility.
|
||||
func (p *textParser) consumeOptionalSeparator() error {
|
||||
tok := p.next()
|
||||
if tok.err != nil {
|
||||
return tok.err
|
||||
}
|
||||
if tok.value != ";" && tok.value != "," {
|
||||
p.back()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *textParser) readAny(v reflect.Value, props *Properties) error {
|
||||
tok := p.next()
|
||||
if tok.err != nil {
|
||||
return tok.err
|
||||
}
|
||||
if tok.value == "" {
|
||||
return p.errorf("unexpected EOF")
|
||||
}
|
||||
|
||||
switch fv := v; fv.Kind() {
|
||||
case reflect.Slice:
|
||||
at := v.Type()
|
||||
if at.Elem().Kind() == reflect.Uint8 {
|
||||
// Special case for []byte
|
||||
if tok.value[0] != '"' && tok.value[0] != '\'' {
|
||||
// Deliberately written out here, as the error after
|
||||
// this switch statement would write "invalid []byte: ...",
|
||||
// which is not as user-friendly.
|
||||
return p.errorf("invalid string: %v", tok.value)
|
||||
}
|
||||
bytes := []byte(tok.unquoted)
|
||||
fv.Set(reflect.ValueOf(bytes))
|
||||
return nil
|
||||
}
|
||||
// Repeated field.
|
||||
if tok.value == "[" {
|
||||
// Repeated field with list notation, like [1,2,3].
|
||||
for {
|
||||
fv.Set(reflect.Append(fv, reflect.New(at.Elem()).Elem()))
|
||||
err := p.readAny(fv.Index(fv.Len()-1), props)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tok := p.next()
|
||||
if tok.err != nil {
|
||||
return tok.err
|
||||
}
|
||||
if tok.value == "]" {
|
||||
break
|
||||
}
|
||||
if tok.value != "," {
|
||||
return p.errorf("Expected ']' or ',' found %q", tok.value)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
// One value of the repeated field.
|
||||
p.back()
|
||||
fv.Set(reflect.Append(fv, reflect.New(at.Elem()).Elem()))
|
||||
return p.readAny(fv.Index(fv.Len()-1), props)
|
||||
case reflect.Bool:
|
||||
// true/1/t/True or false/f/0/False.
|
||||
switch tok.value {
|
||||
case "true", "1", "t", "True":
|
||||
fv.SetBool(true)
|
||||
return nil
|
||||
case "false", "0", "f", "False":
|
||||
fv.SetBool(false)
|
||||
return nil
|
||||
}
|
||||
case reflect.Float32, reflect.Float64:
|
||||
v := tok.value
|
||||
// Ignore 'f' for compatibility with output generated by C++, but don't
|
||||
// remove 'f' when the value is "-inf" or "inf".
|
||||
if strings.HasSuffix(v, "f") && tok.value != "-inf" && tok.value != "inf" {
|
||||
v = v[:len(v)-1]
|
||||
}
|
||||
if f, err := strconv.ParseFloat(v, fv.Type().Bits()); err == nil {
|
||||
fv.SetFloat(f)
|
||||
return nil
|
||||
}
|
||||
case reflect.Int32:
|
||||
if x, err := strconv.ParseInt(tok.value, 0, 32); err == nil {
|
||||
fv.SetInt(x)
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(props.Enum) == 0 {
|
||||
break
|
||||
}
|
||||
m, ok := enumValueMaps[props.Enum]
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
x, ok := m[tok.value]
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
fv.SetInt(int64(x))
|
||||
return nil
|
||||
case reflect.Int64:
|
||||
if x, err := strconv.ParseInt(tok.value, 0, 64); err == nil {
|
||||
fv.SetInt(x)
|
||||
return nil
|
||||
}
|
||||
|
||||
case reflect.Ptr:
|
||||
// A basic field (indirected through pointer), or a repeated message/group
|
||||
p.back()
|
||||
fv.Set(reflect.New(fv.Type().Elem()))
|
||||
return p.readAny(fv.Elem(), props)
|
||||
case reflect.String:
|
||||
if tok.value[0] == '"' || tok.value[0] == '\'' {
|
||||
fv.SetString(tok.unquoted)
|
||||
return nil
|
||||
}
|
||||
case reflect.Struct:
|
||||
var terminator string
|
||||
switch tok.value {
|
||||
case "{":
|
||||
terminator = "}"
|
||||
case "<":
|
||||
terminator = ">"
|
||||
default:
|
||||
return p.errorf("expected '{' or '<', found %q", tok.value)
|
||||
}
|
||||
// TODO: Handle nested messages which implement encoding.TextUnmarshaler.
|
||||
return p.readStruct(fv, terminator)
|
||||
case reflect.Uint32:
|
||||
if x, err := strconv.ParseUint(tok.value, 0, 32); err == nil {
|
||||
fv.SetUint(uint64(x))
|
||||
return nil
|
||||
}
|
||||
case reflect.Uint64:
|
||||
if x, err := strconv.ParseUint(tok.value, 0, 64); err == nil {
|
||||
fv.SetUint(x)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return p.errorf("invalid %v: %v", v.Type(), tok.value)
|
||||
}
|
||||
|
||||
// UnmarshalText reads a protocol buffer in Text format. UnmarshalText resets pb
|
||||
// before starting to unmarshal, so any existing data in pb is always removed.
|
||||
// If a required field is not set and no other error occurs,
|
||||
// UnmarshalText returns *RequiredNotSetError.
|
||||
func UnmarshalText(s string, pb Message) error {
|
||||
if um, ok := pb.(encoding.TextUnmarshaler); ok {
|
||||
err := um.UnmarshalText([]byte(s))
|
||||
return err
|
||||
}
|
||||
pb.Reset()
|
||||
v := reflect.ValueOf(pb)
|
||||
if pe := newTextParser(s).readStruct(v.Elem(), ""); pe != nil {
|
||||
return pe
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+168
@@ -0,0 +1,168 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// source: github.com/golang/protobuf/ptypes/any/any.proto
|
||||
|
||||
/*
|
||||
Package any is a generated protocol buffer package.
|
||||
|
||||
It is generated from these files:
|
||||
github.com/golang/protobuf/ptypes/any/any.proto
|
||||
|
||||
It has these top-level messages:
|
||||
Any
|
||||
*/
|
||||
package any
|
||||
|
||||
import proto "github.com/golang/protobuf/proto"
|
||||
import fmt "fmt"
|
||||
import math "math"
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
|
||||
|
||||
// `Any` contains an arbitrary serialized protocol buffer message along with a
|
||||
// URL that describes the type of the serialized message.
|
||||
//
|
||||
// Protobuf library provides support to pack/unpack Any values in the form
|
||||
// of utility functions or additional generated methods of the Any type.
|
||||
//
|
||||
// Example 1: Pack and unpack a message in C++.
|
||||
//
|
||||
// Foo foo = ...;
|
||||
// Any any;
|
||||
// any.PackFrom(foo);
|
||||
// ...
|
||||
// if (any.UnpackTo(&foo)) {
|
||||
// ...
|
||||
// }
|
||||
//
|
||||
// Example 2: Pack and unpack a message in Java.
|
||||
//
|
||||
// Foo foo = ...;
|
||||
// Any any = Any.pack(foo);
|
||||
// ...
|
||||
// if (any.is(Foo.class)) {
|
||||
// foo = any.unpack(Foo.class);
|
||||
// }
|
||||
//
|
||||
// Example 3: Pack and unpack a message in Python.
|
||||
//
|
||||
// foo = Foo(...)
|
||||
// any = Any()
|
||||
// any.Pack(foo)
|
||||
// ...
|
||||
// if any.Is(Foo.DESCRIPTOR):
|
||||
// any.Unpack(foo)
|
||||
// ...
|
||||
//
|
||||
// The pack methods provided by protobuf library will by default use
|
||||
// 'type.googleapis.com/full.type.name' as the type URL and the unpack
|
||||
// methods only use the fully qualified type name after the last '/'
|
||||
// in the type URL, for example "foo.bar.com/x/y.z" will yield type
|
||||
// name "y.z".
|
||||
//
|
||||
//
|
||||
// JSON
|
||||
// ====
|
||||
// The JSON representation of an `Any` value uses the regular
|
||||
// representation of the deserialized, embedded message, with an
|
||||
// additional field `@type` which contains the type URL. Example:
|
||||
//
|
||||
// package google.profile;
|
||||
// message Person {
|
||||
// string first_name = 1;
|
||||
// string last_name = 2;
|
||||
// }
|
||||
//
|
||||
// {
|
||||
// "@type": "type.googleapis.com/google.profile.Person",
|
||||
// "firstName": <string>,
|
||||
// "lastName": <string>
|
||||
// }
|
||||
//
|
||||
// If the embedded message type is well-known and has a custom JSON
|
||||
// representation, that representation will be embedded adding a field
|
||||
// `value` which holds the custom JSON in addition to the `@type`
|
||||
// field. Example (for message [google.protobuf.Duration][]):
|
||||
//
|
||||
// {
|
||||
// "@type": "type.googleapis.com/google.protobuf.Duration",
|
||||
// "value": "1.212s"
|
||||
// }
|
||||
//
|
||||
type Any struct {
|
||||
// A URL/resource name whose content describes the type of the
|
||||
// serialized protocol buffer message.
|
||||
//
|
||||
// For URLs which use the scheme `http`, `https`, or no scheme, the
|
||||
// following restrictions and interpretations apply:
|
||||
//
|
||||
// * If no scheme is provided, `https` is assumed.
|
||||
// * The last segment of the URL's path must represent the fully
|
||||
// qualified name of the type (as in `path/google.protobuf.Duration`).
|
||||
// The name should be in a canonical form (e.g., leading "." is
|
||||
// not accepted).
|
||||
// * An HTTP GET on the URL must yield a [google.protobuf.Type][]
|
||||
// value in binary format, or produce an error.
|
||||
// * Applications are allowed to cache lookup results based on the
|
||||
// URL, or have them precompiled into a binary to avoid any
|
||||
// lookup. Therefore, binary compatibility needs to be preserved
|
||||
// on changes to types. (Use versioned type names to manage
|
||||
// breaking changes.)
|
||||
//
|
||||
// Schemes other than `http`, `https` (or the empty scheme) might be
|
||||
// used with implementation specific semantics.
|
||||
//
|
||||
TypeUrl string `protobuf:"bytes,1,opt,name=type_url,json=typeUrl" json:"type_url,omitempty"`
|
||||
// Must be a valid serialized protocol buffer of the above specified type.
|
||||
Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"`
|
||||
}
|
||||
|
||||
func (m *Any) Reset() { *m = Any{} }
|
||||
func (m *Any) String() string { return proto.CompactTextString(m) }
|
||||
func (*Any) ProtoMessage() {}
|
||||
func (*Any) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
|
||||
func (*Any) XXX_WellKnownType() string { return "Any" }
|
||||
|
||||
func (m *Any) GetTypeUrl() string {
|
||||
if m != nil {
|
||||
return m.TypeUrl
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *Any) GetValue() []byte {
|
||||
if m != nil {
|
||||
return m.Value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*Any)(nil), "google.protobuf.Any")
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("github.com/golang/protobuf/ptypes/any/any.proto", fileDescriptor0) }
|
||||
|
||||
var fileDescriptor0 = []byte{
|
||||
// 184 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xd2, 0x4f, 0xcf, 0x2c, 0xc9,
|
||||
0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf, 0xd5, 0x4f, 0xcf, 0xcf, 0x49, 0xcc, 0x4b, 0xd7, 0x2f, 0x28,
|
||||
0xca, 0x2f, 0xc9, 0x4f, 0x2a, 0x4d, 0xd3, 0x2f, 0x28, 0xa9, 0x2c, 0x48, 0x2d, 0xd6, 0x4f, 0xcc,
|
||||
0xab, 0x04, 0x61, 0x3d, 0xb0, 0xb8, 0x10, 0x7f, 0x7a, 0x7e, 0x7e, 0x7a, 0x4e, 0xaa, 0x1e, 0x4c,
|
||||
0x95, 0x92, 0x19, 0x17, 0xb3, 0x63, 0x5e, 0xa5, 0x90, 0x24, 0x17, 0x07, 0x48, 0x79, 0x7c, 0x69,
|
||||
0x51, 0x8e, 0x04, 0xa3, 0x02, 0xa3, 0x06, 0x67, 0x10, 0x3b, 0x88, 0x1f, 0x5a, 0x94, 0x23, 0x24,
|
||||
0xc2, 0xc5, 0x5a, 0x96, 0x98, 0x53, 0x9a, 0x2a, 0xc1, 0xa4, 0xc0, 0xa8, 0xc1, 0x13, 0x04, 0xe1,
|
||||
0x38, 0xe5, 0x73, 0x09, 0x27, 0xe7, 0xe7, 0xea, 0xa1, 0x19, 0xe7, 0xc4, 0xe1, 0x98, 0x57, 0x19,
|
||||
0x00, 0xe2, 0x04, 0x30, 0x46, 0xa9, 0x12, 0xe5, 0xb8, 0x45, 0x4c, 0xcc, 0xee, 0x01, 0x4e, 0xab,
|
||||
0x98, 0xe4, 0xdc, 0x21, 0x46, 0x05, 0x40, 0x95, 0xe8, 0x85, 0xa7, 0xe6, 0xe4, 0x78, 0xe7, 0xe5,
|
||||
0x97, 0xe7, 0x85, 0x80, 0x94, 0x26, 0xb1, 0x81, 0xf5, 0x1a, 0x03, 0x02, 0x00, 0x00, 0xff, 0xff,
|
||||
0x45, 0x1f, 0x1a, 0xf2, 0xf3, 0x00, 0x00, 0x00,
|
||||
}
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2008 Google Inc. All rights reserved.
|
||||
// https://developers.google.com/protocol-buffers/
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
syntax = "proto3";
|
||||
|
||||
package google.protobuf;
|
||||
|
||||
option csharp_namespace = "Google.Protobuf.WellKnownTypes";
|
||||
option go_package = "github.com/golang/protobuf/ptypes/any";
|
||||
option java_package = "com.google.protobuf";
|
||||
option java_outer_classname = "AnyProto";
|
||||
option java_multiple_files = true;
|
||||
option objc_class_prefix = "GPB";
|
||||
|
||||
// `Any` contains an arbitrary serialized protocol buffer message along with a
|
||||
// URL that describes the type of the serialized message.
|
||||
//
|
||||
// Protobuf library provides support to pack/unpack Any values in the form
|
||||
// of utility functions or additional generated methods of the Any type.
|
||||
//
|
||||
// Example 1: Pack and unpack a message in C++.
|
||||
//
|
||||
// Foo foo = ...;
|
||||
// Any any;
|
||||
// any.PackFrom(foo);
|
||||
// ...
|
||||
// if (any.UnpackTo(&foo)) {
|
||||
// ...
|
||||
// }
|
||||
//
|
||||
// Example 2: Pack and unpack a message in Java.
|
||||
//
|
||||
// Foo foo = ...;
|
||||
// Any any = Any.pack(foo);
|
||||
// ...
|
||||
// if (any.is(Foo.class)) {
|
||||
// foo = any.unpack(Foo.class);
|
||||
// }
|
||||
//
|
||||
// Example 3: Pack and unpack a message in Python.
|
||||
//
|
||||
// foo = Foo(...)
|
||||
// any = Any()
|
||||
// any.Pack(foo)
|
||||
// ...
|
||||
// if any.Is(Foo.DESCRIPTOR):
|
||||
// any.Unpack(foo)
|
||||
// ...
|
||||
//
|
||||
// The pack methods provided by protobuf library will by default use
|
||||
// 'type.googleapis.com/full.type.name' as the type URL and the unpack
|
||||
// methods only use the fully qualified type name after the last '/'
|
||||
// in the type URL, for example "foo.bar.com/x/y.z" will yield type
|
||||
// name "y.z".
|
||||
//
|
||||
//
|
||||
// JSON
|
||||
// ====
|
||||
// The JSON representation of an `Any` value uses the regular
|
||||
// representation of the deserialized, embedded message, with an
|
||||
// additional field `@type` which contains the type URL. Example:
|
||||
//
|
||||
// package google.profile;
|
||||
// message Person {
|
||||
// string first_name = 1;
|
||||
// string last_name = 2;
|
||||
// }
|
||||
//
|
||||
// {
|
||||
// "@type": "type.googleapis.com/google.profile.Person",
|
||||
// "firstName": <string>,
|
||||
// "lastName": <string>
|
||||
// }
|
||||
//
|
||||
// If the embedded message type is well-known and has a custom JSON
|
||||
// representation, that representation will be embedded adding a field
|
||||
// `value` which holds the custom JSON in addition to the `@type`
|
||||
// field. Example (for message [google.protobuf.Duration][]):
|
||||
//
|
||||
// {
|
||||
// "@type": "type.googleapis.com/google.protobuf.Duration",
|
||||
// "value": "1.212s"
|
||||
// }
|
||||
//
|
||||
message Any {
|
||||
// A URL/resource name whose content describes the type of the
|
||||
// serialized protocol buffer message.
|
||||
//
|
||||
// For URLs which use the scheme `http`, `https`, or no scheme, the
|
||||
// following restrictions and interpretations apply:
|
||||
//
|
||||
// * If no scheme is provided, `https` is assumed.
|
||||
// * The last segment of the URL's path must represent the fully
|
||||
// qualified name of the type (as in `path/google.protobuf.Duration`).
|
||||
// The name should be in a canonical form (e.g., leading "." is
|
||||
// not accepted).
|
||||
// * An HTTP GET on the URL must yield a [google.protobuf.Type][]
|
||||
// value in binary format, or produce an error.
|
||||
// * Applications are allowed to cache lookup results based on the
|
||||
// URL, or have them precompiled into a binary to avoid any
|
||||
// lookup. Therefore, binary compatibility needs to be preserved
|
||||
// on changes to types. (Use versioned type names to manage
|
||||
// breaking changes.)
|
||||
//
|
||||
// Schemes other than `http`, `https` (or the empty scheme) might be
|
||||
// used with implementation specific semantics.
|
||||
//
|
||||
string type_url = 1;
|
||||
|
||||
// Must be a valid serialized protocol buffer of the above specified type.
|
||||
bytes value = 2;
|
||||
}
|
||||
-40
@@ -1,40 +0,0 @@
|
||||
# timewheel
|
||||
Golang实现的时间轮
|
||||
|
||||
|
||||

|
||||
|
||||
# 安装
|
||||
|
||||
```shell
|
||||
go get -u github.com/ouqiang/timewheel
|
||||
```
|
||||
|
||||
# 使用
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/ouqiang/timewheel"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// tick刻度为1秒, 3600个槽
|
||||
tw := timewheel.New(1 * time.Second, 3600)
|
||||
tw.Start()
|
||||
tw.Add(5 * time.Second, func() {
|
||||
// do something
|
||||
})
|
||||
tw.Add(10 * time.Minute, func() {
|
||||
// do something
|
||||
})
|
||||
tw.Add(35 * time.Hour, func() {
|
||||
// do something
|
||||
})
|
||||
// 停止
|
||||
tw.Stop()
|
||||
}
|
||||
```
|
||||
|
||||
-124
@@ -1,124 +0,0 @@
|
||||
package timewheel
|
||||
|
||||
import (
|
||||
"time"
|
||||
"container/list"
|
||||
)
|
||||
|
||||
// @author qiang.ou<qingqianludao@gmail.com>
|
||||
|
||||
type TimeWheel struct {
|
||||
interval time.Duration
|
||||
ticker *time.Ticker
|
||||
slots []*list.List
|
||||
currentPos int
|
||||
slotNum int
|
||||
taskChannel chan Task
|
||||
stopChannel chan bool
|
||||
}
|
||||
|
||||
|
||||
type Task struct {
|
||||
delay time.Duration
|
||||
circle int
|
||||
job Job
|
||||
}
|
||||
|
||||
type Job func()
|
||||
|
||||
func New(interval time.Duration, slotNum int) *TimeWheel {
|
||||
if interval <= 0 || slotNum <= 0 {
|
||||
return nil
|
||||
}
|
||||
tw := &TimeWheel{
|
||||
interval: interval,
|
||||
slots: make([]*list.List, slotNum),
|
||||
currentPos: 0,
|
||||
slotNum: slotNum,
|
||||
taskChannel: make(chan Task),
|
||||
stopChannel: make(chan bool),
|
||||
}
|
||||
|
||||
tw.initSlots()
|
||||
|
||||
return tw
|
||||
}
|
||||
|
||||
func (tw *TimeWheel) initSlots() {
|
||||
for i := 0; i < tw.slotNum; i++ {
|
||||
tw.slots[i] = list.New()
|
||||
}
|
||||
}
|
||||
|
||||
func (tw *TimeWheel) Start() {
|
||||
tw.ticker = time.NewTicker(tw.interval)
|
||||
go tw.start()
|
||||
}
|
||||
|
||||
func (tw *TimeWheel) Add(delay time.Duration, job Job) {
|
||||
if delay < 0 || job == nil {
|
||||
return
|
||||
}
|
||||
tw.taskChannel <- Task{delay:delay, job: job}
|
||||
}
|
||||
|
||||
func (tw *TimeWheel) Stop() {
|
||||
tw.stopChannel <- true
|
||||
}
|
||||
|
||||
func (tw *TimeWheel) start() {
|
||||
for {
|
||||
select {
|
||||
case <- tw.ticker.C:
|
||||
tw.tickHandler()
|
||||
case task := <- tw.taskChannel:
|
||||
tw.addTask(&task)
|
||||
case <- tw.stopChannel:
|
||||
tw.ticker.Stop()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (tw *TimeWheel) tickHandler() {
|
||||
l := tw.slots[tw.currentPos]
|
||||
tw.scanAndRunTask(l)
|
||||
if tw.currentPos == tw.slotNum - 1 {
|
||||
tw.currentPos = 0
|
||||
} else {
|
||||
tw.currentPos++
|
||||
}
|
||||
}
|
||||
|
||||
func (tw *TimeWheel) scanAndRunTask(l *list.List) {
|
||||
for e := l.Front(); e != nil; {
|
||||
task := e.Value.(*Task)
|
||||
if task.circle > 0 {
|
||||
task.circle--
|
||||
e = e.Next()
|
||||
continue
|
||||
}
|
||||
|
||||
go task.job()
|
||||
next := e.Next()
|
||||
l.Remove(e)
|
||||
e = next
|
||||
}
|
||||
}
|
||||
|
||||
func (tw *TimeWheel) addTask(task *Task) {
|
||||
pos, circle := tw.getPositionAndCircle(task.delay)
|
||||
task.circle = circle
|
||||
|
||||
tw.slots[pos].PushBack(task)
|
||||
}
|
||||
|
||||
func (tw *TimeWheel) getPositionAndCircle(d time.Duration) (pos int, circle int) {
|
||||
delaySeconds := int(d.Seconds())
|
||||
intervalSeconds := int(tw.interval.Seconds())
|
||||
circle = int(delaySeconds / intervalSeconds / tw.slotNum)
|
||||
pos = int(tw.currentPos + delaySeconds / intervalSeconds) % tw.slotNum
|
||||
|
||||
|
||||
return
|
||||
}
|
||||
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 12 KiB |
Generated
Vendored
+1
-1
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017 qiang.ou
|
||||
Copyright (c) 2016 nestgo
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
# pool
|
||||
[](http://godoc.org/github.com/silenceper/pool)
|
||||
|
||||
Golang 实现的连接池
|
||||
|
||||
|
||||
##功能:
|
||||
|
||||
- 连接池中连接类型为`interface{}`,使得更加通用
|
||||
- 链接的最大空闲时间,超时的链接将关闭丢弃,可避免空闲时链接自动失效问题
|
||||
- 使用channel处理池中的链接,高效
|
||||
|
||||
## 基本用法
|
||||
|
||||
```go
|
||||
|
||||
//factory 创建连接的方法
|
||||
factory := func() (interface{}, error) { return net.Dial("tcp", "127.0.0.1:4000") }
|
||||
|
||||
//close 关闭链接的方法
|
||||
close := func(v interface{}) error { return v.(net.Conn).Close() }
|
||||
|
||||
//创建一个连接池: 初始化5,最大链接30
|
||||
poolConfig := &pool.PoolConfig{
|
||||
InitialCap: 5,
|
||||
MaxCap: 30,
|
||||
Factory: factory,
|
||||
Close: close,
|
||||
//链接最大空闲时间,超过该时间的链接 将会关闭,可避免空闲时链接EOF,自动失效的问题
|
||||
IdleTimeout: 15 * time.Second,
|
||||
}
|
||||
p, err := pool.NewChannelPool(poolConfig)
|
||||
if err != nil {
|
||||
fmt.Println("err=", err)
|
||||
}
|
||||
|
||||
//从连接池中取得一个链接
|
||||
v, err := p.Get()
|
||||
|
||||
//do something
|
||||
//conn=v.(net.Conn)
|
||||
|
||||
//将链接放回连接池中
|
||||
p.Put(v)
|
||||
|
||||
//释放连接池中的所有链接
|
||||
p.Release()
|
||||
|
||||
//查看当前链接中的数量
|
||||
current := p.Len()
|
||||
|
||||
|
||||
```
|
||||
|
||||
|
||||
####注:
|
||||
该连接池参考 [https://github.com/fatih/pool](https://github.com/fatih/pool) 实现,改变以及增加原有的一些功能。
|
||||
|
||||
|
||||
## License
|
||||
|
||||
The MIT License (MIT) - see LICENSE for more details
|
||||
+156
@@ -0,0 +1,156 @@
|
||||
package pool
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
//PoolConfig 连接池相关配置
|
||||
type PoolConfig struct {
|
||||
//连接池中拥有的最小连接数
|
||||
InitialCap int
|
||||
//连接池中拥有的最大的连接数
|
||||
MaxCap int
|
||||
//生成连接的方法
|
||||
Factory func() (interface{}, error)
|
||||
//关闭链接的方法
|
||||
Close func(interface{}) error
|
||||
//链接最大空闲时间,超过该事件则将失效
|
||||
IdleTimeout time.Duration
|
||||
}
|
||||
|
||||
//channelPool 存放链接信息
|
||||
type channelPool struct {
|
||||
mu sync.Mutex
|
||||
conns chan *idleConn
|
||||
factory func() (interface{}, error)
|
||||
close func(interface{}) error
|
||||
idleTimeout time.Duration
|
||||
}
|
||||
|
||||
type idleConn struct {
|
||||
conn interface{}
|
||||
t time.Time
|
||||
}
|
||||
|
||||
//NewChannelPool 初始化链接
|
||||
func NewChannelPool(poolConfig *PoolConfig) (Pool, error) {
|
||||
if poolConfig.InitialCap < 0 || poolConfig.MaxCap <= 0 || poolConfig.InitialCap > poolConfig.MaxCap {
|
||||
return nil, errors.New("invalid capacity settings")
|
||||
}
|
||||
|
||||
c := &channelPool{
|
||||
conns: make(chan *idleConn, poolConfig.MaxCap),
|
||||
factory: poolConfig.Factory,
|
||||
close: poolConfig.Close,
|
||||
idleTimeout: poolConfig.IdleTimeout,
|
||||
}
|
||||
|
||||
for i := 0; i < poolConfig.InitialCap; i++ {
|
||||
conn, err := c.factory()
|
||||
if err != nil {
|
||||
c.Release()
|
||||
return nil, fmt.Errorf("factory is not able to fill the pool: %s", err)
|
||||
}
|
||||
c.conns <- &idleConn{conn: conn, t: time.Now()}
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
//getConns 获取所有连接
|
||||
func (c *channelPool) getConns() chan *idleConn {
|
||||
c.mu.Lock()
|
||||
conns := c.conns
|
||||
c.mu.Unlock()
|
||||
return conns
|
||||
}
|
||||
|
||||
//Get 从pool中取一个连接
|
||||
func (c *channelPool) Get() (interface{}, error) {
|
||||
conns := c.getConns()
|
||||
if conns == nil {
|
||||
return nil, ErrClosed
|
||||
}
|
||||
for {
|
||||
select {
|
||||
case wrapConn := <-conns:
|
||||
if wrapConn == nil {
|
||||
return nil, ErrClosed
|
||||
}
|
||||
//判断是否超时,超时则丢弃
|
||||
if timeout := c.idleTimeout; timeout > 0 {
|
||||
if wrapConn.t.Add(timeout).Before(time.Now()) {
|
||||
//丢弃并关闭该链接
|
||||
c.Close(wrapConn.conn)
|
||||
continue
|
||||
}
|
||||
}
|
||||
return wrapConn.conn, nil
|
||||
default:
|
||||
conn, err := c.factory()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Put 将连接放回pool中
|
||||
func (c *channelPool) Put(conn interface{}) error {
|
||||
if conn == nil {
|
||||
return errors.New("connection is nil. rejecting")
|
||||
}
|
||||
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
if c.conns == nil {
|
||||
return c.Close(conn)
|
||||
}
|
||||
|
||||
select {
|
||||
case c.conns <- &idleConn{conn: conn, t: time.Now()}:
|
||||
return nil
|
||||
default:
|
||||
//连接池已满,直接关闭该链接
|
||||
return c.Close(conn)
|
||||
}
|
||||
}
|
||||
|
||||
//Close 关闭单条连接
|
||||
func (c *channelPool) Close(conn interface{}) error {
|
||||
if conn == nil {
|
||||
return errors.New("connection is nil. rejecting")
|
||||
}
|
||||
return c.close(conn)
|
||||
}
|
||||
|
||||
//Release 释放连接池中所有链接
|
||||
func (c *channelPool) Release() {
|
||||
c.mu.Lock()
|
||||
conns := c.conns
|
||||
c.conns = nil
|
||||
c.factory = nil
|
||||
closeFun := c.close
|
||||
c.close = nil
|
||||
c.mu.Unlock()
|
||||
|
||||
if conns == nil {
|
||||
return
|
||||
}
|
||||
|
||||
close(conns)
|
||||
for wrapConn := range conns {
|
||||
closeFun(wrapConn.conn)
|
||||
}
|
||||
}
|
||||
|
||||
//Len 连接池中已有的连接
|
||||
func (c *channelPool) Len() int {
|
||||
return len(c.getConns())
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package pool
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
//ErrClosed 连接池已经关闭Error
|
||||
ErrClosed = errors.New("pool is closed")
|
||||
)
|
||||
|
||||
//Pool 基本方法
|
||||
type Pool interface {
|
||||
Get() (interface{}, error)
|
||||
|
||||
Put(interface{}) error
|
||||
|
||||
Close(interface{}) error
|
||||
|
||||
Release()
|
||||
|
||||
Len() int
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
#
|
||||
# This Dockerfile builds a recent curl with HTTP/2 client support, using
|
||||
# a recent nghttp2 build.
|
||||
#
|
||||
# See the Makefile for how to tag it. If Docker and that image is found, the
|
||||
# Go tests use this curl binary for integration tests.
|
||||
#
|
||||
|
||||
FROM ubuntu:trusty
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get upgrade -y && \
|
||||
apt-get install -y git-core build-essential wget
|
||||
|
||||
RUN apt-get install -y --no-install-recommends \
|
||||
autotools-dev libtool pkg-config zlib1g-dev \
|
||||
libcunit1-dev libssl-dev libxml2-dev libevent-dev \
|
||||
automake autoconf
|
||||
|
||||
# The list of packages nghttp2 recommends for h2load:
|
||||
RUN apt-get install -y --no-install-recommends make binutils \
|
||||
autoconf automake autotools-dev \
|
||||
libtool pkg-config zlib1g-dev libcunit1-dev libssl-dev libxml2-dev \
|
||||
libev-dev libevent-dev libjansson-dev libjemalloc-dev \
|
||||
cython python3.4-dev python-setuptools
|
||||
|
||||
# Note: setting NGHTTP2_VER before the git clone, so an old git clone isn't cached:
|
||||
ENV NGHTTP2_VER 895da9a
|
||||
RUN cd /root && git clone https://github.com/tatsuhiro-t/nghttp2.git
|
||||
|
||||
WORKDIR /root/nghttp2
|
||||
RUN git reset --hard $NGHTTP2_VER
|
||||
RUN autoreconf -i
|
||||
RUN automake
|
||||
RUN autoconf
|
||||
RUN ./configure
|
||||
RUN make
|
||||
RUN make install
|
||||
|
||||
WORKDIR /root
|
||||
RUN wget http://curl.haxx.se/download/curl-7.45.0.tar.gz
|
||||
RUN tar -zxvf curl-7.45.0.tar.gz
|
||||
WORKDIR /root/curl-7.45.0
|
||||
RUN ./configure --with-ssl --with-nghttp2=/usr/local
|
||||
RUN make
|
||||
RUN make install
|
||||
RUN ldconfig
|
||||
|
||||
CMD ["-h"]
|
||||
ENTRYPOINT ["/usr/local/bin/curl"]
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
curlimage:
|
||||
docker build -t gohttp2/curl .
|
||||
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
This is a work-in-progress HTTP/2 implementation for Go.
|
||||
|
||||
It will eventually live in the Go standard library and won't require
|
||||
any changes to your code to use. It will just be automatic.
|
||||
|
||||
Status:
|
||||
|
||||
* The server support is pretty good. A few things are missing
|
||||
but are being worked on.
|
||||
* The client work has just started but shares a lot of code
|
||||
is coming along much quicker.
|
||||
|
||||
Docs are at https://godoc.org/golang.org/x/net/http2
|
||||
|
||||
Demo test server at https://http2.golang.org/
|
||||
|
||||
Help & bug reports welcome!
|
||||
|
||||
Contributing: https://golang.org/doc/contribute.html
|
||||
Bugs: https://golang.org/issue/new?title=x/net/http2:+
|
||||
+641
@@ -0,0 +1,641 @@
|
||||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package http2
|
||||
|
||||
// A list of the possible cipher suite ids. Taken from
|
||||
// http://www.iana.org/assignments/tls-parameters/tls-parameters.txt
|
||||
|
||||
const (
|
||||
cipher_TLS_NULL_WITH_NULL_NULL uint16 = 0x0000
|
||||
cipher_TLS_RSA_WITH_NULL_MD5 uint16 = 0x0001
|
||||
cipher_TLS_RSA_WITH_NULL_SHA uint16 = 0x0002
|
||||
cipher_TLS_RSA_EXPORT_WITH_RC4_40_MD5 uint16 = 0x0003
|
||||
cipher_TLS_RSA_WITH_RC4_128_MD5 uint16 = 0x0004
|
||||
cipher_TLS_RSA_WITH_RC4_128_SHA uint16 = 0x0005
|
||||
cipher_TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5 uint16 = 0x0006
|
||||
cipher_TLS_RSA_WITH_IDEA_CBC_SHA uint16 = 0x0007
|
||||
cipher_TLS_RSA_EXPORT_WITH_DES40_CBC_SHA uint16 = 0x0008
|
||||
cipher_TLS_RSA_WITH_DES_CBC_SHA uint16 = 0x0009
|
||||
cipher_TLS_RSA_WITH_3DES_EDE_CBC_SHA uint16 = 0x000A
|
||||
cipher_TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA uint16 = 0x000B
|
||||
cipher_TLS_DH_DSS_WITH_DES_CBC_SHA uint16 = 0x000C
|
||||
cipher_TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA uint16 = 0x000D
|
||||
cipher_TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA uint16 = 0x000E
|
||||
cipher_TLS_DH_RSA_WITH_DES_CBC_SHA uint16 = 0x000F
|
||||
cipher_TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA uint16 = 0x0010
|
||||
cipher_TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA uint16 = 0x0011
|
||||
cipher_TLS_DHE_DSS_WITH_DES_CBC_SHA uint16 = 0x0012
|
||||
cipher_TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA uint16 = 0x0013
|
||||
cipher_TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA uint16 = 0x0014
|
||||
cipher_TLS_DHE_RSA_WITH_DES_CBC_SHA uint16 = 0x0015
|
||||
cipher_TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA uint16 = 0x0016
|
||||
cipher_TLS_DH_anon_EXPORT_WITH_RC4_40_MD5 uint16 = 0x0017
|
||||
cipher_TLS_DH_anon_WITH_RC4_128_MD5 uint16 = 0x0018
|
||||
cipher_TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA uint16 = 0x0019
|
||||
cipher_TLS_DH_anon_WITH_DES_CBC_SHA uint16 = 0x001A
|
||||
cipher_TLS_DH_anon_WITH_3DES_EDE_CBC_SHA uint16 = 0x001B
|
||||
// Reserved uint16 = 0x001C-1D
|
||||
cipher_TLS_KRB5_WITH_DES_CBC_SHA uint16 = 0x001E
|
||||
cipher_TLS_KRB5_WITH_3DES_EDE_CBC_SHA uint16 = 0x001F
|
||||
cipher_TLS_KRB5_WITH_RC4_128_SHA uint16 = 0x0020
|
||||
cipher_TLS_KRB5_WITH_IDEA_CBC_SHA uint16 = 0x0021
|
||||
cipher_TLS_KRB5_WITH_DES_CBC_MD5 uint16 = 0x0022
|
||||
cipher_TLS_KRB5_WITH_3DES_EDE_CBC_MD5 uint16 = 0x0023
|
||||
cipher_TLS_KRB5_WITH_RC4_128_MD5 uint16 = 0x0024
|
||||
cipher_TLS_KRB5_WITH_IDEA_CBC_MD5 uint16 = 0x0025
|
||||
cipher_TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA uint16 = 0x0026
|
||||
cipher_TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA uint16 = 0x0027
|
||||
cipher_TLS_KRB5_EXPORT_WITH_RC4_40_SHA uint16 = 0x0028
|
||||
cipher_TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5 uint16 = 0x0029
|
||||
cipher_TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5 uint16 = 0x002A
|
||||
cipher_TLS_KRB5_EXPORT_WITH_RC4_40_MD5 uint16 = 0x002B
|
||||
cipher_TLS_PSK_WITH_NULL_SHA uint16 = 0x002C
|
||||
cipher_TLS_DHE_PSK_WITH_NULL_SHA uint16 = 0x002D
|
||||
cipher_TLS_RSA_PSK_WITH_NULL_SHA uint16 = 0x002E
|
||||
cipher_TLS_RSA_WITH_AES_128_CBC_SHA uint16 = 0x002F
|
||||
cipher_TLS_DH_DSS_WITH_AES_128_CBC_SHA uint16 = 0x0030
|
||||
cipher_TLS_DH_RSA_WITH_AES_128_CBC_SHA uint16 = 0x0031
|
||||
cipher_TLS_DHE_DSS_WITH_AES_128_CBC_SHA uint16 = 0x0032
|
||||
cipher_TLS_DHE_RSA_WITH_AES_128_CBC_SHA uint16 = 0x0033
|
||||
cipher_TLS_DH_anon_WITH_AES_128_CBC_SHA uint16 = 0x0034
|
||||
cipher_TLS_RSA_WITH_AES_256_CBC_SHA uint16 = 0x0035
|
||||
cipher_TLS_DH_DSS_WITH_AES_256_CBC_SHA uint16 = 0x0036
|
||||
cipher_TLS_DH_RSA_WITH_AES_256_CBC_SHA uint16 = 0x0037
|
||||
cipher_TLS_DHE_DSS_WITH_AES_256_CBC_SHA uint16 = 0x0038
|
||||
cipher_TLS_DHE_RSA_WITH_AES_256_CBC_SHA uint16 = 0x0039
|
||||
cipher_TLS_DH_anon_WITH_AES_256_CBC_SHA uint16 = 0x003A
|
||||
cipher_TLS_RSA_WITH_NULL_SHA256 uint16 = 0x003B
|
||||
cipher_TLS_RSA_WITH_AES_128_CBC_SHA256 uint16 = 0x003C
|
||||
cipher_TLS_RSA_WITH_AES_256_CBC_SHA256 uint16 = 0x003D
|
||||
cipher_TLS_DH_DSS_WITH_AES_128_CBC_SHA256 uint16 = 0x003E
|
||||
cipher_TLS_DH_RSA_WITH_AES_128_CBC_SHA256 uint16 = 0x003F
|
||||
cipher_TLS_DHE_DSS_WITH_AES_128_CBC_SHA256 uint16 = 0x0040
|
||||
cipher_TLS_RSA_WITH_CAMELLIA_128_CBC_SHA uint16 = 0x0041
|
||||
cipher_TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA uint16 = 0x0042
|
||||
cipher_TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA uint16 = 0x0043
|
||||
cipher_TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA uint16 = 0x0044
|
||||
cipher_TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA uint16 = 0x0045
|
||||
cipher_TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA uint16 = 0x0046
|
||||
// Reserved uint16 = 0x0047-4F
|
||||
// Reserved uint16 = 0x0050-58
|
||||
// Reserved uint16 = 0x0059-5C
|
||||
// Unassigned uint16 = 0x005D-5F
|
||||
// Reserved uint16 = 0x0060-66
|
||||
cipher_TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 uint16 = 0x0067
|
||||
cipher_TLS_DH_DSS_WITH_AES_256_CBC_SHA256 uint16 = 0x0068
|
||||
cipher_TLS_DH_RSA_WITH_AES_256_CBC_SHA256 uint16 = 0x0069
|
||||
cipher_TLS_DHE_DSS_WITH_AES_256_CBC_SHA256 uint16 = 0x006A
|
||||
cipher_TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 uint16 = 0x006B
|
||||
cipher_TLS_DH_anon_WITH_AES_128_CBC_SHA256 uint16 = 0x006C
|
||||
cipher_TLS_DH_anon_WITH_AES_256_CBC_SHA256 uint16 = 0x006D
|
||||
// Unassigned uint16 = 0x006E-83
|
||||
cipher_TLS_RSA_WITH_CAMELLIA_256_CBC_SHA uint16 = 0x0084
|
||||
cipher_TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA uint16 = 0x0085
|
||||
cipher_TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA uint16 = 0x0086
|
||||
cipher_TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA uint16 = 0x0087
|
||||
cipher_TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA uint16 = 0x0088
|
||||
cipher_TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA uint16 = 0x0089
|
||||
cipher_TLS_PSK_WITH_RC4_128_SHA uint16 = 0x008A
|
||||
cipher_TLS_PSK_WITH_3DES_EDE_CBC_SHA uint16 = 0x008B
|
||||
cipher_TLS_PSK_WITH_AES_128_CBC_SHA uint16 = 0x008C
|
||||
cipher_TLS_PSK_WITH_AES_256_CBC_SHA uint16 = 0x008D
|
||||
cipher_TLS_DHE_PSK_WITH_RC4_128_SHA uint16 = 0x008E
|
||||
cipher_TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA uint16 = 0x008F
|
||||
cipher_TLS_DHE_PSK_WITH_AES_128_CBC_SHA uint16 = 0x0090
|
||||
cipher_TLS_DHE_PSK_WITH_AES_256_CBC_SHA uint16 = 0x0091
|
||||
cipher_TLS_RSA_PSK_WITH_RC4_128_SHA uint16 = 0x0092
|
||||
cipher_TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA uint16 = 0x0093
|
||||
cipher_TLS_RSA_PSK_WITH_AES_128_CBC_SHA uint16 = 0x0094
|
||||
cipher_TLS_RSA_PSK_WITH_AES_256_CBC_SHA uint16 = 0x0095
|
||||
cipher_TLS_RSA_WITH_SEED_CBC_SHA uint16 = 0x0096
|
||||
cipher_TLS_DH_DSS_WITH_SEED_CBC_SHA uint16 = 0x0097
|
||||
cipher_TLS_DH_RSA_WITH_SEED_CBC_SHA uint16 = 0x0098
|
||||
cipher_TLS_DHE_DSS_WITH_SEED_CBC_SHA uint16 = 0x0099
|
||||
cipher_TLS_DHE_RSA_WITH_SEED_CBC_SHA uint16 = 0x009A
|
||||
cipher_TLS_DH_anon_WITH_SEED_CBC_SHA uint16 = 0x009B
|
||||
cipher_TLS_RSA_WITH_AES_128_GCM_SHA256 uint16 = 0x009C
|
||||
cipher_TLS_RSA_WITH_AES_256_GCM_SHA384 uint16 = 0x009D
|
||||
cipher_TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 uint16 = 0x009E
|
||||
cipher_TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 uint16 = 0x009F
|
||||
cipher_TLS_DH_RSA_WITH_AES_128_GCM_SHA256 uint16 = 0x00A0
|
||||
cipher_TLS_DH_RSA_WITH_AES_256_GCM_SHA384 uint16 = 0x00A1
|
||||
cipher_TLS_DHE_DSS_WITH_AES_128_GCM_SHA256 uint16 = 0x00A2
|
||||
cipher_TLS_DHE_DSS_WITH_AES_256_GCM_SHA384 uint16 = 0x00A3
|
||||
cipher_TLS_DH_DSS_WITH_AES_128_GCM_SHA256 uint16 = 0x00A4
|
||||
cipher_TLS_DH_DSS_WITH_AES_256_GCM_SHA384 uint16 = 0x00A5
|
||||
cipher_TLS_DH_anon_WITH_AES_128_GCM_SHA256 uint16 = 0x00A6
|
||||
cipher_TLS_DH_anon_WITH_AES_256_GCM_SHA384 uint16 = 0x00A7
|
||||
cipher_TLS_PSK_WITH_AES_128_GCM_SHA256 uint16 = 0x00A8
|
||||
cipher_TLS_PSK_WITH_AES_256_GCM_SHA384 uint16 = 0x00A9
|
||||
cipher_TLS_DHE_PSK_WITH_AES_128_GCM_SHA256 uint16 = 0x00AA
|
||||
cipher_TLS_DHE_PSK_WITH_AES_256_GCM_SHA384 uint16 = 0x00AB
|
||||
cipher_TLS_RSA_PSK_WITH_AES_128_GCM_SHA256 uint16 = 0x00AC
|
||||
cipher_TLS_RSA_PSK_WITH_AES_256_GCM_SHA384 uint16 = 0x00AD
|
||||
cipher_TLS_PSK_WITH_AES_128_CBC_SHA256 uint16 = 0x00AE
|
||||
cipher_TLS_PSK_WITH_AES_256_CBC_SHA384 uint16 = 0x00AF
|
||||
cipher_TLS_PSK_WITH_NULL_SHA256 uint16 = 0x00B0
|
||||
cipher_TLS_PSK_WITH_NULL_SHA384 uint16 = 0x00B1
|
||||
cipher_TLS_DHE_PSK_WITH_AES_128_CBC_SHA256 uint16 = 0x00B2
|
||||
cipher_TLS_DHE_PSK_WITH_AES_256_CBC_SHA384 uint16 = 0x00B3
|
||||
cipher_TLS_DHE_PSK_WITH_NULL_SHA256 uint16 = 0x00B4
|
||||
cipher_TLS_DHE_PSK_WITH_NULL_SHA384 uint16 = 0x00B5
|
||||
cipher_TLS_RSA_PSK_WITH_AES_128_CBC_SHA256 uint16 = 0x00B6
|
||||
cipher_TLS_RSA_PSK_WITH_AES_256_CBC_SHA384 uint16 = 0x00B7
|
||||
cipher_TLS_RSA_PSK_WITH_NULL_SHA256 uint16 = 0x00B8
|
||||
cipher_TLS_RSA_PSK_WITH_NULL_SHA384 uint16 = 0x00B9
|
||||
cipher_TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256 uint16 = 0x00BA
|
||||
cipher_TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256 uint16 = 0x00BB
|
||||
cipher_TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256 uint16 = 0x00BC
|
||||
cipher_TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256 uint16 = 0x00BD
|
||||
cipher_TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256 uint16 = 0x00BE
|
||||
cipher_TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256 uint16 = 0x00BF
|
||||
cipher_TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256 uint16 = 0x00C0
|
||||
cipher_TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256 uint16 = 0x00C1
|
||||
cipher_TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256 uint16 = 0x00C2
|
||||
cipher_TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256 uint16 = 0x00C3
|
||||
cipher_TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256 uint16 = 0x00C4
|
||||
cipher_TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256 uint16 = 0x00C5
|
||||
// Unassigned uint16 = 0x00C6-FE
|
||||
cipher_TLS_EMPTY_RENEGOTIATION_INFO_SCSV uint16 = 0x00FF
|
||||
// Unassigned uint16 = 0x01-55,*
|
||||
cipher_TLS_FALLBACK_SCSV uint16 = 0x5600
|
||||
// Unassigned uint16 = 0x5601 - 0xC000
|
||||
cipher_TLS_ECDH_ECDSA_WITH_NULL_SHA uint16 = 0xC001
|
||||
cipher_TLS_ECDH_ECDSA_WITH_RC4_128_SHA uint16 = 0xC002
|
||||
cipher_TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA uint16 = 0xC003
|
||||
cipher_TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA uint16 = 0xC004
|
||||
cipher_TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA uint16 = 0xC005
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_NULL_SHA uint16 = 0xC006
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_RC4_128_SHA uint16 = 0xC007
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA uint16 = 0xC008
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA uint16 = 0xC009
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA uint16 = 0xC00A
|
||||
cipher_TLS_ECDH_RSA_WITH_NULL_SHA uint16 = 0xC00B
|
||||
cipher_TLS_ECDH_RSA_WITH_RC4_128_SHA uint16 = 0xC00C
|
||||
cipher_TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA uint16 = 0xC00D
|
||||
cipher_TLS_ECDH_RSA_WITH_AES_128_CBC_SHA uint16 = 0xC00E
|
||||
cipher_TLS_ECDH_RSA_WITH_AES_256_CBC_SHA uint16 = 0xC00F
|
||||
cipher_TLS_ECDHE_RSA_WITH_NULL_SHA uint16 = 0xC010
|
||||
cipher_TLS_ECDHE_RSA_WITH_RC4_128_SHA uint16 = 0xC011
|
||||
cipher_TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA uint16 = 0xC012
|
||||
cipher_TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA uint16 = 0xC013
|
||||
cipher_TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA uint16 = 0xC014
|
||||
cipher_TLS_ECDH_anon_WITH_NULL_SHA uint16 = 0xC015
|
||||
cipher_TLS_ECDH_anon_WITH_RC4_128_SHA uint16 = 0xC016
|
||||
cipher_TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA uint16 = 0xC017
|
||||
cipher_TLS_ECDH_anon_WITH_AES_128_CBC_SHA uint16 = 0xC018
|
||||
cipher_TLS_ECDH_anon_WITH_AES_256_CBC_SHA uint16 = 0xC019
|
||||
cipher_TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA uint16 = 0xC01A
|
||||
cipher_TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA uint16 = 0xC01B
|
||||
cipher_TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA uint16 = 0xC01C
|
||||
cipher_TLS_SRP_SHA_WITH_AES_128_CBC_SHA uint16 = 0xC01D
|
||||
cipher_TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA uint16 = 0xC01E
|
||||
cipher_TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA uint16 = 0xC01F
|
||||
cipher_TLS_SRP_SHA_WITH_AES_256_CBC_SHA uint16 = 0xC020
|
||||
cipher_TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA uint16 = 0xC021
|
||||
cipher_TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA uint16 = 0xC022
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 uint16 = 0xC023
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 uint16 = 0xC024
|
||||
cipher_TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256 uint16 = 0xC025
|
||||
cipher_TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384 uint16 = 0xC026
|
||||
cipher_TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 uint16 = 0xC027
|
||||
cipher_TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 uint16 = 0xC028
|
||||
cipher_TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256 uint16 = 0xC029
|
||||
cipher_TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384 uint16 = 0xC02A
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 uint16 = 0xC02B
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 uint16 = 0xC02C
|
||||
cipher_TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256 uint16 = 0xC02D
|
||||
cipher_TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384 uint16 = 0xC02E
|
||||
cipher_TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 uint16 = 0xC02F
|
||||
cipher_TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 uint16 = 0xC030
|
||||
cipher_TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256 uint16 = 0xC031
|
||||
cipher_TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384 uint16 = 0xC032
|
||||
cipher_TLS_ECDHE_PSK_WITH_RC4_128_SHA uint16 = 0xC033
|
||||
cipher_TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA uint16 = 0xC034
|
||||
cipher_TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA uint16 = 0xC035
|
||||
cipher_TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA uint16 = 0xC036
|
||||
cipher_TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256 uint16 = 0xC037
|
||||
cipher_TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384 uint16 = 0xC038
|
||||
cipher_TLS_ECDHE_PSK_WITH_NULL_SHA uint16 = 0xC039
|
||||
cipher_TLS_ECDHE_PSK_WITH_NULL_SHA256 uint16 = 0xC03A
|
||||
cipher_TLS_ECDHE_PSK_WITH_NULL_SHA384 uint16 = 0xC03B
|
||||
cipher_TLS_RSA_WITH_ARIA_128_CBC_SHA256 uint16 = 0xC03C
|
||||
cipher_TLS_RSA_WITH_ARIA_256_CBC_SHA384 uint16 = 0xC03D
|
||||
cipher_TLS_DH_DSS_WITH_ARIA_128_CBC_SHA256 uint16 = 0xC03E
|
||||
cipher_TLS_DH_DSS_WITH_ARIA_256_CBC_SHA384 uint16 = 0xC03F
|
||||
cipher_TLS_DH_RSA_WITH_ARIA_128_CBC_SHA256 uint16 = 0xC040
|
||||
cipher_TLS_DH_RSA_WITH_ARIA_256_CBC_SHA384 uint16 = 0xC041
|
||||
cipher_TLS_DHE_DSS_WITH_ARIA_128_CBC_SHA256 uint16 = 0xC042
|
||||
cipher_TLS_DHE_DSS_WITH_ARIA_256_CBC_SHA384 uint16 = 0xC043
|
||||
cipher_TLS_DHE_RSA_WITH_ARIA_128_CBC_SHA256 uint16 = 0xC044
|
||||
cipher_TLS_DHE_RSA_WITH_ARIA_256_CBC_SHA384 uint16 = 0xC045
|
||||
cipher_TLS_DH_anon_WITH_ARIA_128_CBC_SHA256 uint16 = 0xC046
|
||||
cipher_TLS_DH_anon_WITH_ARIA_256_CBC_SHA384 uint16 = 0xC047
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_ARIA_128_CBC_SHA256 uint16 = 0xC048
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_ARIA_256_CBC_SHA384 uint16 = 0xC049
|
||||
cipher_TLS_ECDH_ECDSA_WITH_ARIA_128_CBC_SHA256 uint16 = 0xC04A
|
||||
cipher_TLS_ECDH_ECDSA_WITH_ARIA_256_CBC_SHA384 uint16 = 0xC04B
|
||||
cipher_TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256 uint16 = 0xC04C
|
||||
cipher_TLS_ECDHE_RSA_WITH_ARIA_256_CBC_SHA384 uint16 = 0xC04D
|
||||
cipher_TLS_ECDH_RSA_WITH_ARIA_128_CBC_SHA256 uint16 = 0xC04E
|
||||
cipher_TLS_ECDH_RSA_WITH_ARIA_256_CBC_SHA384 uint16 = 0xC04F
|
||||
cipher_TLS_RSA_WITH_ARIA_128_GCM_SHA256 uint16 = 0xC050
|
||||
cipher_TLS_RSA_WITH_ARIA_256_GCM_SHA384 uint16 = 0xC051
|
||||
cipher_TLS_DHE_RSA_WITH_ARIA_128_GCM_SHA256 uint16 = 0xC052
|
||||
cipher_TLS_DHE_RSA_WITH_ARIA_256_GCM_SHA384 uint16 = 0xC053
|
||||
cipher_TLS_DH_RSA_WITH_ARIA_128_GCM_SHA256 uint16 = 0xC054
|
||||
cipher_TLS_DH_RSA_WITH_ARIA_256_GCM_SHA384 uint16 = 0xC055
|
||||
cipher_TLS_DHE_DSS_WITH_ARIA_128_GCM_SHA256 uint16 = 0xC056
|
||||
cipher_TLS_DHE_DSS_WITH_ARIA_256_GCM_SHA384 uint16 = 0xC057
|
||||
cipher_TLS_DH_DSS_WITH_ARIA_128_GCM_SHA256 uint16 = 0xC058
|
||||
cipher_TLS_DH_DSS_WITH_ARIA_256_GCM_SHA384 uint16 = 0xC059
|
||||
cipher_TLS_DH_anon_WITH_ARIA_128_GCM_SHA256 uint16 = 0xC05A
|
||||
cipher_TLS_DH_anon_WITH_ARIA_256_GCM_SHA384 uint16 = 0xC05B
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_ARIA_128_GCM_SHA256 uint16 = 0xC05C
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_ARIA_256_GCM_SHA384 uint16 = 0xC05D
|
||||
cipher_TLS_ECDH_ECDSA_WITH_ARIA_128_GCM_SHA256 uint16 = 0xC05E
|
||||
cipher_TLS_ECDH_ECDSA_WITH_ARIA_256_GCM_SHA384 uint16 = 0xC05F
|
||||
cipher_TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256 uint16 = 0xC060
|
||||
cipher_TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384 uint16 = 0xC061
|
||||
cipher_TLS_ECDH_RSA_WITH_ARIA_128_GCM_SHA256 uint16 = 0xC062
|
||||
cipher_TLS_ECDH_RSA_WITH_ARIA_256_GCM_SHA384 uint16 = 0xC063
|
||||
cipher_TLS_PSK_WITH_ARIA_128_CBC_SHA256 uint16 = 0xC064
|
||||
cipher_TLS_PSK_WITH_ARIA_256_CBC_SHA384 uint16 = 0xC065
|
||||
cipher_TLS_DHE_PSK_WITH_ARIA_128_CBC_SHA256 uint16 = 0xC066
|
||||
cipher_TLS_DHE_PSK_WITH_ARIA_256_CBC_SHA384 uint16 = 0xC067
|
||||
cipher_TLS_RSA_PSK_WITH_ARIA_128_CBC_SHA256 uint16 = 0xC068
|
||||
cipher_TLS_RSA_PSK_WITH_ARIA_256_CBC_SHA384 uint16 = 0xC069
|
||||
cipher_TLS_PSK_WITH_ARIA_128_GCM_SHA256 uint16 = 0xC06A
|
||||
cipher_TLS_PSK_WITH_ARIA_256_GCM_SHA384 uint16 = 0xC06B
|
||||
cipher_TLS_DHE_PSK_WITH_ARIA_128_GCM_SHA256 uint16 = 0xC06C
|
||||
cipher_TLS_DHE_PSK_WITH_ARIA_256_GCM_SHA384 uint16 = 0xC06D
|
||||
cipher_TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256 uint16 = 0xC06E
|
||||
cipher_TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384 uint16 = 0xC06F
|
||||
cipher_TLS_ECDHE_PSK_WITH_ARIA_128_CBC_SHA256 uint16 = 0xC070
|
||||
cipher_TLS_ECDHE_PSK_WITH_ARIA_256_CBC_SHA384 uint16 = 0xC071
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256 uint16 = 0xC072
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384 uint16 = 0xC073
|
||||
cipher_TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256 uint16 = 0xC074
|
||||
cipher_TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384 uint16 = 0xC075
|
||||
cipher_TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256 uint16 = 0xC076
|
||||
cipher_TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384 uint16 = 0xC077
|
||||
cipher_TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256 uint16 = 0xC078
|
||||
cipher_TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384 uint16 = 0xC079
|
||||
cipher_TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256 uint16 = 0xC07A
|
||||
cipher_TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384 uint16 = 0xC07B
|
||||
cipher_TLS_DHE_RSA_WITH_CAMELLIA_128_GCM_SHA256 uint16 = 0xC07C
|
||||
cipher_TLS_DHE_RSA_WITH_CAMELLIA_256_GCM_SHA384 uint16 = 0xC07D
|
||||
cipher_TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256 uint16 = 0xC07E
|
||||
cipher_TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384 uint16 = 0xC07F
|
||||
cipher_TLS_DHE_DSS_WITH_CAMELLIA_128_GCM_SHA256 uint16 = 0xC080
|
||||
cipher_TLS_DHE_DSS_WITH_CAMELLIA_256_GCM_SHA384 uint16 = 0xC081
|
||||
cipher_TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256 uint16 = 0xC082
|
||||
cipher_TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384 uint16 = 0xC083
|
||||
cipher_TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256 uint16 = 0xC084
|
||||
cipher_TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384 uint16 = 0xC085
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_GCM_SHA256 uint16 = 0xC086
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_GCM_SHA384 uint16 = 0xC087
|
||||
cipher_TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256 uint16 = 0xC088
|
||||
cipher_TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384 uint16 = 0xC089
|
||||
cipher_TLS_ECDHE_RSA_WITH_CAMELLIA_128_GCM_SHA256 uint16 = 0xC08A
|
||||
cipher_TLS_ECDHE_RSA_WITH_CAMELLIA_256_GCM_SHA384 uint16 = 0xC08B
|
||||
cipher_TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256 uint16 = 0xC08C
|
||||
cipher_TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384 uint16 = 0xC08D
|
||||
cipher_TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256 uint16 = 0xC08E
|
||||
cipher_TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384 uint16 = 0xC08F
|
||||
cipher_TLS_DHE_PSK_WITH_CAMELLIA_128_GCM_SHA256 uint16 = 0xC090
|
||||
cipher_TLS_DHE_PSK_WITH_CAMELLIA_256_GCM_SHA384 uint16 = 0xC091
|
||||
cipher_TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256 uint16 = 0xC092
|
||||
cipher_TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384 uint16 = 0xC093
|
||||
cipher_TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256 uint16 = 0xC094
|
||||
cipher_TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384 uint16 = 0xC095
|
||||
cipher_TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256 uint16 = 0xC096
|
||||
cipher_TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384 uint16 = 0xC097
|
||||
cipher_TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256 uint16 = 0xC098
|
||||
cipher_TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384 uint16 = 0xC099
|
||||
cipher_TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256 uint16 = 0xC09A
|
||||
cipher_TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384 uint16 = 0xC09B
|
||||
cipher_TLS_RSA_WITH_AES_128_CCM uint16 = 0xC09C
|
||||
cipher_TLS_RSA_WITH_AES_256_CCM uint16 = 0xC09D
|
||||
cipher_TLS_DHE_RSA_WITH_AES_128_CCM uint16 = 0xC09E
|
||||
cipher_TLS_DHE_RSA_WITH_AES_256_CCM uint16 = 0xC09F
|
||||
cipher_TLS_RSA_WITH_AES_128_CCM_8 uint16 = 0xC0A0
|
||||
cipher_TLS_RSA_WITH_AES_256_CCM_8 uint16 = 0xC0A1
|
||||
cipher_TLS_DHE_RSA_WITH_AES_128_CCM_8 uint16 = 0xC0A2
|
||||
cipher_TLS_DHE_RSA_WITH_AES_256_CCM_8 uint16 = 0xC0A3
|
||||
cipher_TLS_PSK_WITH_AES_128_CCM uint16 = 0xC0A4
|
||||
cipher_TLS_PSK_WITH_AES_256_CCM uint16 = 0xC0A5
|
||||
cipher_TLS_DHE_PSK_WITH_AES_128_CCM uint16 = 0xC0A6
|
||||
cipher_TLS_DHE_PSK_WITH_AES_256_CCM uint16 = 0xC0A7
|
||||
cipher_TLS_PSK_WITH_AES_128_CCM_8 uint16 = 0xC0A8
|
||||
cipher_TLS_PSK_WITH_AES_256_CCM_8 uint16 = 0xC0A9
|
||||
cipher_TLS_PSK_DHE_WITH_AES_128_CCM_8 uint16 = 0xC0AA
|
||||
cipher_TLS_PSK_DHE_WITH_AES_256_CCM_8 uint16 = 0xC0AB
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_AES_128_CCM uint16 = 0xC0AC
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_AES_256_CCM uint16 = 0xC0AD
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8 uint16 = 0xC0AE
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8 uint16 = 0xC0AF
|
||||
// Unassigned uint16 = 0xC0B0-FF
|
||||
// Unassigned uint16 = 0xC1-CB,*
|
||||
// Unassigned uint16 = 0xCC00-A7
|
||||
cipher_TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 uint16 = 0xCCA8
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 uint16 = 0xCCA9
|
||||
cipher_TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256 uint16 = 0xCCAA
|
||||
cipher_TLS_PSK_WITH_CHACHA20_POLY1305_SHA256 uint16 = 0xCCAB
|
||||
cipher_TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256 uint16 = 0xCCAC
|
||||
cipher_TLS_DHE_PSK_WITH_CHACHA20_POLY1305_SHA256 uint16 = 0xCCAD
|
||||
cipher_TLS_RSA_PSK_WITH_CHACHA20_POLY1305_SHA256 uint16 = 0xCCAE
|
||||
)
|
||||
|
||||
// isBadCipher reports whether the cipher is blacklisted by the HTTP/2 spec.
|
||||
// References:
|
||||
// https://tools.ietf.org/html/rfc7540#appendix-A
|
||||
// Reject cipher suites from Appendix A.
|
||||
// "This list includes those cipher suites that do not
|
||||
// offer an ephemeral key exchange and those that are
|
||||
// based on the TLS null, stream or block cipher type"
|
||||
func isBadCipher(cipher uint16) bool {
|
||||
switch cipher {
|
||||
case cipher_TLS_NULL_WITH_NULL_NULL,
|
||||
cipher_TLS_RSA_WITH_NULL_MD5,
|
||||
cipher_TLS_RSA_WITH_NULL_SHA,
|
||||
cipher_TLS_RSA_EXPORT_WITH_RC4_40_MD5,
|
||||
cipher_TLS_RSA_WITH_RC4_128_MD5,
|
||||
cipher_TLS_RSA_WITH_RC4_128_SHA,
|
||||
cipher_TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5,
|
||||
cipher_TLS_RSA_WITH_IDEA_CBC_SHA,
|
||||
cipher_TLS_RSA_EXPORT_WITH_DES40_CBC_SHA,
|
||||
cipher_TLS_RSA_WITH_DES_CBC_SHA,
|
||||
cipher_TLS_RSA_WITH_3DES_EDE_CBC_SHA,
|
||||
cipher_TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA,
|
||||
cipher_TLS_DH_DSS_WITH_DES_CBC_SHA,
|
||||
cipher_TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA,
|
||||
cipher_TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA,
|
||||
cipher_TLS_DH_RSA_WITH_DES_CBC_SHA,
|
||||
cipher_TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA,
|
||||
cipher_TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA,
|
||||
cipher_TLS_DHE_DSS_WITH_DES_CBC_SHA,
|
||||
cipher_TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA,
|
||||
cipher_TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA,
|
||||
cipher_TLS_DHE_RSA_WITH_DES_CBC_SHA,
|
||||
cipher_TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA,
|
||||
cipher_TLS_DH_anon_EXPORT_WITH_RC4_40_MD5,
|
||||
cipher_TLS_DH_anon_WITH_RC4_128_MD5,
|
||||
cipher_TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA,
|
||||
cipher_TLS_DH_anon_WITH_DES_CBC_SHA,
|
||||
cipher_TLS_DH_anon_WITH_3DES_EDE_CBC_SHA,
|
||||
cipher_TLS_KRB5_WITH_DES_CBC_SHA,
|
||||
cipher_TLS_KRB5_WITH_3DES_EDE_CBC_SHA,
|
||||
cipher_TLS_KRB5_WITH_RC4_128_SHA,
|
||||
cipher_TLS_KRB5_WITH_IDEA_CBC_SHA,
|
||||
cipher_TLS_KRB5_WITH_DES_CBC_MD5,
|
||||
cipher_TLS_KRB5_WITH_3DES_EDE_CBC_MD5,
|
||||
cipher_TLS_KRB5_WITH_RC4_128_MD5,
|
||||
cipher_TLS_KRB5_WITH_IDEA_CBC_MD5,
|
||||
cipher_TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA,
|
||||
cipher_TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA,
|
||||
cipher_TLS_KRB5_EXPORT_WITH_RC4_40_SHA,
|
||||
cipher_TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5,
|
||||
cipher_TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5,
|
||||
cipher_TLS_KRB5_EXPORT_WITH_RC4_40_MD5,
|
||||
cipher_TLS_PSK_WITH_NULL_SHA,
|
||||
cipher_TLS_DHE_PSK_WITH_NULL_SHA,
|
||||
cipher_TLS_RSA_PSK_WITH_NULL_SHA,
|
||||
cipher_TLS_RSA_WITH_AES_128_CBC_SHA,
|
||||
cipher_TLS_DH_DSS_WITH_AES_128_CBC_SHA,
|
||||
cipher_TLS_DH_RSA_WITH_AES_128_CBC_SHA,
|
||||
cipher_TLS_DHE_DSS_WITH_AES_128_CBC_SHA,
|
||||
cipher_TLS_DHE_RSA_WITH_AES_128_CBC_SHA,
|
||||
cipher_TLS_DH_anon_WITH_AES_128_CBC_SHA,
|
||||
cipher_TLS_RSA_WITH_AES_256_CBC_SHA,
|
||||
cipher_TLS_DH_DSS_WITH_AES_256_CBC_SHA,
|
||||
cipher_TLS_DH_RSA_WITH_AES_256_CBC_SHA,
|
||||
cipher_TLS_DHE_DSS_WITH_AES_256_CBC_SHA,
|
||||
cipher_TLS_DHE_RSA_WITH_AES_256_CBC_SHA,
|
||||
cipher_TLS_DH_anon_WITH_AES_256_CBC_SHA,
|
||||
cipher_TLS_RSA_WITH_NULL_SHA256,
|
||||
cipher_TLS_RSA_WITH_AES_128_CBC_SHA256,
|
||||
cipher_TLS_RSA_WITH_AES_256_CBC_SHA256,
|
||||
cipher_TLS_DH_DSS_WITH_AES_128_CBC_SHA256,
|
||||
cipher_TLS_DH_RSA_WITH_AES_128_CBC_SHA256,
|
||||
cipher_TLS_DHE_DSS_WITH_AES_128_CBC_SHA256,
|
||||
cipher_TLS_RSA_WITH_CAMELLIA_128_CBC_SHA,
|
||||
cipher_TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA,
|
||||
cipher_TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA,
|
||||
cipher_TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA,
|
||||
cipher_TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA,
|
||||
cipher_TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA,
|
||||
cipher_TLS_DHE_RSA_WITH_AES_128_CBC_SHA256,
|
||||
cipher_TLS_DH_DSS_WITH_AES_256_CBC_SHA256,
|
||||
cipher_TLS_DH_RSA_WITH_AES_256_CBC_SHA256,
|
||||
cipher_TLS_DHE_DSS_WITH_AES_256_CBC_SHA256,
|
||||
cipher_TLS_DHE_RSA_WITH_AES_256_CBC_SHA256,
|
||||
cipher_TLS_DH_anon_WITH_AES_128_CBC_SHA256,
|
||||
cipher_TLS_DH_anon_WITH_AES_256_CBC_SHA256,
|
||||
cipher_TLS_RSA_WITH_CAMELLIA_256_CBC_SHA,
|
||||
cipher_TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA,
|
||||
cipher_TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA,
|
||||
cipher_TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA,
|
||||
cipher_TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA,
|
||||
cipher_TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA,
|
||||
cipher_TLS_PSK_WITH_RC4_128_SHA,
|
||||
cipher_TLS_PSK_WITH_3DES_EDE_CBC_SHA,
|
||||
cipher_TLS_PSK_WITH_AES_128_CBC_SHA,
|
||||
cipher_TLS_PSK_WITH_AES_256_CBC_SHA,
|
||||
cipher_TLS_DHE_PSK_WITH_RC4_128_SHA,
|
||||
cipher_TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA,
|
||||
cipher_TLS_DHE_PSK_WITH_AES_128_CBC_SHA,
|
||||
cipher_TLS_DHE_PSK_WITH_AES_256_CBC_SHA,
|
||||
cipher_TLS_RSA_PSK_WITH_RC4_128_SHA,
|
||||
cipher_TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA,
|
||||
cipher_TLS_RSA_PSK_WITH_AES_128_CBC_SHA,
|
||||
cipher_TLS_RSA_PSK_WITH_AES_256_CBC_SHA,
|
||||
cipher_TLS_RSA_WITH_SEED_CBC_SHA,
|
||||
cipher_TLS_DH_DSS_WITH_SEED_CBC_SHA,
|
||||
cipher_TLS_DH_RSA_WITH_SEED_CBC_SHA,
|
||||
cipher_TLS_DHE_DSS_WITH_SEED_CBC_SHA,
|
||||
cipher_TLS_DHE_RSA_WITH_SEED_CBC_SHA,
|
||||
cipher_TLS_DH_anon_WITH_SEED_CBC_SHA,
|
||||
cipher_TLS_RSA_WITH_AES_128_GCM_SHA256,
|
||||
cipher_TLS_RSA_WITH_AES_256_GCM_SHA384,
|
||||
cipher_TLS_DH_RSA_WITH_AES_128_GCM_SHA256,
|
||||
cipher_TLS_DH_RSA_WITH_AES_256_GCM_SHA384,
|
||||
cipher_TLS_DH_DSS_WITH_AES_128_GCM_SHA256,
|
||||
cipher_TLS_DH_DSS_WITH_AES_256_GCM_SHA384,
|
||||
cipher_TLS_DH_anon_WITH_AES_128_GCM_SHA256,
|
||||
cipher_TLS_DH_anon_WITH_AES_256_GCM_SHA384,
|
||||
cipher_TLS_PSK_WITH_AES_128_GCM_SHA256,
|
||||
cipher_TLS_PSK_WITH_AES_256_GCM_SHA384,
|
||||
cipher_TLS_RSA_PSK_WITH_AES_128_GCM_SHA256,
|
||||
cipher_TLS_RSA_PSK_WITH_AES_256_GCM_SHA384,
|
||||
cipher_TLS_PSK_WITH_AES_128_CBC_SHA256,
|
||||
cipher_TLS_PSK_WITH_AES_256_CBC_SHA384,
|
||||
cipher_TLS_PSK_WITH_NULL_SHA256,
|
||||
cipher_TLS_PSK_WITH_NULL_SHA384,
|
||||
cipher_TLS_DHE_PSK_WITH_AES_128_CBC_SHA256,
|
||||
cipher_TLS_DHE_PSK_WITH_AES_256_CBC_SHA384,
|
||||
cipher_TLS_DHE_PSK_WITH_NULL_SHA256,
|
||||
cipher_TLS_DHE_PSK_WITH_NULL_SHA384,
|
||||
cipher_TLS_RSA_PSK_WITH_AES_128_CBC_SHA256,
|
||||
cipher_TLS_RSA_PSK_WITH_AES_256_CBC_SHA384,
|
||||
cipher_TLS_RSA_PSK_WITH_NULL_SHA256,
|
||||
cipher_TLS_RSA_PSK_WITH_NULL_SHA384,
|
||||
cipher_TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256,
|
||||
cipher_TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256,
|
||||
cipher_TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256,
|
||||
cipher_TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256,
|
||||
cipher_TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256,
|
||||
cipher_TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256,
|
||||
cipher_TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256,
|
||||
cipher_TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256,
|
||||
cipher_TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256,
|
||||
cipher_TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256,
|
||||
cipher_TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256,
|
||||
cipher_TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256,
|
||||
cipher_TLS_EMPTY_RENEGOTIATION_INFO_SCSV,
|
||||
cipher_TLS_ECDH_ECDSA_WITH_NULL_SHA,
|
||||
cipher_TLS_ECDH_ECDSA_WITH_RC4_128_SHA,
|
||||
cipher_TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA,
|
||||
cipher_TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA,
|
||||
cipher_TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA,
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_NULL_SHA,
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA,
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
|
||||
cipher_TLS_ECDH_RSA_WITH_NULL_SHA,
|
||||
cipher_TLS_ECDH_RSA_WITH_RC4_128_SHA,
|
||||
cipher_TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA,
|
||||
cipher_TLS_ECDH_RSA_WITH_AES_128_CBC_SHA,
|
||||
cipher_TLS_ECDH_RSA_WITH_AES_256_CBC_SHA,
|
||||
cipher_TLS_ECDHE_RSA_WITH_NULL_SHA,
|
||||
cipher_TLS_ECDHE_RSA_WITH_RC4_128_SHA,
|
||||
cipher_TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
|
||||
cipher_TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
|
||||
cipher_TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
|
||||
cipher_TLS_ECDH_anon_WITH_NULL_SHA,
|
||||
cipher_TLS_ECDH_anon_WITH_RC4_128_SHA,
|
||||
cipher_TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA,
|
||||
cipher_TLS_ECDH_anon_WITH_AES_128_CBC_SHA,
|
||||
cipher_TLS_ECDH_anon_WITH_AES_256_CBC_SHA,
|
||||
cipher_TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA,
|
||||
cipher_TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA,
|
||||
cipher_TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA,
|
||||
cipher_TLS_SRP_SHA_WITH_AES_128_CBC_SHA,
|
||||
cipher_TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA,
|
||||
cipher_TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA,
|
||||
cipher_TLS_SRP_SHA_WITH_AES_256_CBC_SHA,
|
||||
cipher_TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA,
|
||||
cipher_TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA,
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384,
|
||||
cipher_TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256,
|
||||
cipher_TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384,
|
||||
cipher_TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
|
||||
cipher_TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384,
|
||||
cipher_TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256,
|
||||
cipher_TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384,
|
||||
cipher_TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||
cipher_TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384,
|
||||
cipher_TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256,
|
||||
cipher_TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384,
|
||||
cipher_TLS_ECDHE_PSK_WITH_RC4_128_SHA,
|
||||
cipher_TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA,
|
||||
cipher_TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA,
|
||||
cipher_TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA,
|
||||
cipher_TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256,
|
||||
cipher_TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384,
|
||||
cipher_TLS_ECDHE_PSK_WITH_NULL_SHA,
|
||||
cipher_TLS_ECDHE_PSK_WITH_NULL_SHA256,
|
||||
cipher_TLS_ECDHE_PSK_WITH_NULL_SHA384,
|
||||
cipher_TLS_RSA_WITH_ARIA_128_CBC_SHA256,
|
||||
cipher_TLS_RSA_WITH_ARIA_256_CBC_SHA384,
|
||||
cipher_TLS_DH_DSS_WITH_ARIA_128_CBC_SHA256,
|
||||
cipher_TLS_DH_DSS_WITH_ARIA_256_CBC_SHA384,
|
||||
cipher_TLS_DH_RSA_WITH_ARIA_128_CBC_SHA256,
|
||||
cipher_TLS_DH_RSA_WITH_ARIA_256_CBC_SHA384,
|
||||
cipher_TLS_DHE_DSS_WITH_ARIA_128_CBC_SHA256,
|
||||
cipher_TLS_DHE_DSS_WITH_ARIA_256_CBC_SHA384,
|
||||
cipher_TLS_DHE_RSA_WITH_ARIA_128_CBC_SHA256,
|
||||
cipher_TLS_DHE_RSA_WITH_ARIA_256_CBC_SHA384,
|
||||
cipher_TLS_DH_anon_WITH_ARIA_128_CBC_SHA256,
|
||||
cipher_TLS_DH_anon_WITH_ARIA_256_CBC_SHA384,
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_ARIA_128_CBC_SHA256,
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_ARIA_256_CBC_SHA384,
|
||||
cipher_TLS_ECDH_ECDSA_WITH_ARIA_128_CBC_SHA256,
|
||||
cipher_TLS_ECDH_ECDSA_WITH_ARIA_256_CBC_SHA384,
|
||||
cipher_TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256,
|
||||
cipher_TLS_ECDHE_RSA_WITH_ARIA_256_CBC_SHA384,
|
||||
cipher_TLS_ECDH_RSA_WITH_ARIA_128_CBC_SHA256,
|
||||
cipher_TLS_ECDH_RSA_WITH_ARIA_256_CBC_SHA384,
|
||||
cipher_TLS_RSA_WITH_ARIA_128_GCM_SHA256,
|
||||
cipher_TLS_RSA_WITH_ARIA_256_GCM_SHA384,
|
||||
cipher_TLS_DH_RSA_WITH_ARIA_128_GCM_SHA256,
|
||||
cipher_TLS_DH_RSA_WITH_ARIA_256_GCM_SHA384,
|
||||
cipher_TLS_DH_DSS_WITH_ARIA_128_GCM_SHA256,
|
||||
cipher_TLS_DH_DSS_WITH_ARIA_256_GCM_SHA384,
|
||||
cipher_TLS_DH_anon_WITH_ARIA_128_GCM_SHA256,
|
||||
cipher_TLS_DH_anon_WITH_ARIA_256_GCM_SHA384,
|
||||
cipher_TLS_ECDH_ECDSA_WITH_ARIA_128_GCM_SHA256,
|
||||
cipher_TLS_ECDH_ECDSA_WITH_ARIA_256_GCM_SHA384,
|
||||
cipher_TLS_ECDH_RSA_WITH_ARIA_128_GCM_SHA256,
|
||||
cipher_TLS_ECDH_RSA_WITH_ARIA_256_GCM_SHA384,
|
||||
cipher_TLS_PSK_WITH_ARIA_128_CBC_SHA256,
|
||||
cipher_TLS_PSK_WITH_ARIA_256_CBC_SHA384,
|
||||
cipher_TLS_DHE_PSK_WITH_ARIA_128_CBC_SHA256,
|
||||
cipher_TLS_DHE_PSK_WITH_ARIA_256_CBC_SHA384,
|
||||
cipher_TLS_RSA_PSK_WITH_ARIA_128_CBC_SHA256,
|
||||
cipher_TLS_RSA_PSK_WITH_ARIA_256_CBC_SHA384,
|
||||
cipher_TLS_PSK_WITH_ARIA_128_GCM_SHA256,
|
||||
cipher_TLS_PSK_WITH_ARIA_256_GCM_SHA384,
|
||||
cipher_TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256,
|
||||
cipher_TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384,
|
||||
cipher_TLS_ECDHE_PSK_WITH_ARIA_128_CBC_SHA256,
|
||||
cipher_TLS_ECDHE_PSK_WITH_ARIA_256_CBC_SHA384,
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256,
|
||||
cipher_TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384,
|
||||
cipher_TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256,
|
||||
cipher_TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384,
|
||||
cipher_TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256,
|
||||
cipher_TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384,
|
||||
cipher_TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256,
|
||||
cipher_TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384,
|
||||
cipher_TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256,
|
||||
cipher_TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384,
|
||||
cipher_TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256,
|
||||
cipher_TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384,
|
||||
cipher_TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256,
|
||||
cipher_TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384,
|
||||
cipher_TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256,
|
||||
cipher_TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384,
|
||||
cipher_TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256,
|
||||
cipher_TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384,
|
||||
cipher_TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256,
|
||||
cipher_TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384,
|
||||
cipher_TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256,
|
||||
cipher_TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384,
|
||||
cipher_TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256,
|
||||
cipher_TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384,
|
||||
cipher_TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256,
|
||||
cipher_TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384,
|
||||
cipher_TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256,
|
||||
cipher_TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384,
|
||||
cipher_TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256,
|
||||
cipher_TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384,
|
||||
cipher_TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256,
|
||||
cipher_TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384,
|
||||
cipher_TLS_RSA_WITH_AES_128_CCM,
|
||||
cipher_TLS_RSA_WITH_AES_256_CCM,
|
||||
cipher_TLS_RSA_WITH_AES_128_CCM_8,
|
||||
cipher_TLS_RSA_WITH_AES_256_CCM_8,
|
||||
cipher_TLS_PSK_WITH_AES_128_CCM,
|
||||
cipher_TLS_PSK_WITH_AES_256_CCM,
|
||||
cipher_TLS_PSK_WITH_AES_128_CCM_8,
|
||||
cipher_TLS_PSK_WITH_AES_256_CCM_8:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
+256
@@ -0,0 +1,256 @@
|
||||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Transport code's client connection pooling.
|
||||
|
||||
package http2
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net/http"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// ClientConnPool manages a pool of HTTP/2 client connections.
|
||||
type ClientConnPool interface {
|
||||
GetClientConn(req *http.Request, addr string) (*ClientConn, error)
|
||||
MarkDead(*ClientConn)
|
||||
}
|
||||
|
||||
// clientConnPoolIdleCloser is the interface implemented by ClientConnPool
|
||||
// implementations which can close their idle connections.
|
||||
type clientConnPoolIdleCloser interface {
|
||||
ClientConnPool
|
||||
closeIdleConnections()
|
||||
}
|
||||
|
||||
var (
|
||||
_ clientConnPoolIdleCloser = (*clientConnPool)(nil)
|
||||
_ clientConnPoolIdleCloser = noDialClientConnPool{}
|
||||
)
|
||||
|
||||
// TODO: use singleflight for dialing and addConnCalls?
|
||||
type clientConnPool struct {
|
||||
t *Transport
|
||||
|
||||
mu sync.Mutex // TODO: maybe switch to RWMutex
|
||||
// TODO: add support for sharing conns based on cert names
|
||||
// (e.g. share conn for googleapis.com and appspot.com)
|
||||
conns map[string][]*ClientConn // key is host:port
|
||||
dialing map[string]*dialCall // currently in-flight dials
|
||||
keys map[*ClientConn][]string
|
||||
addConnCalls map[string]*addConnCall // in-flight addConnIfNeede calls
|
||||
}
|
||||
|
||||
func (p *clientConnPool) GetClientConn(req *http.Request, addr string) (*ClientConn, error) {
|
||||
return p.getClientConn(req, addr, dialOnMiss)
|
||||
}
|
||||
|
||||
const (
|
||||
dialOnMiss = true
|
||||
noDialOnMiss = false
|
||||
)
|
||||
|
||||
func (p *clientConnPool) getClientConn(req *http.Request, addr string, dialOnMiss bool) (*ClientConn, error) {
|
||||
if isConnectionCloseRequest(req) && dialOnMiss {
|
||||
// It gets its own connection.
|
||||
const singleUse = true
|
||||
cc, err := p.t.dialClientConn(addr, singleUse)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cc, nil
|
||||
}
|
||||
p.mu.Lock()
|
||||
for _, cc := range p.conns[addr] {
|
||||
if cc.CanTakeNewRequest() {
|
||||
p.mu.Unlock()
|
||||
return cc, nil
|
||||
}
|
||||
}
|
||||
if !dialOnMiss {
|
||||
p.mu.Unlock()
|
||||
return nil, ErrNoCachedConn
|
||||
}
|
||||
call := p.getStartDialLocked(addr)
|
||||
p.mu.Unlock()
|
||||
<-call.done
|
||||
return call.res, call.err
|
||||
}
|
||||
|
||||
// dialCall is an in-flight Transport dial call to a host.
|
||||
type dialCall struct {
|
||||
p *clientConnPool
|
||||
done chan struct{} // closed when done
|
||||
res *ClientConn // valid after done is closed
|
||||
err error // valid after done is closed
|
||||
}
|
||||
|
||||
// requires p.mu is held.
|
||||
func (p *clientConnPool) getStartDialLocked(addr string) *dialCall {
|
||||
if call, ok := p.dialing[addr]; ok {
|
||||
// A dial is already in-flight. Don't start another.
|
||||
return call
|
||||
}
|
||||
call := &dialCall{p: p, done: make(chan struct{})}
|
||||
if p.dialing == nil {
|
||||
p.dialing = make(map[string]*dialCall)
|
||||
}
|
||||
p.dialing[addr] = call
|
||||
go call.dial(addr)
|
||||
return call
|
||||
}
|
||||
|
||||
// run in its own goroutine.
|
||||
func (c *dialCall) dial(addr string) {
|
||||
const singleUse = false // shared conn
|
||||
c.res, c.err = c.p.t.dialClientConn(addr, singleUse)
|
||||
close(c.done)
|
||||
|
||||
c.p.mu.Lock()
|
||||
delete(c.p.dialing, addr)
|
||||
if c.err == nil {
|
||||
c.p.addConnLocked(addr, c.res)
|
||||
}
|
||||
c.p.mu.Unlock()
|
||||
}
|
||||
|
||||
// addConnIfNeeded makes a NewClientConn out of c if a connection for key doesn't
|
||||
// already exist. It coalesces concurrent calls with the same key.
|
||||
// This is used by the http1 Transport code when it creates a new connection. Because
|
||||
// the http1 Transport doesn't de-dup TCP dials to outbound hosts (because it doesn't know
|
||||
// the protocol), it can get into a situation where it has multiple TLS connections.
|
||||
// This code decides which ones live or die.
|
||||
// The return value used is whether c was used.
|
||||
// c is never closed.
|
||||
func (p *clientConnPool) addConnIfNeeded(key string, t *Transport, c *tls.Conn) (used bool, err error) {
|
||||
p.mu.Lock()
|
||||
for _, cc := range p.conns[key] {
|
||||
if cc.CanTakeNewRequest() {
|
||||
p.mu.Unlock()
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
call, dup := p.addConnCalls[key]
|
||||
if !dup {
|
||||
if p.addConnCalls == nil {
|
||||
p.addConnCalls = make(map[string]*addConnCall)
|
||||
}
|
||||
call = &addConnCall{
|
||||
p: p,
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
p.addConnCalls[key] = call
|
||||
go call.run(t, key, c)
|
||||
}
|
||||
p.mu.Unlock()
|
||||
|
||||
<-call.done
|
||||
if call.err != nil {
|
||||
return false, call.err
|
||||
}
|
||||
return !dup, nil
|
||||
}
|
||||
|
||||
type addConnCall struct {
|
||||
p *clientConnPool
|
||||
done chan struct{} // closed when done
|
||||
err error
|
||||
}
|
||||
|
||||
func (c *addConnCall) run(t *Transport, key string, tc *tls.Conn) {
|
||||
cc, err := t.NewClientConn(tc)
|
||||
|
||||
p := c.p
|
||||
p.mu.Lock()
|
||||
if err != nil {
|
||||
c.err = err
|
||||
} else {
|
||||
p.addConnLocked(key, cc)
|
||||
}
|
||||
delete(p.addConnCalls, key)
|
||||
p.mu.Unlock()
|
||||
close(c.done)
|
||||
}
|
||||
|
||||
func (p *clientConnPool) addConn(key string, cc *ClientConn) {
|
||||
p.mu.Lock()
|
||||
p.addConnLocked(key, cc)
|
||||
p.mu.Unlock()
|
||||
}
|
||||
|
||||
// p.mu must be held
|
||||
func (p *clientConnPool) addConnLocked(key string, cc *ClientConn) {
|
||||
for _, v := range p.conns[key] {
|
||||
if v == cc {
|
||||
return
|
||||
}
|
||||
}
|
||||
if p.conns == nil {
|
||||
p.conns = make(map[string][]*ClientConn)
|
||||
}
|
||||
if p.keys == nil {
|
||||
p.keys = make(map[*ClientConn][]string)
|
||||
}
|
||||
p.conns[key] = append(p.conns[key], cc)
|
||||
p.keys[cc] = append(p.keys[cc], key)
|
||||
}
|
||||
|
||||
func (p *clientConnPool) MarkDead(cc *ClientConn) {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
for _, key := range p.keys[cc] {
|
||||
vv, ok := p.conns[key]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
newList := filterOutClientConn(vv, cc)
|
||||
if len(newList) > 0 {
|
||||
p.conns[key] = newList
|
||||
} else {
|
||||
delete(p.conns, key)
|
||||
}
|
||||
}
|
||||
delete(p.keys, cc)
|
||||
}
|
||||
|
||||
func (p *clientConnPool) closeIdleConnections() {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
// TODO: don't close a cc if it was just added to the pool
|
||||
// milliseconds ago and has never been used. There's currently
|
||||
// a small race window with the HTTP/1 Transport's integration
|
||||
// where it can add an idle conn just before using it, and
|
||||
// somebody else can concurrently call CloseIdleConns and
|
||||
// break some caller's RoundTrip.
|
||||
for _, vv := range p.conns {
|
||||
for _, cc := range vv {
|
||||
cc.closeIfIdle()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func filterOutClientConn(in []*ClientConn, exclude *ClientConn) []*ClientConn {
|
||||
out := in[:0]
|
||||
for _, v := range in {
|
||||
if v != exclude {
|
||||
out = append(out, v)
|
||||
}
|
||||
}
|
||||
// If we filtered it out, zero out the last item to prevent
|
||||
// the GC from seeing it.
|
||||
if len(in) != len(out) {
|
||||
in[len(in)-1] = nil
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// noDialClientConnPool is an implementation of http2.ClientConnPool
|
||||
// which never dials. We let the HTTP/1.1 client dial and use its TLS
|
||||
// connection instead.
|
||||
type noDialClientConnPool struct{ *clientConnPool }
|
||||
|
||||
func (p noDialClientConnPool) GetClientConn(req *http.Request, addr string) (*ClientConn, error) {
|
||||
return p.getClientConn(req, addr, noDialOnMiss)
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.6
|
||||
|
||||
package http2
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func configureTransport(t1 *http.Transport) (*Transport, error) {
|
||||
connPool := new(clientConnPool)
|
||||
t2 := &Transport{
|
||||
ConnPool: noDialClientConnPool{connPool},
|
||||
t1: t1,
|
||||
}
|
||||
connPool.t = t2
|
||||
if err := registerHTTPSProtocol(t1, noDialH2RoundTripper{t2}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if t1.TLSClientConfig == nil {
|
||||
t1.TLSClientConfig = new(tls.Config)
|
||||
}
|
||||
if !strSliceContains(t1.TLSClientConfig.NextProtos, "h2") {
|
||||
t1.TLSClientConfig.NextProtos = append([]string{"h2"}, t1.TLSClientConfig.NextProtos...)
|
||||
}
|
||||
if !strSliceContains(t1.TLSClientConfig.NextProtos, "http/1.1") {
|
||||
t1.TLSClientConfig.NextProtos = append(t1.TLSClientConfig.NextProtos, "http/1.1")
|
||||
}
|
||||
upgradeFn := func(authority string, c *tls.Conn) http.RoundTripper {
|
||||
addr := authorityAddr("https", authority)
|
||||
if used, err := connPool.addConnIfNeeded(addr, t2, c); err != nil {
|
||||
go c.Close()
|
||||
return erringRoundTripper{err}
|
||||
} else if !used {
|
||||
// Turns out we don't need this c.
|
||||
// For example, two goroutines made requests to the same host
|
||||
// at the same time, both kicking off TCP dials. (since protocol
|
||||
// was unknown)
|
||||
go c.Close()
|
||||
}
|
||||
return t2
|
||||
}
|
||||
if m := t1.TLSNextProto; len(m) == 0 {
|
||||
t1.TLSNextProto = map[string]func(string, *tls.Conn) http.RoundTripper{
|
||||
"h2": upgradeFn,
|
||||
}
|
||||
} else {
|
||||
m["h2"] = upgradeFn
|
||||
}
|
||||
return t2, nil
|
||||
}
|
||||
|
||||
// registerHTTPSProtocol calls Transport.RegisterProtocol but
|
||||
// convering panics into errors.
|
||||
func registerHTTPSProtocol(t *http.Transport, rt http.RoundTripper) (err error) {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
err = fmt.Errorf("%v", e)
|
||||
}
|
||||
}()
|
||||
t.RegisterProtocol("https", rt)
|
||||
return nil
|
||||
}
|
||||
|
||||
// noDialH2RoundTripper is a RoundTripper which only tries to complete the request
|
||||
// if there's already has a cached connection to the host.
|
||||
type noDialH2RoundTripper struct{ t *Transport }
|
||||
|
||||
func (rt noDialH2RoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
res, err := rt.t.RoundTrip(req)
|
||||
if err == ErrNoCachedConn {
|
||||
return nil, http.ErrSkipAltProtocol
|
||||
}
|
||||
return res, err
|
||||
}
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package http2
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Buffer chunks are allocated from a pool to reduce pressure on GC.
|
||||
// The maximum wasted space per dataBuffer is 2x the largest size class,
|
||||
// which happens when the dataBuffer has multiple chunks and there is
|
||||
// one unread byte in both the first and last chunks. We use a few size
|
||||
// classes to minimize overheads for servers that typically receive very
|
||||
// small request bodies.
|
||||
//
|
||||
// TODO: Benchmark to determine if the pools are necessary. The GC may have
|
||||
// improved enough that we can instead allocate chunks like this:
|
||||
// make([]byte, max(16<<10, expectedBytesRemaining))
|
||||
var (
|
||||
dataChunkSizeClasses = []int{
|
||||
1 << 10,
|
||||
2 << 10,
|
||||
4 << 10,
|
||||
8 << 10,
|
||||
16 << 10,
|
||||
}
|
||||
dataChunkPools = [...]sync.Pool{
|
||||
{New: func() interface{} { return make([]byte, 1<<10) }},
|
||||
{New: func() interface{} { return make([]byte, 2<<10) }},
|
||||
{New: func() interface{} { return make([]byte, 4<<10) }},
|
||||
{New: func() interface{} { return make([]byte, 8<<10) }},
|
||||
{New: func() interface{} { return make([]byte, 16<<10) }},
|
||||
}
|
||||
)
|
||||
|
||||
func getDataBufferChunk(size int64) []byte {
|
||||
i := 0
|
||||
for ; i < len(dataChunkSizeClasses)-1; i++ {
|
||||
if size <= int64(dataChunkSizeClasses[i]) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return dataChunkPools[i].Get().([]byte)
|
||||
}
|
||||
|
||||
func putDataBufferChunk(p []byte) {
|
||||
for i, n := range dataChunkSizeClasses {
|
||||
if len(p) == n {
|
||||
dataChunkPools[i].Put(p)
|
||||
return
|
||||
}
|
||||
}
|
||||
panic(fmt.Sprintf("unexpected buffer len=%v", len(p)))
|
||||
}
|
||||
|
||||
// dataBuffer is an io.ReadWriter backed by a list of data chunks.
|
||||
// Each dataBuffer is used to read DATA frames on a single stream.
|
||||
// The buffer is divided into chunks so the server can limit the
|
||||
// total memory used by a single connection without limiting the
|
||||
// request body size on any single stream.
|
||||
type dataBuffer struct {
|
||||
chunks [][]byte
|
||||
r int // next byte to read is chunks[0][r]
|
||||
w int // next byte to write is chunks[len(chunks)-1][w]
|
||||
size int // total buffered bytes
|
||||
expected int64 // we expect at least this many bytes in future Write calls (ignored if <= 0)
|
||||
}
|
||||
|
||||
var errReadEmpty = errors.New("read from empty dataBuffer")
|
||||
|
||||
// Read copies bytes from the buffer into p.
|
||||
// It is an error to read when no data is available.
|
||||
func (b *dataBuffer) Read(p []byte) (int, error) {
|
||||
if b.size == 0 {
|
||||
return 0, errReadEmpty
|
||||
}
|
||||
var ntotal int
|
||||
for len(p) > 0 && b.size > 0 {
|
||||
readFrom := b.bytesFromFirstChunk()
|
||||
n := copy(p, readFrom)
|
||||
p = p[n:]
|
||||
ntotal += n
|
||||
b.r += n
|
||||
b.size -= n
|
||||
// If the first chunk has been consumed, advance to the next chunk.
|
||||
if b.r == len(b.chunks[0]) {
|
||||
putDataBufferChunk(b.chunks[0])
|
||||
end := len(b.chunks) - 1
|
||||
copy(b.chunks[:end], b.chunks[1:])
|
||||
b.chunks[end] = nil
|
||||
b.chunks = b.chunks[:end]
|
||||
b.r = 0
|
||||
}
|
||||
}
|
||||
return ntotal, nil
|
||||
}
|
||||
|
||||
func (b *dataBuffer) bytesFromFirstChunk() []byte {
|
||||
if len(b.chunks) == 1 {
|
||||
return b.chunks[0][b.r:b.w]
|
||||
}
|
||||
return b.chunks[0][b.r:]
|
||||
}
|
||||
|
||||
// Len returns the number of bytes of the unread portion of the buffer.
|
||||
func (b *dataBuffer) Len() int {
|
||||
return b.size
|
||||
}
|
||||
|
||||
// Write appends p to the buffer.
|
||||
func (b *dataBuffer) Write(p []byte) (int, error) {
|
||||
ntotal := len(p)
|
||||
for len(p) > 0 {
|
||||
// If the last chunk is empty, allocate a new chunk. Try to allocate
|
||||
// enough to fully copy p plus any additional bytes we expect to
|
||||
// receive. However, this may allocate less than len(p).
|
||||
want := int64(len(p))
|
||||
if b.expected > want {
|
||||
want = b.expected
|
||||
}
|
||||
chunk := b.lastChunkOrAlloc(want)
|
||||
n := copy(chunk[b.w:], p)
|
||||
p = p[n:]
|
||||
b.w += n
|
||||
b.size += n
|
||||
b.expected -= int64(n)
|
||||
}
|
||||
return ntotal, nil
|
||||
}
|
||||
|
||||
func (b *dataBuffer) lastChunkOrAlloc(want int64) []byte {
|
||||
if len(b.chunks) != 0 {
|
||||
last := b.chunks[len(b.chunks)-1]
|
||||
if b.w < len(last) {
|
||||
return last
|
||||
}
|
||||
}
|
||||
chunk := getDataBufferChunk(want)
|
||||
b.chunks = append(b.chunks, chunk)
|
||||
b.w = 0
|
||||
return chunk
|
||||
}
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package http2
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// An ErrCode is an unsigned 32-bit error code as defined in the HTTP/2 spec.
|
||||
type ErrCode uint32
|
||||
|
||||
const (
|
||||
ErrCodeNo ErrCode = 0x0
|
||||
ErrCodeProtocol ErrCode = 0x1
|
||||
ErrCodeInternal ErrCode = 0x2
|
||||
ErrCodeFlowControl ErrCode = 0x3
|
||||
ErrCodeSettingsTimeout ErrCode = 0x4
|
||||
ErrCodeStreamClosed ErrCode = 0x5
|
||||
ErrCodeFrameSize ErrCode = 0x6
|
||||
ErrCodeRefusedStream ErrCode = 0x7
|
||||
ErrCodeCancel ErrCode = 0x8
|
||||
ErrCodeCompression ErrCode = 0x9
|
||||
ErrCodeConnect ErrCode = 0xa
|
||||
ErrCodeEnhanceYourCalm ErrCode = 0xb
|
||||
ErrCodeInadequateSecurity ErrCode = 0xc
|
||||
ErrCodeHTTP11Required ErrCode = 0xd
|
||||
)
|
||||
|
||||
var errCodeName = map[ErrCode]string{
|
||||
ErrCodeNo: "NO_ERROR",
|
||||
ErrCodeProtocol: "PROTOCOL_ERROR",
|
||||
ErrCodeInternal: "INTERNAL_ERROR",
|
||||
ErrCodeFlowControl: "FLOW_CONTROL_ERROR",
|
||||
ErrCodeSettingsTimeout: "SETTINGS_TIMEOUT",
|
||||
ErrCodeStreamClosed: "STREAM_CLOSED",
|
||||
ErrCodeFrameSize: "FRAME_SIZE_ERROR",
|
||||
ErrCodeRefusedStream: "REFUSED_STREAM",
|
||||
ErrCodeCancel: "CANCEL",
|
||||
ErrCodeCompression: "COMPRESSION_ERROR",
|
||||
ErrCodeConnect: "CONNECT_ERROR",
|
||||
ErrCodeEnhanceYourCalm: "ENHANCE_YOUR_CALM",
|
||||
ErrCodeInadequateSecurity: "INADEQUATE_SECURITY",
|
||||
ErrCodeHTTP11Required: "HTTP_1_1_REQUIRED",
|
||||
}
|
||||
|
||||
func (e ErrCode) String() string {
|
||||
if s, ok := errCodeName[e]; ok {
|
||||
return s
|
||||
}
|
||||
return fmt.Sprintf("unknown error code 0x%x", uint32(e))
|
||||
}
|
||||
|
||||
// ConnectionError is an error that results in the termination of the
|
||||
// entire connection.
|
||||
type ConnectionError ErrCode
|
||||
|
||||
func (e ConnectionError) Error() string { return fmt.Sprintf("connection error: %s", ErrCode(e)) }
|
||||
|
||||
// StreamError is an error that only affects one stream within an
|
||||
// HTTP/2 connection.
|
||||
type StreamError struct {
|
||||
StreamID uint32
|
||||
Code ErrCode
|
||||
Cause error // optional additional detail
|
||||
}
|
||||
|
||||
func streamError(id uint32, code ErrCode) StreamError {
|
||||
return StreamError{StreamID: id, Code: code}
|
||||
}
|
||||
|
||||
func (e StreamError) Error() string {
|
||||
if e.Cause != nil {
|
||||
return fmt.Sprintf("stream error: stream ID %d; %v; %v", e.StreamID, e.Code, e.Cause)
|
||||
}
|
||||
return fmt.Sprintf("stream error: stream ID %d; %v", e.StreamID, e.Code)
|
||||
}
|
||||
|
||||
// 6.9.1 The Flow Control Window
|
||||
// "If a sender receives a WINDOW_UPDATE that causes a flow control
|
||||
// window to exceed this maximum it MUST terminate either the stream
|
||||
// or the connection, as appropriate. For streams, [...]; for the
|
||||
// connection, a GOAWAY frame with a FLOW_CONTROL_ERROR code."
|
||||
type goAwayFlowError struct{}
|
||||
|
||||
func (goAwayFlowError) Error() string { return "connection exceeded flow control window size" }
|
||||
|
||||
// connErrorReason wraps a ConnectionError with an informative error about why it occurs.
|
||||
|
||||
// Errors of this type are only returned by the frame parser functions
|
||||
// and converted into ConnectionError(ErrCodeProtocol).
|
||||
type connError struct {
|
||||
Code ErrCode
|
||||
Reason string
|
||||
}
|
||||
|
||||
func (e connError) Error() string {
|
||||
return fmt.Sprintf("http2: connection error: %v: %v", e.Code, e.Reason)
|
||||
}
|
||||
|
||||
type pseudoHeaderError string
|
||||
|
||||
func (e pseudoHeaderError) Error() string {
|
||||
return fmt.Sprintf("invalid pseudo-header %q", string(e))
|
||||
}
|
||||
|
||||
type duplicatePseudoHeaderError string
|
||||
|
||||
func (e duplicatePseudoHeaderError) Error() string {
|
||||
return fmt.Sprintf("duplicate pseudo-header %q", string(e))
|
||||
}
|
||||
|
||||
type headerFieldNameError string
|
||||
|
||||
func (e headerFieldNameError) Error() string {
|
||||
return fmt.Sprintf("invalid header field name %q", string(e))
|
||||
}
|
||||
|
||||
type headerFieldValueError string
|
||||
|
||||
func (e headerFieldValueError) Error() string {
|
||||
return fmt.Sprintf("invalid header field value %q", string(e))
|
||||
}
|
||||
|
||||
var (
|
||||
errMixPseudoHeaderTypes = errors.New("mix of request and response pseudo headers")
|
||||
errPseudoAfterRegular = errors.New("pseudo header field after regular")
|
||||
)
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Flow control
|
||||
|
||||
package http2
|
||||
|
||||
// flow is the flow control window's size.
|
||||
type flow struct {
|
||||
// n is the number of DATA bytes we're allowed to send.
|
||||
// A flow is kept both on a conn and a per-stream.
|
||||
n int32
|
||||
|
||||
// conn points to the shared connection-level flow that is
|
||||
// shared by all streams on that conn. It is nil for the flow
|
||||
// that's on the conn directly.
|
||||
conn *flow
|
||||
}
|
||||
|
||||
func (f *flow) setConnFlow(cf *flow) { f.conn = cf }
|
||||
|
||||
func (f *flow) available() int32 {
|
||||
n := f.n
|
||||
if f.conn != nil && f.conn.n < n {
|
||||
n = f.conn.n
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (f *flow) take(n int32) {
|
||||
if n > f.available() {
|
||||
panic("internal error: took too much")
|
||||
}
|
||||
f.n -= n
|
||||
if f.conn != nil {
|
||||
f.conn.n -= n
|
||||
}
|
||||
}
|
||||
|
||||
// add adds n bytes (positive or negative) to the flow control window.
|
||||
// It returns false if the sum would exceed 2^31-1.
|
||||
func (f *flow) add(n int32) bool {
|
||||
remain := (1<<31 - 1) - f.n
|
||||
if n > remain {
|
||||
return false
|
||||
}
|
||||
f.n += n
|
||||
return true
|
||||
}
|
||||
+1579
File diff suppressed because it is too large
Load Diff
+16
@@ -0,0 +1,16 @@
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.6
|
||||
|
||||
package http2
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
func transportExpectContinueTimeout(t1 *http.Transport) time.Duration {
|
||||
return t1.ExpectContinueTimeout
|
||||
}
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.7
|
||||
|
||||
package http2
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptrace"
|
||||
"time"
|
||||
)
|
||||
|
||||
type contextContext interface {
|
||||
context.Context
|
||||
}
|
||||
|
||||
func serverConnBaseContext(c net.Conn, opts *ServeConnOpts) (ctx contextContext, cancel func()) {
|
||||
ctx, cancel = context.WithCancel(context.Background())
|
||||
ctx = context.WithValue(ctx, http.LocalAddrContextKey, c.LocalAddr())
|
||||
if hs := opts.baseConfig(); hs != nil {
|
||||
ctx = context.WithValue(ctx, http.ServerContextKey, hs)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func contextWithCancel(ctx contextContext) (_ contextContext, cancel func()) {
|
||||
return context.WithCancel(ctx)
|
||||
}
|
||||
|
||||
func requestWithContext(req *http.Request, ctx contextContext) *http.Request {
|
||||
return req.WithContext(ctx)
|
||||
}
|
||||
|
||||
type clientTrace httptrace.ClientTrace
|
||||
|
||||
func reqContext(r *http.Request) context.Context { return r.Context() }
|
||||
|
||||
func (t *Transport) idleConnTimeout() time.Duration {
|
||||
if t.t1 != nil {
|
||||
return t.t1.IdleConnTimeout
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func setResponseUncompressed(res *http.Response) { res.Uncompressed = true }
|
||||
|
||||
func traceGotConn(req *http.Request, cc *ClientConn) {
|
||||
trace := httptrace.ContextClientTrace(req.Context())
|
||||
if trace == nil || trace.GotConn == nil {
|
||||
return
|
||||
}
|
||||
ci := httptrace.GotConnInfo{Conn: cc.tconn}
|
||||
cc.mu.Lock()
|
||||
ci.Reused = cc.nextStreamID > 1
|
||||
ci.WasIdle = len(cc.streams) == 0 && ci.Reused
|
||||
if ci.WasIdle && !cc.lastActive.IsZero() {
|
||||
ci.IdleTime = time.Now().Sub(cc.lastActive)
|
||||
}
|
||||
cc.mu.Unlock()
|
||||
|
||||
trace.GotConn(ci)
|
||||
}
|
||||
|
||||
func traceWroteHeaders(trace *clientTrace) {
|
||||
if trace != nil && trace.WroteHeaders != nil {
|
||||
trace.WroteHeaders()
|
||||
}
|
||||
}
|
||||
|
||||
func traceGot100Continue(trace *clientTrace) {
|
||||
if trace != nil && trace.Got100Continue != nil {
|
||||
trace.Got100Continue()
|
||||
}
|
||||
}
|
||||
|
||||
func traceWait100Continue(trace *clientTrace) {
|
||||
if trace != nil && trace.Wait100Continue != nil {
|
||||
trace.Wait100Continue()
|
||||
}
|
||||
}
|
||||
|
||||
func traceWroteRequest(trace *clientTrace, err error) {
|
||||
if trace != nil && trace.WroteRequest != nil {
|
||||
trace.WroteRequest(httptrace.WroteRequestInfo{Err: err})
|
||||
}
|
||||
}
|
||||
|
||||
func traceFirstResponseByte(trace *clientTrace) {
|
||||
if trace != nil && trace.GotFirstResponseByte != nil {
|
||||
trace.GotFirstResponseByte()
|
||||
}
|
||||
}
|
||||
|
||||
func requestTrace(req *http.Request) *clientTrace {
|
||||
trace := httptrace.ContextClientTrace(req.Context())
|
||||
return (*clientTrace)(trace)
|
||||
}
|
||||
|
||||
// Ping sends a PING frame to the server and waits for the ack.
|
||||
func (cc *ClientConn) Ping(ctx context.Context) error {
|
||||
return cc.ping(ctx)
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.7,!go1.8
|
||||
|
||||
package http2
|
||||
|
||||
import "crypto/tls"
|
||||
|
||||
// temporary copy of Go 1.7's private tls.Config.clone:
|
||||
func cloneTLSConfig(c *tls.Config) *tls.Config {
|
||||
return &tls.Config{
|
||||
Rand: c.Rand,
|
||||
Time: c.Time,
|
||||
Certificates: c.Certificates,
|
||||
NameToCertificate: c.NameToCertificate,
|
||||
GetCertificate: c.GetCertificate,
|
||||
RootCAs: c.RootCAs,
|
||||
NextProtos: c.NextProtos,
|
||||
ServerName: c.ServerName,
|
||||
ClientAuth: c.ClientAuth,
|
||||
ClientCAs: c.ClientCAs,
|
||||
InsecureSkipVerify: c.InsecureSkipVerify,
|
||||
CipherSuites: c.CipherSuites,
|
||||
PreferServerCipherSuites: c.PreferServerCipherSuites,
|
||||
SessionTicketsDisabled: c.SessionTicketsDisabled,
|
||||
SessionTicketKey: c.SessionTicketKey,
|
||||
ClientSessionCache: c.ClientSessionCache,
|
||||
MinVersion: c.MinVersion,
|
||||
MaxVersion: c.MaxVersion,
|
||||
CurvePreferences: c.CurvePreferences,
|
||||
DynamicRecordSizingDisabled: c.DynamicRecordSizingDisabled,
|
||||
Renegotiation: c.Renegotiation,
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user