添加注册验证码接口

master
18650180552 2019-01-31 16:57:22 +08:00
parent 48c5a9c552
commit b133067a85
404 changed files with 38277 additions and 139934 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
glide.yaml
api_user.exe

View File

@ -17,11 +17,17 @@ type ConfAPI struct {
RunMode string `yaml:"runmode"` // 服务运行模式 RunMode string `yaml:"runmode"` // 服务运行模式
MaxConn int `yaml:"max_conn"` MaxConn int `yaml:"max_conn"`
Logs LogConfig `yaml:"logs"` // 日志 Logs LogConfig `yaml:"logs"` // 日志
Redis RedisConfig `yaml:"redis"` Redis1 EntityRedis `yaml:"redis1"`
Mysql MysqlConfig `yaml:"mysql"` // 认证配置 Mysql MysqlConfig `yaml:"mysql"` // 认证配置
init bool init bool
} }
type EntityRedis struct {
Addr string `yaml:"addr"`
Pwd string `yaml:"password"`
DB int `yaml:"db"`
PoolSize int `yaml:"poolsize"`
}
type RedisConfig struct { type RedisConfig struct {
Addr string `yaml:"addr"` Addr string `yaml:"addr"`
Pwd string `yaml:"password"` Pwd string `yaml:"password"`
@ -73,6 +79,13 @@ func GetMysqlConfig() *MysqlConfig{
return nil return nil
} }
} }
func GetRedis1() *EntityRedis {
if gConf.init{
return &gConf.Redis1
}else {
return nil
}
}
func GetLogConfig() *LogConfig { func GetLogConfig() *LogConfig {
if gConf.init{ if gConf.init{
return &gConf.Logs return &gConf.Logs

48
config/redisConf.go Normal file
View File

@ -0,0 +1,48 @@
package config
import (
"fmt"
"github.com/pkg/errors"
"gopkg.in/redis.v4"
"user/logs"
)
var (
gRedis1 *redis.Client
)
func initRedis(addr string, pwd string, db int, coon int) (*redis.Client, error) {
red := redis.NewClient(&redis.Options{
Addr: addr,
Password: pwd,
DB: db,
PoolSize: coon,
})
_, err := red.Ping().Result()
if nil != err {
red.Close()
return nil, errors.New(fmt.Sprintf("fail to ping redis,addr :%s , pwd :%s ,DB :%d", addr, pwd, db))
}
return red, nil
}
func InitRedis() error {
redisConf := GetRedis1()
if nil == redisConf{
return errors.New("Error Config Redis")
}else {
var e error
gRedis1,e = initRedis(GetRedis1().Addr,GetRedis1().Pwd,GetRedis1().DB,GetRedis1().PoolSize)
if nil != e{
logs.Error(e.Error())
return e
}
}
return nil
}
func RedisOne() *redis.Client {
return gRedis1
}

View File

@ -520,13 +520,7 @@ func SendEmailCode(c *gin.Context) {
verify := CreateVerify(6) verify := CreateVerify(6)
session := sessions.Get(c) session := sessions.Get(c)
session.Set(req.EmailAdress,verify) session.Set(req.EmailAdress,verify)
/*
var Options *sessions.Options
Options = &sessions.Options{
MaxAge: 60,
}
session.Options(*Options)*/
session.Save() session.Save()
sendcontent := make( map[string] interface{},1) sendcontent := make( map[string] interface{},1)
sendcontent["subject"] = "邮箱验证码,请注意查收" sendcontent["subject"] = "邮箱验证码,请注意查收"

View File

@ -4,6 +4,7 @@ import (
"bufio" "bufio"
"bytes" "bytes"
"errors" "errors"
"fmt"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"io/ioutil" "io/ioutil"
"log" "log"
@ -12,6 +13,9 @@ import (
"regexp" "regexp"
"strings" "strings"
"text/template" "text/template"
"time"
"user/controller"
"user/config"
) )
type RespJson struct { type RespJson struct {
@ -49,7 +53,7 @@ func GetTemplateFile(template string) ([]byte, error) {
//静态文件存放地 //静态文件存放地
const PATH_STATIC = "G://GoPath//Email//static//" const PATH_STATIC = "G://GoPath//Email//static//"
type ReqSendEmail struct { type ReqSendEmailTpl struct {
From string `json:"from"` From string `json:"from"`
To string `json:"to"` To string `json:"to"`
Tittle string `json:"tittle"` Tittle string `json:"tittle"`
@ -57,8 +61,38 @@ type ReqSendEmail struct {
Template string `json:"template"` Template string `json:"template"`
Generate bool `json:"generate"` Generate bool `json:"generate"`
} }
type ReqSendEmail struct {
Id int `json:"id"`
From string `json:"from"`
To string `json:"to"`
Content string `json:"content"`
}
func SendToMail(title, user, password, host, to, content string) error {
var content_type string
hp := strings.Split(host, ":")
auth := smtp.PlainAuth("", user, password, hp[0])
func SendToMail(title, user, password, host, to, tplname string, content interface{}, mailtype string, ifgenerate bool) error { msg := []byte("To: " + to + "\r\nFrom: " + user + "\r\nSubject: " + title + "\r\n" +
content_type + "\r\n\r\n" + content + "\r\n")
send_to := strings.Split(to, ";")
//检测是否是邮件地址
for k, _ := range send_to {
match, _ := regexp.MatchString("[\\w!#$%&'*+/=?^_`{|}~-]+(?:\\.[\\w!#$%&'*+/=?^_`{|}~-]+)*@(?:[\\w](?:[\\w-]*[\\w])?\\.)+[\\w](?:[\\w-]*[\\w])?", send_to[k])
if !match {
return errors.New("Format Error")
}
}
err := smtp.SendMail(host, auth, user, send_to, msg)
if err != nil {
return err
}
return err
}
func SendToMailTpl(title, user, password, host, to, tplname string, content interface{}, mailtype string, ifgenerate bool) error {
var content_type string var content_type string
var paseresult bytes.Buffer var paseresult bytes.Buffer
writer := bufio.NewWriter(&paseresult) writer := bufio.NewWriter(&paseresult)
@ -168,7 +202,41 @@ func OnSendEmailSendCloud(c *gin.Context){
//content :网页模板的参数 key-value结构 //content :网页模板的参数 key-value结构
//temp_data 模板内具体要替换的变量名字 Key-value结构 //temp_data 模板内具体要替换的变量名字 Key-value结构
//generate 是否生成静态html //generate 是否生成静态html
func OnSendEmail(c *gin.Context) { func OnSendEmailTpl(c *gin.Context) {
var req ReqSendEmailTpl
var resp RespJson
defer func() {
c.JSON(200, resp)
}()
e := c.Bind(&req)
if e != nil {
log.Println(e.Error())
resp.Msg = "ParaErr"
return
}
user := "c7458969@163.com"
password := "caiyu123"
host := "smtp.163.com:25"
//抄送给自己
//e = SendToMail(user,password,host,req.From,req.Template,req.Content,"html")
//发送
e = SendToMailTpl(req.Tittle, user, password, host, req.To, req.Template, req.TempData, "html", req.Generate)
if nil != e {
log.Println(e.Error())
resp.Msg = "Error"
return
}
resp.Msg = "OK"
}
//from: 发送人邮箱
//to:接收邮件,可以是"290198252@qq.com;29019822@qq.com;2901982@qq.com" 邮箱之间用分号隔开
//template:模板名字
//content :网页模板的参数 key-value结构
//temp_data 模板内具体要替换的变量名字 Key-value结构
//generate 是否生成静态html
func OnSendEmailCode(c *gin.Context) {
var req ReqSendEmail var req ReqSendEmail
var resp RespJson var resp RespJson
defer func() { defer func() {
@ -187,7 +255,11 @@ func OnSendEmail(c *gin.Context) {
//抄送给自己 //抄送给自己
//e = SendToMail(user,password,host,req.From,req.Template,req.Content,"html") //e = SendToMail(user,password,host,req.From,req.Template,req.Content,"html")
//发送 //发送
e = SendToMail(req.Tittle, user, password, host, req.To, req.Template, req.TempData, "html", req.Generate) verCode := controller.CreateVerify(6)
content := "您的验证码是" + verCode
userKey := fmt.Sprintf("user_%d_verify",req.Id)
config.RedisOne().Set(userKey,verCode,time.Hour * 24)
e = SendToMail("后台管理系统验证码", user, password, host, req.To, content)
if nil != e { if nil != e {
log.Println(e.Error()) log.Println(e.Error())
resp.Msg = "Error" resp.Msg = "Error"

View File

@ -27,10 +27,14 @@ func main() {
} }
logs.Init(config.GetLogConfig().Dir,config.GetLogConfig().File,config.GetLogConfig().Level,config.GetLogConfig().SaveFile) logs.Init(config.GetLogConfig().Dir,config.GetLogConfig().File,config.GetLogConfig().Level,config.GetLogConfig().SaveFile)
db.Init() db.Init()
e = config.InitRedis()
if nil != e{
logs.Error(e.Error())
return
}
r := gin.Default() r := gin.Default()
store := sessions.NewCookieStore([]byte("secret123")) store := sessions.NewCookieStore([]byte("secret123"))
r.Use(sessions.Middleware("my_session", store)) r.Use(sessions.Middleware("my_session", store))
{ {
/** 添加或修改用户 **/ /** 添加或修改用户 **/
r.POST("/api/user", controller.SetUser) r.POST("/api/user", controller.SetUser)
@ -46,6 +50,8 @@ func main() {
r.POST("/api/register", controller.Register) r.POST("/api/register", controller.Register)
/** 用户退出登陆 **/ /** 用户退出登陆 **/
r.GET("/api/logout", controller.Logout) r.GET("/api/logout", controller.Logout)
r.POST("/api/email_code",controller.SendEmailCode)
} }
r.Run(":" + strconv.Itoa(config.GetPort())) r.Run(":" + strconv.Itoa(config.GetPort()))
} }

View File

@ -6,9 +6,9 @@ logs:
file: "user.log" file: "user.log"
level: 1 level: 1
savefile: false savefile: false
redis: redis1:
addr: 118.24.238.198 addr: 118.24.238.198:16379
password: 6379 password:
db: 1 db: 1
mysql: mysql:
addr: 118.24.238.198 addr: 118.24.238.198

202
vendor/cloud.google.com/go/LICENSE generated vendored
View File

@ -1,202 +0,0 @@
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:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) 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
(d) 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.

View File

@ -1,4 +1,4 @@
// Copyright 2016 Google LLC // Copyright 2016 Google Inc. All Rights Reserved.
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.

0
vendor/git.jiaxianghudong.com/go/db/README.md generated vendored Normal file
View File

475
vendor/git.jiaxianghudong.com/go/db/db.go generated vendored Normal file
View File

@ -0,0 +1,475 @@
// 数据库工具包
package db
import (
"database/sql"
"errors"
"fmt"
"reflect"
"strconv"
"sync"
"time"
)
// 数据容器抽象对象定义
type Database struct {
Type string // 用来给SqlBuilder进行一些特殊的判断 (空值或mysql 皆表示这是一个MySQL实例)
DB *sql.DB
}
// SQL异步执行队列定义
type queueList struct {
list []*QueueItem //队列列表
sleeping chan bool
loop chan bool
lock sync.RWMutex
quit chan bool
quited bool
}
// SQL异步执行队列子元素定义
type QueueItem struct {
DB *Database //数据库对象
Query string //SQL语句字符串
Params []interface{} //参数列表
}
// 缓存数据对象定义
type cache struct {
data map[string]map[string]interface{}
}
func (this *cache) Init() {
this.data["default"] = make(map[string]interface{})
}
// 设置缓存
func (this *cache) Set(key string, value interface{}, args ...string) {
var group string
if len(args) > 0 {
group = args[0]
if _, exist := this.data[group]; !exist {
this.data[group] = make(map[string]interface{})
}
} else {
group = "default"
}
this.data[group][key] = value
}
// 获取缓存数据
func (this *cache) Get(key string, args ...string) interface{} {
var group string
if len(args) > 0 {
group = args[0]
} else {
group = "default"
}
if g, exist := this.data[group]; exist {
if v, ok := g[key]; ok {
return v
}
}
return nil
}
// 删除缓存数据
func (this *cache) Del(key string, args ...string) {
var group string
if len(args) > 0 {
group = args[0]
} else {
group = "default"
}
if g, exist := this.data[group]; exist {
if _, ok := g[key]; ok {
delete(this.data[group], key)
}
}
}
var (
lastError error
Cache *cache
queue *queueList
Obj *Database
)
func init() {
Cache = &cache{data: make(map[string]map[string]interface{})}
Cache.Init()
queue = &queueList{}
go queue.Start()
}
// 关闭数据库连接
func (this *Database) Close() {
this.DB.Close()
}
// 获取最后发生的错误字符串
func LastErr() string {
if lastError != nil {
return lastError.Error()
}
return ""
}
// 执行语句
func (this *Database) Exec(query string, args ...interface{}) (sql.Result, error) {
return this.DB.Exec(query, args...)
}
// 查询单条记录
func (this *Database) Query(query string, args ...interface{}) (*sql.Rows, error) {
return this.DB.Query(query, args...)
}
// 查询单条记录
func (this *Database) QueryRow(query string, args ...interface{}) *sql.Row {
return this.DB.QueryRow(query, args...)
}
// Query2 查询实体集合
// obj 为接收数据的实体指针
func (this *Database) Query2(sql string, obj interface{}, args ...interface{}) error {
var tagMap map[string]int
var tp, tps reflect.Type
var n, i int
var err error
var ret reflect.Value
// 检测val参数是否为我们所想要的参数
tp = reflect.TypeOf(obj)
if reflect.Ptr != tp.Kind() {
return errors.New("is not pointer")
}
if reflect.Slice != tp.Elem().Kind() {
return errors.New("is not slice pointer")
}
tp = tp.Elem()
tps = tp.Elem()
if reflect.Struct != tps.Kind() {
return errors.New("is not struct slice pointer")
}
tagMap = make(map[string]int)
n = tps.NumField()
for i = 0; i < n; i++ {
tag := tps.Field(i).Tag.Get("sql")
if len(tag) > 0 {
tagMap[tag] = i + 1
}
}
// 执行查询
ret, err = this.queryAndReflect(sql, tagMap, tp, args...)
if nil != err {
return err
}
// 返回结果
reflect.ValueOf(obj).Elem().Set(ret)
return nil
}
// queryAndReflect 查询并将结果反射成实体集合
func (this *Database) queryAndReflect(sql string,
tagMap map[string]int,
tpSlice reflect.Type, args ...interface{}) (reflect.Value, error) {
var ret reflect.Value
// 执行sql语句
rows, err := this.DB.Query(sql, args...)
if nil != err {
return reflect.Value{}, err
}
defer rows.Close()
// 开始枚举结果
cols, err := rows.Columns()
if nil != err {
return reflect.Value{}, err
}
ret = reflect.MakeSlice(tpSlice, 0, 50)
// 构建接收队列
scan := make([]interface{}, len(cols))
row := make([]interface{}, len(cols))
for r := range row {
scan[r] = &row[r]
}
for rows.Next() {
feild := reflect.New(tpSlice.Elem()).Elem()
// 取得结果
err = rows.Scan(scan...)
// 开始遍历结果
for i := 0; i < len(cols); i++ {
n := tagMap[cols[i]] - 1
if n < 0 {
continue
}
switch feild.Type().Field(n).Type.Kind() {
case reflect.Bool:
if nil != row[i] {
feild.Field(n).SetBool("false" != string(row[i].([]byte)))
} else {
feild.Field(n).SetBool(false)
}
case reflect.String:
if nil != row[i] {
feild.Field(n).SetString(string(row[i].([]byte)))
} else {
feild.Field(n).SetString("")
}
case reflect.Float32:
fallthrough
case reflect.Float64:
if nil != row[i] {
v, e := strconv.ParseFloat(string(row[i].([]byte)), 0)
if nil == e {
feild.Field(n).SetFloat(v)
}
} else {
feild.Field(n).SetFloat(0)
}
case reflect.Int8:
fallthrough
case reflect.Int16:
fallthrough
case reflect.Int32:
fallthrough
case reflect.Int64:
fallthrough
case reflect.Int:
if nil != row[i] {
byRow, ok := row[i].([]byte)
if ok {
v, e := strconv.ParseInt(string(byRow), 10, 64)
if nil == e {
feild.Field(n).SetInt(v)
}
} else {
v, e := strconv.ParseInt(fmt.Sprint(row[i]), 10, 64)
if nil == e {
feild.Field(n).SetInt(v)
}
}
} else {
feild.Field(n).SetInt(0)
}
}
}
ret = reflect.Append(ret, feild)
}
return ret, nil
}
// 执行UPDATE语句并返回受影响的行数
// 返回0表示没有出错, 但没有被更新的行
// 返回-1表示出错
func (this *Database) Update(query string, args ...interface{}) (int64, error) {
ret, err := this.Exec(query, args...)
if err != nil {
return -1, err
}
aff, err := ret.RowsAffected()
if err != nil {
return -1, err
}
return aff, nil
}
// 执行DELETE语句并返回受影响的行数
// 返回0表示没有出错, 但没有被删除的行
// 返回-1表示出错
func (this *Database) Delete(query string, args ...interface{}) (int64, error) {
return this.Update(query, args...)
}
// 执行INSERT语句并返回最后生成的自增ID
// 返回0表示没有出错, 但没生成自增ID
// 返回-1表示出错
func (this *Database) Insert(query string, args ...interface{}) (int64, error) {
ret, err := this.Exec(query, args...)
if err != nil {
return -1, err
}
last, err := ret.LastInsertId()
if err != nil {
return -1, err
}
return last, nil
}
type OneRow map[string]string
type Results []OneRow
// 判断字段是否存在
func (row OneRow) Exist(field string) bool {
if _, ok := row[field]; ok {
return true
}
return false
}
// 获取指定字段的值
func (row OneRow) Get(field string) string {
if v, ok := row[field]; ok {
return v
}
return ""
}
// 获取指定字段的整数值, 注意, 如果该字段不存在则会返回0
func (row OneRow) GetInt(field string) int {
if v, ok := row[field]; ok {
return Atoi(v)
}
return 0
}
// 获取指定字段的整数值, 注意, 如果该字段不存在则会返回0
func (row OneRow) GetInt64(field string) int64 {
if v, ok := row[field]; ok {
return Atoi64(v)
}
return 0
}
// 设置值
func (row OneRow) Set(key, val string) {
row[key] = val
}
// 查询不定字段的结果集
func (this *Database) Select(query string, args ...interface{}) (Results, error) {
rows, err := this.DB.Query(query, args...)
if err != nil {
return nil, err
}
defer rows.Close()
cols, err := rows.Columns()
if err != nil {
return nil, err
}
colNum := len(cols)
rawValues := make([][]byte, colNum)
scans := make([]interface{}, len(cols)) //query.Scan的参数因为每次查询出来的列是不定长的所以传入长度固定当次查询的长度
// 将每行数据填充到[][]byte里
for i := range rawValues {
scans[i] = &rawValues[i]
}
results := make(Results, 0)
for rows.Next() {
err = rows.Scan(scans...)
if err != nil {
return nil, err
}
row := make(map[string]string)
for k, raw := range rawValues {
key := cols[k]
/*if raw == nil {
row[key] = "\\N"
} else {*/
row[key] = string(raw)
//}
}
results = append(results, row)
}
return results, nil
}
// 查询一行不定字段的结果
func (this *Database) SelectOne(query string, args ...interface{}) (OneRow, error) {
ret, err := this.Select(query, args...)
if err != nil {
return nil, err
}
if len(ret) > 0 {
return ret[0], nil
}
return make(OneRow), nil
}
// 队列入栈
func (this *queueList) Push(item *QueueItem) {
this.lock.Lock()
this.list = append(this.list, item)
this.lock.Unlock()
}
// 队列出栈
func (this *queueList) Pop() chan *QueueItem {
item := make(chan *QueueItem)
go func() {
defer close(item)
for {
switch {
case len(this.list) == 0:
timeout := time.After(time.Second * 2)
select {
case <-this.quit:
this.quited = true
return
case <-timeout:
//log.Println("SQL Queue polling")
}
default:
this.lock.Lock()
i := this.list[0]
this.list = this.list[1:]
this.lock.Unlock()
select {
case item <- i:
return
case <-this.quit:
this.quited = true
return
}
}
}
}()
return item
}
// 执行开始执行
func (this *queueList) Start() {
for {
if this.quited {
return
}
c := this.Pop()
item := <-c
item.DB.Exec(item.Query, item.Params...)
}
}
// 停止队列
func (this *queueList) Stop() {
this.quit <- true
}
// 向Sql队列中插入一条执行语句
func (this *Database) Queue(query string, args ...interface{}) {
item := &QueueItem{
DB: this,
Query: query,
Params: args,
}
queue.Push(item)
}

76
vendor/git.jiaxianghudong.com/go/db/mssql.go generated vendored Normal file
View File

@ -0,0 +1,76 @@
package db
import (
"database/sql"
_ "github.com/denisenkom/go-mssqldb"
)
// ProcExec 执行存储过程, 返回受影响的行数
func (this *Database) ExecProc(procname string, params ...interface{}) (int64, error) {
result, err := this.Exec("EXEC " + procname + " " + this.GetProcPlaceholder(len(params)), params...)
if err != nil {
return 0, err
}
affected, err := result.RowsAffected()
if err != nil {
return 0, err
}
lastinsertid, err := result.LastInsertId()
if err != nil {
return affected, nil
}
return lastinsertid, nil
}
// GetExecProcErr 执行存储过程, 返回是否在执行过程中出现错误
func (this *Database) GetExecProcErr(procname string, params ...interface{}) error {
_, err := this.ExecProc(procname, params...)
if err != nil {
return err
}
return nil
}
// ProcQuery 通过存储过程查询记录
func (this *Database) ProcQuery(procname string, params ...interface{}) (rows *sql.Rows, err error) {
rows, err = this.Query("EXEC " + procname + " " + this.GetProcPlaceholder(len(params)), params...)
return
}
// ProcQueryRow 通过存储过程查询单条记录
func (this *Database) ProcQueryRow(procname string, params ...interface{}) *sql.Row {
return this.QueryRow("EXEC " + procname + " " + this.GetProcPlaceholder(len(params)), params...)
}
// ProcStatus 调用存储过程并获取最终的执行状态码和提示信息
func (this *Database) ProcStatus(procname string, params ...interface{}) (int, string) {
var status int
var msg string
err := this.QueryRow("EXEC " + procname + " " + this.GetProcPlaceholder(len(params)), params...).Scan(&status, &msg)
if err != nil {
return -99, err.Error()
}
return status, msg
}
// ProcSelect 通过存储过程查询结果集
func (this *Database) ProcSelect(procname string, params ...interface{}) (Results, error) {
return this.Select("EXEC " + procname + " " + this.GetProcPlaceholder(len(params)), params...)
}
// ProcSelectOne 通过存储查询一行不定字段的结果
func (this *Database) ProcSelectOne(procname string, params ...interface{}) (OneRow, error) {
return this.SelectOne("EXEC " + procname + " " + this.GetProcPlaceholder(len(params)), params...)
}
// GetProcPlaceholder 按照指定数量生成调用存储过程时所用的参数占位符
func (this *Database) GetProcPlaceholder(count int) (placeholder string) {
placeholder = ""
for i := 0; i < count; i++ {
if i > 0 {
placeholder += ","
}
placeholder += "?"
}
return
}

542
vendor/git.jiaxianghudong.com/go/db/sql_builder.go generated vendored Normal file
View File

@ -0,0 +1,542 @@
package db
import (
"database/sql"
"errors"
"strconv"
"strings"
"log"
"fmt"
"reflect"
"math/big"
"git.jiaxianghudong.com/go/utils"
)
const (
_ = iota
TYPE_INSERT
TYPE_DELETE
TYPE_UPDATE
TYPE_SELECT
TYPE_INSERTUPDATE
)
var (
WrapSymbol = "`"
DBType = "mysql"
)
// SQL语句构造结构
type SB struct {
db *Database
t int
field, table, where, group, order, limit string
values SBValues
values2 SBValues
ignore bool
fullsql bool
debug bool
unsafe bool //是否进行安全检查, 专门针对无限定的UPDATE和DELETE进行二次验证
args []interface{}
}
// Exec返回结果
type SBResult struct {
Success bool //语句是否执行成功
Code int //错误代码
Msg string //错误提示信息
LastID int64 //最后产生的ID
Affected int64 //受影响的行数
Sql string //最后执行的SQL
}
// 值对象
type SBValues map[string]interface{}
// 增量值
type IncVal struct {
Val int64
BaseField string // 为空表示对当前字段累加
}
// 向值对象中加入值
func (v SBValues) Add(key string, val interface{}) {
v[key] = val
}
// 删除值对象中的某个值
func (v SBValues) Del(key string) {
delete(v, key)
}
// 判断指定键是否存在
func (v SBValues) IsExist(key string) bool {
if _, exist := v[key]; exist {
return true
}
return false
}
// 获取键的整形值
func (v SBValues) Get(key string) interface{} {
if val, exist := v[key]; exist {
return val
}
return nil
}
// 获取键的字符串值
func (v SBValues) GetString(key string) string {
if val, exist := v[key]; exist {
if trueVal, ok := val.(string); ok {
return trueVal
}
}
return ""
}
// 获取键的整形值
func (v SBValues) GetInt(key string) int {
if val, exist := v[key]; exist {
if trueVal, ok := val.(int); ok {
return trueVal
}
}
return 0
}
// 获取键的无符号整形值
func (v SBValues) GetUint(key string) uint {
if val, exist := v[key]; exist {
if trueVal, ok := val.(uint); ok {
return trueVal
}
}
return 0
}
// 获取键的64位整形值
func (v SBValues) GetInt64(key string) int64 {
if val, exist := v[key]; exist {
if trueVal, ok := val.(int64); ok {
return trueVal
}
}
return 0
}
// 返回绑定完参数的完整的SQL语句
func FullSql(str string, args ...interface{}) (string, error) {
if !strings.Contains(str, "?") {
return str, nil
}
sons := strings.Split(str, "?")
var ret string
var argIndex int
var maxArgIndex = len(args)
for _, son := range sons {
ret += son
if argIndex < maxArgIndex {
switch v := args[argIndex].(type) {
case int:
ret += strconv.Itoa(v)
case int8:
ret += strconv.Itoa(int(v))
case int16:
ret += strconv.Itoa(int(v))
case int32:
ret += utils.I64toA(int64(v))
case int64:
ret += utils.I64toA(v)
case uint:
ret += utils.UitoA(v)
case uint8:
ret += utils.UitoA(uint(v))
case uint16:
ret += utils.UitoA(uint(v))
case uint32:
ret += utils.Ui32toA(v)
case uint64:
ret += utils.Ui64toA(v)
case float32:
ret += utils.F32toA(v)
case float64:
ret += utils.F64toA(v)
case *big.Int:
ret += v.String()
case bool:
if v {
ret += "true"
} else {
ret += "false"
}
case string:
ret += "'" + strings.Replace(strings.Replace(v, "'", "", -1), `\`, `\\`, -1) + "'"
case nil:
ret += "NULL"
default:
return "", errors.New(fmt.Sprintf("invalid sql argument type: %v => %v (sql: %s)", reflect.TypeOf(v).String(), v, str))
}
argIndex++
}
}
return ret, nil
}
// 构建SQL语句
// param: returnFullSql 是否返回完整的sql语句(即:绑定参数之后的语句)
func (q *SB) ToSql(returnFullSql ...bool) (str string, err error) {
q.args = make([]interface{}, 0)
switch q.t {
case TYPE_INSERT:
if q.table == "" {
err = errors.New("table cannot be empty.")
return
}
if len(q.values) == 0 {
err = errors.New("values cannot be empty.")
return
}
if q.ignore {
str = "INSERT IGNORE INTO " + q.table
} else {
str = "INSERT INTO " + q.table
}
var fields, placeholder string
for k, v := range q.values {
fields += "," + WrapSymbol + k + WrapSymbol
placeholder += ",?"
q.args = append(q.args, v)
}
str += " (" + utils.Substr(fields, 1) + ") VALUES (" + utils.Substr(placeholder, 1) + ")"
case TYPE_DELETE:
if q.table != "" {
if q.where == "" && !q.unsafe {
err = errors.New("deleting all data is not safe.")
return
}
str = "DELETE " + q.table
if q.table != "" {
str += " FROM " + q.table
}
if q.where != "" {
str += " WHERE " + q.where
}
}
case TYPE_UPDATE:
if q.table != "" {
if q.where == "" && !q.unsafe {
err = errors.New("updating all data is not safe.")
return
}
str = "UPDATE " + q.table
str += " SET " + utils.Substr(q.buildUpdateParams(q.values), 1)
if q.where != "" {
str += " WHERE " + q.where
}
}
case TYPE_INSERTUPDATE:
if q.table != "" {
str = "INSERT INTO " + q.table
var fields, placeholder string
for k, v := range q.values {
fields += "," + WrapSymbol + k + WrapSymbol
placeholder += ",?"
q.args = append(q.args, v)
}
str += " (" + utils.Substr(fields, 1) + ") VALUES (" + utils.Substr(placeholder, 1) + ") ON DUPLICATE KEY UPDATE "
placeholder = q.buildUpdateParams(q.values2)
str += utils.Substr(placeholder, 1)
}
case TYPE_SELECT:
str = "SELECT " + q.field
if q.table != "" {
str += " FROM " + q.table
}
if q.where != "" {
str += " WHERE " + q.where
}
if q.group != "" {
str += " GROUP BY " + q.group
}
if q.order != "" {
str += " ORDER BY " + q.order
}
if q.limit != "" && (q.db.Type == "" || q.db.Type == "mysql") {
str += " LIMIT " + q.limit
}
}
if len(returnFullSql) == 1 && returnFullSql[0] {
str, err = FullSql(str, q.args...)
}
return
}
// 构造Update更新参数
func (q *SB) buildUpdateParams(vals SBValues) string {
var placeholder string
for k, v := range vals {
if iv, ok := v.(IncVal); ok {
placeholder += "," + WrapSymbol + k + WrapSymbol + "=" + utils.Ternary(iv.BaseField == "", k, iv.BaseField).(string)
if iv.Val >= 0 {
placeholder += "+" + utils.I64toA(iv.Val)
} else {
placeholder += utils.I64toA(iv.Val)
}
} else {
placeholder += "," + WrapSymbol + k + WrapSymbol + "=?"
q.args = append(q.args, v)
}
}
return placeholder
}
// 设置数据库对象
func (q *SB) DB(db *Database) *SB {
q.db = db
return q
}
// 设置FROM字句
func (q *SB) From(str string) *SB {
q.table = str
return q
}
// 设置表名
func (q *SB) Table(str string) *SB {
return q.From(str)
}
// 设置WHERE字句
func (q *SB) Where(str string) *SB {
q.where = str
return q
}
// 设置GROUP字句
func (q *SB) Group(str string) *SB {
q.group = str
return q
}
// 设置GROUP字句
func (q *SB) Order(str string) *SB {
q.order = str
return q
}
// 设置LIMIT字句
func (q *SB) Limit(count int, offset ...int) *SB {
if len(offset) > 0 {
q.limit = utils.Itoa(offset[0]) + "," + utils.Itoa(count)
} else {
q.limit = "0," + utils.Itoa(count)
}
return q
}
// 设置安全检查开关
func (q *SB) Unsafe(unsefe ...bool) *SB {
if len(unsefe) == 1 && !unsefe[0] {
q.unsafe = false
} else {
q.unsafe = true
}
return q
}
// 是否Debug
func (q *SB) Debug(debug ...bool) *SB {
if len(debug) == 1 && !debug[0] {
q.debug = false
} else {
q.debug = true
}
return q
}
// 设置值
func (q *SB) Value(m SBValues) *SB {
q.values = m
return q
}
// 设置值2
func (q *SB) Value2(m SBValues) *SB {
q.values2 = m
return q
}
// 添加值
func (q *SB) AddValue(key string, val interface{}) *SB {
q.values.Add(key, val)
return q
}
// 添加值2
func (q *SB) AddValue2(key string, val interface{}) *SB {
q.values2.Add(key, val)
return q
}
// 获取一个值对象
func NewValues() SBValues {
return SBValues{}
}
// 构建INSERT语句
func Insert(ignore ...bool) *SB {
var i bool
if len(ignore) == 1 && ignore[0] {
i = true
}
return &SB{t: TYPE_INSERT, db: Obj, ignore: i, values: SBValues{}, args: make([]interface{}, 0)}
}
// 构建DELETE语句
func Delete() *SB {
return &SB{t: TYPE_DELETE, db: Obj}
}
// 构建UPDATE语句
func Update() *SB {
return &SB{t: TYPE_UPDATE, db: Obj, values: SBValues{}, args: make([]interface{}, 0)}
}
// 构建InsertUpdate语句, 仅针对MySQL有效, 内部使用ON DUPLICATE KEY UPDATE方式实现
func InsertUpdate() *SB {
return &SB{t: TYPE_INSERTUPDATE, db: Obj, values: SBValues{}, values2: SBValues{}, args: make([]interface{}, 0)}
}
// 构建SELECT语句
func Select(str ...string) *SB {
fields := "*"
if len(str) == 1 {
fields = str[0]
}
return &SB{t: TYPE_SELECT, db: Obj, field: fields}
}
// 获取构造SQL后的参数
func (q *SB) GetArgs() []interface{} {
return q.args
}
//
func (q *SB) FullSql(yes ...bool) *SB {
if len(yes) == 1 {
q.fullsql = yes[0]
} else {
q.fullsql = true
}
return q
}
// 执行INSERT、DELETE、UPDATE语句
func (q *SB) Exec(args ...interface{}) *SBResult {
var err error
sbRet := &SBResult{}
sbRet.Sql, err = q.ToSql()
if err != nil {
sbRet.Msg = err.Error()
} else {
if q.debug {
log.Println("\n\tSQL prepare statement:\n\t", sbRet.Sql, "\n\tMap args:\n\t", q.args, "\n\tParams:\n\t", args)
}
var ret sql.Result
var err error
if q.fullsql {
var sqlStr string
sqlStr, err = FullSql(sbRet.Sql, append(q.args, args...)...)
if err == nil {
ret, err = q.db.Exec(sqlStr)
}
} else {
ret, err = q.db.Exec(sbRet.Sql, append(q.args, args...)...)
}
if err != nil {
sbRet.Msg = err.Error()
} else {
sbRet.Success = true
switch q.t {
case TYPE_INSERT:
if DBType == "mysql" {
last, err := ret.LastInsertId()
if (err == nil) {
sbRet.LastID = last;
}
}
case TYPE_DELETE:
fallthrough
case TYPE_UPDATE:
fallthrough
case TYPE_INSERTUPDATE:
aff, err := ret.RowsAffected()
if (err == nil) {
sbRet.Affected = aff
}
}
}
}
return sbRet
}
// 查询记录集
func (q *SB) Query(args ...interface{}) (Results, error) {
s, e := q.ToSql()
if e != nil {
return nil, e
}
if q.debug {
log.Println("\n\tSQL prepare statement:\n\t", s, "\n\tParams:\n\t", args)
}
return q.db.Select(s, args...)
}
// 查询单行数据
func (q *SB) QueryOne(args ...interface{}) (OneRow, error) {
q.Limit(1, 0)
s, e := q.ToSql()
if e != nil {
return nil, e
}
if q.debug {
log.Println("\n\tSQL prepare statement:\n\t", s, "\n\tParams:\n\t", args)
}
return q.db.SelectOne(s, args...)
}
// 查询记录集
func (q *SB) QueryAllRow(args ...interface{}) (*sql.Rows, error) {
s, e := q.ToSql()
if e != nil {
return nil, e
}
if q.debug {
log.Println("\n\tSQL prepare statement:\n\t", s, "\n\tParams:\n\t", args)
}
return q.db.Query(s, args...)
}
// 查询单行数据
func (q *SB) QueryRow(args ...interface{}) *sql.Row {
s, e := q.ToSql()
if e != nil {
return nil
}
if q.debug {
log.Println("\n\tSQL prepare statement:\n\t", s, "\n\tParams:\n\t", args)
}
return q.db.QueryRow(s, args...)
}

66
vendor/git.jiaxianghudong.com/go/db/utils.go generated vendored Normal file
View File

@ -0,0 +1,66 @@
package db
import (
"strings"
"strconv"
"database/sql"
)
// 根据传入的字段列表生成相符数量的占位符
func GetPlaceholderByFields(fileds string) string {
fileds = strings.Replace(fileds, " ", "", -1)
fileds = strings.Trim(fileds, ",")
count := len(strings.Split(fileds, ","))
ret := make([]string, count)
for i := 0; i < count; i++ {
ret[i] = "?"
}
return strings.Join(ret, ",")
}
// Atoi 转换成整型
func Atoi(s string, d ...int) int {
i, err := strconv.Atoi(s)
if err != nil {
if len(d) > 0 {
return d[0]
} else {
return 0
}
}
return i
}
// Atoi64 转换成整型int64
func Atoi64(s string, d ...int64) int64 {
i, err := strconv.ParseInt(s, 10, 64)
if err != nil {
if len(d) > 0 {
return d[0]
} else {
return 0
}
}
return i
}
// 返回一个带有Null值的数据库字符串
func NewNullString(s string) sql.NullString {
if len(s) == 0 {
return sql.NullString{}
}
return sql.NullString{
String: s,
Valid: true,
}
}
// 返回一个带有Null值的数据库整形
func NewNullInt64(s int64, isNull bool) sql.NullInt64 {
return sql.NullInt64{
Int64: s,
Valid: !isNull,
}
}

View File

@ -15,21 +15,33 @@ const (
) )
var log *mylog var log *mylog
var LogMap map[string]mylog
var logDir string
/* /*
* *
*/ */
func init() { func init() {
log = newMylog() log = newMylog()
LogMap=make(map[string]mylog)
} }
func Init(dir string, file string, level int, savefile bool) { func Init(dir string, file string, level int, savefile bool) {
log.setDir(dir) logDir=dir
log.setDir(dir+ "/"+fmt.Sprintf("%04d%02d%02d",time.Now().Year(), time.Now().Month(), time.Now().Day()))
log.setFile(file) log.setFile(file)
log.setLevel(level) log.setLevel(level)
log.setSavefile(savefile) log.setSavefile(savefile)
} }
func InitExtLogs(dir string, file string, level int, savefile bool) {
log := newMylog()
log.setDir(dir+"/"+ fmt.Sprintf("%04d%02d%02d",time.Now().Year(), time.Now().Month(), time.Now().Day()))
log.setFile(file)
log.setLevel(level)
log.setSavefile(savefile)
LogMap[file]=*log
}
func Error(err ...interface{}) { func Error(err ...interface{}) {
log.write(LOG_ERROR, fmt.Sprint(err...)) log.write(LOG_ERROR, fmt.Sprint(err...))
} }
@ -37,9 +49,7 @@ func Error(err ...interface{}) {
func Waring(war ...interface{}) { func Waring(war ...interface{}) {
log.write(LOG_WARING, fmt.Sprint(war...)) log.write(LOG_WARING, fmt.Sprint(war...))
} }
func SetLevel(level int) {
log.setLevel(level)
}
func Info(info ...interface{}) { func Info(info ...interface{}) {
log.write(LOG_INFO, fmt.Sprint(info...)) log.write(LOG_INFO, fmt.Sprint(info...))
} }
@ -48,6 +58,23 @@ func Debug(deb ...interface{}) {
log.write(LOG_DEBUG, fmt.Sprint(deb...)) log.write(LOG_DEBUG, fmt.Sprint(deb...))
} }
func (log *mylog)Error(err ...interface{}) {
log.write(LOG_ERROR, fmt.Sprint(err...))
}
func (log *mylog)Waring(war ...interface{}) {
log.write(LOG_WARING, fmt.Sprint(war...))
}
func (log *mylog)Info(info ...interface{}) {
log.write(LOG_INFO, fmt.Sprint(info...))
}
func (log *mylog)Debug(deb ...interface{}) {
fmt.Println(*log)
log.write(LOG_DEBUG, fmt.Sprint(deb...))
}
/* /*
* *
*/ */
@ -128,15 +155,15 @@ func (l *mylog) write(level int, str string) {
func (l *mylog) run() { func (l *mylog) run() {
for { for {
str := <-l.log str := <-l.log
t:=time.Now()
// 判断文件夹是否存在 // 判断文件夹是否存在
_, err := os.Stat(l.dir) _, err := os.Stat(l.dir+"/"+fmt.Sprintf("%04d%02d%02d",t.Year(), t.Month(), t.Day()))
if nil != err { if nil != err {
l.dir=logDir+"/"+fmt.Sprintf("%04d%02d%02d",t.Year(), t.Month(), t.Day())
os.MkdirAll(l.dir, os.ModePerm) os.MkdirAll(l.dir, os.ModePerm)
} }
// 获取时间 // 获取时间
t := time.Now()
path := fmt.Sprintf("%s/%s-%04d-%02d-%02d.log", l.dir, l.file, path := fmt.Sprintf("%s/%s-%04d-%02d-%02d.log", l.dir, l.file,
t.Year(), t.Month(), t.Day()) t.Year(), t.Month(), t.Day())
fp, err := os.OpenFile(path, os.O_WRONLY|os.O_APPEND|os.O_CREATE, os.ModePerm) fp, err := os.OpenFile(path, os.O_WRONLY|os.O_APPEND|os.O_CREATE, os.ModePerm)

View File

@ -1,20 +1,11 @@
package utils package utils
import ( // 加入8字节
"bytes"
"encoding/binary"
// "errors"
// "fmt"
// "reflect"
// "unsafe"
)
// Put8bit 加入8字节到[]byte中
func Put8bit(buf []byte, n byte) []byte { func Put8bit(buf []byte, n byte) []byte {
return append(buf, n) return append(buf, n)
} }
// Put16bit 加入16字节 // 加入16字节
func Put16bit(buf []byte, n uint16) []byte { func Put16bit(buf []byte, n uint16) []byte {
var by [2]byte var by [2]byte
@ -24,7 +15,7 @@ func Put16bit(buf []byte, n uint16) []byte {
return append(buf, by[:]...) return append(buf, by[:]...)
} }
// Put32bit 加入32字节 // 加入32字节
func Put32bit(buf []byte, n uint32) []byte { func Put32bit(buf []byte, n uint32) []byte {
var by [4]byte var by [4]byte
@ -36,7 +27,7 @@ func Put32bit(buf []byte, n uint32) []byte {
return append(buf, by[:]...) return append(buf, by[:]...)
} }
// Put64bit 加入64字节 // 加入64字节
func Put64bit(buf []byte, n uint64) []byte { func Put64bit(buf []byte, n uint64) []byte {
var by [8]byte var by [8]byte
@ -52,12 +43,12 @@ func Put64bit(buf []byte, n uint64) []byte {
return append(buf, by[:]...) return append(buf, by[:]...)
} }
// Get8bit 获取8bit // 获取8bit
func Get8bit(buf []byte, start int) byte { func Get8bit(buf []byte, start int) byte {
return buf[start] return buf[start]
} }
// Get16bit 获取16bit // 获取16bit
func Get16bit(buf []byte, start int) uint16 { func Get16bit(buf []byte, start int) uint16 {
var ret uint16 var ret uint16
@ -67,7 +58,7 @@ func Get16bit(buf []byte, start int) uint16 {
return ret return ret
} }
// Get32bit 获取32big // 获取32big
func Get32bit(buf []byte, start int) uint32 { func Get32bit(buf []byte, start int) uint32 {
var ret uint32 var ret uint32
@ -79,7 +70,7 @@ func Get32bit(buf []byte, start int) uint32 {
return ret return ret
} }
// Get64bit 获取64bit // 获取64bit
func Get64bit(buf []byte, start int) uint64 { func Get64bit(buf []byte, start int) uint64 {
var ret uint64 var ret uint64
@ -94,114 +85,3 @@ func Get64bit(buf []byte, start int) uint64 {
return ret return ret
} }
// BytesToInt 字节数组转int
func BytesToInt(b []byte) int {
buf := bytes.NewBuffer(b)
var x int
binary.Read(buf, binary.BigEndian, &x)
return int(x)
}
// IntToBytes int转字节数组
func IntToBytes(n int) []byte {
buf := bytes.NewBuffer([]byte{})
binary.Write(buf, binary.BigEndian, n)
return buf.Bytes()
}
// BytesToInt16 字节数组转int16
func BytesToInt16(b []byte) int16 {
buf := bytes.NewBuffer(b)
var x int16
binary.Read(buf, binary.BigEndian, &x)
return int16(x)
}
// Int16ToBytes int16转字节数组
func Int16ToBytes(n int16) []byte {
x := int16(n)
buf := bytes.NewBuffer([]byte{})
binary.Write(buf, binary.BigEndian, x)
return buf.Bytes()
}
// BytesToInt32 字节数组转int32
func BytesToInt32(b []byte) int32 {
buf := bytes.NewBuffer(b)
var x int32
binary.Read(buf, binary.BigEndian, &x)
return int32(x)
}
// Int32ToBytes int32转字节数组
func Int32ToBytes(n int32) []byte {
x := int32(n)
buf := bytes.NewBuffer([]byte{})
binary.Write(buf, binary.BigEndian, x)
return buf.Bytes()
}
// 字节数组转int64
func BytesToInt64(b []byte) int64 {
buf := bytes.NewBuffer(b)
var x int64
binary.Read(buf, binary.BigEndian, &x)
return int64(x)
}
// Int64ToBytes int64转字节数组
func Int64ToBytes(n int64) []byte {
buf := bytes.NewBuffer([]byte{})
binary.Write(buf, binary.BigEndian, n)
return buf.Bytes()
}
// BytesToUInt64 字节数组转uint64
func BytesToUInt64(b []byte) uint64 {
buf := bytes.NewBuffer(b)
var x uint64
binary.Read(buf, binary.BigEndian, &x)
return uint64(x)
}
// UInt64ToBytes uint64转字节数组
func UInt64ToBytes(n uint64) []byte {
buf := bytes.NewBuffer([]byte{})
binary.Write(buf, binary.BigEndian, n)
return buf.Bytes()
}
// UInt32ToBytes uint32转字节数组
func UInt32ToBytes(n uint32) []byte {
buf := bytes.NewBuffer([]byte{})
binary.Write(buf, binary.BigEndian, n)
return buf.Bytes()
}
// BytesToBool 字节数组转bool
func BytesToBool(b []byte) bool {
buf := bytes.NewBuffer(b)
var x bool
binary.Read(buf, binary.BigEndian, &x)
return x
}
// BoolToBytes bool转字节数组
func BoolToBytes(x bool) []byte {
buf := bytes.NewBuffer([]byte{})
binary.Write(buf, binary.BigEndian, x)
return buf.Bytes()
}

View File

@ -52,9 +52,6 @@ func Authcode(text string, params ...interface{}) string {
if l > 1 { if l > 1 {
key = params[1].(string) key = params[1].(string)
if key=="" {
key = "DH-Framework"
}
} }
if l > 2 { if l > 2 {
@ -172,18 +169,18 @@ func AuthcodeUrl(text string, params ...interface{}) string {
} }
} }
// JsonEncodeWithError 编码JSON // JsonEncode 编码JSON
func JsonEncodeWithError(m interface{}) (string, error) { func JsonEncode(m interface{}) string {
b, err := json.Marshal(m) b, err := json.Marshal(m)
if err != nil { if err != nil {
return "", err log.Printf("Json Encode[%#v] Error:%s", m, err.Error())
return ""
} }
return string(b), nil return string(b)
} }
// JsonDecode 解码JSON
// JsonDecodeWithError 解码JSON func JsonDecode(str string, v ...interface{}) (interface{}, error) {
func JsonDecodeWithError(str string, v ...interface{}) (interface{}, error) {
var m interface{} var m interface{}
if len(v) > 0 { if len(v) > 0 {
m = v[0] m = v[0]
@ -199,26 +196,6 @@ func JsonDecodeWithError(str string, v ...interface{}) (interface{}, error) {
return m, nil return m, nil
} }
// JsonEncode 编码JSON
func JsonEncode(m interface{}) string {
s, err := JsonEncodeWithError(m)
if err != nil {
log.Printf("Json Encode[%#v] Error:%s", m, err.Error())
return ""
}
return s
}
// JsonDecode 解码JSON
func JsonDecode(str string, v ...interface{}) interface{} {
i, err := JsonDecodeWithError(str, v...)
if err != nil {
log.Printf("Json Decode[%s] Error:%s", str, err.Error())
return nil
}
return i
}
func Crc32(text string) string { func Crc32(text string) string {
h := crc32.NewIEEE() h := crc32.NewIEEE()
io.WriteString(h, text) io.WriteString(h, text)

View File

@ -6,7 +6,6 @@ import (
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"fmt"
) )
const ( const (
@ -17,7 +16,6 @@ const (
// ReadTextFile 读取文件 // ReadTextFile 读取文件
func ReadTextFile(path string) string { func ReadTextFile(path string) string {
fmt.Println("path:",path)
fp, err := os.Open(path) fp, err := os.Open(path)
if nil != err { if nil != err {
return "" return ""

View File

@ -4,10 +4,8 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net/url"
"reflect" "reflect"
"regexp" "regexp"
"sort"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@ -310,33 +308,6 @@ func Long2ip(ip uint32) string {
return fmt.Sprintf("%d.%d.%d.%d", ip>>24, ip<<8>>24, ip<<16>>24, ip<<24>>24) return fmt.Sprintf("%d.%d.%d.%d", ip>>24, ip<<8>>24, ip<<16>>24, ip<<24>>24)
} }
// SignStrBuild
func MapToRawStr(data map[string]string) string {
str := ""
if len(data) > 0 {
keys := make([]string, 0)
for k, _ := range data {
if k != "sign" && k != "encode" && k != "v" {
keys = append(keys, k)
}
}
sort.Strings(keys)
//
for _, k := range keys {
if data[k] != "" {
if str == "" {
str = fmt.Sprintf("%s=%s", k, url.QueryEscape(strings.Replace(data[k], " ", "+", -1)))
} else {
str = fmt.Sprintf("%s&%s=%s", str, k, url.QueryEscape(strings.Replace(data[k], " ", "+", -1)))
}
}
}
}
return str
}
// GbkToUtf8 GBK转UTF-8 // GbkToUtf8 GBK转UTF-8
func GbkToUtf8(s []byte) ([]byte, error) { func GbkToUtf8(s []byte) ([]byte, error) {
reader := transform.NewReader(bytes.NewReader(s), simplifiedchinese.GBK.NewDecoder()) reader := transform.NewReader(bytes.NewReader(s), simplifiedchinese.GBK.NewDecoder())

View File

@ -27,6 +27,7 @@ func Get(apiUrl string, parm map[string]string, header map[string]string, isHttp
} }
apiUrl = fmt.Sprintf("%s%s", apiUrl, p) apiUrl = fmt.Sprintf("%s%s", apiUrl, p)
} }
client := &http.Client{} client := &http.Client{}
if isHttps { if isHttps {
@ -34,10 +35,8 @@ func Get(apiUrl string, parm map[string]string, header map[string]string, isHttp
TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
} }
} }
reqest, err := http.NewRequest("GET", apiUrl, nil) reqest, _ := http.NewRequest("GET", apiUrl, nil)
if err != nil {
return nil, err
}
for k, v := range header { for k, v := range header {
reqest.Header.Set(k, v) reqest.Header.Set(k, v)
} }
@ -143,43 +142,3 @@ func GetRemoteIP(r *http.Request) string {
return strings.Split(addr, ":")[0] return strings.Split(addr, ":")[0]
} }
// ParseQuery 参数解析兼容部分字段有encode部分没encode的情况
func ParseQuery(query string) (m url.Values, err error) {
m = make(url.Values)
for query != "" {
key := query
if i := strings.IndexAny(key, "&"); i >= 0 {
key, query = key[:i], key[i+1:]
} else {
query = ""
}
if key == "" {
continue
}
value := ""
if i := strings.Index(key, "="); i >= 0 {
key, value = key[:i], key[i+1:]
}
key, err1 := url.QueryUnescape(key)
if err1 != nil {
if err == nil {
err = err1
}
continue
}
value1, err1 := url.QueryUnescape(value)
if err1 != nil {
if !strings.HasPrefix(err1.Error(), "invalid URL escape") {
if err == nil {
err = err1
}
continue
}
m[key] = append(m[key], value)
} else {
m[key] = append(m[key], value1)
}
}
return m, err
}

View File

@ -4,7 +4,6 @@ import (
"strconv" "strconv"
"strings" "strings"
"net/http" "net/http"
"fmt"
) )
// buildLuaResponse构造Lua响应结果 // buildLuaResponse构造Lua响应结果
@ -53,11 +52,6 @@ func BuildLuaResponse(m interface{}) string {
vStr = "{" + Substr(vStr, 1) + "}" vStr = "{" + Substr(vStr, 1) + "}"
case string: case string:
vStr = `"` + strings.Replace(v, `"`, `\"`, -1) + `"` vStr = `"` + strings.Replace(v, `"`, `\"`, -1) + `"`
case map[string]interface{}:
for ks, vs := range v {
vStr += `,` + ks + `=` + `"`+strings.Replace(fmt.Sprint(vs), `"`, `\"`, -1)+`"`
}
vStr = "{" + Substr(vStr, 1) + "}"
default: default:
if vv, ok := v.(map[interface{}]interface{}); ok { if vv, ok := v.(map[interface{}]interface{}); ok {
vStr = BuildLuaResponse(vv) vStr = BuildLuaResponse(vv)

View File

@ -12,28 +12,12 @@ const (
regEmail = `^[a-z0-9]+([._\\-]*[a-z0-9])*@([a-z0-9]+[-a-z0-9]*[a-z0-9]+.){1,63}[a-z0-9]+$` regEmail = `^[a-z0-9]+([._\\-]*[a-z0-9])*@([a-z0-9]+[-a-z0-9]*[a-z0-9]+.){1,63}[a-z0-9]+$`
regPhone = `^((\d3)|(\d{3}\-))?13[0-9]\d{8}|14[0-9]\d{8}|15[0-9]\d{8}|17[0-9]\d{8}|18[0-9]\d{8}` regPhone = `^((\d3)|(\d{3}\-))?13[0-9]\d{8}|14[0-9]\d{8}|15[0-9]\d{8}|17[0-9]\d{8}|18[0-9]\d{8}`
regUrl = `^((https?|ftp|news|http):\/\/)?([a-z]([a-z0-9\-]*[\.。])+([a-z]{2}|aero|arpa|biz|com|coop|edu|gov|info|int|jobs|mil|museum|name|nato|net|org|pro|travel)|(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))(\/[a-z0-9_\-\.~]+)*(\/([a-z0-9_\-\.]*)(\?[a-z0-9+_\-\.%=&]*)?)?(#[a-z][a-z0-9_]*)?$` regUrl = `^((https?|ftp|news|http):\/\/)?([a-z]([a-z0-9\-]*[\.。])+([a-z]{2}|aero|arpa|biz|com|coop|edu|gov|info|int|jobs|mil|museum|name|nato|net|org|pro|travel)|(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))(\/[a-z0-9_\-\.~]+)*(\/([a-z0-9_\-\.]*)(\?[a-z0-9+_\-\.%=&]*)?)?(#[a-z][a-z0-9_]*)?$`
regGuid = `[a-zA-Z0-9-_]{1,40}` regGuid = `[a-zA-Z0-9-_]{0,40}`
regDescription = `^{0,64}$` regDescription = `^{0,64}$`
regOutTypeDescription = `^{0,20}$` regOutTypeDescription = `^{0,20}$`
regMac = `^{0,40}$` regMac = `^{0,40}$`
regTradeNo = `^[a-zA-Z0-9_-]{1,40}$` regTradeNo = `^[a-zA-Z0-9_-]{1,40}$`
regAttach = `^{0,127}$` regAttach = `^{0,127}$`
// LT
regID = `^[0-9]{0,11}$`
regTitle = `^[\s\S]{0,40}$`
regIntro = `^[\s\S]{0,120}$`
regHash = `^[\S]{0,160}$`
reqAuthorName = `^{0,60}$`
reqAtType = `^[1-7]{1}$`
reqActionListAtType = `^[1,3]{1}$`
regContent = `^[\s\S]{0,255}$`
reqSrcType = `^[1,2,3]{1}$`
reqCommentActionAtType = `^[1,2,6]{1}$`
reqUserActionAtType = `^[1,2,3,4]{1}$`
reqKeyword = `^.{0,40}$`
regReason = `^[\s\S]{0,140}$`
regUserSuggestTitle = `^[\s\S]{0,120}$`
) )
func CheckString(data string, pat string) bool { func CheckString(data string, pat string) bool {
@ -103,73 +87,3 @@ func CheckTradeNo(tradeNo string) bool {
func CheckAttach(attach string) bool { func CheckAttach(attach string) bool {
return CheckString(attach, regAttach) return CheckString(attach, regAttach)
} }
// 检测资源id
func CheckID(anchorId string) bool {
return CheckString(anchorId, regID)
}
// 检测资源title
func CheckTitle(title string) bool {
return CheckString(title, regTitle)
}
// 检测资源intro
func CheckIntro(intro string) bool {
return CheckString(intro, regIntro)
}
// 检测资源Hash
func CheckHash(hash string) bool {
return CheckString(hash, regHash)
}
// 检测资源AuthorName
func CheckAuthorName(authorName string) bool {
return CheckString(authorName, reqAuthorName)
}
// 检测资源atType
func CheckAtType(atType string) bool {
return CheckString(atType, reqAtType)
}
// 检测资源atType
func CheckActionListAtType(atType string) bool {
return CheckString(atType, reqActionListAtType)
}
// 检测资源atType
func CheckContent(content string) bool {
return CheckString(content, regContent)
}
// 检测资源atType
func CheckSrcType(srcType string) bool {
return CheckString(srcType, reqSrcType)
}
// 检测资源atType
func CheckCommentActionAtType(atType string) bool {
return CheckString(atType, reqCommentActionAtType)
}
// 检测资源atType
func CheckUserActionAtType(atType string) bool {
return CheckString(atType, reqUserActionAtType)
}
// 检测资源keyword
func CheckKeyword(keyword string) bool {
return CheckString(keyword, reqKeyword)
}
// 检测资源Reason
func CheckReason(reason string) bool {
return CheckString(reason, regReason)
}
// 检测资源SuggestTitle
func CheckSuggestTitle(title string) bool {
return CheckString(title, regUserSuggestTitle)
}

View File

@ -4,7 +4,6 @@ import (
"fmt" "fmt"
"log" "log"
"math/rand" "math/rand"
"net"
"net/url" "net/url"
"reflect" "reflect"
"strconv" "strconv"
@ -356,22 +355,10 @@ func LenSyncMap(m *sync.Map) int {
return length return length
} }
// Ternary 模拟三元操作符 // 模拟三元操作符
func Ternary(b bool, trueVal, falseVal interface{}) interface{} { func Ternary(b bool, trueVal, falseVal interface{}) interface{} {
if b { if b {
return trueVal return trueVal
} }
return falseVal return falseVal
} }
// GetMacAddr 获取本地mac地址
func GetLocalMacAddr() ([]byte, error) {
// 获取本机的MAC地址
interfaces, err := net.Interfaces()
if err != nil || len(interfaces) == 0 {
return nil, err
}
return interfaces[0].HardwareAddr, nil
}

View File

@ -29,19 +29,24 @@ var driverInstanceNoProcess = &Driver{processQueryText: false}
func init() { func init() {
sql.Register("mssql", driverInstance) sql.Register("mssql", driverInstance)
sql.Register("sqlserver", driverInstanceNoProcess) sql.Register("sqlserver", driverInstanceNoProcess)
createDialer = func(p *connectParams) Dialer { createDialer = func(p *connectParams) dialer {
return netDialer{&net.Dialer{KeepAlive: p.keepAlive}} return tcpDialer{&net.Dialer{KeepAlive: p.keepAlive}}
} }
} }
var createDialer func(p *connectParams) Dialer // Abstract the dialer for testing and for non-TCP based connections.
type dialer interface {
Dial(ctx context.Context, addr string) (net.Conn, error)
}
type netDialer struct { var createDialer func(p *connectParams) dialer
type tcpDialer struct {
nd *net.Dialer nd *net.Dialer
} }
func (d netDialer) DialContext(ctx context.Context, network string, addr string) (net.Conn, error) { func (d tcpDialer) Dial(ctx context.Context, addr string) (net.Conn, error) {
return d.nd.DialContext(ctx, network, addr) return d.nd.DialContext(ctx, "tcp", addr)
} }
type Driver struct { type Driver struct {
@ -120,21 +125,6 @@ type Connector struct {
// SessionInitSQL is optional. The session will be reset even if // SessionInitSQL is optional. The session will be reset even if
// SessionInitSQL is empty. // SessionInitSQL is empty.
SessionInitSQL string SessionInitSQL string
// Dialer sets a custom dialer for all network operations.
// If Dialer is not set, normal net dialers are used.
Dialer Dialer
}
type Dialer interface {
DialContext(ctx context.Context, network string, addr string) (net.Conn, error)
}
func (c *Connector) getDialer(p *connectParams) Dialer {
if c != nil && c.Dialer != nil {
return c.Dialer
}
return createDialer(p)
} }
type Conn struct { type Conn struct {
@ -320,12 +310,12 @@ func (d *Driver) open(ctx context.Context, dsn string) (*Conn, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return d.connect(ctx, nil, params) return d.connect(ctx, params)
} }
// connect to the server, using the provided context for dialing only. // connect to the server, using the provided context for dialing only.
func (d *Driver) connect(ctx context.Context, c *Connector, params connectParams) (*Conn, error) { func (d *Driver) connect(ctx context.Context, params connectParams) (*Conn, error) {
sess, err := connect(ctx, c, d.log, params) sess, err := connect(ctx, d.log, params)
if err != nil { if err != nil {
// main server failed, try fail-over partner // main server failed, try fail-over partner
if params.failOverPartner == "" { if params.failOverPartner == "" {
@ -337,7 +327,7 @@ func (d *Driver) connect(ctx context.Context, c *Connector, params connectParams
params.port = params.failOverPort params.port = params.failOverPort
} }
sess, err = connect(ctx, c, d.log, params) sess, err = connect(ctx, d.log, params)
if err != nil { if err != nil {
// fail-over partner also failed, now fail // fail-over partner also failed, now fail
return nil, err return nil, err
@ -345,7 +335,6 @@ func (d *Driver) connect(ctx context.Context, c *Connector, params connectParams
} }
conn := &Conn{ conn := &Conn{
connector: c,
sess: sess, sess: sess,
transactionCtx: context.Background(), transactionCtx: context.Background(),
processQueryText: d.processQueryText, processQueryText: d.processQueryText,

View File

@ -34,7 +34,10 @@ func (c *Conn) ResetSession(ctx context.Context) error {
// Connect to the server and return a TDS connection. // Connect to the server and return a TDS connection.
func (c *Connector) Connect(ctx context.Context) (driver.Conn, error) { func (c *Connector) Connect(ctx context.Context) (driver.Conn, error) {
conn, err := c.driver.connect(ctx, c, c.params) conn, err := c.driver.connect(ctx, c.params)
if conn != nil {
conn.connector = c
}
if err == nil { if err == nil {
err = conn.ResetSession(ctx) err = conn.ResetSession(ctx)
} }

View File

@ -50,11 +50,12 @@ func parseInstances(msg []byte) map[string]map[string]string {
return results return results
} }
func getInstances(ctx context.Context, d Dialer, address string) (map[string]map[string]string, error) { func getInstances(ctx context.Context, address string) (map[string]map[string]string, error) {
maxTime := 5 * time.Second maxTime := 5 * time.Second
ctx, cancel := context.WithTimeout(ctx, maxTime) dialer := &net.Dialer{
defer cancel() Timeout: maxTime,
conn, err := d.DialContext(ctx, "udp", address+":1434") }
conn, err := dialer.DialContext(ctx, "udp", address+":1434")
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1111,7 +1112,7 @@ type auth interface {
// SQL Server AlwaysOn Availability Group Listeners are bound by DNS to a // SQL Server AlwaysOn Availability Group Listeners are bound by DNS to a
// list of IP addresses. So if there is more than one, try them all and // list of IP addresses. So if there is more than one, try them all and
// use the first one that allows a connection. // use the first one that allows a connection.
func dialConnection(ctx context.Context, c *Connector, p connectParams) (conn net.Conn, err error) { func dialConnection(ctx context.Context, p connectParams) (conn net.Conn, err error) {
var ips []net.IP var ips []net.IP
ips, err = net.LookupIP(p.host) ips, err = net.LookupIP(p.host)
if err != nil { if err != nil {
@ -1122,9 +1123,9 @@ func dialConnection(ctx context.Context, c *Connector, p connectParams) (conn ne
ips = []net.IP{ip} ips = []net.IP{ip}
} }
if len(ips) == 1 { if len(ips) == 1 {
d := c.getDialer(&p) d := createDialer(&p)
addr := net.JoinHostPort(ips[0].String(), strconv.Itoa(int(p.port))) addr := net.JoinHostPort(ips[0].String(), strconv.Itoa(int(p.port)))
conn, err = d.DialContext(ctx, "tcp", addr) conn, err = d.Dial(ctx, addr)
} else { } else {
//Try Dials in parallel to avoid waiting for timeouts. //Try Dials in parallel to avoid waiting for timeouts.
@ -1133,9 +1134,9 @@ func dialConnection(ctx context.Context, c *Connector, p connectParams) (conn ne
portStr := strconv.Itoa(int(p.port)) portStr := strconv.Itoa(int(p.port))
for _, ip := range ips { for _, ip := range ips {
go func(ip net.IP) { go func(ip net.IP) {
d := c.getDialer(&p) d := createDialer(&p)
addr := net.JoinHostPort(ip.String(), portStr) addr := net.JoinHostPort(ip.String(), portStr)
conn, err := d.DialContext(ctx, "tcp", addr) conn, err := d.Dial(ctx, addr)
if err == nil { if err == nil {
connChan <- conn connChan <- conn
} else { } else {
@ -1173,7 +1174,7 @@ func dialConnection(ctx context.Context, c *Connector, p connectParams) (conn ne
return conn, err return conn, err
} }
func connect(ctx context.Context, c *Connector, log optionalLogger, p connectParams) (res *tdsSession, err error) { func connect(ctx context.Context, log optionalLogger, p connectParams) (res *tdsSession, err error) {
dialCtx := ctx dialCtx := ctx
if p.dial_timeout > 0 { if p.dial_timeout > 0 {
var cancel func() var cancel func()
@ -1183,8 +1184,7 @@ func connect(ctx context.Context, c *Connector, log optionalLogger, p connectPar
// if instance is specified use instance resolution service // if instance is specified use instance resolution service
if p.instance != "" { if p.instance != "" {
p.instance = strings.ToUpper(p.instance) p.instance = strings.ToUpper(p.instance)
d := c.getDialer(&p) instances, err := getInstances(dialCtx, p.host)
instances, err := getInstances(dialCtx, d, p.host)
if err != nil { if err != nil {
f := "Unable to get instances from Sql Server Browser on host %v: %v" f := "Unable to get instances from Sql Server Browser on host %v: %v"
return nil, fmt.Errorf(f, p.host, err.Error()) return nil, fmt.Errorf(f, p.host, err.Error())
@ -1202,7 +1202,7 @@ func connect(ctx context.Context, c *Connector, log optionalLogger, p connectPar
} }
initiate_connection: initiate_connection:
conn, err := dialConnection(dialCtx, c, p) conn, err := dialConnection(dialCtx, p)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -116,23 +116,25 @@ func resizeHorizontal(img image.Image, width int, filter ResampleFilter) *image.
for y := range ys { for y := range ys {
src.scan(0, y, src.w, y+1, scanLine) src.scan(0, y, src.w, y+1, scanLine)
j0 := y * dst.Stride j0 := y * dst.Stride
for x := 0; x < width; x++ { for x := range weights {
var r, g, b, a float64 var r, g, b, a float64
for _, w := range weights[x] { for _, w := range weights[x] {
i := w.index * 4 i := w.index * 4
aw := float64(scanLine[i+3]) * w.weight s := scanLine[i : i+4 : i+4]
r += float64(scanLine[i+0]) * aw aw := float64(s[3]) * w.weight
g += float64(scanLine[i+1]) * aw r += float64(s[0]) * aw
b += float64(scanLine[i+2]) * aw g += float64(s[1]) * aw
b += float64(s[2]) * aw
a += aw a += aw
} }
if a != 0 { if a != 0 {
aInv := 1 / a aInv := 1 / a
j := j0 + x*4 j := j0 + x*4
dst.Pix[j+0] = clamp(r * aInv) d := dst.Pix[j : j+4 : j+4]
dst.Pix[j+1] = clamp(g * aInv) d[0] = clamp(r * aInv)
dst.Pix[j+2] = clamp(b * aInv) d[1] = clamp(g * aInv)
dst.Pix[j+3] = clamp(a) d[2] = clamp(b * aInv)
d[3] = clamp(a)
} }
} }
} }
@ -148,23 +150,25 @@ func resizeVertical(img image.Image, height int, filter ResampleFilter) *image.N
scanLine := make([]uint8, src.h*4) scanLine := make([]uint8, src.h*4)
for x := range xs { for x := range xs {
src.scan(x, 0, x+1, src.h, scanLine) src.scan(x, 0, x+1, src.h, scanLine)
for y := 0; y < height; y++ { for y := range weights {
var r, g, b, a float64 var r, g, b, a float64
for _, w := range weights[y] { for _, w := range weights[y] {
i := w.index * 4 i := w.index * 4
aw := float64(scanLine[i+3]) * w.weight s := scanLine[i : i+4 : i+4]
r += float64(scanLine[i+0]) * aw aw := float64(s[3]) * w.weight
g += float64(scanLine[i+1]) * aw r += float64(s[0]) * aw
b += float64(scanLine[i+2]) * aw g += float64(s[1]) * aw
b += float64(s[2]) * aw
a += aw a += aw
} }
if a != 0 { if a != 0 {
aInv := 1 / a aInv := 1 / a
j := y*dst.Stride + x*4 j := y*dst.Stride + x*4
dst.Pix[j+0] = clamp(r * aInv) d := dst.Pix[j : j+4 : j+4]
dst.Pix[j+1] = clamp(g * aInv) d[0] = clamp(r * aInv)
dst.Pix[j+2] = clamp(b * aInv) d[1] = clamp(g * aInv)
dst.Pix[j+3] = clamp(a) d[2] = clamp(b * aInv)
d[3] = clamp(a)
} }
} }
} }

View File

@ -33,21 +33,36 @@ func (s *scanner) scan(x1, y1, x2, y2 int, dst []uint8) {
size := (x2 - x1) * 4 size := (x2 - x1) * 4
j := 0 j := 0
i := y1*img.Stride + x1*4 i := y1*img.Stride + x1*4
if size == 4 {
for y := y1; y < y2; y++ {
d := dst[j : j+4 : j+4]
s := img.Pix[i : i+4 : i+4]
d[0] = s[0]
d[1] = s[1]
d[2] = s[2]
d[3] = s[3]
j += size
i += img.Stride
}
} else {
for y := y1; y < y2; y++ { for y := y1; y < y2; y++ {
copy(dst[j:j+size], img.Pix[i:i+size]) copy(dst[j:j+size], img.Pix[i:i+size])
j += size j += size
i += img.Stride i += img.Stride
} }
}
case *image.NRGBA64: case *image.NRGBA64:
j := 0 j := 0
for y := y1; y < y2; y++ { for y := y1; y < y2; y++ {
i := y*img.Stride + x1*8 i := y*img.Stride + x1*8
for x := x1; x < x2; x++ { for x := x1; x < x2; x++ {
dst[j+0] = img.Pix[i+0] s := img.Pix[i : i+8 : i+8]
dst[j+1] = img.Pix[i+2] d := dst[j : j+4 : j+4]
dst[j+2] = img.Pix[i+4] d[0] = s[0]
dst[j+3] = img.Pix[i+6] d[1] = s[2]
d[2] = s[4]
d[3] = s[6]
j += 4 j += 4
i += 8 i += 8
} }
@ -58,26 +73,31 @@ func (s *scanner) scan(x1, y1, x2, y2 int, dst []uint8) {
for y := y1; y < y2; y++ { for y := y1; y < y2; y++ {
i := y*img.Stride + x1*4 i := y*img.Stride + x1*4
for x := x1; x < x2; x++ { for x := x1; x < x2; x++ {
d := dst[j : j+4 : j+4]
a := img.Pix[i+3] a := img.Pix[i+3]
switch a { switch a {
case 0: case 0:
dst[j+0] = 0 d[0] = 0
dst[j+1] = 0 d[1] = 0
dst[j+2] = 0 d[2] = 0
d[3] = a
case 0xff: case 0xff:
dst[j+0] = img.Pix[i+0] s := img.Pix[i : i+4 : i+4]
dst[j+1] = img.Pix[i+1] d[0] = s[0]
dst[j+2] = img.Pix[i+2] d[1] = s[1]
d[2] = s[2]
d[3] = a
default: default:
r16 := uint16(img.Pix[i+0]) s := img.Pix[i : i+4 : i+4]
g16 := uint16(img.Pix[i+1]) r16 := uint16(s[0])
b16 := uint16(img.Pix[i+2]) g16 := uint16(s[1])
b16 := uint16(s[2])
a16 := uint16(a) a16 := uint16(a)
dst[j+0] = uint8(r16 * 0xff / a16) d[0] = uint8(r16 * 0xff / a16)
dst[j+1] = uint8(g16 * 0xff / a16) d[1] = uint8(g16 * 0xff / a16)
dst[j+2] = uint8(b16 * 0xff / a16) d[2] = uint8(b16 * 0xff / a16)
d[3] = a
} }
dst[j+3] = a
j += 4 j += 4
i += 4 i += 4
} }
@ -88,26 +108,28 @@ func (s *scanner) scan(x1, y1, x2, y2 int, dst []uint8) {
for y := y1; y < y2; y++ { for y := y1; y < y2; y++ {
i := y*img.Stride + x1*8 i := y*img.Stride + x1*8
for x := x1; x < x2; x++ { for x := x1; x < x2; x++ {
a := img.Pix[i+6] s := img.Pix[i : i+8 : i+8]
d := dst[j : j+4 : j+4]
a := s[6]
switch a { switch a {
case 0: case 0:
dst[j+0] = 0 d[0] = 0
dst[j+1] = 0 d[1] = 0
dst[j+2] = 0 d[2] = 0
case 0xff: case 0xff:
dst[j+0] = img.Pix[i+0] d[0] = s[0]
dst[j+1] = img.Pix[i+2] d[1] = s[2]
dst[j+2] = img.Pix[i+4] d[2] = s[4]
default: default:
r32 := uint32(img.Pix[i+0])<<8 | uint32(img.Pix[i+1]) r32 := uint32(s[0])<<8 | uint32(s[1])
g32 := uint32(img.Pix[i+2])<<8 | uint32(img.Pix[i+3]) g32 := uint32(s[2])<<8 | uint32(s[3])
b32 := uint32(img.Pix[i+4])<<8 | uint32(img.Pix[i+5]) b32 := uint32(s[4])<<8 | uint32(s[5])
a32 := uint32(img.Pix[i+6])<<8 | uint32(img.Pix[i+7]) a32 := uint32(s[6])<<8 | uint32(s[7])
dst[j+0] = uint8((r32 * 0xffff / a32) >> 8) d[0] = uint8((r32 * 0xffff / a32) >> 8)
dst[j+1] = uint8((g32 * 0xffff / a32) >> 8) d[1] = uint8((g32 * 0xffff / a32) >> 8)
dst[j+2] = uint8((b32 * 0xffff / a32) >> 8) d[2] = uint8((b32 * 0xffff / a32) >> 8)
} }
dst[j+3] = a d[3] = a
j += 4 j += 4
i += 8 i += 8
} }
@ -119,10 +141,11 @@ func (s *scanner) scan(x1, y1, x2, y2 int, dst []uint8) {
i := y*img.Stride + x1 i := y*img.Stride + x1
for x := x1; x < x2; x++ { for x := x1; x < x2; x++ {
c := img.Pix[i] c := img.Pix[i]
dst[j+0] = c d := dst[j : j+4 : j+4]
dst[j+1] = c d[0] = c
dst[j+2] = c d[1] = c
dst[j+3] = 0xff d[2] = c
d[3] = 0xff
j += 4 j += 4
i++ i++
} }
@ -134,10 +157,11 @@ func (s *scanner) scan(x1, y1, x2, y2 int, dst []uint8) {
i := y*img.Stride + x1*2 i := y*img.Stride + x1*2
for x := x1; x < x2; x++ { for x := x1; x < x2; x++ {
c := img.Pix[i] c := img.Pix[i]
dst[j+0] = c d := dst[j : j+4 : j+4]
dst[j+1] = c d[0] = c
dst[j+2] = c d[1] = c
dst[j+3] = 0xff d[2] = c
d[3] = 0xff
j += 4 j += 4
i += 2 i += 2
} }
@ -149,52 +173,61 @@ func (s *scanner) scan(x1, y1, x2, y2 int, dst []uint8) {
x2 += img.Rect.Min.X x2 += img.Rect.Min.X
y1 += img.Rect.Min.Y y1 += img.Rect.Min.Y
y2 += img.Rect.Min.Y y2 += img.Rect.Min.Y
hy := img.Rect.Min.Y / 2
hx := img.Rect.Min.X / 2
for y := y1; y < y2; y++ { for y := y1; y < y2; y++ {
iy := (y-img.Rect.Min.Y)*img.YStride + (x1 - img.Rect.Min.X) iy := (y-img.Rect.Min.Y)*img.YStride + (x1 - img.Rect.Min.X)
var yBase int
switch img.SubsampleRatio {
case image.YCbCrSubsampleRatio444, image.YCbCrSubsampleRatio422:
yBase = (y - img.Rect.Min.Y) * img.CStride
case image.YCbCrSubsampleRatio420, image.YCbCrSubsampleRatio440:
yBase = (y/2 - hy) * img.CStride
}
for x := x1; x < x2; x++ { for x := x1; x < x2; x++ {
var ic int var ic int
switch img.SubsampleRatio { switch img.SubsampleRatio {
case image.YCbCrSubsampleRatio444: case image.YCbCrSubsampleRatio444, image.YCbCrSubsampleRatio440:
ic = (y-img.Rect.Min.Y)*img.CStride + (x - img.Rect.Min.X) ic = yBase + (x - img.Rect.Min.X)
case image.YCbCrSubsampleRatio422: case image.YCbCrSubsampleRatio422, image.YCbCrSubsampleRatio420:
ic = (y-img.Rect.Min.Y)*img.CStride + (x/2 - img.Rect.Min.X/2) ic = yBase + (x/2 - hx)
case image.YCbCrSubsampleRatio420:
ic = (y/2-img.Rect.Min.Y/2)*img.CStride + (x/2 - img.Rect.Min.X/2)
case image.YCbCrSubsampleRatio440:
ic = (y/2-img.Rect.Min.Y/2)*img.CStride + (x - img.Rect.Min.X)
default: default:
ic = img.COffset(x, y) ic = img.COffset(x, y)
} }
yy := int(img.Y[iy]) yy1 := int32(img.Y[iy]) * 0x10101
cb := int(img.Cb[ic]) - 128 cb1 := int32(img.Cb[ic]) - 128
cr := int(img.Cr[ic]) - 128 cr1 := int32(img.Cr[ic]) - 128
r := (yy<<16 + 91881*cr + 1<<15) >> 16 r := yy1 + 91881*cr1
if r > 0xff { if uint32(r)&0xff000000 == 0 {
r = 0xff r >>= 16
} else if r < 0 { } else {
r = 0 r = ^(r >> 31)
} }
g := (yy<<16 - 22554*cb - 46802*cr + 1<<15) >> 16 g := yy1 - 22554*cb1 - 46802*cr1
if g > 0xff { if uint32(g)&0xff000000 == 0 {
g = 0xff g >>= 16
} else if g < 0 { } else {
g = 0 g = ^(g >> 31)
} }
b := (yy<<16 + 116130*cb + 1<<15) >> 16 b := yy1 + 116130*cb1
if b > 0xff { if uint32(b)&0xff000000 == 0 {
b = 0xff b >>= 16
} else if b < 0 { } else {
b = 0 b = ^(b >> 31)
} }
dst[j+0] = uint8(r) d := dst[j : j+4 : j+4]
dst[j+1] = uint8(g) d[0] = uint8(r)
dst[j+2] = uint8(b) d[1] = uint8(g)
dst[j+3] = 0xff d[2] = uint8(b)
d[3] = 0xff
iy++ iy++
j += 4 j += 4
@ -207,10 +240,11 @@ func (s *scanner) scan(x1, y1, x2, y2 int, dst []uint8) {
i := y*img.Stride + x1 i := y*img.Stride + x1
for x := x1; x < x2; x++ { for x := x1; x < x2; x++ {
c := s.palette[img.Pix[i]] c := s.palette[img.Pix[i]]
dst[j+0] = c.R d := dst[j : j+4 : j+4]
dst[j+1] = c.G d[0] = c.R
dst[j+2] = c.B d[1] = c.G
dst[j+3] = c.A d[2] = c.B
d[3] = c.A
j += 4 j += 4
i++ i++
} }
@ -226,22 +260,23 @@ func (s *scanner) scan(x1, y1, x2, y2 int, dst []uint8) {
for y := y1; y < y2; y++ { for y := y1; y < y2; y++ {
for x := x1; x < x2; x++ { for x := x1; x < x2; x++ {
r16, g16, b16, a16 := s.image.At(x, y).RGBA() r16, g16, b16, a16 := s.image.At(x, y).RGBA()
d := dst[j : j+4 : j+4]
switch a16 { switch a16 {
case 0xffff: case 0xffff:
dst[j+0] = uint8(r16 >> 8) d[0] = uint8(r16 >> 8)
dst[j+1] = uint8(g16 >> 8) d[1] = uint8(g16 >> 8)
dst[j+2] = uint8(b16 >> 8) d[2] = uint8(b16 >> 8)
dst[j+3] = 0xff d[3] = 0xff
case 0: case 0:
dst[j+0] = 0 d[0] = 0
dst[j+1] = 0 d[1] = 0
dst[j+2] = 0 d[2] = 0
dst[j+3] = 0 d[3] = 0
default: default:
dst[j+0] = uint8(((r16 * 0xffff) / a16) >> 8) d[0] = uint8(((r16 * 0xffff) / a16) >> 8)
dst[j+1] = uint8(((g16 * 0xffff) / a16) >> 8) d[1] = uint8(((g16 * 0xffff) / a16) >> 8)
dst[j+2] = uint8(((b16 * 0xffff) / a16) >> 8) d[2] = uint8(((b16 * 0xffff) / a16) >> 8)
dst[j+3] = uint8(a16 >> 8) d[3] = uint8(a16 >> 8)
} }
j += 4 j += 4
} }

View File

@ -1,7 +1,9 @@
GO ?= go
GOFMT ?= gofmt "-s" GOFMT ?= gofmt "-s"
PACKAGES ?= $(shell go list ./... | grep -v /vendor/) PACKAGES ?= $(shell $(GO) list ./... | grep -v /vendor/)
VETPACKAGES ?= $(shell go list ./... | grep -v /vendor/ | grep -v /examples/) VETPACKAGES ?= $(shell $(GO) list ./... | grep -v /vendor/ | grep -v /examples/)
GOFILES := $(shell find . -name "*.go" -type f -not -path "./vendor/*") GOFILES := $(shell find . -name "*.go" -type f -not -path "./vendor/*")
TESTFOLDER := $(shell $(GO) list ./... | grep -E 'gin$$|binding$$|render$$' | grep -v examples)
all: install all: install
@ -10,7 +12,19 @@ install: deps
.PHONY: test .PHONY: test
test: test:
sh coverage.sh echo "mode: count" > coverage.out
for d in $(TESTFOLDER); do \
$(GO) test -v -covermode=count -coverprofile=profile.out $$d > tmp.out; \
cat tmp.out; \
if grep -q "^--- FAIL" tmp.out; then \
rm tmp.out; \
exit 1;\
fi; \
if [ -f profile.out ]; then \
cat profile.out | grep -v "mode:" >> coverage.out; \
rm profile.out; \
fi; \
done
.PHONY: fmt .PHONY: fmt
fmt: fmt:
@ -18,7 +32,6 @@ fmt:
.PHONY: fmt-check .PHONY: fmt-check
fmt-check: fmt-check:
# get all go files and run go fmt on them
@diff=$$($(GOFMT) -d $(GOFILES)); \ @diff=$$($(GOFMT) -d $(GOFILES)); \
if [ -n "$$diff" ]; then \ if [ -n "$$diff" ]; then \
echo "Please run 'make fmt' and commit the result:"; \ echo "Please run 'make fmt' and commit the result:"; \
@ -27,14 +40,14 @@ fmt-check:
fi; fi;
vet: vet:
go vet $(VETPACKAGES) $(GO) vet $(VETPACKAGES)
deps: deps:
@hash govendor > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ @hash govendor > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
go get -u github.com/kardianos/govendor; \ $(GO) get -u github.com/kardianos/govendor; \
fi fi
@hash embedmd > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ @hash embedmd > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
go get -u github.com/campoy/embedmd; \ $(GO) get -u github.com/campoy/embedmd; \
fi fi
embedmd: embedmd:
@ -43,20 +56,26 @@ embedmd:
.PHONY: lint .PHONY: lint
lint: lint:
@hash golint > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ @hash golint > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
go get -u github.com/golang/lint/golint; \ $(GO) get -u golang.org/x/lint/golint; \
fi fi
for PKG in $(PACKAGES); do golint -set_exit_status $$PKG || exit 1; done; for PKG in $(PACKAGES); do golint -set_exit_status $$PKG || exit 1; done;
.PHONY: misspell-check .PHONY: misspell-check
misspell-check: misspell-check:
@hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ @hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
go get -u github.com/client9/misspell/cmd/misspell; \ $(GO) get -u github.com/client9/misspell/cmd/misspell; \
fi fi
misspell -error $(GOFILES) misspell -error $(GOFILES)
.PHONY: misspell .PHONY: misspell
misspell: misspell:
@hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ @hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
go get -u github.com/client9/misspell/cmd/misspell; \ $(GO) get -u github.com/client9/misspell/cmd/misspell; \
fi fi
misspell -w $(GOFILES) misspell -w $(GOFILES)
.PHONY: tools
tools:
go install golang.org/x/lint/golint; \
go install github.com/client9/misspell/cmd/misspell; \
go install github.com/campoy/embedmd;

View File

@ -9,6 +9,7 @@
[![Join the chat at https://gitter.im/gin-gonic/gin](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/gin-gonic/gin?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Join the chat at https://gitter.im/gin-gonic/gin](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/gin-gonic/gin?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Sourcegraph](https://sourcegraph.com/github.com/gin-gonic/gin/-/badge.svg)](https://sourcegraph.com/github.com/gin-gonic/gin?badge) [![Sourcegraph](https://sourcegraph.com/github.com/gin-gonic/gin/-/badge.svg)](https://sourcegraph.com/github.com/gin-gonic/gin?badge)
[![Open Source Helpers](https://www.codetriage.com/gin-gonic/gin/badges/users.svg)](https://www.codetriage.com/gin-gonic/gin) [![Open Source Helpers](https://www.codetriage.com/gin-gonic/gin/badges/users.svg)](https://www.codetriage.com/gin-gonic/gin)
[![Release](https://img.shields.io/github/release/gin-gonic/gin.svg?style=flat-square)](https://github.com/gin-gonic/gin/releases)
Gin is a web framework written in Go (Golang). It features a martini-like API with much better performance, up to 40 times faster thanks to [httprouter](https://github.com/julienschmidt/httprouter). If you need performance and good productivity, you will love Gin. Gin is a web framework written in Go (Golang). It features a martini-like API with much better performance, up to 40 times faster thanks to [httprouter](https://github.com/julienschmidt/httprouter). If you need performance and good productivity, you will love Gin.
@ -38,9 +39,10 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi
- [Custom Validators](#custom-validators) - [Custom Validators](#custom-validators)
- [Only Bind Query String](#only-bind-query-string) - [Only Bind Query String](#only-bind-query-string)
- [Bind Query String or Post Data](#bind-query-string-or-post-data) - [Bind Query String or Post Data](#bind-query-string-or-post-data)
- [Bind Uri](#bind-uri)
- [Bind HTML checkboxes](#bind-html-checkboxes) - [Bind HTML checkboxes](#bind-html-checkboxes)
- [Multipart/Urlencoded binding](#multiparturlencoded-binding) - [Multipart/Urlencoded binding](#multiparturlencoded-binding)
- [XML, JSON and YAML rendering](#xml-json-and-yaml-rendering) - [XML, JSON, YAML and ProtoBuf rendering](#xml-json-yaml-and-protobuf-rendering)
- [JSONP rendering](#jsonp) - [JSONP rendering](#jsonp)
- [Serving static files](#serving-static-files) - [Serving static files](#serving-static-files)
- [Serving data from reader](#serving-data-from-reader) - [Serving data from reader](#serving-data-from-reader)
@ -58,8 +60,10 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi
- [Bind form-data request with custom struct](#bind-form-data-request-with-custom-struct) - [Bind form-data request with custom struct](#bind-form-data-request-with-custom-struct)
- [Try to bind body into different structs](#try-to-bind-body-into-different-structs) - [Try to bind body into different structs](#try-to-bind-body-into-different-structs)
- [http2 server push](#http2-server-push) - [http2 server push](#http2-server-push)
- [Define format for the log of routes](#define-format-for-the-log-of-routes)
- [Set and get a cookie](#set-and-get-a-cookie)
- [Testing](#testing) - [Testing](#testing)
- [Users](#users--) - [Users](#users)
## Installation ## Installation
@ -100,7 +104,7 @@ $ mkdir -p $GOPATH/src/github.com/myusername/project && cd "$_"
```sh ```sh
$ govendor init $ govendor init
$ govendor fetch github.com/gin-gonic/gin@v1.2 $ govendor fetch github.com/gin-gonic/gin@v1.3
``` ```
4. Copy a starting template inside your project 4. Copy a starting template inside your project
@ -198,7 +202,7 @@ BenchmarkVulcan_GithubAll | 5000 | 394253 | 19894
## Build with [jsoniter](https://github.com/json-iterator/go) ## Build with [jsoniter](https://github.com/json-iterator/go)
Gin use `encoding/json` as default json package but you can change to [jsoniter](https://github.com/json-iterator/go) by build from other tags. Gin uses `encoding/json` as default json package but you can change to [jsoniter](https://github.com/json-iterator/go) by build from other tags.
```sh ```sh
$ go build -tags=jsoniter . $ go build -tags=jsoniter .
@ -526,7 +530,7 @@ func main() {
### Model binding and validation ### Model binding and validation
To bind a request body into a type, use model binding. We currently support binding of JSON, XML and standard form values (foo=bar&boo=baz). To bind a request body into a type, use model binding. We currently support binding of JSON, XML, YAML and standard form values (foo=bar&boo=baz).
Gin uses [**go-playground/validator.v8**](https://github.com/go-playground/validator) for validation. Check the full docs on tags usage [here](http://godoc.org/gopkg.in/go-playground/validator.v8#hdr-Baked_In_Validators_and_Tags). Gin uses [**go-playground/validator.v8**](https://github.com/go-playground/validator) for validation. Check the full docs on tags usage [here](http://godoc.org/gopkg.in/go-playground/validator.v8#hdr-Baked_In_Validators_and_Tags).
@ -534,10 +538,10 @@ Note that you need to set the corresponding binding tag on all fields you want t
Also, Gin provides two sets of methods for binding: Also, Gin provides two sets of methods for binding:
- **Type** - Must bind - **Type** - Must bind
- **Methods** - `Bind`, `BindJSON`, `BindQuery` - **Methods** - `Bind`, `BindJSON`, `BindXML`, `BindQuery`, `BindYAML`
- **Behavior** - These methods use `MustBindWith` under the hood. If there is a binding error, the request is aborted with `c.AbortWithError(400, err).SetType(ErrorTypeBind)`. This sets the response status code to 400 and the `Content-Type` header is set to `text/plain; charset=utf-8`. Note that if you try to set the response code after this, it will result in a warning `[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 400 with 422`. If you wish to have greater control over the behavior, consider using the `ShouldBind` equivalent method. - **Behavior** - These methods use `MustBindWith` under the hood. If there is a binding error, the request is aborted with `c.AbortWithError(400, err).SetType(ErrorTypeBind)`. This sets the response status code to 400 and the `Content-Type` header is set to `text/plain; charset=utf-8`. Note that if you try to set the response code after this, it will result in a warning `[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 400 with 422`. If you wish to have greater control over the behavior, consider using the `ShouldBind` equivalent method.
- **Type** - Should bind - **Type** - Should bind
- **Methods** - `ShouldBind`, `ShouldBindJSON`, `ShouldBindQuery` - **Methods** - `ShouldBind`, `ShouldBindJSON`, `ShouldBindXML`, `ShouldBindQuery`, `ShouldBindYAML`
- **Behavior** - These methods use `ShouldBindWith` under the hood. If there is a binding error, the error is returned and it is the developer's responsibility to handle the request and error appropriately. - **Behavior** - These methods use `ShouldBindWith` under the hood. If there is a binding error, the error is returned and it is the developer's responsibility to handle the request and error appropriately.
When using the Bind-method, Gin tries to infer the binder depending on the Content-Type header. If you are sure what you are binding, you can use `MustBindWith` or `ShouldBindWith`. When using the Bind-method, Gin tries to infer the binder depending on the Content-Type header. If you are sure what you are binding, you can use `MustBindWith` or `ShouldBindWith`.
@ -547,8 +551,8 @@ You can also specify that specific fields are required. If a field is decorated
```go ```go
// Binding from JSON // Binding from JSON
type Login struct { type Login struct {
User string `form:"user" json:"user" binding:"required"` User string `form:"user" json:"user" xml:"user" binding:"required"`
Password string `form:"password" json:"password" binding:"required"` Password string `form:"password" json:"password" xml:"password" binding:"required"`
} }
func main() { func main() {
@ -557,30 +561,55 @@ func main() {
// Example for binding JSON ({"user": "manu", "password": "123"}) // Example for binding JSON ({"user": "manu", "password": "123"})
router.POST("/loginJSON", func(c *gin.Context) { router.POST("/loginJSON", func(c *gin.Context) {
var json Login var json Login
if err := c.ShouldBindJSON(&json); err == nil { if err := c.ShouldBindJSON(&json); err != nil {
if json.User == "manu" && json.Password == "123" {
c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
} else {
c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
}
} else {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
} }
if json.User != "manu" || json.Password != "123" {
c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
return
}
c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
})
// Example for binding XML (
// <?xml version="1.0" encoding="UTF-8"?>
// <root>
// <user>user</user>
// <password>123</user>
// </root>)
router.POST("/loginXML", func(c *gin.Context) {
var xml Login
if err := c.ShouldBindXML(&xml); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if xml.User != "manu" || xml.Password != "123" {
c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
return
}
c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
}) })
// Example for binding a HTML form (user=manu&password=123) // Example for binding a HTML form (user=manu&password=123)
router.POST("/loginForm", func(c *gin.Context) { router.POST("/loginForm", func(c *gin.Context) {
var form Login var form Login
// This will infer what binder to use depending on the content-type header. // This will infer what binder to use depending on the content-type header.
if err := c.ShouldBind(&form); err == nil { if err := c.ShouldBind(&form); err != nil {
if form.User == "manu" && form.Password == "123" {
c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
} else {
c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
}
} else {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
} }
if form.User != "manu" || form.Password != "123" {
c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
return
}
c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
}) })
// Listen and serve on 0.0.0.0:8080 // Listen and serve on 0.0.0.0:8080
@ -632,6 +661,7 @@ import (
"gopkg.in/go-playground/validator.v8" "gopkg.in/go-playground/validator.v8"
) )
// Booking contains binded and validated data.
type Booking struct { type Booking struct {
CheckIn time.Time `form:"check_in" binding:"required,bookabledate" time_format:"2006-01-02"` CheckIn time.Time `form:"check_in" binding:"required,bookabledate" time_format:"2006-01-02"`
CheckOut time.Time `form:"check_out" binding:"required,gtfield=CheckIn" time_format:"2006-01-02"` CheckOut time.Time `form:"check_out" binding:"required,gtfield=CheckIn" time_format:"2006-01-02"`
@ -679,7 +709,7 @@ $ curl "localhost:8085/bookable?check_in=2018-03-08&check_out=2018-03-09"
{"error":"Key: 'Booking.CheckIn' Error:Field validation for 'CheckIn' failed on the 'bookabledate' tag"} {"error":"Key: 'Booking.CheckIn' Error:Field validation for 'CheckIn' failed on the 'bookabledate' tag"}
``` ```
[Struct level validations](https://github.com/go-playground/validator/releases/tag/v8.7) can also be registed this way. [Struct level validations](https://github.com/go-playground/validator/releases/tag/v8.7) can also be registered this way.
See the [struct-lvl-validation example](examples/struct-lvl-validations) to learn more. See the [struct-lvl-validation example](examples/struct-lvl-validations) to learn more.
### Only Bind Query String ### Only Bind Query String
@ -725,9 +755,12 @@ See the [detail information](https://github.com/gin-gonic/gin/issues/742#issueco
```go ```go
package main package main
import "log" import (
import "github.com/gin-gonic/gin" "log"
import "time" "time"
"github.com/gin-gonic/gin"
)
type Person struct { type Person struct {
Name string `form:"name"` Name string `form:"name"`
@ -761,6 +794,40 @@ Test it with:
$ curl -X GET "localhost:8085/testing?name=appleboy&address=xyz&birthday=1992-03-15" $ curl -X GET "localhost:8085/testing?name=appleboy&address=xyz&birthday=1992-03-15"
``` ```
### Bind Uri
See the [detail information](https://github.com/gin-gonic/gin/issues/846).
```go
package main
import "github.com/gin-gonic/gin"
type Person struct {
ID string `uri:"id" binding:"required,uuid"`
Name string `uri:"name" binding:"required"`
}
func main() {
route := gin.Default()
route.GET("/:name/:id", func(c *gin.Context) {
var person Person
if err := c.ShouldBindUri(&person); err != nil {
c.JSON(400, gin.H{"msg": err})
return
}
c.JSON(200, gin.H{"name": person.Name, "uuid": person.ID})
})
route.Run(":8088")
}
```
Test it with:
```sh
$ curl -v localhost:8088/thinkerou/987fbc97-4bed-5078-9f07-9141ba07c9f3
$ curl -v localhost:8088/thinkerou/not-uuid
```
### Bind HTML checkboxes ### Bind HTML checkboxes
See the [detail information](https://github.com/gin-gonic/gin/issues/129#issuecomment-124260092) See the [detail information](https://github.com/gin-gonic/gin/issues/129#issuecomment-124260092)
@ -792,12 +859,12 @@ form.html
<form action="/" method="POST"> <form action="/" method="POST">
<p>Check some colors</p> <p>Check some colors</p>
<label for="red">Red</label> <label for="red">Red</label>
<input type="checkbox" name="colors[]" value="red" id="red" /> <input type="checkbox" name="colors[]" value="red" id="red">
<label for="green">Green</label> <label for="green">Green</label>
<input type="checkbox" name="colors[]" value="green" id="green" /> <input type="checkbox" name="colors[]" value="green" id="green">
<label for="blue">Blue</label> <label for="blue">Blue</label>
<input type="checkbox" name="colors[]" value="blue" id="blue" /> <input type="checkbox" name="colors[]" value="blue" id="blue">
<input type="submit" /> <input type="submit">
</form> </form>
``` ```
@ -846,7 +913,7 @@ Test it with:
$ curl -v --form user=user --form password=password http://localhost:8080/login $ curl -v --form user=user --form password=password http://localhost:8080/login
``` ```
### XML, JSON and YAML rendering ### XML, JSON, YAML and ProtoBuf rendering
```go ```go
func main() { func main() {
@ -880,6 +947,19 @@ func main() {
c.YAML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK}) c.YAML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
}) })
r.GET("/someProtoBuf", func(c *gin.Context) {
reps := []int64{int64(1), int64(2)}
label := "test"
// The specific definition of protobuf is written in the testdata/protoexample file.
data := &protoexample.Test{
Label: &label,
Reps: reps,
}
// Note that data becomes binary data in the response
// Will output protoexample.Test protobuf serialized data
c.ProtoBuf(http.StatusOK, data)
})
// Listen and serve on 0.0.0.0:8080 // Listen and serve on 0.0.0.0:8080
r.Run(":8080") r.Run(":8080")
} }
@ -953,6 +1033,34 @@ func main() {
} }
``` ```
#### PureJSON
Normally, JSON replaces special HTML characters with their unicode entities, e.g. `<` becomes `\u003c`. If you want to encode such characters literally, you can use PureJSON instead.
This feature is unavailable in Go 1.6 and lower.
```go
func main() {
r := gin.Default()
// Serves unicode entities
r.GET("/json", func(c *gin.Context) {
c.JSON(200, gin.H{
"html": "<b>Hello, world!</b>",
})
})
// Serves literal characters
r.GET("/purejson", func(c *gin.Context) {
c.PureJSON(200, gin.H{
"html": "<b>Hello, world!</b>",
})
})
// listen and serve on 0.0.0.0:8080
r.Run(":8080")
}
```
### Serving static files ### Serving static files
```go ```go
@ -1087,7 +1195,7 @@ You may use custom delims
```go ```go
r := gin.Default() r := gin.Default()
r.Delims("{[{", "}]}") r.Delims("{[{", "}]}")
r.LoadHTMLGlob("/path/to/templates")) r.LoadHTMLGlob("/path/to/templates")
``` ```
#### Custom Template Funcs #### Custom Template Funcs
@ -1649,11 +1757,11 @@ type StructX struct {
} }
type StructY struct { type StructY struct {
Y StructX `form:"name_y"` // HERE hava form Y StructX `form:"name_y"` // HERE have form
} }
type StructZ struct { type StructZ struct {
Z *StructZ `form:"name_z"` // HERE hava form Z *StructZ `form:"name_z"` // HERE have form
} }
``` ```
@ -1766,6 +1874,78 @@ func main() {
} }
``` ```
### Define format for the log of routes
The default log of routes is:
```
[GIN-debug] POST /foo --> main.main.func1 (3 handlers)
[GIN-debug] GET /bar --> main.main.func2 (3 handlers)
[GIN-debug] GET /status --> main.main.func3 (3 handlers)
```
If you want to log this information in given format (e.g. JSON, key values or something else), then you can define this format with `gin.DebugPrintRouteFunc`.
In the example below, we log all routes with standard log package but you can use another log tools that suits of your needs.
```go
import (
"log"
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
gin.DebugPrintRouteFunc = func(httpMethod, absolutePath, handlerName string, nuHandlers int) {
log.Printf("endpoint %v %v %v %v\n", httpMethod, absolutePath, handlerName, nuHandlers)
}
r.POST("/foo", func(c *gin.Context) {
c.JSON(http.StatusOK, "foo")
})
r.GET("/bar", func(c *gin.Context) {
c.JSON(http.StatusOK, "bar")
})
r.GET("/status", func(c *gin.Context) {
c.JSON(http.StatusOK, "ok")
})
// Listen and Server in http://0.0.0.0:8080
r.Run()
}
```
### Set and get a cookie
```go
import (
"fmt"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
router.GET("/cookie", func(c *gin.Context) {
cookie, err := c.Cookie("gin_cookie")
if err != nil {
cookie = "NotSet"
c.SetCookie("gin_cookie", "test", 3600, "/", "localhost", false, true)
}
fmt.Printf("Cookie value: %s \n", cookie)
})
router.Run()
}
```
## Testing ## Testing
The `net/http/httptest` package is preferable way for HTTP testing. The `net/http/httptest` package is preferable way for HTTP testing.
@ -1816,5 +1996,9 @@ func TestPingRoute(t *testing.T) {
Awesome project lists using [Gin](https://github.com/gin-gonic/gin) web framework. Awesome project lists using [Gin](https://github.com/gin-gonic/gin) web framework.
* [drone](https://github.com/drone/drone): Drone is a Continuous Delivery platform built on Docker, written in Go * [drone](https://github.com/drone/drone): Drone is a Continuous Delivery platform built on Docker, written in Go.
* [gorush](https://github.com/appleboy/gorush): A push notification server written in Go. * [gorush](https://github.com/appleboy/gorush): A push notification server written in Go.
* [fnproject](https://github.com/fnproject/fn): The container native, cloud agnostic serverless platform.
* [photoprism](https://github.com/photoprism/photoprism): Personal photo management powered by Go and Google TensorFlow.
* [krakend](https://github.com/devopsfaith/krakend): Ultra performant API Gateway with middlewares.
* [picfit](https://github.com/thoas/picfit): An image resizing server written in Go.

View File

@ -18,6 +18,7 @@ const (
MIMEPROTOBUF = "application/x-protobuf" MIMEPROTOBUF = "application/x-protobuf"
MIMEMSGPACK = "application/x-msgpack" MIMEMSGPACK = "application/x-msgpack"
MIMEMSGPACK2 = "application/msgpack" MIMEMSGPACK2 = "application/msgpack"
MIMEYAML = "application/x-yaml"
) )
// Binding describes the interface which needs to be implemented for binding the // Binding describes the interface which needs to be implemented for binding the
@ -35,9 +36,16 @@ type BindingBody interface {
BindBody([]byte, interface{}) error BindBody([]byte, interface{}) error
} }
// BindingUri adds BindUri method to Binding. BindUri is similar with Bind,
// but it read the Params.
type BindingUri interface {
Name() string
BindUri(map[string][]string, interface{}) error
}
// StructValidator is the minimal interface which needs to be implemented in // StructValidator is the minimal interface which needs to be implemented in
// order for it to be used as the validator engine for ensuring the correctness // order for it to be used as the validator engine for ensuring the correctness
// of the reqest. Gin provides a default implementation for this using // of the request. Gin provides a default implementation for this using
// https://github.com/go-playground/validator/tree/v8.18.2. // https://github.com/go-playground/validator/tree/v8.18.2.
type StructValidator interface { type StructValidator interface {
// ValidateStruct can receive any kind of type and it should never panic, even if the configuration is not right. // ValidateStruct can receive any kind of type and it should never panic, even if the configuration is not right.
@ -68,6 +76,8 @@ var (
FormMultipart = formMultipartBinding{} FormMultipart = formMultipartBinding{}
ProtoBuf = protobufBinding{} ProtoBuf = protobufBinding{}
MsgPack = msgpackBinding{} MsgPack = msgpackBinding{}
YAML = yamlBinding{}
Uri = uriBinding{}
) )
// Default returns the appropriate Binding instance based on the HTTP method // Default returns the appropriate Binding instance based on the HTTP method
@ -86,6 +96,8 @@ func Default(method, contentType string) Binding {
return ProtoBuf return ProtoBuf
case MIMEMSGPACK, MIMEMSGPACK2: case MIMEMSGPACK, MIMEMSGPACK2:
return MsgPack return MsgPack
case MIMEYAML:
return YAML
default: //case MIMEPOSTForm, MIMEMultipartPOSTForm: default: //case MIMEPOSTForm, MIMEMultipartPOSTForm:
return Form return Form
} }

View File

@ -12,7 +12,15 @@ import (
"time" "time"
) )
func mapUri(ptr interface{}, m map[string][]string) error {
return mapFormByTag(ptr, m, "uri")
}
func mapForm(ptr interface{}, form map[string][]string) error { func mapForm(ptr interface{}, form map[string][]string) error {
return mapFormByTag(ptr, form, "form")
}
func mapFormByTag(ptr interface{}, form map[string][]string, tag string) error {
typ := reflect.TypeOf(ptr).Elem() typ := reflect.TypeOf(ptr).Elem()
val := reflect.ValueOf(ptr).Elem() val := reflect.ValueOf(ptr).Elem()
for i := 0; i < typ.NumField(); i++ { for i := 0; i < typ.NumField(); i++ {
@ -23,7 +31,7 @@ func mapForm(ptr interface{}, form map[string][]string) error {
} }
structFieldKind := structField.Kind() structFieldKind := structField.Kind()
inputFieldName := typeField.Tag.Get("form") inputFieldName := typeField.Tag.Get(tag)
inputFieldNameList := strings.Split(inputFieldName, ",") inputFieldNameList := strings.Split(inputFieldName, ",")
inputFieldName = inputFieldNameList[0] inputFieldName = inputFieldNameList[0]
var defaultValue string var defaultValue string
@ -74,7 +82,8 @@ func mapForm(ptr interface{}, form map[string][]string) error {
} }
} }
val.Field(i).Set(slice) val.Field(i).Set(slice)
} else { continue
}
if _, isTime := structField.Interface().(time.Time); isTime { if _, isTime := structField.Interface().(time.Time); isTime {
if err := setTimeField(inputValue[0], typeField, structField); err != nil { if err := setTimeField(inputValue[0], typeField, structField); err != nil {
return err return err
@ -85,7 +94,6 @@ func mapForm(ptr interface{}, form map[string][]string) error {
return err return err
} }
} }
}
return nil return nil
} }
@ -178,7 +186,7 @@ func setFloatField(val string, bitSize int, field reflect.Value) error {
func setTimeField(val string, structField reflect.StructField, value reflect.Value) error { func setTimeField(val string, structField reflect.StructField, value reflect.Value) error {
timeFormat := structField.Tag.Get("time_format") timeFormat := structField.Tag.Get("time_format")
if timeFormat == "" { if timeFormat == "" {
return errors.New("Blank time format") timeFormat = time.RFC3339
} }
if val == "" { if val == "" {

View File

@ -6,10 +6,11 @@ package binding
import ( import (
"bytes" "bytes"
"fmt"
"io" "io"
"net/http" "net/http"
"github.com/gin-gonic/gin/json" "github.com/gin-gonic/gin/internal/json"
) )
// EnableDecoderUseNumber is used to call the UseNumber method on the JSON // EnableDecoderUseNumber is used to call the UseNumber method on the JSON
@ -24,6 +25,9 @@ func (jsonBinding) Name() string {
} }
func (jsonBinding) Bind(req *http.Request, obj interface{}) error { func (jsonBinding) Bind(req *http.Request, obj interface{}) error {
if req == nil || req.Body == nil {
return fmt.Errorf("invalid request")
}
return decodeJSON(req.Body, obj) return decodeJSON(req.Body, obj)
} }

View File

@ -29,7 +29,7 @@ func (protobufBinding) BindBody(body []byte, obj interface{}) error {
if err := proto.Unmarshal(body, obj.(proto.Message)); err != nil { if err := proto.Unmarshal(body, obj.(proto.Message)); err != nil {
return err return err
} }
// Here it's same to return validate(obj), but util now we cann't add // Here it's same to return validate(obj), but util now we can't add
// `binding:""` to the struct which automatically generate by gen-proto // `binding:""` to the struct which automatically generate by gen-proto
return nil return nil
// return validate(obj) // return validate(obj)

View File

@ -31,6 +31,7 @@ const (
MIMEPlain = binding.MIMEPlain MIMEPlain = binding.MIMEPlain
MIMEPOSTForm = binding.MIMEPOSTForm MIMEPOSTForm = binding.MIMEPOSTForm
MIMEMultipartPOSTForm = binding.MIMEMultipartPOSTForm MIMEMultipartPOSTForm = binding.MIMEMultipartPOSTForm
MIMEYAML = binding.MIMEYAML
BodyBytesKey = "_gin-gonic/gin/bodybyteskey" BodyBytesKey = "_gin-gonic/gin/bodybyteskey"
) )
@ -414,7 +415,6 @@ func (c *Context) PostFormArray(key string) []string {
// a boolean value whether at least one value exists for the given key. // a boolean value whether at least one value exists for the given key.
func (c *Context) GetPostFormArray(key string) ([]string, bool) { func (c *Context) GetPostFormArray(key string) ([]string, bool) {
req := c.Request req := c.Request
req.ParseForm()
req.ParseMultipartForm(c.engine.MaxMultipartMemory) req.ParseMultipartForm(c.engine.MaxMultipartMemory)
if values := req.PostForm[key]; len(values) > 0 { if values := req.PostForm[key]; len(values) > 0 {
return values, true return values, true
@ -437,7 +437,6 @@ func (c *Context) PostFormMap(key string) map[string]string {
// whether at least one value exists for the given key. // whether at least one value exists for the given key.
func (c *Context) GetPostFormMap(key string) (map[string]string, bool) { func (c *Context) GetPostFormMap(key string) (map[string]string, bool) {
req := c.Request req := c.Request
req.ParseForm()
req.ParseMultipartForm(c.engine.MaxMultipartMemory) req.ParseMultipartForm(c.engine.MaxMultipartMemory)
dicts, exist := c.get(req.PostForm, key) dicts, exist := c.get(req.PostForm, key)
@ -465,6 +464,11 @@ func (c *Context) get(m map[string][]string, key string) (map[string]string, boo
// FormFile returns the first file for the provided form key. // FormFile returns the first file for the provided form key.
func (c *Context) FormFile(name string) (*multipart.FileHeader, error) { func (c *Context) FormFile(name string) (*multipart.FileHeader, error) {
if c.Request.MultipartForm == nil {
if err := c.Request.ParseMultipartForm(c.engine.MaxMultipartMemory); err != nil {
return nil, err
}
}
_, fh, err := c.Request.FormFile(name) _, fh, err := c.Request.FormFile(name)
return fh, err return fh, err
} }
@ -511,13 +515,23 @@ func (c *Context) BindJSON(obj interface{}) error {
return c.MustBindWith(obj, binding.JSON) return c.MustBindWith(obj, binding.JSON)
} }
// BindXML is a shortcut for c.MustBindWith(obj, binding.BindXML).
func (c *Context) BindXML(obj interface{}) error {
return c.MustBindWith(obj, binding.XML)
}
// BindQuery is a shortcut for c.MustBindWith(obj, binding.Query). // BindQuery is a shortcut for c.MustBindWith(obj, binding.Query).
func (c *Context) BindQuery(obj interface{}) error { func (c *Context) BindQuery(obj interface{}) error {
return c.MustBindWith(obj, binding.Query) return c.MustBindWith(obj, binding.Query)
} }
// BindYAML is a shortcut for c.MustBindWith(obj, binding.YAML).
func (c *Context) BindYAML(obj interface{}) error {
return c.MustBindWith(obj, binding.YAML)
}
// MustBindWith binds the passed struct pointer using the specified binding engine. // MustBindWith binds the passed struct pointer using the specified binding engine.
// It will abort the request with HTTP 400 if any error ocurrs. // It will abort the request with HTTP 400 if any error occurs.
// See the binding package. // See the binding package.
func (c *Context) MustBindWith(obj interface{}, b binding.Binding) (err error) { func (c *Context) MustBindWith(obj interface{}, b binding.Binding) (err error) {
if err = c.ShouldBindWith(obj, b); err != nil { if err = c.ShouldBindWith(obj, b); err != nil {
@ -545,11 +559,30 @@ func (c *Context) ShouldBindJSON(obj interface{}) error {
return c.ShouldBindWith(obj, binding.JSON) return c.ShouldBindWith(obj, binding.JSON)
} }
// ShouldBindXML is a shortcut for c.ShouldBindWith(obj, binding.XML).
func (c *Context) ShouldBindXML(obj interface{}) error {
return c.ShouldBindWith(obj, binding.XML)
}
// ShouldBindQuery is a shortcut for c.ShouldBindWith(obj, binding.Query). // ShouldBindQuery is a shortcut for c.ShouldBindWith(obj, binding.Query).
func (c *Context) ShouldBindQuery(obj interface{}) error { func (c *Context) ShouldBindQuery(obj interface{}) error {
return c.ShouldBindWith(obj, binding.Query) return c.ShouldBindWith(obj, binding.Query)
} }
// ShouldBindYAML is a shortcut for c.ShouldBindWith(obj, binding.YAML).
func (c *Context) ShouldBindYAML(obj interface{}) error {
return c.ShouldBindWith(obj, binding.YAML)
}
// ShouldBindUri binds the passed struct pointer using the specified binding engine.
func (c *Context) ShouldBindUri(obj interface{}) error {
m := make(map[string][]string)
for _, v := range c.Params {
m[v.Key] = []string{v.Value}
}
return binding.Uri.BindUri(m, obj)
}
// ShouldBindWith binds the passed struct pointer using the specified binding engine. // ShouldBindWith binds the passed struct pointer using the specified binding engine.
// See the binding package. // See the binding package.
func (c *Context) ShouldBindWith(obj interface{}, b binding.Binding) error { func (c *Context) ShouldBindWith(obj interface{}, b binding.Binding) error {
@ -561,9 +594,7 @@ func (c *Context) ShouldBindWith(obj interface{}, b binding.Binding) error {
// //
// NOTE: This method reads the body before binding. So you should use // NOTE: This method reads the body before binding. So you should use
// ShouldBindWith for better performance if you need to call only once. // ShouldBindWith for better performance if you need to call only once.
func (c *Context) ShouldBindBodyWith( func (c *Context) ShouldBindBodyWith(obj interface{}, bb binding.BindingBody) (err error) {
obj interface{}, bb binding.BindingBody,
) (err error) {
var body []byte var body []byte
if cb, ok := c.Get(BodyBytesKey); ok { if cb, ok := c.Get(BodyBytesKey); ok {
if cbb, ok := cb.([]byte); ok { if cbb, ok := cb.([]byte); ok {
@ -655,9 +686,9 @@ func (c *Context) Status(code int) {
func (c *Context) Header(key, value string) { func (c *Context) Header(key, value string) {
if value == "" { if value == "" {
c.Writer.Header().Del(key) c.Writer.Header().Del(key)
} else { return
c.Writer.Header().Set(key, value)
} }
c.Writer.Header().Set(key, value)
} }
// GetHeader returns value from request headers. // GetHeader returns value from request headers.
@ -701,6 +732,7 @@ func (c *Context) Cookie(name string) (string, error) {
return val, nil return val, nil
} }
// Render writes the response headers and calls render.Render to render data.
func (c *Context) Render(code int, r render.Render) { func (c *Context) Render(code int, r render.Render) {
c.Status(code) c.Status(code)
@ -745,9 +777,9 @@ func (c *Context) JSONP(code int, obj interface{}) {
callback := c.DefaultQuery("callback", "") callback := c.DefaultQuery("callback", "")
if callback == "" { if callback == "" {
c.Render(code, render.JSON{Data: obj}) c.Render(code, render.JSON{Data: obj})
} else { return
c.Render(code, render.JsonpJSON{Callback: callback, Data: obj})
} }
c.Render(code, render.JsonpJSON{Callback: callback, Data: obj})
} }
// JSON serializes the given struct as JSON into the response body. // JSON serializes the given struct as JSON into the response body.
@ -773,6 +805,11 @@ func (c *Context) YAML(code int, obj interface{}) {
c.Render(code, render.YAML{Data: obj}) c.Render(code, render.YAML{Data: obj})
} }
// ProtoBuf serializes the given struct as ProtoBuf into the response body.
func (c *Context) ProtoBuf(code int, obj interface{}) {
c.Render(code, render.ProtoBuf{Data: obj})
}
// String writes the given string into the response body. // String writes the given string into the response body.
func (c *Context) String(code int, format string, values ...interface{}) { func (c *Context) String(code int, format string, values ...interface{}) {
c.Render(code, render.String{Format: format, Data: values}) c.Render(code, render.String{Format: format, Data: values})
@ -818,6 +855,7 @@ func (c *Context) SSEvent(name string, message interface{}) {
}) })
} }
// Stream sends a streaming response.
func (c *Context) Stream(step func(w io.Writer) bool) { func (c *Context) Stream(step func(w io.Writer) bool) {
w := c.Writer w := c.Writer
clientGone := w.CloseNotify() clientGone := w.CloseNotify()
@ -839,6 +877,7 @@ func (c *Context) Stream(step func(w io.Writer) bool) {
/******** CONTENT NEGOTIATION *******/ /******** CONTENT NEGOTIATION *******/
/************************************/ /************************************/
// Negotiate contains all negotiations data.
type Negotiate struct { type Negotiate struct {
Offered []string Offered []string
HTMLName string HTMLName string
@ -848,6 +887,7 @@ type Negotiate struct {
Data interface{} Data interface{}
} }
// Negotiate calls different Render according acceptable Accept format.
func (c *Context) Negotiate(code int, config Negotiate) { func (c *Context) Negotiate(code int, config Negotiate) {
switch c.NegotiateFormat(config.Offered...) { switch c.NegotiateFormat(config.Offered...) {
case binding.MIMEJSON: case binding.MIMEJSON:
@ -867,6 +907,7 @@ func (c *Context) Negotiate(code int, config Negotiate) {
} }
} }
// NegotiateFormat returns an acceptable Accept format.
func (c *Context) NegotiateFormat(offered ...string) string { func (c *Context) NegotiateFormat(offered ...string) string {
assert1(len(offered) > 0, "you must provide at least one offer") assert1(len(offered) > 0, "you must provide at least one offer")
@ -886,6 +927,7 @@ func (c *Context) NegotiateFormat(offered ...string) string {
return "" return ""
} }
// SetAccepted sets Accept header data.
func (c *Context) SetAccepted(formats ...string) { func (c *Context) SetAccepted(formats ...string) {
c.Accepted = formats c.Accepted = formats
} }

View File

@ -1,13 +0,0 @@
#!/usr/bin/env bash
set -e
echo "mode: count" > coverage.out
for d in $(go list ./... | grep -E 'gin$|binding$|render$' | grep -v 'examples'); do
go test -v -covermode=count -coverprofile=profile.out $d
if [ -f profile.out ]; then
cat profile.out | grep -v "mode:" >> coverage.out
rm profile.out
fi
done

View File

@ -6,13 +6,15 @@ package gin
import ( import (
"bytes" "bytes"
"fmt"
"html/template" "html/template"
"log" "os"
"runtime"
"strconv"
"strings"
) )
func init() { const ginSupportMinGoVer = 6
log.SetFlags(0)
}
// IsDebugging returns true if the framework is running in debug mode. // IsDebugging returns true if the framework is running in debug mode.
// Use SetMode(gin.ReleaseMode) to disable debug mode. // Use SetMode(gin.ReleaseMode) to disable debug mode.
@ -20,11 +22,18 @@ func IsDebugging() bool {
return ginMode == debugCode return ginMode == debugCode
} }
// DebugPrintRouteFunc indicates debug log output format.
var DebugPrintRouteFunc func(httpMethod, absolutePath, handlerName string, nuHandlers int)
func debugPrintRoute(httpMethod, absolutePath string, handlers HandlersChain) { func debugPrintRoute(httpMethod, absolutePath string, handlers HandlersChain) {
if IsDebugging() { if IsDebugging() {
nuHandlers := len(handlers) nuHandlers := len(handlers)
handlerName := nameOfFunction(handlers.Last()) handlerName := nameOfFunction(handlers.Last())
if DebugPrintRouteFunc == nil {
debugPrint("%-6s %-25s --> %s (%d handlers)\n", httpMethod, absolutePath, handlerName, nuHandlers) debugPrint("%-6s %-25s --> %s (%d handlers)\n", httpMethod, absolutePath, handlerName, nuHandlers)
} else {
DebugPrintRouteFunc(httpMethod, absolutePath, handlerName, nuHandlers)
}
} }
} }
@ -42,14 +51,28 @@ func debugPrintLoadTemplate(tmpl *template.Template) {
func debugPrint(format string, values ...interface{}) { func debugPrint(format string, values ...interface{}) {
if IsDebugging() { if IsDebugging() {
log.Printf("[GIN-debug] "+format, values...) if !strings.HasSuffix(format, "\n") {
format += "\n"
}
fmt.Fprintf(os.Stderr, "[GIN-debug] "+format, values...)
} }
} }
func getMinVer(v string) (uint64, error) {
first := strings.IndexByte(v, '.')
last := strings.LastIndexByte(v, '.')
if first == last {
return strconv.ParseUint(v[first+1:], 10, 64)
}
return strconv.ParseUint(v[first+1:last], 10, 64)
}
func debugPrintWARNINGDefault() { func debugPrintWARNINGDefault() {
if v, e := getMinVer(runtime.Version()); e == nil && v <= ginSupportMinGoVer {
debugPrint(`[WARNING] Now Gin requires Go 1.6 or later and Go 1.7 will be required soon. debugPrint(`[WARNING] Now Gin requires Go 1.6 or later and Go 1.7 will be required soon.
`) `)
}
debugPrint(`[WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached. debugPrint(`[WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
`) `)

View File

@ -9,21 +9,28 @@ import (
"fmt" "fmt"
"reflect" "reflect"
"github.com/gin-gonic/gin/json" "github.com/gin-gonic/gin/internal/json"
) )
// ErrorType is an unsigned 64-bit error code as defined in the gin spec.
type ErrorType uint64 type ErrorType uint64
const ( const (
ErrorTypeBind ErrorType = 1 << 63 // used when c.Bind() fails // ErrorTypeBind is used when Context.Bind() fails.
ErrorTypeRender ErrorType = 1 << 62 // used when c.Render() fails ErrorTypeBind ErrorType = 1 << 63
// ErrorTypeRender is used when Context.Render() fails.
ErrorTypeRender ErrorType = 1 << 62
// ErrorTypePrivate indicates a private error.
ErrorTypePrivate ErrorType = 1 << 0 ErrorTypePrivate ErrorType = 1 << 0
// ErrorTypePublic indicates a public error.
ErrorTypePublic ErrorType = 1 << 1 ErrorTypePublic ErrorType = 1 << 1
// ErrorTypeAny indicates any other error.
ErrorTypeAny ErrorType = 1<<64 - 1 ErrorTypeAny ErrorType = 1<<64 - 1
// ErrorTypeNu indicates any other error.
ErrorTypeNu = 2 ErrorTypeNu = 2
) )
// Error represents a error's specification.
type Error struct { type Error struct {
Err error Err error
Type ErrorType Type ErrorType
@ -34,16 +41,19 @@ type errorMsgs []*Error
var _ error = &Error{} var _ error = &Error{}
// SetType sets the error's type.
func (msg *Error) SetType(flags ErrorType) *Error { func (msg *Error) SetType(flags ErrorType) *Error {
msg.Type = flags msg.Type = flags
return msg return msg
} }
// SetMeta sets the error's meta data.
func (msg *Error) SetMeta(data interface{}) *Error { func (msg *Error) SetMeta(data interface{}) *Error {
msg.Meta = data msg.Meta = data
return msg return msg
} }
// JSON creates a properly formated JSON
func (msg *Error) JSON() interface{} { func (msg *Error) JSON() interface{} {
json := H{} json := H{}
if msg.Meta != nil { if msg.Meta != nil {
@ -70,11 +80,12 @@ func (msg *Error) MarshalJSON() ([]byte, error) {
return json.Marshal(msg.JSON()) return json.Marshal(msg.JSON())
} }
// Error implements the error interface // Error implements the error interface.
func (msg Error) Error() string { func (msg Error) Error() string {
return msg.Err.Error() return msg.Err.Error()
} }
// IsType judges one error.
func (msg *Error) IsType(flags ErrorType) bool { func (msg *Error) IsType(flags ErrorType) bool {
return (msg.Type & flags) > 0 return (msg.Type & flags) > 0
} }
@ -138,6 +149,7 @@ func (a errorMsgs) JSON() interface{} {
} }
} }
// MarshalJSON implements the json.Marshaller interface.
func (a errorMsgs) MarshalJSON() ([]byte, error) { func (a errorMsgs) MarshalJSON() ([]byte, error) {
return json.Marshal(a.JSON()) return json.Marshal(a.JSON())
} }

View File

@ -5,6 +5,7 @@
package gin package gin
import ( import (
"fmt"
"html/template" "html/template"
"net" "net"
"net/http" "net/http"
@ -14,11 +15,7 @@ import (
"github.com/gin-gonic/gin/render" "github.com/gin-gonic/gin/render"
) )
const ( const defaultMultipartMemory = 32 << 20 // 32 MB
// Version is Framework's version.
Version = "v1.3.0"
defaultMultipartMemory = 32 << 20 // 32 MB
)
var ( var (
default404Body = []byte("404 page not found") default404Body = []byte("404 page not found")
@ -26,7 +23,10 @@ var (
defaultAppEngine bool defaultAppEngine bool
) )
// HandlerFunc defines the handler used by gin middleware as return value.
type HandlerFunc func(*Context) type HandlerFunc func(*Context)
// HandlersChain defines a HandlerFunc array.
type HandlersChain []HandlerFunc type HandlersChain []HandlerFunc
// Last returns the last handler in the chain. ie. the last handler is the main own. // Last returns the last handler in the chain. ie. the last handler is the main own.
@ -37,12 +37,15 @@ func (c HandlersChain) Last() HandlerFunc {
return nil return nil
} }
// RouteInfo represents a request route's specification which contains method and path and its handler.
type RouteInfo struct { type RouteInfo struct {
Method string Method string
Path string Path string
Handler string Handler string
HandlerFunc HandlerFunc
} }
// RoutesInfo defines a RouteInfo array.
type RoutesInfo []RouteInfo type RoutesInfo []RouteInfo
// Engine is the framework's instance, it contains the muxer, middleware and configuration settings. // Engine is the framework's instance, it contains the muxer, middleware and configuration settings.
@ -155,6 +158,7 @@ func (engine *Engine) allocateContext() *Context {
return &Context{engine: engine} return &Context{engine: engine}
} }
// Delims sets template left and right delims and returns a Engine instance.
func (engine *Engine) Delims(left, right string) *Engine { func (engine *Engine) Delims(left, right string) *Engine {
engine.delims = render.Delims{Left: left, Right: right} engine.delims = render.Delims{Left: left, Right: right}
return engine return engine
@ -264,10 +268,12 @@ func (engine *Engine) Routes() (routes RoutesInfo) {
func iterate(path, method string, routes RoutesInfo, root *node) RoutesInfo { func iterate(path, method string, routes RoutesInfo, root *node) RoutesInfo {
path += root.path path += root.path
if len(root.handlers) > 0 { if len(root.handlers) > 0 {
handlerFunc := root.handlers.Last()
routes = append(routes, RouteInfo{ routes = append(routes, RouteInfo{
Method: method, Method: method,
Path: path, Path: path,
Handler: nameOfFunction(root.handlers.Last()), Handler: nameOfFunction(handlerFunc),
HandlerFunc: handlerFunc,
}) })
} }
for _, child := range root.children { for _, child := range root.children {
@ -316,6 +322,23 @@ func (engine *Engine) RunUnix(file string) (err error) {
return return
} }
// RunFd attaches the router to a http.Server and starts listening and serving HTTP requests
// through the specified file descriptor.
// Note: this method will block the calling goroutine indefinitely unless an error happens.
func (engine *Engine) RunFd(fd int) (err error) {
debugPrint("Listening and serving HTTP on fd@%d", fd)
defer func() { debugPrintError(err) }()
f := os.NewFile(uintptr(fd), fmt.Sprintf("fd@%d", fd))
listener, err := net.FileListener(f)
if err != nil {
return
}
defer listener.Close()
err = http.Serve(listener, engine)
return
}
// ServeHTTP conforms to the http.Handler interface. // ServeHTTP conforms to the http.Handler interface.
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) { func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
c := engine.pool.Get().(*Context) c := engine.pool.Get().(*Context)
@ -334,7 +357,6 @@ func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
func (engine *Engine) HandleContext(c *Context) { func (engine *Engine) HandleContext(c *Context) {
c.reset() c.reset()
engine.handleHTTPRequest(c) engine.handleHTTPRequest(c)
engine.pool.Put(c)
} }
func (engine *Engine) handleHTTPRequest(c *Context) { func (engine *Engine) handleHTTPRequest(c *Context) {

View File

@ -1,15 +0,0 @@
// Copyright 2017 Bo-Yi Wu. All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.
// +build !jsoniter
package json
import "encoding/json"
var (
Marshal = json.Marshal
MarshalIndent = json.MarshalIndent
NewDecoder = json.NewDecoder
)

View File

@ -1,16 +0,0 @@
// Copyright 2017 Bo-Yi Wu. All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.
// +build jsoniter
package json
import "github.com/json-iterator/go"
var (
json = jsoniter.ConfigCompatibleWithStandardLibrary
Marshal = json.Marshal
MarshalIndent = json.MarshalIndent
NewDecoder = json.NewDecoder
)

View File

@ -17,7 +17,7 @@ import (
var ( var (
green = string([]byte{27, 91, 57, 55, 59, 52, 50, 109}) green = string([]byte{27, 91, 57, 55, 59, 52, 50, 109})
white = string([]byte{27, 91, 57, 48, 59, 52, 55, 109}) white = string([]byte{27, 91, 57, 48, 59, 52, 55, 109})
yellow = string([]byte{27, 91, 57, 55, 59, 52, 51, 109}) yellow = string([]byte{27, 91, 57, 48, 59, 52, 51, 109})
red = string([]byte{27, 91, 57, 55, 59, 52, 49, 109}) red = string([]byte{27, 91, 57, 55, 59, 52, 49, 109})
blue = string([]byte{27, 91, 57, 55, 59, 52, 52, 109}) blue = string([]byte{27, 91, 57, 55, 59, 52, 52, 109})
magenta = string([]byte{27, 91, 57, 55, 59, 52, 53, 109}) magenta = string([]byte{27, 91, 57, 55, 59, 52, 53, 109})
@ -53,7 +53,7 @@ func Logger() HandlerFunc {
return LoggerWithWriter(DefaultWriter) return LoggerWithWriter(DefaultWriter)
} }
// LoggerWithWriter instance a Logger middleware with the specified writter buffer. // LoggerWithWriter instance a Logger middleware with the specified writer buffer.
// Example: os.Stdout, a file opened in write mode, a socket... // Example: os.Stdout, a file opened in write mode, a socket...
func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc { func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc {
isTerm := true isTerm := true

View File

@ -11,11 +11,15 @@ import (
"github.com/gin-gonic/gin/binding" "github.com/gin-gonic/gin/binding"
) )
// ENV_GIN_MODE indicates environment name for gin mode.
const ENV_GIN_MODE = "GIN_MODE" const ENV_GIN_MODE = "GIN_MODE"
const ( const (
// DebugMode indicates gin mode is debug.
DebugMode = "debug" DebugMode = "debug"
// ReleaseMode indicates gin mode is release.
ReleaseMode = "release" ReleaseMode = "release"
// TestMode indicates gin mode is test.
TestMode = "test" TestMode = "test"
) )
const ( const (
@ -24,7 +28,7 @@ const (
testCode testCode
) )
// DefaultWriter is the default io.Writer used the Gin for debug output and // DefaultWriter is the default io.Writer used by Gin for debug output and
// middleware output like Logger() or Recovery(). // middleware output like Logger() or Recovery().
// Note that both Logger and Recovery provides custom ways to configure their // Note that both Logger and Recovery provides custom ways to configure their
// output io.Writer. // output io.Writer.
@ -32,6 +36,8 @@ const (
// import "github.com/mattn/go-colorable" // import "github.com/mattn/go-colorable"
// gin.DefaultWriter = colorable.NewColorableStdout() // gin.DefaultWriter = colorable.NewColorableStdout()
var DefaultWriter io.Writer = os.Stdout var DefaultWriter io.Writer = os.Stdout
// DefaultErrorWriter is the default io.Writer used by Gin to debug errors
var DefaultErrorWriter io.Writer = os.Stderr var DefaultErrorWriter io.Writer = os.Stderr
var ginMode = debugCode var ginMode = debugCode
@ -42,6 +48,7 @@ func init() {
SetMode(mode) SetMode(mode)
} }
// SetMode sets gin mode according to input string.
func SetMode(value string) { func SetMode(value string) {
switch value { switch value {
case DebugMode, "": case DebugMode, "":
@ -59,14 +66,18 @@ func SetMode(value string) {
modeName = value modeName = value
} }
// DisableBindValidation closes the default validator.
func DisableBindValidation() { func DisableBindValidation() {
binding.Validator = nil binding.Validator = nil
} }
// EnableJsonDecoderUseNumber sets true for binding.EnableDecoderUseNumberto to
// call the UseNumber method on the JSON Decoder instance.
func EnableJsonDecoderUseNumber() { func EnableJsonDecoderUseNumber() {
binding.EnableDecoderUseNumber = true binding.EnableDecoderUseNumber = true
} }
// Mode returns currently gin mode.
func Mode() string { func Mode() string {
return modeName return modeName
} }

View File

@ -10,9 +10,12 @@ import (
"io" "io"
"io/ioutil" "io/ioutil"
"log" "log"
"net"
"net/http" "net/http"
"net/http/httputil" "net/http/httputil"
"os"
"runtime" "runtime"
"strings"
"time" "time"
) )
@ -37,13 +40,38 @@ func RecoveryWithWriter(out io.Writer) HandlerFunc {
return func(c *Context) { return func(c *Context) {
defer func() { defer func() {
if err := recover(); err != nil { if err := recover(); err != nil {
// Check for a broken connection, as it is not really a
// condition that warrants a panic stack trace.
var brokenPipe bool
if ne, ok := err.(*net.OpError); ok {
if se, ok := ne.Err.(*os.SyscallError); ok {
if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") {
brokenPipe = true
}
}
}
if logger != nil { if logger != nil {
stack := stack(3) stack := stack(3)
httprequest, _ := httputil.DumpRequest(c.Request, false) httprequest, _ := httputil.DumpRequest(c.Request, false)
logger.Printf("[Recovery] %s panic recovered:\n%s\n%s\n%s%s", timeFormat(time.Now()), string(httprequest), err, stack, reset) if brokenPipe {
logger.Printf("%s\n%s%s", err, string(httprequest), reset)
} else if IsDebugging() {
logger.Printf("[Recovery] %s panic recovered:\n%s\n%s\n%s%s",
timeFormat(time.Now()), string(httprequest), err, stack, reset)
} else {
logger.Printf("[Recovery] %s panic recovered:\n%s\n%s%s",
timeFormat(time.Now()), err, stack, reset)
} }
}
// If the connection is dead, we can't write a status to it.
if brokenPipe {
c.Error(err.(error))
c.Abort()
} else {
c.AbortWithStatus(http.StatusInternalServerError) c.AbortWithStatus(http.StatusInternalServerError)
} }
}
}() }()
c.Next() c.Next()
} }

View File

@ -6,6 +6,7 @@ package render
import "net/http" import "net/http"
// Data contains ContentType and bytes data.
type Data struct { type Data struct {
ContentType string ContentType string
Data []byte Data []byte
@ -18,6 +19,7 @@ func (r Data) Render(w http.ResponseWriter) (err error) {
return return
} }
// WriteContentType (Data) writes custom ContentType.
func (r Data) WriteContentType(w http.ResponseWriter) { func (r Data) WriteContentType(w http.ResponseWriter) {
writeContentType(w, []string{r.ContentType}) writeContentType(w, []string{r.ContentType})
} }

View File

@ -9,20 +9,27 @@ import (
"net/http" "net/http"
) )
// Delims represents a set of Left and Right delimiters for HTML template rendering.
type Delims struct { type Delims struct {
// Left delimiter, defaults to {{.
Left string Left string
// Right delimiter, defaults to }}.
Right string Right string
} }
// HTMLRender interface is to be implemented by HTMLProduction and HTMLDebug.
type HTMLRender interface { type HTMLRender interface {
// Instance returns an HTML instance.
Instance(string, interface{}) Render Instance(string, interface{}) Render
} }
// HTMLProduction contains template reference and its delims.
type HTMLProduction struct { type HTMLProduction struct {
Template *template.Template Template *template.Template
Delims Delims Delims Delims
} }
// HTMLDebug contains template delims and pattern and function with file list.
type HTMLDebug struct { type HTMLDebug struct {
Files []string Files []string
Glob string Glob string
@ -30,6 +37,7 @@ type HTMLDebug struct {
FuncMap template.FuncMap FuncMap template.FuncMap
} }
// HTML contains template reference and its name with given interface object.
type HTML struct { type HTML struct {
Template *template.Template Template *template.Template
Name string Name string
@ -38,6 +46,7 @@ type HTML struct {
var htmlContentType = []string{"text/html; charset=utf-8"} var htmlContentType = []string{"text/html; charset=utf-8"}
// Instance (HTMLProduction) returns an HTML instance which it realizes Render interface.
func (r HTMLProduction) Instance(name string, data interface{}) Render { func (r HTMLProduction) Instance(name string, data interface{}) Render {
return HTML{ return HTML{
Template: r.Template, Template: r.Template,
@ -46,6 +55,7 @@ func (r HTMLProduction) Instance(name string, data interface{}) Render {
} }
} }
// Instance (HTMLDebug) returns an HTML instance which it realizes Render interface.
func (r HTMLDebug) Instance(name string, data interface{}) Render { func (r HTMLDebug) Instance(name string, data interface{}) Render {
return HTML{ return HTML{
Template: r.loadTemplate(), Template: r.loadTemplate(),
@ -66,6 +76,7 @@ func (r HTMLDebug) loadTemplate() *template.Template {
panic("the HTML debug render was created without files or glob pattern") panic("the HTML debug render was created without files or glob pattern")
} }
// Render (HTML) executes template and writes its result with custom ContentType for response.
func (r HTML) Render(w http.ResponseWriter) error { func (r HTML) Render(w http.ResponseWriter) error {
r.WriteContentType(w) r.WriteContentType(w)
@ -75,6 +86,7 @@ func (r HTML) Render(w http.ResponseWriter) error {
return r.Template.ExecuteTemplate(w, r.Name, r.Data) return r.Template.ExecuteTemplate(w, r.Name, r.Data)
} }
// WriteContentType (HTML) writes HTML ContentType.
func (r HTML) WriteContentType(w http.ResponseWriter) { func (r HTML) WriteContentType(w http.ResponseWriter) {
writeContentType(w, htmlContentType) writeContentType(w, htmlContentType)
} }

View File

@ -10,37 +10,44 @@ import (
"html/template" "html/template"
"net/http" "net/http"
"github.com/gin-gonic/gin/json" "github.com/gin-gonic/gin/internal/json"
) )
// JSON contains the given interface object.
type JSON struct { type JSON struct {
Data interface{} Data interface{}
} }
// IndentedJSON contains the given interface object.
type IndentedJSON struct { type IndentedJSON struct {
Data interface{} Data interface{}
} }
// SecureJSON contains the given interface object and its prefix.
type SecureJSON struct { type SecureJSON struct {
Prefix string Prefix string
Data interface{} Data interface{}
} }
// JsonpJSON contains the given interface object its callback.
type JsonpJSON struct { type JsonpJSON struct {
Callback string Callback string
Data interface{} Data interface{}
} }
// AsciiJSON contains the given interface object.
type AsciiJSON struct { type AsciiJSON struct {
Data interface{} Data interface{}
} }
// SecureJSONPrefix is a string which represents SecureJSON prefix.
type SecureJSONPrefix string type SecureJSONPrefix string
var jsonContentType = []string{"application/json; charset=utf-8"} var jsonContentType = []string{"application/json; charset=utf-8"}
var jsonpContentType = []string{"application/javascript; charset=utf-8"} var jsonpContentType = []string{"application/javascript; charset=utf-8"}
var jsonAsciiContentType = []string{"application/json"} var jsonAsciiContentType = []string{"application/json"}
// Render (JSON) writes data with custom ContentType.
func (r JSON) Render(w http.ResponseWriter) (err error) { func (r JSON) Render(w http.ResponseWriter) (err error) {
if err = WriteJSON(w, r.Data); err != nil { if err = WriteJSON(w, r.Data); err != nil {
panic(err) panic(err)
@ -48,10 +55,12 @@ func (r JSON) Render(w http.ResponseWriter) (err error) {
return return
} }
// WriteContentType (JSON) writes JSON ContentType.
func (r JSON) WriteContentType(w http.ResponseWriter) { func (r JSON) WriteContentType(w http.ResponseWriter) {
writeContentType(w, jsonContentType) writeContentType(w, jsonContentType)
} }
// WriteJSON marshals the given interface object and writes it with custom ContentType.
func WriteJSON(w http.ResponseWriter, obj interface{}) error { func WriteJSON(w http.ResponseWriter, obj interface{}) error {
writeContentType(w, jsonContentType) writeContentType(w, jsonContentType)
jsonBytes, err := json.Marshal(obj) jsonBytes, err := json.Marshal(obj)
@ -62,6 +71,7 @@ func WriteJSON(w http.ResponseWriter, obj interface{}) error {
return nil return nil
} }
// Render (IndentedJSON) marshals the given interface object and writes it with custom ContentType.
func (r IndentedJSON) Render(w http.ResponseWriter) error { func (r IndentedJSON) Render(w http.ResponseWriter) error {
r.WriteContentType(w) r.WriteContentType(w)
jsonBytes, err := json.MarshalIndent(r.Data, "", " ") jsonBytes, err := json.MarshalIndent(r.Data, "", " ")
@ -72,10 +82,12 @@ func (r IndentedJSON) Render(w http.ResponseWriter) error {
return nil return nil
} }
// WriteContentType (IndentedJSON) writes JSON ContentType.
func (r IndentedJSON) WriteContentType(w http.ResponseWriter) { func (r IndentedJSON) WriteContentType(w http.ResponseWriter) {
writeContentType(w, jsonContentType) writeContentType(w, jsonContentType)
} }
// Render (SecureJSON) marshals the given interface object and writes it with custom ContentType.
func (r SecureJSON) Render(w http.ResponseWriter) error { func (r SecureJSON) Render(w http.ResponseWriter) error {
r.WriteContentType(w) r.WriteContentType(w)
jsonBytes, err := json.Marshal(r.Data) jsonBytes, err := json.Marshal(r.Data)
@ -90,10 +102,12 @@ func (r SecureJSON) Render(w http.ResponseWriter) error {
return nil return nil
} }
// WriteContentType (SecureJSON) writes JSON ContentType.
func (r SecureJSON) WriteContentType(w http.ResponseWriter) { func (r SecureJSON) WriteContentType(w http.ResponseWriter) {
writeContentType(w, jsonContentType) writeContentType(w, jsonContentType)
} }
// Render (JsonpJSON) marshals the given interface object and writes it and its callback with custom ContentType.
func (r JsonpJSON) Render(w http.ResponseWriter) (err error) { func (r JsonpJSON) Render(w http.ResponseWriter) (err error) {
r.WriteContentType(w) r.WriteContentType(w)
ret, err := json.Marshal(r.Data) ret, err := json.Marshal(r.Data)
@ -115,10 +129,12 @@ func (r JsonpJSON) Render(w http.ResponseWriter) (err error) {
return nil return nil
} }
// WriteContentType (JsonpJSON) writes Javascript ContentType.
func (r JsonpJSON) WriteContentType(w http.ResponseWriter) { func (r JsonpJSON) WriteContentType(w http.ResponseWriter) {
writeContentType(w, jsonpContentType) writeContentType(w, jsonpContentType)
} }
// Render (AsciiJSON) marshals the given interface object and writes it with custom ContentType.
func (r AsciiJSON) Render(w http.ResponseWriter) (err error) { func (r AsciiJSON) Render(w http.ResponseWriter) (err error) {
r.WriteContentType(w) r.WriteContentType(w)
ret, err := json.Marshal(r.Data) ret, err := json.Marshal(r.Data)
@ -128,10 +144,8 @@ func (r AsciiJSON) Render(w http.ResponseWriter) (err error) {
var buffer bytes.Buffer var buffer bytes.Buffer
for _, r := range string(ret) { for _, r := range string(ret) {
cvt := "" cvt := string(r)
if r < 128 { if r >= 128 {
cvt = string(r)
} else {
cvt = fmt.Sprintf("\\u%04x", int64(r)) cvt = fmt.Sprintf("\\u%04x", int64(r))
} }
buffer.WriteString(cvt) buffer.WriteString(cvt)
@ -141,6 +155,7 @@ func (r AsciiJSON) Render(w http.ResponseWriter) (err error) {
return nil return nil
} }
// WriteContentType (AsciiJSON) writes JSON ContentType.
func (r AsciiJSON) WriteContentType(w http.ResponseWriter) { func (r AsciiJSON) WriteContentType(w http.ResponseWriter) {
writeContentType(w, jsonAsciiContentType) writeContentType(w, jsonAsciiContentType)
} }

View File

@ -10,22 +10,26 @@ import (
"github.com/ugorji/go/codec" "github.com/ugorji/go/codec"
) )
// MsgPack contains the given interface object.
type MsgPack struct { type MsgPack struct {
Data interface{} Data interface{}
} }
var msgpackContentType = []string{"application/msgpack; charset=utf-8"} var msgpackContentType = []string{"application/msgpack; charset=utf-8"}
// WriteContentType (MsgPack) writes MsgPack ContentType.
func (r MsgPack) WriteContentType(w http.ResponseWriter) { func (r MsgPack) WriteContentType(w http.ResponseWriter) {
writeContentType(w, msgpackContentType) writeContentType(w, msgpackContentType)
} }
// Render (MsgPack) encodes the given interface object and writes data with custom ContentType.
func (r MsgPack) Render(w http.ResponseWriter) error { func (r MsgPack) Render(w http.ResponseWriter) error {
return WriteMsgPack(w, r.Data) return WriteMsgPack(w, r.Data)
} }
// WriteMsgPack writes MsgPack ContentType and encodes the given interface object.
func WriteMsgPack(w http.ResponseWriter, obj interface{}) error { func WriteMsgPack(w http.ResponseWriter, obj interface{}) error {
writeContentType(w, msgpackContentType) writeContentType(w, msgpackContentType)
var h codec.Handle = new(codec.MsgpackHandle) var mh codec.MsgpackHandle
return codec.NewEncoder(w, h).Encode(obj) return codec.NewEncoder(w, &mh).Encode(obj)
} }

View File

@ -1,3 +1,7 @@
// Copyright 2018 Gin Core Team. All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.
package render package render
import ( import (
@ -6,6 +10,7 @@ import (
"strconv" "strconv"
) )
// Reader contains the IO reader and its length, and custom ContentType and other headers.
type Reader struct { type Reader struct {
ContentType string ContentType string
ContentLength int64 ContentLength int64
@ -22,10 +27,12 @@ func (r Reader) Render(w http.ResponseWriter) (err error) {
return return
} }
// WriteContentType (Reader) writes custom ContentType.
func (r Reader) WriteContentType(w http.ResponseWriter) { func (r Reader) WriteContentType(w http.ResponseWriter) {
writeContentType(w, []string{r.ContentType}) writeContentType(w, []string{r.ContentType})
} }
// writeHeaders writes custom Header.
func (r Reader) writeHeaders(w http.ResponseWriter, headers map[string]string) { func (r Reader) writeHeaders(w http.ResponseWriter, headers map[string]string) {
header := w.Header() header := w.Header()
for k, v := range headers { for k, v := range headers {

View File

@ -9,12 +9,14 @@ import (
"net/http" "net/http"
) )
// Redirect contains the http request reference and redirects status code and location.
type Redirect struct { type Redirect struct {
Code int Code int
Request *http.Request Request *http.Request
Location string Location string
} }
// Render (Redirect) redirects the http request to new location and writes redirect response.
func (r Redirect) Render(w http.ResponseWriter) error { func (r Redirect) Render(w http.ResponseWriter) error {
// todo(thinkerou): go1.6 not support StatusPermanentRedirect(308) // todo(thinkerou): go1.6 not support StatusPermanentRedirect(308)
// when we upgrade go version we can use http.StatusPermanentRedirect // when we upgrade go version we can use http.StatusPermanentRedirect
@ -25,4 +27,5 @@ func (r Redirect) Render(w http.ResponseWriter) error {
return nil return nil
} }
// WriteContentType (Redirect) don't write any ContentType.
func (r Redirect) WriteContentType(http.ResponseWriter) {} func (r Redirect) WriteContentType(http.ResponseWriter) {}

View File

@ -6,8 +6,11 @@ package render
import "net/http" import "net/http"
// Render interface is to be implemented by JSON, XML, HTML, YAML and so on.
type Render interface { type Render interface {
// Render writes data with custom ContentType.
Render(http.ResponseWriter) error Render(http.ResponseWriter) error
// WriteContentType writes custom ContentType.
WriteContentType(w http.ResponseWriter) WriteContentType(w http.ResponseWriter)
} }
@ -27,6 +30,7 @@ var (
_ Render = MsgPack{} _ Render = MsgPack{}
_ Render = Reader{} _ Render = Reader{}
_ Render = AsciiJSON{} _ Render = AsciiJSON{}
_ Render = ProtoBuf{}
) )
func writeContentType(w http.ResponseWriter, value []string) { func writeContentType(w http.ResponseWriter, value []string) {

View File

@ -10,6 +10,7 @@ import (
"net/http" "net/http"
) )
// String contains the given interface object slice and its format.
type String struct { type String struct {
Format string Format string
Data []interface{} Data []interface{}
@ -17,20 +18,23 @@ type String struct {
var plainContentType = []string{"text/plain; charset=utf-8"} var plainContentType = []string{"text/plain; charset=utf-8"}
// Render (String) writes data with custom ContentType.
func (r String) Render(w http.ResponseWriter) error { func (r String) Render(w http.ResponseWriter) error {
WriteString(w, r.Format, r.Data) WriteString(w, r.Format, r.Data)
return nil return nil
} }
// WriteContentType (String) writes Plain ContentType.
func (r String) WriteContentType(w http.ResponseWriter) { func (r String) WriteContentType(w http.ResponseWriter) {
writeContentType(w, plainContentType) writeContentType(w, plainContentType)
} }
// WriteString writes data according to its format and write custom ContentType.
func WriteString(w http.ResponseWriter, format string, data []interface{}) { func WriteString(w http.ResponseWriter, format string, data []interface{}) {
writeContentType(w, plainContentType) writeContentType(w, plainContentType)
if len(data) > 0 { if len(data) > 0 {
fmt.Fprintf(w, format, data...) fmt.Fprintf(w, format, data...)
} else { return
io.WriteString(w, format)
} }
io.WriteString(w, format)
} }

View File

@ -9,17 +9,20 @@ import (
"net/http" "net/http"
) )
// XML contains the given interface object.
type XML struct { type XML struct {
Data interface{} Data interface{}
} }
var xmlContentType = []string{"application/xml; charset=utf-8"} var xmlContentType = []string{"application/xml; charset=utf-8"}
// Render (XML) encodes the given interface object and writes data with custom ContentType.
func (r XML) Render(w http.ResponseWriter) error { func (r XML) Render(w http.ResponseWriter) error {
r.WriteContentType(w) r.WriteContentType(w)
return xml.NewEncoder(w).Encode(r.Data) return xml.NewEncoder(w).Encode(r.Data)
} }
// WriteContentType (XML) writes XML ContentType for response.
func (r XML) WriteContentType(w http.ResponseWriter) { func (r XML) WriteContentType(w http.ResponseWriter) {
writeContentType(w, xmlContentType) writeContentType(w, xmlContentType)
} }

View File

@ -10,12 +10,14 @@ import (
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
) )
// YAML contains the given interface object.
type YAML struct { type YAML struct {
Data interface{} Data interface{}
} }
var yamlContentType = []string{"application/x-yaml; charset=utf-8"} var yamlContentType = []string{"application/x-yaml; charset=utf-8"}
// Render (YAML) marshals the given interface object and writes data with custom ContentType.
func (r YAML) Render(w http.ResponseWriter) error { func (r YAML) Render(w http.ResponseWriter) error {
r.WriteContentType(w) r.WriteContentType(w)
@ -28,6 +30,7 @@ func (r YAML) Render(w http.ResponseWriter) error {
return nil return nil
} }
// WriteContentType (YAML) writes YAML ContentType for response.
func (r YAML) WriteContentType(w http.ResponseWriter) { func (r YAML) WriteContentType(w http.ResponseWriter) {
writeContentType(w, yamlContentType) writeContentType(w, yamlContentType)
} }

View File

@ -11,11 +11,13 @@ import (
"strings" "strings"
) )
// IRouter defines all router handle interface includes single and group router.
type IRouter interface { type IRouter interface {
IRoutes IRoutes
Group(string, ...HandlerFunc) *RouterGroup Group(string, ...HandlerFunc) *RouterGroup
} }
// IRoutes defines all router handle interface.
type IRoutes interface { type IRoutes interface {
Use(...HandlerFunc) IRoutes Use(...HandlerFunc) IRoutes
@ -34,8 +36,8 @@ type IRoutes interface {
StaticFS(string, http.FileSystem) IRoutes StaticFS(string, http.FileSystem) IRoutes
} }
// RouterGroup is used internally to configure router, a RouterGroup is associated with a prefix // RouterGroup is used internally to configure router, a RouterGroup is associated with
// and an array of handlers (middleware). // a prefix and an array of handlers (middleware).
type RouterGroup struct { type RouterGroup struct {
Handlers HandlersChain Handlers HandlersChain
basePath string basePath string
@ -51,8 +53,8 @@ func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {
return group.returnObj() return group.returnObj()
} }
// Group creates a new router group. You should add all the routes that have common middlwares or the same path prefix. // Group creates a new router group. You should add all the routes that have common middlewares or the same path prefix.
// For example, all the routes that use a common middlware for authorization could be grouped. // For example, all the routes that use a common middleware for authorization could be grouped.
func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *RouterGroup { func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *RouterGroup {
return &RouterGroup{ return &RouterGroup{
Handlers: group.combineHandlers(handlers), Handlers: group.combineHandlers(handlers),
@ -61,6 +63,8 @@ func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *R
} }
} }
// BasePath returns the base path of router group.
// For example, if v := router.Group("/rest/n/v1/api"), v.BasePath() is "/rest/n/v1/api".
func (group *RouterGroup) BasePath() string { func (group *RouterGroup) BasePath() string {
return group.basePath return group.basePath
} }
@ -181,11 +185,22 @@ func (group *RouterGroup) StaticFS(relativePath string, fs http.FileSystem) IRou
func (group *RouterGroup) createStaticHandler(relativePath string, fs http.FileSystem) HandlerFunc { func (group *RouterGroup) createStaticHandler(relativePath string, fs http.FileSystem) HandlerFunc {
absolutePath := group.calculateAbsolutePath(relativePath) absolutePath := group.calculateAbsolutePath(relativePath)
fileServer := http.StripPrefix(absolutePath, http.FileServer(fs)) fileServer := http.StripPrefix(absolutePath, http.FileServer(fs))
_, nolisting := fs.(*onlyfilesFS)
return func(c *Context) { return func(c *Context) {
if nolisting { if _, nolisting := fs.(*onlyfilesFS); nolisting {
c.Writer.WriteHeader(http.StatusNotFound) c.Writer.WriteHeader(http.StatusNotFound)
} }
file := c.Param("filepath")
// Check if file exists and/or if we have permission to access it
if _, err := fs.Open(file); err != nil {
c.Writer.WriteHeader(http.StatusNotFound)
c.handlers = group.engine.allNoRoute
// Reset index
c.index = -1
return
}
fileServer.ServeHTTP(c.Writer, c.Request) fileServer.ServeHTTP(c.Writer, c.Request)
} }
} }

View File

@ -193,9 +193,16 @@ func (n *node) addRoute(path string, handlers HandlersChain) {
} }
} }
panic("path segment '" + path + pathSeg := path
if n.nType != catchAll {
pathSeg = strings.SplitN(path, "/", 2)[0]
}
prefix := fullPath[:strings.Index(fullPath, pathSeg)] + n.path
panic("'" + pathSeg +
"' in new path '" + fullPath +
"' conflicts with existing wildcard '" + n.path + "' conflicts with existing wildcard '" + n.path +
"' in path '" + fullPath + "'") "' in existing prefix '" + prefix +
"'")
} }
c := path[0] c := path[0]

View File

@ -14,8 +14,10 @@ import (
"strings" "strings"
) )
// BindKey indicates a default bind key.
const BindKey = "_gin-gonic/gin/bindkey" const BindKey = "_gin-gonic/gin/bindkey"
// Bind is a helper function for given interface object and returns a Gin middleware.
func Bind(val interface{}) HandlerFunc { func Bind(val interface{}) HandlerFunc {
value := reflect.ValueOf(val) value := reflect.ValueOf(val)
if value.Kind() == reflect.Ptr { if value.Kind() == reflect.Ptr {
@ -33,16 +35,14 @@ func Bind(val interface{}) HandlerFunc {
} }
} }
// WrapF is a helper function for wrapping http.HandlerFunc // WrapF is a helper function for wrapping http.HandlerFunc and returns a Gin middleware.
// Returns a Gin middleware
func WrapF(f http.HandlerFunc) HandlerFunc { func WrapF(f http.HandlerFunc) HandlerFunc {
return func(c *Context) { return func(c *Context) {
f(c.Writer, c.Request) f(c.Writer, c.Request)
} }
} }
// WrapH is a helper function for wrapping http.Handler // WrapH is a helper function for wrapping http.Handler and returns a Gin middleware.
// Returns a Gin middleware
func WrapH(h http.Handler) HandlerFunc { func WrapH(h http.Handler) HandlerFunc {
return func(c *Context) { return func(c *Context) {
h.ServeHTTP(c.Writer, c.Request) h.ServeHTTP(c.Writer, c.Request)

View File

@ -1 +0,0 @@
box: wercker/default

View File

@ -35,6 +35,7 @@ Hanno Braun <mail at hannobraun.com>
Henri Yandell <flamefew at gmail.com> Henri Yandell <flamefew at gmail.com>
Hirotaka Yamamoto <ymmt2005 at gmail.com> Hirotaka Yamamoto <ymmt2005 at gmail.com>
ICHINOSE Shogo <shogo82148 at gmail.com> ICHINOSE Shogo <shogo82148 at gmail.com>
Ilia Cimpoes <ichimpoesh at gmail.com>
INADA Naoki <songofacandy at gmail.com> INADA Naoki <songofacandy at gmail.com>
Jacek Szwec <szwec.jacek at gmail.com> Jacek Szwec <szwec.jacek at gmail.com>
James Harr <james.harr at gmail.com> James Harr <james.harr at gmail.com>
@ -72,6 +73,9 @@ Shuode Li <elemount at qq.com>
Soroush Pour <me at soroushjp.com> Soroush Pour <me at soroushjp.com>
Stan Putrya <root.vagner at gmail.com> Stan Putrya <root.vagner at gmail.com>
Stanley Gunawan <gunawan.stanley at gmail.com> Stanley Gunawan <gunawan.stanley at gmail.com>
Steven Hartland <steven.hartland at multiplay.co.uk>
Thomas Wodarek <wodarekwebpage at gmail.com>
Tom Jenkinson <tom at tjenkinson.me>
Xiangyu Hu <xiangyu.hu at outlook.com> Xiangyu Hu <xiangyu.hu at outlook.com>
Xiaobing Jiang <s7v7nislands at gmail.com> Xiaobing Jiang <s7v7nislands at gmail.com>
Xiuming Chen <cc at cxm.cc> Xiuming Chen <cc at cxm.cc>
@ -87,3 +91,4 @@ Keybase Inc.
Percona LLC Percona LLC
Pivotal Inc. Pivotal Inc.
Stripe Inc. Stripe Inc.
Multiplay Ltd.

View File

@ -1,14 +1,3 @@
## Version 1.4.1 (2018-11-14)
Bugfixes:
- Fix TIME format for binary columns (#818)
- Fix handling of empty auth plugin names (#835)
- Fix caching_sha2_password with empty password (#826)
- Fix canceled context broke mysqlConn (#862)
- Fix OldAuthSwitchRequest support (#870)
- Fix Auth Response packet for cleartext password (#887)
## Version 1.4 (2018-06-03) ## Version 1.4 (2018-06-03)
Changes: Changes:

View File

@ -40,7 +40,7 @@ A MySQL-Driver for Go's [database/sql](https://golang.org/pkg/database/sql/) pac
* Optional placeholder interpolation * Optional placeholder interpolation
## Requirements ## Requirements
* Go 1.7 or higher. We aim to support the 3 latest versions of Go. * Go 1.8 or higher. We aim to support the 3 latest versions of Go.
* MySQL (4.1+), MariaDB, Percona Server, Google CloudSQL or Sphinx (2.2.3+) * MySQL (4.1+), MariaDB, Percona Server, Google CloudSQL or Sphinx (2.2.3+)
--------------------------------------- ---------------------------------------

View File

@ -360,13 +360,15 @@ func (mc *mysqlConn) handleAuthResult(oldAuthData []byte, plugin string) error {
pubKey := mc.cfg.pubKey pubKey := mc.cfg.pubKey
if pubKey == nil { if pubKey == nil {
// request public key from server // request public key from server
data := mc.buf.takeSmallBuffer(4 + 1) data, err := mc.buf.takeSmallBuffer(4 + 1)
if err != nil {
return err
}
data[4] = cachingSha2PasswordRequestPublicKey data[4] = cachingSha2PasswordRequestPublicKey
mc.writePacket(data) mc.writePacket(data)
// parse public key // parse public key
data, err := mc.readPacket() if data, err = mc.readPacket(); err != nil {
if err != nil {
return err return err
} }

View File

@ -22,17 +22,17 @@ const defaultBufSize = 4096
// The buffer is similar to bufio.Reader / Writer but zero-copy-ish // The buffer is similar to bufio.Reader / Writer but zero-copy-ish
// Also highly optimized for this particular use case. // Also highly optimized for this particular use case.
type buffer struct { type buffer struct {
buf []byte buf []byte // buf is a byte buffer who's length and capacity are equal.
nc net.Conn nc net.Conn
idx int idx int
length int length int
timeout time.Duration timeout time.Duration
} }
// newBuffer allocates and returns a new buffer.
func newBuffer(nc net.Conn) buffer { func newBuffer(nc net.Conn) buffer {
var b [defaultBufSize]byte
return buffer{ return buffer{
buf: b[:], buf: make([]byte, defaultBufSize),
nc: nc, nc: nc,
} }
} }
@ -105,43 +105,56 @@ func (b *buffer) readNext(need int) ([]byte, error) {
return b.buf[offset:b.idx], nil return b.buf[offset:b.idx], nil
} }
// returns a buffer with the requested size. // takeBuffer returns a buffer with the requested size.
// If possible, a slice from the existing buffer is returned. // If possible, a slice from the existing buffer is returned.
// Otherwise a bigger buffer is made. // Otherwise a bigger buffer is made.
// Only one buffer (total) can be used at a time. // Only one buffer (total) can be used at a time.
func (b *buffer) takeBuffer(length int) []byte { func (b *buffer) takeBuffer(length int) ([]byte, error) {
if b.length > 0 { if b.length > 0 {
return nil return nil, ErrBusyBuffer
} }
// test (cheap) general case first // test (cheap) general case first
if length <= defaultBufSize || length <= cap(b.buf) { if length <= cap(b.buf) {
return b.buf[:length] return b.buf[:length], nil
} }
if length < maxPacketSize { if length < maxPacketSize {
b.buf = make([]byte, length) b.buf = make([]byte, length)
return b.buf return b.buf, nil
} }
return make([]byte, length)
// buffer is larger than we want to store.
return make([]byte, length), nil
} }
// shortcut which can be used if the requested buffer is guaranteed to be // takeSmallBuffer is shortcut which can be used if length is
// smaller than defaultBufSize // known to be smaller than defaultBufSize.
// Only one buffer (total) can be used at a time. // Only one buffer (total) can be used at a time.
func (b *buffer) takeSmallBuffer(length int) []byte { func (b *buffer) takeSmallBuffer(length int) ([]byte, error) {
if b.length > 0 { if b.length > 0 {
return nil return nil, ErrBusyBuffer
} }
return b.buf[:length] return b.buf[:length], nil
} }
// takeCompleteBuffer returns the complete existing buffer. // takeCompleteBuffer returns the complete existing buffer.
// This can be used if the necessary buffer size is unknown. // This can be used if the necessary buffer size is unknown.
// cap and len of the returned buffer will be equal.
// Only one buffer (total) can be used at a time. // Only one buffer (total) can be used at a time.
func (b *buffer) takeCompleteBuffer() []byte { func (b *buffer) takeCompleteBuffer() ([]byte, error) {
if b.length > 0 { if b.length > 0 {
return nil return nil, ErrBusyBuffer
} }
return b.buf return b.buf, nil
}
// store stores buf, an updated buffer, if its suitable to do so.
func (b *buffer) store(buf []byte) error {
if b.length > 0 {
return ErrBusyBuffer
} else if cap(buf) <= maxPacketSize && cap(buf) > cap(b.buf) {
b.buf = buf[:cap(buf)]
}
return nil
} }

View File

@ -9,6 +9,8 @@
package mysql package mysql
import ( import (
"context"
"database/sql"
"database/sql/driver" "database/sql/driver"
"io" "io"
"net" "net"
@ -17,16 +19,6 @@ import (
"time" "time"
) )
// a copy of context.Context for Go 1.7 and earlier
type mysqlContext interface {
Done() <-chan struct{}
Err() error
// defined in context.Context, but not used in this driver:
// Deadline() (deadline time.Time, ok bool)
// Value(key interface{}) interface{}
}
type mysqlConn struct { type mysqlConn struct {
buf buffer buf buffer
netConn net.Conn netConn net.Conn
@ -43,7 +35,7 @@ type mysqlConn struct {
// for context support (Go 1.8+) // for context support (Go 1.8+)
watching bool watching bool
watcher chan<- mysqlContext watcher chan<- context.Context
closech chan struct{} closech chan struct{}
finished chan<- struct{} finished chan<- struct{}
canceled atomicError // set non-nil if conn is canceled canceled atomicError // set non-nil if conn is canceled
@ -190,10 +182,10 @@ func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (strin
return "", driver.ErrSkip return "", driver.ErrSkip
} }
buf := mc.buf.takeCompleteBuffer() buf, err := mc.buf.takeCompleteBuffer()
if buf == nil { if err != nil {
// can not take the buffer. Something must be wrong with the connection // can not take the buffer. Something must be wrong with the connection
errLog.Print(ErrBusyBuffer) errLog.Print(err)
return "", ErrInvalidConn return "", ErrInvalidConn
} }
buf = buf[:0] buf = buf[:0]
@ -459,3 +451,193 @@ func (mc *mysqlConn) finish() {
case <-mc.closech: case <-mc.closech:
} }
} }
// Ping implements driver.Pinger interface
func (mc *mysqlConn) Ping(ctx context.Context) (err error) {
if mc.closed.IsSet() {
errLog.Print(ErrInvalidConn)
return driver.ErrBadConn
}
if err = mc.watchCancel(ctx); err != nil {
return
}
defer mc.finish()
if err = mc.writeCommandPacket(comPing); err != nil {
return mc.markBadConn(err)
}
return mc.readResultOK()
}
// BeginTx implements driver.ConnBeginTx interface
func (mc *mysqlConn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) {
if err := mc.watchCancel(ctx); err != nil {
return nil, err
}
defer mc.finish()
if sql.IsolationLevel(opts.Isolation) != sql.LevelDefault {
level, err := mapIsolationLevel(opts.Isolation)
if err != nil {
return nil, err
}
err = mc.exec("SET TRANSACTION ISOLATION LEVEL " + level)
if err != nil {
return nil, err
}
}
return mc.begin(opts.ReadOnly)
}
func (mc *mysqlConn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) {
dargs, err := namedValueToValue(args)
if err != nil {
return nil, err
}
if err := mc.watchCancel(ctx); err != nil {
return nil, err
}
rows, err := mc.query(query, dargs)
if err != nil {
mc.finish()
return nil, err
}
rows.finish = mc.finish
return rows, err
}
func (mc *mysqlConn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) {
dargs, err := namedValueToValue(args)
if err != nil {
return nil, err
}
if err := mc.watchCancel(ctx); err != nil {
return nil, err
}
defer mc.finish()
return mc.Exec(query, dargs)
}
func (mc *mysqlConn) PrepareContext(ctx context.Context, query string) (driver.Stmt, error) {
if err := mc.watchCancel(ctx); err != nil {
return nil, err
}
stmt, err := mc.Prepare(query)
mc.finish()
if err != nil {
return nil, err
}
select {
default:
case <-ctx.Done():
stmt.Close()
return nil, ctx.Err()
}
return stmt, nil
}
func (stmt *mysqlStmt) QueryContext(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) {
dargs, err := namedValueToValue(args)
if err != nil {
return nil, err
}
if err := stmt.mc.watchCancel(ctx); err != nil {
return nil, err
}
rows, err := stmt.query(dargs)
if err != nil {
stmt.mc.finish()
return nil, err
}
rows.finish = stmt.mc.finish
return rows, err
}
func (stmt *mysqlStmt) ExecContext(ctx context.Context, args []driver.NamedValue) (driver.Result, error) {
dargs, err := namedValueToValue(args)
if err != nil {
return nil, err
}
if err := stmt.mc.watchCancel(ctx); err != nil {
return nil, err
}
defer stmt.mc.finish()
return stmt.Exec(dargs)
}
func (mc *mysqlConn) watchCancel(ctx context.Context) error {
if mc.watching {
// Reach here if canceled,
// so the connection is already invalid
mc.cleanup()
return nil
}
// When ctx is already cancelled, don't watch it.
if err := ctx.Err(); err != nil {
return err
}
// When ctx is not cancellable, don't watch it.
if ctx.Done() == nil {
return nil
}
// When watcher is not alive, can't watch it.
if mc.watcher == nil {
return nil
}
mc.watching = true
mc.watcher <- ctx
return nil
}
func (mc *mysqlConn) startWatcher() {
watcher := make(chan context.Context, 1)
mc.watcher = watcher
finished := make(chan struct{})
mc.finished = finished
go func() {
for {
var ctx context.Context
select {
case ctx = <-watcher:
case <-mc.closech:
return
}
select {
case <-ctx.Done():
mc.cancel(ctx.Err())
case <-finished:
case <-mc.closech:
return
}
}
}()
}
func (mc *mysqlConn) CheckNamedValue(nv *driver.NamedValue) (err error) {
nv.Value, err = converter{}.ConvertValue(nv.Value)
return
}
// ResetSession implements driver.SessionResetter.
// (From Go 1.10)
func (mc *mysqlConn) ResetSession(ctx context.Context) error {
if mc.closed.IsSet() {
return driver.ErrBadConn
}
return nil
}

View File

@ -1,207 +0,0 @@
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
//
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
// +build go1.8
package mysql
import (
"context"
"database/sql"
"database/sql/driver"
)
// Ping implements driver.Pinger interface
func (mc *mysqlConn) Ping(ctx context.Context) (err error) {
if mc.closed.IsSet() {
errLog.Print(ErrInvalidConn)
return driver.ErrBadConn
}
if err = mc.watchCancel(ctx); err != nil {
return
}
defer mc.finish()
if err = mc.writeCommandPacket(comPing); err != nil {
return
}
return mc.readResultOK()
}
// BeginTx implements driver.ConnBeginTx interface
func (mc *mysqlConn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) {
if err := mc.watchCancel(ctx); err != nil {
return nil, err
}
defer mc.finish()
if sql.IsolationLevel(opts.Isolation) != sql.LevelDefault {
level, err := mapIsolationLevel(opts.Isolation)
if err != nil {
return nil, err
}
err = mc.exec("SET TRANSACTION ISOLATION LEVEL " + level)
if err != nil {
return nil, err
}
}
return mc.begin(opts.ReadOnly)
}
func (mc *mysqlConn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) {
dargs, err := namedValueToValue(args)
if err != nil {
return nil, err
}
if err := mc.watchCancel(ctx); err != nil {
return nil, err
}
rows, err := mc.query(query, dargs)
if err != nil {
mc.finish()
return nil, err
}
rows.finish = mc.finish
return rows, err
}
func (mc *mysqlConn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) {
dargs, err := namedValueToValue(args)
if err != nil {
return nil, err
}
if err := mc.watchCancel(ctx); err != nil {
return nil, err
}
defer mc.finish()
return mc.Exec(query, dargs)
}
func (mc *mysqlConn) PrepareContext(ctx context.Context, query string) (driver.Stmt, error) {
if err := mc.watchCancel(ctx); err != nil {
return nil, err
}
stmt, err := mc.Prepare(query)
mc.finish()
if err != nil {
return nil, err
}
select {
default:
case <-ctx.Done():
stmt.Close()
return nil, ctx.Err()
}
return stmt, nil
}
func (stmt *mysqlStmt) QueryContext(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) {
dargs, err := namedValueToValue(args)
if err != nil {
return nil, err
}
if err := stmt.mc.watchCancel(ctx); err != nil {
return nil, err
}
rows, err := stmt.query(dargs)
if err != nil {
stmt.mc.finish()
return nil, err
}
rows.finish = stmt.mc.finish
return rows, err
}
func (stmt *mysqlStmt) ExecContext(ctx context.Context, args []driver.NamedValue) (driver.Result, error) {
dargs, err := namedValueToValue(args)
if err != nil {
return nil, err
}
if err := stmt.mc.watchCancel(ctx); err != nil {
return nil, err
}
defer stmt.mc.finish()
return stmt.Exec(dargs)
}
func (mc *mysqlConn) watchCancel(ctx context.Context) error {
if mc.watching {
// Reach here if canceled,
// so the connection is already invalid
mc.cleanup()
return nil
}
// When ctx is already cancelled, don't watch it.
if err := ctx.Err(); err != nil {
return err
}
// When ctx is not cancellable, don't watch it.
if ctx.Done() == nil {
return nil
}
// When watcher is not alive, can't watch it.
if mc.watcher == nil {
return nil
}
mc.watching = true
mc.watcher <- ctx
return nil
}
func (mc *mysqlConn) startWatcher() {
watcher := make(chan mysqlContext, 1)
mc.watcher = watcher
finished := make(chan struct{})
mc.finished = finished
go func() {
for {
var ctx mysqlContext
select {
case ctx = <-watcher:
case <-mc.closech:
return
}
select {
case <-ctx.Done():
mc.cancel(ctx.Err())
case <-finished:
case <-mc.closech:
return
}
}
}()
}
func (mc *mysqlConn) CheckNamedValue(nv *driver.NamedValue) (err error) {
nv.Value, err = converter{}.ConvertValue(nv.Value)
return
}
// ResetSession implements driver.SessionResetter.
// (From Go 1.10)
func (mc *mysqlConn) ResetSession(ctx context.Context) error {
if mc.closed.IsSet() {
return driver.ErrBadConn
}
return nil
}

View File

@ -23,11 +23,6 @@ import (
"sync" "sync"
) )
// watcher interface is used for context support (From Go 1.8)
type watcher interface {
startWatcher()
}
// MySQLDriver is exported to make the driver directly accessible. // MySQLDriver is exported to make the driver directly accessible.
// In general the driver is used via the database/sql package. // In general the driver is used via the database/sql package.
type MySQLDriver struct{} type MySQLDriver struct{}
@ -55,7 +50,7 @@ func RegisterDial(net string, dial DialFunc) {
// Open new Connection. // Open new Connection.
// See https://github.com/go-sql-driver/mysql#dsn-data-source-name for how // See https://github.com/go-sql-driver/mysql#dsn-data-source-name for how
// the DSN string is formated // the DSN string is formatted
func (d MySQLDriver) Open(dsn string) (driver.Conn, error) { func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
var err error var err error
@ -82,6 +77,10 @@ func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
mc.netConn, err = nd.Dial(mc.cfg.Net, mc.cfg.Addr) mc.netConn, err = nd.Dial(mc.cfg.Net, mc.cfg.Addr)
} }
if err != nil { if err != nil {
if nerr, ok := err.(net.Error); ok && nerr.Temporary() {
errLog.Print("net.Error from Dial()': ", nerr.Error())
return nil, driver.ErrBadConn
}
return nil, err return nil, err
} }
@ -96,9 +95,7 @@ func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
} }
// Call startWatcher for context support (From Go 1.8) // Call startWatcher for context support (From Go 1.8)
if s, ok := interface{}(mc).(watcher); ok { mc.startWatcher()
s.startWatcher()
}
mc.buf = newBuffer(mc.netConn) mc.buf = newBuffer(mc.netConn)

View File

@ -51,7 +51,7 @@ func (mc *mysqlConn) readPacket() ([]byte, error) {
mc.sequence++ mc.sequence++
// packets with length 0 terminate a previous packet which is a // packets with length 0 terminate a previous packet which is a
// multiple of (2^24)1 bytes long // multiple of (2^24)-1 bytes long
if pktLen == 0 { if pktLen == 0 {
// there was no previous packet // there was no previous packet
if prevData == nil { if prevData == nil {
@ -286,10 +286,10 @@ func (mc *mysqlConn) writeHandshakeResponsePacket(authResp []byte, plugin string
} }
// Calculate packet length and get buffer with that size // Calculate packet length and get buffer with that size
data := mc.buf.takeSmallBuffer(pktLen + 4) data, err := mc.buf.takeSmallBuffer(pktLen + 4)
if data == nil { if err != nil {
// cannot take the buffer. Something must be wrong with the connection // cannot take the buffer. Something must be wrong with the connection
errLog.Print(ErrBusyBuffer) errLog.Print(err)
return errBadConnNoWrite return errBadConnNoWrite
} }
@ -367,10 +367,10 @@ func (mc *mysqlConn) writeHandshakeResponsePacket(authResp []byte, plugin string
// http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::AuthSwitchResponse // http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::AuthSwitchResponse
func (mc *mysqlConn) writeAuthSwitchPacket(authData []byte) error { func (mc *mysqlConn) writeAuthSwitchPacket(authData []byte) error {
pktLen := 4 + len(authData) pktLen := 4 + len(authData)
data := mc.buf.takeSmallBuffer(pktLen) data, err := mc.buf.takeSmallBuffer(pktLen)
if data == nil { if err != nil {
// cannot take the buffer. Something must be wrong with the connection // cannot take the buffer. Something must be wrong with the connection
errLog.Print(ErrBusyBuffer) errLog.Print(err)
return errBadConnNoWrite return errBadConnNoWrite
} }
@ -387,10 +387,10 @@ func (mc *mysqlConn) writeCommandPacket(command byte) error {
// Reset Packet Sequence // Reset Packet Sequence
mc.sequence = 0 mc.sequence = 0
data := mc.buf.takeSmallBuffer(4 + 1) data, err := mc.buf.takeSmallBuffer(4 + 1)
if data == nil { if err != nil {
// cannot take the buffer. Something must be wrong with the connection // cannot take the buffer. Something must be wrong with the connection
errLog.Print(ErrBusyBuffer) errLog.Print(err)
return errBadConnNoWrite return errBadConnNoWrite
} }
@ -406,10 +406,10 @@ func (mc *mysqlConn) writeCommandPacketStr(command byte, arg string) error {
mc.sequence = 0 mc.sequence = 0
pktLen := 1 + len(arg) pktLen := 1 + len(arg)
data := mc.buf.takeBuffer(pktLen + 4) data, err := mc.buf.takeBuffer(pktLen + 4)
if data == nil { if err != nil {
// cannot take the buffer. Something must be wrong with the connection // cannot take the buffer. Something must be wrong with the connection
errLog.Print(ErrBusyBuffer) errLog.Print(err)
return errBadConnNoWrite return errBadConnNoWrite
} }
@ -427,10 +427,10 @@ func (mc *mysqlConn) writeCommandPacketUint32(command byte, arg uint32) error {
// Reset Packet Sequence // Reset Packet Sequence
mc.sequence = 0 mc.sequence = 0
data := mc.buf.takeSmallBuffer(4 + 1 + 4) data, err := mc.buf.takeSmallBuffer(4 + 1 + 4)
if data == nil { if err != nil {
// cannot take the buffer. Something must be wrong with the connection // cannot take the buffer. Something must be wrong with the connection
errLog.Print(ErrBusyBuffer) errLog.Print(err)
return errBadConnNoWrite return errBadConnNoWrite
} }
@ -883,7 +883,7 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
const minPktLen = 4 + 1 + 4 + 1 + 4 const minPktLen = 4 + 1 + 4 + 1 + 4
mc := stmt.mc mc := stmt.mc
// Determine threshould dynamically to avoid packet size shortage. // Determine threshold dynamically to avoid packet size shortage.
longDataSize := mc.maxAllowedPacket / (stmt.paramCount + 1) longDataSize := mc.maxAllowedPacket / (stmt.paramCount + 1)
if longDataSize < 64 { if longDataSize < 64 {
longDataSize = 64 longDataSize = 64
@ -893,15 +893,17 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
mc.sequence = 0 mc.sequence = 0
var data []byte var data []byte
var err error
if len(args) == 0 { if len(args) == 0 {
data = mc.buf.takeBuffer(minPktLen) data, err = mc.buf.takeBuffer(minPktLen)
} else { } else {
data = mc.buf.takeCompleteBuffer() data, err = mc.buf.takeCompleteBuffer()
// In this case the len(data) == cap(data) which is used to optimise the flow below.
} }
if data == nil { if err != nil {
// cannot take the buffer. Something must be wrong with the connection // cannot take the buffer. Something must be wrong with the connection
errLog.Print(ErrBusyBuffer) errLog.Print(err)
return errBadConnNoWrite return errBadConnNoWrite
} }
@ -927,7 +929,7 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
pos := minPktLen pos := minPktLen
var nullMask []byte var nullMask []byte
if maskLen, typesLen := (len(args)+7)/8, 1+2*len(args); pos+maskLen+typesLen >= len(data) { if maskLen, typesLen := (len(args)+7)/8, 1+2*len(args); pos+maskLen+typesLen >= cap(data) {
// buffer has to be extended but we don't know by how much so // buffer has to be extended but we don't know by how much so
// we depend on append after all data with known sizes fit. // we depend on append after all data with known sizes fit.
// We stop at that because we deal with a lot of columns here // We stop at that because we deal with a lot of columns here
@ -936,10 +938,11 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
copy(tmp[:pos], data[:pos]) copy(tmp[:pos], data[:pos])
data = tmp data = tmp
nullMask = data[pos : pos+maskLen] nullMask = data[pos : pos+maskLen]
// No need to clean nullMask as make ensures that.
pos += maskLen pos += maskLen
} else { } else {
nullMask = data[pos : pos+maskLen] nullMask = data[pos : pos+maskLen]
for i := 0; i < maskLen; i++ { for i := range nullMask {
nullMask[i] = 0 nullMask[i] = 0
} }
pos += maskLen pos += maskLen
@ -1076,7 +1079,10 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
// In that case we must build the data packet with the new values buffer // In that case we must build the data packet with the new values buffer
if valuesCap != cap(paramValues) { if valuesCap != cap(paramValues) {
data = append(data[:pos], paramValues...) data = append(data[:pos], paramValues...)
mc.buf.buf = data if err = mc.buf.store(data); err != nil {
errLog.Print(err)
return errBadConnNoWrite
}
} }
pos += len(paramValues) pos += len(paramValues)

View File

@ -10,8 +10,10 @@ package mysql
import ( import (
"crypto/tls" "crypto/tls"
"database/sql"
"database/sql/driver" "database/sql/driver"
"encoding/binary" "encoding/binary"
"errors"
"fmt" "fmt"
"io" "io"
"strconv" "strconv"
@ -80,7 +82,7 @@ func DeregisterTLSConfig(key string) {
func getTLSConfigClone(key string) (config *tls.Config) { func getTLSConfigClone(key string) (config *tls.Config) {
tlsConfigLock.RLock() tlsConfigLock.RLock()
if v, ok := tlsConfigRegistry[key]; ok { if v, ok := tlsConfigRegistry[key]; ok {
config = cloneTLSConfig(v) config = v.Clone()
} }
tlsConfigLock.RUnlock() tlsConfigLock.RUnlock()
return return
@ -724,3 +726,30 @@ func (ae *atomicError) Value() error {
} }
return nil return nil
} }
func namedValueToValue(named []driver.NamedValue) ([]driver.Value, error) {
dargs := make([]driver.Value, len(named))
for n, param := range named {
if len(param.Name) > 0 {
// TODO: support the use of Named Parameters #561
return nil, errors.New("mysql: driver does not support the use of Named Parameters")
}
dargs[n] = param.Value
}
return dargs, nil
}
func mapIsolationLevel(level driver.IsolationLevel) (string, error) {
switch sql.IsolationLevel(level) {
case sql.LevelRepeatableRead:
return "REPEATABLE READ", nil
case sql.LevelReadCommitted:
return "READ COMMITTED", nil
case sql.LevelReadUncommitted:
return "READ UNCOMMITTED", nil
case sql.LevelSerializable:
return "SERIALIZABLE", nil
default:
return "", fmt.Errorf("mysql: unsupported isolation level: %v", level)
}
}

View File

@ -1,40 +0,0 @@
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
//
// Copyright 2017 The Go-MySQL-Driver Authors. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
// +build go1.7
// +build !go1.8
package mysql
import "crypto/tls"
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,
}
}

View File

@ -1,50 +0,0 @@
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
//
// Copyright 2017 The Go-MySQL-Driver Authors. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
// +build go1.8
package mysql
import (
"crypto/tls"
"database/sql"
"database/sql/driver"
"errors"
"fmt"
)
func cloneTLSConfig(c *tls.Config) *tls.Config {
return c.Clone()
}
func namedValueToValue(named []driver.NamedValue) ([]driver.Value, error) {
dargs := make([]driver.Value, len(named))
for n, param := range named {
if len(param.Name) > 0 {
// TODO: support the use of Named Parameters #561
return nil, errors.New("mysql: driver does not support the use of Named Parameters")
}
dargs[n] = param.Value
}
return dargs, nil
}
func mapIsolationLevel(level driver.IsolationLevel) (string, error) {
switch sql.IsolationLevel(level) {
case sql.LevelRepeatableRead:
return "REPEATABLE READ", nil
case sql.LevelReadCommitted:
return "READ COMMITTED", nil
case sql.LevelReadUncommitted:
return "READ UNCOMMITTED", nil
case sql.LevelSerializable:
return "SERIALIZABLE", nil
default:
return "", fmt.Errorf("mysql: unsupported isolation level: %v", level)
}
}

View File

@ -186,7 +186,6 @@ func (p *Buffer) DecodeVarint() (x uint64, err error) {
if b&0x80 == 0 { if b&0x80 == 0 {
goto done goto done
} }
// x -= 0x80 << 63 // Always zero.
return 0, errOverflow return 0, errOverflow

View File

@ -246,7 +246,8 @@ func equalExtMap(base reflect.Type, em1, em2 map[int32]Extension) bool {
return false return false
} }
m1, m2 := e1.value, e2.value m1 := extensionAsLegacyType(e1.value)
m2 := extensionAsLegacyType(e2.value)
if m1 == nil && m2 == nil { if m1 == nil && m2 == nil {
// Both have only encoded form. // Both have only encoded form.

View File

@ -186,7 +186,23 @@ type Extension struct {
// accessed using GetExtension (or GetExtensions) desc and value // accessed using GetExtension (or GetExtensions) desc and value
// will be set. // will be set.
desc *ExtensionDesc desc *ExtensionDesc
// value is a concrete value for the extension field. Let the type of
// desc.ExtensionType be the "API type" and the type of Extension.value
// be the "storage type". The API type and storage type are the same except:
// * For scalars (except []byte), the API type uses *T,
// while the storage type uses T.
// * For repeated fields, the API type uses []T, while the storage type
// uses *[]T.
//
// The reason for the divergence is so that the storage type more naturally
// matches what is expected of when retrieving the values through the
// protobuf reflection APIs.
//
// The value may only be populated if desc is also populated.
value interface{} value interface{}
// enc is the raw bytes for the extension field.
enc []byte enc []byte
} }
@ -334,7 +350,7 @@ func GetExtension(pb Message, extension *ExtensionDesc) (interface{}, error) {
// descriptors with the same field number. // descriptors with the same field number.
return nil, errors.New("proto: descriptor conflict") return nil, errors.New("proto: descriptor conflict")
} }
return e.value, nil return extensionAsLegacyType(e.value), nil
} }
if extension.ExtensionType == nil { if extension.ExtensionType == nil {
@ -349,11 +365,11 @@ func GetExtension(pb Message, extension *ExtensionDesc) (interface{}, error) {
// Remember the decoded version and drop the encoded version. // Remember the decoded version and drop the encoded version.
// That way it is safe to mutate what we return. // That way it is safe to mutate what we return.
e.value = v e.value = extensionAsStorageType(v)
e.desc = extension e.desc = extension
e.enc = nil e.enc = nil
emap[extension.Field] = e emap[extension.Field] = e
return e.value, nil return extensionAsLegacyType(e.value), nil
} }
// defaultExtensionValue returns the default value for extension. // defaultExtensionValue returns the default value for extension.
@ -488,7 +504,7 @@ func SetExtension(pb Message, extension *ExtensionDesc, value interface{}) error
} }
typ := reflect.TypeOf(extension.ExtensionType) typ := reflect.TypeOf(extension.ExtensionType)
if typ != reflect.TypeOf(value) { if typ != reflect.TypeOf(value) {
return errors.New("proto: bad extension value type") return fmt.Errorf("proto: bad extension value type. got: %T, want: %T", value, extension.ExtensionType)
} }
// nil extension values need to be caught early, because the // nil extension values need to be caught early, because the
// encoder can't distinguish an ErrNil due to a nil extension // encoder can't distinguish an ErrNil due to a nil extension
@ -500,7 +516,7 @@ func SetExtension(pb Message, extension *ExtensionDesc, value interface{}) error
} }
extmap := epb.extensionsWrite() extmap := epb.extensionsWrite()
extmap[extension.Field] = Extension{desc: extension, value: value} extmap[extension.Field] = Extension{desc: extension, value: extensionAsStorageType(value)}
return nil return nil
} }
@ -541,3 +557,51 @@ func RegisterExtension(desc *ExtensionDesc) {
func RegisteredExtensions(pb Message) map[int32]*ExtensionDesc { func RegisteredExtensions(pb Message) map[int32]*ExtensionDesc {
return extensionMaps[reflect.TypeOf(pb).Elem()] return extensionMaps[reflect.TypeOf(pb).Elem()]
} }
// extensionAsLegacyType converts an value in the storage type as the API type.
// See Extension.value.
func extensionAsLegacyType(v interface{}) interface{} {
switch rv := reflect.ValueOf(v); rv.Kind() {
case reflect.Bool, reflect.Int32, reflect.Int64, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64, reflect.String:
// Represent primitive types as a pointer to the value.
rv2 := reflect.New(rv.Type())
rv2.Elem().Set(rv)
v = rv2.Interface()
case reflect.Ptr:
// Represent slice types as the value itself.
switch rv.Type().Elem().Kind() {
case reflect.Slice:
if rv.IsNil() {
v = reflect.Zero(rv.Type().Elem()).Interface()
} else {
v = rv.Elem().Interface()
}
}
}
return v
}
// extensionAsStorageType converts an value in the API type as the storage type.
// See Extension.value.
func extensionAsStorageType(v interface{}) interface{} {
switch rv := reflect.ValueOf(v); rv.Kind() {
case reflect.Ptr:
// Represent slice types as the value itself.
switch rv.Type().Elem().Kind() {
case reflect.Bool, reflect.Int32, reflect.Int64, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64, reflect.String:
if rv.IsNil() {
v = reflect.Zero(rv.Type().Elem()).Interface()
} else {
v = rv.Elem().Interface()
}
}
case reflect.Slice:
// Represent slice types as a pointer to the value.
if rv.Type().Elem().Kind() != reflect.Uint8 {
rv2 := reflect.New(rv.Type())
rv2.Elem().Set(rv)
v = rv2.Interface()
}
}
return v
}

View File

@ -341,26 +341,6 @@ type Message interface {
ProtoMessage() 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 // A Buffer is a buffer manager for marshaling and unmarshaling
// protocol buffers. It may be reused between invocations to // protocol buffers. It may be reused between invocations to
// reduce memory usage. It is not necessary to use a Buffer; // reduce memory usage. It is not necessary to use a Buffer;
@ -960,13 +940,19 @@ func isProto3Zero(v reflect.Value) bool {
return false return false
} }
// ProtoPackageIsVersion2 is referenced from generated protocol buffer files const (
// to assert that that code is compatible with this version of the proto package. // ProtoPackageIsVersion3 is referenced from generated protocol buffer files
const ProtoPackageIsVersion2 = true // to assert that that code is compatible with this version of the proto package.
ProtoPackageIsVersion3 = true
// ProtoPackageIsVersion1 is referenced from generated protocol buffer files // ProtoPackageIsVersion2 is referenced from generated protocol buffer files
// to assert that that code is compatible with this version of the proto package. // to assert that that code is compatible with this version of the proto package.
const ProtoPackageIsVersion1 = true ProtoPackageIsVersion2 = true
// ProtoPackageIsVersion1 is referenced from generated protocol buffer files
// to assert that that code is compatible with this version of the proto package.
ProtoPackageIsVersion1 = true
)
// InternalMessageInfo is a type used internally by generated .pb.go files. // InternalMessageInfo is a type used internally by generated .pb.go files.
// This type is not intended to be used by non-generated code. // This type is not intended to be used by non-generated code.

View File

@ -36,13 +36,7 @@ package proto
*/ */
import ( import (
"bytes"
"encoding/json"
"errors" "errors"
"fmt"
"reflect"
"sort"
"sync"
) )
// errNoMessageTypeID occurs when a protocol buffer does not have a message type ID. // errNoMessageTypeID occurs when a protocol buffer does not have a message type ID.
@ -145,46 +139,9 @@ func skipVarint(buf []byte) []byte {
return buf[i+1:] return buf[i+1:]
} }
// MarshalMessageSet encodes the extension map represented by m in the message set wire format. // unmarshalMessageSet decodes the extension map encoded in buf 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) {
return marshalMessageSet(exts, false)
}
// marshaMessageSet implements above function, with the opt to turn on / off deterministic during Marshal.
func marshalMessageSet(exts interface{}, deterministic bool) ([]byte, error) {
switch exts := exts.(type) {
case *XXX_InternalExtensions:
var u marshalInfo
siz := u.sizeMessageSet(exts)
b := make([]byte, 0, siz)
return u.appendMessageSet(b, exts, deterministic)
case map[int32]Extension:
// This is an old-style extension map.
// Wrap it in a new-style XXX_InternalExtensions.
ie := XXX_InternalExtensions{
p: &struct {
mu sync.Mutex
extensionMap map[int32]Extension
}{
extensionMap: exts,
},
}
var u marshalInfo
siz := u.sizeMessageSet(&ie)
b := make([]byte, 0, siz)
return u.appendMessageSet(b, &ie, deterministic)
default:
return nil, errors.New("proto: not an extension map")
}
}
// UnmarshalMessageSet decodes the extension map encoded in buf in the message set wire format.
// It is called by Unmarshal methods on protocol buffer messages with the message_set_wire_format option. // It is called by Unmarshal methods on protocol buffer messages with the message_set_wire_format option.
func UnmarshalMessageSet(buf []byte, exts interface{}) error { func unmarshalMessageSet(buf []byte, exts interface{}) error {
var m map[int32]Extension var m map[int32]Extension
switch exts := exts.(type) { switch exts := exts.(type) {
case *XXX_InternalExtensions: case *XXX_InternalExtensions:
@ -222,93 +179,3 @@ func UnmarshalMessageSet(buf []byte, exts interface{}) error {
} }
return nil 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:
var mu sync.Locker
m, mu = exts.extensionsRead()
if m != nil {
// Keep the extensions map locked until we're done marshaling to prevent
// races between marshaling and unmarshaling the lazily-{en,de}coded
// values.
mu.Lock()
defer mu.Unlock()
}
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]
msd, ok := messageSetMap[id]
if !ok {
// Unknown type; we can't render it, so skip it.
continue
}
if i > 0 && b.Len() > 1 {
b.WriteByte(',')
}
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,
}
}

View File

@ -79,10 +79,13 @@ func toPointer(i *Message) pointer {
// toAddrPointer converts an interface to a pointer that points to // toAddrPointer converts an interface to a pointer that points to
// the interface data. // the interface data.
func toAddrPointer(i *interface{}, isptr bool) pointer { func toAddrPointer(i *interface{}, isptr, deref bool) pointer {
v := reflect.ValueOf(*i) v := reflect.ValueOf(*i)
u := reflect.New(v.Type()) u := reflect.New(v.Type())
u.Elem().Set(v) u.Elem().Set(v)
if deref {
u = u.Elem()
}
return pointer{v: u} return pointer{v: u}
} }

View File

@ -85,16 +85,21 @@ func toPointer(i *Message) pointer {
// toAddrPointer converts an interface to a pointer that points to // toAddrPointer converts an interface to a pointer that points to
// the interface data. // the interface data.
func toAddrPointer(i *interface{}, isptr bool) pointer { func toAddrPointer(i *interface{}, isptr, deref bool) (p pointer) {
// Super-tricky - read or get the address of data word of interface value. // Super-tricky - read or get the address of data word of interface value.
if isptr { if isptr {
// The interface is of pointer type, thus it is a direct interface. // The interface is of pointer type, thus it is a direct interface.
// The data word is the pointer data itself. We take its address. // The data word is the pointer data itself. We take its address.
return pointer{p: unsafe.Pointer(uintptr(unsafe.Pointer(i)) + ptrSize)} p = pointer{p: unsafe.Pointer(uintptr(unsafe.Pointer(i)) + ptrSize)}
} } else {
// The interface is not of pointer type. The data word is the pointer // The interface is not of pointer type. The data word is the pointer
// to the data. // to the data.
return pointer{p: (*[2]unsafe.Pointer)(unsafe.Pointer(i))[1]} p = pointer{p: (*[2]unsafe.Pointer)(unsafe.Pointer(i))[1]}
}
if deref {
p.p = *(*unsafe.Pointer)(p.p)
}
return p
} }
// valToPointer converts v to a pointer. v must be of pointer type. // valToPointer converts v to a pointer. v must be of pointer type.

View File

@ -334,9 +334,6 @@ func GetProperties(t reflect.Type) *StructProperties {
sprop, ok := propertiesMap[t] sprop, ok := propertiesMap[t]
propertiesMu.RUnlock() propertiesMu.RUnlock()
if ok { if ok {
if collectStats {
stats.Chit++
}
return sprop return sprop
} }
@ -346,17 +343,20 @@ func GetProperties(t reflect.Type) *StructProperties {
return sprop return sprop
} }
type (
oneofFuncsIface interface {
XXX_OneofFuncs() (func(Message, *Buffer) error, func(Message, int, int, *Buffer) (bool, error), func(Message) int, []interface{})
}
oneofWrappersIface interface {
XXX_OneofWrappers() []interface{}
}
)
// getPropertiesLocked requires that propertiesMu is held. // getPropertiesLocked requires that propertiesMu is held.
func getPropertiesLocked(t reflect.Type) *StructProperties { func getPropertiesLocked(t reflect.Type) *StructProperties {
if prop, ok := propertiesMap[t]; ok { if prop, ok := propertiesMap[t]; ok {
if collectStats {
stats.Chit++
}
return prop return prop
} }
if collectStats {
stats.Cmiss++
}
prop := new(StructProperties) prop := new(StructProperties)
// in case of recursive protos, fill this in now. // in case of recursive protos, fill this in now.
@ -391,13 +391,14 @@ func getPropertiesLocked(t reflect.Type) *StructProperties {
// Re-order prop.order. // Re-order prop.order.
sort.Sort(prop) 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{} var oots []interface{}
_, _, _, oots = om.XXX_OneofFuncs() switch m := reflect.Zero(reflect.PtrTo(t)).Interface().(type) {
case oneofFuncsIface:
_, _, _, oots = m.XXX_OneofFuncs()
case oneofWrappersIface:
oots = m.XXX_OneofWrappers()
}
if len(oots) > 0 {
// Interpret oneof metadata. // Interpret oneof metadata.
prop.OneofTypes = make(map[string]*OneofProperties) prop.OneofTypes = make(map[string]*OneofProperties)
for _, oot := range oots { for _, oot := range oots {

View File

@ -87,6 +87,7 @@ type marshalElemInfo struct {
sizer sizer sizer sizer
marshaler marshaler marshaler marshaler
isptr bool // elem is pointer typed, thus interface of this type is a direct interface (extension only) isptr bool // elem is pointer typed, thus interface of this type is a direct interface (extension only)
deref bool // dereference the pointer before operating on it; implies isptr
} }
var ( var (
@ -320,8 +321,11 @@ func (u *marshalInfo) computeMarshalInfo() {
// get oneof implementers // get oneof implementers
var oneofImplementers []interface{} var oneofImplementers []interface{}
if m, ok := reflect.Zero(reflect.PtrTo(t)).Interface().(oneofMessage); ok { switch m := reflect.Zero(reflect.PtrTo(t)).Interface().(type) {
case oneofFuncsIface:
_, _, _, oneofImplementers = m.XXX_OneofFuncs() _, _, _, oneofImplementers = m.XXX_OneofFuncs()
case oneofWrappersIface:
oneofImplementers = m.XXX_OneofWrappers()
} }
n := t.NumField() n := t.NumField()
@ -407,13 +411,22 @@ func (u *marshalInfo) getExtElemInfo(desc *ExtensionDesc) *marshalElemInfo {
panic("tag is not an integer") panic("tag is not an integer")
} }
wt := wiretype(tags[0]) wt := wiretype(tags[0])
if t.Kind() == reflect.Ptr && t.Elem().Kind() != reflect.Struct {
t = t.Elem()
}
sizer, marshaler := typeMarshaler(t, tags, false, false) sizer, marshaler := typeMarshaler(t, tags, false, false)
var deref bool
if t.Kind() == reflect.Slice && t.Elem().Kind() != reflect.Uint8 {
t = reflect.PtrTo(t)
deref = true
}
e = &marshalElemInfo{ e = &marshalElemInfo{
wiretag: uint64(tag)<<3 | wt, wiretag: uint64(tag)<<3 | wt,
tagsize: SizeVarint(uint64(tag) << 3), tagsize: SizeVarint(uint64(tag) << 3),
sizer: sizer, sizer: sizer,
marshaler: marshaler, marshaler: marshaler,
isptr: t.Kind() == reflect.Ptr, isptr: t.Kind() == reflect.Ptr,
deref: deref,
} }
// update cache // update cache
@ -448,7 +461,7 @@ func (fi *marshalFieldInfo) computeMarshalFieldInfo(f *reflect.StructField) {
func (fi *marshalFieldInfo) computeOneofFieldInfo(f *reflect.StructField, oneofImplementers []interface{}) { func (fi *marshalFieldInfo) computeOneofFieldInfo(f *reflect.StructField, oneofImplementers []interface{}) {
fi.field = toField(f) fi.field = toField(f)
fi.wiretag = 1<<31 - 1 // Use a large tag number, make oneofs sorted at the end. This tag will not appear on the wire. fi.wiretag = math.MaxInt32 // Use a large tag number, make oneofs sorted at the end. This tag will not appear on the wire.
fi.isPointer = true fi.isPointer = true
fi.sizer, fi.marshaler = makeOneOfMarshaler(fi, f) fi.sizer, fi.marshaler = makeOneOfMarshaler(fi, f)
fi.oneofElems = make(map[reflect.Type]*marshalElemInfo) fi.oneofElems = make(map[reflect.Type]*marshalElemInfo)
@ -476,10 +489,6 @@ func (fi *marshalFieldInfo) computeOneofFieldInfo(f *reflect.StructField, oneofI
} }
} }
type oneofMessage interface {
XXX_OneofFuncs() (func(Message, *Buffer) error, func(Message, int, int, *Buffer) (bool, error), func(Message) int, []interface{})
}
// wiretype returns the wire encoding of the type. // wiretype returns the wire encoding of the type.
func wiretype(encoding string) uint64 { func wiretype(encoding string) uint64 {
switch encoding { switch encoding {
@ -2310,8 +2319,8 @@ func makeMapMarshaler(f *reflect.StructField) (sizer, marshaler) {
for _, k := range m.MapKeys() { for _, k := range m.MapKeys() {
ki := k.Interface() ki := k.Interface()
vi := m.MapIndex(k).Interface() vi := m.MapIndex(k).Interface()
kaddr := toAddrPointer(&ki, false) // pointer to key kaddr := toAddrPointer(&ki, false, false) // pointer to key
vaddr := toAddrPointer(&vi, valIsPtr) // pointer to value vaddr := toAddrPointer(&vi, valIsPtr, false) // pointer to value
siz := keySizer(kaddr, 1) + valSizer(vaddr, 1) // tag of key = 1 (size=1), tag of val = 2 (size=1) siz := keySizer(kaddr, 1) + valSizer(vaddr, 1) // tag of key = 1 (size=1), tag of val = 2 (size=1)
n += siz + SizeVarint(uint64(siz)) + tagsize n += siz + SizeVarint(uint64(siz)) + tagsize
} }
@ -2329,8 +2338,8 @@ func makeMapMarshaler(f *reflect.StructField) (sizer, marshaler) {
for _, k := range keys { for _, k := range keys {
ki := k.Interface() ki := k.Interface()
vi := m.MapIndex(k).Interface() vi := m.MapIndex(k).Interface()
kaddr := toAddrPointer(&ki, false) // pointer to key kaddr := toAddrPointer(&ki, false, false) // pointer to key
vaddr := toAddrPointer(&vi, valIsPtr) // pointer to value vaddr := toAddrPointer(&vi, valIsPtr, false) // pointer to value
b = appendVarint(b, tag) b = appendVarint(b, tag)
siz := keySizer(kaddr, 1) + valCachedSizer(vaddr, 1) // tag of key = 1 (size=1), tag of val = 2 (size=1) siz := keySizer(kaddr, 1) + valCachedSizer(vaddr, 1) // tag of key = 1 (size=1), tag of val = 2 (size=1)
b = appendVarint(b, uint64(siz)) b = appendVarint(b, uint64(siz))
@ -2399,7 +2408,7 @@ func (u *marshalInfo) sizeExtensions(ext *XXX_InternalExtensions) int {
// the last time this function was called. // the last time this function was called.
ei := u.getExtElemInfo(e.desc) ei := u.getExtElemInfo(e.desc)
v := e.value v := e.value
p := toAddrPointer(&v, ei.isptr) p := toAddrPointer(&v, ei.isptr, ei.deref)
n += ei.sizer(p, ei.tagsize) n += ei.sizer(p, ei.tagsize)
} }
mu.Unlock() mu.Unlock()
@ -2434,7 +2443,7 @@ func (u *marshalInfo) appendExtensions(b []byte, ext *XXX_InternalExtensions, de
ei := u.getExtElemInfo(e.desc) ei := u.getExtElemInfo(e.desc)
v := e.value v := e.value
p := toAddrPointer(&v, ei.isptr) p := toAddrPointer(&v, ei.isptr, ei.deref)
b, err = ei.marshaler(b, p, ei.wiretag, deterministic) b, err = ei.marshaler(b, p, ei.wiretag, deterministic)
if !nerr.Merge(err) { if !nerr.Merge(err) {
return b, err return b, err
@ -2465,7 +2474,7 @@ func (u *marshalInfo) appendExtensions(b []byte, ext *XXX_InternalExtensions, de
ei := u.getExtElemInfo(e.desc) ei := u.getExtElemInfo(e.desc)
v := e.value v := e.value
p := toAddrPointer(&v, ei.isptr) p := toAddrPointer(&v, ei.isptr, ei.deref)
b, err = ei.marshaler(b, p, ei.wiretag, deterministic) b, err = ei.marshaler(b, p, ei.wiretag, deterministic)
if !nerr.Merge(err) { if !nerr.Merge(err) {
return b, err return b, err
@ -2510,7 +2519,7 @@ func (u *marshalInfo) sizeMessageSet(ext *XXX_InternalExtensions) int {
ei := u.getExtElemInfo(e.desc) ei := u.getExtElemInfo(e.desc)
v := e.value v := e.value
p := toAddrPointer(&v, ei.isptr) p := toAddrPointer(&v, ei.isptr, ei.deref)
n += ei.sizer(p, 1) // message, tag = 3 (size=1) n += ei.sizer(p, 1) // message, tag = 3 (size=1)
} }
mu.Unlock() mu.Unlock()
@ -2553,7 +2562,7 @@ func (u *marshalInfo) appendMessageSet(b []byte, ext *XXX_InternalExtensions, de
ei := u.getExtElemInfo(e.desc) ei := u.getExtElemInfo(e.desc)
v := e.value v := e.value
p := toAddrPointer(&v, ei.isptr) p := toAddrPointer(&v, ei.isptr, ei.deref)
b, err = ei.marshaler(b, p, 3<<3|WireBytes, deterministic) b, err = ei.marshaler(b, p, 3<<3|WireBytes, deterministic)
if !nerr.Merge(err) { if !nerr.Merge(err) {
return b, err return b, err
@ -2591,7 +2600,7 @@ func (u *marshalInfo) appendMessageSet(b []byte, ext *XXX_InternalExtensions, de
ei := u.getExtElemInfo(e.desc) ei := u.getExtElemInfo(e.desc)
v := e.value v := e.value
p := toAddrPointer(&v, ei.isptr) p := toAddrPointer(&v, ei.isptr, ei.deref)
b, err = ei.marshaler(b, p, 3<<3|WireBytes, deterministic) b, err = ei.marshaler(b, p, 3<<3|WireBytes, deterministic)
b = append(b, 1<<3|WireEndGroup) b = append(b, 1<<3|WireEndGroup)
if !nerr.Merge(err) { if !nerr.Merge(err) {
@ -2621,7 +2630,7 @@ func (u *marshalInfo) sizeV1Extensions(m map[int32]Extension) int {
ei := u.getExtElemInfo(e.desc) ei := u.getExtElemInfo(e.desc)
v := e.value v := e.value
p := toAddrPointer(&v, ei.isptr) p := toAddrPointer(&v, ei.isptr, ei.deref)
n += ei.sizer(p, ei.tagsize) n += ei.sizer(p, ei.tagsize)
} }
return n return n
@ -2656,7 +2665,7 @@ func (u *marshalInfo) appendV1Extensions(b []byte, m map[int32]Extension, determ
ei := u.getExtElemInfo(e.desc) ei := u.getExtElemInfo(e.desc)
v := e.value v := e.value
p := toAddrPointer(&v, ei.isptr) p := toAddrPointer(&v, ei.isptr, ei.deref)
b, err = ei.marshaler(b, p, ei.wiretag, deterministic) b, err = ei.marshaler(b, p, ei.wiretag, deterministic)
if !nerr.Merge(err) { if !nerr.Merge(err) {
return b, err return b, err

View File

@ -136,7 +136,7 @@ func (u *unmarshalInfo) unmarshal(m pointer, b []byte) error {
u.computeUnmarshalInfo() u.computeUnmarshalInfo()
} }
if u.isMessageSet { if u.isMessageSet {
return UnmarshalMessageSet(b, m.offset(u.extensions).toExtensions()) return unmarshalMessageSet(b, m.offset(u.extensions).toExtensions())
} }
var reqMask uint64 // bitmask of required fields we've seen. var reqMask uint64 // bitmask of required fields we've seen.
var errLater error var errLater error
@ -362,13 +362,15 @@ func (u *unmarshalInfo) computeUnmarshalInfo() {
} }
// Find any types associated with oneof fields. // Find any types associated with oneof fields.
// TODO: XXX_OneofFuncs returns more info than we need. Get rid of some of it? var oneofImplementers []interface{}
fn := reflect.Zero(reflect.PtrTo(t)).MethodByName("XXX_OneofFuncs") switch m := reflect.Zero(reflect.PtrTo(t)).Interface().(type) {
if fn.IsValid() { case oneofFuncsIface:
res := fn.Call(nil)[3] // last return value from XXX_OneofFuncs: []interface{} _, _, _, oneofImplementers = m.XXX_OneofFuncs()
for i := res.Len() - 1; i >= 0; i-- { case oneofWrappersIface:
v := res.Index(i) // interface{} oneofImplementers = m.XXX_OneofWrappers()
tptr := reflect.ValueOf(v.Interface()).Type() // *Msg_X }
for _, v := range oneofImplementers {
tptr := reflect.TypeOf(v) // *Msg_X
typ := tptr.Elem() // Msg_X typ := tptr.Elem() // Msg_X
f := typ.Field(0) // oneof implementers have one field f := typ.Field(0) // oneof implementers have one field
@ -397,11 +399,11 @@ func (u *unmarshalInfo) computeUnmarshalInfo() {
u.setTag(fieldNum, of.field, unmarshal, 0, name) u.setTag(fieldNum, of.field, unmarshal, 0, name)
} }
} }
}
} }
// Get extension ranges, if any. // Get extension ranges, if any.
fn = reflect.Zero(reflect.PtrTo(t)).MethodByName("ExtensionRangeArray") fn := reflect.Zero(reflect.PtrTo(t)).MethodByName("ExtensionRangeArray")
if fn.IsValid() { if fn.IsValid() {
if !u.extensions.IsValid() && !u.oldExtensions.IsValid() { if !u.extensions.IsValid() && !u.oldExtensions.IsValid() {
panic("a message with extensions, but no extensions field in " + t.Name()) panic("a message with extensions, but no extensions field in " + t.Name())
@ -1948,7 +1950,7 @@ func encodeVarint(b []byte, x uint64) []byte {
// If there is an error, it returns 0,0. // If there is an error, it returns 0,0.
func decodeVarint(b []byte) (uint64, int) { func decodeVarint(b []byte) (uint64, int) {
var x, y uint64 var x, y uint64
if len(b) <= 0 { if len(b) == 0 {
goto bad goto bad
} }
x = uint64(b[0]) x = uint64(b[0])

View File

@ -31,7 +31,7 @@ Let's start with an example that shows the sessions API in a nutshell:
// environmental variable, or flag (or both), and don't accidentally commit it // environmental variable, or flag (or both), and don't accidentally commit it
// alongside your code. Ensure your key is sufficiently random - i.e. use Go's // alongside your code. Ensure your key is sufficiently random - i.e. use Go's
// crypto/rand or securecookie.GenerateRandomKey(32) and persist the result. // crypto/rand or securecookie.GenerateRandomKey(32) and persist the result.
var store = sessions.NewCookieStore([]byte(os.Getenv("SESSION_KEY"))) var store = sessions.NewCookieStore(os.Getenv("SESSION_KEY"))
func MyHandler(w http.ResponseWriter, r *http.Request) { func MyHandler(w http.ResponseWriter, r *http.Request) {
// Get a session. We're ignoring the error resulted from decoding an // Get a session. We're ignoring the error resulted from decoding an
@ -51,6 +51,18 @@ secret key used to authenticate the session. Inside the handler, we call
some session values in session.Values, which is a `map[interface{}]interface{}`. some session values in session.Values, which is a `map[interface{}]interface{}`.
And finally we call `session.Save()` to save the session in the response. And finally we call `session.Save()` to save the session in the response.
Important Note: If you aren't using gorilla/mux, you need to wrap your handlers
with
[`context.ClearHandler`](https://www.gorillatoolkit.org/pkg/context#ClearHandler)
or else you will leak memory! An easy way to do this is to wrap the top-level
mux when calling http.ListenAndServe:
```go
http.ListenAndServe(":8080", context.ClearHandler(http.DefaultServeMux))
```
The ClearHandler function is provided by the gorilla/context package.
More examples are available [on the Gorilla More examples are available [on the Gorilla
website](https://www.gorillatoolkit.org/pkg/sessions). website](https://www.gorillatoolkit.org/pkg/sessions).

View File

@ -59,6 +59,14 @@ session.Save(r, w), and either display an error message or otherwise handle it.
Save must be called before writing to the response, otherwise the session Save must be called before writing to the response, otherwise the session
cookie will not be sent to the client. cookie will not be sent to the client.
Important Note: If you aren't using gorilla/mux, you need to wrap your handlers
with context.ClearHandler as or else you will leak memory! An easy way to do this
is to wrap the top-level mux when calling http.ListenAndServe:
http.ListenAndServe(":8080", context.ClearHandler(http.DefaultServeMux))
The ClearHandler function is provided by the gorilla/context package.
That's all you need to know for the basic usage. Let's take a look at other That's all you need to know for the basic usage. Let's take a look at other
options, starting with flash messages. options, starting with flash messages.

View File

@ -5,11 +5,12 @@
package sessions package sessions
import ( import (
"context"
"encoding/gob" "encoding/gob"
"fmt" "fmt"
"net/http" "net/http"
"time" "time"
"github.com/gorilla/context"
) )
// Default flashes key. // Default flashes key.
@ -107,8 +108,7 @@ const registryKey contextKey = 0
// GetRegistry returns a registry instance for the current request. // GetRegistry returns a registry instance for the current request.
func GetRegistry(r *http.Request) *Registry { func GetRegistry(r *http.Request) *Registry {
var ctx = r.Context() registry := context.Get(r, registryKey)
registry := ctx.Value(registryKey)
if registry != nil { if registry != nil {
return registry.(*Registry) return registry.(*Registry)
} }
@ -116,7 +116,7 @@ func GetRegistry(r *http.Request) *Registry {
request: r, request: r,
sessions: make(map[string]sessionInfo), sessions: make(map[string]sessionInfo),
} }
*r = *r.WithContext(context.WithValue(ctx, registryKey, newRegistry)) context.Set(r, registryKey, newRegistry)
return newRegistry return newRegistry
} }

View File

@ -312,6 +312,10 @@ func (codec *directAnyCodec) Decode(ptr unsafe.Pointer, iter *Iterator) {
func (codec *directAnyCodec) Encode(ptr unsafe.Pointer, stream *Stream) { func (codec *directAnyCodec) Encode(ptr unsafe.Pointer, stream *Stream) {
any := *(*Any)(ptr) any := *(*Any)(ptr)
if any == nil {
stream.WriteNil()
return
}
any.WriteTo(stream) any.WriteTo(stream)
} }

View File

@ -77,14 +77,12 @@ func (iter *Iterator) ReadFloat32() (ret float32) {
} }
func (iter *Iterator) readPositiveFloat32() (ret float32) { func (iter *Iterator) readPositiveFloat32() (ret float32) {
value := uint64(0)
c := byte(' ')
i := iter.head i := iter.head
// first char // first char
if i == iter.tail { if i == iter.tail {
return iter.readFloat32SlowPath() return iter.readFloat32SlowPath()
} }
c = iter.buf[i] c := iter.buf[i]
i++ i++
ind := floatDigits[c] ind := floatDigits[c]
switch ind { switch ind {
@ -107,7 +105,7 @@ func (iter *Iterator) readPositiveFloat32() (ret float32) {
return return
} }
} }
value = uint64(ind) value := uint64(ind)
// chars before dot // chars before dot
non_decimal_loop: non_decimal_loop:
for ; i < iter.tail; i++ { for ; i < iter.tail; i++ {
@ -145,9 +143,7 @@ non_decimal_loop:
} }
// too many decimal places // too many decimal places
return iter.readFloat32SlowPath() return iter.readFloat32SlowPath()
case invalidCharForNumber: case invalidCharForNumber, dotInNumber:
fallthrough
case dotInNumber:
return iter.readFloat32SlowPath() return iter.readFloat32SlowPath()
} }
decimalPlaces++ decimalPlaces++
@ -218,14 +214,12 @@ func (iter *Iterator) ReadFloat64() (ret float64) {
} }
func (iter *Iterator) readPositiveFloat64() (ret float64) { func (iter *Iterator) readPositiveFloat64() (ret float64) {
value := uint64(0)
c := byte(' ')
i := iter.head i := iter.head
// first char // first char
if i == iter.tail { if i == iter.tail {
return iter.readFloat64SlowPath() return iter.readFloat64SlowPath()
} }
c = iter.buf[i] c := iter.buf[i]
i++ i++
ind := floatDigits[c] ind := floatDigits[c]
switch ind { switch ind {
@ -248,7 +242,7 @@ func (iter *Iterator) readPositiveFloat64() (ret float64) {
return return
} }
} }
value = uint64(ind) value := uint64(ind)
// chars before dot // chars before dot
non_decimal_loop: non_decimal_loop:
for ; i < iter.tail; i++ { for ; i < iter.tail; i++ {
@ -286,9 +280,7 @@ non_decimal_loop:
} }
// too many decimal places // too many decimal places
return iter.readFloat64SlowPath() return iter.readFloat64SlowPath()
case invalidCharForNumber: case invalidCharForNumber, dotInNumber:
fallthrough
case dotInNumber:
return iter.readFloat64SlowPath() return iter.readFloat64SlowPath()
} }
decimalPlaces++ decimalPlaces++

View File

@ -2,12 +2,22 @@
package jsoniter package jsoniter
import "fmt" import (
"fmt"
"io"
)
func (iter *Iterator) skipNumber() { func (iter *Iterator) skipNumber() {
if !iter.trySkipNumber() { if !iter.trySkipNumber() {
iter.unreadByte() iter.unreadByte()
iter.ReadFloat32() if iter.Error != nil && iter.Error != io.EOF {
return
}
iter.ReadFloat64()
if iter.Error != nil && iter.Error != io.EOF {
iter.Error = nil
iter.ReadBigFloat()
}
} }
} }

View File

@ -64,14 +64,26 @@ func decoderOfMapKey(ctx *ctx, typ reflect2.Type) ValDecoder {
return &numericMapKeyDecoder{decoderOfType(ctx, typ)} return &numericMapKeyDecoder{decoderOfType(ctx, typ)}
default: default:
ptrType := reflect2.PtrTo(typ) ptrType := reflect2.PtrTo(typ)
if ptrType.Implements(textMarshalerType) { if ptrType.Implements(unmarshalerType) {
return &referenceDecoder{
&unmarshalerDecoder{
valType: ptrType,
},
}
}
if typ.Implements(unmarshalerType) {
return &unmarshalerDecoder{
valType: typ,
}
}
if ptrType.Implements(textUnmarshalerType) {
return &referenceDecoder{ return &referenceDecoder{
&textUnmarshalerDecoder{ &textUnmarshalerDecoder{
valType: ptrType, valType: ptrType,
}, },
} }
} }
if typ.Implements(textMarshalerType) { if typ.Implements(textUnmarshalerType) {
return &textUnmarshalerDecoder{ return &textUnmarshalerDecoder{
valType: typ, valType: typ,
} }

View File

@ -93,8 +93,7 @@ func (encoder *marshalerEncoder) Encode(ptr unsafe.Pointer, stream *Stream) {
stream.WriteNil() stream.WriteNil()
return return
} }
marshaler := obj.(json.Marshaler) bytes, err := json.Marshal(obj)
bytes, err := marshaler.MarshalJSON()
if err != nil { if err != nil {
stream.Error = err stream.Error = err
} else { } else {

View File

@ -1,15 +0,0 @@
// +build appengine
package isatty
// IsTerminal returns true if the file descriptor is terminal which
// is always false on on appengine classic which is a sandboxed PaaS.
func IsTerminal(fd uintptr) bool {
return false
}
// IsCygwinTerminal() return true if the file descriptor is a cygwin or msys2
// terminal. This is also always false on this environment.
func IsCygwinTerminal(fd uintptr) bool {
return false
}

View File

@ -16,3 +16,9 @@ func IsTerminal(fd uintptr) bool {
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, fd, ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0) _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, fd, ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0)
return err == 0 return err == 0
} }
// IsCygwinTerminal return true if the file descriptor is a cygwin or msys2
// terminal. This is also always false on this environment.
func IsCygwinTerminal(fd uintptr) bool {
return false
}

View File

@ -16,3 +16,9 @@ func IsTerminal(fd uintptr) bool {
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, fd, ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0) _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, fd, ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0)
return err == 0 return err == 0
} }
// IsCygwinTerminal return true if the file descriptor is a cygwin or msys2
// terminal. This is also always false on this environment.
func IsCygwinTerminal(fd uintptr) bool {
return false
}

View File

@ -17,3 +17,9 @@ func IsTerminal(fd uintptr) bool {
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, fd, ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0) _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, fd, ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0)
return err == 0 return err == 0
} }
// IsCygwinTerminal return true if the file descriptor is a cygwin or msys2
// terminal. This is also always false on this environment.
func IsCygwinTerminal(fd uintptr) bool {
return false
}

View File

@ -1,9 +1,14 @@
// +build !windows // +build appengine js
// +build !appengine
package isatty package isatty
// IsCygwinTerminal return true if the file descriptor is a cygwin or msys2 // IsTerminal returns true if the file descriptor is terminal which
// is always false on js and appengine classic which is a sandboxed PaaS.
func IsTerminal(fd uintptr) bool {
return false
}
// IsCygwinTerminal() return true if the file descriptor is a cygwin or msys2
// terminal. This is also always false on this environment. // terminal. This is also always false on this environment.
func IsCygwinTerminal(fd uintptr) bool { func IsCygwinTerminal(fd uintptr) bool {
return false return false

View File

@ -14,3 +14,9 @@ func IsTerminal(fd uintptr) bool {
err := unix.IoctlSetTermio(int(fd), unix.TCGETA, &termio) err := unix.IoctlSetTermio(int(fd), unix.TCGETA, &termio)
return err == nil return err == nil
} }
// IsCygwinTerminal return true if the file descriptor is a cygwin or msys2
// terminal. This is also always false on this environment.
func IsCygwinTerminal(fd uintptr) bool {
return false
}

23
vendor/github.com/pkg/errors/LICENSE generated vendored Normal file
View File

@ -0,0 +1,23 @@
Copyright (c) 2015, Dave Cheney <dave@cheney.net>
All rights reserved.
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.
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 HOLDER 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.

52
vendor/github.com/pkg/errors/README.md generated vendored Normal file
View File

@ -0,0 +1,52 @@
# errors [![Travis-CI](https://travis-ci.org/pkg/errors.svg)](https://travis-ci.org/pkg/errors) [![AppVeyor](https://ci.appveyor.com/api/projects/status/b98mptawhudj53ep/branch/master?svg=true)](https://ci.appveyor.com/project/davecheney/errors/branch/master) [![GoDoc](https://godoc.org/github.com/pkg/errors?status.svg)](http://godoc.org/github.com/pkg/errors) [![Report card](https://goreportcard.com/badge/github.com/pkg/errors)](https://goreportcard.com/report/github.com/pkg/errors) [![Sourcegraph](https://sourcegraph.com/github.com/pkg/errors/-/badge.svg)](https://sourcegraph.com/github.com/pkg/errors?badge)
Package errors provides simple error handling primitives.
`go get github.com/pkg/errors`
The traditional error handling idiom in Go is roughly akin to
```go
if err != nil {
return err
}
```
which applied recursively up the call stack results in error reports without context or debugging information. The errors package allows programmers to add context to the failure path in their code in a way that does not destroy the original value of the error.
## Adding context to an error
The errors.Wrap function returns a new error that adds context to the original error. For example
```go
_, err := ioutil.ReadAll(r)
if err != nil {
return errors.Wrap(err, "read failed")
}
```
## Retrieving the cause of an error
Using `errors.Wrap` constructs a stack of errors, adding context to the preceding error. Depending on the nature of the error it may be necessary to reverse the operation of errors.Wrap to retrieve the original error for inspection. Any error value which implements this interface can be inspected by `errors.Cause`.
```go
type causer interface {
Cause() error
}
```
`errors.Cause` will recursively retrieve the topmost error which does not implement `causer`, which is assumed to be the original cause. For example:
```go
switch err := errors.Cause(err).(type) {
case *MyError:
// handle specifically
default:
// unknown error
}
```
[Read the package documentation for more information](https://godoc.org/github.com/pkg/errors).
## Contributing
We welcome pull requests, bug fixes and issue reports. With that said, the bar for adding new symbols to this package is intentionally set high.
Before proposing a change, please discuss your change by raising an issue.
## License
BSD-2-Clause

32
vendor/github.com/pkg/errors/appveyor.yml generated vendored Normal file
View File

@ -0,0 +1,32 @@
version: build-{build}.{branch}
clone_folder: C:\gopath\src\github.com\pkg\errors
shallow_clone: true # for startup speed
environment:
GOPATH: C:\gopath
platform:
- x64
# http://www.appveyor.com/docs/installed-software
install:
# some helpful output for debugging builds
- go version
- go env
# pre-installed MinGW at C:\MinGW is 32bit only
# but MSYS2 at C:\msys64 has mingw64
- set PATH=C:\msys64\mingw64\bin;%PATH%
- gcc --version
- g++ --version
build_script:
- go install -v ./...
test_script:
- set PATH=C:\gopath\bin;%PATH%
- go test -v ./...
#artifacts:
# - path: '%GOPATH%\bin\*.exe'
deploy: off

282
vendor/github.com/pkg/errors/errors.go generated vendored Normal file
View File

@ -0,0 +1,282 @@
// Package errors provides simple error handling primitives.
//
// The traditional error handling idiom in Go is roughly akin to
//
// if err != nil {
// return err
// }
//
// which when applied recursively up the call stack results in error reports
// without context or debugging information. The errors package allows
// programmers to add context to the failure path in their code in a way
// that does not destroy the original value of the error.
//
// Adding context to an error
//
// The errors.Wrap function returns a new error that adds context to the
// original error by recording a stack trace at the point Wrap is called,
// together with the supplied message. For example
//
// _, err := ioutil.ReadAll(r)
// if err != nil {
// return errors.Wrap(err, "read failed")
// }
//
// If additional control is required, the errors.WithStack and
// errors.WithMessage functions destructure errors.Wrap into its component
// operations: annotating an error with a stack trace and with a message,
// respectively.
//
// Retrieving the cause of an error
//
// Using errors.Wrap constructs a stack of errors, adding context to the
// preceding error. Depending on the nature of the error it may be necessary
// to reverse the operation of errors.Wrap to retrieve the original error
// for inspection. Any error value which implements this interface
//
// type causer interface {
// Cause() error
// }
//
// can be inspected by errors.Cause. errors.Cause will recursively retrieve
// the topmost error that does not implement causer, which is assumed to be
// the original cause. For example:
//
// switch err := errors.Cause(err).(type) {
// case *MyError:
// // handle specifically
// default:
// // unknown error
// }
//
// Although the causer interface is not exported by this package, it is
// considered a part of its stable public interface.
//
// Formatted printing of errors
//
// All error values returned from this package implement fmt.Formatter and can
// be formatted by the fmt package. The following verbs are supported:
//
// %s print the error. If the error has a Cause it will be
// printed recursively.
// %v see %s
// %+v extended format. Each Frame of the error's StackTrace will
// be printed in detail.
//
// Retrieving the stack trace of an error or wrapper
//
// New, Errorf, Wrap, and Wrapf record a stack trace at the point they are
// invoked. This information can be retrieved with the following interface:
//
// type stackTracer interface {
// StackTrace() errors.StackTrace
// }
//
// The returned errors.StackTrace type is defined as
//
// type StackTrace []Frame
//
// The Frame type represents a call site in the stack trace. Frame supports
// the fmt.Formatter interface that can be used for printing information about
// the stack trace of this error. For example:
//
// if err, ok := err.(stackTracer); ok {
// for _, f := range err.StackTrace() {
// fmt.Printf("%+s:%d", f)
// }
// }
//
// Although the stackTracer interface is not exported by this package, it is
// considered a part of its stable public interface.
//
// See the documentation for Frame.Format for more details.
package errors
import (
"fmt"
"io"
)
// New returns an error with the supplied message.
// New also records the stack trace at the point it was called.
func New(message string) error {
return &fundamental{
msg: message,
stack: callers(),
}
}
// Errorf formats according to a format specifier and returns the string
// as a value that satisfies error.
// Errorf also records the stack trace at the point it was called.
func Errorf(format string, args ...interface{}) error {
return &fundamental{
msg: fmt.Sprintf(format, args...),
stack: callers(),
}
}
// fundamental is an error that has a message and a stack, but no caller.
type fundamental struct {
msg string
*stack
}
func (f *fundamental) Error() string { return f.msg }
func (f *fundamental) Format(s fmt.State, verb rune) {
switch verb {
case 'v':
if s.Flag('+') {
io.WriteString(s, f.msg)
f.stack.Format(s, verb)
return
}
fallthrough
case 's':
io.WriteString(s, f.msg)
case 'q':
fmt.Fprintf(s, "%q", f.msg)
}
}
// WithStack annotates err with a stack trace at the point WithStack was called.
// If err is nil, WithStack returns nil.
func WithStack(err error) error {
if err == nil {
return nil
}
return &withStack{
err,
callers(),
}
}
type withStack struct {
error
*stack
}
func (w *withStack) Cause() error { return w.error }
func (w *withStack) Format(s fmt.State, verb rune) {
switch verb {
case 'v':
if s.Flag('+') {
fmt.Fprintf(s, "%+v", w.Cause())
w.stack.Format(s, verb)
return
}
fallthrough
case 's':
io.WriteString(s, w.Error())
case 'q':
fmt.Fprintf(s, "%q", w.Error())
}
}
// Wrap returns an error annotating err with a stack trace
// at the point Wrap is called, and the supplied message.
// If err is nil, Wrap returns nil.
func Wrap(err error, message string) error {
if err == nil {
return nil
}
err = &withMessage{
cause: err,
msg: message,
}
return &withStack{
err,
callers(),
}
}
// Wrapf returns an error annotating err with a stack trace
// at the point Wrapf is called, and the format specifier.
// If err is nil, Wrapf returns nil.
func Wrapf(err error, format string, args ...interface{}) error {
if err == nil {
return nil
}
err = &withMessage{
cause: err,
msg: fmt.Sprintf(format, args...),
}
return &withStack{
err,
callers(),
}
}
// WithMessage annotates err with a new message.
// If err is nil, WithMessage returns nil.
func WithMessage(err error, message string) error {
if err == nil {
return nil
}
return &withMessage{
cause: err,
msg: message,
}
}
// WithMessagef annotates err with the format specifier.
// If err is nil, WithMessagef returns nil.
func WithMessagef(err error, format string, args ...interface{}) error {
if err == nil {
return nil
}
return &withMessage{
cause: err,
msg: fmt.Sprintf(format, args...),
}
}
type withMessage struct {
cause error
msg string
}
func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() }
func (w *withMessage) Cause() error { return w.cause }
func (w *withMessage) Format(s fmt.State, verb rune) {
switch verb {
case 'v':
if s.Flag('+') {
fmt.Fprintf(s, "%+v\n", w.Cause())
io.WriteString(s, w.msg)
return
}
fallthrough
case 's', 'q':
io.WriteString(s, w.Error())
}
}
// Cause returns the underlying cause of the error, if possible.
// An error value has a cause if it implements the following
// interface:
//
// type causer interface {
// Cause() error
// }
//
// If the error does not implement Cause, the original error will
// be returned. If the error is nil, nil will be returned without further
// investigation.
func Cause(err error) error {
type causer interface {
Cause() error
}
for err != nil {
cause, ok := err.(causer)
if !ok {
break
}
err = cause.Cause()
}
return err
}

147
vendor/github.com/pkg/errors/stack.go generated vendored Normal file
View File

@ -0,0 +1,147 @@
package errors
import (
"fmt"
"io"
"path"
"runtime"
"strings"
)
// Frame represents a program counter inside a stack frame.
type Frame uintptr
// pc returns the program counter for this frame;
// multiple frames may have the same PC value.
func (f Frame) pc() uintptr { return uintptr(f) - 1 }
// file returns the full path to the file that contains the
// function for this Frame's pc.
func (f Frame) file() string {
fn := runtime.FuncForPC(f.pc())
if fn == nil {
return "unknown"
}
file, _ := fn.FileLine(f.pc())
return file
}
// line returns the line number of source code of the
// function for this Frame's pc.
func (f Frame) line() int {
fn := runtime.FuncForPC(f.pc())
if fn == nil {
return 0
}
_, line := fn.FileLine(f.pc())
return line
}
// Format formats the frame according to the fmt.Formatter interface.
//
// %s source file
// %d source line
// %n function name
// %v equivalent to %s:%d
//
// Format accepts flags that alter the printing of some verbs, as follows:
//
// %+s function name and path of source file relative to the compile time
// GOPATH separated by \n\t (<funcname>\n\t<path>)
// %+v equivalent to %+s:%d
func (f Frame) Format(s fmt.State, verb rune) {
switch verb {
case 's':
switch {
case s.Flag('+'):
pc := f.pc()
fn := runtime.FuncForPC(pc)
if fn == nil {
io.WriteString(s, "unknown")
} else {
file, _ := fn.FileLine(pc)
fmt.Fprintf(s, "%s\n\t%s", fn.Name(), file)
}
default:
io.WriteString(s, path.Base(f.file()))
}
case 'd':
fmt.Fprintf(s, "%d", f.line())
case 'n':
name := runtime.FuncForPC(f.pc()).Name()
io.WriteString(s, funcname(name))
case 'v':
f.Format(s, 's')
io.WriteString(s, ":")
f.Format(s, 'd')
}
}
// StackTrace is stack of Frames from innermost (newest) to outermost (oldest).
type StackTrace []Frame
// Format formats the stack of Frames according to the fmt.Formatter interface.
//
// %s lists source files for each Frame in the stack
// %v lists the source file and line number for each Frame in the stack
//
// Format accepts flags that alter the printing of some verbs, as follows:
//
// %+v Prints filename, function, and line number for each Frame in the stack.
func (st StackTrace) Format(s fmt.State, verb rune) {
switch verb {
case 'v':
switch {
case s.Flag('+'):
for _, f := range st {
fmt.Fprintf(s, "\n%+v", f)
}
case s.Flag('#'):
fmt.Fprintf(s, "%#v", []Frame(st))
default:
fmt.Fprintf(s, "%v", []Frame(st))
}
case 's':
fmt.Fprintf(s, "%s", []Frame(st))
}
}
// stack represents a stack of program counters.
type stack []uintptr
func (s *stack) Format(st fmt.State, verb rune) {
switch verb {
case 'v':
switch {
case st.Flag('+'):
for _, pc := range *s {
f := Frame(pc)
fmt.Fprintf(st, "\n%+v", f)
}
}
}
}
func (s *stack) StackTrace() StackTrace {
f := make([]Frame, len(*s))
for i := 0; i < len(f); i++ {
f[i] = Frame((*s)[i])
}
return f
}
func callers() *stack {
const depth = 32
var pcs [depth]uintptr
n := runtime.Callers(3, pcs[:])
var st stack = pcs[0:n]
return &st
}
// funcname removes the path prefix component of a function's name reported by func.Name().
func funcname(name string) string {
i := strings.LastIndex(name, "/")
name = name[i+1:]
i = strings.Index(name, ".")
return name[i+1:]
}

View File

@ -1,22 +0,0 @@
The MIT License (MIT)
Copyright (c) 2012-2015 Ugorji Nwoke.
All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

Some files were not shown because too many files have changed in this diff Show More