添加注册验证码接口
parent
48c5a9c552
commit
b133067a85
|
@ -0,0 +1,2 @@
|
||||||
|
glide.yaml
|
||||||
|
api_user.exe
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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"] = "邮箱验证码,请注意查收"
|
||||||
|
|
|
@ -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"
|
||||||
|
|
8
main.go
8
main.go
|
@ -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()))
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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.
|
|
|
@ -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,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)
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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...)
|
||||||
|
}
|
|
@ -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,
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
|
@ -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()
|
|
||||||
}
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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 ""
|
||||||
|
|
|
@ -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())
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 == "" {
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
|
@ -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.
|
||||||
|
|
||||||
`)
|
`)
|
||||||
|
|
|
@ -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())
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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
|
|
||||||
)
|
|
|
@ -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
|
|
||||||
)
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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})
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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) {}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
box: wercker/default
|
|
|
@ -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.
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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+)
|
||||||
|
|
||||||
---------------------------------------
|
---------------------------------------
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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,
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -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}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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])
|
||||||
|
|
|
@ -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).
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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++
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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.
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||||
|
}
|
|
@ -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:]
|
||||||
|
}
|
|
@ -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
Loading…
Reference in New Issue