use go modules
This commit is contained in:
parent
5a07b47a21
commit
ed9ee356a4
17
vendor/github.com/Azure/go-ntlmssp/.travis.yml
generated
vendored
Normal file
17
vendor/github.com/Azure/go-ntlmssp/.travis.yml
generated
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
sudo: false
|
||||||
|
|
||||||
|
language: go
|
||||||
|
|
||||||
|
before_script:
|
||||||
|
- go get -u golang.org/x/lint/golint
|
||||||
|
|
||||||
|
go:
|
||||||
|
- 1.10.x
|
||||||
|
- master
|
||||||
|
|
||||||
|
script:
|
||||||
|
- test -z "$(gofmt -s -l . | tee /dev/stderr)"
|
||||||
|
- test -z "$(golint ./... | tee /dev/stderr)"
|
||||||
|
- go vet ./...
|
||||||
|
- go build -v ./...
|
||||||
|
- go test -v ./...
|
21
vendor/github.com/Azure/go-ntlmssp/LICENSE
generated
vendored
Normal file
21
vendor/github.com/Azure/go-ntlmssp/LICENSE
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2016 Microsoft
|
||||||
|
|
||||||
|
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.
|
29
vendor/github.com/Azure/go-ntlmssp/README.md
generated
vendored
Normal file
29
vendor/github.com/Azure/go-ntlmssp/README.md
generated
vendored
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
# go-ntlmssp
|
||||||
|
Golang package that provides NTLM/Negotiate authentication over HTTP
|
||||||
|
|
||||||
|
[![GoDoc](https://godoc.org/github.com/Azure/go-ntlmssp?status.svg)](https://godoc.org/github.com/Azure/go-ntlmssp) [![Build Status](https://travis-ci.org/Azure/go-ntlmssp.svg?branch=dev)](https://travis-ci.org/Azure/go-ntlmssp)
|
||||||
|
|
||||||
|
Protocol details from https://msdn.microsoft.com/en-us/library/cc236621.aspx
|
||||||
|
Implementation hints from http://davenport.sourceforge.net/ntlm.html
|
||||||
|
|
||||||
|
This package only implements authentication, no key exchange or encryption. It
|
||||||
|
only supports Unicode (UTF16LE) encoding of protocol strings, no OEM encoding.
|
||||||
|
This package implements NTLMv2.
|
||||||
|
|
||||||
|
# Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
url, user, password := "http://www.example.com/secrets", "robpike", "pw123"
|
||||||
|
client := &http.Client{
|
||||||
|
Transport: ntlmssp.Negotiator{
|
||||||
|
RoundTripper:&http.Transport{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
req, _ := http.NewRequest("GET", url, nil)
|
||||||
|
req.SetBasicAuth(user, password)
|
||||||
|
res, _ := client.Do(req)
|
||||||
|
```
|
||||||
|
|
||||||
|
-----
|
||||||
|
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
|
187
vendor/github.com/Azure/go-ntlmssp/authenticate_message.go
generated
vendored
Normal file
187
vendor/github.com/Azure/go-ntlmssp/authenticate_message.go
generated
vendored
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
package ntlmssp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/binary"
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type authenicateMessage struct {
|
||||||
|
LmChallengeResponse []byte
|
||||||
|
NtChallengeResponse []byte
|
||||||
|
|
||||||
|
TargetName string
|
||||||
|
UserName string
|
||||||
|
|
||||||
|
// only set if negotiateFlag_NTLMSSP_NEGOTIATE_KEY_EXCH
|
||||||
|
EncryptedRandomSessionKey []byte
|
||||||
|
|
||||||
|
NegotiateFlags negotiateFlags
|
||||||
|
|
||||||
|
MIC []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type authenticateMessageFields struct {
|
||||||
|
messageHeader
|
||||||
|
LmChallengeResponse varField
|
||||||
|
NtChallengeResponse varField
|
||||||
|
TargetName varField
|
||||||
|
UserName varField
|
||||||
|
Workstation varField
|
||||||
|
_ [8]byte
|
||||||
|
NegotiateFlags negotiateFlags
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m authenicateMessage) MarshalBinary() ([]byte, error) {
|
||||||
|
if !m.NegotiateFlags.Has(negotiateFlagNTLMSSPNEGOTIATEUNICODE) {
|
||||||
|
return nil, errors.New("Only unicode is supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
target, user := toUnicode(m.TargetName), toUnicode(m.UserName)
|
||||||
|
workstation := toUnicode("")
|
||||||
|
|
||||||
|
ptr := binary.Size(&authenticateMessageFields{})
|
||||||
|
f := authenticateMessageFields{
|
||||||
|
messageHeader: newMessageHeader(3),
|
||||||
|
NegotiateFlags: m.NegotiateFlags,
|
||||||
|
LmChallengeResponse: newVarField(&ptr, len(m.LmChallengeResponse)),
|
||||||
|
NtChallengeResponse: newVarField(&ptr, len(m.NtChallengeResponse)),
|
||||||
|
TargetName: newVarField(&ptr, len(target)),
|
||||||
|
UserName: newVarField(&ptr, len(user)),
|
||||||
|
Workstation: newVarField(&ptr, len(workstation)),
|
||||||
|
}
|
||||||
|
|
||||||
|
f.NegotiateFlags.Unset(negotiateFlagNTLMSSPNEGOTIATEVERSION)
|
||||||
|
|
||||||
|
b := bytes.Buffer{}
|
||||||
|
if err := binary.Write(&b, binary.LittleEndian, &f); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := binary.Write(&b, binary.LittleEndian, &m.LmChallengeResponse); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := binary.Write(&b, binary.LittleEndian, &m.NtChallengeResponse); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := binary.Write(&b, binary.LittleEndian, &target); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := binary.Write(&b, binary.LittleEndian, &user); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := binary.Write(&b, binary.LittleEndian, &workstation); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//ProcessChallenge crafts an AUTHENTICATE message in response to the CHALLENGE message
|
||||||
|
//that was received from the server
|
||||||
|
func ProcessChallenge(challengeMessageData []byte, user, password string, domainNeeded bool) ([]byte, error) {
|
||||||
|
if user == "" && password == "" {
|
||||||
|
return nil, errors.New("Anonymous authentication not supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
var cm challengeMessage
|
||||||
|
if err := cm.UnmarshalBinary(challengeMessageData); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if cm.NegotiateFlags.Has(negotiateFlagNTLMSSPNEGOTIATELMKEY) {
|
||||||
|
return nil, errors.New("Only NTLM v2 is supported, but server requested v1 (NTLMSSP_NEGOTIATE_LM_KEY)")
|
||||||
|
}
|
||||||
|
if cm.NegotiateFlags.Has(negotiateFlagNTLMSSPNEGOTIATEKEYEXCH) {
|
||||||
|
return nil, errors.New("Key exchange requested but not supported (NTLMSSP_NEGOTIATE_KEY_EXCH)")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !domainNeeded {
|
||||||
|
cm.TargetName = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
am := authenicateMessage{
|
||||||
|
UserName: user,
|
||||||
|
TargetName: cm.TargetName,
|
||||||
|
NegotiateFlags: cm.NegotiateFlags,
|
||||||
|
}
|
||||||
|
|
||||||
|
timestamp := cm.TargetInfo[avIDMsvAvTimestamp]
|
||||||
|
if timestamp == nil { // no time sent, take current time
|
||||||
|
ft := uint64(time.Now().UnixNano()) / 100
|
||||||
|
ft += 116444736000000000 // add time between unix & windows offset
|
||||||
|
timestamp = make([]byte, 8)
|
||||||
|
binary.LittleEndian.PutUint64(timestamp, ft)
|
||||||
|
}
|
||||||
|
|
||||||
|
clientChallenge := make([]byte, 8)
|
||||||
|
rand.Reader.Read(clientChallenge)
|
||||||
|
|
||||||
|
ntlmV2Hash := getNtlmV2Hash(password, user, cm.TargetName)
|
||||||
|
|
||||||
|
am.NtChallengeResponse = computeNtlmV2Response(ntlmV2Hash,
|
||||||
|
cm.ServerChallenge[:], clientChallenge, timestamp, cm.TargetInfoRaw)
|
||||||
|
|
||||||
|
if cm.TargetInfoRaw == nil {
|
||||||
|
am.LmChallengeResponse = computeLmV2Response(ntlmV2Hash,
|
||||||
|
cm.ServerChallenge[:], clientChallenge)
|
||||||
|
}
|
||||||
|
return am.MarshalBinary()
|
||||||
|
}
|
||||||
|
|
||||||
|
func ProcessChallengeWithHash(challengeMessageData []byte, user, hash string) ([]byte, error) {
|
||||||
|
if user == "" && hash == "" {
|
||||||
|
return nil, errors.New("Anonymous authentication not supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
var cm challengeMessage
|
||||||
|
if err := cm.UnmarshalBinary(challengeMessageData); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if cm.NegotiateFlags.Has(negotiateFlagNTLMSSPNEGOTIATELMKEY) {
|
||||||
|
return nil, errors.New("Only NTLM v2 is supported, but server requested v1 (NTLMSSP_NEGOTIATE_LM_KEY)")
|
||||||
|
}
|
||||||
|
if cm.NegotiateFlags.Has(negotiateFlagNTLMSSPNEGOTIATEKEYEXCH) {
|
||||||
|
return nil, errors.New("Key exchange requested but not supported (NTLMSSP_NEGOTIATE_KEY_EXCH)")
|
||||||
|
}
|
||||||
|
|
||||||
|
am := authenicateMessage{
|
||||||
|
UserName: user,
|
||||||
|
TargetName: cm.TargetName,
|
||||||
|
NegotiateFlags: cm.NegotiateFlags,
|
||||||
|
}
|
||||||
|
|
||||||
|
timestamp := cm.TargetInfo[avIDMsvAvTimestamp]
|
||||||
|
if timestamp == nil { // no time sent, take current time
|
||||||
|
ft := uint64(time.Now().UnixNano()) / 100
|
||||||
|
ft += 116444736000000000 // add time between unix & windows offset
|
||||||
|
timestamp = make([]byte, 8)
|
||||||
|
binary.LittleEndian.PutUint64(timestamp, ft)
|
||||||
|
}
|
||||||
|
|
||||||
|
clientChallenge := make([]byte, 8)
|
||||||
|
rand.Reader.Read(clientChallenge)
|
||||||
|
|
||||||
|
hashParts := strings.Split(hash, ":")
|
||||||
|
if len(hashParts) > 1 {
|
||||||
|
hash = hashParts[1]
|
||||||
|
}
|
||||||
|
hashBytes, err := hex.DecodeString(hash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ntlmV2Hash := hmacMd5(hashBytes, toUnicode(strings.ToUpper(user)+cm.TargetName))
|
||||||
|
|
||||||
|
am.NtChallengeResponse = computeNtlmV2Response(ntlmV2Hash,
|
||||||
|
cm.ServerChallenge[:], clientChallenge, timestamp, cm.TargetInfoRaw)
|
||||||
|
|
||||||
|
if cm.TargetInfoRaw == nil {
|
||||||
|
am.LmChallengeResponse = computeLmV2Response(ntlmV2Hash,
|
||||||
|
cm.ServerChallenge[:], clientChallenge)
|
||||||
|
}
|
||||||
|
return am.MarshalBinary()
|
||||||
|
}
|
66
vendor/github.com/Azure/go-ntlmssp/authheader.go
generated
vendored
Normal file
66
vendor/github.com/Azure/go-ntlmssp/authheader.go
generated
vendored
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
package ntlmssp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type authheader []string
|
||||||
|
|
||||||
|
func (h authheader) IsBasic() bool {
|
||||||
|
for _, s := range h {
|
||||||
|
if strings.HasPrefix(string(s), "Basic ") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h authheader) Basic() string {
|
||||||
|
for _, s := range h {
|
||||||
|
if strings.HasPrefix(string(s), "Basic ") {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h authheader) IsNegotiate() bool {
|
||||||
|
for _, s := range h {
|
||||||
|
if strings.HasPrefix(string(s), "Negotiate") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h authheader) IsNTLM() bool {
|
||||||
|
for _, s := range h {
|
||||||
|
if strings.HasPrefix(string(s), "NTLM") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h authheader) GetData() ([]byte, error) {
|
||||||
|
for _, s := range h {
|
||||||
|
if strings.HasPrefix(string(s), "NTLM") || strings.HasPrefix(string(s), "Negotiate") || strings.HasPrefix(string(s), "Basic ") {
|
||||||
|
p := strings.Split(string(s), " ")
|
||||||
|
if len(p) < 2 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return base64.StdEncoding.DecodeString(string(p[1]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h authheader) GetBasicCreds() (username, password string, err error) {
|
||||||
|
d, err := h.GetData()
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
parts := strings.SplitN(string(d), ":", 2)
|
||||||
|
return parts[0], parts[1], nil
|
||||||
|
}
|
17
vendor/github.com/Azure/go-ntlmssp/avids.go
generated
vendored
Normal file
17
vendor/github.com/Azure/go-ntlmssp/avids.go
generated
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package ntlmssp
|
||||||
|
|
||||||
|
type avID uint16
|
||||||
|
|
||||||
|
const (
|
||||||
|
avIDMsvAvEOL avID = iota
|
||||||
|
avIDMsvAvNbComputerName
|
||||||
|
avIDMsvAvNbDomainName
|
||||||
|
avIDMsvAvDNSComputerName
|
||||||
|
avIDMsvAvDNSDomainName
|
||||||
|
avIDMsvAvDNSTreeName
|
||||||
|
avIDMsvAvFlags
|
||||||
|
avIDMsvAvTimestamp
|
||||||
|
avIDMsvAvSingleHost
|
||||||
|
avIDMsvAvTargetName
|
||||||
|
avIDMsvChannelBindings
|
||||||
|
)
|
82
vendor/github.com/Azure/go-ntlmssp/challenge_message.go
generated
vendored
Normal file
82
vendor/github.com/Azure/go-ntlmssp/challenge_message.go
generated
vendored
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
package ntlmssp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type challengeMessageFields struct {
|
||||||
|
messageHeader
|
||||||
|
TargetName varField
|
||||||
|
NegotiateFlags negotiateFlags
|
||||||
|
ServerChallenge [8]byte
|
||||||
|
_ [8]byte
|
||||||
|
TargetInfo varField
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m challengeMessageFields) IsValid() bool {
|
||||||
|
return m.messageHeader.IsValid() && m.MessageType == 2
|
||||||
|
}
|
||||||
|
|
||||||
|
type challengeMessage struct {
|
||||||
|
challengeMessageFields
|
||||||
|
TargetName string
|
||||||
|
TargetInfo map[avID][]byte
|
||||||
|
TargetInfoRaw []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *challengeMessage) UnmarshalBinary(data []byte) error {
|
||||||
|
r := bytes.NewReader(data)
|
||||||
|
err := binary.Read(r, binary.LittleEndian, &m.challengeMessageFields)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !m.challengeMessageFields.IsValid() {
|
||||||
|
return fmt.Errorf("Message is not a valid challenge message: %+v", m.challengeMessageFields.messageHeader)
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.challengeMessageFields.TargetName.Len > 0 {
|
||||||
|
m.TargetName, err = m.challengeMessageFields.TargetName.ReadStringFrom(data, m.NegotiateFlags.Has(negotiateFlagNTLMSSPNEGOTIATEUNICODE))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.challengeMessageFields.TargetInfo.Len > 0 {
|
||||||
|
d, err := m.challengeMessageFields.TargetInfo.ReadFrom(data)
|
||||||
|
m.TargetInfoRaw = d
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
m.TargetInfo = make(map[avID][]byte)
|
||||||
|
r := bytes.NewReader(d)
|
||||||
|
for {
|
||||||
|
var id avID
|
||||||
|
var l uint16
|
||||||
|
err = binary.Read(r, binary.LittleEndian, &id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if id == avIDMsvAvEOL {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
err = binary.Read(r, binary.LittleEndian, &l)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
value := make([]byte, l)
|
||||||
|
n, err := r.Read(value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if n != int(l) {
|
||||||
|
return fmt.Errorf("Expected to read %d bytes, got only %d", l, n)
|
||||||
|
}
|
||||||
|
m.TargetInfo[id] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
21
vendor/github.com/Azure/go-ntlmssp/messageheader.go
generated
vendored
Normal file
21
vendor/github.com/Azure/go-ntlmssp/messageheader.go
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package ntlmssp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
)
|
||||||
|
|
||||||
|
var signature = [8]byte{'N', 'T', 'L', 'M', 'S', 'S', 'P', 0}
|
||||||
|
|
||||||
|
type messageHeader struct {
|
||||||
|
Signature [8]byte
|
||||||
|
MessageType uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h messageHeader) IsValid() bool {
|
||||||
|
return bytes.Equal(h.Signature[:], signature[:]) &&
|
||||||
|
h.MessageType > 0 && h.MessageType < 4
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMessageHeader(messageType uint32) messageHeader {
|
||||||
|
return messageHeader{signature, messageType}
|
||||||
|
}
|
52
vendor/github.com/Azure/go-ntlmssp/negotiate_flags.go
generated
vendored
Normal file
52
vendor/github.com/Azure/go-ntlmssp/negotiate_flags.go
generated
vendored
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
package ntlmssp
|
||||||
|
|
||||||
|
type negotiateFlags uint32
|
||||||
|
|
||||||
|
const (
|
||||||
|
/*A*/ negotiateFlagNTLMSSPNEGOTIATEUNICODE negotiateFlags = 1 << 0
|
||||||
|
/*B*/ negotiateFlagNTLMNEGOTIATEOEM = 1 << 1
|
||||||
|
/*C*/ negotiateFlagNTLMSSPREQUESTTARGET = 1 << 2
|
||||||
|
|
||||||
|
/*D*/
|
||||||
|
negotiateFlagNTLMSSPNEGOTIATESIGN = 1 << 4
|
||||||
|
/*E*/ negotiateFlagNTLMSSPNEGOTIATESEAL = 1 << 5
|
||||||
|
/*F*/ negotiateFlagNTLMSSPNEGOTIATEDATAGRAM = 1 << 6
|
||||||
|
/*G*/ negotiateFlagNTLMSSPNEGOTIATELMKEY = 1 << 7
|
||||||
|
|
||||||
|
/*H*/
|
||||||
|
negotiateFlagNTLMSSPNEGOTIATENTLM = 1 << 9
|
||||||
|
|
||||||
|
/*J*/
|
||||||
|
negotiateFlagANONYMOUS = 1 << 11
|
||||||
|
/*K*/ negotiateFlagNTLMSSPNEGOTIATEOEMDOMAINSUPPLIED = 1 << 12
|
||||||
|
/*L*/ negotiateFlagNTLMSSPNEGOTIATEOEMWORKSTATIONSUPPLIED = 1 << 13
|
||||||
|
|
||||||
|
/*M*/
|
||||||
|
negotiateFlagNTLMSSPNEGOTIATEALWAYSSIGN = 1 << 15
|
||||||
|
/*N*/ negotiateFlagNTLMSSPTARGETTYPEDOMAIN = 1 << 16
|
||||||
|
/*O*/ negotiateFlagNTLMSSPTARGETTYPESERVER = 1 << 17
|
||||||
|
|
||||||
|
/*P*/
|
||||||
|
negotiateFlagNTLMSSPNEGOTIATEEXTENDEDSESSIONSECURITY = 1 << 19
|
||||||
|
/*Q*/ negotiateFlagNTLMSSPNEGOTIATEIDENTIFY = 1 << 20
|
||||||
|
|
||||||
|
/*R*/
|
||||||
|
negotiateFlagNTLMSSPREQUESTNONNTSESSIONKEY = 1 << 22
|
||||||
|
/*S*/ negotiateFlagNTLMSSPNEGOTIATETARGETINFO = 1 << 23
|
||||||
|
|
||||||
|
/*T*/
|
||||||
|
negotiateFlagNTLMSSPNEGOTIATEVERSION = 1 << 25
|
||||||
|
|
||||||
|
/*U*/
|
||||||
|
negotiateFlagNTLMSSPNEGOTIATE128 = 1 << 29
|
||||||
|
/*V*/ negotiateFlagNTLMSSPNEGOTIATEKEYEXCH = 1 << 30
|
||||||
|
/*W*/ negotiateFlagNTLMSSPNEGOTIATE56 = 1 << 31
|
||||||
|
)
|
||||||
|
|
||||||
|
func (field negotiateFlags) Has(flags negotiateFlags) bool {
|
||||||
|
return field&flags == flags
|
||||||
|
}
|
||||||
|
|
||||||
|
func (field *negotiateFlags) Unset(flags negotiateFlags) {
|
||||||
|
*field = *field ^ (*field & flags)
|
||||||
|
}
|
64
vendor/github.com/Azure/go-ntlmssp/negotiate_message.go
generated
vendored
Normal file
64
vendor/github.com/Azure/go-ntlmssp/negotiate_message.go
generated
vendored
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
package ntlmssp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const expMsgBodyLen = 40
|
||||||
|
|
||||||
|
type negotiateMessageFields struct {
|
||||||
|
messageHeader
|
||||||
|
NegotiateFlags negotiateFlags
|
||||||
|
|
||||||
|
Domain varField
|
||||||
|
Workstation varField
|
||||||
|
|
||||||
|
Version
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaultFlags = negotiateFlagNTLMSSPNEGOTIATETARGETINFO |
|
||||||
|
negotiateFlagNTLMSSPNEGOTIATE56 |
|
||||||
|
negotiateFlagNTLMSSPNEGOTIATE128 |
|
||||||
|
negotiateFlagNTLMSSPNEGOTIATEUNICODE |
|
||||||
|
negotiateFlagNTLMSSPNEGOTIATEEXTENDEDSESSIONSECURITY
|
||||||
|
|
||||||
|
//NewNegotiateMessage creates a new NEGOTIATE message with the
|
||||||
|
//flags that this package supports.
|
||||||
|
func NewNegotiateMessage(domainName, workstationName string) ([]byte, error) {
|
||||||
|
payloadOffset := expMsgBodyLen
|
||||||
|
flags := defaultFlags
|
||||||
|
|
||||||
|
if domainName != "" {
|
||||||
|
flags |= negotiateFlagNTLMSSPNEGOTIATEOEMDOMAINSUPPLIED
|
||||||
|
}
|
||||||
|
|
||||||
|
if workstationName != "" {
|
||||||
|
flags |= negotiateFlagNTLMSSPNEGOTIATEOEMWORKSTATIONSUPPLIED
|
||||||
|
}
|
||||||
|
|
||||||
|
msg := negotiateMessageFields{
|
||||||
|
messageHeader: newMessageHeader(1),
|
||||||
|
NegotiateFlags: flags,
|
||||||
|
Domain: newVarField(&payloadOffset, len(domainName)),
|
||||||
|
Workstation: newVarField(&payloadOffset, len(workstationName)),
|
||||||
|
Version: DefaultVersion(),
|
||||||
|
}
|
||||||
|
|
||||||
|
b := bytes.Buffer{}
|
||||||
|
if err := binary.Write(&b, binary.LittleEndian, &msg); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if b.Len() != expMsgBodyLen {
|
||||||
|
return nil, errors.New("incorrect body length")
|
||||||
|
}
|
||||||
|
|
||||||
|
payload := strings.ToUpper(domainName + workstationName)
|
||||||
|
if _, err := b.WriteString(payload); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.Bytes(), nil
|
||||||
|
}
|
151
vendor/github.com/Azure/go-ntlmssp/negotiator.go
generated
vendored
Normal file
151
vendor/github.com/Azure/go-ntlmssp/negotiator.go
generated
vendored
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
package ntlmssp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/base64"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetDomain : parse domain name from based on slashes in the input
|
||||||
|
// Need to check for upn as well
|
||||||
|
func GetDomain(user string) (string, string, bool) {
|
||||||
|
domain := ""
|
||||||
|
domainNeeded := false
|
||||||
|
|
||||||
|
if strings.Contains(user, "\\") {
|
||||||
|
ucomponents := strings.SplitN(user, "\\", 2)
|
||||||
|
domain = ucomponents[0]
|
||||||
|
user = ucomponents[1]
|
||||||
|
domainNeeded = true
|
||||||
|
} else if strings.Contains(user, "@") {
|
||||||
|
domainNeeded = false
|
||||||
|
} else {
|
||||||
|
domainNeeded = true
|
||||||
|
}
|
||||||
|
return user, domain, domainNeeded
|
||||||
|
}
|
||||||
|
|
||||||
|
//Negotiator is a http.Roundtripper decorator that automatically
|
||||||
|
//converts basic authentication to NTLM/Negotiate authentication when appropriate.
|
||||||
|
type Negotiator struct{ http.RoundTripper }
|
||||||
|
|
||||||
|
//RoundTrip sends the request to the server, handling any authentication
|
||||||
|
//re-sends as needed.
|
||||||
|
func (l Negotiator) RoundTrip(req *http.Request) (res *http.Response, err error) {
|
||||||
|
// Use default round tripper if not provided
|
||||||
|
rt := l.RoundTripper
|
||||||
|
if rt == nil {
|
||||||
|
rt = http.DefaultTransport
|
||||||
|
}
|
||||||
|
// If it is not basic auth, just round trip the request as usual
|
||||||
|
reqauth := authheader(req.Header.Values("Authorization"))
|
||||||
|
if !reqauth.IsBasic() {
|
||||||
|
return rt.RoundTrip(req)
|
||||||
|
}
|
||||||
|
reqauthBasic := reqauth.Basic()
|
||||||
|
// Save request body
|
||||||
|
body := bytes.Buffer{}
|
||||||
|
if req.Body != nil {
|
||||||
|
_, err = body.ReadFrom(req.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Body.Close()
|
||||||
|
req.Body = ioutil.NopCloser(bytes.NewReader(body.Bytes()))
|
||||||
|
}
|
||||||
|
// first try anonymous, in case the server still finds us
|
||||||
|
// authenticated from previous traffic
|
||||||
|
req.Header.Del("Authorization")
|
||||||
|
res, err = rt.RoundTrip(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if res.StatusCode != http.StatusUnauthorized {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
resauth := authheader(res.Header.Values("Www-Authenticate"))
|
||||||
|
if !resauth.IsNegotiate() && !resauth.IsNTLM() {
|
||||||
|
// Unauthorized, Negotiate not requested, let's try with basic auth
|
||||||
|
req.Header.Set("Authorization", string(reqauthBasic))
|
||||||
|
io.Copy(ioutil.Discard, res.Body)
|
||||||
|
res.Body.Close()
|
||||||
|
req.Body = ioutil.NopCloser(bytes.NewReader(body.Bytes()))
|
||||||
|
|
||||||
|
res, err = rt.RoundTrip(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if res.StatusCode != http.StatusUnauthorized {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
resauth = authheader(res.Header.Values("Www-Authenticate"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if resauth.IsNegotiate() || resauth.IsNTLM() {
|
||||||
|
// 401 with request:Basic and response:Negotiate
|
||||||
|
io.Copy(ioutil.Discard, res.Body)
|
||||||
|
res.Body.Close()
|
||||||
|
|
||||||
|
// recycle credentials
|
||||||
|
u, p, err := reqauth.GetBasicCreds()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// get domain from username
|
||||||
|
domain := ""
|
||||||
|
u, domain, domainNeeded := GetDomain(u)
|
||||||
|
|
||||||
|
// send negotiate
|
||||||
|
negotiateMessage, err := NewNegotiateMessage(domain, "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if resauth.IsNTLM() {
|
||||||
|
req.Header.Set("Authorization", "NTLM "+base64.StdEncoding.EncodeToString(negotiateMessage))
|
||||||
|
} else {
|
||||||
|
req.Header.Set("Authorization", "Negotiate "+base64.StdEncoding.EncodeToString(negotiateMessage))
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Body = ioutil.NopCloser(bytes.NewReader(body.Bytes()))
|
||||||
|
|
||||||
|
res, err = rt.RoundTrip(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// receive challenge?
|
||||||
|
resauth = authheader(res.Header.Values("Www-Authenticate"))
|
||||||
|
challengeMessage, err := resauth.GetData()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !(resauth.IsNegotiate() || resauth.IsNTLM()) || len(challengeMessage) == 0 {
|
||||||
|
// Negotiation failed, let client deal with response
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
io.Copy(ioutil.Discard, res.Body)
|
||||||
|
res.Body.Close()
|
||||||
|
|
||||||
|
// send authenticate
|
||||||
|
authenticateMessage, err := ProcessChallenge(challengeMessage, u, p, domainNeeded)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if resauth.IsNTLM() {
|
||||||
|
req.Header.Set("Authorization", "NTLM "+base64.StdEncoding.EncodeToString(authenticateMessage))
|
||||||
|
} else {
|
||||||
|
req.Header.Set("Authorization", "Negotiate "+base64.StdEncoding.EncodeToString(authenticateMessage))
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Body = ioutil.NopCloser(bytes.NewReader(body.Bytes()))
|
||||||
|
|
||||||
|
return rt.RoundTrip(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, err
|
||||||
|
}
|
51
vendor/github.com/Azure/go-ntlmssp/nlmp.go
generated
vendored
Normal file
51
vendor/github.com/Azure/go-ntlmssp/nlmp.go
generated
vendored
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
// Package ntlmssp provides NTLM/Negotiate authentication over HTTP
|
||||||
|
//
|
||||||
|
// Protocol details from https://msdn.microsoft.com/en-us/library/cc236621.aspx,
|
||||||
|
// implementation hints from http://davenport.sourceforge.net/ntlm.html .
|
||||||
|
// This package only implements authentication, no key exchange or encryption. It
|
||||||
|
// only supports Unicode (UTF16LE) encoding of protocol strings, no OEM encoding.
|
||||||
|
// This package implements NTLMv2.
|
||||||
|
package ntlmssp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/md5"
|
||||||
|
"golang.org/x/crypto/md4"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getNtlmV2Hash(password, username, target string) []byte {
|
||||||
|
return hmacMd5(getNtlmHash(password), toUnicode(strings.ToUpper(username)+target))
|
||||||
|
}
|
||||||
|
|
||||||
|
func getNtlmHash(password string) []byte {
|
||||||
|
hash := md4.New()
|
||||||
|
hash.Write(toUnicode(password))
|
||||||
|
return hash.Sum(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func computeNtlmV2Response(ntlmV2Hash, serverChallenge, clientChallenge,
|
||||||
|
timestamp, targetInfo []byte) []byte {
|
||||||
|
|
||||||
|
temp := []byte{1, 1, 0, 0, 0, 0, 0, 0}
|
||||||
|
temp = append(temp, timestamp...)
|
||||||
|
temp = append(temp, clientChallenge...)
|
||||||
|
temp = append(temp, 0, 0, 0, 0)
|
||||||
|
temp = append(temp, targetInfo...)
|
||||||
|
temp = append(temp, 0, 0, 0, 0)
|
||||||
|
|
||||||
|
NTProofStr := hmacMd5(ntlmV2Hash, serverChallenge, temp)
|
||||||
|
return append(NTProofStr, temp...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func computeLmV2Response(ntlmV2Hash, serverChallenge, clientChallenge []byte) []byte {
|
||||||
|
return append(hmacMd5(ntlmV2Hash, serverChallenge, clientChallenge), clientChallenge...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func hmacMd5(key []byte, data ...[]byte) []byte {
|
||||||
|
mac := hmac.New(md5.New, key)
|
||||||
|
for _, d := range data {
|
||||||
|
mac.Write(d)
|
||||||
|
}
|
||||||
|
return mac.Sum(nil)
|
||||||
|
}
|
29
vendor/github.com/Azure/go-ntlmssp/unicode.go
generated
vendored
Normal file
29
vendor/github.com/Azure/go-ntlmssp/unicode.go
generated
vendored
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
package ntlmssp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"unicode/utf16"
|
||||||
|
)
|
||||||
|
|
||||||
|
// helper func's for dealing with Windows Unicode (UTF16LE)
|
||||||
|
|
||||||
|
func fromUnicode(d []byte) (string, error) {
|
||||||
|
if len(d)%2 > 0 {
|
||||||
|
return "", errors.New("Unicode (UTF 16 LE) specified, but uneven data length")
|
||||||
|
}
|
||||||
|
s := make([]uint16, len(d)/2)
|
||||||
|
err := binary.Read(bytes.NewReader(d), binary.LittleEndian, &s)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(utf16.Decode(s)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func toUnicode(s string) []byte {
|
||||||
|
uints := utf16.Encode([]rune(s))
|
||||||
|
b := bytes.Buffer{}
|
||||||
|
binary.Write(&b, binary.LittleEndian, &uints)
|
||||||
|
return b.Bytes()
|
||||||
|
}
|
40
vendor/github.com/Azure/go-ntlmssp/varfield.go
generated
vendored
Normal file
40
vendor/github.com/Azure/go-ntlmssp/varfield.go
generated
vendored
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
package ntlmssp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type varField struct {
|
||||||
|
Len uint16
|
||||||
|
MaxLen uint16
|
||||||
|
BufferOffset uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f varField) ReadFrom(buffer []byte) ([]byte, error) {
|
||||||
|
if len(buffer) < int(f.BufferOffset+uint32(f.Len)) {
|
||||||
|
return nil, errors.New("Error reading data, varField extends beyond buffer")
|
||||||
|
}
|
||||||
|
return buffer[f.BufferOffset : f.BufferOffset+uint32(f.Len)], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f varField) ReadStringFrom(buffer []byte, unicode bool) (string, error) {
|
||||||
|
d, err := f.ReadFrom(buffer)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if unicode { // UTF-16LE encoding scheme
|
||||||
|
return fromUnicode(d)
|
||||||
|
}
|
||||||
|
// OEM encoding, close enough to ASCII, since no code page is specified
|
||||||
|
return string(d), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func newVarField(ptr *int, fieldsize int) varField {
|
||||||
|
f := varField{
|
||||||
|
Len: uint16(fieldsize),
|
||||||
|
MaxLen: uint16(fieldsize),
|
||||||
|
BufferOffset: uint32(*ptr),
|
||||||
|
}
|
||||||
|
*ptr += fieldsize
|
||||||
|
return f
|
||||||
|
}
|
20
vendor/github.com/Azure/go-ntlmssp/version.go
generated
vendored
Normal file
20
vendor/github.com/Azure/go-ntlmssp/version.go
generated
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package ntlmssp
|
||||||
|
|
||||||
|
// Version is a struct representing https://msdn.microsoft.com/en-us/library/cc236654.aspx
|
||||||
|
type Version struct {
|
||||||
|
ProductMajorVersion uint8
|
||||||
|
ProductMinorVersion uint8
|
||||||
|
ProductBuild uint16
|
||||||
|
_ [3]byte
|
||||||
|
NTLMRevisionCurrent uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultVersion returns a Version with "sensible" defaults (Windows 7)
|
||||||
|
func DefaultVersion() Version {
|
||||||
|
return Version{
|
||||||
|
ProductMajorVersion: 6,
|
||||||
|
ProductMinorVersion: 1,
|
||||||
|
ProductBuild: 7601,
|
||||||
|
NTLMRevisionCurrent: 15,
|
||||||
|
}
|
||||||
|
}
|
12
vendor/github.com/fsnotify/fsnotify/.editorconfig
generated
vendored
Normal file
12
vendor/github.com/fsnotify/fsnotify/.editorconfig
generated
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*.go]
|
||||||
|
indent_style = tab
|
||||||
|
indent_size = 4
|
||||||
|
insert_final_newline = true
|
||||||
|
|
||||||
|
[*.{yml,yaml}]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
1
vendor/github.com/fsnotify/fsnotify/.gitattributes
generated
vendored
Normal file
1
vendor/github.com/fsnotify/fsnotify/.gitattributes
generated
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
go.sum linguist-generated
|
6
vendor/github.com/fsnotify/fsnotify/.gitignore
generated
vendored
Normal file
6
vendor/github.com/fsnotify/fsnotify/.gitignore
generated
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
# Setup a Global .gitignore for OS and editor generated files:
|
||||||
|
# https://help.github.com/articles/ignoring-files
|
||||||
|
# git config --global core.excludesfile ~/.gitignore_global
|
||||||
|
|
||||||
|
.vagrant
|
||||||
|
*.sublime-project
|
2
vendor/github.com/fsnotify/fsnotify/.mailmap
generated
vendored
Normal file
2
vendor/github.com/fsnotify/fsnotify/.mailmap
generated
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
Chris Howey <howeyc@gmail.com> <chris@howey.me>
|
||||||
|
Nathan Youngman <git@nathany.com> <4566+nathany@users.noreply.github.com>
|
62
vendor/github.com/fsnotify/fsnotify/AUTHORS
generated
vendored
Normal file
62
vendor/github.com/fsnotify/fsnotify/AUTHORS
generated
vendored
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
# Names should be added to this file as
|
||||||
|
# Name or Organization <email address>
|
||||||
|
# The email address is not required for organizations.
|
||||||
|
|
||||||
|
# You can update this list using the following command:
|
||||||
|
#
|
||||||
|
# $ (head -n10 AUTHORS && git shortlog -se | sed -E 's/^\s+[0-9]+\t//') | tee AUTHORS
|
||||||
|
|
||||||
|
# Please keep the list sorted.
|
||||||
|
|
||||||
|
Aaron L <aaron@bettercoder.net>
|
||||||
|
Adrien Bustany <adrien@bustany.org>
|
||||||
|
Alexey Kazakov <alkazako@redhat.com>
|
||||||
|
Amit Krishnan <amit.krishnan@oracle.com>
|
||||||
|
Anmol Sethi <me@anmol.io>
|
||||||
|
Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
|
||||||
|
Brian Goff <cpuguy83@gmail.com>
|
||||||
|
Bruno Bigras <bigras.bruno@gmail.com>
|
||||||
|
Caleb Spare <cespare@gmail.com>
|
||||||
|
Case Nelson <case@teammating.com>
|
||||||
|
Chris Howey <howeyc@gmail.com>
|
||||||
|
Christoffer Buchholz <christoffer.buchholz@gmail.com>
|
||||||
|
Daniel Wagner-Hall <dawagner@gmail.com>
|
||||||
|
Dave Cheney <dave@cheney.net>
|
||||||
|
Eric Lin <linxiulei@gmail.com>
|
||||||
|
Evan Phoenix <evan@fallingsnow.net>
|
||||||
|
Francisco Souza <f@souza.cc>
|
||||||
|
Gautam Dey <gautam.dey77@gmail.com>
|
||||||
|
Hari haran <hariharan.uno@gmail.com>
|
||||||
|
Ichinose Shogo <shogo82148@gmail.com>
|
||||||
|
Johannes Ebke <johannes@ebke.org>
|
||||||
|
John C Barstow <jbowtie@amathaine.com>
|
||||||
|
Kelvin Fo <vmirage@gmail.com>
|
||||||
|
Ken-ichirou MATSUZAWA <chamas@h4.dion.ne.jp>
|
||||||
|
Matt Layher <mdlayher@gmail.com>
|
||||||
|
Matthias Stone <matthias@bellstone.ca>
|
||||||
|
Nathan Youngman <git@nathany.com>
|
||||||
|
Nickolai Zeldovich <nickolai@csail.mit.edu>
|
||||||
|
Oliver Bristow <evilumbrella+github@gmail.com>
|
||||||
|
Patrick <patrick@dropbox.com>
|
||||||
|
Paul Hammond <paul@paulhammond.org>
|
||||||
|
Pawel Knap <pawelknap88@gmail.com>
|
||||||
|
Pieter Droogendijk <pieter@binky.org.uk>
|
||||||
|
Pratik Shinde <pratikshinde320@gmail.com>
|
||||||
|
Pursuit92 <JoshChase@techpursuit.net>
|
||||||
|
Riku Voipio <riku.voipio@linaro.org>
|
||||||
|
Rob Figueiredo <robfig@gmail.com>
|
||||||
|
Rodrigo Chiossi <rodrigochiossi@gmail.com>
|
||||||
|
Slawek Ligus <root@ooz.ie>
|
||||||
|
Soge Zhang <zhssoge@gmail.com>
|
||||||
|
Tiffany Jernigan <tiffany.jernigan@intel.com>
|
||||||
|
Tilak Sharma <tilaks@google.com>
|
||||||
|
Tobias Klauser <tobias.klauser@gmail.com>
|
||||||
|
Tom Payne <twpayne@gmail.com>
|
||||||
|
Travis Cline <travis.cline@gmail.com>
|
||||||
|
Tudor Golubenco <tudor.g@gmail.com>
|
||||||
|
Vahe Khachikyan <vahe@live.ca>
|
||||||
|
Yukang <moorekang@gmail.com>
|
||||||
|
bronze1man <bronze1man@gmail.com>
|
||||||
|
debrando <denis.brandolini@gmail.com>
|
||||||
|
henrikedwards <henrik.edwards@gmail.com>
|
||||||
|
铁哥 <guotie.9@gmail.com>
|
357
vendor/github.com/fsnotify/fsnotify/CHANGELOG.md
generated
vendored
Normal file
357
vendor/github.com/fsnotify/fsnotify/CHANGELOG.md
generated
vendored
Normal file
@ -0,0 +1,357 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [1.5.4] - 2022-04-25
|
||||||
|
|
||||||
|
* Windows: add missing defer to `Watcher.WatchList` [#447](https://github.com/fsnotify/fsnotify/pull/447)
|
||||||
|
* go.mod: use latest x/sys [#444](https://github.com/fsnotify/fsnotify/pull/444)
|
||||||
|
* Fix compilation for OpenBSD [#443](https://github.com/fsnotify/fsnotify/pull/443)
|
||||||
|
|
||||||
|
## [1.5.3] - 2022-04-22
|
||||||
|
|
||||||
|
* This version is retracted. An incorrect branch is published accidentally [#445](https://github.com/fsnotify/fsnotify/issues/445)
|
||||||
|
|
||||||
|
## [1.5.2] - 2022-04-21
|
||||||
|
|
||||||
|
* Add a feature to return the directories and files that are being monitored [#374](https://github.com/fsnotify/fsnotify/pull/374)
|
||||||
|
* Fix potential crash on windows if `raw.FileNameLength` exceeds `syscall.MAX_PATH` [#361](https://github.com/fsnotify/fsnotify/pull/361)
|
||||||
|
* Allow build on unsupported GOOS [#424](https://github.com/fsnotify/fsnotify/pull/424)
|
||||||
|
* Don't set `poller.fd` twice in `newFdPoller` [#406](https://github.com/fsnotify/fsnotify/pull/406)
|
||||||
|
* fix go vet warnings: call to `(*T).Fatalf` from a non-test goroutine [#416](https://github.com/fsnotify/fsnotify/pull/416)
|
||||||
|
|
||||||
|
## [1.5.1] - 2021-08-24
|
||||||
|
|
||||||
|
* Revert Add AddRaw to not follow symlinks [#394](https://github.com/fsnotify/fsnotify/pull/394)
|
||||||
|
|
||||||
|
## [1.5.0] - 2021-08-20
|
||||||
|
|
||||||
|
* Go: Increase minimum required version to Go 1.12 [#381](https://github.com/fsnotify/fsnotify/pull/381)
|
||||||
|
* Feature: Add AddRaw method which does not follow symlinks when adding a watch [#289](https://github.com/fsnotify/fsnotify/pull/298)
|
||||||
|
* Windows: Follow symlinks by default like on all other systems [#289](https://github.com/fsnotify/fsnotify/pull/289)
|
||||||
|
* CI: Use GitHub Actions for CI and cover go 1.12-1.17
|
||||||
|
[#378](https://github.com/fsnotify/fsnotify/pull/378)
|
||||||
|
[#381](https://github.com/fsnotify/fsnotify/pull/381)
|
||||||
|
[#385](https://github.com/fsnotify/fsnotify/pull/385)
|
||||||
|
* Go 1.14+: Fix unsafe pointer conversion [#325](https://github.com/fsnotify/fsnotify/pull/325)
|
||||||
|
|
||||||
|
## [1.4.7] - 2018-01-09
|
||||||
|
|
||||||
|
* BSD/macOS: Fix possible deadlock on closing the watcher on kqueue (thanks @nhooyr and @glycerine)
|
||||||
|
* Tests: Fix missing verb on format string (thanks @rchiossi)
|
||||||
|
* Linux: Fix deadlock in Remove (thanks @aarondl)
|
||||||
|
* Linux: Watch.Add improvements (avoid race, fix consistency, reduce garbage) (thanks @twpayne)
|
||||||
|
* Docs: Moved FAQ into the README (thanks @vahe)
|
||||||
|
* Linux: Properly handle inotify's IN_Q_OVERFLOW event (thanks @zeldovich)
|
||||||
|
* Docs: replace references to OS X with macOS
|
||||||
|
|
||||||
|
## [1.4.2] - 2016-10-10
|
||||||
|
|
||||||
|
* Linux: use InotifyInit1 with IN_CLOEXEC to stop leaking a file descriptor to a child process when using fork/exec [#178](https://github.com/fsnotify/fsnotify/pull/178) (thanks @pattyshack)
|
||||||
|
|
||||||
|
## [1.4.1] - 2016-10-04
|
||||||
|
|
||||||
|
* Fix flaky inotify stress test on Linux [#177](https://github.com/fsnotify/fsnotify/pull/177) (thanks @pattyshack)
|
||||||
|
|
||||||
|
## [1.4.0] - 2016-10-01
|
||||||
|
|
||||||
|
* add a String() method to Event.Op [#165](https://github.com/fsnotify/fsnotify/pull/165) (thanks @oozie)
|
||||||
|
|
||||||
|
## [1.3.1] - 2016-06-28
|
||||||
|
|
||||||
|
* Windows: fix for double backslash when watching the root of a drive [#151](https://github.com/fsnotify/fsnotify/issues/151) (thanks @brunoqc)
|
||||||
|
|
||||||
|
## [1.3.0] - 2016-04-19
|
||||||
|
|
||||||
|
* Support linux/arm64 by [patching](https://go-review.googlesource.com/#/c/21971/) x/sys/unix and switching to to it from syscall (thanks @suihkulokki) [#135](https://github.com/fsnotify/fsnotify/pull/135)
|
||||||
|
|
||||||
|
## [1.2.10] - 2016-03-02
|
||||||
|
|
||||||
|
* Fix golint errors in windows.go [#121](https://github.com/fsnotify/fsnotify/pull/121) (thanks @tiffanyfj)
|
||||||
|
|
||||||
|
## [1.2.9] - 2016-01-13
|
||||||
|
|
||||||
|
kqueue: Fix logic for CREATE after REMOVE [#111](https://github.com/fsnotify/fsnotify/pull/111) (thanks @bep)
|
||||||
|
|
||||||
|
## [1.2.8] - 2015-12-17
|
||||||
|
|
||||||
|
* kqueue: fix race condition in Close [#105](https://github.com/fsnotify/fsnotify/pull/105) (thanks @djui for reporting the issue and @ppknap for writing a failing test)
|
||||||
|
* inotify: fix race in test
|
||||||
|
* enable race detection for continuous integration (Linux, Mac, Windows)
|
||||||
|
|
||||||
|
## [1.2.5] - 2015-10-17
|
||||||
|
|
||||||
|
* inotify: use epoll_create1 for arm64 support (requires Linux 2.6.27 or later) [#100](https://github.com/fsnotify/fsnotify/pull/100) (thanks @suihkulokki)
|
||||||
|
* inotify: fix path leaks [#73](https://github.com/fsnotify/fsnotify/pull/73) (thanks @chamaken)
|
||||||
|
* kqueue: watch for rename events on subdirectories [#83](https://github.com/fsnotify/fsnotify/pull/83) (thanks @guotie)
|
||||||
|
* kqueue: avoid infinite loops from symlinks cycles [#101](https://github.com/fsnotify/fsnotify/pull/101) (thanks @illicitonion)
|
||||||
|
|
||||||
|
## [1.2.1] - 2015-10-14
|
||||||
|
|
||||||
|
* kqueue: don't watch named pipes [#98](https://github.com/fsnotify/fsnotify/pull/98) (thanks @evanphx)
|
||||||
|
|
||||||
|
## [1.2.0] - 2015-02-08
|
||||||
|
|
||||||
|
* inotify: use epoll to wake up readEvents [#66](https://github.com/fsnotify/fsnotify/pull/66) (thanks @PieterD)
|
||||||
|
* inotify: closing watcher should now always shut down goroutine [#63](https://github.com/fsnotify/fsnotify/pull/63) (thanks @PieterD)
|
||||||
|
* kqueue: close kqueue after removing watches, fixes [#59](https://github.com/fsnotify/fsnotify/issues/59)
|
||||||
|
|
||||||
|
## [1.1.1] - 2015-02-05
|
||||||
|
|
||||||
|
* inotify: Retry read on EINTR [#61](https://github.com/fsnotify/fsnotify/issues/61) (thanks @PieterD)
|
||||||
|
|
||||||
|
## [1.1.0] - 2014-12-12
|
||||||
|
|
||||||
|
* kqueue: rework internals [#43](https://github.com/fsnotify/fsnotify/pull/43)
|
||||||
|
* add low-level functions
|
||||||
|
* only need to store flags on directories
|
||||||
|
* less mutexes [#13](https://github.com/fsnotify/fsnotify/issues/13)
|
||||||
|
* done can be an unbuffered channel
|
||||||
|
* remove calls to os.NewSyscallError
|
||||||
|
* More efficient string concatenation for Event.String() [#52](https://github.com/fsnotify/fsnotify/pull/52) (thanks @mdlayher)
|
||||||
|
* kqueue: fix regression in rework causing subdirectories to be watched [#48](https://github.com/fsnotify/fsnotify/issues/48)
|
||||||
|
* kqueue: cleanup internal watch before sending remove event [#51](https://github.com/fsnotify/fsnotify/issues/51)
|
||||||
|
|
||||||
|
## [1.0.4] - 2014-09-07
|
||||||
|
|
||||||
|
* kqueue: add dragonfly to the build tags.
|
||||||
|
* Rename source code files, rearrange code so exported APIs are at the top.
|
||||||
|
* Add done channel to example code. [#37](https://github.com/fsnotify/fsnotify/pull/37) (thanks @chenyukang)
|
||||||
|
|
||||||
|
## [1.0.3] - 2014-08-19
|
||||||
|
|
||||||
|
* [Fix] Windows MOVED_TO now translates to Create like on BSD and Linux. [#36](https://github.com/fsnotify/fsnotify/issues/36)
|
||||||
|
|
||||||
|
## [1.0.2] - 2014-08-17
|
||||||
|
|
||||||
|
* [Fix] Missing create events on macOS. [#14](https://github.com/fsnotify/fsnotify/issues/14) (thanks @zhsso)
|
||||||
|
* [Fix] Make ./path and path equivalent. (thanks @zhsso)
|
||||||
|
|
||||||
|
## [1.0.0] - 2014-08-15
|
||||||
|
|
||||||
|
* [API] Remove AddWatch on Windows, use Add.
|
||||||
|
* Improve documentation for exported identifiers. [#30](https://github.com/fsnotify/fsnotify/issues/30)
|
||||||
|
* Minor updates based on feedback from golint.
|
||||||
|
|
||||||
|
## dev / 2014-07-09
|
||||||
|
|
||||||
|
* Moved to [github.com/fsnotify/fsnotify](https://github.com/fsnotify/fsnotify).
|
||||||
|
* Use os.NewSyscallError instead of returning errno (thanks @hariharan-uno)
|
||||||
|
|
||||||
|
## dev / 2014-07-04
|
||||||
|
|
||||||
|
* kqueue: fix incorrect mutex used in Close()
|
||||||
|
* Update example to demonstrate usage of Op.
|
||||||
|
|
||||||
|
## dev / 2014-06-28
|
||||||
|
|
||||||
|
* [API] Don't set the Write Op for attribute notifications [#4](https://github.com/fsnotify/fsnotify/issues/4)
|
||||||
|
* Fix for String() method on Event (thanks Alex Brainman)
|
||||||
|
* Don't build on Plan 9 or Solaris (thanks @4ad)
|
||||||
|
|
||||||
|
## dev / 2014-06-21
|
||||||
|
|
||||||
|
* Events channel of type Event rather than *Event.
|
||||||
|
* [internal] use syscall constants directly for inotify and kqueue.
|
||||||
|
* [internal] kqueue: rename events to kevents and fileEvent to event.
|
||||||
|
|
||||||
|
## dev / 2014-06-19
|
||||||
|
|
||||||
|
* Go 1.3+ required on Windows (uses syscall.ERROR_MORE_DATA internally).
|
||||||
|
* [internal] remove cookie from Event struct (unused).
|
||||||
|
* [internal] Event struct has the same definition across every OS.
|
||||||
|
* [internal] remove internal watch and removeWatch methods.
|
||||||
|
|
||||||
|
## dev / 2014-06-12
|
||||||
|
|
||||||
|
* [API] Renamed Watch() to Add() and RemoveWatch() to Remove().
|
||||||
|
* [API] Pluralized channel names: Events and Errors.
|
||||||
|
* [API] Renamed FileEvent struct to Event.
|
||||||
|
* [API] Op constants replace methods like IsCreate().
|
||||||
|
|
||||||
|
## dev / 2014-06-12
|
||||||
|
|
||||||
|
* Fix data race on kevent buffer (thanks @tilaks) [#98](https://github.com/howeyc/fsnotify/pull/98)
|
||||||
|
|
||||||
|
## dev / 2014-05-23
|
||||||
|
|
||||||
|
* [API] Remove current implementation of WatchFlags.
|
||||||
|
* current implementation doesn't take advantage of OS for efficiency
|
||||||
|
* provides little benefit over filtering events as they are received, but has extra bookkeeping and mutexes
|
||||||
|
* no tests for the current implementation
|
||||||
|
* not fully implemented on Windows [#93](https://github.com/howeyc/fsnotify/issues/93#issuecomment-39285195)
|
||||||
|
|
||||||
|
## [0.9.3] - 2014-12-31
|
||||||
|
|
||||||
|
* kqueue: cleanup internal watch before sending remove event [#51](https://github.com/fsnotify/fsnotify/issues/51)
|
||||||
|
|
||||||
|
## [0.9.2] - 2014-08-17
|
||||||
|
|
||||||
|
* [Backport] Fix missing create events on macOS. [#14](https://github.com/fsnotify/fsnotify/issues/14) (thanks @zhsso)
|
||||||
|
|
||||||
|
## [0.9.1] - 2014-06-12
|
||||||
|
|
||||||
|
* Fix data race on kevent buffer (thanks @tilaks) [#98](https://github.com/howeyc/fsnotify/pull/98)
|
||||||
|
|
||||||
|
## [0.9.0] - 2014-01-17
|
||||||
|
|
||||||
|
* IsAttrib() for events that only concern a file's metadata [#79][] (thanks @abustany)
|
||||||
|
* [Fix] kqueue: fix deadlock [#77][] (thanks @cespare)
|
||||||
|
* [NOTICE] Development has moved to `code.google.com/p/go.exp/fsnotify` in preparation for inclusion in the Go standard library.
|
||||||
|
|
||||||
|
## [0.8.12] - 2013-11-13
|
||||||
|
|
||||||
|
* [API] Remove FD_SET and friends from Linux adapter
|
||||||
|
|
||||||
|
## [0.8.11] - 2013-11-02
|
||||||
|
|
||||||
|
* [Doc] Add Changelog [#72][] (thanks @nathany)
|
||||||
|
* [Doc] Spotlight and double modify events on macOS [#62][] (reported by @paulhammond)
|
||||||
|
|
||||||
|
## [0.8.10] - 2013-10-19
|
||||||
|
|
||||||
|
* [Fix] kqueue: remove file watches when parent directory is removed [#71][] (reported by @mdwhatcott)
|
||||||
|
* [Fix] kqueue: race between Close and readEvents [#70][] (reported by @bernerdschaefer)
|
||||||
|
* [Doc] specify OS-specific limits in README (thanks @debrando)
|
||||||
|
|
||||||
|
## [0.8.9] - 2013-09-08
|
||||||
|
|
||||||
|
* [Doc] Contributing (thanks @nathany)
|
||||||
|
* [Doc] update package path in example code [#63][] (thanks @paulhammond)
|
||||||
|
* [Doc] GoCI badge in README (Linux only) [#60][]
|
||||||
|
* [Doc] Cross-platform testing with Vagrant [#59][] (thanks @nathany)
|
||||||
|
|
||||||
|
## [0.8.8] - 2013-06-17
|
||||||
|
|
||||||
|
* [Fix] Windows: handle `ERROR_MORE_DATA` on Windows [#49][] (thanks @jbowtie)
|
||||||
|
|
||||||
|
## [0.8.7] - 2013-06-03
|
||||||
|
|
||||||
|
* [API] Make syscall flags internal
|
||||||
|
* [Fix] inotify: ignore event changes
|
||||||
|
* [Fix] race in symlink test [#45][] (reported by @srid)
|
||||||
|
* [Fix] tests on Windows
|
||||||
|
* lower case error messages
|
||||||
|
|
||||||
|
## [0.8.6] - 2013-05-23
|
||||||
|
|
||||||
|
* kqueue: Use EVT_ONLY flag on Darwin
|
||||||
|
* [Doc] Update README with full example
|
||||||
|
|
||||||
|
## [0.8.5] - 2013-05-09
|
||||||
|
|
||||||
|
* [Fix] inotify: allow monitoring of "broken" symlinks (thanks @tsg)
|
||||||
|
|
||||||
|
## [0.8.4] - 2013-04-07
|
||||||
|
|
||||||
|
* [Fix] kqueue: watch all file events [#40][] (thanks @ChrisBuchholz)
|
||||||
|
|
||||||
|
## [0.8.3] - 2013-03-13
|
||||||
|
|
||||||
|
* [Fix] inoitfy/kqueue memory leak [#36][] (reported by @nbkolchin)
|
||||||
|
* [Fix] kqueue: use fsnFlags for watching a directory [#33][] (reported by @nbkolchin)
|
||||||
|
|
||||||
|
## [0.8.2] - 2013-02-07
|
||||||
|
|
||||||
|
* [Doc] add Authors
|
||||||
|
* [Fix] fix data races for map access [#29][] (thanks @fsouza)
|
||||||
|
|
||||||
|
## [0.8.1] - 2013-01-09
|
||||||
|
|
||||||
|
* [Fix] Windows path separators
|
||||||
|
* [Doc] BSD License
|
||||||
|
|
||||||
|
## [0.8.0] - 2012-11-09
|
||||||
|
|
||||||
|
* kqueue: directory watching improvements (thanks @vmirage)
|
||||||
|
* inotify: add `IN_MOVED_TO` [#25][] (requested by @cpisto)
|
||||||
|
* [Fix] kqueue: deleting watched directory [#24][] (reported by @jakerr)
|
||||||
|
|
||||||
|
## [0.7.4] - 2012-10-09
|
||||||
|
|
||||||
|
* [Fix] inotify: fixes from https://codereview.appspot.com/5418045/ (ugorji)
|
||||||
|
* [Fix] kqueue: preserve watch flags when watching for delete [#21][] (reported by @robfig)
|
||||||
|
* [Fix] kqueue: watch the directory even if it isn't a new watch (thanks @robfig)
|
||||||
|
* [Fix] kqueue: modify after recreation of file
|
||||||
|
|
||||||
|
## [0.7.3] - 2012-09-27
|
||||||
|
|
||||||
|
* [Fix] kqueue: watch with an existing folder inside the watched folder (thanks @vmirage)
|
||||||
|
* [Fix] kqueue: no longer get duplicate CREATE events
|
||||||
|
|
||||||
|
## [0.7.2] - 2012-09-01
|
||||||
|
|
||||||
|
* kqueue: events for created directories
|
||||||
|
|
||||||
|
## [0.7.1] - 2012-07-14
|
||||||
|
|
||||||
|
* [Fix] for renaming files
|
||||||
|
|
||||||
|
## [0.7.0] - 2012-07-02
|
||||||
|
|
||||||
|
* [Feature] FSNotify flags
|
||||||
|
* [Fix] inotify: Added file name back to event path
|
||||||
|
|
||||||
|
## [0.6.0] - 2012-06-06
|
||||||
|
|
||||||
|
* kqueue: watch files after directory created (thanks @tmc)
|
||||||
|
|
||||||
|
## [0.5.1] - 2012-05-22
|
||||||
|
|
||||||
|
* [Fix] inotify: remove all watches before Close()
|
||||||
|
|
||||||
|
## [0.5.0] - 2012-05-03
|
||||||
|
|
||||||
|
* [API] kqueue: return errors during watch instead of sending over channel
|
||||||
|
* kqueue: match symlink behavior on Linux
|
||||||
|
* inotify: add `DELETE_SELF` (requested by @taralx)
|
||||||
|
* [Fix] kqueue: handle EINTR (reported by @robfig)
|
||||||
|
* [Doc] Godoc example [#1][] (thanks @davecheney)
|
||||||
|
|
||||||
|
## [0.4.0] - 2012-03-30
|
||||||
|
|
||||||
|
* Go 1 released: build with go tool
|
||||||
|
* [Feature] Windows support using winfsnotify
|
||||||
|
* Windows does not have attribute change notifications
|
||||||
|
* Roll attribute notifications into IsModify
|
||||||
|
|
||||||
|
## [0.3.0] - 2012-02-19
|
||||||
|
|
||||||
|
* kqueue: add files when watch directory
|
||||||
|
|
||||||
|
## [0.2.0] - 2011-12-30
|
||||||
|
|
||||||
|
* update to latest Go weekly code
|
||||||
|
|
||||||
|
## [0.1.0] - 2011-10-19
|
||||||
|
|
||||||
|
* kqueue: add watch on file creation to match inotify
|
||||||
|
* kqueue: create file event
|
||||||
|
* inotify: ignore `IN_IGNORED` events
|
||||||
|
* event String()
|
||||||
|
* linux: common FileEvent functions
|
||||||
|
* initial commit
|
||||||
|
|
||||||
|
[#79]: https://github.com/howeyc/fsnotify/pull/79
|
||||||
|
[#77]: https://github.com/howeyc/fsnotify/pull/77
|
||||||
|
[#72]: https://github.com/howeyc/fsnotify/issues/72
|
||||||
|
[#71]: https://github.com/howeyc/fsnotify/issues/71
|
||||||
|
[#70]: https://github.com/howeyc/fsnotify/issues/70
|
||||||
|
[#63]: https://github.com/howeyc/fsnotify/issues/63
|
||||||
|
[#62]: https://github.com/howeyc/fsnotify/issues/62
|
||||||
|
[#60]: https://github.com/howeyc/fsnotify/issues/60
|
||||||
|
[#59]: https://github.com/howeyc/fsnotify/issues/59
|
||||||
|
[#49]: https://github.com/howeyc/fsnotify/issues/49
|
||||||
|
[#45]: https://github.com/howeyc/fsnotify/issues/45
|
||||||
|
[#40]: https://github.com/howeyc/fsnotify/issues/40
|
||||||
|
[#36]: https://github.com/howeyc/fsnotify/issues/36
|
||||||
|
[#33]: https://github.com/howeyc/fsnotify/issues/33
|
||||||
|
[#29]: https://github.com/howeyc/fsnotify/issues/29
|
||||||
|
[#25]: https://github.com/howeyc/fsnotify/issues/25
|
||||||
|
[#24]: https://github.com/howeyc/fsnotify/issues/24
|
||||||
|
[#21]: https://github.com/howeyc/fsnotify/issues/21
|
60
vendor/github.com/fsnotify/fsnotify/CONTRIBUTING.md
generated
vendored
Normal file
60
vendor/github.com/fsnotify/fsnotify/CONTRIBUTING.md
generated
vendored
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
# Contributing
|
||||||
|
|
||||||
|
## Issues
|
||||||
|
|
||||||
|
* Request features and report bugs using the [GitHub Issue Tracker](https://github.com/fsnotify/fsnotify/issues).
|
||||||
|
* Please indicate the platform you are using fsnotify on.
|
||||||
|
* A code example to reproduce the problem is appreciated.
|
||||||
|
|
||||||
|
## Pull Requests
|
||||||
|
|
||||||
|
### Contributor License Agreement
|
||||||
|
|
||||||
|
fsnotify is derived from code in the [golang.org/x/exp](https://godoc.org/golang.org/x/exp) package and it may be included [in the standard library](https://github.com/fsnotify/fsnotify/issues/1) in the future. Therefore fsnotify carries the same [LICENSE](https://github.com/fsnotify/fsnotify/blob/master/LICENSE) as Go. Contributors retain their copyright, so you need to fill out a short form before we can accept your contribution: [Google Individual Contributor License Agreement](https://developers.google.com/open-source/cla/individual).
|
||||||
|
|
||||||
|
Please indicate that you have signed the CLA in your pull request.
|
||||||
|
|
||||||
|
### How fsnotify is Developed
|
||||||
|
|
||||||
|
* Development is done on feature branches.
|
||||||
|
* Tests are run on BSD, Linux, macOS and Windows.
|
||||||
|
* Pull requests are reviewed and [applied to master][am] using [hub][].
|
||||||
|
* Maintainers may modify or squash commits rather than asking contributors to.
|
||||||
|
* To issue a new release, the maintainers will:
|
||||||
|
* Update the CHANGELOG
|
||||||
|
* Tag a version, which will become available through gopkg.in.
|
||||||
|
|
||||||
|
### How to Fork
|
||||||
|
|
||||||
|
For smooth sailing, always use the original import path. Installing with `go get` makes this easy.
|
||||||
|
|
||||||
|
1. Install from GitHub (`go get -u github.com/fsnotify/fsnotify`)
|
||||||
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
||||||
|
3. Ensure everything works and the tests pass (see below)
|
||||||
|
4. Commit your changes (`git commit -am 'Add some feature'`)
|
||||||
|
|
||||||
|
Contribute upstream:
|
||||||
|
|
||||||
|
1. Fork fsnotify on GitHub
|
||||||
|
2. Add your remote (`git remote add fork git@github.com:mycompany/repo.git`)
|
||||||
|
3. Push to the branch (`git push fork my-new-feature`)
|
||||||
|
4. Create a new Pull Request on GitHub
|
||||||
|
|
||||||
|
This workflow is [thoroughly explained by Katrina Owen](https://splice.com/blog/contributing-open-source-git-repositories-go/).
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
|
||||||
|
fsnotify uses build tags to compile different code on Linux, BSD, macOS, and Windows.
|
||||||
|
|
||||||
|
Before doing a pull request, please do your best to test your changes on multiple platforms, and list which platforms you were able/unable to test on.
|
||||||
|
|
||||||
|
### Maintainers
|
||||||
|
|
||||||
|
Help maintaining fsnotify is welcome. To be a maintainer:
|
||||||
|
|
||||||
|
* Submit a pull request and sign the CLA as above.
|
||||||
|
* You must be able to run the test suite on Mac, Windows, Linux and BSD.
|
||||||
|
|
||||||
|
All code changes should be internal pull requests.
|
||||||
|
|
||||||
|
Releases are tagged using [Semantic Versioning](http://semver.org/).
|
28
vendor/github.com/fsnotify/fsnotify/LICENSE
generated
vendored
Normal file
28
vendor/github.com/fsnotify/fsnotify/LICENSE
generated
vendored
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
Copyright (c) 2012 The Go Authors. All rights reserved.
|
||||||
|
Copyright (c) 2012-2019 fsnotify Authors. 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.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
120
vendor/github.com/fsnotify/fsnotify/README.md
generated
vendored
Normal file
120
vendor/github.com/fsnotify/fsnotify/README.md
generated
vendored
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
# File system notifications for Go
|
||||||
|
|
||||||
|
[![Go Reference](https://pkg.go.dev/badge/github.com/fsnotify/fsnotify.svg)](https://pkg.go.dev/github.com/fsnotify/fsnotify) [![Go Report Card](https://goreportcard.com/badge/github.com/fsnotify/fsnotify)](https://goreportcard.com/report/github.com/fsnotify/fsnotify) [![Maintainers Wanted](https://img.shields.io/badge/maintainers-wanted-red.svg)](https://github.com/fsnotify/fsnotify/issues/413)
|
||||||
|
|
||||||
|
fsnotify utilizes [`golang.org/x/sys`](https://pkg.go.dev/golang.org/x/sys) rather than [`syscall`](https://pkg.go.dev/syscall) from the standard library.
|
||||||
|
|
||||||
|
Cross platform: Windows, Linux, BSD and macOS.
|
||||||
|
|
||||||
|
| Adapter | OS | Status |
|
||||||
|
| --------------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| inotify | Linux 2.6.27 or later, Android\* | Supported |
|
||||||
|
| kqueue | BSD, macOS, iOS\* | Supported |
|
||||||
|
| ReadDirectoryChangesW | Windows | Supported |
|
||||||
|
| FSEvents | macOS | [Planned](https://github.com/fsnotify/fsnotify/issues/11) |
|
||||||
|
| FEN | Solaris 11 | [In Progress](https://github.com/fsnotify/fsnotify/pull/371) |
|
||||||
|
| fanotify | Linux 2.6.37+ | [Maybe](https://github.com/fsnotify/fsnotify/issues/114) |
|
||||||
|
| USN Journals | Windows | [Maybe](https://github.com/fsnotify/fsnotify/issues/53) |
|
||||||
|
| Polling | *All* | [Maybe](https://github.com/fsnotify/fsnotify/issues/9) |
|
||||||
|
|
||||||
|
\* Android and iOS are untested.
|
||||||
|
|
||||||
|
Please see [the documentation](https://pkg.go.dev/github.com/fsnotify/fsnotify) and consult the [FAQ](#faq) for usage information.
|
||||||
|
|
||||||
|
## API stability
|
||||||
|
|
||||||
|
fsnotify is a fork of [howeyc/fsnotify](https://github.com/howeyc/fsnotify) with a new API as of v1.0. The API is based on [this design document](http://goo.gl/MrYxyA).
|
||||||
|
|
||||||
|
All [releases](https://github.com/fsnotify/fsnotify/releases) are tagged based on [Semantic Versioning](http://semver.org/).
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/fsnotify/fsnotify"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
watcher, err := fsnotify.NewWatcher()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer watcher.Close()
|
||||||
|
|
||||||
|
done := make(chan bool)
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case event, ok := <-watcher.Events:
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Println("event:", event)
|
||||||
|
if event.Op&fsnotify.Write == fsnotify.Write {
|
||||||
|
log.Println("modified file:", event.Name)
|
||||||
|
}
|
||||||
|
case err, ok := <-watcher.Errors:
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Println("error:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
err = watcher.Add("/tmp/foo")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
<-done
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Please refer to [CONTRIBUTING][] before opening an issue or pull request.
|
||||||
|
|
||||||
|
## FAQ
|
||||||
|
|
||||||
|
**When a file is moved to another directory is it still being watched?**
|
||||||
|
|
||||||
|
No (it shouldn't be, unless you are watching where it was moved to).
|
||||||
|
|
||||||
|
**When I watch a directory, are all subdirectories watched as well?**
|
||||||
|
|
||||||
|
No, you must add watches for any directory you want to watch (a recursive watcher is on the roadmap [#18][]).
|
||||||
|
|
||||||
|
**Do I have to watch the Error and Event channels in a separate goroutine?**
|
||||||
|
|
||||||
|
As of now, yes. Looking into making this single-thread friendly (see [howeyc #7][#7])
|
||||||
|
|
||||||
|
**Why am I receiving multiple events for the same file on OS X?**
|
||||||
|
|
||||||
|
Spotlight indexing on OS X can result in multiple events (see [howeyc #62][#62]). A temporary workaround is to add your folder(s) to the *Spotlight Privacy settings* until we have a native FSEvents implementation (see [#11][]).
|
||||||
|
|
||||||
|
**How many files can be watched at once?**
|
||||||
|
|
||||||
|
There are OS-specific limits as to how many watches can be created:
|
||||||
|
* Linux: /proc/sys/fs/inotify/max_user_watches contains the limit, reaching this limit results in a "no space left on device" error.
|
||||||
|
* BSD / OSX: sysctl variables "kern.maxfiles" and "kern.maxfilesperproc", reaching these limits results in a "too many open files" error.
|
||||||
|
|
||||||
|
**Why don't notifications work with NFS filesystems or filesystem in userspace (FUSE)?**
|
||||||
|
|
||||||
|
fsnotify requires support from underlying OS to work. The current NFS protocol does not provide network level support for file notifications.
|
||||||
|
|
||||||
|
[#62]: https://github.com/howeyc/fsnotify/issues/62
|
||||||
|
[#18]: https://github.com/fsnotify/fsnotify/issues/18
|
||||||
|
[#11]: https://github.com/fsnotify/fsnotify/issues/11
|
||||||
|
[#7]: https://github.com/howeyc/fsnotify/issues/7
|
||||||
|
|
||||||
|
[contributing]: https://github.com/fsnotify/fsnotify/blob/master/CONTRIBUTING.md
|
||||||
|
|
||||||
|
## Related Projects
|
||||||
|
|
||||||
|
* [notify](https://github.com/rjeczalik/notify)
|
||||||
|
* [fsevents](https://github.com/fsnotify/fsevents)
|
||||||
|
|
38
vendor/github.com/fsnotify/fsnotify/fen.go
generated
vendored
Normal file
38
vendor/github.com/fsnotify/fsnotify/fen.go
generated
vendored
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
// Copyright 2010 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build solaris
|
||||||
|
// +build solaris
|
||||||
|
|
||||||
|
package fsnotify
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Watcher watches a set of files, delivering events to a channel.
|
||||||
|
type Watcher struct {
|
||||||
|
Events chan Event
|
||||||
|
Errors chan error
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
|
||||||
|
func NewWatcher() (*Watcher, error) {
|
||||||
|
return nil, errors.New("FEN based watcher not yet supported for fsnotify\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close removes all watches and closes the events channel.
|
||||||
|
func (w *Watcher) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add starts watching the named file or directory (non-recursively).
|
||||||
|
func (w *Watcher) Add(name string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove stops watching the the named file or directory (non-recursively).
|
||||||
|
func (w *Watcher) Remove(name string) error {
|
||||||
|
return nil
|
||||||
|
}
|
69
vendor/github.com/fsnotify/fsnotify/fsnotify.go
generated
vendored
Normal file
69
vendor/github.com/fsnotify/fsnotify/fsnotify.go
generated
vendored
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
// Copyright 2012 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build !plan9
|
||||||
|
// +build !plan9
|
||||||
|
|
||||||
|
// Package fsnotify provides a platform-independent interface for file system notifications.
|
||||||
|
package fsnotify
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Event represents a single file system notification.
|
||||||
|
type Event struct {
|
||||||
|
Name string // Relative path to the file or directory.
|
||||||
|
Op Op // File operation that triggered the event.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Op describes a set of file operations.
|
||||||
|
type Op uint32
|
||||||
|
|
||||||
|
// These are the generalized file operations that can trigger a notification.
|
||||||
|
const (
|
||||||
|
Create Op = 1 << iota
|
||||||
|
Write
|
||||||
|
Remove
|
||||||
|
Rename
|
||||||
|
Chmod
|
||||||
|
)
|
||||||
|
|
||||||
|
func (op Op) String() string {
|
||||||
|
// Use a buffer for efficient string concatenation
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
|
||||||
|
if op&Create == Create {
|
||||||
|
buffer.WriteString("|CREATE")
|
||||||
|
}
|
||||||
|
if op&Remove == Remove {
|
||||||
|
buffer.WriteString("|REMOVE")
|
||||||
|
}
|
||||||
|
if op&Write == Write {
|
||||||
|
buffer.WriteString("|WRITE")
|
||||||
|
}
|
||||||
|
if op&Rename == Rename {
|
||||||
|
buffer.WriteString("|RENAME")
|
||||||
|
}
|
||||||
|
if op&Chmod == Chmod {
|
||||||
|
buffer.WriteString("|CHMOD")
|
||||||
|
}
|
||||||
|
if buffer.Len() == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return buffer.String()[1:] // Strip leading pipe
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a string representation of the event in the form
|
||||||
|
// "file: REMOVE|WRITE|..."
|
||||||
|
func (e Event) String() string {
|
||||||
|
return fmt.Sprintf("%q: %s", e.Name, e.Op.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Common errors that can be reported by a watcher
|
||||||
|
var (
|
||||||
|
ErrEventOverflow = errors.New("fsnotify queue overflow")
|
||||||
|
)
|
36
vendor/github.com/fsnotify/fsnotify/fsnotify_unsupported.go
generated
vendored
Normal file
36
vendor/github.com/fsnotify/fsnotify/fsnotify_unsupported.go
generated
vendored
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
// Copyright 2022 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build !darwin && !dragonfly && !freebsd && !openbsd && !linux && !netbsd && !solaris && !windows
|
||||||
|
// +build !darwin,!dragonfly,!freebsd,!openbsd,!linux,!netbsd,!solaris,!windows
|
||||||
|
|
||||||
|
package fsnotify
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Watcher watches a set of files, delivering events to a channel.
|
||||||
|
type Watcher struct{}
|
||||||
|
|
||||||
|
// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
|
||||||
|
func NewWatcher() (*Watcher, error) {
|
||||||
|
return nil, fmt.Errorf("fsnotify not supported on %s", runtime.GOOS)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close removes all watches and closes the events channel.
|
||||||
|
func (w *Watcher) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add starts watching the named file or directory (non-recursively).
|
||||||
|
func (w *Watcher) Add(name string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove stops watching the the named file or directory (non-recursively).
|
||||||
|
func (w *Watcher) Remove(name string) error {
|
||||||
|
return nil
|
||||||
|
}
|
351
vendor/github.com/fsnotify/fsnotify/inotify.go
generated
vendored
Normal file
351
vendor/github.com/fsnotify/fsnotify/inotify.go
generated
vendored
Normal file
@ -0,0 +1,351 @@
|
|||||||
|
// Copyright 2010 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build linux
|
||||||
|
// +build linux
|
||||||
|
|
||||||
|
package fsnotify
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Watcher watches a set of files, delivering events to a channel.
|
||||||
|
type Watcher struct {
|
||||||
|
Events chan Event
|
||||||
|
Errors chan error
|
||||||
|
mu sync.Mutex // Map access
|
||||||
|
fd int
|
||||||
|
poller *fdPoller
|
||||||
|
watches map[string]*watch // Map of inotify watches (key: path)
|
||||||
|
paths map[int]string // Map of watched paths (key: watch descriptor)
|
||||||
|
done chan struct{} // Channel for sending a "quit message" to the reader goroutine
|
||||||
|
doneResp chan struct{} // Channel to respond to Close
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
|
||||||
|
func NewWatcher() (*Watcher, error) {
|
||||||
|
// Create inotify fd
|
||||||
|
fd, errno := unix.InotifyInit1(unix.IN_CLOEXEC)
|
||||||
|
if fd == -1 {
|
||||||
|
return nil, errno
|
||||||
|
}
|
||||||
|
// Create epoll
|
||||||
|
poller, err := newFdPoller(fd)
|
||||||
|
if err != nil {
|
||||||
|
unix.Close(fd)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
w := &Watcher{
|
||||||
|
fd: fd,
|
||||||
|
poller: poller,
|
||||||
|
watches: make(map[string]*watch),
|
||||||
|
paths: make(map[int]string),
|
||||||
|
Events: make(chan Event),
|
||||||
|
Errors: make(chan error),
|
||||||
|
done: make(chan struct{}),
|
||||||
|
doneResp: make(chan struct{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
go w.readEvents()
|
||||||
|
return w, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Watcher) isClosed() bool {
|
||||||
|
select {
|
||||||
|
case <-w.done:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close removes all watches and closes the events channel.
|
||||||
|
func (w *Watcher) Close() error {
|
||||||
|
if w.isClosed() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send 'close' signal to goroutine, and set the Watcher to closed.
|
||||||
|
close(w.done)
|
||||||
|
|
||||||
|
// Wake up goroutine
|
||||||
|
w.poller.wake()
|
||||||
|
|
||||||
|
// Wait for goroutine to close
|
||||||
|
<-w.doneResp
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add starts watching the named file or directory (non-recursively).
|
||||||
|
func (w *Watcher) Add(name string) error {
|
||||||
|
name = filepath.Clean(name)
|
||||||
|
if w.isClosed() {
|
||||||
|
return errors.New("inotify instance already closed")
|
||||||
|
}
|
||||||
|
|
||||||
|
const agnosticEvents = unix.IN_MOVED_TO | unix.IN_MOVED_FROM |
|
||||||
|
unix.IN_CREATE | unix.IN_ATTRIB | unix.IN_MODIFY |
|
||||||
|
unix.IN_MOVE_SELF | unix.IN_DELETE | unix.IN_DELETE_SELF
|
||||||
|
|
||||||
|
var flags uint32 = agnosticEvents
|
||||||
|
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
watchEntry := w.watches[name]
|
||||||
|
if watchEntry != nil {
|
||||||
|
flags |= watchEntry.flags | unix.IN_MASK_ADD
|
||||||
|
}
|
||||||
|
wd, errno := unix.InotifyAddWatch(w.fd, name, flags)
|
||||||
|
if wd == -1 {
|
||||||
|
return errno
|
||||||
|
}
|
||||||
|
|
||||||
|
if watchEntry == nil {
|
||||||
|
w.watches[name] = &watch{wd: uint32(wd), flags: flags}
|
||||||
|
w.paths[wd] = name
|
||||||
|
} else {
|
||||||
|
watchEntry.wd = uint32(wd)
|
||||||
|
watchEntry.flags = flags
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove stops watching the named file or directory (non-recursively).
|
||||||
|
func (w *Watcher) Remove(name string) error {
|
||||||
|
name = filepath.Clean(name)
|
||||||
|
|
||||||
|
// Fetch the watch.
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
watch, ok := w.watches[name]
|
||||||
|
|
||||||
|
// Remove it from inotify.
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("can't remove non-existent inotify watch for: %s", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We successfully removed the watch if InotifyRmWatch doesn't return an
|
||||||
|
// error, we need to clean up our internal state to ensure it matches
|
||||||
|
// inotify's kernel state.
|
||||||
|
delete(w.paths, int(watch.wd))
|
||||||
|
delete(w.watches, name)
|
||||||
|
|
||||||
|
// inotify_rm_watch will return EINVAL if the file has been deleted;
|
||||||
|
// the inotify will already have been removed.
|
||||||
|
// watches and pathes are deleted in ignoreLinux() implicitly and asynchronously
|
||||||
|
// by calling inotify_rm_watch() below. e.g. readEvents() goroutine receives IN_IGNORE
|
||||||
|
// so that EINVAL means that the wd is being rm_watch()ed or its file removed
|
||||||
|
// by another thread and we have not received IN_IGNORE event.
|
||||||
|
success, errno := unix.InotifyRmWatch(w.fd, watch.wd)
|
||||||
|
if success == -1 {
|
||||||
|
// TODO: Perhaps it's not helpful to return an error here in every case.
|
||||||
|
// the only two possible errors are:
|
||||||
|
// EBADF, which happens when w.fd is not a valid file descriptor of any kind.
|
||||||
|
// EINVAL, which is when fd is not an inotify descriptor or wd is not a valid watch descriptor.
|
||||||
|
// Watch descriptors are invalidated when they are removed explicitly or implicitly;
|
||||||
|
// explicitly by inotify_rm_watch, implicitly when the file they are watching is deleted.
|
||||||
|
return errno
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WatchList returns the directories and files that are being monitered.
|
||||||
|
func (w *Watcher) WatchList() []string {
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
|
||||||
|
entries := make([]string, 0, len(w.watches))
|
||||||
|
for pathname := range w.watches {
|
||||||
|
entries = append(entries, pathname)
|
||||||
|
}
|
||||||
|
|
||||||
|
return entries
|
||||||
|
}
|
||||||
|
|
||||||
|
type watch struct {
|
||||||
|
wd uint32 // Watch descriptor (as returned by the inotify_add_watch() syscall)
|
||||||
|
flags uint32 // inotify flags of this watch (see inotify(7) for the list of valid flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
// readEvents reads from the inotify file descriptor, converts the
|
||||||
|
// received events into Event objects and sends them via the Events channel
|
||||||
|
func (w *Watcher) readEvents() {
|
||||||
|
var (
|
||||||
|
buf [unix.SizeofInotifyEvent * 4096]byte // Buffer for a maximum of 4096 raw events
|
||||||
|
n int // Number of bytes read with read()
|
||||||
|
errno error // Syscall errno
|
||||||
|
ok bool // For poller.wait
|
||||||
|
)
|
||||||
|
|
||||||
|
defer close(w.doneResp)
|
||||||
|
defer close(w.Errors)
|
||||||
|
defer close(w.Events)
|
||||||
|
defer unix.Close(w.fd)
|
||||||
|
defer w.poller.close()
|
||||||
|
|
||||||
|
for {
|
||||||
|
// See if we have been closed.
|
||||||
|
if w.isClosed() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ok, errno = w.poller.wait()
|
||||||
|
if errno != nil {
|
||||||
|
select {
|
||||||
|
case w.Errors <- errno:
|
||||||
|
case <-w.done:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
n, errno = unix.Read(w.fd, buf[:])
|
||||||
|
// If a signal interrupted execution, see if we've been asked to close, and try again.
|
||||||
|
// http://man7.org/linux/man-pages/man7/signal.7.html :
|
||||||
|
// "Before Linux 3.8, reads from an inotify(7) file descriptor were not restartable"
|
||||||
|
if errno == unix.EINTR {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// unix.Read might have been woken up by Close. If so, we're done.
|
||||||
|
if w.isClosed() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if n < unix.SizeofInotifyEvent {
|
||||||
|
var err error
|
||||||
|
if n == 0 {
|
||||||
|
// If EOF is received. This should really never happen.
|
||||||
|
err = io.EOF
|
||||||
|
} else if n < 0 {
|
||||||
|
// If an error occurred while reading.
|
||||||
|
err = errno
|
||||||
|
} else {
|
||||||
|
// Read was too short.
|
||||||
|
err = errors.New("notify: short read in readEvents()")
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case w.Errors <- err:
|
||||||
|
case <-w.done:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var offset uint32
|
||||||
|
// We don't know how many events we just read into the buffer
|
||||||
|
// While the offset points to at least one whole event...
|
||||||
|
for offset <= uint32(n-unix.SizeofInotifyEvent) {
|
||||||
|
// Point "raw" to the event in the buffer
|
||||||
|
raw := (*unix.InotifyEvent)(unsafe.Pointer(&buf[offset]))
|
||||||
|
|
||||||
|
mask := uint32(raw.Mask)
|
||||||
|
nameLen := uint32(raw.Len)
|
||||||
|
|
||||||
|
if mask&unix.IN_Q_OVERFLOW != 0 {
|
||||||
|
select {
|
||||||
|
case w.Errors <- ErrEventOverflow:
|
||||||
|
case <-w.done:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the event happened to the watched directory or the watched file, the kernel
|
||||||
|
// doesn't append the filename to the event, but we would like to always fill the
|
||||||
|
// the "Name" field with a valid filename. We retrieve the path of the watch from
|
||||||
|
// the "paths" map.
|
||||||
|
w.mu.Lock()
|
||||||
|
name, ok := w.paths[int(raw.Wd)]
|
||||||
|
// IN_DELETE_SELF occurs when the file/directory being watched is removed.
|
||||||
|
// This is a sign to clean up the maps, otherwise we are no longer in sync
|
||||||
|
// with the inotify kernel state which has already deleted the watch
|
||||||
|
// automatically.
|
||||||
|
if ok && mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF {
|
||||||
|
delete(w.paths, int(raw.Wd))
|
||||||
|
delete(w.watches, name)
|
||||||
|
}
|
||||||
|
w.mu.Unlock()
|
||||||
|
|
||||||
|
if nameLen > 0 {
|
||||||
|
// Point "bytes" at the first byte of the filename
|
||||||
|
bytes := (*[unix.PathMax]byte)(unsafe.Pointer(&buf[offset+unix.SizeofInotifyEvent]))[:nameLen:nameLen]
|
||||||
|
// The filename is padded with NULL bytes. TrimRight() gets rid of those.
|
||||||
|
name += "/" + strings.TrimRight(string(bytes[0:nameLen]), "\000")
|
||||||
|
}
|
||||||
|
|
||||||
|
event := newEvent(name, mask)
|
||||||
|
|
||||||
|
// Send the events that are not ignored on the events channel
|
||||||
|
if !event.ignoreLinux(mask) {
|
||||||
|
select {
|
||||||
|
case w.Events <- event:
|
||||||
|
case <-w.done:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move to the next event in the buffer
|
||||||
|
offset += unix.SizeofInotifyEvent + nameLen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Certain types of events can be "ignored" and not sent over the Events
|
||||||
|
// channel. Such as events marked ignore by the kernel, or MODIFY events
|
||||||
|
// against files that do not exist.
|
||||||
|
func (e *Event) ignoreLinux(mask uint32) bool {
|
||||||
|
// Ignore anything the inotify API says to ignore
|
||||||
|
if mask&unix.IN_IGNORED == unix.IN_IGNORED {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the event is not a DELETE or RENAME, the file must exist.
|
||||||
|
// Otherwise the event is ignored.
|
||||||
|
// *Note*: this was put in place because it was seen that a MODIFY
|
||||||
|
// event was sent after the DELETE. This ignores that MODIFY and
|
||||||
|
// assumes a DELETE will come or has come if the file doesn't exist.
|
||||||
|
if !(e.Op&Remove == Remove || e.Op&Rename == Rename) {
|
||||||
|
_, statErr := os.Lstat(e.Name)
|
||||||
|
return os.IsNotExist(statErr)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// newEvent returns an platform-independent Event based on an inotify mask.
|
||||||
|
func newEvent(name string, mask uint32) Event {
|
||||||
|
e := Event{Name: name}
|
||||||
|
if mask&unix.IN_CREATE == unix.IN_CREATE || mask&unix.IN_MOVED_TO == unix.IN_MOVED_TO {
|
||||||
|
e.Op |= Create
|
||||||
|
}
|
||||||
|
if mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF || mask&unix.IN_DELETE == unix.IN_DELETE {
|
||||||
|
e.Op |= Remove
|
||||||
|
}
|
||||||
|
if mask&unix.IN_MODIFY == unix.IN_MODIFY {
|
||||||
|
e.Op |= Write
|
||||||
|
}
|
||||||
|
if mask&unix.IN_MOVE_SELF == unix.IN_MOVE_SELF || mask&unix.IN_MOVED_FROM == unix.IN_MOVED_FROM {
|
||||||
|
e.Op |= Rename
|
||||||
|
}
|
||||||
|
if mask&unix.IN_ATTRIB == unix.IN_ATTRIB {
|
||||||
|
e.Op |= Chmod
|
||||||
|
}
|
||||||
|
return e
|
||||||
|
}
|
187
vendor/github.com/fsnotify/fsnotify/inotify_poller.go
generated
vendored
Normal file
187
vendor/github.com/fsnotify/fsnotify/inotify_poller.go
generated
vendored
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
// Copyright 2015 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build linux
|
||||||
|
// +build linux
|
||||||
|
|
||||||
|
package fsnotify
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
type fdPoller struct {
|
||||||
|
fd int // File descriptor (as returned by the inotify_init() syscall)
|
||||||
|
epfd int // Epoll file descriptor
|
||||||
|
pipe [2]int // Pipe for waking up
|
||||||
|
}
|
||||||
|
|
||||||
|
func emptyPoller(fd int) *fdPoller {
|
||||||
|
poller := new(fdPoller)
|
||||||
|
poller.fd = fd
|
||||||
|
poller.epfd = -1
|
||||||
|
poller.pipe[0] = -1
|
||||||
|
poller.pipe[1] = -1
|
||||||
|
return poller
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new inotify poller.
|
||||||
|
// This creates an inotify handler, and an epoll handler.
|
||||||
|
func newFdPoller(fd int) (*fdPoller, error) {
|
||||||
|
var errno error
|
||||||
|
poller := emptyPoller(fd)
|
||||||
|
defer func() {
|
||||||
|
if errno != nil {
|
||||||
|
poller.close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Create epoll fd
|
||||||
|
poller.epfd, errno = unix.EpollCreate1(unix.EPOLL_CLOEXEC)
|
||||||
|
if poller.epfd == -1 {
|
||||||
|
return nil, errno
|
||||||
|
}
|
||||||
|
// Create pipe; pipe[0] is the read end, pipe[1] the write end.
|
||||||
|
errno = unix.Pipe2(poller.pipe[:], unix.O_NONBLOCK|unix.O_CLOEXEC)
|
||||||
|
if errno != nil {
|
||||||
|
return nil, errno
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register inotify fd with epoll
|
||||||
|
event := unix.EpollEvent{
|
||||||
|
Fd: int32(poller.fd),
|
||||||
|
Events: unix.EPOLLIN,
|
||||||
|
}
|
||||||
|
errno = unix.EpollCtl(poller.epfd, unix.EPOLL_CTL_ADD, poller.fd, &event)
|
||||||
|
if errno != nil {
|
||||||
|
return nil, errno
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register pipe fd with epoll
|
||||||
|
event = unix.EpollEvent{
|
||||||
|
Fd: int32(poller.pipe[0]),
|
||||||
|
Events: unix.EPOLLIN,
|
||||||
|
}
|
||||||
|
errno = unix.EpollCtl(poller.epfd, unix.EPOLL_CTL_ADD, poller.pipe[0], &event)
|
||||||
|
if errno != nil {
|
||||||
|
return nil, errno
|
||||||
|
}
|
||||||
|
|
||||||
|
return poller, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait using epoll.
|
||||||
|
// Returns true if something is ready to be read,
|
||||||
|
// false if there is not.
|
||||||
|
func (poller *fdPoller) wait() (bool, error) {
|
||||||
|
// 3 possible events per fd, and 2 fds, makes a maximum of 6 events.
|
||||||
|
// I don't know whether epoll_wait returns the number of events returned,
|
||||||
|
// or the total number of events ready.
|
||||||
|
// I decided to catch both by making the buffer one larger than the maximum.
|
||||||
|
events := make([]unix.EpollEvent, 7)
|
||||||
|
for {
|
||||||
|
n, errno := unix.EpollWait(poller.epfd, events, -1)
|
||||||
|
if n == -1 {
|
||||||
|
if errno == unix.EINTR {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return false, errno
|
||||||
|
}
|
||||||
|
if n == 0 {
|
||||||
|
// If there are no events, try again.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if n > 6 {
|
||||||
|
// This should never happen. More events were returned than should be possible.
|
||||||
|
return false, errors.New("epoll_wait returned more events than I know what to do with")
|
||||||
|
}
|
||||||
|
ready := events[:n]
|
||||||
|
epollhup := false
|
||||||
|
epollerr := false
|
||||||
|
epollin := false
|
||||||
|
for _, event := range ready {
|
||||||
|
if event.Fd == int32(poller.fd) {
|
||||||
|
if event.Events&unix.EPOLLHUP != 0 {
|
||||||
|
// This should not happen, but if it does, treat it as a wakeup.
|
||||||
|
epollhup = true
|
||||||
|
}
|
||||||
|
if event.Events&unix.EPOLLERR != 0 {
|
||||||
|
// If an error is waiting on the file descriptor, we should pretend
|
||||||
|
// something is ready to read, and let unix.Read pick up the error.
|
||||||
|
epollerr = true
|
||||||
|
}
|
||||||
|
if event.Events&unix.EPOLLIN != 0 {
|
||||||
|
// There is data to read.
|
||||||
|
epollin = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if event.Fd == int32(poller.pipe[0]) {
|
||||||
|
if event.Events&unix.EPOLLHUP != 0 {
|
||||||
|
// Write pipe descriptor was closed, by us. This means we're closing down the
|
||||||
|
// watcher, and we should wake up.
|
||||||
|
}
|
||||||
|
if event.Events&unix.EPOLLERR != 0 {
|
||||||
|
// If an error is waiting on the pipe file descriptor.
|
||||||
|
// This is an absolute mystery, and should never ever happen.
|
||||||
|
return false, errors.New("Error on the pipe descriptor.")
|
||||||
|
}
|
||||||
|
if event.Events&unix.EPOLLIN != 0 {
|
||||||
|
// This is a regular wakeup, so we have to clear the buffer.
|
||||||
|
err := poller.clearWake()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if epollhup || epollerr || epollin {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close the write end of the poller.
|
||||||
|
func (poller *fdPoller) wake() error {
|
||||||
|
buf := make([]byte, 1)
|
||||||
|
n, errno := unix.Write(poller.pipe[1], buf)
|
||||||
|
if n == -1 {
|
||||||
|
if errno == unix.EAGAIN {
|
||||||
|
// Buffer is full, poller will wake.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return errno
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (poller *fdPoller) clearWake() error {
|
||||||
|
// You have to be woken up a LOT in order to get to 100!
|
||||||
|
buf := make([]byte, 100)
|
||||||
|
n, errno := unix.Read(poller.pipe[0], buf)
|
||||||
|
if n == -1 {
|
||||||
|
if errno == unix.EAGAIN {
|
||||||
|
// Buffer is empty, someone else cleared our wake.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return errno
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close all poller file descriptors, but not the one passed to it.
|
||||||
|
func (poller *fdPoller) close() {
|
||||||
|
if poller.pipe[1] != -1 {
|
||||||
|
unix.Close(poller.pipe[1])
|
||||||
|
}
|
||||||
|
if poller.pipe[0] != -1 {
|
||||||
|
unix.Close(poller.pipe[0])
|
||||||
|
}
|
||||||
|
if poller.epfd != -1 {
|
||||||
|
unix.Close(poller.epfd)
|
||||||
|
}
|
||||||
|
}
|
535
vendor/github.com/fsnotify/fsnotify/kqueue.go
generated
vendored
Normal file
535
vendor/github.com/fsnotify/fsnotify/kqueue.go
generated
vendored
Normal file
@ -0,0 +1,535 @@
|
|||||||
|
// Copyright 2010 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build freebsd || openbsd || netbsd || dragonfly || darwin
|
||||||
|
// +build freebsd openbsd netbsd dragonfly darwin
|
||||||
|
|
||||||
|
package fsnotify
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Watcher watches a set of files, delivering events to a channel.
|
||||||
|
type Watcher struct {
|
||||||
|
Events chan Event
|
||||||
|
Errors chan error
|
||||||
|
done chan struct{} // Channel for sending a "quit message" to the reader goroutine
|
||||||
|
|
||||||
|
kq int // File descriptor (as returned by the kqueue() syscall).
|
||||||
|
|
||||||
|
mu sync.Mutex // Protects access to watcher data
|
||||||
|
watches map[string]int // Map of watched file descriptors (key: path).
|
||||||
|
externalWatches map[string]bool // Map of watches added by user of the library.
|
||||||
|
dirFlags map[string]uint32 // Map of watched directories to fflags used in kqueue.
|
||||||
|
paths map[int]pathInfo // Map file descriptors to path names for processing kqueue events.
|
||||||
|
fileExists map[string]bool // Keep track of if we know this file exists (to stop duplicate create events).
|
||||||
|
isClosed bool // Set to true when Close() is first called
|
||||||
|
}
|
||||||
|
|
||||||
|
type pathInfo struct {
|
||||||
|
name string
|
||||||
|
isDir bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
|
||||||
|
func NewWatcher() (*Watcher, error) {
|
||||||
|
kq, err := kqueue()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
w := &Watcher{
|
||||||
|
kq: kq,
|
||||||
|
watches: make(map[string]int),
|
||||||
|
dirFlags: make(map[string]uint32),
|
||||||
|
paths: make(map[int]pathInfo),
|
||||||
|
fileExists: make(map[string]bool),
|
||||||
|
externalWatches: make(map[string]bool),
|
||||||
|
Events: make(chan Event),
|
||||||
|
Errors: make(chan error),
|
||||||
|
done: make(chan struct{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
go w.readEvents()
|
||||||
|
return w, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close removes all watches and closes the events channel.
|
||||||
|
func (w *Watcher) Close() error {
|
||||||
|
w.mu.Lock()
|
||||||
|
if w.isClosed {
|
||||||
|
w.mu.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
w.isClosed = true
|
||||||
|
|
||||||
|
// copy paths to remove while locked
|
||||||
|
var pathsToRemove = make([]string, 0, len(w.watches))
|
||||||
|
for name := range w.watches {
|
||||||
|
pathsToRemove = append(pathsToRemove, name)
|
||||||
|
}
|
||||||
|
w.mu.Unlock()
|
||||||
|
// unlock before calling Remove, which also locks
|
||||||
|
|
||||||
|
for _, name := range pathsToRemove {
|
||||||
|
w.Remove(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// send a "quit" message to the reader goroutine
|
||||||
|
close(w.done)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add starts watching the named file or directory (non-recursively).
|
||||||
|
func (w *Watcher) Add(name string) error {
|
||||||
|
w.mu.Lock()
|
||||||
|
w.externalWatches[name] = true
|
||||||
|
w.mu.Unlock()
|
||||||
|
_, err := w.addWatch(name, noteAllEvents)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove stops watching the the named file or directory (non-recursively).
|
||||||
|
func (w *Watcher) Remove(name string) error {
|
||||||
|
name = filepath.Clean(name)
|
||||||
|
w.mu.Lock()
|
||||||
|
watchfd, ok := w.watches[name]
|
||||||
|
w.mu.Unlock()
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("can't remove non-existent kevent watch for: %s", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
const registerRemove = unix.EV_DELETE
|
||||||
|
if err := register(w.kq, []int{watchfd}, registerRemove, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
unix.Close(watchfd)
|
||||||
|
|
||||||
|
w.mu.Lock()
|
||||||
|
isDir := w.paths[watchfd].isDir
|
||||||
|
delete(w.watches, name)
|
||||||
|
delete(w.paths, watchfd)
|
||||||
|
delete(w.dirFlags, name)
|
||||||
|
w.mu.Unlock()
|
||||||
|
|
||||||
|
// Find all watched paths that are in this directory that are not external.
|
||||||
|
if isDir {
|
||||||
|
var pathsToRemove []string
|
||||||
|
w.mu.Lock()
|
||||||
|
for _, path := range w.paths {
|
||||||
|
wdir, _ := filepath.Split(path.name)
|
||||||
|
if filepath.Clean(wdir) == name {
|
||||||
|
if !w.externalWatches[path.name] {
|
||||||
|
pathsToRemove = append(pathsToRemove, path.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.mu.Unlock()
|
||||||
|
for _, name := range pathsToRemove {
|
||||||
|
// Since these are internal, not much sense in propagating error
|
||||||
|
// to the user, as that will just confuse them with an error about
|
||||||
|
// a path they did not explicitly watch themselves.
|
||||||
|
w.Remove(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WatchList returns the directories and files that are being monitered.
|
||||||
|
func (w *Watcher) WatchList() []string {
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
|
||||||
|
entries := make([]string, 0, len(w.watches))
|
||||||
|
for pathname := range w.watches {
|
||||||
|
entries = append(entries, pathname)
|
||||||
|
}
|
||||||
|
|
||||||
|
return entries
|
||||||
|
}
|
||||||
|
|
||||||
|
// Watch all events (except NOTE_EXTEND, NOTE_LINK, NOTE_REVOKE)
|
||||||
|
const noteAllEvents = unix.NOTE_DELETE | unix.NOTE_WRITE | unix.NOTE_ATTRIB | unix.NOTE_RENAME
|
||||||
|
|
||||||
|
// keventWaitTime to block on each read from kevent
|
||||||
|
var keventWaitTime = durationToTimespec(100 * time.Millisecond)
|
||||||
|
|
||||||
|
// addWatch adds name to the watched file set.
|
||||||
|
// The flags are interpreted as described in kevent(2).
|
||||||
|
// Returns the real path to the file which was added, if any, which may be different from the one passed in the case of symlinks.
|
||||||
|
func (w *Watcher) addWatch(name string, flags uint32) (string, error) {
|
||||||
|
var isDir bool
|
||||||
|
// Make ./name and name equivalent
|
||||||
|
name = filepath.Clean(name)
|
||||||
|
|
||||||
|
w.mu.Lock()
|
||||||
|
if w.isClosed {
|
||||||
|
w.mu.Unlock()
|
||||||
|
return "", errors.New("kevent instance already closed")
|
||||||
|
}
|
||||||
|
watchfd, alreadyWatching := w.watches[name]
|
||||||
|
// We already have a watch, but we can still override flags.
|
||||||
|
if alreadyWatching {
|
||||||
|
isDir = w.paths[watchfd].isDir
|
||||||
|
}
|
||||||
|
w.mu.Unlock()
|
||||||
|
|
||||||
|
if !alreadyWatching {
|
||||||
|
fi, err := os.Lstat(name)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't watch sockets.
|
||||||
|
if fi.Mode()&os.ModeSocket == os.ModeSocket {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't watch named pipes.
|
||||||
|
if fi.Mode()&os.ModeNamedPipe == os.ModeNamedPipe {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Follow Symlinks
|
||||||
|
// Unfortunately, Linux can add bogus symlinks to watch list without
|
||||||
|
// issue, and Windows can't do symlinks period (AFAIK). To maintain
|
||||||
|
// consistency, we will act like everything is fine. There will simply
|
||||||
|
// be no file events for broken symlinks.
|
||||||
|
// Hence the returns of nil on errors.
|
||||||
|
if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
|
||||||
|
name, err = filepath.EvalSymlinks(name)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
w.mu.Lock()
|
||||||
|
_, alreadyWatching = w.watches[name]
|
||||||
|
w.mu.Unlock()
|
||||||
|
|
||||||
|
if alreadyWatching {
|
||||||
|
return name, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
fi, err = os.Lstat(name)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watchfd, err = unix.Open(name, openMode, 0700)
|
||||||
|
if watchfd == -1 {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
isDir = fi.IsDir()
|
||||||
|
}
|
||||||
|
|
||||||
|
const registerAdd = unix.EV_ADD | unix.EV_CLEAR | unix.EV_ENABLE
|
||||||
|
if err := register(w.kq, []int{watchfd}, registerAdd, flags); err != nil {
|
||||||
|
unix.Close(watchfd)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !alreadyWatching {
|
||||||
|
w.mu.Lock()
|
||||||
|
w.watches[name] = watchfd
|
||||||
|
w.paths[watchfd] = pathInfo{name: name, isDir: isDir}
|
||||||
|
w.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
if isDir {
|
||||||
|
// Watch the directory if it has not been watched before,
|
||||||
|
// or if it was watched before, but perhaps only a NOTE_DELETE (watchDirectoryFiles)
|
||||||
|
w.mu.Lock()
|
||||||
|
|
||||||
|
watchDir := (flags&unix.NOTE_WRITE) == unix.NOTE_WRITE &&
|
||||||
|
(!alreadyWatching || (w.dirFlags[name]&unix.NOTE_WRITE) != unix.NOTE_WRITE)
|
||||||
|
// Store flags so this watch can be updated later
|
||||||
|
w.dirFlags[name] = flags
|
||||||
|
w.mu.Unlock()
|
||||||
|
|
||||||
|
if watchDir {
|
||||||
|
if err := w.watchDirectoryFiles(name); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return name, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// readEvents reads from kqueue and converts the received kevents into
|
||||||
|
// Event values that it sends down the Events channel.
|
||||||
|
func (w *Watcher) readEvents() {
|
||||||
|
eventBuffer := make([]unix.Kevent_t, 10)
|
||||||
|
|
||||||
|
loop:
|
||||||
|
for {
|
||||||
|
// See if there is a message on the "done" channel
|
||||||
|
select {
|
||||||
|
case <-w.done:
|
||||||
|
break loop
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get new events
|
||||||
|
kevents, err := read(w.kq, eventBuffer, &keventWaitTime)
|
||||||
|
// EINTR is okay, the syscall was interrupted before timeout expired.
|
||||||
|
if err != nil && err != unix.EINTR {
|
||||||
|
select {
|
||||||
|
case w.Errors <- err:
|
||||||
|
case <-w.done:
|
||||||
|
break loop
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush the events we received to the Events channel
|
||||||
|
for len(kevents) > 0 {
|
||||||
|
kevent := &kevents[0]
|
||||||
|
watchfd := int(kevent.Ident)
|
||||||
|
mask := uint32(kevent.Fflags)
|
||||||
|
w.mu.Lock()
|
||||||
|
path := w.paths[watchfd]
|
||||||
|
w.mu.Unlock()
|
||||||
|
event := newEvent(path.name, mask)
|
||||||
|
|
||||||
|
if path.isDir && !(event.Op&Remove == Remove) {
|
||||||
|
// Double check to make sure the directory exists. This can happen when
|
||||||
|
// we do a rm -fr on a recursively watched folders and we receive a
|
||||||
|
// modification event first but the folder has been deleted and later
|
||||||
|
// receive the delete event
|
||||||
|
if _, err := os.Lstat(event.Name); os.IsNotExist(err) {
|
||||||
|
// mark is as delete event
|
||||||
|
event.Op |= Remove
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if event.Op&Rename == Rename || event.Op&Remove == Remove {
|
||||||
|
w.Remove(event.Name)
|
||||||
|
w.mu.Lock()
|
||||||
|
delete(w.fileExists, event.Name)
|
||||||
|
w.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
if path.isDir && event.Op&Write == Write && !(event.Op&Remove == Remove) {
|
||||||
|
w.sendDirectoryChangeEvents(event.Name)
|
||||||
|
} else {
|
||||||
|
// Send the event on the Events channel.
|
||||||
|
select {
|
||||||
|
case w.Events <- event:
|
||||||
|
case <-w.done:
|
||||||
|
break loop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if event.Op&Remove == Remove {
|
||||||
|
// Look for a file that may have overwritten this.
|
||||||
|
// For example, mv f1 f2 will delete f2, then create f2.
|
||||||
|
if path.isDir {
|
||||||
|
fileDir := filepath.Clean(event.Name)
|
||||||
|
w.mu.Lock()
|
||||||
|
_, found := w.watches[fileDir]
|
||||||
|
w.mu.Unlock()
|
||||||
|
if found {
|
||||||
|
// make sure the directory exists before we watch for changes. When we
|
||||||
|
// do a recursive watch and perform rm -fr, the parent directory might
|
||||||
|
// have gone missing, ignore the missing directory and let the
|
||||||
|
// upcoming delete event remove the watch from the parent directory.
|
||||||
|
if _, err := os.Lstat(fileDir); err == nil {
|
||||||
|
w.sendDirectoryChangeEvents(fileDir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
filePath := filepath.Clean(event.Name)
|
||||||
|
if fileInfo, err := os.Lstat(filePath); err == nil {
|
||||||
|
w.sendFileCreatedEventIfNew(filePath, fileInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move to next event
|
||||||
|
kevents = kevents[1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// cleanup
|
||||||
|
err := unix.Close(w.kq)
|
||||||
|
if err != nil {
|
||||||
|
// only way the previous loop breaks is if w.done was closed so we need to async send to w.Errors.
|
||||||
|
select {
|
||||||
|
case w.Errors <- err:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
close(w.Events)
|
||||||
|
close(w.Errors)
|
||||||
|
}
|
||||||
|
|
||||||
|
// newEvent returns an platform-independent Event based on kqueue Fflags.
|
||||||
|
func newEvent(name string, mask uint32) Event {
|
||||||
|
e := Event{Name: name}
|
||||||
|
if mask&unix.NOTE_DELETE == unix.NOTE_DELETE {
|
||||||
|
e.Op |= Remove
|
||||||
|
}
|
||||||
|
if mask&unix.NOTE_WRITE == unix.NOTE_WRITE {
|
||||||
|
e.Op |= Write
|
||||||
|
}
|
||||||
|
if mask&unix.NOTE_RENAME == unix.NOTE_RENAME {
|
||||||
|
e.Op |= Rename
|
||||||
|
}
|
||||||
|
if mask&unix.NOTE_ATTRIB == unix.NOTE_ATTRIB {
|
||||||
|
e.Op |= Chmod
|
||||||
|
}
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCreateEvent(name string) Event {
|
||||||
|
return Event{Name: name, Op: Create}
|
||||||
|
}
|
||||||
|
|
||||||
|
// watchDirectoryFiles to mimic inotify when adding a watch on a directory
|
||||||
|
func (w *Watcher) watchDirectoryFiles(dirPath string) error {
|
||||||
|
// Get all files
|
||||||
|
files, err := ioutil.ReadDir(dirPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, fileInfo := range files {
|
||||||
|
filePath := filepath.Join(dirPath, fileInfo.Name())
|
||||||
|
filePath, err = w.internalWatch(filePath, fileInfo)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
w.mu.Lock()
|
||||||
|
w.fileExists[filePath] = true
|
||||||
|
w.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendDirectoryEvents searches the directory for newly created files
|
||||||
|
// and sends them over the event channel. This functionality is to have
|
||||||
|
// the BSD version of fsnotify match Linux inotify which provides a
|
||||||
|
// create event for files created in a watched directory.
|
||||||
|
func (w *Watcher) sendDirectoryChangeEvents(dirPath string) {
|
||||||
|
// Get all files
|
||||||
|
files, err := ioutil.ReadDir(dirPath)
|
||||||
|
if err != nil {
|
||||||
|
select {
|
||||||
|
case w.Errors <- err:
|
||||||
|
case <-w.done:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search for new files
|
||||||
|
for _, fileInfo := range files {
|
||||||
|
filePath := filepath.Join(dirPath, fileInfo.Name())
|
||||||
|
err := w.sendFileCreatedEventIfNew(filePath, fileInfo)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendFileCreatedEvent sends a create event if the file isn't already being tracked.
|
||||||
|
func (w *Watcher) sendFileCreatedEventIfNew(filePath string, fileInfo os.FileInfo) (err error) {
|
||||||
|
w.mu.Lock()
|
||||||
|
_, doesExist := w.fileExists[filePath]
|
||||||
|
w.mu.Unlock()
|
||||||
|
if !doesExist {
|
||||||
|
// Send create event
|
||||||
|
select {
|
||||||
|
case w.Events <- newCreateEvent(filePath):
|
||||||
|
case <-w.done:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// like watchDirectoryFiles (but without doing another ReadDir)
|
||||||
|
filePath, err = w.internalWatch(filePath, fileInfo)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
w.mu.Lock()
|
||||||
|
w.fileExists[filePath] = true
|
||||||
|
w.mu.Unlock()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Watcher) internalWatch(name string, fileInfo os.FileInfo) (string, error) {
|
||||||
|
if fileInfo.IsDir() {
|
||||||
|
// mimic Linux providing delete events for subdirectories
|
||||||
|
// but preserve the flags used if currently watching subdirectory
|
||||||
|
w.mu.Lock()
|
||||||
|
flags := w.dirFlags[name]
|
||||||
|
w.mu.Unlock()
|
||||||
|
|
||||||
|
flags |= unix.NOTE_DELETE | unix.NOTE_RENAME
|
||||||
|
return w.addWatch(name, flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
// watch file to mimic Linux inotify
|
||||||
|
return w.addWatch(name, noteAllEvents)
|
||||||
|
}
|
||||||
|
|
||||||
|
// kqueue creates a new kernel event queue and returns a descriptor.
|
||||||
|
func kqueue() (kq int, err error) {
|
||||||
|
kq, err = unix.Kqueue()
|
||||||
|
if kq == -1 {
|
||||||
|
return kq, err
|
||||||
|
}
|
||||||
|
return kq, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// register events with the queue
|
||||||
|
func register(kq int, fds []int, flags int, fflags uint32) error {
|
||||||
|
changes := make([]unix.Kevent_t, len(fds))
|
||||||
|
|
||||||
|
for i, fd := range fds {
|
||||||
|
// SetKevent converts int to the platform-specific types:
|
||||||
|
unix.SetKevent(&changes[i], fd, unix.EVFILT_VNODE, flags)
|
||||||
|
changes[i].Fflags = fflags
|
||||||
|
}
|
||||||
|
|
||||||
|
// register the events
|
||||||
|
success, err := unix.Kevent(kq, changes, nil, nil)
|
||||||
|
if success == -1 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// read retrieves pending events, or waits until an event occurs.
|
||||||
|
// A timeout of nil blocks indefinitely, while 0 polls the queue.
|
||||||
|
func read(kq int, events []unix.Kevent_t, timeout *unix.Timespec) ([]unix.Kevent_t, error) {
|
||||||
|
n, err := unix.Kevent(kq, nil, events, timeout)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return events[0:n], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// durationToTimespec prepares a timeout value
|
||||||
|
func durationToTimespec(d time.Duration) unix.Timespec {
|
||||||
|
return unix.NsecToTimespec(d.Nanoseconds())
|
||||||
|
}
|
12
vendor/github.com/fsnotify/fsnotify/open_mode_bsd.go
generated
vendored
Normal file
12
vendor/github.com/fsnotify/fsnotify/open_mode_bsd.go
generated
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// Copyright 2013 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build freebsd || openbsd || netbsd || dragonfly
|
||||||
|
// +build freebsd openbsd netbsd dragonfly
|
||||||
|
|
||||||
|
package fsnotify
|
||||||
|
|
||||||
|
import "golang.org/x/sys/unix"
|
||||||
|
|
||||||
|
const openMode = unix.O_NONBLOCK | unix.O_RDONLY | unix.O_CLOEXEC
|
13
vendor/github.com/fsnotify/fsnotify/open_mode_darwin.go
generated
vendored
Normal file
13
vendor/github.com/fsnotify/fsnotify/open_mode_darwin.go
generated
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// Copyright 2013 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build darwin
|
||||||
|
// +build darwin
|
||||||
|
|
||||||
|
package fsnotify
|
||||||
|
|
||||||
|
import "golang.org/x/sys/unix"
|
||||||
|
|
||||||
|
// note: this constant is not defined on BSD
|
||||||
|
const openMode = unix.O_EVTONLY | unix.O_CLOEXEC
|
586
vendor/github.com/fsnotify/fsnotify/windows.go
generated
vendored
Normal file
586
vendor/github.com/fsnotify/fsnotify/windows.go
generated
vendored
Normal file
@ -0,0 +1,586 @@
|
|||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build windows
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package fsnotify
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
|
"runtime"
|
||||||
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Watcher watches a set of files, delivering events to a channel.
|
||||||
|
type Watcher struct {
|
||||||
|
Events chan Event
|
||||||
|
Errors chan error
|
||||||
|
isClosed bool // Set to true when Close() is first called
|
||||||
|
mu sync.Mutex // Map access
|
||||||
|
port syscall.Handle // Handle to completion port
|
||||||
|
watches watchMap // Map of watches (key: i-number)
|
||||||
|
input chan *input // Inputs to the reader are sent on this channel
|
||||||
|
quit chan chan<- error
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
|
||||||
|
func NewWatcher() (*Watcher, error) {
|
||||||
|
port, e := syscall.CreateIoCompletionPort(syscall.InvalidHandle, 0, 0, 0)
|
||||||
|
if e != nil {
|
||||||
|
return nil, os.NewSyscallError("CreateIoCompletionPort", e)
|
||||||
|
}
|
||||||
|
w := &Watcher{
|
||||||
|
port: port,
|
||||||
|
watches: make(watchMap),
|
||||||
|
input: make(chan *input, 1),
|
||||||
|
Events: make(chan Event, 50),
|
||||||
|
Errors: make(chan error),
|
||||||
|
quit: make(chan chan<- error, 1),
|
||||||
|
}
|
||||||
|
go w.readEvents()
|
||||||
|
return w, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close removes all watches and closes the events channel.
|
||||||
|
func (w *Watcher) Close() error {
|
||||||
|
if w.isClosed {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
w.isClosed = true
|
||||||
|
|
||||||
|
// Send "quit" message to the reader goroutine
|
||||||
|
ch := make(chan error)
|
||||||
|
w.quit <- ch
|
||||||
|
if err := w.wakeupReader(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return <-ch
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add starts watching the named file or directory (non-recursively).
|
||||||
|
func (w *Watcher) Add(name string) error {
|
||||||
|
if w.isClosed {
|
||||||
|
return errors.New("watcher already closed")
|
||||||
|
}
|
||||||
|
in := &input{
|
||||||
|
op: opAddWatch,
|
||||||
|
path: filepath.Clean(name),
|
||||||
|
flags: sysFSALLEVENTS,
|
||||||
|
reply: make(chan error),
|
||||||
|
}
|
||||||
|
w.input <- in
|
||||||
|
if err := w.wakeupReader(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return <-in.reply
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove stops watching the the named file or directory (non-recursively).
|
||||||
|
func (w *Watcher) Remove(name string) error {
|
||||||
|
in := &input{
|
||||||
|
op: opRemoveWatch,
|
||||||
|
path: filepath.Clean(name),
|
||||||
|
reply: make(chan error),
|
||||||
|
}
|
||||||
|
w.input <- in
|
||||||
|
if err := w.wakeupReader(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return <-in.reply
|
||||||
|
}
|
||||||
|
|
||||||
|
// WatchList returns the directories and files that are being monitered.
|
||||||
|
func (w *Watcher) WatchList() []string {
|
||||||
|
w.mu.Lock()
|
||||||
|
defer w.mu.Unlock()
|
||||||
|
|
||||||
|
entries := make([]string, 0, len(w.watches))
|
||||||
|
for _, entry := range w.watches {
|
||||||
|
for _, watchEntry := range entry {
|
||||||
|
entries = append(entries, watchEntry.path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return entries
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Options for AddWatch
|
||||||
|
sysFSONESHOT = 0x80000000
|
||||||
|
sysFSONLYDIR = 0x1000000
|
||||||
|
|
||||||
|
// Events
|
||||||
|
sysFSACCESS = 0x1
|
||||||
|
sysFSALLEVENTS = 0xfff
|
||||||
|
sysFSATTRIB = 0x4
|
||||||
|
sysFSCLOSE = 0x18
|
||||||
|
sysFSCREATE = 0x100
|
||||||
|
sysFSDELETE = 0x200
|
||||||
|
sysFSDELETESELF = 0x400
|
||||||
|
sysFSMODIFY = 0x2
|
||||||
|
sysFSMOVE = 0xc0
|
||||||
|
sysFSMOVEDFROM = 0x40
|
||||||
|
sysFSMOVEDTO = 0x80
|
||||||
|
sysFSMOVESELF = 0x800
|
||||||
|
|
||||||
|
// Special events
|
||||||
|
sysFSIGNORED = 0x8000
|
||||||
|
sysFSQOVERFLOW = 0x4000
|
||||||
|
)
|
||||||
|
|
||||||
|
func newEvent(name string, mask uint32) Event {
|
||||||
|
e := Event{Name: name}
|
||||||
|
if mask&sysFSCREATE == sysFSCREATE || mask&sysFSMOVEDTO == sysFSMOVEDTO {
|
||||||
|
e.Op |= Create
|
||||||
|
}
|
||||||
|
if mask&sysFSDELETE == sysFSDELETE || mask&sysFSDELETESELF == sysFSDELETESELF {
|
||||||
|
e.Op |= Remove
|
||||||
|
}
|
||||||
|
if mask&sysFSMODIFY == sysFSMODIFY {
|
||||||
|
e.Op |= Write
|
||||||
|
}
|
||||||
|
if mask&sysFSMOVE == sysFSMOVE || mask&sysFSMOVESELF == sysFSMOVESELF || mask&sysFSMOVEDFROM == sysFSMOVEDFROM {
|
||||||
|
e.Op |= Rename
|
||||||
|
}
|
||||||
|
if mask&sysFSATTRIB == sysFSATTRIB {
|
||||||
|
e.Op |= Chmod
|
||||||
|
}
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
opAddWatch = iota
|
||||||
|
opRemoveWatch
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
provisional uint64 = 1 << (32 + iota)
|
||||||
|
)
|
||||||
|
|
||||||
|
type input struct {
|
||||||
|
op int
|
||||||
|
path string
|
||||||
|
flags uint32
|
||||||
|
reply chan error
|
||||||
|
}
|
||||||
|
|
||||||
|
type inode struct {
|
||||||
|
handle syscall.Handle
|
||||||
|
volume uint32
|
||||||
|
index uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
type watch struct {
|
||||||
|
ov syscall.Overlapped
|
||||||
|
ino *inode // i-number
|
||||||
|
path string // Directory path
|
||||||
|
mask uint64 // Directory itself is being watched with these notify flags
|
||||||
|
names map[string]uint64 // Map of names being watched and their notify flags
|
||||||
|
rename string // Remembers the old name while renaming a file
|
||||||
|
buf [4096]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type indexMap map[uint64]*watch
|
||||||
|
type watchMap map[uint32]indexMap
|
||||||
|
|
||||||
|
func (w *Watcher) wakeupReader() error {
|
||||||
|
e := syscall.PostQueuedCompletionStatus(w.port, 0, 0, nil)
|
||||||
|
if e != nil {
|
||||||
|
return os.NewSyscallError("PostQueuedCompletionStatus", e)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDir(pathname string) (dir string, err error) {
|
||||||
|
attr, e := syscall.GetFileAttributes(syscall.StringToUTF16Ptr(pathname))
|
||||||
|
if e != nil {
|
||||||
|
return "", os.NewSyscallError("GetFileAttributes", e)
|
||||||
|
}
|
||||||
|
if attr&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 {
|
||||||
|
dir = pathname
|
||||||
|
} else {
|
||||||
|
dir, _ = filepath.Split(pathname)
|
||||||
|
dir = filepath.Clean(dir)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getIno(path string) (ino *inode, err error) {
|
||||||
|
h, e := syscall.CreateFile(syscall.StringToUTF16Ptr(path),
|
||||||
|
syscall.FILE_LIST_DIRECTORY,
|
||||||
|
syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE,
|
||||||
|
nil, syscall.OPEN_EXISTING,
|
||||||
|
syscall.FILE_FLAG_BACKUP_SEMANTICS|syscall.FILE_FLAG_OVERLAPPED, 0)
|
||||||
|
if e != nil {
|
||||||
|
return nil, os.NewSyscallError("CreateFile", e)
|
||||||
|
}
|
||||||
|
var fi syscall.ByHandleFileInformation
|
||||||
|
if e = syscall.GetFileInformationByHandle(h, &fi); e != nil {
|
||||||
|
syscall.CloseHandle(h)
|
||||||
|
return nil, os.NewSyscallError("GetFileInformationByHandle", e)
|
||||||
|
}
|
||||||
|
ino = &inode{
|
||||||
|
handle: h,
|
||||||
|
volume: fi.VolumeSerialNumber,
|
||||||
|
index: uint64(fi.FileIndexHigh)<<32 | uint64(fi.FileIndexLow),
|
||||||
|
}
|
||||||
|
return ino, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must run within the I/O thread.
|
||||||
|
func (m watchMap) get(ino *inode) *watch {
|
||||||
|
if i := m[ino.volume]; i != nil {
|
||||||
|
return i[ino.index]
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must run within the I/O thread.
|
||||||
|
func (m watchMap) set(ino *inode, watch *watch) {
|
||||||
|
i := m[ino.volume]
|
||||||
|
if i == nil {
|
||||||
|
i = make(indexMap)
|
||||||
|
m[ino.volume] = i
|
||||||
|
}
|
||||||
|
i[ino.index] = watch
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must run within the I/O thread.
|
||||||
|
func (w *Watcher) addWatch(pathname string, flags uint64) error {
|
||||||
|
dir, err := getDir(pathname)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if flags&sysFSONLYDIR != 0 && pathname != dir {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
ino, err := getIno(dir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
w.mu.Lock()
|
||||||
|
watchEntry := w.watches.get(ino)
|
||||||
|
w.mu.Unlock()
|
||||||
|
if watchEntry == nil {
|
||||||
|
if _, e := syscall.CreateIoCompletionPort(ino.handle, w.port, 0, 0); e != nil {
|
||||||
|
syscall.CloseHandle(ino.handle)
|
||||||
|
return os.NewSyscallError("CreateIoCompletionPort", e)
|
||||||
|
}
|
||||||
|
watchEntry = &watch{
|
||||||
|
ino: ino,
|
||||||
|
path: dir,
|
||||||
|
names: make(map[string]uint64),
|
||||||
|
}
|
||||||
|
w.mu.Lock()
|
||||||
|
w.watches.set(ino, watchEntry)
|
||||||
|
w.mu.Unlock()
|
||||||
|
flags |= provisional
|
||||||
|
} else {
|
||||||
|
syscall.CloseHandle(ino.handle)
|
||||||
|
}
|
||||||
|
if pathname == dir {
|
||||||
|
watchEntry.mask |= flags
|
||||||
|
} else {
|
||||||
|
watchEntry.names[filepath.Base(pathname)] |= flags
|
||||||
|
}
|
||||||
|
if err = w.startRead(watchEntry); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if pathname == dir {
|
||||||
|
watchEntry.mask &= ^provisional
|
||||||
|
} else {
|
||||||
|
watchEntry.names[filepath.Base(pathname)] &= ^provisional
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must run within the I/O thread.
|
||||||
|
func (w *Watcher) remWatch(pathname string) error {
|
||||||
|
dir, err := getDir(pathname)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ino, err := getIno(dir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
w.mu.Lock()
|
||||||
|
watch := w.watches.get(ino)
|
||||||
|
w.mu.Unlock()
|
||||||
|
if watch == nil {
|
||||||
|
return fmt.Errorf("can't remove non-existent watch for: %s", pathname)
|
||||||
|
}
|
||||||
|
if pathname == dir {
|
||||||
|
w.sendEvent(watch.path, watch.mask&sysFSIGNORED)
|
||||||
|
watch.mask = 0
|
||||||
|
} else {
|
||||||
|
name := filepath.Base(pathname)
|
||||||
|
w.sendEvent(filepath.Join(watch.path, name), watch.names[name]&sysFSIGNORED)
|
||||||
|
delete(watch.names, name)
|
||||||
|
}
|
||||||
|
return w.startRead(watch)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must run within the I/O thread.
|
||||||
|
func (w *Watcher) deleteWatch(watch *watch) {
|
||||||
|
for name, mask := range watch.names {
|
||||||
|
if mask&provisional == 0 {
|
||||||
|
w.sendEvent(filepath.Join(watch.path, name), mask&sysFSIGNORED)
|
||||||
|
}
|
||||||
|
delete(watch.names, name)
|
||||||
|
}
|
||||||
|
if watch.mask != 0 {
|
||||||
|
if watch.mask&provisional == 0 {
|
||||||
|
w.sendEvent(watch.path, watch.mask&sysFSIGNORED)
|
||||||
|
}
|
||||||
|
watch.mask = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must run within the I/O thread.
|
||||||
|
func (w *Watcher) startRead(watch *watch) error {
|
||||||
|
if e := syscall.CancelIo(watch.ino.handle); e != nil {
|
||||||
|
w.Errors <- os.NewSyscallError("CancelIo", e)
|
||||||
|
w.deleteWatch(watch)
|
||||||
|
}
|
||||||
|
mask := toWindowsFlags(watch.mask)
|
||||||
|
for _, m := range watch.names {
|
||||||
|
mask |= toWindowsFlags(m)
|
||||||
|
}
|
||||||
|
if mask == 0 {
|
||||||
|
if e := syscall.CloseHandle(watch.ino.handle); e != nil {
|
||||||
|
w.Errors <- os.NewSyscallError("CloseHandle", e)
|
||||||
|
}
|
||||||
|
w.mu.Lock()
|
||||||
|
delete(w.watches[watch.ino.volume], watch.ino.index)
|
||||||
|
w.mu.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
e := syscall.ReadDirectoryChanges(watch.ino.handle, &watch.buf[0],
|
||||||
|
uint32(unsafe.Sizeof(watch.buf)), false, mask, nil, &watch.ov, 0)
|
||||||
|
if e != nil {
|
||||||
|
err := os.NewSyscallError("ReadDirectoryChanges", e)
|
||||||
|
if e == syscall.ERROR_ACCESS_DENIED && watch.mask&provisional == 0 {
|
||||||
|
// Watched directory was probably removed
|
||||||
|
if w.sendEvent(watch.path, watch.mask&sysFSDELETESELF) {
|
||||||
|
if watch.mask&sysFSONESHOT != 0 {
|
||||||
|
watch.mask = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
w.deleteWatch(watch)
|
||||||
|
w.startRead(watch)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// readEvents reads from the I/O completion port, converts the
|
||||||
|
// received events into Event objects and sends them via the Events channel.
|
||||||
|
// Entry point to the I/O thread.
|
||||||
|
func (w *Watcher) readEvents() {
|
||||||
|
var (
|
||||||
|
n, key uint32
|
||||||
|
ov *syscall.Overlapped
|
||||||
|
)
|
||||||
|
runtime.LockOSThread()
|
||||||
|
|
||||||
|
for {
|
||||||
|
e := syscall.GetQueuedCompletionStatus(w.port, &n, &key, &ov, syscall.INFINITE)
|
||||||
|
watch := (*watch)(unsafe.Pointer(ov))
|
||||||
|
|
||||||
|
if watch == nil {
|
||||||
|
select {
|
||||||
|
case ch := <-w.quit:
|
||||||
|
w.mu.Lock()
|
||||||
|
var indexes []indexMap
|
||||||
|
for _, index := range w.watches {
|
||||||
|
indexes = append(indexes, index)
|
||||||
|
}
|
||||||
|
w.mu.Unlock()
|
||||||
|
for _, index := range indexes {
|
||||||
|
for _, watch := range index {
|
||||||
|
w.deleteWatch(watch)
|
||||||
|
w.startRead(watch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
if e := syscall.CloseHandle(w.port); e != nil {
|
||||||
|
err = os.NewSyscallError("CloseHandle", e)
|
||||||
|
}
|
||||||
|
close(w.Events)
|
||||||
|
close(w.Errors)
|
||||||
|
ch <- err
|
||||||
|
return
|
||||||
|
case in := <-w.input:
|
||||||
|
switch in.op {
|
||||||
|
case opAddWatch:
|
||||||
|
in.reply <- w.addWatch(in.path, uint64(in.flags))
|
||||||
|
case opRemoveWatch:
|
||||||
|
in.reply <- w.remWatch(in.path)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch e {
|
||||||
|
case syscall.ERROR_MORE_DATA:
|
||||||
|
if watch == nil {
|
||||||
|
w.Errors <- errors.New("ERROR_MORE_DATA has unexpectedly null lpOverlapped buffer")
|
||||||
|
} else {
|
||||||
|
// The i/o succeeded but the buffer is full.
|
||||||
|
// In theory we should be building up a full packet.
|
||||||
|
// In practice we can get away with just carrying on.
|
||||||
|
n = uint32(unsafe.Sizeof(watch.buf))
|
||||||
|
}
|
||||||
|
case syscall.ERROR_ACCESS_DENIED:
|
||||||
|
// Watched directory was probably removed
|
||||||
|
w.sendEvent(watch.path, watch.mask&sysFSDELETESELF)
|
||||||
|
w.deleteWatch(watch)
|
||||||
|
w.startRead(watch)
|
||||||
|
continue
|
||||||
|
case syscall.ERROR_OPERATION_ABORTED:
|
||||||
|
// CancelIo was called on this handle
|
||||||
|
continue
|
||||||
|
default:
|
||||||
|
w.Errors <- os.NewSyscallError("GetQueuedCompletionPort", e)
|
||||||
|
continue
|
||||||
|
case nil:
|
||||||
|
}
|
||||||
|
|
||||||
|
var offset uint32
|
||||||
|
for {
|
||||||
|
if n == 0 {
|
||||||
|
w.Events <- newEvent("", sysFSQOVERFLOW)
|
||||||
|
w.Errors <- errors.New("short read in readEvents()")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Point "raw" to the event in the buffer
|
||||||
|
raw := (*syscall.FileNotifyInformation)(unsafe.Pointer(&watch.buf[offset]))
|
||||||
|
// TODO: Consider using unsafe.Slice that is available from go1.17
|
||||||
|
// https://stackoverflow.com/questions/51187973/how-to-create-an-array-or-a-slice-from-an-array-unsafe-pointer-in-golang
|
||||||
|
// instead of using a fixed syscall.MAX_PATH buf, we create a buf that is the size of the path name
|
||||||
|
size := int(raw.FileNameLength / 2)
|
||||||
|
var buf []uint16
|
||||||
|
sh := (*reflect.SliceHeader)(unsafe.Pointer(&buf))
|
||||||
|
sh.Data = uintptr(unsafe.Pointer(&raw.FileName))
|
||||||
|
sh.Len = size
|
||||||
|
sh.Cap = size
|
||||||
|
name := syscall.UTF16ToString(buf)
|
||||||
|
fullname := filepath.Join(watch.path, name)
|
||||||
|
|
||||||
|
var mask uint64
|
||||||
|
switch raw.Action {
|
||||||
|
case syscall.FILE_ACTION_REMOVED:
|
||||||
|
mask = sysFSDELETESELF
|
||||||
|
case syscall.FILE_ACTION_MODIFIED:
|
||||||
|
mask = sysFSMODIFY
|
||||||
|
case syscall.FILE_ACTION_RENAMED_OLD_NAME:
|
||||||
|
watch.rename = name
|
||||||
|
case syscall.FILE_ACTION_RENAMED_NEW_NAME:
|
||||||
|
if watch.names[watch.rename] != 0 {
|
||||||
|
watch.names[name] |= watch.names[watch.rename]
|
||||||
|
delete(watch.names, watch.rename)
|
||||||
|
mask = sysFSMOVESELF
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sendNameEvent := func() {
|
||||||
|
if w.sendEvent(fullname, watch.names[name]&mask) {
|
||||||
|
if watch.names[name]&sysFSONESHOT != 0 {
|
||||||
|
delete(watch.names, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if raw.Action != syscall.FILE_ACTION_RENAMED_NEW_NAME {
|
||||||
|
sendNameEvent()
|
||||||
|
}
|
||||||
|
if raw.Action == syscall.FILE_ACTION_REMOVED {
|
||||||
|
w.sendEvent(fullname, watch.names[name]&sysFSIGNORED)
|
||||||
|
delete(watch.names, name)
|
||||||
|
}
|
||||||
|
if w.sendEvent(fullname, watch.mask&toFSnotifyFlags(raw.Action)) {
|
||||||
|
if watch.mask&sysFSONESHOT != 0 {
|
||||||
|
watch.mask = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if raw.Action == syscall.FILE_ACTION_RENAMED_NEW_NAME {
|
||||||
|
fullname = filepath.Join(watch.path, watch.rename)
|
||||||
|
sendNameEvent()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move to the next event in the buffer
|
||||||
|
if raw.NextEntryOffset == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
offset += raw.NextEntryOffset
|
||||||
|
|
||||||
|
// Error!
|
||||||
|
if offset >= n {
|
||||||
|
w.Errors <- errors.New("Windows system assumed buffer larger than it is, events have likely been missed.")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := w.startRead(watch); err != nil {
|
||||||
|
w.Errors <- err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Watcher) sendEvent(name string, mask uint64) bool {
|
||||||
|
if mask == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
event := newEvent(name, uint32(mask))
|
||||||
|
select {
|
||||||
|
case ch := <-w.quit:
|
||||||
|
w.quit <- ch
|
||||||
|
case w.Events <- event:
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func toWindowsFlags(mask uint64) uint32 {
|
||||||
|
var m uint32
|
||||||
|
if mask&sysFSACCESS != 0 {
|
||||||
|
m |= syscall.FILE_NOTIFY_CHANGE_LAST_ACCESS
|
||||||
|
}
|
||||||
|
if mask&sysFSMODIFY != 0 {
|
||||||
|
m |= syscall.FILE_NOTIFY_CHANGE_LAST_WRITE
|
||||||
|
}
|
||||||
|
if mask&sysFSATTRIB != 0 {
|
||||||
|
m |= syscall.FILE_NOTIFY_CHANGE_ATTRIBUTES
|
||||||
|
}
|
||||||
|
if mask&(sysFSMOVE|sysFSCREATE|sysFSDELETE) != 0 {
|
||||||
|
m |= syscall.FILE_NOTIFY_CHANGE_FILE_NAME | syscall.FILE_NOTIFY_CHANGE_DIR_NAME
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func toFSnotifyFlags(action uint32) uint64 {
|
||||||
|
switch action {
|
||||||
|
case syscall.FILE_ACTION_ADDED:
|
||||||
|
return sysFSCREATE
|
||||||
|
case syscall.FILE_ACTION_REMOVED:
|
||||||
|
return sysFSDELETE
|
||||||
|
case syscall.FILE_ACTION_MODIFIED:
|
||||||
|
return sysFSMODIFY
|
||||||
|
case syscall.FILE_ACTION_RENAMED_OLD_NAME:
|
||||||
|
return sysFSMOVEDFROM
|
||||||
|
case syscall.FILE_ACTION_RENAMED_NEW_NAME:
|
||||||
|
return sysFSMOVEDTO
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
26
vendor/github.com/gin-contrib/sse/.travis.yml
generated
vendored
Normal file
26
vendor/github.com/gin-contrib/sse/.travis.yml
generated
vendored
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
language: go
|
||||||
|
sudo: false
|
||||||
|
go:
|
||||||
|
- 1.8.x
|
||||||
|
- 1.9.x
|
||||||
|
- 1.10.x
|
||||||
|
- 1.11.x
|
||||||
|
- 1.12.x
|
||||||
|
- master
|
||||||
|
|
||||||
|
git:
|
||||||
|
depth: 10
|
||||||
|
|
||||||
|
matrix:
|
||||||
|
fast_finish: true
|
||||||
|
include:
|
||||||
|
- go: 1.11.x
|
||||||
|
env: GO111MODULE=on
|
||||||
|
- go: 1.12.x
|
||||||
|
env: GO111MODULE=on
|
||||||
|
|
||||||
|
script:
|
||||||
|
- go test -v -covermode=count -coverprofile=coverage.out
|
||||||
|
|
||||||
|
after_success:
|
||||||
|
- bash <(curl -s https://codecov.io/bash)
|
21
vendor/github.com/gin-contrib/sse/LICENSE
generated
vendored
Normal file
21
vendor/github.com/gin-contrib/sse/LICENSE
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2014 Manuel Martínez-Almeida
|
||||||
|
|
||||||
|
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.
|
58
vendor/github.com/gin-contrib/sse/README.md
generated
vendored
Normal file
58
vendor/github.com/gin-contrib/sse/README.md
generated
vendored
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
# Server-Sent Events
|
||||||
|
|
||||||
|
[![GoDoc](https://godoc.org/github.com/gin-contrib/sse?status.svg)](https://godoc.org/github.com/gin-contrib/sse)
|
||||||
|
[![Build Status](https://travis-ci.org/gin-contrib/sse.svg)](https://travis-ci.org/gin-contrib/sse)
|
||||||
|
[![codecov](https://codecov.io/gh/gin-contrib/sse/branch/master/graph/badge.svg)](https://codecov.io/gh/gin-contrib/sse)
|
||||||
|
[![Go Report Card](https://goreportcard.com/badge/github.com/gin-contrib/sse)](https://goreportcard.com/report/github.com/gin-contrib/sse)
|
||||||
|
|
||||||
|
Server-sent events (SSE) is a technology where a browser receives automatic updates from a server via HTTP connection. The Server-Sent Events EventSource API is [standardized as part of HTML5[1] by the W3C](http://www.w3.org/TR/2009/WD-eventsource-20091029/).
|
||||||
|
|
||||||
|
- [Read this great SSE introduction by the HTML5Rocks guys](http://www.html5rocks.com/en/tutorials/eventsource/basics/)
|
||||||
|
- [Browser support](http://caniuse.com/#feat=eventsource)
|
||||||
|
|
||||||
|
## Sample code
|
||||||
|
|
||||||
|
```go
|
||||||
|
import "github.com/gin-contrib/sse"
|
||||||
|
|
||||||
|
func httpHandler(w http.ResponseWriter, req *http.Request) {
|
||||||
|
// data can be a primitive like a string, an integer or a float
|
||||||
|
sse.Encode(w, sse.Event{
|
||||||
|
Event: "message",
|
||||||
|
Data: "some data\nmore data",
|
||||||
|
})
|
||||||
|
|
||||||
|
// also a complex type, like a map, a struct or a slice
|
||||||
|
sse.Encode(w, sse.Event{
|
||||||
|
Id: "124",
|
||||||
|
Event: "message",
|
||||||
|
Data: map[string]interface{}{
|
||||||
|
"user": "manu",
|
||||||
|
"date": time.Now().Unix(),
|
||||||
|
"content": "hi!",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
```
|
||||||
|
event: message
|
||||||
|
data: some data\\nmore data
|
||||||
|
|
||||||
|
id: 124
|
||||||
|
event: message
|
||||||
|
data: {"content":"hi!","date":1431540810,"user":"manu"}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## Content-Type
|
||||||
|
|
||||||
|
```go
|
||||||
|
fmt.Println(sse.ContentType)
|
||||||
|
```
|
||||||
|
```
|
||||||
|
text/event-stream
|
||||||
|
```
|
||||||
|
|
||||||
|
## Decoding support
|
||||||
|
|
||||||
|
There is a client-side implementation of SSE coming soon.
|
116
vendor/github.com/gin-contrib/sse/sse-decoder.go
generated
vendored
Normal file
116
vendor/github.com/gin-contrib/sse/sse-decoder.go
generated
vendored
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package sse
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
type decoder struct {
|
||||||
|
events []Event
|
||||||
|
}
|
||||||
|
|
||||||
|
func Decode(r io.Reader) ([]Event, error) {
|
||||||
|
var dec decoder
|
||||||
|
return dec.decode(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *decoder) dispatchEvent(event Event, data string) {
|
||||||
|
dataLength := len(data)
|
||||||
|
if dataLength > 0 {
|
||||||
|
//If the data buffer's last character is a U+000A LINE FEED (LF) character, then remove the last character from the data buffer.
|
||||||
|
data = data[:dataLength-1]
|
||||||
|
dataLength--
|
||||||
|
}
|
||||||
|
if dataLength == 0 && event.Event == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if event.Event == "" {
|
||||||
|
event.Event = "message"
|
||||||
|
}
|
||||||
|
event.Data = data
|
||||||
|
d.events = append(d.events, event)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *decoder) decode(r io.Reader) ([]Event, error) {
|
||||||
|
buf, err := ioutil.ReadAll(r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var currentEvent Event
|
||||||
|
var dataBuffer *bytes.Buffer = new(bytes.Buffer)
|
||||||
|
// TODO (and unit tests)
|
||||||
|
// Lines must be separated by either a U+000D CARRIAGE RETURN U+000A LINE FEED (CRLF) character pair,
|
||||||
|
// a single U+000A LINE FEED (LF) character,
|
||||||
|
// or a single U+000D CARRIAGE RETURN (CR) character.
|
||||||
|
lines := bytes.Split(buf, []byte{'\n'})
|
||||||
|
for _, line := range lines {
|
||||||
|
if len(line) == 0 {
|
||||||
|
// If the line is empty (a blank line). Dispatch the event.
|
||||||
|
d.dispatchEvent(currentEvent, dataBuffer.String())
|
||||||
|
|
||||||
|
// reset current event and data buffer
|
||||||
|
currentEvent = Event{}
|
||||||
|
dataBuffer.Reset()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if line[0] == byte(':') {
|
||||||
|
// If the line starts with a U+003A COLON character (:), ignore the line.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var field, value []byte
|
||||||
|
colonIndex := bytes.IndexRune(line, ':')
|
||||||
|
if colonIndex != -1 {
|
||||||
|
// If the line contains a U+003A COLON character character (:)
|
||||||
|
// Collect the characters on the line before the first U+003A COLON character (:),
|
||||||
|
// and let field be that string.
|
||||||
|
field = line[:colonIndex]
|
||||||
|
// Collect the characters on the line after the first U+003A COLON character (:),
|
||||||
|
// and let value be that string.
|
||||||
|
value = line[colonIndex+1:]
|
||||||
|
// If value starts with a single U+0020 SPACE character, remove it from value.
|
||||||
|
if len(value) > 0 && value[0] == ' ' {
|
||||||
|
value = value[1:]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Otherwise, the string is not empty but does not contain a U+003A COLON character character (:)
|
||||||
|
// Use the whole line as the field name, and the empty string as the field value.
|
||||||
|
field = line
|
||||||
|
value = []byte{}
|
||||||
|
}
|
||||||
|
// The steps to process the field given a field name and a field value depend on the field name,
|
||||||
|
// as given in the following list. Field names must be compared literally,
|
||||||
|
// with no case folding performed.
|
||||||
|
switch string(field) {
|
||||||
|
case "event":
|
||||||
|
// Set the event name buffer to field value.
|
||||||
|
currentEvent.Event = string(value)
|
||||||
|
case "id":
|
||||||
|
// Set the event stream's last event ID to the field value.
|
||||||
|
currentEvent.Id = string(value)
|
||||||
|
case "retry":
|
||||||
|
// If the field value consists of only characters in the range U+0030 DIGIT ZERO (0) to U+0039 DIGIT NINE (9),
|
||||||
|
// then interpret the field value as an integer in base ten, and set the event stream's reconnection time to that integer.
|
||||||
|
// Otherwise, ignore the field.
|
||||||
|
currentEvent.Id = string(value)
|
||||||
|
case "data":
|
||||||
|
// Append the field value to the data buffer,
|
||||||
|
dataBuffer.Write(value)
|
||||||
|
// then append a single U+000A LINE FEED (LF) character to the data buffer.
|
||||||
|
dataBuffer.WriteString("\n")
|
||||||
|
default:
|
||||||
|
//Otherwise. The field is ignored.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Once the end of the file is reached, the user agent must dispatch the event one final time.
|
||||||
|
d.dispatchEvent(currentEvent, dataBuffer.String())
|
||||||
|
|
||||||
|
return d.events, nil
|
||||||
|
}
|
110
vendor/github.com/gin-contrib/sse/sse-encoder.go
generated
vendored
Normal file
110
vendor/github.com/gin-contrib/sse/sse-encoder.go
generated
vendored
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package sse
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Server-Sent Events
|
||||||
|
// W3C Working Draft 29 October 2009
|
||||||
|
// http://www.w3.org/TR/2009/WD-eventsource-20091029/
|
||||||
|
|
||||||
|
const ContentType = "text/event-stream"
|
||||||
|
|
||||||
|
var contentType = []string{ContentType}
|
||||||
|
var noCache = []string{"no-cache"}
|
||||||
|
|
||||||
|
var fieldReplacer = strings.NewReplacer(
|
||||||
|
"\n", "\\n",
|
||||||
|
"\r", "\\r")
|
||||||
|
|
||||||
|
var dataReplacer = strings.NewReplacer(
|
||||||
|
"\n", "\ndata:",
|
||||||
|
"\r", "\\r")
|
||||||
|
|
||||||
|
type Event struct {
|
||||||
|
Event string
|
||||||
|
Id string
|
||||||
|
Retry uint
|
||||||
|
Data interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Encode(writer io.Writer, event Event) error {
|
||||||
|
w := checkWriter(writer)
|
||||||
|
writeId(w, event.Id)
|
||||||
|
writeEvent(w, event.Event)
|
||||||
|
writeRetry(w, event.Retry)
|
||||||
|
return writeData(w, event.Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeId(w stringWriter, id string) {
|
||||||
|
if len(id) > 0 {
|
||||||
|
w.WriteString("id:")
|
||||||
|
fieldReplacer.WriteString(w, id)
|
||||||
|
w.WriteString("\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeEvent(w stringWriter, event string) {
|
||||||
|
if len(event) > 0 {
|
||||||
|
w.WriteString("event:")
|
||||||
|
fieldReplacer.WriteString(w, event)
|
||||||
|
w.WriteString("\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeRetry(w stringWriter, retry uint) {
|
||||||
|
if retry > 0 {
|
||||||
|
w.WriteString("retry:")
|
||||||
|
w.WriteString(strconv.FormatUint(uint64(retry), 10))
|
||||||
|
w.WriteString("\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeData(w stringWriter, data interface{}) error {
|
||||||
|
w.WriteString("data:")
|
||||||
|
switch kindOfData(data) {
|
||||||
|
case reflect.Struct, reflect.Slice, reflect.Map:
|
||||||
|
err := json.NewEncoder(w).Encode(data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
w.WriteString("\n")
|
||||||
|
default:
|
||||||
|
dataReplacer.WriteString(w, fmt.Sprint(data))
|
||||||
|
w.WriteString("\n\n")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r Event) Render(w http.ResponseWriter) error {
|
||||||
|
r.WriteContentType(w)
|
||||||
|
return Encode(w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r Event) WriteContentType(w http.ResponseWriter) {
|
||||||
|
header := w.Header()
|
||||||
|
header["Content-Type"] = contentType
|
||||||
|
|
||||||
|
if _, exist := header["Cache-Control"]; !exist {
|
||||||
|
header["Cache-Control"] = noCache
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func kindOfData(data interface{}) reflect.Kind {
|
||||||
|
value := reflect.ValueOf(data)
|
||||||
|
valueType := value.Kind()
|
||||||
|
if valueType == reflect.Ptr {
|
||||||
|
valueType = value.Elem().Kind()
|
||||||
|
}
|
||||||
|
return valueType
|
||||||
|
}
|
24
vendor/github.com/gin-contrib/sse/writer.go
generated
vendored
Normal file
24
vendor/github.com/gin-contrib/sse/writer.go
generated
vendored
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package sse
|
||||||
|
|
||||||
|
import "io"
|
||||||
|
|
||||||
|
type stringWriter interface {
|
||||||
|
io.Writer
|
||||||
|
WriteString(string) (int, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type stringWrapper struct {
|
||||||
|
io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w stringWrapper) WriteString(str string) (int, error) {
|
||||||
|
return w.Writer.Write([]byte(str))
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkWriter(writer io.Writer) stringWriter {
|
||||||
|
if w, ok := writer.(stringWriter); ok {
|
||||||
|
return w
|
||||||
|
} else {
|
||||||
|
return stringWrapper{writer}
|
||||||
|
}
|
||||||
|
}
|
7
vendor/github.com/gin-gonic/gin/.gitignore
generated
vendored
Normal file
7
vendor/github.com/gin-gonic/gin/.gitignore
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
vendor/*
|
||||||
|
!vendor/vendor.json
|
||||||
|
coverage.out
|
||||||
|
count.out
|
||||||
|
test
|
||||||
|
profile.out
|
||||||
|
tmp.out
|
39
vendor/github.com/gin-gonic/gin/.golangci.yml
generated
vendored
Normal file
39
vendor/github.com/gin-gonic/gin/.golangci.yml
generated
vendored
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
run:
|
||||||
|
timeout: 5m
|
||||||
|
linters:
|
||||||
|
enable:
|
||||||
|
- asciicheck
|
||||||
|
- depguard
|
||||||
|
- dogsled
|
||||||
|
- durationcheck
|
||||||
|
- errcheck
|
||||||
|
- errorlint
|
||||||
|
- exportloopref
|
||||||
|
- gci
|
||||||
|
- gofmt
|
||||||
|
- goimports
|
||||||
|
- gosec
|
||||||
|
- misspell
|
||||||
|
- nakedret
|
||||||
|
- nilerr
|
||||||
|
- nolintlint
|
||||||
|
- revive
|
||||||
|
- wastedassign
|
||||||
|
issues:
|
||||||
|
exclude-rules:
|
||||||
|
- linters:
|
||||||
|
- structcheck
|
||||||
|
- unused
|
||||||
|
text: "`data` is unused"
|
||||||
|
- linters:
|
||||||
|
- staticcheck
|
||||||
|
text: "SA1019:"
|
||||||
|
- linters:
|
||||||
|
- revive
|
||||||
|
text: "var-naming:"
|
||||||
|
- linters:
|
||||||
|
- revive
|
||||||
|
text: "exported:"
|
||||||
|
- path: _test\.go
|
||||||
|
linters:
|
||||||
|
- gosec # security is not make sense in tests
|
57
vendor/github.com/gin-gonic/gin/.goreleaser.yaml
generated
vendored
Normal file
57
vendor/github.com/gin-gonic/gin/.goreleaser.yaml
generated
vendored
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
project_name: gin
|
||||||
|
|
||||||
|
builds:
|
||||||
|
-
|
||||||
|
# If true, skip the build.
|
||||||
|
# Useful for library projects.
|
||||||
|
# Default is false
|
||||||
|
skip: true
|
||||||
|
|
||||||
|
changelog:
|
||||||
|
# Set it to true if you wish to skip the changelog generation.
|
||||||
|
# This may result in an empty release notes on GitHub/GitLab/Gitea.
|
||||||
|
skip: false
|
||||||
|
|
||||||
|
# Changelog generation implementation to use.
|
||||||
|
#
|
||||||
|
# Valid options are:
|
||||||
|
# - `git`: uses `git log`;
|
||||||
|
# - `github`: uses the compare GitHub API, appending the author login to the changelog.
|
||||||
|
# - `gitlab`: uses the compare GitLab API, appending the author name and email to the changelog.
|
||||||
|
# - `github-native`: uses the GitHub release notes generation API, disables the groups feature.
|
||||||
|
#
|
||||||
|
# Defaults to `git`.
|
||||||
|
use: git
|
||||||
|
|
||||||
|
# Sorts the changelog by the commit's messages.
|
||||||
|
# Could either be asc, desc or empty
|
||||||
|
# Default is empty
|
||||||
|
sort: asc
|
||||||
|
|
||||||
|
# Group commits messages by given regex and title.
|
||||||
|
# Order value defines the order of the groups.
|
||||||
|
# Proving no regex means all commits will be grouped under the default group.
|
||||||
|
# Groups are disabled when using github-native, as it already groups things by itself.
|
||||||
|
#
|
||||||
|
# Default is no groups.
|
||||||
|
groups:
|
||||||
|
- title: Features
|
||||||
|
regexp: "^.*feat[(\\w)]*:+.*$"
|
||||||
|
order: 0
|
||||||
|
- title: 'Bug fixes'
|
||||||
|
regexp: "^.*fix[(\\w)]*:+.*$"
|
||||||
|
order: 1
|
||||||
|
- title: 'Enhancements'
|
||||||
|
regexp: "^.*chore[(\\w)]*:+.*$"
|
||||||
|
order: 2
|
||||||
|
- title: Others
|
||||||
|
order: 999
|
||||||
|
|
||||||
|
filters:
|
||||||
|
# Commit messages matching the regexp listed here will be removed from
|
||||||
|
# the changelog
|
||||||
|
# Default is empty
|
||||||
|
exclude:
|
||||||
|
- '^docs'
|
||||||
|
- 'CICD'
|
||||||
|
- typo
|
406
vendor/github.com/gin-gonic/gin/AUTHORS.md
generated
vendored
Normal file
406
vendor/github.com/gin-gonic/gin/AUTHORS.md
generated
vendored
Normal file
@ -0,0 +1,406 @@
|
|||||||
|
List of all the awesome people working to make Gin the best Web Framework in Go.
|
||||||
|
|
||||||
|
## gin 1.x series authors
|
||||||
|
|
||||||
|
**Gin Core Team:** Bo-Yi Wu (@appleboy), thinkerou (@thinkerou), Javier Provecho (@javierprovecho)
|
||||||
|
|
||||||
|
## gin 0.x series authors
|
||||||
|
|
||||||
|
**Maintainers:** Manu Martinez-Almeida (@manucorporat), Javier Provecho (@javierprovecho)
|
||||||
|
|
||||||
|
------
|
||||||
|
|
||||||
|
People and companies, who have contributed, in alphabetical order.
|
||||||
|
|
||||||
|
- 178inaba <178inaba@users.noreply.github.com>
|
||||||
|
- A. F <hello@clivern.com>
|
||||||
|
- ABHISHEK SONI <abhishek.rocks26@gmail.com>
|
||||||
|
- Abhishek Chanda <achanda@users.noreply.github.com>
|
||||||
|
- Abner Chen <houjunchen@gmail.com>
|
||||||
|
- AcoNCodes <acongame@gmail.com>
|
||||||
|
- Adam Dratwinski <adam.dratwinski@gmail.com>
|
||||||
|
- Adam Mckaig <adam.mckaig@gmail.com>
|
||||||
|
- Adam Zielinski <MusicAdam@users.noreply.github.com>
|
||||||
|
- Adonis <donileo@gmail.com>
|
||||||
|
- Alan Wang <azzwacb9001@126.com>
|
||||||
|
- Albin Gilles <gilles.albin@gmail.com>
|
||||||
|
- Aleksandr Didenko <aa.didenko@yandex.ru>
|
||||||
|
- Alessandro (Ale) Segala <43508+ItalyPaleAle@users.noreply.github.com>
|
||||||
|
- Alex <AWulkan@users.noreply.github.com>
|
||||||
|
- Alexander <alexanderchenmh@gmail.com>
|
||||||
|
- Alexander Lokhman <alex.lokhman@gmail.com>
|
||||||
|
- Alexander Melentyev <55826637+alexander-melentyev@users.noreply.github.com>
|
||||||
|
- Alexander Nyquist <nyquist.alexander@gmail.com>
|
||||||
|
- Allen Ren <kulong0105@gmail.com>
|
||||||
|
- AllinGo <tanhp@outlook.com>
|
||||||
|
- Ammar Bandukwala <ammar@ammar.io>
|
||||||
|
- An Xiao (Luffy) <hac@zju.edu.cn>
|
||||||
|
- Andre Dublin <81dublin@gmail.com>
|
||||||
|
- Andrew Szeto <github@jabagawee.com>
|
||||||
|
- Andrey Abramov <andreyabramov.aaa@gmail.com>
|
||||||
|
- Andrey Nering <andrey.nering@gmail.com>
|
||||||
|
- Andrey Smirnov <Smirnov.Andrey@gmail.com>
|
||||||
|
- Andrii Bubis <firstrow@gmail.com>
|
||||||
|
- André Bazaglia <bazaglia@users.noreply.github.com>
|
||||||
|
- Andy Pan <panjf2000@gmail.com>
|
||||||
|
- Antoine GIRARD <sapk@users.noreply.github.com>
|
||||||
|
- Anup Kumar Panwar <1anuppanwar@gmail.com>
|
||||||
|
- Aravinth Sundaram <gosh.aravind@gmail.com>
|
||||||
|
- Artem <horechek@gmail.com>
|
||||||
|
- Ashwani <ashwanisharma686@gmail.com>
|
||||||
|
- Aurelien Regat-Barrel <arb@cyberkarma.net>
|
||||||
|
- Austin Heap <me@austinheap.com>
|
||||||
|
- Barnabus <jbampton@users.noreply.github.com>
|
||||||
|
- Bo-Yi Wu <appleboy.tw@gmail.com>
|
||||||
|
- Boris Borshevsky <BorisBorshevsky@gmail.com>
|
||||||
|
- Boyi Wu <p581581@gmail.com>
|
||||||
|
- BradyBromley <51128276+BradyBromley@users.noreply.github.com>
|
||||||
|
- Brendan Fosberry <brendan@shopkeep.com>
|
||||||
|
- Brian Wigginton <brianwigginton@gmail.com>
|
||||||
|
- Carlos Eduardo <carlosedp@gmail.com>
|
||||||
|
- Chad Russell <chaddouglasrussell@gmail.com>
|
||||||
|
- Charles <cxjava@gmail.com>
|
||||||
|
- Christian Muehlhaeuser <muesli@gmail.com>
|
||||||
|
- Christian Persson <saser@live.se>
|
||||||
|
- Christopher Harrington <ironiridis@gmail.com>
|
||||||
|
- Damon Zhao <yijun.zhao@outlook.com>
|
||||||
|
- Dan Markham <dmarkham@gmail.com>
|
||||||
|
- Dang Nguyen <hoangdang.me@gmail.com>
|
||||||
|
- Daniel Krom <kromdan@gmail.com>
|
||||||
|
- Daniel M. Lambea <dmlambea@gmail.com>
|
||||||
|
- Danieliu <liudanking@gmail.com>
|
||||||
|
- David Irvine <aviddiviner@gmail.com>
|
||||||
|
- David Zhang <crispgm@gmail.com>
|
||||||
|
- Davor Kapsa <dvrkps@users.noreply.github.com>
|
||||||
|
- DeathKing <DeathKing@users.noreply.github.com>
|
||||||
|
- Dennis Cho <47404603+forest747@users.noreply.github.com>
|
||||||
|
- Dmitry Dorogin <dmirogin@ya.ru>
|
||||||
|
- Dmitry Kutakov <vkd.castle@gmail.com>
|
||||||
|
- Dmitry Sedykh <dmitrys@d3h.local>
|
||||||
|
- Don2Quixote <35610661+Don2Quixote@users.noreply.github.com>
|
||||||
|
- Donn Pebe <iam@donnpebe.com>
|
||||||
|
- Dustin Decker <dustindecker@protonmail.com>
|
||||||
|
- Eason Lin <easonlin404@gmail.com>
|
||||||
|
- Edward Betts <edward@4angle.com>
|
||||||
|
- Egor Seredin <4819888+agmt@users.noreply.github.com>
|
||||||
|
- Emmanuel Goh <emmanuel@visenze.com>
|
||||||
|
- Equim <sayaka@ekyu.moe>
|
||||||
|
- Eren A. Akyol <eren@redmc.me>
|
||||||
|
- Eric_Lee <xplzv@126.com>
|
||||||
|
- Erik Bender <erik.bender@develerik.dev>
|
||||||
|
- Ethan Kan <ethankan@neoplot.com>
|
||||||
|
- Evgeny Persienko <e.persienko@office.ngs.ru>
|
||||||
|
- Faisal Alam <ifaisalalam@gmail.com>
|
||||||
|
- Fareed Dudhia <fareeddudhia@googlemail.com>
|
||||||
|
- Filip Figiel <figiel.filip@gmail.com>
|
||||||
|
- Florian Polster <couchpolster@icqmail.com>
|
||||||
|
- Frank Bille <github@frankbille.dk>
|
||||||
|
- Franz Bettag <franz@bett.ag>
|
||||||
|
- Ganlv <ganlvtech@users.noreply.github.com>
|
||||||
|
- Gaozhen Ying <yinggaozhen@hotmail.com>
|
||||||
|
- George Gabolaev <gabolaev98@gmail.com>
|
||||||
|
- George Kirilenko <necryin@users.noreply.github.com>
|
||||||
|
- Georges Varouchas <georges.varouchas@gmail.com>
|
||||||
|
- Gordon Tyler <gordon@doxxx.net>
|
||||||
|
- Harindu Perera <harinduenator@gmail.com>
|
||||||
|
- Helios <674876158@qq.com>
|
||||||
|
- Henry Kwan <piengeng@users.noreply.github.com>
|
||||||
|
- Henry Yee <henry@yearning.io>
|
||||||
|
- Himanshu Mishra <OrkoHunter@users.noreply.github.com>
|
||||||
|
- Hiroyuki Tanaka <h.tanaka.0325@gmail.com>
|
||||||
|
- Ibraheem Ahmed <ibrah1440@gmail.com>
|
||||||
|
- Ignacio Galindo <joiggama@gmail.com>
|
||||||
|
- Igor H. Vieira <zignd.igor@gmail.com>
|
||||||
|
- Ildar1111 <54001462+Ildar1111@users.noreply.github.com>
|
||||||
|
- Iskander (Alex) Sharipov <iskander.sharipov@intel.com>
|
||||||
|
- Ismail Gjevori <isgjevori@protonmail.com>
|
||||||
|
- Ivan Chen <allenivan@gmail.com>
|
||||||
|
- JINNOUCHI Yasushi <delphinus@remora.cx>
|
||||||
|
- James Pettyjohn <japettyjohn@users.noreply.github.com>
|
||||||
|
- Jamie Stackhouse <jamie.stackhouse@redspace.com>
|
||||||
|
- Jason Lee <jawc@hotmail.com>
|
||||||
|
- Javier Provecho <j.provecho@dartekstudios.com>
|
||||||
|
- Javier Provecho <javier.provecho@bq.com>
|
||||||
|
- Javier Provecho <javiertitan@gmail.com>
|
||||||
|
- Javier Provecho Fernandez <j.provecho@dartekstudios.com>
|
||||||
|
- Javier Provecho Fernandez <javiertitan@gmail.com>
|
||||||
|
- Jean-Christophe Lebreton <jclebreton@gmail.com>
|
||||||
|
- Jeff <laojianzi1994@gmail.com>
|
||||||
|
- Jeremy Loy <jeremy.b.loy@icloud.com>
|
||||||
|
- Jim Filippou <p3160253@aueb.gr>
|
||||||
|
- Jimmy Pettersson <jimmy@expertmaker.com>
|
||||||
|
- John Bampton <jbampton@users.noreply.github.com>
|
||||||
|
- Johnny Dallas <johnnydallas0308@gmail.com>
|
||||||
|
- Johnny Dallas <theonlyjohnny@theonlyjohnny.sh>
|
||||||
|
- Jonathan (JC) Chen <jc@dijonkitchen.org>
|
||||||
|
- Josep Jesus Bigorra Algaba <42377845+averageflow@users.noreply.github.com>
|
||||||
|
- Josh Horowitz <joshua.m.horowitz@gmail.com>
|
||||||
|
- Joshua Loper <josh.el3@gmail.com>
|
||||||
|
- Julien Schmidt <github@julienschmidt.com>
|
||||||
|
- Jun Kimura <jksmphone@gmail.com>
|
||||||
|
- Justin Beckwith <justin.beckwith@gmail.com>
|
||||||
|
- Justin Israel <justinisrael@gmail.com>
|
||||||
|
- Justin Mayhew <mayhew@live.ca>
|
||||||
|
- Jérôme Laforge <jerome-laforge@users.noreply.github.com>
|
||||||
|
- Kacper Bąk <56700396+53jk1@users.noreply.github.com>
|
||||||
|
- Kamron Batman <kamronbatman@users.noreply.github.com>
|
||||||
|
- Kane Rogers <kane@cleanstream.com.au>
|
||||||
|
- Kaushik Neelichetty <kaushikneelichetty6132@gmail.com>
|
||||||
|
- Keiji Yoshida <yoshida.keiji.84@gmail.com>
|
||||||
|
- Kel Cecil <kel.cecil@listhub.com>
|
||||||
|
- Kevin Mulvey <kmulvey@linux.com>
|
||||||
|
- Kevin Zhu <ipandtcp@gmail.com>
|
||||||
|
- Kirill Motkov <motkov.kirill@gmail.com>
|
||||||
|
- Klemen Sever <ksever@student.42.fr>
|
||||||
|
- Kristoffer A. Iversen <kristoffer.a.iversen@gmail.com>
|
||||||
|
- Krzysztof Szafrański <k.p.szafranski@gmail.com>
|
||||||
|
- Kumar McMillan <kumar.mcmillan@gmail.com>
|
||||||
|
- Kyle Mcgill <email@kylescottmcgill.com>
|
||||||
|
- Lanco <35420416+lancoLiu@users.noreply.github.com>
|
||||||
|
- Levi Olson <olson.levi@gmail.com>
|
||||||
|
- Lin Kao-Yuan <mosdeo@gmail.com>
|
||||||
|
- Linus Unnebäck <linus@folkdatorn.se>
|
||||||
|
- Lucas Clemente <lucas@clemente.io>
|
||||||
|
- Ludwig Valda Vasquez <bredov@gmail.com>
|
||||||
|
- Luis GG <lggomez@users.noreply.github.com>
|
||||||
|
- MW Lim <williamchange@gmail.com>
|
||||||
|
- Maksimov Sergey <konjoot@gmail.com>
|
||||||
|
- Manjusaka <lizheao940510@gmail.com>
|
||||||
|
- Manu MA <manu.mtza@gmail.com>
|
||||||
|
- Manu MA <manu.valladolid@gmail.com>
|
||||||
|
- Manu Mtz-Almeida <manu.valladolid@gmail.com>
|
||||||
|
- Manu Mtz.-Almeida <manu.valladolid@gmail.com>
|
||||||
|
- Manuel Alonso <manuelalonso@invisionapp.com>
|
||||||
|
- Mara Kim <hacker.root@gmail.com>
|
||||||
|
- Mario Kostelac <mario@intercom.io>
|
||||||
|
- Martin Karlsch <martin@karlsch.com>
|
||||||
|
- Matt Newberry <mnewberry@opentable.com>
|
||||||
|
- Matt Williams <gh@mattyw.net>
|
||||||
|
- Matthieu MOREL <mmorel-35@users.noreply.github.com>
|
||||||
|
- Max Hilbrunner <mhilbrunner@users.noreply.github.com>
|
||||||
|
- Maxime Soulé <btik-git@scoubidou.com>
|
||||||
|
- MetalBreaker <johnymichelson@gmail.com>
|
||||||
|
- Michael Puncel <mpuncel@squareup.com>
|
||||||
|
- MichaelDeSteven <51652084+MichaelDeSteven@users.noreply.github.com>
|
||||||
|
- Mike <38686456+icy4ever@users.noreply.github.com>
|
||||||
|
- Mike Stipicevic <mst@ableton.com>
|
||||||
|
- Miki Tebeka <miki.tebeka@gmail.com>
|
||||||
|
- Miles <MilesLin@users.noreply.github.com>
|
||||||
|
- Mirza Ceric <mirza.ceric@b2match.com>
|
||||||
|
- Mykyta Semenistyi <nikeiwe@gmail.com>
|
||||||
|
- Naoki Takano <honten@tinkermode.com>
|
||||||
|
- Ngalim Siregar <ngalim.siregar@gmail.com>
|
||||||
|
- Ni Hao <supernihaooo@qq.com>
|
||||||
|
- Nick Gerakines <nick@gerakines.net>
|
||||||
|
- Nikifor Seryakov <nikandfor@gmail.com>
|
||||||
|
- Notealot <714804968@qq.com>
|
||||||
|
- Olivier Mengué <dolmen@cpan.org>
|
||||||
|
- Olivier Robardet <orobardet@users.noreply.github.com>
|
||||||
|
- Pablo Moncada <pablo.moncada@bq.com>
|
||||||
|
- Pablo Moncada <pmoncadaisla@gmail.com>
|
||||||
|
- Panmax <967168@qq.com>
|
||||||
|
- Peperoncino <2wua4nlyi@gmail.com>
|
||||||
|
- Philipp Meinen <philipp@bind.ch>
|
||||||
|
- Pierre Massat <pierre@massat.io>
|
||||||
|
- Qt <golang.chen@gmail.com>
|
||||||
|
- Quentin ROYER <aydendevg@gmail.com>
|
||||||
|
- README Bot <35302948+codetriage-readme-bot@users.noreply.github.com>
|
||||||
|
- Rafal Zajac <rzajac@gmail.com>
|
||||||
|
- Rahul Datta Roy <rahuldroy@users.noreply.github.com>
|
||||||
|
- Rajiv Kilaparti <rajivk085@gmail.com>
|
||||||
|
- Raphael Gavache <raphael.gavache@datadoghq.com>
|
||||||
|
- Ray Rodriguez <rayrod2030@gmail.com>
|
||||||
|
- Regner Blok-Andersen <shadowdf@gmail.com>
|
||||||
|
- Remco <remco@dutchcoders.io>
|
||||||
|
- Rex Lee(李俊) <duguying2008@gmail.com>
|
||||||
|
- Richard Lee <dlackty@gmail.com>
|
||||||
|
- Riverside <wangyb65@gmail.com>
|
||||||
|
- Robert Wilkinson <wilkinson.robert.a@gmail.com>
|
||||||
|
- Rogier Lommers <rogier@lommers.org>
|
||||||
|
- Rohan Pai <me@rohanpai.com>
|
||||||
|
- Romain Beuque <rbeuque74@gmail.com>
|
||||||
|
- Roman Belyakovsky <ihryamzik@gmail.com>
|
||||||
|
- Roman Zaynetdinov <627197+zaynetro@users.noreply.github.com>
|
||||||
|
- Roman Zaynetdinov <roman.zaynetdinov@lekane.com>
|
||||||
|
- Ronald Petty <ronald.petty@rx-m.com>
|
||||||
|
- Ross Wolf <31489089+rw-access@users.noreply.github.com>
|
||||||
|
- Roy Lou <roylou@gmail.com>
|
||||||
|
- Rubi <14269809+codenoid@users.noreply.github.com>
|
||||||
|
- Ryan <46182144+ryanker@users.noreply.github.com>
|
||||||
|
- Ryan J. Yoder <me@ryanjyoder.com>
|
||||||
|
- SRK.Lyu <superalsrk@gmail.com>
|
||||||
|
- Sai <sairoutine@gmail.com>
|
||||||
|
- Samuel Abreu <sdepaula@gmail.com>
|
||||||
|
- Santhosh Kumar <santhoshkumarr1096@gmail.com>
|
||||||
|
- Sasha Melentyev <sasha@melentyev.io>
|
||||||
|
- Sasha Myasoedov <msoedov@gmail.com>
|
||||||
|
- Segev Finer <segev208@gmail.com>
|
||||||
|
- Sergey Egorov <egorovhome@gmail.com>
|
||||||
|
- Sergey Fedchenko <seregayoga@bk.ru>
|
||||||
|
- Sergey Gonimar <sergey.gonimar@gmail.com>
|
||||||
|
- Sergey Ponomarev <me@sergey-ponomarev.ru>
|
||||||
|
- Serica <943914044@qq.com>
|
||||||
|
- Shamus Taylor <Shamus03@me.com>
|
||||||
|
- Shilin Wang <jarvisfironman@gmail.com>
|
||||||
|
- Shuo <openset.wang@gmail.com>
|
||||||
|
- Skuli Oskarsson <skuli@codeiak.io>
|
||||||
|
- Snawoot <vladislav-ex-github@vm-0.com>
|
||||||
|
- Sridhar Ratnakumar <srid@srid.ca>
|
||||||
|
- Steeve Chailloux <steeve@chaahk.com>
|
||||||
|
- Sudhir Mishra <sudhirxps@gmail.com>
|
||||||
|
- Suhas Karanth <sudo-suhas@users.noreply.github.com>
|
||||||
|
- TaeJun Park <miking38@gmail.com>
|
||||||
|
- Tatsuya Hoshino <tatsuya7.hoshino7@gmail.com>
|
||||||
|
- Tevic <tevic.tt@gmail.com>
|
||||||
|
- Tevin Jeffrey <tev.jeffrey@gmail.com>
|
||||||
|
- The Gitter Badger <badger@gitter.im>
|
||||||
|
- Thibault Jamet <tjamet@users.noreply.github.com>
|
||||||
|
- Thomas Boerger <thomas@webhippie.de>
|
||||||
|
- Thomas Schaffer <loopfz@gmail.com>
|
||||||
|
- Tommy Chu <tommychu2256@gmail.com>
|
||||||
|
- Tudor Roman <tudurom@gmail.com>
|
||||||
|
- Uwe Dauernheim <djui@users.noreply.github.com>
|
||||||
|
- Valentine Oragbakosi <valentine13400@gmail.com>
|
||||||
|
- Vas N <pnvasanth@users.noreply.github.com>
|
||||||
|
- Vasilyuk Vasiliy <By-Vasiliy@users.noreply.github.com>
|
||||||
|
- Victor Castell <victor@victorcastell.com>
|
||||||
|
- Vince Yuan <vince.yuan@gmail.com>
|
||||||
|
- Vyacheslav Dubinin <vyacheslav.dubinin@gmail.com>
|
||||||
|
- Waynerv <ampedee@gmail.com>
|
||||||
|
- Weilin Shi <934587911@qq.com>
|
||||||
|
- Xudong Cai <fifsky@gmail.com>
|
||||||
|
- Yasuhiro Matsumoto <mattn.jp@gmail.com>
|
||||||
|
- Yehezkiel Syamsuhadi <ybs@ybs.im>
|
||||||
|
- Yoshiki Nakagawa <yyoshiki41@gmail.com>
|
||||||
|
- Yoshiyuki Kinjo <yskkin+github@gmail.com>
|
||||||
|
- Yue Yang <g1enyy0ung@gmail.com>
|
||||||
|
- ZYunH <zyunhjob@163.com>
|
||||||
|
- Zach Newburgh <zach.newburgh@gmail.com>
|
||||||
|
- Zasda Yusuf Mikail <zasdaym@gmail.com>
|
||||||
|
- ZhangYunHao <zyunhjob@163.com>
|
||||||
|
- ZhiFeng Hu <hufeng1987@gmail.com>
|
||||||
|
- Zhu Xi <zhuxi910511@163.com>
|
||||||
|
- a2tt <usera2tt@gmail.com>
|
||||||
|
- ahuigo <1781999+ahuigo@users.noreply.github.com>
|
||||||
|
- ali <anio@users.noreply.github.com>
|
||||||
|
- aljun <salameryy@163.com>
|
||||||
|
- andrea <crypto.andrea@protonmail.ch>
|
||||||
|
- andriikushch <andrii.kushch@gmail.com>
|
||||||
|
- anoty <anjunyou@foxmail.com>
|
||||||
|
- awkj <hzzbiu@gmail.com>
|
||||||
|
- axiaoxin <254606826@qq.com>
|
||||||
|
- bbiao <bbbiao@gmail.com>
|
||||||
|
- bestgopher <84328409@qq.com>
|
||||||
|
- betahu <zhong.wenhuang@foxmail.com>
|
||||||
|
- bigwheel <k.bigwheel+eng@gmail.com>
|
||||||
|
- bn4t <17193640+bn4t@users.noreply.github.com>
|
||||||
|
- bullgare <bullgare@gmail.com>
|
||||||
|
- chainhelen <chainhelen@gmail.com>
|
||||||
|
- chenyang929 <chenyang929code@gmail.com>
|
||||||
|
- chriswhelix <chris.williams@helix.com>
|
||||||
|
- collinmsn <4130944@qq.com>
|
||||||
|
- cssivision <cssivision@gmail.com>
|
||||||
|
- danielalves <alves.lopes.dan@gmail.com>
|
||||||
|
- delphinus <delphinus@remora.cx>
|
||||||
|
- dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
|
||||||
|
- dickeyxxx <jeff@dickeyxxx.com>
|
||||||
|
- edebernis <emeric.debernis@gmail.com>
|
||||||
|
- error10 <error@ioerror.us>
|
||||||
|
- esplo <esplo@users.noreply.github.com>
|
||||||
|
- eudore <30709860+eudore@users.noreply.github.com>
|
||||||
|
- ffhelicopter <32922889+ffhelicopter@users.noreply.github.com>
|
||||||
|
- filikos <11477309+filikos@users.noreply.github.com>
|
||||||
|
- forging2012 <forging2012@users.noreply.github.com>
|
||||||
|
- goqihoo <goqihoo@gmail.com>
|
||||||
|
- grapeVine <treeui.old@gmail.com>
|
||||||
|
- guonaihong <guonaihong@qq.com>
|
||||||
|
- heige <daheige@users.noreply.github.com>
|
||||||
|
- heige <zhuwei313@hotmail.com>
|
||||||
|
- hellojukay <hellojukay@163.com>
|
||||||
|
- henrylee2cn <henrylee2cn@gmail.com>
|
||||||
|
- htobenothing <htobenothing@gmail.com>
|
||||||
|
- iamhesir <78344375+iamhesir@users.noreply.github.com>
|
||||||
|
- ijaa <kailiu2013@gmail.com>
|
||||||
|
- ishanray <ishan.iipm@gmail.com>
|
||||||
|
- ishanray <ishanray@users.noreply.github.com>
|
||||||
|
- itcloudy <272685110@qq.com>
|
||||||
|
- jarodsong6 <jarodsong6@gmail.com>
|
||||||
|
- jasonrhansen <jasonrodneyhansen@gmail.com>
|
||||||
|
- jincheng9 <perfume0607@gmail.com>
|
||||||
|
- joeADSP <75027008+joeADSP@users.noreply.github.com>
|
||||||
|
- junfengye <junfeng.yejf@gmail.com>
|
||||||
|
- kaiiak <aNxFi37X@outlook.com>
|
||||||
|
- kebo <kevinke2020@outlook.com>
|
||||||
|
- keke <19yamashita15@gmail.com>
|
||||||
|
- kishor kunal raj <68464660+kishorkunal-raj@users.noreply.github.com>
|
||||||
|
- kyledinh <kyledinh@gmail.com>
|
||||||
|
- lantw44 <lantw44@gmail.com>
|
||||||
|
- likakuli <1154584512@qq.com>
|
||||||
|
- linfangrong <linfangrong.liuxin@qq.com>
|
||||||
|
- linzi <873804682@qq.com>
|
||||||
|
- llgoer <yanghuxiao@vip.qq.com>
|
||||||
|
- long-road <13412081338@163.com>
|
||||||
|
- mbesancon <mathieu.besancon@gmail.com>
|
||||||
|
- mehdy <mehdy.khoshnoody@gmail.com>
|
||||||
|
- metal A-wing <freedom.awing.777@gmail.com>
|
||||||
|
- micanzhang <micanzhang@gmail.com>
|
||||||
|
- minarc <ragnhildmowinckel@gmail.com>
|
||||||
|
- mllu <mornlyn@gmail.com>
|
||||||
|
- mopemoepe <yutaka.matsubara@gmail.com>
|
||||||
|
- msoedov <msoedov@gmail.com>
|
||||||
|
- mstmdev <mstmdev@gmail.com>
|
||||||
|
- novaeye <fcoffee@gmail.com>
|
||||||
|
- olebedev <oolebedev@gmail.com>
|
||||||
|
- phithon <phith0n@users.noreply.github.com>
|
||||||
|
- pjgg <pablo.gonzalez.granados@gmail.com>
|
||||||
|
- qm012 <67568757+qm012@users.noreply.github.com>
|
||||||
|
- raymonder jin <rayjingithub@gmail.com>
|
||||||
|
- rns <ruslan.shvedov@gmail.com>
|
||||||
|
- root@andrea:~# <crypto.andrea@protonmail.ch>
|
||||||
|
- sekky0905 <20237968+sekky0905@users.noreply.github.com>
|
||||||
|
- senhtry <w169q169@gmail.com>
|
||||||
|
- shadrus <shadrus@gmail.com>
|
||||||
|
- silasb <silas.baronda@gmail.com>
|
||||||
|
- solos <lxl1217@gmail.com>
|
||||||
|
- songjiayang <songjiayang@users.noreply.github.com>
|
||||||
|
- sope <shenshouer@163.com>
|
||||||
|
- srt180 <30768686+srt180@users.noreply.github.com>
|
||||||
|
- stackerzzq <foo_stacker@yeah.net>
|
||||||
|
- sunshineplan <sunshineplan@users.noreply.github.com>
|
||||||
|
- syssam <s.y.s.sam.sys@gmail.com>
|
||||||
|
- techjanitor <puntme@gmail.com>
|
||||||
|
- techjanitor <techjanitor@users.noreply.github.com>
|
||||||
|
- thinkerou <thinkerou@gmail.com>
|
||||||
|
- thinkgo <49174849+thinkgos@users.noreply.github.com>
|
||||||
|
- tsirolnik <tsirolnik@users.noreply.github.com>
|
||||||
|
- tyltr <31768692+tylitianrui@users.noreply.github.com>
|
||||||
|
- vinhha96 <anhvinha1@gmail.com>
|
||||||
|
- voidman <retmain@foxmail.com>
|
||||||
|
- vz <vzvway@gmail.com>
|
||||||
|
- wei <wei840222@gmail.com>
|
||||||
|
- weibaohui <weibaohui@yeah.net>
|
||||||
|
- whirosan <whirosan@users.noreply.github.com>
|
||||||
|
- willnewrelic <will@newrelic.com>
|
||||||
|
- wssccc <wssccc@qq.com>
|
||||||
|
- wuhuizuo <wuhuizuo@126.com>
|
||||||
|
- xyb <xyb4638@gmail.com>
|
||||||
|
- y-yagi <yuuji.yaginuma@gmail.com>
|
||||||
|
- yiranzai <wuqingdzx@gmail.com>
|
||||||
|
- youzeliang <youzel@126.com>
|
||||||
|
- yugu <chenzilong_1227@foxmail.com>
|
||||||
|
- yuyabe <yuyabee@gmail.com>
|
||||||
|
- zebozhuang <zebozhuang@163.com>
|
||||||
|
- zero11-0203 <93071220+zero11-0203@users.noreply.github.com>
|
||||||
|
- zesani <7sin@outlook.co.th>
|
||||||
|
- zhanweidu <zhanweidu@163.com>
|
||||||
|
- zhing <zqwillseven@gmail.com>
|
||||||
|
- ziheng <zihenglv@gmail.com>
|
||||||
|
- zzjin <zzjin@users.noreply.github.com>
|
||||||
|
- 森 優太 <59682979+uta-mori@users.noreply.github.com>
|
||||||
|
- 杰哥 <858806258@qq.com>
|
||||||
|
- 涛叔 <hi@taoshu.in>
|
||||||
|
- 市民233 <mengrenxiong@gmail.com>
|
||||||
|
- 尹宝强 <wmdandme@gmail.com>
|
||||||
|
- 梦溪笔谈 <loongmxbt@gmail.com>
|
||||||
|
- 飞雪无情 <ls8707@gmail.com>
|
||||||
|
- 寻寻觅觅的Gopher <zoujh99@qq.com>
|
666
vendor/github.com/gin-gonic/gin/BENCHMARKS.md
generated
vendored
Normal file
666
vendor/github.com/gin-gonic/gin/BENCHMARKS.md
generated
vendored
Normal file
@ -0,0 +1,666 @@
|
|||||||
|
|
||||||
|
# Benchmark System
|
||||||
|
|
||||||
|
**VM HOST:** Travis
|
||||||
|
**Machine:** Ubuntu 16.04.6 LTS x64
|
||||||
|
**Date:** May 04th, 2020
|
||||||
|
**Version:** Gin v1.6.3
|
||||||
|
**Go Version:** 1.14.2 linux/amd64
|
||||||
|
**Source:** [Go HTTP Router Benchmark](https://github.com/gin-gonic/go-http-routing-benchmark)
|
||||||
|
**Result:** [See the gist](https://gist.github.com/appleboy/b5f2ecfaf50824ae9c64dcfb9165ae5e) or [Travis result](https://travis-ci.org/github/gin-gonic/go-http-routing-benchmark/jobs/682947061)
|
||||||
|
|
||||||
|
## Static Routes: 157
|
||||||
|
|
||||||
|
```sh
|
||||||
|
Gin: 34936 Bytes
|
||||||
|
|
||||||
|
HttpServeMux: 14512 Bytes
|
||||||
|
Ace: 30680 Bytes
|
||||||
|
Aero: 34536 Bytes
|
||||||
|
Bear: 30456 Bytes
|
||||||
|
Beego: 98456 Bytes
|
||||||
|
Bone: 40224 Bytes
|
||||||
|
Chi: 83608 Bytes
|
||||||
|
Denco: 10216 Bytes
|
||||||
|
Echo: 80328 Bytes
|
||||||
|
GocraftWeb: 55288 Bytes
|
||||||
|
Goji: 29744 Bytes
|
||||||
|
Gojiv2: 105840 Bytes
|
||||||
|
GoJsonRest: 137496 Bytes
|
||||||
|
GoRestful: 816936 Bytes
|
||||||
|
GorillaMux: 585632 Bytes
|
||||||
|
GowwwRouter: 24968 Bytes
|
||||||
|
HttpRouter: 21712 Bytes
|
||||||
|
HttpTreeMux: 73448 Bytes
|
||||||
|
Kocha: 115472 Bytes
|
||||||
|
LARS: 30640 Bytes
|
||||||
|
Macaron: 38592 Bytes
|
||||||
|
Martini: 310864 Bytes
|
||||||
|
Pat: 19696 Bytes
|
||||||
|
Possum: 89920 Bytes
|
||||||
|
R2router: 23712 Bytes
|
||||||
|
Rivet: 24608 Bytes
|
||||||
|
Tango: 28264 Bytes
|
||||||
|
TigerTonic: 78768 Bytes
|
||||||
|
Traffic: 538976 Bytes
|
||||||
|
Vulcan: 369960 Bytes
|
||||||
|
```
|
||||||
|
|
||||||
|
## GithubAPI Routes: 203
|
||||||
|
|
||||||
|
```sh
|
||||||
|
Gin: 58512 Bytes
|
||||||
|
|
||||||
|
Ace: 48688 Bytes
|
||||||
|
Aero: 318568 Bytes
|
||||||
|
Bear: 84248 Bytes
|
||||||
|
Beego: 150936 Bytes
|
||||||
|
Bone: 100976 Bytes
|
||||||
|
Chi: 95112 Bytes
|
||||||
|
Denco: 36736 Bytes
|
||||||
|
Echo: 100296 Bytes
|
||||||
|
GocraftWeb: 95432 Bytes
|
||||||
|
Goji: 49680 Bytes
|
||||||
|
Gojiv2: 104704 Bytes
|
||||||
|
GoJsonRest: 141976 Bytes
|
||||||
|
GoRestful: 1241656 Bytes
|
||||||
|
GorillaMux: 1322784 Bytes
|
||||||
|
GowwwRouter: 80008 Bytes
|
||||||
|
HttpRouter: 37144 Bytes
|
||||||
|
HttpTreeMux: 78800 Bytes
|
||||||
|
Kocha: 785120 Bytes
|
||||||
|
LARS: 48600 Bytes
|
||||||
|
Macaron: 92784 Bytes
|
||||||
|
Martini: 485264 Bytes
|
||||||
|
Pat: 21200 Bytes
|
||||||
|
Possum: 85312 Bytes
|
||||||
|
R2router: 47104 Bytes
|
||||||
|
Rivet: 42840 Bytes
|
||||||
|
Tango: 54840 Bytes
|
||||||
|
TigerTonic: 95264 Bytes
|
||||||
|
Traffic: 921744 Bytes
|
||||||
|
Vulcan: 425992 Bytes
|
||||||
|
```
|
||||||
|
|
||||||
|
## GPlusAPI Routes: 13
|
||||||
|
|
||||||
|
```sh
|
||||||
|
Gin: 4384 Bytes
|
||||||
|
|
||||||
|
Ace: 3712 Bytes
|
||||||
|
Aero: 26056 Bytes
|
||||||
|
Bear: 7112 Bytes
|
||||||
|
Beego: 10272 Bytes
|
||||||
|
Bone: 6688 Bytes
|
||||||
|
Chi: 8024 Bytes
|
||||||
|
Denco: 3264 Bytes
|
||||||
|
Echo: 9688 Bytes
|
||||||
|
GocraftWeb: 7496 Bytes
|
||||||
|
Goji: 3152 Bytes
|
||||||
|
Gojiv2: 7376 Bytes
|
||||||
|
GoJsonRest: 11400 Bytes
|
||||||
|
GoRestful: 74328 Bytes
|
||||||
|
GorillaMux: 66208 Bytes
|
||||||
|
GowwwRouter: 5744 Bytes
|
||||||
|
HttpRouter: 2808 Bytes
|
||||||
|
HttpTreeMux: 7440 Bytes
|
||||||
|
Kocha: 128880 Bytes
|
||||||
|
LARS: 3656 Bytes
|
||||||
|
Macaron: 8656 Bytes
|
||||||
|
Martini: 23920 Bytes
|
||||||
|
Pat: 1856 Bytes
|
||||||
|
Possum: 7248 Bytes
|
||||||
|
R2router: 3928 Bytes
|
||||||
|
Rivet: 3064 Bytes
|
||||||
|
Tango: 5168 Bytes
|
||||||
|
TigerTonic: 9408 Bytes
|
||||||
|
Traffic: 46400 Bytes
|
||||||
|
Vulcan: 25544 Bytes
|
||||||
|
```
|
||||||
|
|
||||||
|
## ParseAPI Routes: 26
|
||||||
|
|
||||||
|
```sh
|
||||||
|
Gin: 7776 Bytes
|
||||||
|
|
||||||
|
Ace: 6704 Bytes
|
||||||
|
Aero: 28488 Bytes
|
||||||
|
Bear: 12320 Bytes
|
||||||
|
Beego: 19280 Bytes
|
||||||
|
Bone: 11440 Bytes
|
||||||
|
Chi: 9744 Bytes
|
||||||
|
Denco: 4192 Bytes
|
||||||
|
Echo: 11664 Bytes
|
||||||
|
GocraftWeb: 12800 Bytes
|
||||||
|
Goji: 5680 Bytes
|
||||||
|
Gojiv2: 14464 Bytes
|
||||||
|
GoJsonRest: 14072 Bytes
|
||||||
|
GoRestful: 116264 Bytes
|
||||||
|
GorillaMux: 105880 Bytes
|
||||||
|
GowwwRouter: 9344 Bytes
|
||||||
|
HttpRouter: 5072 Bytes
|
||||||
|
HttpTreeMux: 7848 Bytes
|
||||||
|
Kocha: 181712 Bytes
|
||||||
|
LARS: 6632 Bytes
|
||||||
|
Macaron: 13648 Bytes
|
||||||
|
Martini: 45888 Bytes
|
||||||
|
Pat: 2560 Bytes
|
||||||
|
Possum: 9200 Bytes
|
||||||
|
R2router: 7056 Bytes
|
||||||
|
Rivet: 5680 Bytes
|
||||||
|
Tango: 8920 Bytes
|
||||||
|
TigerTonic: 9840 Bytes
|
||||||
|
Traffic: 79096 Bytes
|
||||||
|
Vulcan: 44504 Bytes
|
||||||
|
```
|
||||||
|
|
||||||
|
## Static Routes
|
||||||
|
|
||||||
|
```sh
|
||||||
|
BenchmarkGin_StaticAll 62169 19319 ns/op 0 B/op 0 allocs/op
|
||||||
|
|
||||||
|
BenchmarkAce_StaticAll 65428 18313 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkAero_StaticAll 121132 9632 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkHttpServeMux_StaticAll 52626 22758 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkBeego_StaticAll 9962 179058 ns/op 55264 B/op 471 allocs/op
|
||||||
|
BenchmarkBear_StaticAll 14894 80966 ns/op 20272 B/op 469 allocs/op
|
||||||
|
BenchmarkBone_StaticAll 18718 64065 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkChi_StaticAll 10000 149827 ns/op 67824 B/op 471 allocs/op
|
||||||
|
BenchmarkDenco_StaticAll 211393 5680 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkEcho_StaticAll 49341 24343 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkGocraftWeb_StaticAll 10000 126209 ns/op 46312 B/op 785 allocs/op
|
||||||
|
BenchmarkGoji_StaticAll 27956 43174 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkGojiv2_StaticAll 3430 370718 ns/op 205984 B/op 1570 allocs/op
|
||||||
|
BenchmarkGoJsonRest_StaticAll 9134 188888 ns/op 51653 B/op 1727 allocs/op
|
||||||
|
BenchmarkGoRestful_StaticAll 706 1703330 ns/op 613280 B/op 2053 allocs/op
|
||||||
|
BenchmarkGorillaMux_StaticAll 1268 924083 ns/op 153233 B/op 1413 allocs/op
|
||||||
|
BenchmarkGowwwRouter_StaticAll 63374 18935 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkHttpRouter_StaticAll 109938 10902 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkHttpTreeMux_StaticAll 109166 10861 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkKocha_StaticAll 92258 12992 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkLARS_StaticAll 65200 18387 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkMacaron_StaticAll 5671 291501 ns/op 115553 B/op 1256 allocs/op
|
||||||
|
BenchmarkMartini_StaticAll 807 1460498 ns/op 125444 B/op 1717 allocs/op
|
||||||
|
BenchmarkPat_StaticAll 513 2342396 ns/op 602832 B/op 12559 allocs/op
|
||||||
|
BenchmarkPossum_StaticAll 10000 128270 ns/op 65312 B/op 471 allocs/op
|
||||||
|
BenchmarkR2router_StaticAll 16726 71760 ns/op 22608 B/op 628 allocs/op
|
||||||
|
BenchmarkRivet_StaticAll 41722 28723 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkTango_StaticAll 7606 205082 ns/op 39209 B/op 1256 allocs/op
|
||||||
|
BenchmarkTigerTonic_StaticAll 26247 45806 ns/op 7376 B/op 157 allocs/op
|
||||||
|
BenchmarkTraffic_StaticAll 550 2284518 ns/op 754864 B/op 14601 allocs/op
|
||||||
|
BenchmarkVulcan_StaticAll 10000 131343 ns/op 15386 B/op 471 allocs/op
|
||||||
|
```
|
||||||
|
|
||||||
|
## Micro Benchmarks
|
||||||
|
|
||||||
|
```sh
|
||||||
|
BenchmarkGin_Param 18785022 63.9 ns/op 0 B/op 0 allocs/op
|
||||||
|
|
||||||
|
BenchmarkAce_Param 14689765 81.5 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkAero_Param 23094770 51.2 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkBear_Param 1417045 845 ns/op 456 B/op 5 allocs/op
|
||||||
|
BenchmarkBeego_Param 1000000 1080 ns/op 352 B/op 3 allocs/op
|
||||||
|
BenchmarkBone_Param 1000000 1463 ns/op 816 B/op 6 allocs/op
|
||||||
|
BenchmarkChi_Param 1378756 885 ns/op 432 B/op 3 allocs/op
|
||||||
|
BenchmarkDenco_Param 8557899 143 ns/op 32 B/op 1 allocs/op
|
||||||
|
BenchmarkEcho_Param 16433347 75.5 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkGocraftWeb_Param 1000000 1218 ns/op 648 B/op 8 allocs/op
|
||||||
|
BenchmarkGoji_Param 1921248 617 ns/op 336 B/op 2 allocs/op
|
||||||
|
BenchmarkGojiv2_Param 561848 2156 ns/op 1328 B/op 11 allocs/op
|
||||||
|
BenchmarkGoJsonRest_Param 1000000 1358 ns/op 649 B/op 13 allocs/op
|
||||||
|
BenchmarkGoRestful_Param 224857 5307 ns/op 4192 B/op 14 allocs/op
|
||||||
|
BenchmarkGorillaMux_Param 498313 2459 ns/op 1280 B/op 10 allocs/op
|
||||||
|
BenchmarkGowwwRouter_Param 1864354 654 ns/op 432 B/op 3 allocs/op
|
||||||
|
BenchmarkHttpRouter_Param 26269074 47.7 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkHttpTreeMux_Param 2109829 557 ns/op 352 B/op 3 allocs/op
|
||||||
|
BenchmarkKocha_Param 5050216 243 ns/op 56 B/op 3 allocs/op
|
||||||
|
BenchmarkLARS_Param 19811712 59.9 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkMacaron_Param 662746 2329 ns/op 1072 B/op 10 allocs/op
|
||||||
|
BenchmarkMartini_Param 279902 4260 ns/op 1072 B/op 10 allocs/op
|
||||||
|
BenchmarkPat_Param 1000000 1382 ns/op 536 B/op 11 allocs/op
|
||||||
|
BenchmarkPossum_Param 1000000 1014 ns/op 496 B/op 5 allocs/op
|
||||||
|
BenchmarkR2router_Param 1712559 707 ns/op 432 B/op 5 allocs/op
|
||||||
|
BenchmarkRivet_Param 6648086 182 ns/op 48 B/op 1 allocs/op
|
||||||
|
BenchmarkTango_Param 1221504 994 ns/op 248 B/op 8 allocs/op
|
||||||
|
BenchmarkTigerTonic_Param 891661 2261 ns/op 776 B/op 16 allocs/op
|
||||||
|
BenchmarkTraffic_Param 350059 3598 ns/op 1856 B/op 21 allocs/op
|
||||||
|
BenchmarkVulcan_Param 2517823 472 ns/op 98 B/op 3 allocs/op
|
||||||
|
BenchmarkAce_Param5 9214365 130 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkAero_Param5 15369013 77.9 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkBear_Param5 1000000 1113 ns/op 501 B/op 5 allocs/op
|
||||||
|
BenchmarkBeego_Param5 1000000 1269 ns/op 352 B/op 3 allocs/op
|
||||||
|
BenchmarkBone_Param5 986820 1873 ns/op 864 B/op 6 allocs/op
|
||||||
|
BenchmarkChi_Param5 1000000 1156 ns/op 432 B/op 3 allocs/op
|
||||||
|
BenchmarkDenco_Param5 3036331 400 ns/op 160 B/op 1 allocs/op
|
||||||
|
BenchmarkEcho_Param5 6447133 186 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkGin_Param5 10786068 110 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkGocraftWeb_Param5 844820 1944 ns/op 920 B/op 11 allocs/op
|
||||||
|
BenchmarkGoji_Param5 1474965 827 ns/op 336 B/op 2 allocs/op
|
||||||
|
BenchmarkGojiv2_Param5 442820 2516 ns/op 1392 B/op 11 allocs/op
|
||||||
|
BenchmarkGoJsonRest_Param5 507555 2711 ns/op 1097 B/op 16 allocs/op
|
||||||
|
BenchmarkGoRestful_Param5 216481 6093 ns/op 4288 B/op 14 allocs/op
|
||||||
|
BenchmarkGorillaMux_Param5 314402 3628 ns/op 1344 B/op 10 allocs/op
|
||||||
|
BenchmarkGowwwRouter_Param5 1624660 733 ns/op 432 B/op 3 allocs/op
|
||||||
|
BenchmarkHttpRouter_Param5 13167324 92.0 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkHttpTreeMux_Param5 1000000 1295 ns/op 576 B/op 6 allocs/op
|
||||||
|
BenchmarkKocha_Param5 1000000 1138 ns/op 440 B/op 10 allocs/op
|
||||||
|
BenchmarkLARS_Param5 11580613 105 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkMacaron_Param5 473596 2755 ns/op 1072 B/op 10 allocs/op
|
||||||
|
BenchmarkMartini_Param5 230756 5111 ns/op 1232 B/op 11 allocs/op
|
||||||
|
BenchmarkPat_Param5 469190 3370 ns/op 888 B/op 29 allocs/op
|
||||||
|
BenchmarkPossum_Param5 1000000 1002 ns/op 496 B/op 5 allocs/op
|
||||||
|
BenchmarkR2router_Param5 1422129 844 ns/op 432 B/op 5 allocs/op
|
||||||
|
BenchmarkRivet_Param5 2263789 539 ns/op 240 B/op 1 allocs/op
|
||||||
|
BenchmarkTango_Param5 1000000 1256 ns/op 360 B/op 8 allocs/op
|
||||||
|
BenchmarkTigerTonic_Param5 175500 7492 ns/op 2279 B/op 39 allocs/op
|
||||||
|
BenchmarkTraffic_Param5 233631 5816 ns/op 2208 B/op 27 allocs/op
|
||||||
|
BenchmarkVulcan_Param5 1923416 629 ns/op 98 B/op 3 allocs/op
|
||||||
|
BenchmarkAce_Param20 4321266 281 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkAero_Param20 31501641 35.2 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkBear_Param20 335204 3489 ns/op 1665 B/op 5 allocs/op
|
||||||
|
BenchmarkBeego_Param20 503674 2860 ns/op 352 B/op 3 allocs/op
|
||||||
|
BenchmarkBone_Param20 298922 4741 ns/op 2031 B/op 6 allocs/op
|
||||||
|
BenchmarkChi_Param20 878181 1957 ns/op 432 B/op 3 allocs/op
|
||||||
|
BenchmarkDenco_Param20 1000000 1360 ns/op 640 B/op 1 allocs/op
|
||||||
|
BenchmarkEcho_Param20 2104946 580 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkGin_Param20 4167204 290 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkGocraftWeb_Param20 173064 7514 ns/op 3796 B/op 15 allocs/op
|
||||||
|
BenchmarkGoji_Param20 458778 2651 ns/op 1247 B/op 2 allocs/op
|
||||||
|
BenchmarkGojiv2_Param20 364862 3178 ns/op 1632 B/op 11 allocs/op
|
||||||
|
BenchmarkGoJsonRest_Param20 125514 9760 ns/op 4485 B/op 20 allocs/op
|
||||||
|
BenchmarkGoRestful_Param20 101217 11964 ns/op 6715 B/op 18 allocs/op
|
||||||
|
BenchmarkGorillaMux_Param20 147654 8132 ns/op 3452 B/op 12 allocs/op
|
||||||
|
BenchmarkGowwwRouter_Param20 1000000 1225 ns/op 432 B/op 3 allocs/op
|
||||||
|
BenchmarkHttpRouter_Param20 4920895 247 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkHttpTreeMux_Param20 173202 6605 ns/op 3196 B/op 10 allocs/op
|
||||||
|
BenchmarkKocha_Param20 345988 3620 ns/op 1808 B/op 27 allocs/op
|
||||||
|
BenchmarkLARS_Param20 4592326 262 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkMacaron_Param20 166492 7286 ns/op 2924 B/op 12 allocs/op
|
||||||
|
BenchmarkMartini_Param20 122162 10653 ns/op 3595 B/op 13 allocs/op
|
||||||
|
BenchmarkPat_Param20 78630 15239 ns/op 4424 B/op 93 allocs/op
|
||||||
|
BenchmarkPossum_Param20 1000000 1008 ns/op 496 B/op 5 allocs/op
|
||||||
|
BenchmarkR2router_Param20 294981 4587 ns/op 2284 B/op 7 allocs/op
|
||||||
|
BenchmarkRivet_Param20 691798 2090 ns/op 1024 B/op 1 allocs/op
|
||||||
|
BenchmarkTango_Param20 842440 2505 ns/op 856 B/op 8 allocs/op
|
||||||
|
BenchmarkTigerTonic_Param20 38614 31509 ns/op 9870 B/op 119 allocs/op
|
||||||
|
BenchmarkTraffic_Param20 57633 21107 ns/op 7853 B/op 47 allocs/op
|
||||||
|
BenchmarkVulcan_Param20 1000000 1178 ns/op 98 B/op 3 allocs/op
|
||||||
|
BenchmarkAce_ParamWrite 7330743 180 ns/op 8 B/op 1 allocs/op
|
||||||
|
BenchmarkAero_ParamWrite 13833598 86.7 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkBear_ParamWrite 1363321 867 ns/op 456 B/op 5 allocs/op
|
||||||
|
BenchmarkBeego_ParamWrite 1000000 1104 ns/op 360 B/op 4 allocs/op
|
||||||
|
BenchmarkBone_ParamWrite 1000000 1475 ns/op 816 B/op 6 allocs/op
|
||||||
|
BenchmarkChi_ParamWrite 1320590 892 ns/op 432 B/op 3 allocs/op
|
||||||
|
BenchmarkDenco_ParamWrite 7093605 172 ns/op 32 B/op 1 allocs/op
|
||||||
|
BenchmarkEcho_ParamWrite 8434424 161 ns/op 8 B/op 1 allocs/op
|
||||||
|
BenchmarkGin_ParamWrite 10377034 118 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkGocraftWeb_ParamWrite 1000000 1266 ns/op 656 B/op 9 allocs/op
|
||||||
|
BenchmarkGoji_ParamWrite 1874168 654 ns/op 336 B/op 2 allocs/op
|
||||||
|
BenchmarkGojiv2_ParamWrite 459032 2352 ns/op 1360 B/op 13 allocs/op
|
||||||
|
BenchmarkGoJsonRest_ParamWrite 499434 2145 ns/op 1128 B/op 18 allocs/op
|
||||||
|
BenchmarkGoRestful_ParamWrite 241087 5470 ns/op 4200 B/op 15 allocs/op
|
||||||
|
BenchmarkGorillaMux_ParamWrite 425686 2522 ns/op 1280 B/op 10 allocs/op
|
||||||
|
BenchmarkGowwwRouter_ParamWrite 922172 1778 ns/op 976 B/op 8 allocs/op
|
||||||
|
BenchmarkHttpRouter_ParamWrite 15392049 77.7 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkHttpTreeMux_ParamWrite 1973385 597 ns/op 352 B/op 3 allocs/op
|
||||||
|
BenchmarkKocha_ParamWrite 4262500 281 ns/op 56 B/op 3 allocs/op
|
||||||
|
BenchmarkLARS_ParamWrite 10764410 113 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkMacaron_ParamWrite 486769 2726 ns/op 1176 B/op 14 allocs/op
|
||||||
|
BenchmarkMartini_ParamWrite 264804 4842 ns/op 1176 B/op 14 allocs/op
|
||||||
|
BenchmarkPat_ParamWrite 735116 2047 ns/op 960 B/op 15 allocs/op
|
||||||
|
BenchmarkPossum_ParamWrite 1000000 1004 ns/op 496 B/op 5 allocs/op
|
||||||
|
BenchmarkR2router_ParamWrite 1592136 768 ns/op 432 B/op 5 allocs/op
|
||||||
|
BenchmarkRivet_ParamWrite 3582051 339 ns/op 112 B/op 2 allocs/op
|
||||||
|
BenchmarkTango_ParamWrite 2237337 534 ns/op 136 B/op 4 allocs/op
|
||||||
|
BenchmarkTigerTonic_ParamWrite 439608 3136 ns/op 1216 B/op 21 allocs/op
|
||||||
|
BenchmarkTraffic_ParamWrite 306979 4328 ns/op 2280 B/op 25 allocs/op
|
||||||
|
BenchmarkVulcan_ParamWrite 2529973 472 ns/op 98 B/op 3 allocs/op
|
||||||
|
```
|
||||||
|
|
||||||
|
## GitHub
|
||||||
|
|
||||||
|
```sh
|
||||||
|
BenchmarkGin_GithubStatic 15629472 76.7 ns/op 0 B/op 0 allocs/op
|
||||||
|
|
||||||
|
BenchmarkAce_GithubStatic 15542612 75.9 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkAero_GithubStatic 24777151 48.5 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkBear_GithubStatic 2788894 435 ns/op 120 B/op 3 allocs/op
|
||||||
|
BenchmarkBeego_GithubStatic 1000000 1064 ns/op 352 B/op 3 allocs/op
|
||||||
|
BenchmarkBone_GithubStatic 93507 12838 ns/op 2880 B/op 60 allocs/op
|
||||||
|
BenchmarkChi_GithubStatic 1387743 860 ns/op 432 B/op 3 allocs/op
|
||||||
|
BenchmarkDenco_GithubStatic 39384996 30.4 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkEcho_GithubStatic 12076382 99.1 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkGocraftWeb_GithubStatic 1596495 756 ns/op 296 B/op 5 allocs/op
|
||||||
|
BenchmarkGoji_GithubStatic 6364876 189 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkGojiv2_GithubStatic 550202 2098 ns/op 1312 B/op 10 allocs/op
|
||||||
|
BenchmarkGoRestful_GithubStatic 102183 12552 ns/op 4256 B/op 13 allocs/op
|
||||||
|
BenchmarkGoJsonRest_GithubStatic 1000000 1029 ns/op 329 B/op 11 allocs/op
|
||||||
|
BenchmarkGorillaMux_GithubStatic 255552 5190 ns/op 976 B/op 9 allocs/op
|
||||||
|
BenchmarkGowwwRouter_GithubStatic 15531916 77.1 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkHttpRouter_GithubStatic 27920724 43.1 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkHttpTreeMux_GithubStatic 21448953 55.8 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkKocha_GithubStatic 21405310 56.0 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkLARS_GithubStatic 13625156 89.0 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkMacaron_GithubStatic 1000000 1747 ns/op 736 B/op 8 allocs/op
|
||||||
|
BenchmarkMartini_GithubStatic 187186 7326 ns/op 768 B/op 9 allocs/op
|
||||||
|
BenchmarkPat_GithubStatic 109143 11563 ns/op 3648 B/op 76 allocs/op
|
||||||
|
BenchmarkPossum_GithubStatic 1575898 770 ns/op 416 B/op 3 allocs/op
|
||||||
|
BenchmarkR2router_GithubStatic 3046231 404 ns/op 144 B/op 4 allocs/op
|
||||||
|
BenchmarkRivet_GithubStatic 11484826 105 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkTango_GithubStatic 1000000 1153 ns/op 248 B/op 8 allocs/op
|
||||||
|
BenchmarkTigerTonic_GithubStatic 4929780 249 ns/op 48 B/op 1 allocs/op
|
||||||
|
BenchmarkTraffic_GithubStatic 106351 11819 ns/op 4664 B/op 90 allocs/op
|
||||||
|
BenchmarkVulcan_GithubStatic 1613271 722 ns/op 98 B/op 3 allocs/op
|
||||||
|
BenchmarkAce_GithubParam 8386032 143 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkAero_GithubParam 11816200 102 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkBear_GithubParam 1000000 1012 ns/op 496 B/op 5 allocs/op
|
||||||
|
BenchmarkBeego_GithubParam 1000000 1157 ns/op 352 B/op 3 allocs/op
|
||||||
|
BenchmarkBone_GithubParam 184653 6912 ns/op 1888 B/op 19 allocs/op
|
||||||
|
BenchmarkChi_GithubParam 1000000 1102 ns/op 432 B/op 3 allocs/op
|
||||||
|
BenchmarkDenco_GithubParam 3484798 352 ns/op 128 B/op 1 allocs/op
|
||||||
|
BenchmarkEcho_GithubParam 6337380 189 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkGin_GithubParam 9132032 131 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkGocraftWeb_GithubParam 1000000 1446 ns/op 712 B/op 9 allocs/op
|
||||||
|
BenchmarkGoji_GithubParam 1248640 977 ns/op 336 B/op 2 allocs/op
|
||||||
|
BenchmarkGojiv2_GithubParam 383233 2784 ns/op 1408 B/op 13 allocs/op
|
||||||
|
BenchmarkGoJsonRest_GithubParam 1000000 1991 ns/op 713 B/op 14 allocs/op
|
||||||
|
BenchmarkGoRestful_GithubParam 76414 16015 ns/op 4352 B/op 16 allocs/op
|
||||||
|
BenchmarkGorillaMux_GithubParam 150026 7663 ns/op 1296 B/op 10 allocs/op
|
||||||
|
BenchmarkGowwwRouter_GithubParam 1592044 751 ns/op 432 B/op 3 allocs/op
|
||||||
|
BenchmarkHttpRouter_GithubParam 10420628 115 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkHttpTreeMux_GithubParam 1403755 835 ns/op 384 B/op 4 allocs/op
|
||||||
|
BenchmarkKocha_GithubParam 2286170 533 ns/op 128 B/op 5 allocs/op
|
||||||
|
BenchmarkLARS_GithubParam 9540374 129 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkMacaron_GithubParam 533154 2742 ns/op 1072 B/op 10 allocs/op
|
||||||
|
BenchmarkMartini_GithubParam 119397 9638 ns/op 1152 B/op 11 allocs/op
|
||||||
|
BenchmarkPat_GithubParam 150675 8858 ns/op 2408 B/op 48 allocs/op
|
||||||
|
BenchmarkPossum_GithubParam 1000000 1001 ns/op 496 B/op 5 allocs/op
|
||||||
|
BenchmarkR2router_GithubParam 1602886 761 ns/op 432 B/op 5 allocs/op
|
||||||
|
BenchmarkRivet_GithubParam 2986579 409 ns/op 96 B/op 1 allocs/op
|
||||||
|
BenchmarkTango_GithubParam 1000000 1356 ns/op 344 B/op 8 allocs/op
|
||||||
|
BenchmarkTigerTonic_GithubParam 388899 3429 ns/op 1176 B/op 22 allocs/op
|
||||||
|
BenchmarkTraffic_GithubParam 123160 9734 ns/op 2816 B/op 40 allocs/op
|
||||||
|
BenchmarkVulcan_GithubParam 1000000 1138 ns/op 98 B/op 3 allocs/op
|
||||||
|
BenchmarkAce_GithubAll 40543 29670 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkAero_GithubAll 57632 20648 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkBear_GithubAll 9234 216179 ns/op 86448 B/op 943 allocs/op
|
||||||
|
BenchmarkBeego_GithubAll 7407 243496 ns/op 71456 B/op 609 allocs/op
|
||||||
|
BenchmarkBone_GithubAll 420 2922835 ns/op 720160 B/op 8620 allocs/op
|
||||||
|
BenchmarkChi_GithubAll 7620 238331 ns/op 87696 B/op 609 allocs/op
|
||||||
|
BenchmarkDenco_GithubAll 18355 64494 ns/op 20224 B/op 167 allocs/op
|
||||||
|
BenchmarkEcho_GithubAll 31251 38479 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkGin_GithubAll 43550 27364 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkGocraftWeb_GithubAll 4117 300062 ns/op 131656 B/op 1686 allocs/op
|
||||||
|
BenchmarkGoji_GithubAll 3274 416158 ns/op 56112 B/op 334 allocs/op
|
||||||
|
BenchmarkGojiv2_GithubAll 1402 870518 ns/op 352720 B/op 4321 allocs/op
|
||||||
|
BenchmarkGoJsonRest_GithubAll 2976 401507 ns/op 134371 B/op 2737 allocs/op
|
||||||
|
BenchmarkGoRestful_GithubAll 410 2913158 ns/op 910144 B/op 2938 allocs/op
|
||||||
|
BenchmarkGorillaMux_GithubAll 346 3384987 ns/op 251650 B/op 1994 allocs/op
|
||||||
|
BenchmarkGowwwRouter_GithubAll 10000 143025 ns/op 72144 B/op 501 allocs/op
|
||||||
|
BenchmarkHttpRouter_GithubAll 55938 21360 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkHttpTreeMux_GithubAll 10000 153944 ns/op 65856 B/op 671 allocs/op
|
||||||
|
BenchmarkKocha_GithubAll 10000 106315 ns/op 23304 B/op 843 allocs/op
|
||||||
|
BenchmarkLARS_GithubAll 47779 25084 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkMacaron_GithubAll 3266 371907 ns/op 149409 B/op 1624 allocs/op
|
||||||
|
BenchmarkMartini_GithubAll 331 3444706 ns/op 226551 B/op 2325 allocs/op
|
||||||
|
BenchmarkPat_GithubAll 273 4381818 ns/op 1483152 B/op 26963 allocs/op
|
||||||
|
BenchmarkPossum_GithubAll 10000 164367 ns/op 84448 B/op 609 allocs/op
|
||||||
|
BenchmarkR2router_GithubAll 10000 160220 ns/op 77328 B/op 979 allocs/op
|
||||||
|
BenchmarkRivet_GithubAll 14625 82453 ns/op 16272 B/op 167 allocs/op
|
||||||
|
BenchmarkTango_GithubAll 6255 279611 ns/op 63826 B/op 1618 allocs/op
|
||||||
|
BenchmarkTigerTonic_GithubAll 2008 687874 ns/op 193856 B/op 4474 allocs/op
|
||||||
|
BenchmarkTraffic_GithubAll 355 3478508 ns/op 820744 B/op 14114 allocs/op
|
||||||
|
BenchmarkVulcan_GithubAll 6885 193333 ns/op 19894 B/op 609 allocs/op
|
||||||
|
```
|
||||||
|
|
||||||
|
## Google+
|
||||||
|
|
||||||
|
```sh
|
||||||
|
BenchmarkGin_GPlusStatic 19247326 62.2 ns/op 0 B/op 0 allocs/op
|
||||||
|
|
||||||
|
BenchmarkAce_GPlusStatic 20235060 59.2 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkAero_GPlusStatic 31978935 37.6 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkBear_GPlusStatic 3516523 341 ns/op 104 B/op 3 allocs/op
|
||||||
|
BenchmarkBeego_GPlusStatic 1212036 991 ns/op 352 B/op 3 allocs/op
|
||||||
|
BenchmarkBone_GPlusStatic 6736242 183 ns/op 32 B/op 1 allocs/op
|
||||||
|
BenchmarkChi_GPlusStatic 1490640 814 ns/op 432 B/op 3 allocs/op
|
||||||
|
BenchmarkDenco_GPlusStatic 55006856 21.8 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkEcho_GPlusStatic 17688258 67.9 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkGocraftWeb_GPlusStatic 1829181 666 ns/op 280 B/op 5 allocs/op
|
||||||
|
BenchmarkGoji_GPlusStatic 9147451 130 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkGojiv2_GPlusStatic 594015 2063 ns/op 1312 B/op 10 allocs/op
|
||||||
|
BenchmarkGoJsonRest_GPlusStatic 1264906 950 ns/op 329 B/op 11 allocs/op
|
||||||
|
BenchmarkGoRestful_GPlusStatic 231558 5341 ns/op 3872 B/op 13 allocs/op
|
||||||
|
BenchmarkGorillaMux_GPlusStatic 908418 1809 ns/op 976 B/op 9 allocs/op
|
||||||
|
BenchmarkGowwwRouter_GPlusStatic 40684604 29.5 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkHttpRouter_GPlusStatic 46742804 25.7 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkHttpTreeMux_GPlusStatic 32567161 36.9 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkKocha_GPlusStatic 33800060 35.3 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkLARS_GPlusStatic 20431858 60.0 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkMacaron_GPlusStatic 1000000 1745 ns/op 736 B/op 8 allocs/op
|
||||||
|
BenchmarkMartini_GPlusStatic 442248 3619 ns/op 768 B/op 9 allocs/op
|
||||||
|
BenchmarkPat_GPlusStatic 4328004 292 ns/op 96 B/op 2 allocs/op
|
||||||
|
BenchmarkPossum_GPlusStatic 1570753 763 ns/op 416 B/op 3 allocs/op
|
||||||
|
BenchmarkR2router_GPlusStatic 3339474 355 ns/op 144 B/op 4 allocs/op
|
||||||
|
BenchmarkRivet_GPlusStatic 18570961 64.7 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkTango_GPlusStatic 1388702 860 ns/op 200 B/op 8 allocs/op
|
||||||
|
BenchmarkTigerTonic_GPlusStatic 7803543 159 ns/op 32 B/op 1 allocs/op
|
||||||
|
BenchmarkTraffic_GPlusStatic 878605 2171 ns/op 1112 B/op 16 allocs/op
|
||||||
|
BenchmarkVulcan_GPlusStatic 2742446 437 ns/op 98 B/op 3 allocs/op
|
||||||
|
BenchmarkAce_GPlusParam 11626975 105 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkAero_GPlusParam 16914322 71.6 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkBear_GPlusParam 1405173 832 ns/op 480 B/op 5 allocs/op
|
||||||
|
BenchmarkBeego_GPlusParam 1000000 1075 ns/op 352 B/op 3 allocs/op
|
||||||
|
BenchmarkBone_GPlusParam 1000000 1557 ns/op 816 B/op 6 allocs/op
|
||||||
|
BenchmarkChi_GPlusParam 1347926 894 ns/op 432 B/op 3 allocs/op
|
||||||
|
BenchmarkDenco_GPlusParam 5513000 212 ns/op 64 B/op 1 allocs/op
|
||||||
|
BenchmarkEcho_GPlusParam 11884383 101 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkGin_GPlusParam 12898952 93.1 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkGocraftWeb_GPlusParam 1000000 1194 ns/op 648 B/op 8 allocs/op
|
||||||
|
BenchmarkGoji_GPlusParam 1857229 645 ns/op 336 B/op 2 allocs/op
|
||||||
|
BenchmarkGojiv2_GPlusParam 520939 2322 ns/op 1328 B/op 11 allocs/op
|
||||||
|
BenchmarkGoJsonRest_GPlusParam 1000000 1536 ns/op 649 B/op 13 allocs/op
|
||||||
|
BenchmarkGoRestful_GPlusParam 205449 5800 ns/op 4192 B/op 14 allocs/op
|
||||||
|
BenchmarkGorillaMux_GPlusParam 395310 3188 ns/op 1280 B/op 10 allocs/op
|
||||||
|
BenchmarkGowwwRouter_GPlusParam 1851798 667 ns/op 432 B/op 3 allocs/op
|
||||||
|
BenchmarkHttpRouter_GPlusParam 18420789 65.2 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkHttpTreeMux_GPlusParam 1878463 629 ns/op 352 B/op 3 allocs/op
|
||||||
|
BenchmarkKocha_GPlusParam 4495610 273 ns/op 56 B/op 3 allocs/op
|
||||||
|
BenchmarkLARS_GPlusParam 14615976 83.2 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkMacaron_GPlusParam 584145 2549 ns/op 1072 B/op 10 allocs/op
|
||||||
|
BenchmarkMartini_GPlusParam 250501 4583 ns/op 1072 B/op 10 allocs/op
|
||||||
|
BenchmarkPat_GPlusParam 1000000 1645 ns/op 576 B/op 11 allocs/op
|
||||||
|
BenchmarkPossum_GPlusParam 1000000 1008 ns/op 496 B/op 5 allocs/op
|
||||||
|
BenchmarkR2router_GPlusParam 1708191 688 ns/op 432 B/op 5 allocs/op
|
||||||
|
BenchmarkRivet_GPlusParam 5795014 211 ns/op 48 B/op 1 allocs/op
|
||||||
|
BenchmarkTango_GPlusParam 1000000 1091 ns/op 264 B/op 8 allocs/op
|
||||||
|
BenchmarkTigerTonic_GPlusParam 760221 2489 ns/op 856 B/op 16 allocs/op
|
||||||
|
BenchmarkTraffic_GPlusParam 309774 4039 ns/op 1872 B/op 21 allocs/op
|
||||||
|
BenchmarkVulcan_GPlusParam 1935730 623 ns/op 98 B/op 3 allocs/op
|
||||||
|
BenchmarkAce_GPlus2Params 9158314 134 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkAero_GPlus2Params 11300517 107 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkBear_GPlus2Params 1239238 961 ns/op 496 B/op 5 allocs/op
|
||||||
|
BenchmarkBeego_GPlus2Params 1000000 1202 ns/op 352 B/op 3 allocs/op
|
||||||
|
BenchmarkBone_GPlus2Params 335576 3725 ns/op 1168 B/op 10 allocs/op
|
||||||
|
BenchmarkChi_GPlus2Params 1000000 1014 ns/op 432 B/op 3 allocs/op
|
||||||
|
BenchmarkDenco_GPlus2Params 4394598 280 ns/op 64 B/op 1 allocs/op
|
||||||
|
BenchmarkEcho_GPlus2Params 7851861 154 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkGin_GPlus2Params 9958588 120 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkGocraftWeb_GPlus2Params 1000000 1433 ns/op 712 B/op 9 allocs/op
|
||||||
|
BenchmarkGoji_GPlus2Params 1325134 909 ns/op 336 B/op 2 allocs/op
|
||||||
|
BenchmarkGojiv2_GPlus2Params 405955 2870 ns/op 1408 B/op 14 allocs/op
|
||||||
|
BenchmarkGoJsonRest_GPlus2Params 977038 1987 ns/op 713 B/op 14 allocs/op
|
||||||
|
BenchmarkGoRestful_GPlus2Params 205018 6142 ns/op 4384 B/op 16 allocs/op
|
||||||
|
BenchmarkGorillaMux_GPlus2Params 205641 6015 ns/op 1296 B/op 10 allocs/op
|
||||||
|
BenchmarkGowwwRouter_GPlus2Params 1748542 684 ns/op 432 B/op 3 allocs/op
|
||||||
|
BenchmarkHttpRouter_GPlus2Params 14047102 87.7 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkHttpTreeMux_GPlus2Params 1418673 828 ns/op 384 B/op 4 allocs/op
|
||||||
|
BenchmarkKocha_GPlus2Params 2334562 520 ns/op 128 B/op 5 allocs/op
|
||||||
|
BenchmarkLARS_GPlus2Params 11954094 101 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkMacaron_GPlus2Params 491552 2890 ns/op 1072 B/op 10 allocs/op
|
||||||
|
BenchmarkMartini_GPlus2Params 120532 9545 ns/op 1200 B/op 13 allocs/op
|
||||||
|
BenchmarkPat_GPlus2Params 194739 6766 ns/op 2168 B/op 33 allocs/op
|
||||||
|
BenchmarkPossum_GPlus2Params 1201224 1009 ns/op 496 B/op 5 allocs/op
|
||||||
|
BenchmarkR2router_GPlus2Params 1575535 756 ns/op 432 B/op 5 allocs/op
|
||||||
|
BenchmarkRivet_GPlus2Params 3698930 325 ns/op 96 B/op 1 allocs/op
|
||||||
|
BenchmarkTango_GPlus2Params 1000000 1212 ns/op 344 B/op 8 allocs/op
|
||||||
|
BenchmarkTigerTonic_GPlus2Params 349350 3660 ns/op 1200 B/op 22 allocs/op
|
||||||
|
BenchmarkTraffic_GPlus2Params 169714 7862 ns/op 2248 B/op 28 allocs/op
|
||||||
|
BenchmarkVulcan_GPlus2Params 1222288 974 ns/op 98 B/op 3 allocs/op
|
||||||
|
BenchmarkAce_GPlusAll 845606 1398 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkAero_GPlusAll 1000000 1009 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkBear_GPlusAll 103830 11386 ns/op 5488 B/op 61 allocs/op
|
||||||
|
BenchmarkBeego_GPlusAll 82653 14784 ns/op 4576 B/op 39 allocs/op
|
||||||
|
BenchmarkBone_GPlusAll 36601 33123 ns/op 11744 B/op 109 allocs/op
|
||||||
|
BenchmarkChi_GPlusAll 95264 12831 ns/op 5616 B/op 39 allocs/op
|
||||||
|
BenchmarkDenco_GPlusAll 567681 2950 ns/op 672 B/op 11 allocs/op
|
||||||
|
BenchmarkEcho_GPlusAll 720366 1665 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkGin_GPlusAll 1000000 1185 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkGocraftWeb_GPlusAll 71575 16365 ns/op 8040 B/op 103 allocs/op
|
||||||
|
BenchmarkGoji_GPlusAll 136352 9191 ns/op 3696 B/op 22 allocs/op
|
||||||
|
BenchmarkGojiv2_GPlusAll 38006 31802 ns/op 17616 B/op 154 allocs/op
|
||||||
|
BenchmarkGoJsonRest_GPlusAll 57238 21561 ns/op 8117 B/op 170 allocs/op
|
||||||
|
BenchmarkGoRestful_GPlusAll 15147 79276 ns/op 55520 B/op 192 allocs/op
|
||||||
|
BenchmarkGorillaMux_GPlusAll 24446 48410 ns/op 16112 B/op 128 allocs/op
|
||||||
|
BenchmarkGowwwRouter_GPlusAll 150112 7770 ns/op 4752 B/op 33 allocs/op
|
||||||
|
BenchmarkHttpRouter_GPlusAll 1367820 878 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkHttpTreeMux_GPlusAll 166628 8004 ns/op 4032 B/op 38 allocs/op
|
||||||
|
BenchmarkKocha_GPlusAll 265694 4570 ns/op 976 B/op 43 allocs/op
|
||||||
|
BenchmarkLARS_GPlusAll 1000000 1068 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkMacaron_GPlusAll 54564 23305 ns/op 9568 B/op 104 allocs/op
|
||||||
|
BenchmarkMartini_GPlusAll 16274 73845 ns/op 14016 B/op 145 allocs/op
|
||||||
|
BenchmarkPat_GPlusAll 27181 44478 ns/op 15264 B/op 271 allocs/op
|
||||||
|
BenchmarkPossum_GPlusAll 122587 10277 ns/op 5408 B/op 39 allocs/op
|
||||||
|
BenchmarkR2router_GPlusAll 130137 9297 ns/op 5040 B/op 63 allocs/op
|
||||||
|
BenchmarkRivet_GPlusAll 532438 3323 ns/op 768 B/op 11 allocs/op
|
||||||
|
BenchmarkTango_GPlusAll 86054 14531 ns/op 3656 B/op 104 allocs/op
|
||||||
|
BenchmarkTigerTonic_GPlusAll 33936 35356 ns/op 11600 B/op 242 allocs/op
|
||||||
|
BenchmarkTraffic_GPlusAll 17833 68181 ns/op 26248 B/op 341 allocs/op
|
||||||
|
BenchmarkVulcan_GPlusAll 120109 9861 ns/op 1274 B/op 39 allocs/op
|
||||||
|
```
|
||||||
|
|
||||||
|
## Parse.com
|
||||||
|
|
||||||
|
```sh
|
||||||
|
BenchmarkGin_ParseStatic 18877833 63.5 ns/op 0 B/op 0 allocs/op
|
||||||
|
|
||||||
|
BenchmarkAce_ParseStatic 19663731 60.8 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkAero_ParseStatic 28967341 41.5 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkBear_ParseStatic 3006984 402 ns/op 120 B/op 3 allocs/op
|
||||||
|
BenchmarkBeego_ParseStatic 1000000 1031 ns/op 352 B/op 3 allocs/op
|
||||||
|
BenchmarkBone_ParseStatic 1782482 675 ns/op 144 B/op 3 allocs/op
|
||||||
|
BenchmarkChi_ParseStatic 1453261 819 ns/op 432 B/op 3 allocs/op
|
||||||
|
BenchmarkDenco_ParseStatic 45023595 26.5 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkEcho_ParseStatic 17330470 69.3 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkGocraftWeb_ParseStatic 1644006 731 ns/op 296 B/op 5 allocs/op
|
||||||
|
BenchmarkGoji_ParseStatic 7026930 170 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkGojiv2_ParseStatic 517618 2037 ns/op 1312 B/op 10 allocs/op
|
||||||
|
BenchmarkGoJsonRest_ParseStatic 1227080 975 ns/op 329 B/op 11 allocs/op
|
||||||
|
BenchmarkGoRestful_ParseStatic 192458 6659 ns/op 4256 B/op 13 allocs/op
|
||||||
|
BenchmarkGorillaMux_ParseStatic 744062 2109 ns/op 976 B/op 9 allocs/op
|
||||||
|
BenchmarkGowwwRouter_ParseStatic 37781062 31.8 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkHttpRouter_ParseStatic 45311223 26.5 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkHttpTreeMux_ParseStatic 21383475 56.1 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkKocha_ParseStatic 29953290 40.1 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkLARS_ParseStatic 20036196 62.7 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkMacaron_ParseStatic 1000000 1740 ns/op 736 B/op 8 allocs/op
|
||||||
|
BenchmarkMartini_ParseStatic 404156 3801 ns/op 768 B/op 9 allocs/op
|
||||||
|
BenchmarkPat_ParseStatic 1547180 772 ns/op 240 B/op 5 allocs/op
|
||||||
|
BenchmarkPossum_ParseStatic 1608991 757 ns/op 416 B/op 3 allocs/op
|
||||||
|
BenchmarkR2router_ParseStatic 3177936 385 ns/op 144 B/op 4 allocs/op
|
||||||
|
BenchmarkRivet_ParseStatic 17783205 67.4 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkTango_ParseStatic 1210777 990 ns/op 248 B/op 8 allocs/op
|
||||||
|
BenchmarkTigerTonic_ParseStatic 5316440 231 ns/op 48 B/op 1 allocs/op
|
||||||
|
BenchmarkTraffic_ParseStatic 496050 2539 ns/op 1256 B/op 19 allocs/op
|
||||||
|
BenchmarkVulcan_ParseStatic 2462798 488 ns/op 98 B/op 3 allocs/op
|
||||||
|
BenchmarkAce_ParseParam 13393669 89.6 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkAero_ParseParam 19836619 60.4 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkBear_ParseParam 1405954 864 ns/op 467 B/op 5 allocs/op
|
||||||
|
BenchmarkBeego_ParseParam 1000000 1065 ns/op 352 B/op 3 allocs/op
|
||||||
|
BenchmarkBone_ParseParam 1000000 1698 ns/op 896 B/op 7 allocs/op
|
||||||
|
BenchmarkChi_ParseParam 1356037 873 ns/op 432 B/op 3 allocs/op
|
||||||
|
BenchmarkDenco_ParseParam 6241392 204 ns/op 64 B/op 1 allocs/op
|
||||||
|
BenchmarkEcho_ParseParam 14088100 85.1 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkGin_ParseParam 17426064 68.9 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkGocraftWeb_ParseParam 1000000 1254 ns/op 664 B/op 8 allocs/op
|
||||||
|
BenchmarkGoji_ParseParam 1682574 713 ns/op 336 B/op 2 allocs/op
|
||||||
|
BenchmarkGojiv2_ParseParam 502224 2333 ns/op 1360 B/op 12 allocs/op
|
||||||
|
BenchmarkGoJsonRest_ParseParam 1000000 1401 ns/op 649 B/op 13 allocs/op
|
||||||
|
BenchmarkGoRestful_ParseParam 182623 7097 ns/op 4576 B/op 14 allocs/op
|
||||||
|
BenchmarkGorillaMux_ParseParam 482332 2477 ns/op 1280 B/op 10 allocs/op
|
||||||
|
BenchmarkGowwwRouter_ParseParam 1834873 657 ns/op 432 B/op 3 allocs/op
|
||||||
|
BenchmarkHttpRouter_ParseParam 23593393 51.0 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkHttpTreeMux_ParseParam 2100160 574 ns/op 352 B/op 3 allocs/op
|
||||||
|
BenchmarkKocha_ParseParam 4837220 252 ns/op 56 B/op 3 allocs/op
|
||||||
|
BenchmarkLARS_ParseParam 18411192 66.2 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkMacaron_ParseParam 571870 2398 ns/op 1072 B/op 10 allocs/op
|
||||||
|
BenchmarkMartini_ParseParam 286262 4268 ns/op 1072 B/op 10 allocs/op
|
||||||
|
BenchmarkPat_ParseParam 692906 2157 ns/op 992 B/op 15 allocs/op
|
||||||
|
BenchmarkPossum_ParseParam 1000000 1011 ns/op 496 B/op 5 allocs/op
|
||||||
|
BenchmarkR2router_ParseParam 1722735 697 ns/op 432 B/op 5 allocs/op
|
||||||
|
BenchmarkRivet_ParseParam 6058054 203 ns/op 48 B/op 1 allocs/op
|
||||||
|
BenchmarkTango_ParseParam 1000000 1061 ns/op 280 B/op 8 allocs/op
|
||||||
|
BenchmarkTigerTonic_ParseParam 890275 2277 ns/op 784 B/op 15 allocs/op
|
||||||
|
BenchmarkTraffic_ParseParam 351322 3543 ns/op 1896 B/op 21 allocs/op
|
||||||
|
BenchmarkVulcan_ParseParam 2076544 572 ns/op 98 B/op 3 allocs/op
|
||||||
|
BenchmarkAce_Parse2Params 11718074 101 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkAero_Parse2Params 16264988 73.4 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkBear_Parse2Params 1238322 973 ns/op 496 B/op 5 allocs/op
|
||||||
|
BenchmarkBeego_Parse2Params 1000000 1120 ns/op 352 B/op 3 allocs/op
|
||||||
|
BenchmarkBone_Parse2Params 1000000 1632 ns/op 848 B/op 6 allocs/op
|
||||||
|
BenchmarkChi_Parse2Params 1239477 955 ns/op 432 B/op 3 allocs/op
|
||||||
|
BenchmarkDenco_Parse2Params 4944133 245 ns/op 64 B/op 1 allocs/op
|
||||||
|
BenchmarkEcho_Parse2Params 10518286 114 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkGin_Parse2Params 14505195 82.7 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkGocraftWeb_Parse2Params 1000000 1437 ns/op 712 B/op 9 allocs/op
|
||||||
|
BenchmarkGoji_Parse2Params 1689883 707 ns/op 336 B/op 2 allocs/op
|
||||||
|
BenchmarkGojiv2_Parse2Params 502334 2308 ns/op 1344 B/op 11 allocs/op
|
||||||
|
BenchmarkGoJsonRest_Parse2Params 1000000 1771 ns/op 713 B/op 14 allocs/op
|
||||||
|
BenchmarkGoRestful_Parse2Params 159092 7583 ns/op 4928 B/op 14 allocs/op
|
||||||
|
BenchmarkGorillaMux_Parse2Params 417548 2980 ns/op 1296 B/op 10 allocs/op
|
||||||
|
BenchmarkGowwwRouter_Parse2Params 1751737 686 ns/op 432 B/op 3 allocs/op
|
||||||
|
BenchmarkHttpRouter_Parse2Params 18089204 66.3 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkHttpTreeMux_Parse2Params 1556986 777 ns/op 384 B/op 4 allocs/op
|
||||||
|
BenchmarkKocha_Parse2Params 2493082 485 ns/op 128 B/op 5 allocs/op
|
||||||
|
BenchmarkLARS_Parse2Params 15350108 78.5 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkMacaron_Parse2Params 530974 2605 ns/op 1072 B/op 10 allocs/op
|
||||||
|
BenchmarkMartini_Parse2Params 247069 4673 ns/op 1152 B/op 11 allocs/op
|
||||||
|
BenchmarkPat_Parse2Params 816295 2126 ns/op 752 B/op 16 allocs/op
|
||||||
|
BenchmarkPossum_Parse2Params 1000000 1002 ns/op 496 B/op 5 allocs/op
|
||||||
|
BenchmarkR2router_Parse2Params 1569771 733 ns/op 432 B/op 5 allocs/op
|
||||||
|
BenchmarkRivet_Parse2Params 4080546 295 ns/op 96 B/op 1 allocs/op
|
||||||
|
BenchmarkTango_Parse2Params 1000000 1121 ns/op 312 B/op 8 allocs/op
|
||||||
|
BenchmarkTigerTonic_Parse2Params 399556 3470 ns/op 1168 B/op 22 allocs/op
|
||||||
|
BenchmarkTraffic_Parse2Params 314194 4159 ns/op 1944 B/op 22 allocs/op
|
||||||
|
BenchmarkVulcan_Parse2Params 1827559 664 ns/op 98 B/op 3 allocs/op
|
||||||
|
BenchmarkAce_ParseAll 478395 2503 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkAero_ParseAll 715392 1658 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkBear_ParseAll 59191 20124 ns/op 8928 B/op 110 allocs/op
|
||||||
|
BenchmarkBeego_ParseAll 45507 27266 ns/op 9152 B/op 78 allocs/op
|
||||||
|
BenchmarkBone_ParseAll 29328 41459 ns/op 16208 B/op 147 allocs/op
|
||||||
|
BenchmarkChi_ParseAll 48531 25053 ns/op 11232 B/op 78 allocs/op
|
||||||
|
BenchmarkDenco_ParseAll 325532 4284 ns/op 928 B/op 16 allocs/op
|
||||||
|
BenchmarkEcho_ParseAll 433771 2759 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkGin_ParseAll 576316 2082 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkGocraftWeb_ParseAll 41500 29692 ns/op 13728 B/op 181 allocs/op
|
||||||
|
BenchmarkGoji_ParseAll 80833 15563 ns/op 5376 B/op 32 allocs/op
|
||||||
|
BenchmarkGojiv2_ParseAll 19836 60335 ns/op 34448 B/op 277 allocs/op
|
||||||
|
BenchmarkGoJsonRest_ParseAll 32210 38027 ns/op 13866 B/op 321 allocs/op
|
||||||
|
BenchmarkGoRestful_ParseAll 6644 190842 ns/op 117600 B/op 354 allocs/op
|
||||||
|
BenchmarkGorillaMux_ParseAll 12634 95894 ns/op 30288 B/op 250 allocs/op
|
||||||
|
BenchmarkGowwwRouter_ParseAll 98152 12159 ns/op 6912 B/op 48 allocs/op
|
||||||
|
BenchmarkHttpRouter_ParseAll 933208 1273 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkHttpTreeMux_ParseAll 107191 11554 ns/op 5728 B/op 51 allocs/op
|
||||||
|
BenchmarkKocha_ParseAll 184862 6225 ns/op 1112 B/op 54 allocs/op
|
||||||
|
BenchmarkLARS_ParseAll 644546 1858 ns/op 0 B/op 0 allocs/op
|
||||||
|
BenchmarkMacaron_ParseAll 26145 46484 ns/op 19136 B/op 208 allocs/op
|
||||||
|
BenchmarkMartini_ParseAll 10000 121838 ns/op 25072 B/op 253 allocs/op
|
||||||
|
BenchmarkPat_ParseAll 25417 47196 ns/op 15216 B/op 308 allocs/op
|
||||||
|
BenchmarkPossum_ParseAll 58550 20735 ns/op 10816 B/op 78 allocs/op
|
||||||
|
BenchmarkR2router_ParseAll 72732 16584 ns/op 8352 B/op 120 allocs/op
|
||||||
|
BenchmarkRivet_ParseAll 281365 4968 ns/op 912 B/op 16 allocs/op
|
||||||
|
BenchmarkTango_ParseAll 42831 28668 ns/op 7168 B/op 208 allocs/op
|
||||||
|
BenchmarkTigerTonic_ParseAll 23774 49972 ns/op 16048 B/op 332 allocs/op
|
||||||
|
BenchmarkTraffic_ParseAll 10000 104679 ns/op 45520 B/op 605 allocs/op
|
||||||
|
BenchmarkVulcan_ParseAll 64810 18108 ns/op 2548 B/op 78 allocs/op
|
||||||
|
```
|
502
vendor/github.com/gin-gonic/gin/CHANGELOG.md
generated
vendored
Normal file
502
vendor/github.com/gin-gonic/gin/CHANGELOG.md
generated
vendored
Normal file
@ -0,0 +1,502 @@
|
|||||||
|
# Gin ChangeLog
|
||||||
|
|
||||||
|
## Gin v1.8.1
|
||||||
|
|
||||||
|
### ENHANCEMENTS
|
||||||
|
|
||||||
|
* feat(context): add ContextWithFallback feature flag [#3172](https://github.com/gin-gonic/gin/pull/3172)
|
||||||
|
|
||||||
|
## Gin v1.8.0
|
||||||
|
|
||||||
|
## Break Changes
|
||||||
|
|
||||||
|
* TrustedProxies: Add default IPv6 support and refactor [#2967](https://github.com/gin-gonic/gin/pull/2967). Please replace `RemoteIP() (net.IP, bool)` with `RemoteIP() net.IP`
|
||||||
|
* gin.Context with fallback value from gin.Context.Request.Context() [#2751](https://github.com/gin-gonic/gin/pull/2751)
|
||||||
|
|
||||||
|
### BUGFIXES
|
||||||
|
|
||||||
|
* Fixed SetOutput() panics on go 1.17 [#2861](https://github.com/gin-gonic/gin/pull/2861)
|
||||||
|
* Fix: wrong when wildcard follows named param [#2983](https://github.com/gin-gonic/gin/pull/2983)
|
||||||
|
* Fix: missing sameSite when do context.reset() [#3123](https://github.com/gin-gonic/gin/pull/3123)
|
||||||
|
|
||||||
|
### ENHANCEMENTS
|
||||||
|
|
||||||
|
* Use Header() instead of deprecated HeaderMap [#2694](https://github.com/gin-gonic/gin/pull/2694)
|
||||||
|
* RouterGroup.Handle regular match optimization of http method [#2685](https://github.com/gin-gonic/gin/pull/2685)
|
||||||
|
* Add support go-json, another drop-in json replacement [#2680](https://github.com/gin-gonic/gin/pull/2680)
|
||||||
|
* Use errors.New to replace fmt.Errorf will much better [#2707](https://github.com/gin-gonic/gin/pull/2707)
|
||||||
|
* Use Duration.Truncate for truncating precision [#2711](https://github.com/gin-gonic/gin/pull/2711)
|
||||||
|
* Get client IP when using Cloudflare [#2723](https://github.com/gin-gonic/gin/pull/2723)
|
||||||
|
* Optimize code adjust [#2700](https://github.com/gin-gonic/gin/pull/2700/files)
|
||||||
|
* Optimize code and reduce code cyclomatic complexity [#2737](https://github.com/gin-gonic/gin/pull/2737)
|
||||||
|
* Improve sliceValidateError.Error performance [#2765](https://github.com/gin-gonic/gin/pull/2765)
|
||||||
|
* Support custom struct tag [#2720](https://github.com/gin-gonic/gin/pull/2720)
|
||||||
|
* Improve router group tests [#2787](https://github.com/gin-gonic/gin/pull/2787)
|
||||||
|
* Fallback Context.Deadline() Context.Done() Context.Err() to Context.Request.Context() [#2769](https://github.com/gin-gonic/gin/pull/2769)
|
||||||
|
* Some codes optimize [#2830](https://github.com/gin-gonic/gin/pull/2830) [#2834](https://github.com/gin-gonic/gin/pull/2834) [#2838](https://github.com/gin-gonic/gin/pull/2838) [#2837](https://github.com/gin-gonic/gin/pull/2837) [#2788](https://github.com/gin-gonic/gin/pull/2788) [#2848](https://github.com/gin-gonic/gin/pull/2848) [#2851](https://github.com/gin-gonic/gin/pull/2851) [#2701](https://github.com/gin-gonic/gin/pull/2701)
|
||||||
|
* TrustedProxies: Add default IPv6 support and refactor [#2967](https://github.com/gin-gonic/gin/pull/2967)
|
||||||
|
* Test(route): expose performRequest func [#3012](https://github.com/gin-gonic/gin/pull/3012)
|
||||||
|
* Support h2c with prior knowledge [#1398](https://github.com/gin-gonic/gin/pull/1398)
|
||||||
|
* Feat attachment filename support utf8 [#3071](https://github.com/gin-gonic/gin/pull/3071)
|
||||||
|
* Feat: add StaticFileFS [#2749](https://github.com/gin-gonic/gin/pull/2749)
|
||||||
|
* Feat(context): return GIN Context from Value method [#2825](https://github.com/gin-gonic/gin/pull/2825)
|
||||||
|
* Feat: automatically SetMode to TestMode when run go test [#3139](https://github.com/gin-gonic/gin/pull/3139)
|
||||||
|
* Add TOML bining for gin [#3081](https://github.com/gin-gonic/gin/pull/3081)
|
||||||
|
* IPv6 add default trusted proxies [#3033](https://github.com/gin-gonic/gin/pull/3033)
|
||||||
|
|
||||||
|
### DOCS
|
||||||
|
|
||||||
|
* Add note about nomsgpack tag to the readme [#2703](https://github.com/gin-gonic/gin/pull/2703)
|
||||||
|
|
||||||
|
## Gin v1.7.7
|
||||||
|
|
||||||
|
### BUGFIXES
|
||||||
|
|
||||||
|
* Fixed X-Forwarded-For unsafe handling of CVE-2020-28483 [#2844](https://github.com/gin-gonic/gin/pull/2844), closed issue [#2862](https://github.com/gin-gonic/gin/issues/2862).
|
||||||
|
* Tree: updated the code logic for `latestNode` [#2897](https://github.com/gin-gonic/gin/pull/2897), closed issue [#2894](https://github.com/gin-gonic/gin/issues/2894) [#2878](https://github.com/gin-gonic/gin/issues/2878).
|
||||||
|
* Tree: fixed the misplacement of adding slashes [#2847](https://github.com/gin-gonic/gin/pull/2847), closed issue [#2843](https://github.com/gin-gonic/gin/issues/2843).
|
||||||
|
* Tree: fixed tsr with mixed static and wildcard paths [#2924](https://github.com/gin-gonic/gin/pull/2924), closed issue [#2918](https://github.com/gin-gonic/gin/issues/2918).
|
||||||
|
|
||||||
|
### ENHANCEMENTS
|
||||||
|
|
||||||
|
* TrustedProxies: make it backward-compatible [#2887](https://github.com/gin-gonic/gin/pull/2887), closed issue [#2819](https://github.com/gin-gonic/gin/issues/2819).
|
||||||
|
* TrustedPlatform: provide custom options for another CDN services [#2906](https://github.com/gin-gonic/gin/pull/2906).
|
||||||
|
|
||||||
|
### DOCS
|
||||||
|
|
||||||
|
* NoMethod: added usage annotation ([#2832](https://github.com/gin-gonic/gin/pull/2832#issuecomment-929954463)).
|
||||||
|
|
||||||
|
## Gin v1.7.6
|
||||||
|
|
||||||
|
### BUGFIXES
|
||||||
|
|
||||||
|
* bump new release to fix v1.7.5 release error by using v1.7.4 codes.
|
||||||
|
|
||||||
|
## Gin v1.7.4
|
||||||
|
|
||||||
|
### BUGFIXES
|
||||||
|
|
||||||
|
* bump new release to fix checksum mismatch
|
||||||
|
|
||||||
|
## Gin v1.7.3
|
||||||
|
|
||||||
|
### BUGFIXES
|
||||||
|
|
||||||
|
* fix level 1 router match [#2767](https://github.com/gin-gonic/gin/issues/2767), [#2796](https://github.com/gin-gonic/gin/issues/2796)
|
||||||
|
|
||||||
|
## Gin v1.7.2
|
||||||
|
|
||||||
|
### BUGFIXES
|
||||||
|
|
||||||
|
* Fix conflict between param and exact path [#2706](https://github.com/gin-gonic/gin/issues/2706). Close issue [#2682](https://github.com/gin-gonic/gin/issues/2682) [#2696](https://github.com/gin-gonic/gin/issues/2696).
|
||||||
|
|
||||||
|
## Gin v1.7.1
|
||||||
|
|
||||||
|
### BUGFIXES
|
||||||
|
|
||||||
|
* fix: data race with trustedCIDRs from [#2674](https://github.com/gin-gonic/gin/issues/2674)([#2675](https://github.com/gin-gonic/gin/pull/2675))
|
||||||
|
|
||||||
|
## Gin v1.7.0
|
||||||
|
|
||||||
|
### BUGFIXES
|
||||||
|
|
||||||
|
* fix compile error from [#2572](https://github.com/gin-gonic/gin/pull/2572) ([#2600](https://github.com/gin-gonic/gin/pull/2600))
|
||||||
|
* fix: print headers without Authorization header on broken pipe ([#2528](https://github.com/gin-gonic/gin/pull/2528))
|
||||||
|
* fix(tree): reassign fullpath when register new node ([#2366](https://github.com/gin-gonic/gin/pull/2366))
|
||||||
|
|
||||||
|
### ENHANCEMENTS
|
||||||
|
|
||||||
|
* Support params and exact routes without creating conflicts ([#2663](https://github.com/gin-gonic/gin/pull/2663))
|
||||||
|
* chore: improve render string performance ([#2365](https://github.com/gin-gonic/gin/pull/2365))
|
||||||
|
* Sync route tree to httprouter latest code ([#2368](https://github.com/gin-gonic/gin/pull/2368))
|
||||||
|
* chore: rename getQueryCache/getFormCache to initQueryCache/initFormCa ([#2375](https://github.com/gin-gonic/gin/pull/2375))
|
||||||
|
* chore(performance): improve countParams ([#2378](https://github.com/gin-gonic/gin/pull/2378))
|
||||||
|
* Remove some functions that have the same effect as the bytes package ([#2387](https://github.com/gin-gonic/gin/pull/2387))
|
||||||
|
* update:SetMode function ([#2321](https://github.com/gin-gonic/gin/pull/2321))
|
||||||
|
* remove a unused type SecureJSONPrefix ([#2391](https://github.com/gin-gonic/gin/pull/2391))
|
||||||
|
* Add a redirect sample for POST method ([#2389](https://github.com/gin-gonic/gin/pull/2389))
|
||||||
|
* Add CustomRecovery builtin middleware ([#2322](https://github.com/gin-gonic/gin/pull/2322))
|
||||||
|
* binding: avoid 2038 problem on 32-bit architectures ([#2450](https://github.com/gin-gonic/gin/pull/2450))
|
||||||
|
* Prevent panic in Context.GetQuery() when there is no Request ([#2412](https://github.com/gin-gonic/gin/pull/2412))
|
||||||
|
* Add GetUint and GetUint64 method on gin.context ([#2487](https://github.com/gin-gonic/gin/pull/2487))
|
||||||
|
* update content-disposition header to MIME-style ([#2512](https://github.com/gin-gonic/gin/pull/2512))
|
||||||
|
* reduce allocs and improve the render `WriteString` ([#2508](https://github.com/gin-gonic/gin/pull/2508))
|
||||||
|
* implement ".Unwrap() error" on Error type ([#2525](https://github.com/gin-gonic/gin/pull/2525)) ([#2526](https://github.com/gin-gonic/gin/pull/2526))
|
||||||
|
* Allow bind with a map[string]string ([#2484](https://github.com/gin-gonic/gin/pull/2484))
|
||||||
|
* chore: update tree ([#2371](https://github.com/gin-gonic/gin/pull/2371))
|
||||||
|
* Support binding for slice/array obj [Rewrite] ([#2302](https://github.com/gin-gonic/gin/pull/2302))
|
||||||
|
* basic auth: fix timing oracle ([#2609](https://github.com/gin-gonic/gin/pull/2609))
|
||||||
|
* Add mixed param and non-param paths (port of httprouter[#329](https://github.com/gin-gonic/gin/pull/329)) ([#2663](https://github.com/gin-gonic/gin/pull/2663))
|
||||||
|
* feat(engine): add trustedproxies and remoteIP ([#2632](https://github.com/gin-gonic/gin/pull/2632))
|
||||||
|
|
||||||
|
## Gin v1.6.3
|
||||||
|
|
||||||
|
### ENHANCEMENTS
|
||||||
|
|
||||||
|
* Improve performance: Change `*sync.RWMutex` to `sync.RWMutex` in context. [#2351](https://github.com/gin-gonic/gin/pull/2351)
|
||||||
|
|
||||||
|
## Gin v1.6.2
|
||||||
|
|
||||||
|
### BUGFIXES
|
||||||
|
* fix missing initial sync.RWMutex [#2305](https://github.com/gin-gonic/gin/pull/2305)
|
||||||
|
### ENHANCEMENTS
|
||||||
|
* Add set samesite in cookie. [#2306](https://github.com/gin-gonic/gin/pull/2306)
|
||||||
|
|
||||||
|
## Gin v1.6.1
|
||||||
|
|
||||||
|
### BUGFIXES
|
||||||
|
* Revert "fix accept incoming network connections" [#2294](https://github.com/gin-gonic/gin/pull/2294)
|
||||||
|
|
||||||
|
## Gin v1.6.0
|
||||||
|
|
||||||
|
### BREAKING
|
||||||
|
* chore(performance): Improve performance for adding RemoveExtraSlash flag [#2159](https://github.com/gin-gonic/gin/pull/2159)
|
||||||
|
* drop support govendor [#2148](https://github.com/gin-gonic/gin/pull/2148)
|
||||||
|
* Added support for SameSite cookie flag [#1615](https://github.com/gin-gonic/gin/pull/1615)
|
||||||
|
### FEATURES
|
||||||
|
* add yaml negotiation [#2220](https://github.com/gin-gonic/gin/pull/2220)
|
||||||
|
* FileFromFS [#2112](https://github.com/gin-gonic/gin/pull/2112)
|
||||||
|
### BUGFIXES
|
||||||
|
* Unix Socket Handling [#2280](https://github.com/gin-gonic/gin/pull/2280)
|
||||||
|
* Use json marshall in context json to fix breaking new line issue. Fixes #2209 [#2228](https://github.com/gin-gonic/gin/pull/2228)
|
||||||
|
* fix accept incoming network connections [#2216](https://github.com/gin-gonic/gin/pull/2216)
|
||||||
|
* Fixed a bug in the calculation of the maximum number of parameters [#2166](https://github.com/gin-gonic/gin/pull/2166)
|
||||||
|
* [FIX] allow empty headers on DataFromReader [#2121](https://github.com/gin-gonic/gin/pull/2121)
|
||||||
|
* Add mutex for protect Context.Keys map [#1391](https://github.com/gin-gonic/gin/pull/1391)
|
||||||
|
### ENHANCEMENTS
|
||||||
|
* Add mitigation for log injection [#2277](https://github.com/gin-gonic/gin/pull/2277)
|
||||||
|
* tree: range over nodes values [#2229](https://github.com/gin-gonic/gin/pull/2229)
|
||||||
|
* tree: remove duplicate assignment [#2222](https://github.com/gin-gonic/gin/pull/2222)
|
||||||
|
* chore: upgrade go-isatty and json-iterator/go [#2215](https://github.com/gin-gonic/gin/pull/2215)
|
||||||
|
* path: sync code with httprouter [#2212](https://github.com/gin-gonic/gin/pull/2212)
|
||||||
|
* Use zero-copy approach to convert types between string and byte slice [#2206](https://github.com/gin-gonic/gin/pull/2206)
|
||||||
|
* Reuse bytes when cleaning the URL paths [#2179](https://github.com/gin-gonic/gin/pull/2179)
|
||||||
|
* tree: remove one else statement [#2177](https://github.com/gin-gonic/gin/pull/2177)
|
||||||
|
* tree: sync httprouter update (#2173) (#2172) [#2171](https://github.com/gin-gonic/gin/pull/2171)
|
||||||
|
* tree: sync part httprouter codes and reduce if/else [#2163](https://github.com/gin-gonic/gin/pull/2163)
|
||||||
|
* use http method constant [#2155](https://github.com/gin-gonic/gin/pull/2155)
|
||||||
|
* upgrade go-validator to v10 [#2149](https://github.com/gin-gonic/gin/pull/2149)
|
||||||
|
* Refactor redirect request in gin.go [#1970](https://github.com/gin-gonic/gin/pull/1970)
|
||||||
|
* Add build tag nomsgpack [#1852](https://github.com/gin-gonic/gin/pull/1852)
|
||||||
|
### DOCS
|
||||||
|
* docs(path): improve comments [#2223](https://github.com/gin-gonic/gin/pull/2223)
|
||||||
|
* Renew README to fit the modification of SetCookie method [#2217](https://github.com/gin-gonic/gin/pull/2217)
|
||||||
|
* Fix spelling [#2202](https://github.com/gin-gonic/gin/pull/2202)
|
||||||
|
* Remove broken link from README. [#2198](https://github.com/gin-gonic/gin/pull/2198)
|
||||||
|
* Update docs on Context.Done(), Context.Deadline() and Context.Err() [#2196](https://github.com/gin-gonic/gin/pull/2196)
|
||||||
|
* Update validator to v10 [#2190](https://github.com/gin-gonic/gin/pull/2190)
|
||||||
|
* upgrade go-validator to v10 for README [#2189](https://github.com/gin-gonic/gin/pull/2189)
|
||||||
|
* Update to currently output [#2188](https://github.com/gin-gonic/gin/pull/2188)
|
||||||
|
* Fix "Custom Validators" example [#2186](https://github.com/gin-gonic/gin/pull/2186)
|
||||||
|
* Add project to README [#2165](https://github.com/gin-gonic/gin/pull/2165)
|
||||||
|
* docs(benchmarks): for gin v1.5 [#2153](https://github.com/gin-gonic/gin/pull/2153)
|
||||||
|
* Changed wording for clarity in README.md [#2122](https://github.com/gin-gonic/gin/pull/2122)
|
||||||
|
### MISC
|
||||||
|
* ci support go1.14 [#2262](https://github.com/gin-gonic/gin/pull/2262)
|
||||||
|
* chore: upgrade depend version [#2231](https://github.com/gin-gonic/gin/pull/2231)
|
||||||
|
* Drop support go1.10 [#2147](https://github.com/gin-gonic/gin/pull/2147)
|
||||||
|
* fix comment in `mode.go` [#2129](https://github.com/gin-gonic/gin/pull/2129)
|
||||||
|
|
||||||
|
## Gin v1.5.0
|
||||||
|
|
||||||
|
- [FIX] Use DefaultWriter and DefaultErrorWriter for debug messages [#1891](https://github.com/gin-gonic/gin/pull/1891)
|
||||||
|
- [NEW] Now you can parse the inline lowercase start structure [#1893](https://github.com/gin-gonic/gin/pull/1893)
|
||||||
|
- [FIX] Some code improvements [#1909](https://github.com/gin-gonic/gin/pull/1909)
|
||||||
|
- [FIX] Use encode replace json marshal increase json encoder speed [#1546](https://github.com/gin-gonic/gin/pull/1546)
|
||||||
|
- [NEW] Hold matched route full path in the Context [#1826](https://github.com/gin-gonic/gin/pull/1826)
|
||||||
|
- [FIX] Fix context.Params race condition on Copy() [#1841](https://github.com/gin-gonic/gin/pull/1841)
|
||||||
|
- [NEW] Add context param query cache [#1450](https://github.com/gin-gonic/gin/pull/1450)
|
||||||
|
- [FIX] Improve GetQueryMap performance [#1918](https://github.com/gin-gonic/gin/pull/1918)
|
||||||
|
- [FIX] Improve get post data [#1920](https://github.com/gin-gonic/gin/pull/1920)
|
||||||
|
- [FIX] Use context instead of x/net/context [#1922](https://github.com/gin-gonic/gin/pull/1922)
|
||||||
|
- [FIX] Attempt to fix PostForm cache bug [#1931](https://github.com/gin-gonic/gin/pull/1931)
|
||||||
|
- [NEW] Add support of multipart multi files [#1949](https://github.com/gin-gonic/gin/pull/1949)
|
||||||
|
- [NEW] Support bind http header param [#1957](https://github.com/gin-gonic/gin/pull/1957)
|
||||||
|
- [FIX] Drop support for go1.8 and go1.9 [#1933](https://github.com/gin-gonic/gin/pull/1933)
|
||||||
|
- [FIX] Bugfix for the FullPath feature [#1919](https://github.com/gin-gonic/gin/pull/1919)
|
||||||
|
- [FIX] Gin1.5 bytes.Buffer to strings.Builder [#1939](https://github.com/gin-gonic/gin/pull/1939)
|
||||||
|
- [FIX] Upgrade github.com/ugorji/go/codec [#1969](https://github.com/gin-gonic/gin/pull/1969)
|
||||||
|
- [NEW] Support bind unix time [#1980](https://github.com/gin-gonic/gin/pull/1980)
|
||||||
|
- [FIX] Simplify code [#2004](https://github.com/gin-gonic/gin/pull/2004)
|
||||||
|
- [NEW] Support negative Content-Length in DataFromReader [#1981](https://github.com/gin-gonic/gin/pull/1981)
|
||||||
|
- [FIX] Identify terminal on a RISC-V architecture for auto-colored logs [#2019](https://github.com/gin-gonic/gin/pull/2019)
|
||||||
|
- [BREAKING] `Context.JSONP()` now expects a semicolon (`;`) at the end [#2007](https://github.com/gin-gonic/gin/pull/2007)
|
||||||
|
- [BREAKING] Upgrade default `binding.Validator` to v9 (see [its changelog](https://github.com/go-playground/validator/releases/tag/v9.0.0)) [#1015](https://github.com/gin-gonic/gin/pull/1015)
|
||||||
|
- [NEW] Add `DisallowUnknownFields()` in `Context.BindJSON()` [#2028](https://github.com/gin-gonic/gin/pull/2028)
|
||||||
|
- [NEW] Use specific `net.Listener` with `Engine.RunListener()` [#2023](https://github.com/gin-gonic/gin/pull/2023)
|
||||||
|
- [FIX] Fix some typo [#2079](https://github.com/gin-gonic/gin/pull/2079) [#2080](https://github.com/gin-gonic/gin/pull/2080)
|
||||||
|
- [FIX] Relocate binding body tests [#2086](https://github.com/gin-gonic/gin/pull/2086)
|
||||||
|
- [FIX] Use Writer in Context.Status [#1606](https://github.com/gin-gonic/gin/pull/1606)
|
||||||
|
- [FIX] `Engine.RunUnix()` now returns the error if it can't change the file mode [#2093](https://github.com/gin-gonic/gin/pull/2093)
|
||||||
|
- [FIX] `RouterGroup.StaticFS()` leaked files. Now it closes them. [#2118](https://github.com/gin-gonic/gin/pull/2118)
|
||||||
|
- [FIX] `Context.Request.FormFile` leaked file. Now it closes it. [#2114](https://github.com/gin-gonic/gin/pull/2114)
|
||||||
|
- [FIX] Ignore walking on `form:"-"` mapping [#1943](https://github.com/gin-gonic/gin/pull/1943)
|
||||||
|
|
||||||
|
### Gin v1.4.0
|
||||||
|
|
||||||
|
- [NEW] Support for [Go Modules](https://github.com/golang/go/wiki/Modules) [#1569](https://github.com/gin-gonic/gin/pull/1569)
|
||||||
|
- [NEW] Refactor of form mapping multipart request [#1829](https://github.com/gin-gonic/gin/pull/1829)
|
||||||
|
- [FIX] Truncate Latency precision in long running request [#1830](https://github.com/gin-gonic/gin/pull/1830)
|
||||||
|
- [FIX] IsTerm flag should not be affected by DisableConsoleColor method. [#1802](https://github.com/gin-gonic/gin/pull/1802)
|
||||||
|
- [NEW] Supporting file binding [#1264](https://github.com/gin-gonic/gin/pull/1264)
|
||||||
|
- [NEW] Add support for mapping arrays [#1797](https://github.com/gin-gonic/gin/pull/1797)
|
||||||
|
- [FIX] Readme updates [#1793](https://github.com/gin-gonic/gin/pull/1793) [#1788](https://github.com/gin-gonic/gin/pull/1788) [1789](https://github.com/gin-gonic/gin/pull/1789)
|
||||||
|
- [FIX] StaticFS: Fixed Logging two log lines on 404. [#1805](https://github.com/gin-gonic/gin/pull/1805), [#1804](https://github.com/gin-gonic/gin/pull/1804)
|
||||||
|
- [NEW] Make context.Keys available as LogFormatterParams [#1779](https://github.com/gin-gonic/gin/pull/1779)
|
||||||
|
- [NEW] Use internal/json for Marshal/Unmarshal [#1791](https://github.com/gin-gonic/gin/pull/1791)
|
||||||
|
- [NEW] Support mapping time.Duration [#1794](https://github.com/gin-gonic/gin/pull/1794)
|
||||||
|
- [NEW] Refactor form mappings [#1749](https://github.com/gin-gonic/gin/pull/1749)
|
||||||
|
- [NEW] Added flag to context.Stream indicates if client disconnected in middle of stream [#1252](https://github.com/gin-gonic/gin/pull/1252)
|
||||||
|
- [FIX] Moved [examples](https://github.com/gin-gonic/examples) to stand alone Repo [#1775](https://github.com/gin-gonic/gin/pull/1775)
|
||||||
|
- [NEW] Extend context.File to allow for the content-disposition attachments via a new method context.Attachment [#1260](https://github.com/gin-gonic/gin/pull/1260)
|
||||||
|
- [FIX] Support HTTP content negotiation wildcards [#1112](https://github.com/gin-gonic/gin/pull/1112)
|
||||||
|
- [NEW] Add prefix from X-Forwarded-Prefix in redirectTrailingSlash [#1238](https://github.com/gin-gonic/gin/pull/1238)
|
||||||
|
- [FIX] context.Copy() race condition [#1020](https://github.com/gin-gonic/gin/pull/1020)
|
||||||
|
- [NEW] Add context.HandlerNames() [#1729](https://github.com/gin-gonic/gin/pull/1729)
|
||||||
|
- [FIX] Change color methods to public in the defaultLogger. [#1771](https://github.com/gin-gonic/gin/pull/1771)
|
||||||
|
- [FIX] Update writeHeaders method to use http.Header.Set [#1722](https://github.com/gin-gonic/gin/pull/1722)
|
||||||
|
- [NEW] Add response size to LogFormatterParams [#1752](https://github.com/gin-gonic/gin/pull/1752)
|
||||||
|
- [NEW] Allow ignoring field on form mapping [#1733](https://github.com/gin-gonic/gin/pull/1733)
|
||||||
|
- [NEW] Add a function to force color in console output. [#1724](https://github.com/gin-gonic/gin/pull/1724)
|
||||||
|
- [FIX] Context.Next() - recheck len of handlers on every iteration. [#1745](https://github.com/gin-gonic/gin/pull/1745)
|
||||||
|
- [FIX] Fix all errcheck warnings [#1739](https://github.com/gin-gonic/gin/pull/1739) [#1653](https://github.com/gin-gonic/gin/pull/1653)
|
||||||
|
- [NEW] context: inherits context cancellation and deadline from http.Request context for Go>=1.7 [#1690](https://github.com/gin-gonic/gin/pull/1690)
|
||||||
|
- [NEW] Binding for URL Params [#1694](https://github.com/gin-gonic/gin/pull/1694)
|
||||||
|
- [NEW] Add LoggerWithFormatter method [#1677](https://github.com/gin-gonic/gin/pull/1677)
|
||||||
|
- [FIX] CI testing updates [#1671](https://github.com/gin-gonic/gin/pull/1671) [#1670](https://github.com/gin-gonic/gin/pull/1670) [#1682](https://github.com/gin-gonic/gin/pull/1682) [#1669](https://github.com/gin-gonic/gin/pull/1669)
|
||||||
|
- [FIX] StaticFS(): Send 404 when path does not exist [#1663](https://github.com/gin-gonic/gin/pull/1663)
|
||||||
|
- [FIX] Handle nil body for JSON binding [#1638](https://github.com/gin-gonic/gin/pull/1638)
|
||||||
|
- [FIX] Support bind uri param [#1612](https://github.com/gin-gonic/gin/pull/1612)
|
||||||
|
- [FIX] recovery: fix issue with syscall import on google app engine [#1640](https://github.com/gin-gonic/gin/pull/1640)
|
||||||
|
- [FIX] Make sure the debug log contains line breaks [#1650](https://github.com/gin-gonic/gin/pull/1650)
|
||||||
|
- [FIX] Panic stack trace being printed during recovery of broken pipe [#1089](https://github.com/gin-gonic/gin/pull/1089) [#1259](https://github.com/gin-gonic/gin/pull/1259)
|
||||||
|
- [NEW] RunFd method to run http.Server through a file descriptor [#1609](https://github.com/gin-gonic/gin/pull/1609)
|
||||||
|
- [NEW] Yaml binding support [#1618](https://github.com/gin-gonic/gin/pull/1618)
|
||||||
|
- [FIX] Pass MaxMultipartMemory when FormFile is called [#1600](https://github.com/gin-gonic/gin/pull/1600)
|
||||||
|
- [FIX] LoadHTML* tests [#1559](https://github.com/gin-gonic/gin/pull/1559)
|
||||||
|
- [FIX] Removed use of sync.pool from HandleContext [#1565](https://github.com/gin-gonic/gin/pull/1565)
|
||||||
|
- [FIX] Format output log to os.Stderr [#1571](https://github.com/gin-gonic/gin/pull/1571)
|
||||||
|
- [FIX] Make logger use a yellow background and a darkgray text for legibility [#1570](https://github.com/gin-gonic/gin/pull/1570)
|
||||||
|
- [FIX] Remove sensitive request information from panic log. [#1370](https://github.com/gin-gonic/gin/pull/1370)
|
||||||
|
- [FIX] log.Println() does not print timestamp [#829](https://github.com/gin-gonic/gin/pull/829) [#1560](https://github.com/gin-gonic/gin/pull/1560)
|
||||||
|
- [NEW] Add PureJSON renderer [#694](https://github.com/gin-gonic/gin/pull/694)
|
||||||
|
- [FIX] Add missing copyright and update if/else [#1497](https://github.com/gin-gonic/gin/pull/1497)
|
||||||
|
- [FIX] Update msgpack usage [#1498](https://github.com/gin-gonic/gin/pull/1498)
|
||||||
|
- [FIX] Use protobuf on render [#1496](https://github.com/gin-gonic/gin/pull/1496)
|
||||||
|
- [FIX] Add support for Protobuf format response [#1479](https://github.com/gin-gonic/gin/pull/1479)
|
||||||
|
- [NEW] Set default time format in form binding [#1487](https://github.com/gin-gonic/gin/pull/1487)
|
||||||
|
- [FIX] Add BindXML and ShouldBindXML [#1485](https://github.com/gin-gonic/gin/pull/1485)
|
||||||
|
- [NEW] Upgrade dependency libraries [#1491](https://github.com/gin-gonic/gin/pull/1491)
|
||||||
|
|
||||||
|
|
||||||
|
## Gin v1.3.0
|
||||||
|
|
||||||
|
- [NEW] Add [`func (*Context) QueryMap`](https://godoc.org/github.com/gin-gonic/gin#Context.QueryMap), [`func (*Context) GetQueryMap`](https://godoc.org/github.com/gin-gonic/gin#Context.GetQueryMap), [`func (*Context) PostFormMap`](https://godoc.org/github.com/gin-gonic/gin#Context.PostFormMap) and [`func (*Context) GetPostFormMap`](https://godoc.org/github.com/gin-gonic/gin#Context.GetPostFormMap) to support `type map[string]string` as query string or form parameters, see [#1383](https://github.com/gin-gonic/gin/pull/1383)
|
||||||
|
- [NEW] Add [`func (*Context) AsciiJSON`](https://godoc.org/github.com/gin-gonic/gin#Context.AsciiJSON), see [#1358](https://github.com/gin-gonic/gin/pull/1358)
|
||||||
|
- [NEW] Add `Pusher()` in [`type ResponseWriter`](https://godoc.org/github.com/gin-gonic/gin#ResponseWriter) for supporting http2 push, see [#1273](https://github.com/gin-gonic/gin/pull/1273)
|
||||||
|
- [NEW] Add [`func (*Context) DataFromReader`](https://godoc.org/github.com/gin-gonic/gin#Context.DataFromReader) for serving dynamic data, see [#1304](https://github.com/gin-gonic/gin/pull/1304)
|
||||||
|
- [NEW] Add [`func (*Context) ShouldBindBodyWith`](https://godoc.org/github.com/gin-gonic/gin#Context.ShouldBindBodyWith) allowing to call binding multiple times, see [#1341](https://github.com/gin-gonic/gin/pull/1341)
|
||||||
|
- [NEW] Support pointers in form binding, see [#1336](https://github.com/gin-gonic/gin/pull/1336)
|
||||||
|
- [NEW] Add [`func (*Context) JSONP`](https://godoc.org/github.com/gin-gonic/gin#Context.JSONP), see [#1333](https://github.com/gin-gonic/gin/pull/1333)
|
||||||
|
- [NEW] Support default value in form binding, see [#1138](https://github.com/gin-gonic/gin/pull/1138)
|
||||||
|
- [NEW] Expose validator engine in [`type StructValidator`](https://godoc.org/github.com/gin-gonic/gin/binding#StructValidator), see [#1277](https://github.com/gin-gonic/gin/pull/1277)
|
||||||
|
- [NEW] Add [`func (*Context) ShouldBind`](https://godoc.org/github.com/gin-gonic/gin#Context.ShouldBind), [`func (*Context) ShouldBindQuery`](https://godoc.org/github.com/gin-gonic/gin#Context.ShouldBindQuery) and [`func (*Context) ShouldBindJSON`](https://godoc.org/github.com/gin-gonic/gin#Context.ShouldBindJSON), see [#1047](https://github.com/gin-gonic/gin/pull/1047)
|
||||||
|
- [NEW] Add support for `time.Time` location in form binding, see [#1117](https://github.com/gin-gonic/gin/pull/1117)
|
||||||
|
- [NEW] Add [`func (*Context) BindQuery`](https://godoc.org/github.com/gin-gonic/gin#Context.BindQuery), see [#1029](https://github.com/gin-gonic/gin/pull/1029)
|
||||||
|
- [NEW] Make [jsonite](https://github.com/json-iterator/go) optional with build tags, see [#1026](https://github.com/gin-gonic/gin/pull/1026)
|
||||||
|
- [NEW] Show query string in logger, see [#999](https://github.com/gin-gonic/gin/pull/999)
|
||||||
|
- [NEW] Add [`func (*Context) SecureJSON`](https://godoc.org/github.com/gin-gonic/gin#Context.SecureJSON), see [#987](https://github.com/gin-gonic/gin/pull/987) and [#993](https://github.com/gin-gonic/gin/pull/993)
|
||||||
|
- [DEPRECATE] `func (*Context) GetCookie` for [`func (*Context) Cookie`](https://godoc.org/github.com/gin-gonic/gin#Context.Cookie)
|
||||||
|
- [FIX] Don't display color tags if [`func DisableConsoleColor`](https://godoc.org/github.com/gin-gonic/gin#DisableConsoleColor) called, see [#1072](https://github.com/gin-gonic/gin/pull/1072)
|
||||||
|
- [FIX] Gin Mode `""` when calling [`func Mode`](https://godoc.org/github.com/gin-gonic/gin#Mode) now returns `const DebugMode`, see [#1250](https://github.com/gin-gonic/gin/pull/1250)
|
||||||
|
- [FIX] `Flush()` now doesn't overwrite `responseWriter` status code, see [#1460](https://github.com/gin-gonic/gin/pull/1460)
|
||||||
|
|
||||||
|
## Gin 1.2.0
|
||||||
|
|
||||||
|
- [NEW] Switch from godeps to govendor
|
||||||
|
- [NEW] Add support for Let's Encrypt via gin-gonic/autotls
|
||||||
|
- [NEW] Improve README examples and add extra at examples folder
|
||||||
|
- [NEW] Improved support with App Engine
|
||||||
|
- [NEW] Add custom template delimiters, see #860
|
||||||
|
- [NEW] Add Template Func Maps, see #962
|
||||||
|
- [NEW] Add \*context.Handler(), see #928
|
||||||
|
- [NEW] Add \*context.GetRawData()
|
||||||
|
- [NEW] Add \*context.GetHeader() (request)
|
||||||
|
- [NEW] Add \*context.AbortWithStatusJSON() (JSON content type)
|
||||||
|
- [NEW] Add \*context.Keys type cast helpers
|
||||||
|
- [NEW] Add \*context.ShouldBindWith()
|
||||||
|
- [NEW] Add \*context.MustBindWith()
|
||||||
|
- [NEW] Add \*engine.SetFuncMap()
|
||||||
|
- [DEPRECATE] On next release: \*context.BindWith(), see #855
|
||||||
|
- [FIX] Refactor render
|
||||||
|
- [FIX] Reworked tests
|
||||||
|
- [FIX] logger now supports cygwin
|
||||||
|
- [FIX] Use X-Forwarded-For before X-Real-Ip
|
||||||
|
- [FIX] time.Time binding (#904)
|
||||||
|
|
||||||
|
## Gin 1.1.4
|
||||||
|
|
||||||
|
- [NEW] Support google appengine for IsTerminal func
|
||||||
|
|
||||||
|
## Gin 1.1.3
|
||||||
|
|
||||||
|
- [FIX] Reverted Logger: skip ANSI color commands
|
||||||
|
|
||||||
|
## Gin 1.1
|
||||||
|
|
||||||
|
- [NEW] Implement QueryArray and PostArray methods
|
||||||
|
- [NEW] Refactor GetQuery and GetPostForm
|
||||||
|
- [NEW] Add contribution guide
|
||||||
|
- [FIX] Corrected typos in README
|
||||||
|
- [FIX] Removed additional Iota
|
||||||
|
- [FIX] Changed imports to gopkg instead of github in README (#733)
|
||||||
|
- [FIX] Logger: skip ANSI color commands if output is not a tty
|
||||||
|
|
||||||
|
## Gin 1.0rc2 (...)
|
||||||
|
|
||||||
|
- [PERFORMANCE] Fast path for writing Content-Type.
|
||||||
|
- [PERFORMANCE] Much faster 404 routing
|
||||||
|
- [PERFORMANCE] Allocation optimizations
|
||||||
|
- [PERFORMANCE] Faster root tree lookup
|
||||||
|
- [PERFORMANCE] Zero overhead, String() and JSON() rendering.
|
||||||
|
- [PERFORMANCE] Faster ClientIP parsing
|
||||||
|
- [PERFORMANCE] Much faster SSE implementation
|
||||||
|
- [NEW] Benchmarks suite
|
||||||
|
- [NEW] Bind validation can be disabled and replaced with custom validators.
|
||||||
|
- [NEW] More flexible HTML render
|
||||||
|
- [NEW] Multipart and PostForm bindings
|
||||||
|
- [NEW] Adds method to return all the registered routes
|
||||||
|
- [NEW] Context.HandlerName() returns the main handler's name
|
||||||
|
- [NEW] Adds Error.IsType() helper
|
||||||
|
- [FIX] Binding multipart form
|
||||||
|
- [FIX] Integration tests
|
||||||
|
- [FIX] Crash when binding non struct object in Context.
|
||||||
|
- [FIX] RunTLS() implementation
|
||||||
|
- [FIX] Logger() unit tests
|
||||||
|
- [FIX] Adds SetHTMLTemplate() warning
|
||||||
|
- [FIX] Context.IsAborted()
|
||||||
|
- [FIX] More unit tests
|
||||||
|
- [FIX] JSON, XML, HTML renders accept custom content-types
|
||||||
|
- [FIX] gin.AbortIndex is unexported
|
||||||
|
- [FIX] Better approach to avoid directory listing in StaticFS()
|
||||||
|
- [FIX] Context.ClientIP() always returns the IP with trimmed spaces.
|
||||||
|
- [FIX] Better warning when running in debug mode.
|
||||||
|
- [FIX] Google App Engine integration. debugPrint does not use os.Stdout
|
||||||
|
- [FIX] Fixes integer overflow in error type
|
||||||
|
- [FIX] Error implements the json.Marshaller interface
|
||||||
|
- [FIX] MIT license in every file
|
||||||
|
|
||||||
|
|
||||||
|
## Gin 1.0rc1 (May 22, 2015)
|
||||||
|
|
||||||
|
- [PERFORMANCE] Zero allocation router
|
||||||
|
- [PERFORMANCE] Faster JSON, XML and text rendering
|
||||||
|
- [PERFORMANCE] Custom hand optimized HttpRouter for Gin
|
||||||
|
- [PERFORMANCE] Misc code optimizations. Inlining, tail call optimizations
|
||||||
|
- [NEW] Built-in support for golang.org/x/net/context
|
||||||
|
- [NEW] Any(path, handler). Create a route that matches any path
|
||||||
|
- [NEW] Refactored rendering pipeline (faster and static typed)
|
||||||
|
- [NEW] Refactored errors API
|
||||||
|
- [NEW] IndentedJSON() prints pretty JSON
|
||||||
|
- [NEW] Added gin.DefaultWriter
|
||||||
|
- [NEW] UNIX socket support
|
||||||
|
- [NEW] RouterGroup.BasePath is exposed
|
||||||
|
- [NEW] JSON validation using go-validate-yourself (very powerful options)
|
||||||
|
- [NEW] Completed suite of unit tests
|
||||||
|
- [NEW] HTTP streaming with c.Stream()
|
||||||
|
- [NEW] StaticFile() creates a router for serving just one file.
|
||||||
|
- [NEW] StaticFS() has an option to disable directory listing.
|
||||||
|
- [NEW] StaticFS() for serving static files through virtual filesystems
|
||||||
|
- [NEW] Server-Sent Events native support
|
||||||
|
- [NEW] WrapF() and WrapH() helpers for wrapping http.HandlerFunc and http.Handler
|
||||||
|
- [NEW] Added LoggerWithWriter() middleware
|
||||||
|
- [NEW] Added RecoveryWithWriter() middleware
|
||||||
|
- [NEW] Added DefaultPostFormValue()
|
||||||
|
- [NEW] Added DefaultFormValue()
|
||||||
|
- [NEW] Added DefaultParamValue()
|
||||||
|
- [FIX] BasicAuth() when using custom realm
|
||||||
|
- [FIX] Bug when serving static files in nested routing group
|
||||||
|
- [FIX] Redirect using built-in http.Redirect()
|
||||||
|
- [FIX] Logger when printing the requested path
|
||||||
|
- [FIX] Documentation typos
|
||||||
|
- [FIX] Context.Engine renamed to Context.engine
|
||||||
|
- [FIX] Better debugging messages
|
||||||
|
- [FIX] ErrorLogger
|
||||||
|
- [FIX] Debug HTTP render
|
||||||
|
- [FIX] Refactored binding and render modules
|
||||||
|
- [FIX] Refactored Context initialization
|
||||||
|
- [FIX] Refactored BasicAuth()
|
||||||
|
- [FIX] NoMethod/NoRoute handlers
|
||||||
|
- [FIX] Hijacking http
|
||||||
|
- [FIX] Better support for Google App Engine (using log instead of fmt)
|
||||||
|
|
||||||
|
|
||||||
|
## Gin 0.6 (Mar 9, 2015)
|
||||||
|
|
||||||
|
- [NEW] Support multipart/form-data
|
||||||
|
- [NEW] NoMethod handler
|
||||||
|
- [NEW] Validate sub structures
|
||||||
|
- [NEW] Support for HTTP Realm Auth
|
||||||
|
- [FIX] Unsigned integers in binding
|
||||||
|
- [FIX] Improve color logger
|
||||||
|
|
||||||
|
|
||||||
|
## Gin 0.5 (Feb 7, 2015)
|
||||||
|
|
||||||
|
- [NEW] Content Negotiation
|
||||||
|
- [FIX] Solved security bug that allow a client to spoof ip
|
||||||
|
- [FIX] Fix unexported/ignored fields in binding
|
||||||
|
|
||||||
|
|
||||||
|
## Gin 0.4 (Aug 21, 2014)
|
||||||
|
|
||||||
|
- [NEW] Development mode
|
||||||
|
- [NEW] Unit tests
|
||||||
|
- [NEW] Add Content.Redirect()
|
||||||
|
- [FIX] Deferring WriteHeader()
|
||||||
|
- [FIX] Improved documentation for model binding
|
||||||
|
|
||||||
|
|
||||||
|
## Gin 0.3 (Jul 18, 2014)
|
||||||
|
|
||||||
|
- [PERFORMANCE] Normal log and error log are printed in the same call.
|
||||||
|
- [PERFORMANCE] Improve performance of NoRouter()
|
||||||
|
- [PERFORMANCE] Improve context's memory locality, reduce CPU cache faults.
|
||||||
|
- [NEW] Flexible rendering API
|
||||||
|
- [NEW] Add Context.File()
|
||||||
|
- [NEW] Add shortcut RunTLS() for http.ListenAndServeTLS
|
||||||
|
- [FIX] Rename NotFound404() to NoRoute()
|
||||||
|
- [FIX] Errors in context are purged
|
||||||
|
- [FIX] Adds HEAD method in Static file serving
|
||||||
|
- [FIX] Refactors Static() file serving
|
||||||
|
- [FIX] Using keyed initialization to fix app-engine integration
|
||||||
|
- [FIX] Can't unmarshal JSON array, #63
|
||||||
|
- [FIX] Renaming Context.Req to Context.Request
|
||||||
|
- [FIX] Check application/x-www-form-urlencoded when parsing form
|
||||||
|
|
||||||
|
|
||||||
|
## Gin 0.2b (Jul 08, 2014)
|
||||||
|
- [PERFORMANCE] Using sync.Pool to allocatio/gc overhead
|
||||||
|
- [NEW] Travis CI integration
|
||||||
|
- [NEW] Completely new logger
|
||||||
|
- [NEW] New API for serving static files. gin.Static()
|
||||||
|
- [NEW] gin.H() can be serialized into XML
|
||||||
|
- [NEW] Typed errors. Errors can be typed. Internet/external/custom.
|
||||||
|
- [NEW] Support for Godeps
|
||||||
|
- [NEW] Travis/Godocs badges in README
|
||||||
|
- [NEW] New Bind() and BindWith() methods for parsing request body.
|
||||||
|
- [NEW] Add Content.Copy()
|
||||||
|
- [NEW] Add context.LastError()
|
||||||
|
- [NEW] Add shortcut for OPTIONS HTTP method
|
||||||
|
- [FIX] Tons of README fixes
|
||||||
|
- [FIX] Header is written before body
|
||||||
|
- [FIX] BasicAuth() and changes API a little bit
|
||||||
|
- [FIX] Recovery() middleware only prints panics
|
||||||
|
- [FIX] Context.Get() does not panic anymore. Use MustGet() instead.
|
||||||
|
- [FIX] Multiple http.WriteHeader() in NotFound handlers
|
||||||
|
- [FIX] Engine.Run() panics if http server can't be set up
|
||||||
|
- [FIX] Crash when route path doesn't start with '/'
|
||||||
|
- [FIX] Do not update header when status code is negative
|
||||||
|
- [FIX] Setting response headers before calling WriteHeader in context.String()
|
||||||
|
- [FIX] Add MIT license
|
||||||
|
- [FIX] Changes behaviour of ErrorLogger() and Logger()
|
46
vendor/github.com/gin-gonic/gin/CODE_OF_CONDUCT.md
generated
vendored
Normal file
46
vendor/github.com/gin-gonic/gin/CODE_OF_CONDUCT.md
generated
vendored
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
# Contributor Covenant Code of Conduct
|
||||||
|
|
||||||
|
## Our Pledge
|
||||||
|
|
||||||
|
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
||||||
|
|
||||||
|
## Our Standards
|
||||||
|
|
||||||
|
Examples of behavior that contributes to creating a positive environment include:
|
||||||
|
|
||||||
|
* Using welcoming and inclusive language
|
||||||
|
* Being respectful of differing viewpoints and experiences
|
||||||
|
* Gracefully accepting constructive criticism
|
||||||
|
* Focusing on what is best for the community
|
||||||
|
* Showing empathy towards other community members
|
||||||
|
|
||||||
|
Examples of unacceptable behavior by participants include:
|
||||||
|
|
||||||
|
* The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||||
|
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||||
|
* Public or private harassment
|
||||||
|
* Publishing others' private information, such as a physical or electronic address, without explicit permission
|
||||||
|
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||||
|
|
||||||
|
## Our Responsibilities
|
||||||
|
|
||||||
|
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
||||||
|
|
||||||
|
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
|
||||||
|
|
||||||
|
## Enforcement
|
||||||
|
|
||||||
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at teamgingonic@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
||||||
|
|
||||||
|
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
||||||
|
|
||||||
|
## Attribution
|
||||||
|
|
||||||
|
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
|
||||||
|
|
||||||
|
[homepage]: http://contributor-covenant.org
|
||||||
|
[version]: http://contributor-covenant.org/version/1/4/
|
13
vendor/github.com/gin-gonic/gin/CONTRIBUTING.md
generated
vendored
Normal file
13
vendor/github.com/gin-gonic/gin/CONTRIBUTING.md
generated
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
## Contributing
|
||||||
|
|
||||||
|
- With issues:
|
||||||
|
- Use the search tool before opening a new issue.
|
||||||
|
- Please provide source code and commit sha if you found a bug.
|
||||||
|
- Review existing issues and provide feedback or react to them.
|
||||||
|
|
||||||
|
- With pull requests:
|
||||||
|
- Open your pull request against `master`
|
||||||
|
- Your pull request should have no more than two commits, if not you should squash them.
|
||||||
|
- It should pass all tests in the available continuous integration systems such as GitHub Actions.
|
||||||
|
- You should add/modify tests to cover your proposed code changes.
|
||||||
|
- If your pull request contains a new feature, please document it on the README.
|
21
vendor/github.com/gin-gonic/gin/LICENSE
generated
vendored
Normal file
21
vendor/github.com/gin-gonic/gin/LICENSE
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2014 Manuel Martínez-Almeida
|
||||||
|
|
||||||
|
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.
|
77
vendor/github.com/gin-gonic/gin/Makefile
generated
vendored
Normal file
77
vendor/github.com/gin-gonic/gin/Makefile
generated
vendored
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
GO ?= go
|
||||||
|
GOFMT ?= gofmt "-s"
|
||||||
|
GO_VERSION=$(shell $(GO) version | cut -c 14- | cut -d' ' -f1 | cut -d'.' -f2)
|
||||||
|
PACKAGES ?= $(shell $(GO) list ./...)
|
||||||
|
VETPACKAGES ?= $(shell $(GO) list ./... | grep -v /examples/)
|
||||||
|
GOFILES := $(shell find . -name "*.go")
|
||||||
|
TESTFOLDER := $(shell $(GO) list ./... | grep -E 'gin$$|binding$$|render$$' | grep -v examples)
|
||||||
|
TESTTAGS ?= ""
|
||||||
|
|
||||||
|
.PHONY: test
|
||||||
|
test:
|
||||||
|
echo "mode: count" > coverage.out
|
||||||
|
for d in $(TESTFOLDER); do \
|
||||||
|
$(GO) test -tags $(TESTTAGS) -v -covermode=count -coverprofile=profile.out $$d > tmp.out; \
|
||||||
|
cat tmp.out; \
|
||||||
|
if grep -q "^--- FAIL" tmp.out; then \
|
||||||
|
rm tmp.out; \
|
||||||
|
exit 1; \
|
||||||
|
elif grep -q "build failed" tmp.out; then \
|
||||||
|
rm tmp.out; \
|
||||||
|
exit 1; \
|
||||||
|
elif grep -q "setup failed" 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
|
||||||
|
fmt:
|
||||||
|
$(GOFMT) -w $(GOFILES)
|
||||||
|
|
||||||
|
.PHONY: fmt-check
|
||||||
|
fmt-check:
|
||||||
|
@diff=$$($(GOFMT) -d $(GOFILES)); \
|
||||||
|
if [ -n "$$diff" ]; then \
|
||||||
|
echo "Please run 'make fmt' and commit the result:"; \
|
||||||
|
echo "$${diff}"; \
|
||||||
|
exit 1; \
|
||||||
|
fi;
|
||||||
|
|
||||||
|
vet:
|
||||||
|
$(GO) vet $(VETPACKAGES)
|
||||||
|
|
||||||
|
.PHONY: lint
|
||||||
|
lint:
|
||||||
|
@hash golint > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||||
|
$(GO) get -u golang.org/x/lint/golint; \
|
||||||
|
fi
|
||||||
|
for PKG in $(PACKAGES); do golint -set_exit_status $$PKG || exit 1; done;
|
||||||
|
|
||||||
|
.PHONY: misspell-check
|
||||||
|
misspell-check:
|
||||||
|
@hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||||
|
$(GO) get -u github.com/client9/misspell/cmd/misspell; \
|
||||||
|
fi
|
||||||
|
misspell -error $(GOFILES)
|
||||||
|
|
||||||
|
.PHONY: misspell
|
||||||
|
misspell:
|
||||||
|
@hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||||
|
$(GO) get -u github.com/client9/misspell/cmd/misspell; \
|
||||||
|
fi
|
||||||
|
misspell -w $(GOFILES)
|
||||||
|
|
||||||
|
.PHONY: tools
|
||||||
|
tools:
|
||||||
|
@if [ $(GO_VERSION) -gt 15 ]; then \
|
||||||
|
$(GO) install golang.org/x/lint/golint@latest; \
|
||||||
|
$(GO) install github.com/client9/misspell/cmd/misspell@latest; \
|
||||||
|
elif [ $(GO_VERSION) -lt 16 ]; then \
|
||||||
|
$(GO) install golang.org/x/lint/golint; \
|
||||||
|
$(GO) install github.com/client9/misspell/cmd/misspell; \
|
||||||
|
fi
|
2348
vendor/github.com/gin-gonic/gin/README.md
generated
vendored
Normal file
2348
vendor/github.com/gin-gonic/gin/README.md
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
10
vendor/github.com/gin-gonic/gin/any.go
generated
vendored
Normal file
10
vendor/github.com/gin-gonic/gin/any.go
generated
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
// Copyright 2022 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.
|
||||||
|
|
||||||
|
//go:build !go1.18
|
||||||
|
// +build !go1.18
|
||||||
|
|
||||||
|
package gin
|
||||||
|
|
||||||
|
type any = interface{}
|
91
vendor/github.com/gin-gonic/gin/auth.go
generated
vendored
Normal file
91
vendor/github.com/gin-gonic/gin/auth.go
generated
vendored
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package gin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/subtle"
|
||||||
|
"encoding/base64"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin/internal/bytesconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AuthUserKey is the cookie name for user credential in basic auth.
|
||||||
|
const AuthUserKey = "user"
|
||||||
|
|
||||||
|
// Accounts defines a key/value for user/pass list of authorized logins.
|
||||||
|
type Accounts map[string]string
|
||||||
|
|
||||||
|
type authPair struct {
|
||||||
|
value string
|
||||||
|
user string
|
||||||
|
}
|
||||||
|
|
||||||
|
type authPairs []authPair
|
||||||
|
|
||||||
|
func (a authPairs) searchCredential(authValue string) (string, bool) {
|
||||||
|
if authValue == "" {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
for _, pair := range a {
|
||||||
|
if subtle.ConstantTimeCompare(bytesconv.StringToBytes(pair.value), bytesconv.StringToBytes(authValue)) == 1 {
|
||||||
|
return pair.user, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
// BasicAuthForRealm returns a Basic HTTP Authorization middleware. It takes as arguments a map[string]string where
|
||||||
|
// the key is the user name and the value is the password, as well as the name of the Realm.
|
||||||
|
// If the realm is empty, "Authorization Required" will be used by default.
|
||||||
|
// (see http://tools.ietf.org/html/rfc2617#section-1.2)
|
||||||
|
func BasicAuthForRealm(accounts Accounts, realm string) HandlerFunc {
|
||||||
|
if realm == "" {
|
||||||
|
realm = "Authorization Required"
|
||||||
|
}
|
||||||
|
realm = "Basic realm=" + strconv.Quote(realm)
|
||||||
|
pairs := processAccounts(accounts)
|
||||||
|
return func(c *Context) {
|
||||||
|
// Search user in the slice of allowed credentials
|
||||||
|
user, found := pairs.searchCredential(c.requestHeader("Authorization"))
|
||||||
|
if !found {
|
||||||
|
// Credentials doesn't match, we return 401 and abort handlers chain.
|
||||||
|
c.Header("WWW-Authenticate", realm)
|
||||||
|
c.AbortWithStatus(http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// The user credentials was found, set user's id to key AuthUserKey in this context, the user's id can be read later using
|
||||||
|
// c.MustGet(gin.AuthUserKey).
|
||||||
|
c.Set(AuthUserKey, user)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BasicAuth returns a Basic HTTP Authorization middleware. It takes as argument a map[string]string where
|
||||||
|
// the key is the user name and the value is the password.
|
||||||
|
func BasicAuth(accounts Accounts) HandlerFunc {
|
||||||
|
return BasicAuthForRealm(accounts, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func processAccounts(accounts Accounts) authPairs {
|
||||||
|
length := len(accounts)
|
||||||
|
assert1(length > 0, "Empty list of authorized credentials")
|
||||||
|
pairs := make(authPairs, 0, length)
|
||||||
|
for user, password := range accounts {
|
||||||
|
assert1(user != "", "User can not be empty")
|
||||||
|
value := authorizationHeader(user, password)
|
||||||
|
pairs = append(pairs, authPair{
|
||||||
|
value: value,
|
||||||
|
user: user,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return pairs
|
||||||
|
}
|
||||||
|
|
||||||
|
func authorizationHeader(user, password string) string {
|
||||||
|
base := user + ":" + password
|
||||||
|
return "Basic " + base64.StdEncoding.EncodeToString(bytesconv.StringToBytes(base))
|
||||||
|
}
|
10
vendor/github.com/gin-gonic/gin/binding/any.go
generated
vendored
Normal file
10
vendor/github.com/gin-gonic/gin/binding/any.go
generated
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
// Copyright 2022 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.
|
||||||
|
|
||||||
|
//go:build !go1.18
|
||||||
|
// +build !go1.18
|
||||||
|
|
||||||
|
package binding
|
||||||
|
|
||||||
|
type any = interface{}
|
122
vendor/github.com/gin-gonic/gin/binding/binding.go
generated
vendored
Normal file
122
vendor/github.com/gin-gonic/gin/binding/binding.go
generated
vendored
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build !nomsgpack
|
||||||
|
// +build !nomsgpack
|
||||||
|
|
||||||
|
package binding
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
// Content-Type MIME of the most common data formats.
|
||||||
|
const (
|
||||||
|
MIMEJSON = "application/json"
|
||||||
|
MIMEHTML = "text/html"
|
||||||
|
MIMEXML = "application/xml"
|
||||||
|
MIMEXML2 = "text/xml"
|
||||||
|
MIMEPlain = "text/plain"
|
||||||
|
MIMEPOSTForm = "application/x-www-form-urlencoded"
|
||||||
|
MIMEMultipartPOSTForm = "multipart/form-data"
|
||||||
|
MIMEPROTOBUF = "application/x-protobuf"
|
||||||
|
MIMEMSGPACK = "application/x-msgpack"
|
||||||
|
MIMEMSGPACK2 = "application/msgpack"
|
||||||
|
MIMEYAML = "application/x-yaml"
|
||||||
|
MIMETOML = "application/toml"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Binding describes the interface which needs to be implemented for binding the
|
||||||
|
// data present in the request such as JSON request body, query parameters or
|
||||||
|
// the form POST.
|
||||||
|
type Binding interface {
|
||||||
|
Name() string
|
||||||
|
Bind(*http.Request, any) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// BindingBody adds BindBody method to Binding. BindBody is similar with Bind,
|
||||||
|
// but it reads the body from supplied bytes instead of req.Body.
|
||||||
|
type BindingBody interface {
|
||||||
|
Binding
|
||||||
|
BindBody([]byte, any) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// BindingUri adds BindUri method to Binding. BindUri is similar with Bind,
|
||||||
|
// but it reads the Params.
|
||||||
|
type BindingUri interface {
|
||||||
|
Name() string
|
||||||
|
BindUri(map[string][]string, any) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// of the request. Gin provides a default implementation for this using
|
||||||
|
// https://github.com/go-playground/validator/tree/v10.6.1.
|
||||||
|
type StructValidator interface {
|
||||||
|
// ValidateStruct can receive any kind of type and it should never panic, even if the configuration is not right.
|
||||||
|
// If the received type is a slice|array, the validation should be performed travel on every element.
|
||||||
|
// If the received type is not a struct or slice|array, any validation should be skipped and nil must be returned.
|
||||||
|
// If the received type is a struct or pointer to a struct, the validation should be performed.
|
||||||
|
// If the struct is not valid or the validation itself fails, a descriptive error should be returned.
|
||||||
|
// Otherwise nil must be returned.
|
||||||
|
ValidateStruct(any) error
|
||||||
|
|
||||||
|
// Engine returns the underlying validator engine which powers the
|
||||||
|
// StructValidator implementation.
|
||||||
|
Engine() any
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validator is the default validator which implements the StructValidator
|
||||||
|
// interface. It uses https://github.com/go-playground/validator/tree/v10.6.1
|
||||||
|
// under the hood.
|
||||||
|
var Validator StructValidator = &defaultValidator{}
|
||||||
|
|
||||||
|
// These implement the Binding interface and can be used to bind the data
|
||||||
|
// present in the request to struct instances.
|
||||||
|
var (
|
||||||
|
JSON = jsonBinding{}
|
||||||
|
XML = xmlBinding{}
|
||||||
|
Form = formBinding{}
|
||||||
|
Query = queryBinding{}
|
||||||
|
FormPost = formPostBinding{}
|
||||||
|
FormMultipart = formMultipartBinding{}
|
||||||
|
ProtoBuf = protobufBinding{}
|
||||||
|
MsgPack = msgpackBinding{}
|
||||||
|
YAML = yamlBinding{}
|
||||||
|
Uri = uriBinding{}
|
||||||
|
Header = headerBinding{}
|
||||||
|
TOML = tomlBinding{}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Default returns the appropriate Binding instance based on the HTTP method
|
||||||
|
// and the content type.
|
||||||
|
func Default(method, contentType string) Binding {
|
||||||
|
if method == http.MethodGet {
|
||||||
|
return Form
|
||||||
|
}
|
||||||
|
|
||||||
|
switch contentType {
|
||||||
|
case MIMEJSON:
|
||||||
|
return JSON
|
||||||
|
case MIMEXML, MIMEXML2:
|
||||||
|
return XML
|
||||||
|
case MIMEPROTOBUF:
|
||||||
|
return ProtoBuf
|
||||||
|
case MIMEMSGPACK, MIMEMSGPACK2:
|
||||||
|
return MsgPack
|
||||||
|
case MIMEYAML:
|
||||||
|
return YAML
|
||||||
|
case MIMETOML:
|
||||||
|
return TOML
|
||||||
|
case MIMEMultipartPOSTForm:
|
||||||
|
return FormMultipart
|
||||||
|
default: // case MIMEPOSTForm:
|
||||||
|
return Form
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func validate(obj any) error {
|
||||||
|
if Validator == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return Validator.ValidateStruct(obj)
|
||||||
|
}
|
116
vendor/github.com/gin-gonic/gin/binding/binding_nomsgpack.go
generated
vendored
Normal file
116
vendor/github.com/gin-gonic/gin/binding/binding_nomsgpack.go
generated
vendored
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
// Copyright 2020 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.
|
||||||
|
|
||||||
|
//go:build nomsgpack
|
||||||
|
// +build nomsgpack
|
||||||
|
|
||||||
|
package binding
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
// Content-Type MIME of the most common data formats.
|
||||||
|
const (
|
||||||
|
MIMEJSON = "application/json"
|
||||||
|
MIMEHTML = "text/html"
|
||||||
|
MIMEXML = "application/xml"
|
||||||
|
MIMEXML2 = "text/xml"
|
||||||
|
MIMEPlain = "text/plain"
|
||||||
|
MIMEPOSTForm = "application/x-www-form-urlencoded"
|
||||||
|
MIMEMultipartPOSTForm = "multipart/form-data"
|
||||||
|
MIMEPROTOBUF = "application/x-protobuf"
|
||||||
|
MIMEYAML = "application/x-yaml"
|
||||||
|
MIMETOML = "application/toml"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Binding describes the interface which needs to be implemented for binding the
|
||||||
|
// data present in the request such as JSON request body, query parameters or
|
||||||
|
// the form POST.
|
||||||
|
type Binding interface {
|
||||||
|
Name() string
|
||||||
|
Bind(*http.Request, any) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// BindingBody adds BindBody method to Binding. BindBody is similar with Bind,
|
||||||
|
// but it reads the body from supplied bytes instead of req.Body.
|
||||||
|
type BindingBody interface {
|
||||||
|
Binding
|
||||||
|
BindBody([]byte, any) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// BindingUri adds BindUri method to Binding. BindUri is similar with Bind,
|
||||||
|
// but it reads the Params.
|
||||||
|
type BindingUri interface {
|
||||||
|
Name() string
|
||||||
|
BindUri(map[string][]string, any) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// of the request. Gin provides a default implementation for this using
|
||||||
|
// https://github.com/go-playground/validator/tree/v10.6.1.
|
||||||
|
type StructValidator interface {
|
||||||
|
// ValidateStruct can receive any kind of type and it should never panic, even if the configuration is not right.
|
||||||
|
// If the received type is not a struct, any validation should be skipped and nil must be returned.
|
||||||
|
// If the received type is a struct or pointer to a struct, the validation should be performed.
|
||||||
|
// If the struct is not valid or the validation itself fails, a descriptive error should be returned.
|
||||||
|
// Otherwise nil must be returned.
|
||||||
|
ValidateStruct(any) error
|
||||||
|
|
||||||
|
// Engine returns the underlying validator engine which powers the
|
||||||
|
// StructValidator implementation.
|
||||||
|
Engine() any
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validator is the default validator which implements the StructValidator
|
||||||
|
// interface. It uses https://github.com/go-playground/validator/tree/v10.6.1
|
||||||
|
// under the hood.
|
||||||
|
var Validator StructValidator = &defaultValidator{}
|
||||||
|
|
||||||
|
// These implement the Binding interface and can be used to bind the data
|
||||||
|
// present in the request to struct instances.
|
||||||
|
var (
|
||||||
|
JSON = jsonBinding{}
|
||||||
|
XML = xmlBinding{}
|
||||||
|
Form = formBinding{}
|
||||||
|
Query = queryBinding{}
|
||||||
|
FormPost = formPostBinding{}
|
||||||
|
FormMultipart = formMultipartBinding{}
|
||||||
|
ProtoBuf = protobufBinding{}
|
||||||
|
YAML = yamlBinding{}
|
||||||
|
Uri = uriBinding{}
|
||||||
|
Header = headerBinding{}
|
||||||
|
TOML = tomlBinding{}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Default returns the appropriate Binding instance based on the HTTP method
|
||||||
|
// and the content type.
|
||||||
|
func Default(method, contentType string) Binding {
|
||||||
|
if method == "GET" {
|
||||||
|
return Form
|
||||||
|
}
|
||||||
|
|
||||||
|
switch contentType {
|
||||||
|
case MIMEJSON:
|
||||||
|
return JSON
|
||||||
|
case MIMEXML, MIMEXML2:
|
||||||
|
return XML
|
||||||
|
case MIMEPROTOBUF:
|
||||||
|
return ProtoBuf
|
||||||
|
case MIMEYAML:
|
||||||
|
return YAML
|
||||||
|
case MIMEMultipartPOSTForm:
|
||||||
|
return FormMultipart
|
||||||
|
case MIMETOML:
|
||||||
|
return TOML
|
||||||
|
default: // case MIMEPOSTForm:
|
||||||
|
return Form
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func validate(obj any) error {
|
||||||
|
if Validator == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return Validator.ValidateStruct(obj)
|
||||||
|
}
|
97
vendor/github.com/gin-gonic/gin/binding/default_validator.go
generated
vendored
Normal file
97
vendor/github.com/gin-gonic/gin/binding/default_validator.go
generated
vendored
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
// Copyright 2017 Manu Martinez-Almeida. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package binding
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
)
|
||||||
|
|
||||||
|
type defaultValidator struct {
|
||||||
|
once sync.Once
|
||||||
|
validate *validator.Validate
|
||||||
|
}
|
||||||
|
|
||||||
|
type SliceValidationError []error
|
||||||
|
|
||||||
|
// Error concatenates all error elements in SliceValidationError into a single string separated by \n.
|
||||||
|
func (err SliceValidationError) Error() string {
|
||||||
|
n := len(err)
|
||||||
|
switch n {
|
||||||
|
case 0:
|
||||||
|
return ""
|
||||||
|
default:
|
||||||
|
var b strings.Builder
|
||||||
|
if err[0] != nil {
|
||||||
|
fmt.Fprintf(&b, "[%d]: %s", 0, err[0].Error())
|
||||||
|
}
|
||||||
|
if n > 1 {
|
||||||
|
for i := 1; i < n; i++ {
|
||||||
|
if err[i] != nil {
|
||||||
|
b.WriteString("\n")
|
||||||
|
fmt.Fprintf(&b, "[%d]: %s", i, err[i].Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ StructValidator = &defaultValidator{}
|
||||||
|
|
||||||
|
// ValidateStruct receives any kind of type, but only performed struct or pointer to struct type.
|
||||||
|
func (v *defaultValidator) ValidateStruct(obj any) error {
|
||||||
|
if obj == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
value := reflect.ValueOf(obj)
|
||||||
|
switch value.Kind() {
|
||||||
|
case reflect.Ptr:
|
||||||
|
return v.ValidateStruct(value.Elem().Interface())
|
||||||
|
case reflect.Struct:
|
||||||
|
return v.validateStruct(obj)
|
||||||
|
case reflect.Slice, reflect.Array:
|
||||||
|
count := value.Len()
|
||||||
|
validateRet := make(SliceValidationError, 0)
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
if err := v.ValidateStruct(value.Index(i).Interface()); err != nil {
|
||||||
|
validateRet = append(validateRet, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(validateRet) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return validateRet
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateStruct receives struct type
|
||||||
|
func (v *defaultValidator) validateStruct(obj any) error {
|
||||||
|
v.lazyinit()
|
||||||
|
return v.validate.Struct(obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Engine returns the underlying validator engine which powers the default
|
||||||
|
// Validator instance. This is useful if you want to register custom validations
|
||||||
|
// or struct level validations. See validator GoDoc for more info -
|
||||||
|
// https://pkg.go.dev/github.com/go-playground/validator/v10
|
||||||
|
func (v *defaultValidator) Engine() any {
|
||||||
|
v.lazyinit()
|
||||||
|
return v.validate
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *defaultValidator) lazyinit() {
|
||||||
|
v.once.Do(func() {
|
||||||
|
v.validate = validator.New()
|
||||||
|
v.validate.SetTagName("binding")
|
||||||
|
})
|
||||||
|
}
|
62
vendor/github.com/gin-gonic/gin/binding/form.go
generated
vendored
Normal file
62
vendor/github.com/gin-gonic/gin/binding/form.go
generated
vendored
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package binding
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
const defaultMemory = 32 << 20
|
||||||
|
|
||||||
|
type formBinding struct{}
|
||||||
|
type formPostBinding struct{}
|
||||||
|
type formMultipartBinding struct{}
|
||||||
|
|
||||||
|
func (formBinding) Name() string {
|
||||||
|
return "form"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (formBinding) Bind(req *http.Request, obj any) error {
|
||||||
|
if err := req.ParseForm(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := req.ParseMultipartForm(defaultMemory); err != nil && !errors.Is(err, http.ErrNotMultipart) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := mapForm(obj, req.Form); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return validate(obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (formPostBinding) Name() string {
|
||||||
|
return "form-urlencoded"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (formPostBinding) Bind(req *http.Request, obj any) error {
|
||||||
|
if err := req.ParseForm(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := mapForm(obj, req.PostForm); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return validate(obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (formMultipartBinding) Name() string {
|
||||||
|
return "multipart/form-data"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (formMultipartBinding) Bind(req *http.Request, obj any) error {
|
||||||
|
if err := req.ParseMultipartForm(defaultMemory); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := mappingByPtr(obj, (*multipartRequest)(req), "form"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return validate(obj)
|
||||||
|
}
|
403
vendor/github.com/gin-gonic/gin/binding/form_mapping.go
generated
vendored
Normal file
403
vendor/github.com/gin-gonic/gin/binding/form_mapping.go
generated
vendored
Normal file
@ -0,0 +1,403 @@
|
|||||||
|
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package binding
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin/internal/bytesconv"
|
||||||
|
"github.com/gin-gonic/gin/internal/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errUnknownType = errors.New("unknown type")
|
||||||
|
|
||||||
|
// ErrConvertMapStringSlice can not covert to map[string][]string
|
||||||
|
ErrConvertMapStringSlice = errors.New("can not convert to map slices of strings")
|
||||||
|
|
||||||
|
// ErrConvertToMapString can not convert to map[string]string
|
||||||
|
ErrConvertToMapString = errors.New("can not convert to map of strings")
|
||||||
|
)
|
||||||
|
|
||||||
|
func mapURI(ptr any, m map[string][]string) error {
|
||||||
|
return mapFormByTag(ptr, m, "uri")
|
||||||
|
}
|
||||||
|
|
||||||
|
func mapForm(ptr any, form map[string][]string) error {
|
||||||
|
return mapFormByTag(ptr, form, "form")
|
||||||
|
}
|
||||||
|
|
||||||
|
func MapFormWithTag(ptr any, form map[string][]string, tag string) error {
|
||||||
|
return mapFormByTag(ptr, form, tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
var emptyField = reflect.StructField{}
|
||||||
|
|
||||||
|
func mapFormByTag(ptr any, form map[string][]string, tag string) error {
|
||||||
|
// Check if ptr is a map
|
||||||
|
ptrVal := reflect.ValueOf(ptr)
|
||||||
|
var pointed any
|
||||||
|
if ptrVal.Kind() == reflect.Ptr {
|
||||||
|
ptrVal = ptrVal.Elem()
|
||||||
|
pointed = ptrVal.Interface()
|
||||||
|
}
|
||||||
|
if ptrVal.Kind() == reflect.Map &&
|
||||||
|
ptrVal.Type().Key().Kind() == reflect.String {
|
||||||
|
if pointed != nil {
|
||||||
|
ptr = pointed
|
||||||
|
}
|
||||||
|
return setFormMap(ptr, form)
|
||||||
|
}
|
||||||
|
|
||||||
|
return mappingByPtr(ptr, formSource(form), tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
// setter tries to set value on a walking by fields of a struct
|
||||||
|
type setter interface {
|
||||||
|
TrySet(value reflect.Value, field reflect.StructField, key string, opt setOptions) (isSet bool, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type formSource map[string][]string
|
||||||
|
|
||||||
|
var _ setter = formSource(nil)
|
||||||
|
|
||||||
|
// TrySet tries to set a value by request's form source (like map[string][]string)
|
||||||
|
func (form formSource) TrySet(value reflect.Value, field reflect.StructField, tagValue string, opt setOptions) (isSet bool, err error) {
|
||||||
|
return setByForm(value, field, form, tagValue, opt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func mappingByPtr(ptr any, setter setter, tag string) error {
|
||||||
|
_, err := mapping(reflect.ValueOf(ptr), emptyField, setter, tag)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func mapping(value reflect.Value, field reflect.StructField, setter setter, tag string) (bool, error) {
|
||||||
|
if field.Tag.Get(tag) == "-" { // just ignoring this field
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
vKind := value.Kind()
|
||||||
|
|
||||||
|
if vKind == reflect.Ptr {
|
||||||
|
var isNew bool
|
||||||
|
vPtr := value
|
||||||
|
if value.IsNil() {
|
||||||
|
isNew = true
|
||||||
|
vPtr = reflect.New(value.Type().Elem())
|
||||||
|
}
|
||||||
|
isSet, err := mapping(vPtr.Elem(), field, setter, tag)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if isNew && isSet {
|
||||||
|
value.Set(vPtr)
|
||||||
|
}
|
||||||
|
return isSet, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if vKind != reflect.Struct || !field.Anonymous {
|
||||||
|
ok, err := tryToSetValue(value, field, setter, tag)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if ok {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if vKind == reflect.Struct {
|
||||||
|
tValue := value.Type()
|
||||||
|
|
||||||
|
var isSet bool
|
||||||
|
for i := 0; i < value.NumField(); i++ {
|
||||||
|
sf := tValue.Field(i)
|
||||||
|
if sf.PkgPath != "" && !sf.Anonymous { // unexported
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ok, err := mapping(value.Field(i), sf, setter, tag)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
isSet = isSet || ok
|
||||||
|
}
|
||||||
|
return isSet, nil
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type setOptions struct {
|
||||||
|
isDefaultExists bool
|
||||||
|
defaultValue string
|
||||||
|
}
|
||||||
|
|
||||||
|
func tryToSetValue(value reflect.Value, field reflect.StructField, setter setter, tag string) (bool, error) {
|
||||||
|
var tagValue string
|
||||||
|
var setOpt setOptions
|
||||||
|
|
||||||
|
tagValue = field.Tag.Get(tag)
|
||||||
|
tagValue, opts := head(tagValue, ",")
|
||||||
|
|
||||||
|
if tagValue == "" { // default value is FieldName
|
||||||
|
tagValue = field.Name
|
||||||
|
}
|
||||||
|
if tagValue == "" { // when field is "emptyField" variable
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var opt string
|
||||||
|
for len(opts) > 0 {
|
||||||
|
opt, opts = head(opts, ",")
|
||||||
|
|
||||||
|
if k, v := head(opt, "="); k == "default" {
|
||||||
|
setOpt.isDefaultExists = true
|
||||||
|
setOpt.defaultValue = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return setter.TrySet(value, field, tagValue, setOpt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setByForm(value reflect.Value, field reflect.StructField, form map[string][]string, tagValue string, opt setOptions) (isSet bool, err error) {
|
||||||
|
vs, ok := form[tagValue]
|
||||||
|
if !ok && !opt.isDefaultExists {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch value.Kind() {
|
||||||
|
case reflect.Slice:
|
||||||
|
if !ok {
|
||||||
|
vs = []string{opt.defaultValue}
|
||||||
|
}
|
||||||
|
return true, setSlice(vs, value, field)
|
||||||
|
case reflect.Array:
|
||||||
|
if !ok {
|
||||||
|
vs = []string{opt.defaultValue}
|
||||||
|
}
|
||||||
|
if len(vs) != value.Len() {
|
||||||
|
return false, fmt.Errorf("%q is not valid value for %s", vs, value.Type().String())
|
||||||
|
}
|
||||||
|
return true, setArray(vs, value, field)
|
||||||
|
default:
|
||||||
|
var val string
|
||||||
|
if !ok {
|
||||||
|
val = opt.defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(vs) > 0 {
|
||||||
|
val = vs[0]
|
||||||
|
}
|
||||||
|
return true, setWithProperType(val, value, field)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setWithProperType(val string, value reflect.Value, field reflect.StructField) error {
|
||||||
|
switch value.Kind() {
|
||||||
|
case reflect.Int:
|
||||||
|
return setIntField(val, 0, value)
|
||||||
|
case reflect.Int8:
|
||||||
|
return setIntField(val, 8, value)
|
||||||
|
case reflect.Int16:
|
||||||
|
return setIntField(val, 16, value)
|
||||||
|
case reflect.Int32:
|
||||||
|
return setIntField(val, 32, value)
|
||||||
|
case reflect.Int64:
|
||||||
|
switch value.Interface().(type) {
|
||||||
|
case time.Duration:
|
||||||
|
return setTimeDuration(val, value)
|
||||||
|
}
|
||||||
|
return setIntField(val, 64, value)
|
||||||
|
case reflect.Uint:
|
||||||
|
return setUintField(val, 0, value)
|
||||||
|
case reflect.Uint8:
|
||||||
|
return setUintField(val, 8, value)
|
||||||
|
case reflect.Uint16:
|
||||||
|
return setUintField(val, 16, value)
|
||||||
|
case reflect.Uint32:
|
||||||
|
return setUintField(val, 32, value)
|
||||||
|
case reflect.Uint64:
|
||||||
|
return setUintField(val, 64, value)
|
||||||
|
case reflect.Bool:
|
||||||
|
return setBoolField(val, value)
|
||||||
|
case reflect.Float32:
|
||||||
|
return setFloatField(val, 32, value)
|
||||||
|
case reflect.Float64:
|
||||||
|
return setFloatField(val, 64, value)
|
||||||
|
case reflect.String:
|
||||||
|
value.SetString(val)
|
||||||
|
case reflect.Struct:
|
||||||
|
switch value.Interface().(type) {
|
||||||
|
case time.Time:
|
||||||
|
return setTimeField(val, field, value)
|
||||||
|
}
|
||||||
|
return json.Unmarshal(bytesconv.StringToBytes(val), value.Addr().Interface())
|
||||||
|
case reflect.Map:
|
||||||
|
return json.Unmarshal(bytesconv.StringToBytes(val), value.Addr().Interface())
|
||||||
|
default:
|
||||||
|
return errUnknownType
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setIntField(val string, bitSize int, field reflect.Value) error {
|
||||||
|
if val == "" {
|
||||||
|
val = "0"
|
||||||
|
}
|
||||||
|
intVal, err := strconv.ParseInt(val, 10, bitSize)
|
||||||
|
if err == nil {
|
||||||
|
field.SetInt(intVal)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func setUintField(val string, bitSize int, field reflect.Value) error {
|
||||||
|
if val == "" {
|
||||||
|
val = "0"
|
||||||
|
}
|
||||||
|
uintVal, err := strconv.ParseUint(val, 10, bitSize)
|
||||||
|
if err == nil {
|
||||||
|
field.SetUint(uintVal)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func setBoolField(val string, field reflect.Value) error {
|
||||||
|
if val == "" {
|
||||||
|
val = "false"
|
||||||
|
}
|
||||||
|
boolVal, err := strconv.ParseBool(val)
|
||||||
|
if err == nil {
|
||||||
|
field.SetBool(boolVal)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func setFloatField(val string, bitSize int, field reflect.Value) error {
|
||||||
|
if val == "" {
|
||||||
|
val = "0.0"
|
||||||
|
}
|
||||||
|
floatVal, err := strconv.ParseFloat(val, bitSize)
|
||||||
|
if err == nil {
|
||||||
|
field.SetFloat(floatVal)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func setTimeField(val string, structField reflect.StructField, value reflect.Value) error {
|
||||||
|
timeFormat := structField.Tag.Get("time_format")
|
||||||
|
if timeFormat == "" {
|
||||||
|
timeFormat = time.RFC3339
|
||||||
|
}
|
||||||
|
|
||||||
|
switch tf := strings.ToLower(timeFormat); tf {
|
||||||
|
case "unix", "unixnano":
|
||||||
|
tv, err := strconv.ParseInt(val, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
d := time.Duration(1)
|
||||||
|
if tf == "unixnano" {
|
||||||
|
d = time.Second
|
||||||
|
}
|
||||||
|
|
||||||
|
t := time.Unix(tv/int64(d), tv%int64(d))
|
||||||
|
value.Set(reflect.ValueOf(t))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if val == "" {
|
||||||
|
value.Set(reflect.ValueOf(time.Time{}))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
l := time.Local
|
||||||
|
if isUTC, _ := strconv.ParseBool(structField.Tag.Get("time_utc")); isUTC {
|
||||||
|
l = time.UTC
|
||||||
|
}
|
||||||
|
|
||||||
|
if locTag := structField.Tag.Get("time_location"); locTag != "" {
|
||||||
|
loc, err := time.LoadLocation(locTag)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
l = loc
|
||||||
|
}
|
||||||
|
|
||||||
|
t, err := time.ParseInLocation(timeFormat, val, l)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
value.Set(reflect.ValueOf(t))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setArray(vals []string, value reflect.Value, field reflect.StructField) error {
|
||||||
|
for i, s := range vals {
|
||||||
|
err := setWithProperType(s, value.Index(i), field)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setSlice(vals []string, value reflect.Value, field reflect.StructField) error {
|
||||||
|
slice := reflect.MakeSlice(value.Type(), len(vals), len(vals))
|
||||||
|
err := setArray(vals, slice, field)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
value.Set(slice)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setTimeDuration(val string, value reflect.Value) error {
|
||||||
|
d, err := time.ParseDuration(val)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
value.Set(reflect.ValueOf(d))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func head(str, sep string) (head string, tail string) {
|
||||||
|
idx := strings.Index(str, sep)
|
||||||
|
if idx < 0 {
|
||||||
|
return str, ""
|
||||||
|
}
|
||||||
|
return str[:idx], str[idx+len(sep):]
|
||||||
|
}
|
||||||
|
|
||||||
|
func setFormMap(ptr any, form map[string][]string) error {
|
||||||
|
el := reflect.TypeOf(ptr).Elem()
|
||||||
|
|
||||||
|
if el.Kind() == reflect.Slice {
|
||||||
|
ptrMap, ok := ptr.(map[string][]string)
|
||||||
|
if !ok {
|
||||||
|
return ErrConvertMapStringSlice
|
||||||
|
}
|
||||||
|
for k, v := range form {
|
||||||
|
ptrMap[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ptrMap, ok := ptr.(map[string]string)
|
||||||
|
if !ok {
|
||||||
|
return ErrConvertToMapString
|
||||||
|
}
|
||||||
|
for k, v := range form {
|
||||||
|
ptrMap[k] = v[len(v)-1] // pick last
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
38
vendor/github.com/gin-gonic/gin/binding/header.go
generated
vendored
Normal file
38
vendor/github.com/gin-gonic/gin/binding/header.go
generated
vendored
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
// Copyright 2022 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 binding
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/textproto"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
type headerBinding struct{}
|
||||||
|
|
||||||
|
func (headerBinding) Name() string {
|
||||||
|
return "header"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (headerBinding) Bind(req *http.Request, obj any) error {
|
||||||
|
|
||||||
|
if err := mapHeader(obj, req.Header); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return validate(obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
func mapHeader(ptr any, h map[string][]string) error {
|
||||||
|
return mappingByPtr(ptr, headerSource(h), "header")
|
||||||
|
}
|
||||||
|
|
||||||
|
type headerSource map[string][]string
|
||||||
|
|
||||||
|
var _ setter = headerSource(nil)
|
||||||
|
|
||||||
|
func (hs headerSource) TrySet(value reflect.Value, field reflect.StructField, tagValue string, opt setOptions) (bool, error) {
|
||||||
|
return setByForm(value, field, hs, textproto.CanonicalMIMEHeaderKey(tagValue), opt)
|
||||||
|
}
|
56
vendor/github.com/gin-gonic/gin/binding/json.go
generated
vendored
Normal file
56
vendor/github.com/gin-gonic/gin/binding/json.go
generated
vendored
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package binding
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin/internal/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EnableDecoderUseNumber is used to call the UseNumber method on the JSON
|
||||||
|
// Decoder instance. UseNumber causes the Decoder to unmarshal a number into an
|
||||||
|
// interface{} as a Number instead of as a float64.
|
||||||
|
var EnableDecoderUseNumber = false
|
||||||
|
|
||||||
|
// EnableDecoderDisallowUnknownFields is used to call the DisallowUnknownFields method
|
||||||
|
// on the JSON Decoder instance. DisallowUnknownFields causes the Decoder to
|
||||||
|
// return an error when the destination is a struct and the input contains object
|
||||||
|
// keys which do not match any non-ignored, exported fields in the destination.
|
||||||
|
var EnableDecoderDisallowUnknownFields = false
|
||||||
|
|
||||||
|
type jsonBinding struct{}
|
||||||
|
|
||||||
|
func (jsonBinding) Name() string {
|
||||||
|
return "json"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (jsonBinding) Bind(req *http.Request, obj any) error {
|
||||||
|
if req == nil || req.Body == nil {
|
||||||
|
return errors.New("invalid request")
|
||||||
|
}
|
||||||
|
return decodeJSON(req.Body, obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (jsonBinding) BindBody(body []byte, obj any) error {
|
||||||
|
return decodeJSON(bytes.NewReader(body), obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeJSON(r io.Reader, obj any) error {
|
||||||
|
decoder := json.NewDecoder(r)
|
||||||
|
if EnableDecoderUseNumber {
|
||||||
|
decoder.UseNumber()
|
||||||
|
}
|
||||||
|
if EnableDecoderDisallowUnknownFields {
|
||||||
|
decoder.DisallowUnknownFields()
|
||||||
|
}
|
||||||
|
if err := decoder.Decode(obj); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return validate(obj)
|
||||||
|
}
|
38
vendor/github.com/gin-gonic/gin/binding/msgpack.go
generated
vendored
Normal file
38
vendor/github.com/gin-gonic/gin/binding/msgpack.go
generated
vendored
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
// Copyright 2017 Manu Martinez-Almeida. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build !nomsgpack
|
||||||
|
// +build !nomsgpack
|
||||||
|
|
||||||
|
package binding
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/ugorji/go/codec"
|
||||||
|
)
|
||||||
|
|
||||||
|
type msgpackBinding struct{}
|
||||||
|
|
||||||
|
func (msgpackBinding) Name() string {
|
||||||
|
return "msgpack"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (msgpackBinding) Bind(req *http.Request, obj any) error {
|
||||||
|
return decodeMsgPack(req.Body, obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (msgpackBinding) BindBody(body []byte, obj any) error {
|
||||||
|
return decodeMsgPack(bytes.NewReader(body), obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeMsgPack(r io.Reader, obj any) error {
|
||||||
|
cdc := new(codec.MsgpackHandle)
|
||||||
|
if err := codec.NewDecoder(r, cdc).Decode(&obj); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return validate(obj)
|
||||||
|
}
|
74
vendor/github.com/gin-gonic/gin/binding/multipart_form_mapping.go
generated
vendored
Normal file
74
vendor/github.com/gin-gonic/gin/binding/multipart_form_mapping.go
generated
vendored
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
// Copyright 2019 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 binding
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"mime/multipart"
|
||||||
|
"net/http"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
type multipartRequest http.Request
|
||||||
|
|
||||||
|
var _ setter = (*multipartRequest)(nil)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrMultiFileHeader multipart.FileHeader invalid
|
||||||
|
ErrMultiFileHeader = errors.New("unsupported field type for multipart.FileHeader")
|
||||||
|
|
||||||
|
// ErrMultiFileHeaderLenInvalid array for []*multipart.FileHeader len invalid
|
||||||
|
ErrMultiFileHeaderLenInvalid = errors.New("unsupported len of array for []*multipart.FileHeader")
|
||||||
|
)
|
||||||
|
|
||||||
|
// TrySet tries to set a value by the multipart request with the binding a form file
|
||||||
|
func (r *multipartRequest) TrySet(value reflect.Value, field reflect.StructField, key string, opt setOptions) (bool, error) {
|
||||||
|
if files := r.MultipartForm.File[key]; len(files) != 0 {
|
||||||
|
return setByMultipartFormFile(value, field, files)
|
||||||
|
}
|
||||||
|
|
||||||
|
return setByForm(value, field, r.MultipartForm.Value, key, opt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setByMultipartFormFile(value reflect.Value, field reflect.StructField, files []*multipart.FileHeader) (isSet bool, err error) {
|
||||||
|
switch value.Kind() {
|
||||||
|
case reflect.Ptr:
|
||||||
|
switch value.Interface().(type) {
|
||||||
|
case *multipart.FileHeader:
|
||||||
|
value.Set(reflect.ValueOf(files[0]))
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
case reflect.Struct:
|
||||||
|
switch value.Interface().(type) {
|
||||||
|
case multipart.FileHeader:
|
||||||
|
value.Set(reflect.ValueOf(*files[0]))
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
case reflect.Slice:
|
||||||
|
slice := reflect.MakeSlice(value.Type(), len(files), len(files))
|
||||||
|
isSet, err = setArrayOfMultipartFormFiles(slice, field, files)
|
||||||
|
if err != nil || !isSet {
|
||||||
|
return isSet, err
|
||||||
|
}
|
||||||
|
value.Set(slice)
|
||||||
|
return true, nil
|
||||||
|
case reflect.Array:
|
||||||
|
return setArrayOfMultipartFormFiles(value, field, files)
|
||||||
|
}
|
||||||
|
return false, ErrMultiFileHeader
|
||||||
|
}
|
||||||
|
|
||||||
|
func setArrayOfMultipartFormFiles(value reflect.Value, field reflect.StructField, files []*multipart.FileHeader) (isSet bool, err error) {
|
||||||
|
if value.Len() != len(files) {
|
||||||
|
return false, ErrMultiFileHeaderLenInvalid
|
||||||
|
}
|
||||||
|
for i := range files {
|
||||||
|
set, err := setByMultipartFormFile(value.Index(i), field, files[i:i+1])
|
||||||
|
if err != nil || !set {
|
||||||
|
return set, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
41
vendor/github.com/gin-gonic/gin/binding/protobuf.go
generated
vendored
Normal file
41
vendor/github.com/gin-gonic/gin/binding/protobuf.go
generated
vendored
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package binding
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"google.golang.org/protobuf/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
type protobufBinding struct{}
|
||||||
|
|
||||||
|
func (protobufBinding) Name() string {
|
||||||
|
return "protobuf"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b protobufBinding) Bind(req *http.Request, obj any) error {
|
||||||
|
buf, err := ioutil.ReadAll(req.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return b.BindBody(buf, obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (protobufBinding) BindBody(body []byte, obj any) error {
|
||||||
|
msg, ok := obj.(proto.Message)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("obj is not ProtoMessage")
|
||||||
|
}
|
||||||
|
if err := proto.Unmarshal(body, msg); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// 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
|
||||||
|
return nil
|
||||||
|
// return validate(obj)
|
||||||
|
}
|
21
vendor/github.com/gin-gonic/gin/binding/query.go
generated
vendored
Normal file
21
vendor/github.com/gin-gonic/gin/binding/query.go
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
// Copyright 2017 Manu Martinez-Almeida. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package binding
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
type queryBinding struct{}
|
||||||
|
|
||||||
|
func (queryBinding) Name() string {
|
||||||
|
return "query"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (queryBinding) Bind(req *http.Request, obj any) error {
|
||||||
|
values := req.URL.Query()
|
||||||
|
if err := mapForm(obj, values); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return validate(obj)
|
||||||
|
}
|
35
vendor/github.com/gin-gonic/gin/binding/toml.go
generated
vendored
Normal file
35
vendor/github.com/gin-gonic/gin/binding/toml.go
generated
vendored
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
// Copyright 2022 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 binding
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/pelletier/go-toml/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type tomlBinding struct{}
|
||||||
|
|
||||||
|
func (tomlBinding) Name() string {
|
||||||
|
return "toml"
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeToml(r io.Reader, obj any) error {
|
||||||
|
decoder := toml.NewDecoder(r)
|
||||||
|
if err := decoder.Decode(obj); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return decoder.Decode(obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tomlBinding) Bind(req *http.Request, obj any) error {
|
||||||
|
return decodeToml(req.Body, obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tomlBinding) BindBody(body []byte, obj any) error {
|
||||||
|
return decodeToml(bytes.NewReader(body), obj)
|
||||||
|
}
|
18
vendor/github.com/gin-gonic/gin/binding/uri.go
generated
vendored
Normal file
18
vendor/github.com/gin-gonic/gin/binding/uri.go
generated
vendored
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
// 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 binding
|
||||||
|
|
||||||
|
type uriBinding struct{}
|
||||||
|
|
||||||
|
func (uriBinding) Name() string {
|
||||||
|
return "uri"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uriBinding) BindUri(m map[string][]string, obj any) error {
|
||||||
|
if err := mapURI(obj, m); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return validate(obj)
|
||||||
|
}
|
33
vendor/github.com/gin-gonic/gin/binding/xml.go
generated
vendored
Normal file
33
vendor/github.com/gin-gonic/gin/binding/xml.go
generated
vendored
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package binding
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/xml"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type xmlBinding struct{}
|
||||||
|
|
||||||
|
func (xmlBinding) Name() string {
|
||||||
|
return "xml"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (xmlBinding) Bind(req *http.Request, obj any) error {
|
||||||
|
return decodeXML(req.Body, obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (xmlBinding) BindBody(body []byte, obj any) error {
|
||||||
|
return decodeXML(bytes.NewReader(body), obj)
|
||||||
|
}
|
||||||
|
func decodeXML(r io.Reader, obj any) error {
|
||||||
|
decoder := xml.NewDecoder(r)
|
||||||
|
if err := decoder.Decode(obj); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return validate(obj)
|
||||||
|
}
|
35
vendor/github.com/gin-gonic/gin/binding/yaml.go
generated
vendored
Normal file
35
vendor/github.com/gin-gonic/gin/binding/yaml.go
generated
vendored
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
// 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 binding
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type yamlBinding struct{}
|
||||||
|
|
||||||
|
func (yamlBinding) Name() string {
|
||||||
|
return "yaml"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (yamlBinding) Bind(req *http.Request, obj any) error {
|
||||||
|
return decodeYAML(req.Body, obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (yamlBinding) BindBody(body []byte, obj any) error {
|
||||||
|
return decodeYAML(bytes.NewReader(body), obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeYAML(r io.Reader, obj any) error {
|
||||||
|
decoder := yaml.NewDecoder(r)
|
||||||
|
if err := decoder.Decode(obj); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return validate(obj)
|
||||||
|
}
|
5
vendor/github.com/gin-gonic/gin/codecov.yml
generated
vendored
Normal file
5
vendor/github.com/gin-gonic/gin/codecov.yml
generated
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
coverage:
|
||||||
|
notify:
|
||||||
|
gitter:
|
||||||
|
default:
|
||||||
|
url: https://webhooks.gitter.im/e/d90dcdeeab2f1e357165
|
1202
vendor/github.com/gin-gonic/gin/context.go
generated
vendored
Normal file
1202
vendor/github.com/gin-gonic/gin/context.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
12
vendor/github.com/gin-gonic/gin/context_appengine.go
generated
vendored
Normal file
12
vendor/github.com/gin-gonic/gin/context_appengine.go
generated
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// Copyright 2017 Manu Martinez-Almeida. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build appengine
|
||||||
|
// +build appengine
|
||||||
|
|
||||||
|
package gin
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
defaultPlatform = PlatformGoogleAppEngine
|
||||||
|
}
|
101
vendor/github.com/gin-gonic/gin/debug.go
generated
vendored
Normal file
101
vendor/github.com/gin-gonic/gin/debug.go
generated
vendored
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package gin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const ginSupportMinGoVer = 14
|
||||||
|
|
||||||
|
// IsDebugging returns true if the framework is running in debug mode.
|
||||||
|
// Use SetMode(gin.ReleaseMode) to disable debug mode.
|
||||||
|
func IsDebugging() bool {
|
||||||
|
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) {
|
||||||
|
if IsDebugging() {
|
||||||
|
nuHandlers := len(handlers)
|
||||||
|
handlerName := nameOfFunction(handlers.Last())
|
||||||
|
if DebugPrintRouteFunc == nil {
|
||||||
|
debugPrint("%-6s %-25s --> %s (%d handlers)\n", httpMethod, absolutePath, handlerName, nuHandlers)
|
||||||
|
} else {
|
||||||
|
DebugPrintRouteFunc(httpMethod, absolutePath, handlerName, nuHandlers)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func debugPrintLoadTemplate(tmpl *template.Template) {
|
||||||
|
if IsDebugging() {
|
||||||
|
var buf strings.Builder
|
||||||
|
for _, tmpl := range tmpl.Templates() {
|
||||||
|
buf.WriteString("\t- ")
|
||||||
|
buf.WriteString(tmpl.Name())
|
||||||
|
buf.WriteString("\n")
|
||||||
|
}
|
||||||
|
debugPrint("Loaded HTML Templates (%d): \n%s\n", len(tmpl.Templates()), buf.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func debugPrint(format string, values ...any) {
|
||||||
|
if IsDebugging() {
|
||||||
|
if !strings.HasSuffix(format, "\n") {
|
||||||
|
format += "\n"
|
||||||
|
}
|
||||||
|
fmt.Fprintf(DefaultWriter, "[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() {
|
||||||
|
if v, e := getMinVer(runtime.Version()); e == nil && v <= ginSupportMinGoVer {
|
||||||
|
debugPrint(`[WARNING] Now Gin requires Go 1.14+.
|
||||||
|
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
debugPrint(`[WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
|
||||||
|
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|
||||||
|
func debugPrintWARNINGNew() {
|
||||||
|
debugPrint(`[WARNING] Running in "debug" mode. Switch to "release" mode in production.
|
||||||
|
- using env: export GIN_MODE=release
|
||||||
|
- using code: gin.SetMode(gin.ReleaseMode)
|
||||||
|
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|
||||||
|
func debugPrintWARNINGSetHTMLTemplate() {
|
||||||
|
debugPrint(`[WARNING] Since SetHTMLTemplate() is NOT thread-safe. It should only be called
|
||||||
|
at initialization. ie. before any route is registered or the router is listening in a socket:
|
||||||
|
|
||||||
|
router := gin.Default()
|
||||||
|
router.SetHTMLTemplate(template) // << good place
|
||||||
|
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|
||||||
|
func debugPrintError(err error) {
|
||||||
|
if err != nil && IsDebugging() {
|
||||||
|
fmt.Fprintf(DefaultErrorWriter, "[GIN-debug] [ERROR] %v\n", err)
|
||||||
|
}
|
||||||
|
}
|
21
vendor/github.com/gin-gonic/gin/deprecated.go
generated
vendored
Normal file
21
vendor/github.com/gin-gonic/gin/deprecated.go
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package gin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin/binding"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BindWith binds the passed struct pointer using the specified binding engine.
|
||||||
|
// See the binding package.
|
||||||
|
func (c *Context) BindWith(obj any, b binding.Binding) error {
|
||||||
|
log.Println(`BindWith(\"interface{}, binding.Binding\") error is going to
|
||||||
|
be deprecated, please check issue #662 and either use MustBindWith() if you
|
||||||
|
want HTTP 400 to be automatically returned if any error occur, or use
|
||||||
|
ShouldBindWith() if you need to manage the error.`)
|
||||||
|
return c.MustBindWith(obj, b)
|
||||||
|
}
|
6
vendor/github.com/gin-gonic/gin/doc.go
generated
vendored
Normal file
6
vendor/github.com/gin-gonic/gin/doc.go
generated
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
/*
|
||||||
|
Package gin implements a HTTP web framework called gin.
|
||||||
|
|
||||||
|
See https://gin-gonic.com/ for more information about gin.
|
||||||
|
*/
|
||||||
|
package gin // import "github.com/gin-gonic/gin"
|
174
vendor/github.com/gin-gonic/gin/errors.go
generated
vendored
Normal file
174
vendor/github.com/gin-gonic/gin/errors.go
generated
vendored
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package gin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin/internal/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrorType is an unsigned 64-bit error code as defined in the gin spec.
|
||||||
|
type ErrorType uint64
|
||||||
|
|
||||||
|
const (
|
||||||
|
// ErrorTypeBind is used when Context.Bind() 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
|
||||||
|
// ErrorTypePublic indicates a public error.
|
||||||
|
ErrorTypePublic ErrorType = 1 << 1
|
||||||
|
// ErrorTypeAny indicates any other error.
|
||||||
|
ErrorTypeAny ErrorType = 1<<64 - 1
|
||||||
|
// ErrorTypeNu indicates any other error.
|
||||||
|
ErrorTypeNu = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
// Error represents a error's specification.
|
||||||
|
type Error struct {
|
||||||
|
Err error
|
||||||
|
Type ErrorType
|
||||||
|
Meta any
|
||||||
|
}
|
||||||
|
|
||||||
|
type errorMsgs []*Error
|
||||||
|
|
||||||
|
var _ error = &Error{}
|
||||||
|
|
||||||
|
// SetType sets the error's type.
|
||||||
|
func (msg *Error) SetType(flags ErrorType) *Error {
|
||||||
|
msg.Type = flags
|
||||||
|
return msg
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetMeta sets the error's meta data.
|
||||||
|
func (msg *Error) SetMeta(data any) *Error {
|
||||||
|
msg.Meta = data
|
||||||
|
return msg
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSON creates a properly formatted JSON
|
||||||
|
func (msg *Error) JSON() any {
|
||||||
|
jsonData := H{}
|
||||||
|
if msg.Meta != nil {
|
||||||
|
value := reflect.ValueOf(msg.Meta)
|
||||||
|
switch value.Kind() {
|
||||||
|
case reflect.Struct:
|
||||||
|
return msg.Meta
|
||||||
|
case reflect.Map:
|
||||||
|
for _, key := range value.MapKeys() {
|
||||||
|
jsonData[key.String()] = value.MapIndex(key).Interface()
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
jsonData["meta"] = msg.Meta
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, ok := jsonData["error"]; !ok {
|
||||||
|
jsonData["error"] = msg.Error()
|
||||||
|
}
|
||||||
|
return jsonData
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON implements the json.Marshaller interface.
|
||||||
|
func (msg *Error) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(msg.JSON())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error implements the error interface.
|
||||||
|
func (msg Error) Error() string {
|
||||||
|
return msg.Err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsType judges one error.
|
||||||
|
func (msg *Error) IsType(flags ErrorType) bool {
|
||||||
|
return (msg.Type & flags) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unwrap returns the wrapped error, to allow interoperability with errors.Is(), errors.As() and errors.Unwrap()
|
||||||
|
func (msg *Error) Unwrap() error {
|
||||||
|
return msg.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ByType returns a readonly copy filtered the byte.
|
||||||
|
// ie ByType(gin.ErrorTypePublic) returns a slice of errors with type=ErrorTypePublic.
|
||||||
|
func (a errorMsgs) ByType(typ ErrorType) errorMsgs {
|
||||||
|
if len(a) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if typ == ErrorTypeAny {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
var result errorMsgs
|
||||||
|
for _, msg := range a {
|
||||||
|
if msg.IsType(typ) {
|
||||||
|
result = append(result, msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Last returns the last error in the slice. It returns nil if the array is empty.
|
||||||
|
// Shortcut for errors[len(errors)-1].
|
||||||
|
func (a errorMsgs) Last() *Error {
|
||||||
|
if length := len(a); length > 0 {
|
||||||
|
return a[length-1]
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Errors returns an array with all the error messages.
|
||||||
|
// Example:
|
||||||
|
// c.Error(errors.New("first"))
|
||||||
|
// c.Error(errors.New("second"))
|
||||||
|
// c.Error(errors.New("third"))
|
||||||
|
// c.Errors.Errors() // == []string{"first", "second", "third"}
|
||||||
|
func (a errorMsgs) Errors() []string {
|
||||||
|
if len(a) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
errorStrings := make([]string, len(a))
|
||||||
|
for i, err := range a {
|
||||||
|
errorStrings[i] = err.Error()
|
||||||
|
}
|
||||||
|
return errorStrings
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a errorMsgs) JSON() any {
|
||||||
|
switch length := len(a); length {
|
||||||
|
case 0:
|
||||||
|
return nil
|
||||||
|
case 1:
|
||||||
|
return a.Last().JSON()
|
||||||
|
default:
|
||||||
|
jsonData := make([]any, length)
|
||||||
|
for i, err := range a {
|
||||||
|
jsonData[i] = err.JSON()
|
||||||
|
}
|
||||||
|
return jsonData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON implements the json.Marshaller interface.
|
||||||
|
func (a errorMsgs) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(a.JSON())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a errorMsgs) String() string {
|
||||||
|
if len(a) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
var buffer strings.Builder
|
||||||
|
for i, msg := range a {
|
||||||
|
fmt.Fprintf(&buffer, "Error #%02d: %s\n", i+1, msg.Err)
|
||||||
|
if msg.Meta != nil {
|
||||||
|
fmt.Fprintf(&buffer, " Meta: %v\n", msg.Meta)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buffer.String()
|
||||||
|
}
|
45
vendor/github.com/gin-gonic/gin/fs.go
generated
vendored
Normal file
45
vendor/github.com/gin-gonic/gin/fs.go
generated
vendored
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
// Copyright 2017 Manu Martinez-Almeida. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package gin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
type onlyFilesFS struct {
|
||||||
|
fs http.FileSystem
|
||||||
|
}
|
||||||
|
|
||||||
|
type neuteredReaddirFile struct {
|
||||||
|
http.File
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dir returns a http.FileSystem that can be used by http.FileServer(). It is used internally
|
||||||
|
// in router.Static().
|
||||||
|
// if listDirectory == true, then it works the same as http.Dir() otherwise it returns
|
||||||
|
// a filesystem that prevents http.FileServer() to list the directory files.
|
||||||
|
func Dir(root string, listDirectory bool) http.FileSystem {
|
||||||
|
fs := http.Dir(root)
|
||||||
|
if listDirectory {
|
||||||
|
return fs
|
||||||
|
}
|
||||||
|
return &onlyFilesFS{fs}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open conforms to http.Filesystem.
|
||||||
|
func (fs onlyFilesFS) Open(name string) (http.File, error) {
|
||||||
|
f, err := fs.fs.Open(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return neuteredReaddirFile{f}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Readdir overrides the http.File default implementation.
|
||||||
|
func (f neuteredReaddirFile) Readdir(count int) ([]os.FileInfo, error) {
|
||||||
|
// this disables directory listing
|
||||||
|
return nil, nil
|
||||||
|
}
|
704
vendor/github.com/gin-gonic/gin/gin.go
generated
vendored
Normal file
704
vendor/github.com/gin-gonic/gin/gin.go
generated
vendored
Normal file
@ -0,0 +1,704 @@
|
|||||||
|
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package gin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin/internal/bytesconv"
|
||||||
|
"github.com/gin-gonic/gin/render"
|
||||||
|
"golang.org/x/net/http2"
|
||||||
|
"golang.org/x/net/http2/h2c"
|
||||||
|
)
|
||||||
|
|
||||||
|
const defaultMultipartMemory = 32 << 20 // 32 MB
|
||||||
|
|
||||||
|
var (
|
||||||
|
default404Body = []byte("404 page not found")
|
||||||
|
default405Body = []byte("405 method not allowed")
|
||||||
|
)
|
||||||
|
|
||||||
|
var defaultPlatform string
|
||||||
|
|
||||||
|
var defaultTrustedCIDRs = []*net.IPNet{
|
||||||
|
{ // 0.0.0.0/0 (IPv4)
|
||||||
|
IP: net.IP{0x0, 0x0, 0x0, 0x0},
|
||||||
|
Mask: net.IPMask{0x0, 0x0, 0x0, 0x0},
|
||||||
|
},
|
||||||
|
{ // ::/0 (IPv6)
|
||||||
|
IP: net.IP{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},
|
||||||
|
Mask: net.IPMask{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandlerFunc defines the handler used by gin middleware as return value.
|
||||||
|
type HandlerFunc func(*Context)
|
||||||
|
|
||||||
|
// HandlersChain defines a HandlerFunc slice.
|
||||||
|
type HandlersChain []HandlerFunc
|
||||||
|
|
||||||
|
// Last returns the last handler in the chain. i.e. the last handler is the main one.
|
||||||
|
func (c HandlersChain) Last() HandlerFunc {
|
||||||
|
if length := len(c); length > 0 {
|
||||||
|
return c[length-1]
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RouteInfo represents a request route's specification which contains method and path and its handler.
|
||||||
|
type RouteInfo struct {
|
||||||
|
Method string
|
||||||
|
Path string
|
||||||
|
Handler string
|
||||||
|
HandlerFunc HandlerFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
// RoutesInfo defines a RouteInfo slice.
|
||||||
|
type RoutesInfo []RouteInfo
|
||||||
|
|
||||||
|
// Trusted platforms
|
||||||
|
const (
|
||||||
|
// PlatformGoogleAppEngine when running on Google App Engine. Trust X-Appengine-Remote-Addr
|
||||||
|
// for determining the client's IP
|
||||||
|
PlatformGoogleAppEngine = "X-Appengine-Remote-Addr"
|
||||||
|
// PlatformCloudflare when using Cloudflare's CDN. Trust CF-Connecting-IP for determining
|
||||||
|
// the client's IP
|
||||||
|
PlatformCloudflare = "CF-Connecting-IP"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Engine is the framework's instance, it contains the muxer, middleware and configuration settings.
|
||||||
|
// Create an instance of Engine, by using New() or Default()
|
||||||
|
type Engine struct {
|
||||||
|
RouterGroup
|
||||||
|
|
||||||
|
// RedirectTrailingSlash enables automatic redirection if the current route can't be matched but a
|
||||||
|
// handler for the path with (without) the trailing slash exists.
|
||||||
|
// For example if /foo/ is requested but a route only exists for /foo, the
|
||||||
|
// client is redirected to /foo with http status code 301 for GET requests
|
||||||
|
// and 307 for all other request methods.
|
||||||
|
RedirectTrailingSlash bool
|
||||||
|
|
||||||
|
// RedirectFixedPath if enabled, the router tries to fix the current request path, if no
|
||||||
|
// handle is registered for it.
|
||||||
|
// First superfluous path elements like ../ or // are removed.
|
||||||
|
// Afterwards the router does a case-insensitive lookup of the cleaned path.
|
||||||
|
// If a handle can be found for this route, the router makes a redirection
|
||||||
|
// to the corrected path with status code 301 for GET requests and 307 for
|
||||||
|
// all other request methods.
|
||||||
|
// For example /FOO and /..//Foo could be redirected to /foo.
|
||||||
|
// RedirectTrailingSlash is independent of this option.
|
||||||
|
RedirectFixedPath bool
|
||||||
|
|
||||||
|
// HandleMethodNotAllowed if enabled, the router checks if another method is allowed for the
|
||||||
|
// current route, if the current request can not be routed.
|
||||||
|
// If this is the case, the request is answered with 'Method Not Allowed'
|
||||||
|
// and HTTP status code 405.
|
||||||
|
// If no other Method is allowed, the request is delegated to the NotFound
|
||||||
|
// handler.
|
||||||
|
HandleMethodNotAllowed bool
|
||||||
|
|
||||||
|
// ForwardedByClientIP if enabled, client IP will be parsed from the request's headers that
|
||||||
|
// match those stored at `(*gin.Engine).RemoteIPHeaders`. If no IP was
|
||||||
|
// fetched, it falls back to the IP obtained from
|
||||||
|
// `(*gin.Context).Request.RemoteAddr`.
|
||||||
|
ForwardedByClientIP bool
|
||||||
|
|
||||||
|
// AppEngine was deprecated.
|
||||||
|
// Deprecated: USE `TrustedPlatform` WITH VALUE `gin.PlatformGoogleAppEngine` INSTEAD
|
||||||
|
// #726 #755 If enabled, it will trust some headers starting with
|
||||||
|
// 'X-AppEngine...' for better integration with that PaaS.
|
||||||
|
AppEngine bool
|
||||||
|
|
||||||
|
// UseRawPath if enabled, the url.RawPath will be used to find parameters.
|
||||||
|
UseRawPath bool
|
||||||
|
|
||||||
|
// UnescapePathValues if true, the path value will be unescaped.
|
||||||
|
// If UseRawPath is false (by default), the UnescapePathValues effectively is true,
|
||||||
|
// as url.Path gonna be used, which is already unescaped.
|
||||||
|
UnescapePathValues bool
|
||||||
|
|
||||||
|
// RemoveExtraSlash a parameter can be parsed from the URL even with extra slashes.
|
||||||
|
// See the PR #1817 and issue #1644
|
||||||
|
RemoveExtraSlash bool
|
||||||
|
|
||||||
|
// RemoteIPHeaders list of headers used to obtain the client IP when
|
||||||
|
// `(*gin.Engine).ForwardedByClientIP` is `true` and
|
||||||
|
// `(*gin.Context).Request.RemoteAddr` is matched by at least one of the
|
||||||
|
// network origins of list defined by `(*gin.Engine).SetTrustedProxies()`.
|
||||||
|
RemoteIPHeaders []string
|
||||||
|
|
||||||
|
// TrustedPlatform if set to a constant of value gin.Platform*, trusts the headers set by
|
||||||
|
// that platform, for example to determine the client IP
|
||||||
|
TrustedPlatform string
|
||||||
|
|
||||||
|
// MaxMultipartMemory value of 'maxMemory' param that is given to http.Request's ParseMultipartForm
|
||||||
|
// method call.
|
||||||
|
MaxMultipartMemory int64
|
||||||
|
|
||||||
|
// UseH2C enable h2c support.
|
||||||
|
UseH2C bool
|
||||||
|
|
||||||
|
// ContextWithFallback enable fallback Context.Deadline(), Context.Done(), Context.Err() and Context.Value() when Context.Request.Context() is not nil.
|
||||||
|
ContextWithFallback bool
|
||||||
|
|
||||||
|
delims render.Delims
|
||||||
|
secureJSONPrefix string
|
||||||
|
HTMLRender render.HTMLRender
|
||||||
|
FuncMap template.FuncMap
|
||||||
|
allNoRoute HandlersChain
|
||||||
|
allNoMethod HandlersChain
|
||||||
|
noRoute HandlersChain
|
||||||
|
noMethod HandlersChain
|
||||||
|
pool sync.Pool
|
||||||
|
trees methodTrees
|
||||||
|
maxParams uint16
|
||||||
|
maxSections uint16
|
||||||
|
trustedProxies []string
|
||||||
|
trustedCIDRs []*net.IPNet
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ IRouter = &Engine{}
|
||||||
|
|
||||||
|
// New returns a new blank Engine instance without any middleware attached.
|
||||||
|
// By default, the configuration is:
|
||||||
|
// - RedirectTrailingSlash: true
|
||||||
|
// - RedirectFixedPath: false
|
||||||
|
// - HandleMethodNotAllowed: false
|
||||||
|
// - ForwardedByClientIP: true
|
||||||
|
// - UseRawPath: false
|
||||||
|
// - UnescapePathValues: true
|
||||||
|
func New() *Engine {
|
||||||
|
debugPrintWARNINGNew()
|
||||||
|
engine := &Engine{
|
||||||
|
RouterGroup: RouterGroup{
|
||||||
|
Handlers: nil,
|
||||||
|
basePath: "/",
|
||||||
|
root: true,
|
||||||
|
},
|
||||||
|
FuncMap: template.FuncMap{},
|
||||||
|
RedirectTrailingSlash: true,
|
||||||
|
RedirectFixedPath: false,
|
||||||
|
HandleMethodNotAllowed: false,
|
||||||
|
ForwardedByClientIP: true,
|
||||||
|
RemoteIPHeaders: []string{"X-Forwarded-For", "X-Real-IP"},
|
||||||
|
TrustedPlatform: defaultPlatform,
|
||||||
|
UseRawPath: false,
|
||||||
|
RemoveExtraSlash: false,
|
||||||
|
UnescapePathValues: true,
|
||||||
|
MaxMultipartMemory: defaultMultipartMemory,
|
||||||
|
trees: make(methodTrees, 0, 9),
|
||||||
|
delims: render.Delims{Left: "{{", Right: "}}"},
|
||||||
|
secureJSONPrefix: "while(1);",
|
||||||
|
trustedProxies: []string{"0.0.0.0/0", "::/0"},
|
||||||
|
trustedCIDRs: defaultTrustedCIDRs,
|
||||||
|
}
|
||||||
|
engine.RouterGroup.engine = engine
|
||||||
|
engine.pool.New = func() any {
|
||||||
|
return engine.allocateContext()
|
||||||
|
}
|
||||||
|
return engine
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default returns an Engine instance with the Logger and Recovery middleware already attached.
|
||||||
|
func Default() *Engine {
|
||||||
|
debugPrintWARNINGDefault()
|
||||||
|
engine := New()
|
||||||
|
engine.Use(Logger(), Recovery())
|
||||||
|
return engine
|
||||||
|
}
|
||||||
|
|
||||||
|
func (engine *Engine) Handler() http.Handler {
|
||||||
|
if !engine.UseH2C {
|
||||||
|
return engine
|
||||||
|
}
|
||||||
|
|
||||||
|
h2s := &http2.Server{}
|
||||||
|
return h2c.NewHandler(engine, h2s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (engine *Engine) allocateContext() *Context {
|
||||||
|
v := make(Params, 0, engine.maxParams)
|
||||||
|
skippedNodes := make([]skippedNode, 0, engine.maxSections)
|
||||||
|
return &Context{engine: engine, params: &v, skippedNodes: &skippedNodes}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delims sets template left and right delims and returns an Engine instance.
|
||||||
|
func (engine *Engine) Delims(left, right string) *Engine {
|
||||||
|
engine.delims = render.Delims{Left: left, Right: right}
|
||||||
|
return engine
|
||||||
|
}
|
||||||
|
|
||||||
|
// SecureJsonPrefix sets the secureJSONPrefix used in Context.SecureJSON.
|
||||||
|
func (engine *Engine) SecureJsonPrefix(prefix string) *Engine {
|
||||||
|
engine.secureJSONPrefix = prefix
|
||||||
|
return engine
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadHTMLGlob loads HTML files identified by glob pattern
|
||||||
|
// and associates the result with HTML renderer.
|
||||||
|
func (engine *Engine) LoadHTMLGlob(pattern string) {
|
||||||
|
left := engine.delims.Left
|
||||||
|
right := engine.delims.Right
|
||||||
|
templ := template.Must(template.New("").Delims(left, right).Funcs(engine.FuncMap).ParseGlob(pattern))
|
||||||
|
|
||||||
|
if IsDebugging() {
|
||||||
|
debugPrintLoadTemplate(templ)
|
||||||
|
engine.HTMLRender = render.HTMLDebug{Glob: pattern, FuncMap: engine.FuncMap, Delims: engine.delims}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
engine.SetHTMLTemplate(templ)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadHTMLFiles loads a slice of HTML files
|
||||||
|
// and associates the result with HTML renderer.
|
||||||
|
func (engine *Engine) LoadHTMLFiles(files ...string) {
|
||||||
|
if IsDebugging() {
|
||||||
|
engine.HTMLRender = render.HTMLDebug{Files: files, FuncMap: engine.FuncMap, Delims: engine.delims}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
templ := template.Must(template.New("").Delims(engine.delims.Left, engine.delims.Right).Funcs(engine.FuncMap).ParseFiles(files...))
|
||||||
|
engine.SetHTMLTemplate(templ)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetHTMLTemplate associate a template with HTML renderer.
|
||||||
|
func (engine *Engine) SetHTMLTemplate(templ *template.Template) {
|
||||||
|
if len(engine.trees) > 0 {
|
||||||
|
debugPrintWARNINGSetHTMLTemplate()
|
||||||
|
}
|
||||||
|
|
||||||
|
engine.HTMLRender = render.HTMLProduction{Template: templ.Funcs(engine.FuncMap)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetFuncMap sets the FuncMap used for template.FuncMap.
|
||||||
|
func (engine *Engine) SetFuncMap(funcMap template.FuncMap) {
|
||||||
|
engine.FuncMap = funcMap
|
||||||
|
}
|
||||||
|
|
||||||
|
// NoRoute adds handlers for NoRoute. It returns a 404 code by default.
|
||||||
|
func (engine *Engine) NoRoute(handlers ...HandlerFunc) {
|
||||||
|
engine.noRoute = handlers
|
||||||
|
engine.rebuild404Handlers()
|
||||||
|
}
|
||||||
|
|
||||||
|
// NoMethod sets the handlers called when Engine.HandleMethodNotAllowed = true.
|
||||||
|
func (engine *Engine) NoMethod(handlers ...HandlerFunc) {
|
||||||
|
engine.noMethod = handlers
|
||||||
|
engine.rebuild405Handlers()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use attaches a global middleware to the router. i.e. the middleware attached through Use() will be
|
||||||
|
// included in the handlers chain for every single request. Even 404, 405, static files...
|
||||||
|
// For example, this is the right place for a logger or error management middleware.
|
||||||
|
func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
|
||||||
|
engine.RouterGroup.Use(middleware...)
|
||||||
|
engine.rebuild404Handlers()
|
||||||
|
engine.rebuild405Handlers()
|
||||||
|
return engine
|
||||||
|
}
|
||||||
|
|
||||||
|
func (engine *Engine) rebuild404Handlers() {
|
||||||
|
engine.allNoRoute = engine.combineHandlers(engine.noRoute)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (engine *Engine) rebuild405Handlers() {
|
||||||
|
engine.allNoMethod = engine.combineHandlers(engine.noMethod)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
|
||||||
|
assert1(path[0] == '/', "path must begin with '/'")
|
||||||
|
assert1(method != "", "HTTP method can not be empty")
|
||||||
|
assert1(len(handlers) > 0, "there must be at least one handler")
|
||||||
|
|
||||||
|
debugPrintRoute(method, path, handlers)
|
||||||
|
|
||||||
|
root := engine.trees.get(method)
|
||||||
|
if root == nil {
|
||||||
|
root = new(node)
|
||||||
|
root.fullPath = "/"
|
||||||
|
engine.trees = append(engine.trees, methodTree{method: method, root: root})
|
||||||
|
}
|
||||||
|
root.addRoute(path, handlers)
|
||||||
|
|
||||||
|
// Update maxParams
|
||||||
|
if paramsCount := countParams(path); paramsCount > engine.maxParams {
|
||||||
|
engine.maxParams = paramsCount
|
||||||
|
}
|
||||||
|
|
||||||
|
if sectionsCount := countSections(path); sectionsCount > engine.maxSections {
|
||||||
|
engine.maxSections = sectionsCount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Routes returns a slice of registered routes, including some useful information, such as:
|
||||||
|
// the http method, path and the handler name.
|
||||||
|
func (engine *Engine) Routes() (routes RoutesInfo) {
|
||||||
|
for _, tree := range engine.trees {
|
||||||
|
routes = iterate("", tree.method, routes, tree.root)
|
||||||
|
}
|
||||||
|
return routes
|
||||||
|
}
|
||||||
|
|
||||||
|
func iterate(path, method string, routes RoutesInfo, root *node) RoutesInfo {
|
||||||
|
path += root.path
|
||||||
|
if len(root.handlers) > 0 {
|
||||||
|
handlerFunc := root.handlers.Last()
|
||||||
|
routes = append(routes, RouteInfo{
|
||||||
|
Method: method,
|
||||||
|
Path: path,
|
||||||
|
Handler: nameOfFunction(handlerFunc),
|
||||||
|
HandlerFunc: handlerFunc,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for _, child := range root.children {
|
||||||
|
routes = iterate(path, method, routes, child)
|
||||||
|
}
|
||||||
|
return routes
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run attaches the router to a http.Server and starts listening and serving HTTP requests.
|
||||||
|
// It is a shortcut for http.ListenAndServe(addr, router)
|
||||||
|
// Note: this method will block the calling goroutine indefinitely unless an error happens.
|
||||||
|
func (engine *Engine) Run(addr ...string) (err error) {
|
||||||
|
defer func() { debugPrintError(err) }()
|
||||||
|
|
||||||
|
if engine.isUnsafeTrustedProxies() {
|
||||||
|
debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" +
|
||||||
|
"Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.")
|
||||||
|
}
|
||||||
|
|
||||||
|
address := resolveAddress(addr)
|
||||||
|
debugPrint("Listening and serving HTTP on %s\n", address)
|
||||||
|
err = http.ListenAndServe(address, engine.Handler())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (engine *Engine) prepareTrustedCIDRs() ([]*net.IPNet, error) {
|
||||||
|
if engine.trustedProxies == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cidr := make([]*net.IPNet, 0, len(engine.trustedProxies))
|
||||||
|
for _, trustedProxy := range engine.trustedProxies {
|
||||||
|
if !strings.Contains(trustedProxy, "/") {
|
||||||
|
ip := parseIP(trustedProxy)
|
||||||
|
if ip == nil {
|
||||||
|
return cidr, &net.ParseError{Type: "IP address", Text: trustedProxy}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch len(ip) {
|
||||||
|
case net.IPv4len:
|
||||||
|
trustedProxy += "/32"
|
||||||
|
case net.IPv6len:
|
||||||
|
trustedProxy += "/128"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, cidrNet, err := net.ParseCIDR(trustedProxy)
|
||||||
|
if err != nil {
|
||||||
|
return cidr, err
|
||||||
|
}
|
||||||
|
cidr = append(cidr, cidrNet)
|
||||||
|
}
|
||||||
|
return cidr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTrustedProxies set a list of network origins (IPv4 addresses,
|
||||||
|
// IPv4 CIDRs, IPv6 addresses or IPv6 CIDRs) from which to trust
|
||||||
|
// request's headers that contain alternative client IP when
|
||||||
|
// `(*gin.Engine).ForwardedByClientIP` is `true`. `TrustedProxies`
|
||||||
|
// feature is enabled by default, and it also trusts all proxies
|
||||||
|
// by default. If you want to disable this feature, use
|
||||||
|
// Engine.SetTrustedProxies(nil), then Context.ClientIP() will
|
||||||
|
// return the remote address directly.
|
||||||
|
func (engine *Engine) SetTrustedProxies(trustedProxies []string) error {
|
||||||
|
engine.trustedProxies = trustedProxies
|
||||||
|
return engine.parseTrustedProxies()
|
||||||
|
}
|
||||||
|
|
||||||
|
// isUnsafeTrustedProxies checks if Engine.trustedCIDRs contains all IPs, it's not safe if it has (returns true)
|
||||||
|
func (engine *Engine) isUnsafeTrustedProxies() bool {
|
||||||
|
return engine.isTrustedProxy(net.ParseIP("0.0.0.0")) || engine.isTrustedProxy(net.ParseIP("::"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseTrustedProxies parse Engine.trustedProxies to Engine.trustedCIDRs
|
||||||
|
func (engine *Engine) parseTrustedProxies() error {
|
||||||
|
trustedCIDRs, err := engine.prepareTrustedCIDRs()
|
||||||
|
engine.trustedCIDRs = trustedCIDRs
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// isTrustedProxy will check whether the IP address is included in the trusted list according to Engine.trustedCIDRs
|
||||||
|
func (engine *Engine) isTrustedProxy(ip net.IP) bool {
|
||||||
|
if engine.trustedCIDRs == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, cidr := range engine.trustedCIDRs {
|
||||||
|
if cidr.Contains(ip) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// validateHeader will parse X-Forwarded-For header and return the trusted client IP address
|
||||||
|
func (engine *Engine) validateHeader(header string) (clientIP string, valid bool) {
|
||||||
|
if header == "" {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
items := strings.Split(header, ",")
|
||||||
|
for i := len(items) - 1; i >= 0; i-- {
|
||||||
|
ipStr := strings.TrimSpace(items[i])
|
||||||
|
ip := net.ParseIP(ipStr)
|
||||||
|
if ip == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// X-Forwarded-For is appended by proxy
|
||||||
|
// Check IPs in reverse order and stop when find untrusted proxy
|
||||||
|
if (i == 0) || (!engine.isTrustedProxy(ip)) {
|
||||||
|
return ipStr, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseIP parse a string representation of an IP and returns a net.IP with the
|
||||||
|
// minimum byte representation or nil if input is invalid.
|
||||||
|
func parseIP(ip string) net.IP {
|
||||||
|
parsedIP := net.ParseIP(ip)
|
||||||
|
|
||||||
|
if ipv4 := parsedIP.To4(); ipv4 != nil {
|
||||||
|
// return ip in a 4-byte representation
|
||||||
|
return ipv4
|
||||||
|
}
|
||||||
|
|
||||||
|
// return ip in a 16-byte representation or nil
|
||||||
|
return parsedIP
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunTLS attaches the router to a http.Server and starts listening and serving HTTPS (secure) requests.
|
||||||
|
// It is a shortcut for http.ListenAndServeTLS(addr, certFile, keyFile, router)
|
||||||
|
// Note: this method will block the calling goroutine indefinitely unless an error happens.
|
||||||
|
func (engine *Engine) RunTLS(addr, certFile, keyFile string) (err error) {
|
||||||
|
debugPrint("Listening and serving HTTPS on %s\n", addr)
|
||||||
|
defer func() { debugPrintError(err) }()
|
||||||
|
|
||||||
|
if engine.isUnsafeTrustedProxies() {
|
||||||
|
debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" +
|
||||||
|
"Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = http.ListenAndServeTLS(addr, certFile, keyFile, engine.Handler())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunUnix attaches the router to a http.Server and starts listening and serving HTTP requests
|
||||||
|
// through the specified unix socket (i.e. a file).
|
||||||
|
// Note: this method will block the calling goroutine indefinitely unless an error happens.
|
||||||
|
func (engine *Engine) RunUnix(file string) (err error) {
|
||||||
|
debugPrint("Listening and serving HTTP on unix:/%s", file)
|
||||||
|
defer func() { debugPrintError(err) }()
|
||||||
|
|
||||||
|
if engine.isUnsafeTrustedProxies() {
|
||||||
|
debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" +
|
||||||
|
"Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.")
|
||||||
|
}
|
||||||
|
|
||||||
|
listener, err := net.Listen("unix", file)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer listener.Close()
|
||||||
|
defer os.Remove(file)
|
||||||
|
|
||||||
|
err = http.Serve(listener, engine.Handler())
|
||||||
|
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) }()
|
||||||
|
|
||||||
|
if engine.isUnsafeTrustedProxies() {
|
||||||
|
debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" +
|
||||||
|
"Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.")
|
||||||
|
}
|
||||||
|
|
||||||
|
f := os.NewFile(uintptr(fd), fmt.Sprintf("fd@%d", fd))
|
||||||
|
listener, err := net.FileListener(f)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer listener.Close()
|
||||||
|
err = engine.RunListener(listener)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunListener attaches the router to a http.Server and starts listening and serving HTTP requests
|
||||||
|
// through the specified net.Listener
|
||||||
|
func (engine *Engine) RunListener(listener net.Listener) (err error) {
|
||||||
|
debugPrint("Listening and serving HTTP on listener what's bind with address@%s", listener.Addr())
|
||||||
|
defer func() { debugPrintError(err) }()
|
||||||
|
|
||||||
|
if engine.isUnsafeTrustedProxies() {
|
||||||
|
debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" +
|
||||||
|
"Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = http.Serve(listener, engine.Handler())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServeHTTP conforms to the http.Handler interface.
|
||||||
|
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
|
c := engine.pool.Get().(*Context)
|
||||||
|
c.writermem.reset(w)
|
||||||
|
c.Request = req
|
||||||
|
c.reset()
|
||||||
|
|
||||||
|
engine.handleHTTPRequest(c)
|
||||||
|
|
||||||
|
engine.pool.Put(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleContext re-enters a context that has been rewritten.
|
||||||
|
// This can be done by setting c.Request.URL.Path to your new target.
|
||||||
|
// Disclaimer: You can loop yourself to deal with this, use wisely.
|
||||||
|
func (engine *Engine) HandleContext(c *Context) {
|
||||||
|
oldIndexValue := c.index
|
||||||
|
c.reset()
|
||||||
|
engine.handleHTTPRequest(c)
|
||||||
|
|
||||||
|
c.index = oldIndexValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (engine *Engine) handleHTTPRequest(c *Context) {
|
||||||
|
httpMethod := c.Request.Method
|
||||||
|
rPath := c.Request.URL.Path
|
||||||
|
unescape := false
|
||||||
|
if engine.UseRawPath && len(c.Request.URL.RawPath) > 0 {
|
||||||
|
rPath = c.Request.URL.RawPath
|
||||||
|
unescape = engine.UnescapePathValues
|
||||||
|
}
|
||||||
|
|
||||||
|
if engine.RemoveExtraSlash {
|
||||||
|
rPath = cleanPath(rPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find root of the tree for the given HTTP method
|
||||||
|
t := engine.trees
|
||||||
|
for i, tl := 0, len(t); i < tl; i++ {
|
||||||
|
if t[i].method != httpMethod {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
root := t[i].root
|
||||||
|
// Find route in tree
|
||||||
|
value := root.getValue(rPath, c.params, c.skippedNodes, unescape)
|
||||||
|
if value.params != nil {
|
||||||
|
c.Params = *value.params
|
||||||
|
}
|
||||||
|
if value.handlers != nil {
|
||||||
|
c.handlers = value.handlers
|
||||||
|
c.fullPath = value.fullPath
|
||||||
|
c.Next()
|
||||||
|
c.writermem.WriteHeaderNow()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if httpMethod != http.MethodConnect && rPath != "/" {
|
||||||
|
if value.tsr && engine.RedirectTrailingSlash {
|
||||||
|
redirectTrailingSlash(c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if engine.RedirectFixedPath && redirectFixedPath(c, root, engine.RedirectFixedPath) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if engine.HandleMethodNotAllowed {
|
||||||
|
for _, tree := range engine.trees {
|
||||||
|
if tree.method == httpMethod {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if value := tree.root.getValue(rPath, nil, c.skippedNodes, unescape); value.handlers != nil {
|
||||||
|
c.handlers = engine.allNoMethod
|
||||||
|
serveError(c, http.StatusMethodNotAllowed, default405Body)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.handlers = engine.allNoRoute
|
||||||
|
serveError(c, http.StatusNotFound, default404Body)
|
||||||
|
}
|
||||||
|
|
||||||
|
var mimePlain = []string{MIMEPlain}
|
||||||
|
|
||||||
|
func serveError(c *Context, code int, defaultMessage []byte) {
|
||||||
|
c.writermem.status = code
|
||||||
|
c.Next()
|
||||||
|
if c.writermem.Written() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if c.writermem.Status() == code {
|
||||||
|
c.writermem.Header()["Content-Type"] = mimePlain
|
||||||
|
_, err := c.Writer.Write(defaultMessage)
|
||||||
|
if err != nil {
|
||||||
|
debugPrint("cannot write message to writer during serve error: %v", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.writermem.WriteHeaderNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
func redirectTrailingSlash(c *Context) {
|
||||||
|
req := c.Request
|
||||||
|
p := req.URL.Path
|
||||||
|
if prefix := path.Clean(c.Request.Header.Get("X-Forwarded-Prefix")); prefix != "." {
|
||||||
|
p = prefix + "/" + req.URL.Path
|
||||||
|
}
|
||||||
|
req.URL.Path = p + "/"
|
||||||
|
if length := len(p); length > 1 && p[length-1] == '/' {
|
||||||
|
req.URL.Path = p[:length-1]
|
||||||
|
}
|
||||||
|
redirectRequest(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func redirectFixedPath(c *Context, root *node, trailingSlash bool) bool {
|
||||||
|
req := c.Request
|
||||||
|
rPath := req.URL.Path
|
||||||
|
|
||||||
|
if fixedPath, ok := root.findCaseInsensitivePath(cleanPath(rPath), trailingSlash); ok {
|
||||||
|
req.URL.Path = bytesconv.BytesToString(fixedPath)
|
||||||
|
redirectRequest(c)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func redirectRequest(c *Context) {
|
||||||
|
req := c.Request
|
||||||
|
rPath := req.URL.Path
|
||||||
|
rURL := req.URL.String()
|
||||||
|
|
||||||
|
code := http.StatusMovedPermanently // Permanent redirect, request with GET method
|
||||||
|
if req.Method != http.MethodGet {
|
||||||
|
code = http.StatusTemporaryRedirect
|
||||||
|
}
|
||||||
|
debugPrint("redirecting request %d: %s --> %s", code, rPath, rURL)
|
||||||
|
http.Redirect(c.Writer, req, rURL, code)
|
||||||
|
c.writermem.WriteHeaderNow()
|
||||||
|
}
|
24
vendor/github.com/gin-gonic/gin/internal/bytesconv/bytesconv.go
generated
vendored
Normal file
24
vendor/github.com/gin-gonic/gin/internal/bytesconv/bytesconv.go
generated
vendored
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
// Copyright 2020 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 bytesconv
|
||||||
|
|
||||||
|
import (
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// StringToBytes converts string to byte slice without a memory allocation.
|
||||||
|
func StringToBytes(s string) []byte {
|
||||||
|
return *(*[]byte)(unsafe.Pointer(
|
||||||
|
&struct {
|
||||||
|
string
|
||||||
|
Cap int
|
||||||
|
}{s, len(s)},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
// BytesToString converts byte slice to string without a memory allocation.
|
||||||
|
func BytesToString(b []byte) string {
|
||||||
|
return *(*string)(unsafe.Pointer(&b))
|
||||||
|
}
|
23
vendor/github.com/gin-gonic/gin/internal/json/go_json.go
generated
vendored
Normal file
23
vendor/github.com/gin-gonic/gin/internal/json/go_json.go
generated
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
//go:build go_json
|
||||||
|
// +build go_json
|
||||||
|
|
||||||
|
package json
|
||||||
|
|
||||||
|
import json "github.com/goccy/go-json"
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Marshal is exported by gin/json package.
|
||||||
|
Marshal = json.Marshal
|
||||||
|
// Unmarshal is exported by gin/json package.
|
||||||
|
Unmarshal = json.Unmarshal
|
||||||
|
// MarshalIndent is exported by gin/json package.
|
||||||
|
MarshalIndent = json.MarshalIndent
|
||||||
|
// NewDecoder is exported by gin/json package.
|
||||||
|
NewDecoder = json.NewDecoder
|
||||||
|
// NewEncoder is exported by gin/json package.
|
||||||
|
NewEncoder = json.NewEncoder
|
||||||
|
)
|
23
vendor/github.com/gin-gonic/gin/internal/json/json.go
generated
vendored
Normal file
23
vendor/github.com/gin-gonic/gin/internal/json/json.go
generated
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
//go:build !jsoniter && !go_json
|
||||||
|
// +build !jsoniter,!go_json
|
||||||
|
|
||||||
|
package json
|
||||||
|
|
||||||
|
import "encoding/json"
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Marshal is exported by gin/json package.
|
||||||
|
Marshal = json.Marshal
|
||||||
|
// Unmarshal is exported by gin/json package.
|
||||||
|
Unmarshal = json.Unmarshal
|
||||||
|
// MarshalIndent is exported by gin/json package.
|
||||||
|
MarshalIndent = json.MarshalIndent
|
||||||
|
// NewDecoder is exported by gin/json package.
|
||||||
|
NewDecoder = json.NewDecoder
|
||||||
|
// NewEncoder is exported by gin/json package.
|
||||||
|
NewEncoder = json.NewEncoder
|
||||||
|
)
|
24
vendor/github.com/gin-gonic/gin/internal/json/jsoniter.go
generated
vendored
Normal file
24
vendor/github.com/gin-gonic/gin/internal/json/jsoniter.go
generated
vendored
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
//go:build jsoniter
|
||||||
|
// +build jsoniter
|
||||||
|
|
||||||
|
package json
|
||||||
|
|
||||||
|
import jsoniter "github.com/json-iterator/go"
|
||||||
|
|
||||||
|
var (
|
||||||
|
json = jsoniter.ConfigCompatibleWithStandardLibrary
|
||||||
|
// Marshal is exported by gin/json package.
|
||||||
|
Marshal = json.Marshal
|
||||||
|
// Unmarshal is exported by gin/json package.
|
||||||
|
Unmarshal = json.Unmarshal
|
||||||
|
// MarshalIndent is exported by gin/json package.
|
||||||
|
MarshalIndent = json.MarshalIndent
|
||||||
|
// NewDecoder is exported by gin/json package.
|
||||||
|
NewDecoder = json.NewDecoder
|
||||||
|
// NewEncoder is exported by gin/json package.
|
||||||
|
NewEncoder = json.NewEncoder
|
||||||
|
)
|
270
vendor/github.com/gin-gonic/gin/logger.go
generated
vendored
Normal file
270
vendor/github.com/gin-gonic/gin/logger.go
generated
vendored
Normal file
@ -0,0 +1,270 @@
|
|||||||
|
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package gin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/mattn/go-isatty"
|
||||||
|
)
|
||||||
|
|
||||||
|
type consoleColorModeValue int
|
||||||
|
|
||||||
|
const (
|
||||||
|
autoColor consoleColorModeValue = iota
|
||||||
|
disableColor
|
||||||
|
forceColor
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
green = "\033[97;42m"
|
||||||
|
white = "\033[90;47m"
|
||||||
|
yellow = "\033[90;43m"
|
||||||
|
red = "\033[97;41m"
|
||||||
|
blue = "\033[97;44m"
|
||||||
|
magenta = "\033[97;45m"
|
||||||
|
cyan = "\033[97;46m"
|
||||||
|
reset = "\033[0m"
|
||||||
|
)
|
||||||
|
|
||||||
|
var consoleColorMode = autoColor
|
||||||
|
|
||||||
|
// LoggerConfig defines the config for Logger middleware.
|
||||||
|
type LoggerConfig struct {
|
||||||
|
// Optional. Default value is gin.defaultLogFormatter
|
||||||
|
Formatter LogFormatter
|
||||||
|
|
||||||
|
// Output is a writer where logs are written.
|
||||||
|
// Optional. Default value is gin.DefaultWriter.
|
||||||
|
Output io.Writer
|
||||||
|
|
||||||
|
// SkipPaths is an url path array which logs are not written.
|
||||||
|
// Optional.
|
||||||
|
SkipPaths []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogFormatter gives the signature of the formatter function passed to LoggerWithFormatter
|
||||||
|
type LogFormatter func(params LogFormatterParams) string
|
||||||
|
|
||||||
|
// LogFormatterParams is the structure any formatter will be handed when time to log comes
|
||||||
|
type LogFormatterParams struct {
|
||||||
|
Request *http.Request
|
||||||
|
|
||||||
|
// TimeStamp shows the time after the server returns a response.
|
||||||
|
TimeStamp time.Time
|
||||||
|
// StatusCode is HTTP response code.
|
||||||
|
StatusCode int
|
||||||
|
// Latency is how much time the server cost to process a certain request.
|
||||||
|
Latency time.Duration
|
||||||
|
// ClientIP equals Context's ClientIP method.
|
||||||
|
ClientIP string
|
||||||
|
// Method is the HTTP method given to the request.
|
||||||
|
Method string
|
||||||
|
// Path is a path the client requests.
|
||||||
|
Path string
|
||||||
|
// ErrorMessage is set if error has occurred in processing the request.
|
||||||
|
ErrorMessage string
|
||||||
|
// isTerm shows whether gin's output descriptor refers to a terminal.
|
||||||
|
isTerm bool
|
||||||
|
// BodySize is the size of the Response Body
|
||||||
|
BodySize int
|
||||||
|
// Keys are the keys set on the request's context.
|
||||||
|
Keys map[string]any
|
||||||
|
}
|
||||||
|
|
||||||
|
// StatusCodeColor is the ANSI color for appropriately logging http status code to a terminal.
|
||||||
|
func (p *LogFormatterParams) StatusCodeColor() string {
|
||||||
|
code := p.StatusCode
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case code >= http.StatusOK && code < http.StatusMultipleChoices:
|
||||||
|
return green
|
||||||
|
case code >= http.StatusMultipleChoices && code < http.StatusBadRequest:
|
||||||
|
return white
|
||||||
|
case code >= http.StatusBadRequest && code < http.StatusInternalServerError:
|
||||||
|
return yellow
|
||||||
|
default:
|
||||||
|
return red
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MethodColor is the ANSI color for appropriately logging http method to a terminal.
|
||||||
|
func (p *LogFormatterParams) MethodColor() string {
|
||||||
|
method := p.Method
|
||||||
|
|
||||||
|
switch method {
|
||||||
|
case http.MethodGet:
|
||||||
|
return blue
|
||||||
|
case http.MethodPost:
|
||||||
|
return cyan
|
||||||
|
case http.MethodPut:
|
||||||
|
return yellow
|
||||||
|
case http.MethodDelete:
|
||||||
|
return red
|
||||||
|
case http.MethodPatch:
|
||||||
|
return green
|
||||||
|
case http.MethodHead:
|
||||||
|
return magenta
|
||||||
|
case http.MethodOptions:
|
||||||
|
return white
|
||||||
|
default:
|
||||||
|
return reset
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetColor resets all escape attributes.
|
||||||
|
func (p *LogFormatterParams) ResetColor() string {
|
||||||
|
return reset
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsOutputColor indicates whether can colors be outputted to the log.
|
||||||
|
func (p *LogFormatterParams) IsOutputColor() bool {
|
||||||
|
return consoleColorMode == forceColor || (consoleColorMode == autoColor && p.isTerm)
|
||||||
|
}
|
||||||
|
|
||||||
|
// defaultLogFormatter is the default log format function Logger middleware uses.
|
||||||
|
var defaultLogFormatter = func(param LogFormatterParams) string {
|
||||||
|
var statusColor, methodColor, resetColor string
|
||||||
|
if param.IsOutputColor() {
|
||||||
|
statusColor = param.StatusCodeColor()
|
||||||
|
methodColor = param.MethodColor()
|
||||||
|
resetColor = param.ResetColor()
|
||||||
|
}
|
||||||
|
|
||||||
|
if param.Latency > time.Minute {
|
||||||
|
param.Latency = param.Latency.Truncate(time.Second)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("[GIN] %v |%s %3d %s| %13v | %15s |%s %-7s %s %#v\n%s",
|
||||||
|
param.TimeStamp.Format("2006/01/02 - 15:04:05"),
|
||||||
|
statusColor, param.StatusCode, resetColor,
|
||||||
|
param.Latency,
|
||||||
|
param.ClientIP,
|
||||||
|
methodColor, param.Method, resetColor,
|
||||||
|
param.Path,
|
||||||
|
param.ErrorMessage,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DisableConsoleColor disables color output in the console.
|
||||||
|
func DisableConsoleColor() {
|
||||||
|
consoleColorMode = disableColor
|
||||||
|
}
|
||||||
|
|
||||||
|
// ForceConsoleColor force color output in the console.
|
||||||
|
func ForceConsoleColor() {
|
||||||
|
consoleColorMode = forceColor
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorLogger returns a HandlerFunc for any error type.
|
||||||
|
func ErrorLogger() HandlerFunc {
|
||||||
|
return ErrorLoggerT(ErrorTypeAny)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorLoggerT returns a HandlerFunc for a given error type.
|
||||||
|
func ErrorLoggerT(typ ErrorType) HandlerFunc {
|
||||||
|
return func(c *Context) {
|
||||||
|
c.Next()
|
||||||
|
errors := c.Errors.ByType(typ)
|
||||||
|
if len(errors) > 0 {
|
||||||
|
c.JSON(-1, errors)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logger instances a Logger middleware that will write the logs to gin.DefaultWriter.
|
||||||
|
// By default, gin.DefaultWriter = os.Stdout.
|
||||||
|
func Logger() HandlerFunc {
|
||||||
|
return LoggerWithConfig(LoggerConfig{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoggerWithFormatter instance a Logger middleware with the specified log format function.
|
||||||
|
func LoggerWithFormatter(f LogFormatter) HandlerFunc {
|
||||||
|
return LoggerWithConfig(LoggerConfig{
|
||||||
|
Formatter: f,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoggerWithWriter instance a Logger middleware with the specified writer buffer.
|
||||||
|
// Example: os.Stdout, a file opened in write mode, a socket...
|
||||||
|
func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc {
|
||||||
|
return LoggerWithConfig(LoggerConfig{
|
||||||
|
Output: out,
|
||||||
|
SkipPaths: notlogged,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoggerWithConfig instance a Logger middleware with config.
|
||||||
|
func LoggerWithConfig(conf LoggerConfig) HandlerFunc {
|
||||||
|
formatter := conf.Formatter
|
||||||
|
if formatter == nil {
|
||||||
|
formatter = defaultLogFormatter
|
||||||
|
}
|
||||||
|
|
||||||
|
out := conf.Output
|
||||||
|
if out == nil {
|
||||||
|
out = DefaultWriter
|
||||||
|
}
|
||||||
|
|
||||||
|
notlogged := conf.SkipPaths
|
||||||
|
|
||||||
|
isTerm := true
|
||||||
|
|
||||||
|
if w, ok := out.(*os.File); !ok || os.Getenv("TERM") == "dumb" ||
|
||||||
|
(!isatty.IsTerminal(w.Fd()) && !isatty.IsCygwinTerminal(w.Fd())) {
|
||||||
|
isTerm = false
|
||||||
|
}
|
||||||
|
|
||||||
|
var skip map[string]struct{}
|
||||||
|
|
||||||
|
if length := len(notlogged); length > 0 {
|
||||||
|
skip = make(map[string]struct{}, length)
|
||||||
|
|
||||||
|
for _, path := range notlogged {
|
||||||
|
skip[path] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return func(c *Context) {
|
||||||
|
// Start timer
|
||||||
|
start := time.Now()
|
||||||
|
path := c.Request.URL.Path
|
||||||
|
raw := c.Request.URL.RawQuery
|
||||||
|
|
||||||
|
// Process request
|
||||||
|
c.Next()
|
||||||
|
|
||||||
|
// Log only when path is not being skipped
|
||||||
|
if _, ok := skip[path]; !ok {
|
||||||
|
param := LogFormatterParams{
|
||||||
|
Request: c.Request,
|
||||||
|
isTerm: isTerm,
|
||||||
|
Keys: c.Keys,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop timer
|
||||||
|
param.TimeStamp = time.Now()
|
||||||
|
param.Latency = param.TimeStamp.Sub(start)
|
||||||
|
|
||||||
|
param.ClientIP = c.ClientIP()
|
||||||
|
param.Method = c.Request.Method
|
||||||
|
param.StatusCode = c.Writer.Status()
|
||||||
|
param.ErrorMessage = c.Errors.ByType(ErrorTypePrivate).String()
|
||||||
|
|
||||||
|
param.BodySize = c.Writer.Size()
|
||||||
|
|
||||||
|
if raw != "" {
|
||||||
|
path = path + "?" + raw
|
||||||
|
}
|
||||||
|
|
||||||
|
param.Path = path
|
||||||
|
|
||||||
|
fmt.Fprint(out, formatter(param))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
99
vendor/github.com/gin-gonic/gin/mode.go
generated
vendored
Normal file
99
vendor/github.com/gin-gonic/gin/mode.go
generated
vendored
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package gin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin/binding"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EnvGinMode indicates environment name for gin mode.
|
||||||
|
const EnvGinMode = "GIN_MODE"
|
||||||
|
|
||||||
|
const (
|
||||||
|
// DebugMode indicates gin mode is debug.
|
||||||
|
DebugMode = "debug"
|
||||||
|
// ReleaseMode indicates gin mode is release.
|
||||||
|
ReleaseMode = "release"
|
||||||
|
// TestMode indicates gin mode is test.
|
||||||
|
TestMode = "test"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
debugCode = iota
|
||||||
|
releaseCode
|
||||||
|
testCode
|
||||||
|
)
|
||||||
|
|
||||||
|
// DefaultWriter is the default io.Writer used by Gin for debug output and
|
||||||
|
// middleware output like Logger() or Recovery().
|
||||||
|
// Note that both Logger and Recovery provides custom ways to configure their
|
||||||
|
// output io.Writer.
|
||||||
|
// To support coloring in Windows use:
|
||||||
|
// import "github.com/mattn/go-colorable"
|
||||||
|
// gin.DefaultWriter = colorable.NewColorableStdout()
|
||||||
|
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 (
|
||||||
|
ginMode = debugCode
|
||||||
|
modeName = DebugMode
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
mode := os.Getenv(EnvGinMode)
|
||||||
|
SetMode(mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetMode sets gin mode according to input string.
|
||||||
|
func SetMode(value string) {
|
||||||
|
if value == "" {
|
||||||
|
if flag.Lookup("test.v") != nil {
|
||||||
|
value = TestMode
|
||||||
|
} else {
|
||||||
|
value = DebugMode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch value {
|
||||||
|
case DebugMode:
|
||||||
|
ginMode = debugCode
|
||||||
|
case ReleaseMode:
|
||||||
|
ginMode = releaseCode
|
||||||
|
case TestMode:
|
||||||
|
ginMode = testCode
|
||||||
|
default:
|
||||||
|
panic("gin mode unknown: " + value + " (available mode: debug release test)")
|
||||||
|
}
|
||||||
|
|
||||||
|
modeName = value
|
||||||
|
}
|
||||||
|
|
||||||
|
// DisableBindValidation closes the default validator.
|
||||||
|
func DisableBindValidation() {
|
||||||
|
binding.Validator = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnableJsonDecoderUseNumber sets true for binding.EnableDecoderUseNumber to
|
||||||
|
// call the UseNumber method on the JSON Decoder instance.
|
||||||
|
func EnableJsonDecoderUseNumber() {
|
||||||
|
binding.EnableDecoderUseNumber = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnableJsonDecoderDisallowUnknownFields sets true for binding.EnableDecoderDisallowUnknownFields to
|
||||||
|
// call the DisallowUnknownFields method on the JSON Decoder instance.
|
||||||
|
func EnableJsonDecoderDisallowUnknownFields() {
|
||||||
|
binding.EnableDecoderDisallowUnknownFields = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mode returns current gin mode.
|
||||||
|
func Mode() string {
|
||||||
|
return modeName
|
||||||
|
}
|
150
vendor/github.com/gin-gonic/gin/path.go
generated
vendored
Normal file
150
vendor/github.com/gin-gonic/gin/path.go
generated
vendored
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
// Copyright 2013 Julien Schmidt. All rights reserved.
|
||||||
|
// Based on the path package, Copyright 2009 The Go Authors.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be found
|
||||||
|
// at https://github.com/julienschmidt/httprouter/blob/master/LICENSE.
|
||||||
|
|
||||||
|
package gin
|
||||||
|
|
||||||
|
// cleanPath is the URL version of path.Clean, it returns a canonical URL path
|
||||||
|
// for p, eliminating . and .. elements.
|
||||||
|
//
|
||||||
|
// The following rules are applied iteratively until no further processing can
|
||||||
|
// be done:
|
||||||
|
// 1. Replace multiple slashes with a single slash.
|
||||||
|
// 2. Eliminate each . path name element (the current directory).
|
||||||
|
// 3. Eliminate each inner .. path name element (the parent directory)
|
||||||
|
// along with the non-.. element that precedes it.
|
||||||
|
// 4. Eliminate .. elements that begin a rooted path:
|
||||||
|
// that is, replace "/.." by "/" at the beginning of a path.
|
||||||
|
//
|
||||||
|
// If the result of this process is an empty string, "/" is returned.
|
||||||
|
func cleanPath(p string) string {
|
||||||
|
const stackBufSize = 128
|
||||||
|
// Turn empty string into "/"
|
||||||
|
if p == "" {
|
||||||
|
return "/"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reasonably sized buffer on stack to avoid allocations in the common case.
|
||||||
|
// If a larger buffer is required, it gets allocated dynamically.
|
||||||
|
buf := make([]byte, 0, stackBufSize)
|
||||||
|
|
||||||
|
n := len(p)
|
||||||
|
|
||||||
|
// Invariants:
|
||||||
|
// reading from path; r is index of next byte to process.
|
||||||
|
// writing to buf; w is index of next byte to write.
|
||||||
|
|
||||||
|
// path must start with '/'
|
||||||
|
r := 1
|
||||||
|
w := 1
|
||||||
|
|
||||||
|
if p[0] != '/' {
|
||||||
|
r = 0
|
||||||
|
|
||||||
|
if n+1 > stackBufSize {
|
||||||
|
buf = make([]byte, n+1)
|
||||||
|
} else {
|
||||||
|
buf = buf[:n+1]
|
||||||
|
}
|
||||||
|
buf[0] = '/'
|
||||||
|
}
|
||||||
|
|
||||||
|
trailing := n > 1 && p[n-1] == '/'
|
||||||
|
|
||||||
|
// A bit more clunky without a 'lazybuf' like the path package, but the loop
|
||||||
|
// gets completely inlined (bufApp calls).
|
||||||
|
// loop has no expensive function calls (except 1x make) // So in contrast to the path package this loop has no expensive function
|
||||||
|
// calls (except make, if needed).
|
||||||
|
|
||||||
|
for r < n {
|
||||||
|
switch {
|
||||||
|
case p[r] == '/':
|
||||||
|
// empty path element, trailing slash is added after the end
|
||||||
|
r++
|
||||||
|
|
||||||
|
case p[r] == '.' && r+1 == n:
|
||||||
|
trailing = true
|
||||||
|
r++
|
||||||
|
|
||||||
|
case p[r] == '.' && p[r+1] == '/':
|
||||||
|
// . element
|
||||||
|
r += 2
|
||||||
|
|
||||||
|
case p[r] == '.' && p[r+1] == '.' && (r+2 == n || p[r+2] == '/'):
|
||||||
|
// .. element: remove to last /
|
||||||
|
r += 3
|
||||||
|
|
||||||
|
if w > 1 {
|
||||||
|
// can backtrack
|
||||||
|
w--
|
||||||
|
|
||||||
|
if len(buf) == 0 {
|
||||||
|
for w > 1 && p[w] != '/' {
|
||||||
|
w--
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for w > 1 && buf[w] != '/' {
|
||||||
|
w--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Real path element.
|
||||||
|
// Add slash if needed
|
||||||
|
if w > 1 {
|
||||||
|
bufApp(&buf, p, w, '/')
|
||||||
|
w++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy element
|
||||||
|
for r < n && p[r] != '/' {
|
||||||
|
bufApp(&buf, p, w, p[r])
|
||||||
|
w++
|
||||||
|
r++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-append trailing slash
|
||||||
|
if trailing && w > 1 {
|
||||||
|
bufApp(&buf, p, w, '/')
|
||||||
|
w++
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the original string was not modified (or only shortened at the end),
|
||||||
|
// return the respective substring of the original string.
|
||||||
|
// Otherwise return a new string from the buffer.
|
||||||
|
if len(buf) == 0 {
|
||||||
|
return p[:w]
|
||||||
|
}
|
||||||
|
return string(buf[:w])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal helper to lazily create a buffer if necessary.
|
||||||
|
// Calls to this function get inlined.
|
||||||
|
func bufApp(buf *[]byte, s string, w int, c byte) {
|
||||||
|
b := *buf
|
||||||
|
if len(b) == 0 {
|
||||||
|
// No modification of the original string so far.
|
||||||
|
// If the next character is the same as in the original string, we do
|
||||||
|
// not yet have to allocate a buffer.
|
||||||
|
if s[w] == c {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise use either the stack buffer, if it is large enough, or
|
||||||
|
// allocate a new buffer on the heap, and copy all previous characters.
|
||||||
|
length := len(s)
|
||||||
|
if length > cap(b) {
|
||||||
|
*buf = make([]byte, length)
|
||||||
|
} else {
|
||||||
|
*buf = (*buf)[:length]
|
||||||
|
}
|
||||||
|
b = *buf
|
||||||
|
|
||||||
|
copy(b, s[:w])
|
||||||
|
}
|
||||||
|
b[w] = c
|
||||||
|
}
|
173
vendor/github.com/gin-gonic/gin/recovery.go
generated
vendored
Normal file
173
vendor/github.com/gin-gonic/gin/recovery.go
generated
vendored
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package gin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httputil"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
dunno = []byte("???")
|
||||||
|
centerDot = []byte("·")
|
||||||
|
dot = []byte(".")
|
||||||
|
slash = []byte("/")
|
||||||
|
)
|
||||||
|
|
||||||
|
// RecoveryFunc defines the function passable to CustomRecovery.
|
||||||
|
type RecoveryFunc func(c *Context, err any)
|
||||||
|
|
||||||
|
// Recovery returns a middleware that recovers from any panics and writes a 500 if there was one.
|
||||||
|
func Recovery() HandlerFunc {
|
||||||
|
return RecoveryWithWriter(DefaultErrorWriter)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CustomRecovery returns a middleware that recovers from any panics and calls the provided handle func to handle it.
|
||||||
|
func CustomRecovery(handle RecoveryFunc) HandlerFunc {
|
||||||
|
return RecoveryWithWriter(DefaultErrorWriter, handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RecoveryWithWriter returns a middleware for a given writer that recovers from any panics and writes a 500 if there was one.
|
||||||
|
func RecoveryWithWriter(out io.Writer, recovery ...RecoveryFunc) HandlerFunc {
|
||||||
|
if len(recovery) > 0 {
|
||||||
|
return CustomRecoveryWithWriter(out, recovery[0])
|
||||||
|
}
|
||||||
|
return CustomRecoveryWithWriter(out, defaultHandleRecovery)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CustomRecoveryWithWriter returns a middleware for a given writer that recovers from any panics and calls the provided handle func to handle it.
|
||||||
|
func CustomRecoveryWithWriter(out io.Writer, handle RecoveryFunc) HandlerFunc {
|
||||||
|
var logger *log.Logger
|
||||||
|
if out != nil {
|
||||||
|
logger = log.New(out, "\n\n\x1b[31m", log.LstdFlags)
|
||||||
|
}
|
||||||
|
return func(c *Context) {
|
||||||
|
defer func() {
|
||||||
|
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 {
|
||||||
|
var se *os.SyscallError
|
||||||
|
if errors.As(ne, &se) {
|
||||||
|
if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") {
|
||||||
|
brokenPipe = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if logger != nil {
|
||||||
|
stack := stack(3)
|
||||||
|
httpRequest, _ := httputil.DumpRequest(c.Request, false)
|
||||||
|
headers := strings.Split(string(httpRequest), "\r\n")
|
||||||
|
for idx, header := range headers {
|
||||||
|
current := strings.Split(header, ":")
|
||||||
|
if current[0] == "Authorization" {
|
||||||
|
headers[idx] = current[0] + ": *"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
headersToStr := strings.Join(headers, "\r\n")
|
||||||
|
if brokenPipe {
|
||||||
|
logger.Printf("%s\n%s%s", err, headersToStr, reset)
|
||||||
|
} else if IsDebugging() {
|
||||||
|
logger.Printf("[Recovery] %s panic recovered:\n%s\n%s\n%s%s",
|
||||||
|
timeFormat(time.Now()), headersToStr, err, stack, reset)
|
||||||
|
} else {
|
||||||
|
logger.Printf("[Recovery] %s panic recovered:\n%s\n%s%s",
|
||||||
|
timeFormat(time.Now()), err, stack, reset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if brokenPipe {
|
||||||
|
// If the connection is dead, we can't write a status to it.
|
||||||
|
c.Error(err.(error)) // nolint: errcheck
|
||||||
|
c.Abort()
|
||||||
|
} else {
|
||||||
|
handle(c, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultHandleRecovery(c *Context, err any) {
|
||||||
|
c.AbortWithStatus(http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
// stack returns a nicely formatted stack frame, skipping skip frames.
|
||||||
|
func stack(skip int) []byte {
|
||||||
|
buf := new(bytes.Buffer) // the returned data
|
||||||
|
// As we loop, we open files and read them. These variables record the currently
|
||||||
|
// loaded file.
|
||||||
|
var lines [][]byte
|
||||||
|
var lastFile string
|
||||||
|
for i := skip; ; i++ { // Skip the expected number of frames
|
||||||
|
pc, file, line, ok := runtime.Caller(i)
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// Print this much at least. If we can't find the source, it won't show.
|
||||||
|
fmt.Fprintf(buf, "%s:%d (0x%x)\n", file, line, pc)
|
||||||
|
if file != lastFile {
|
||||||
|
data, err := ioutil.ReadFile(file)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
lines = bytes.Split(data, []byte{'\n'})
|
||||||
|
lastFile = file
|
||||||
|
}
|
||||||
|
fmt.Fprintf(buf, "\t%s: %s\n", function(pc), source(lines, line))
|
||||||
|
}
|
||||||
|
return buf.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
// source returns a space-trimmed slice of the n'th line.
|
||||||
|
func source(lines [][]byte, n int) []byte {
|
||||||
|
n-- // in stack trace, lines are 1-indexed but our array is 0-indexed
|
||||||
|
if n < 0 || n >= len(lines) {
|
||||||
|
return dunno
|
||||||
|
}
|
||||||
|
return bytes.TrimSpace(lines[n])
|
||||||
|
}
|
||||||
|
|
||||||
|
// function returns, if possible, the name of the function containing the PC.
|
||||||
|
func function(pc uintptr) []byte {
|
||||||
|
fn := runtime.FuncForPC(pc)
|
||||||
|
if fn == nil {
|
||||||
|
return dunno
|
||||||
|
}
|
||||||
|
name := []byte(fn.Name())
|
||||||
|
// The name includes the path name to the package, which is unnecessary
|
||||||
|
// since the file name is already included. Plus, it has center dots.
|
||||||
|
// That is, we see
|
||||||
|
// runtime/debug.*T·ptrmethod
|
||||||
|
// and want
|
||||||
|
// *T.ptrmethod
|
||||||
|
// Also the package path might contain dot (e.g. code.google.com/...),
|
||||||
|
// so first eliminate the path prefix
|
||||||
|
if lastSlash := bytes.LastIndex(name, slash); lastSlash >= 0 {
|
||||||
|
name = name[lastSlash+1:]
|
||||||
|
}
|
||||||
|
if period := bytes.Index(name, dot); period >= 0 {
|
||||||
|
name = name[period+1:]
|
||||||
|
}
|
||||||
|
name = bytes.Replace(name, centerDot, dot, -1)
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
// timeFormat returns a customized time string for logger.
|
||||||
|
func timeFormat(t time.Time) string {
|
||||||
|
return t.Format("2006/01/02 - 15:04:05")
|
||||||
|
}
|
10
vendor/github.com/gin-gonic/gin/render/any.go
generated
vendored
Normal file
10
vendor/github.com/gin-gonic/gin/render/any.go
generated
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
// Copyright 2021 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.
|
||||||
|
|
||||||
|
//go:build !go1.18
|
||||||
|
// +build !go1.18
|
||||||
|
|
||||||
|
package render
|
||||||
|
|
||||||
|
type any = interface{}
|
25
vendor/github.com/gin-gonic/gin/render/data.go
generated
vendored
Normal file
25
vendor/github.com/gin-gonic/gin/render/data.go
generated
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
// Copyright 2014 Manu Martinez-Almeida. 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
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
// Data contains ContentType and bytes data.
|
||||||
|
type Data struct {
|
||||||
|
ContentType string
|
||||||
|
Data []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render (Data) writes data with custom ContentType.
|
||||||
|
func (r Data) Render(w http.ResponseWriter) (err error) {
|
||||||
|
r.WriteContentType(w)
|
||||||
|
_, err = w.Write(r.Data)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteContentType (Data) writes custom ContentType.
|
||||||
|
func (r Data) WriteContentType(w http.ResponseWriter) {
|
||||||
|
writeContentType(w, []string{r.ContentType})
|
||||||
|
}
|
92
vendor/github.com/gin-gonic/gin/render/html.go
generated
vendored
Normal file
92
vendor/github.com/gin-gonic/gin/render/html.go
generated
vendored
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
// Copyright 2014 Manu Martinez-Almeida. 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
|
||||||
|
|
||||||
|
import (
|
||||||
|
"html/template"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Delims represents a set of Left and Right delimiters for HTML template rendering.
|
||||||
|
type Delims struct {
|
||||||
|
// Left delimiter, defaults to {{.
|
||||||
|
Left string
|
||||||
|
// Right delimiter, defaults to }}.
|
||||||
|
Right string
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTMLRender interface is to be implemented by HTMLProduction and HTMLDebug.
|
||||||
|
type HTMLRender interface {
|
||||||
|
// Instance returns an HTML instance.
|
||||||
|
Instance(string, any) Render
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTMLProduction contains template reference and its delims.
|
||||||
|
type HTMLProduction struct {
|
||||||
|
Template *template.Template
|
||||||
|
Delims Delims
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTMLDebug contains template delims and pattern and function with file list.
|
||||||
|
type HTMLDebug struct {
|
||||||
|
Files []string
|
||||||
|
Glob string
|
||||||
|
Delims Delims
|
||||||
|
FuncMap template.FuncMap
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTML contains template reference and its name with given interface object.
|
||||||
|
type HTML struct {
|
||||||
|
Template *template.Template
|
||||||
|
Name string
|
||||||
|
Data any
|
||||||
|
}
|
||||||
|
|
||||||
|
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 any) Render {
|
||||||
|
return HTML{
|
||||||
|
Template: r.Template,
|
||||||
|
Name: name,
|
||||||
|
Data: data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Instance (HTMLDebug) returns an HTML instance which it realizes Render interface.
|
||||||
|
func (r HTMLDebug) Instance(name string, data any) Render {
|
||||||
|
return HTML{
|
||||||
|
Template: r.loadTemplate(),
|
||||||
|
Name: name,
|
||||||
|
Data: data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (r HTMLDebug) loadTemplate() *template.Template {
|
||||||
|
if r.FuncMap == nil {
|
||||||
|
r.FuncMap = template.FuncMap{}
|
||||||
|
}
|
||||||
|
if len(r.Files) > 0 {
|
||||||
|
return template.Must(template.New("").Delims(r.Delims.Left, r.Delims.Right).Funcs(r.FuncMap).ParseFiles(r.Files...))
|
||||||
|
}
|
||||||
|
if r.Glob != "" {
|
||||||
|
return template.Must(template.New("").Delims(r.Delims.Left, r.Delims.Right).Funcs(r.FuncMap).ParseGlob(r.Glob))
|
||||||
|
}
|
||||||
|
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 {
|
||||||
|
r.WriteContentType(w)
|
||||||
|
|
||||||
|
if r.Name == "" {
|
||||||
|
return r.Template.Execute(w, r.Data)
|
||||||
|
}
|
||||||
|
return r.Template.ExecuteTemplate(w, r.Name, r.Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteContentType (HTML) writes HTML ContentType.
|
||||||
|
func (r HTML) WriteContentType(w http.ResponseWriter) {
|
||||||
|
writeContentType(w, htmlContentType)
|
||||||
|
}
|
193
vendor/github.com/gin-gonic/gin/render/json.go
generated
vendored
Normal file
193
vendor/github.com/gin-gonic/gin/render/json.go
generated
vendored
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
// Copyright 2014 Manu Martinez-Almeida. 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
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin/internal/bytesconv"
|
||||||
|
"github.com/gin-gonic/gin/internal/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
// JSON contains the given interface object.
|
||||||
|
type JSON struct {
|
||||||
|
Data any
|
||||||
|
}
|
||||||
|
|
||||||
|
// IndentedJSON contains the given interface object.
|
||||||
|
type IndentedJSON struct {
|
||||||
|
Data any
|
||||||
|
}
|
||||||
|
|
||||||
|
// SecureJSON contains the given interface object and its prefix.
|
||||||
|
type SecureJSON struct {
|
||||||
|
Prefix string
|
||||||
|
Data any
|
||||||
|
}
|
||||||
|
|
||||||
|
// JsonpJSON contains the given interface object its callback.
|
||||||
|
type JsonpJSON struct {
|
||||||
|
Callback string
|
||||||
|
Data any
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsciiJSON contains the given interface object.
|
||||||
|
type AsciiJSON struct {
|
||||||
|
Data any
|
||||||
|
}
|
||||||
|
|
||||||
|
// PureJSON contains the given interface object.
|
||||||
|
type PureJSON struct {
|
||||||
|
Data any
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
jsonContentType = []string{"application/json; charset=utf-8"}
|
||||||
|
jsonpContentType = []string{"application/javascript; charset=utf-8"}
|
||||||
|
jsonASCIIContentType = []string{"application/json"}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Render (JSON) writes data with custom ContentType.
|
||||||
|
func (r JSON) Render(w http.ResponseWriter) (err error) {
|
||||||
|
if err = WriteJSON(w, r.Data); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteContentType (JSON) writes JSON ContentType.
|
||||||
|
func (r JSON) WriteContentType(w http.ResponseWriter) {
|
||||||
|
writeContentType(w, jsonContentType)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteJSON marshals the given interface object and writes it with custom ContentType.
|
||||||
|
func WriteJSON(w http.ResponseWriter, obj any) error {
|
||||||
|
writeContentType(w, jsonContentType)
|
||||||
|
jsonBytes, err := json.Marshal(obj)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = w.Write(jsonBytes)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render (IndentedJSON) marshals the given interface object and writes it with custom ContentType.
|
||||||
|
func (r IndentedJSON) Render(w http.ResponseWriter) error {
|
||||||
|
r.WriteContentType(w)
|
||||||
|
jsonBytes, err := json.MarshalIndent(r.Data, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = w.Write(jsonBytes)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteContentType (IndentedJSON) writes JSON ContentType.
|
||||||
|
func (r IndentedJSON) WriteContentType(w http.ResponseWriter) {
|
||||||
|
writeContentType(w, jsonContentType)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render (SecureJSON) marshals the given interface object and writes it with custom ContentType.
|
||||||
|
func (r SecureJSON) Render(w http.ResponseWriter) error {
|
||||||
|
r.WriteContentType(w)
|
||||||
|
jsonBytes, err := json.Marshal(r.Data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// if the jsonBytes is array values
|
||||||
|
if bytes.HasPrefix(jsonBytes, bytesconv.StringToBytes("[")) && bytes.HasSuffix(jsonBytes,
|
||||||
|
bytesconv.StringToBytes("]")) {
|
||||||
|
if _, err = w.Write(bytesconv.StringToBytes(r.Prefix)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, err = w.Write(jsonBytes)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteContentType (SecureJSON) writes JSON ContentType.
|
||||||
|
func (r SecureJSON) WriteContentType(w http.ResponseWriter) {
|
||||||
|
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) {
|
||||||
|
r.WriteContentType(w)
|
||||||
|
ret, err := json.Marshal(r.Data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Callback == "" {
|
||||||
|
_, err = w.Write(ret)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
callback := template.JSEscapeString(r.Callback)
|
||||||
|
if _, err = w.Write(bytesconv.StringToBytes(callback)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = w.Write(bytesconv.StringToBytes("(")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = w.Write(ret); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = w.Write(bytesconv.StringToBytes(");")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteContentType (JsonpJSON) writes Javascript ContentType.
|
||||||
|
func (r JsonpJSON) WriteContentType(w http.ResponseWriter) {
|
||||||
|
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) {
|
||||||
|
r.WriteContentType(w)
|
||||||
|
ret, err := json.Marshal(r.Data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
for _, r := range bytesconv.BytesToString(ret) {
|
||||||
|
cvt := string(r)
|
||||||
|
if r >= 128 {
|
||||||
|
cvt = fmt.Sprintf("\\u%04x", int64(r))
|
||||||
|
}
|
||||||
|
buffer.WriteString(cvt)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = w.Write(buffer.Bytes())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteContentType (AsciiJSON) writes JSON ContentType.
|
||||||
|
func (r AsciiJSON) WriteContentType(w http.ResponseWriter) {
|
||||||
|
writeContentType(w, jsonASCIIContentType)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render (PureJSON) writes custom ContentType and encodes the given interface object.
|
||||||
|
func (r PureJSON) Render(w http.ResponseWriter) error {
|
||||||
|
r.WriteContentType(w)
|
||||||
|
encoder := json.NewEncoder(w)
|
||||||
|
encoder.SetEscapeHTML(false)
|
||||||
|
return encoder.Encode(r.Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteContentType (PureJSON) writes custom ContentType.
|
||||||
|
func (r PureJSON) WriteContentType(w http.ResponseWriter) {
|
||||||
|
writeContentType(w, jsonContentType)
|
||||||
|
}
|
44
vendor/github.com/gin-gonic/gin/render/msgpack.go
generated
vendored
Normal file
44
vendor/github.com/gin-gonic/gin/render/msgpack.go
generated
vendored
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
// Copyright 2017 Manu Martinez-Almeida. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build !nomsgpack
|
||||||
|
// +build !nomsgpack
|
||||||
|
|
||||||
|
package render
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/ugorji/go/codec"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Check interface implemented here to support go build tag nomsgpack.
|
||||||
|
// See: https://github.com/gin-gonic/gin/pull/1852/
|
||||||
|
var (
|
||||||
|
_ Render = MsgPack{}
|
||||||
|
)
|
||||||
|
|
||||||
|
// MsgPack contains the given interface object.
|
||||||
|
type MsgPack struct {
|
||||||
|
Data any
|
||||||
|
}
|
||||||
|
|
||||||
|
var msgpackContentType = []string{"application/msgpack; charset=utf-8"}
|
||||||
|
|
||||||
|
// WriteContentType (MsgPack) writes MsgPack ContentType.
|
||||||
|
func (r MsgPack) WriteContentType(w http.ResponseWriter) {
|
||||||
|
writeContentType(w, msgpackContentType)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render (MsgPack) encodes the given interface object and writes data with custom ContentType.
|
||||||
|
func (r MsgPack) Render(w http.ResponseWriter) error {
|
||||||
|
return WriteMsgPack(w, r.Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteMsgPack writes MsgPack ContentType and encodes the given interface object.
|
||||||
|
func WriteMsgPack(w http.ResponseWriter, obj any) error {
|
||||||
|
writeContentType(w, msgpackContentType)
|
||||||
|
var mh codec.MsgpackHandle
|
||||||
|
return codec.NewEncoder(w, &mh).Encode(obj)
|
||||||
|
}
|
36
vendor/github.com/gin-gonic/gin/render/protobuf.go
generated
vendored
Normal file
36
vendor/github.com/gin-gonic/gin/render/protobuf.go
generated
vendored
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
// 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
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"google.golang.org/protobuf/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProtoBuf contains the given interface object.
|
||||||
|
type ProtoBuf struct {
|
||||||
|
Data any
|
||||||
|
}
|
||||||
|
|
||||||
|
var protobufContentType = []string{"application/x-protobuf"}
|
||||||
|
|
||||||
|
// Render (ProtoBuf) marshals the given interface object and writes data with custom ContentType.
|
||||||
|
func (r ProtoBuf) Render(w http.ResponseWriter) error {
|
||||||
|
r.WriteContentType(w)
|
||||||
|
|
||||||
|
bytes, err := proto.Marshal(r.Data.(proto.Message))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = w.Write(bytes)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteContentType (ProtoBuf) writes ProtoBuf ContentType.
|
||||||
|
func (r ProtoBuf) WriteContentType(w http.ResponseWriter) {
|
||||||
|
writeContentType(w, protobufContentType)
|
||||||
|
}
|
48
vendor/github.com/gin-gonic/gin/render/reader.go
generated
vendored
Normal file
48
vendor/github.com/gin-gonic/gin/render/reader.go
generated
vendored
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
// 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
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Reader contains the IO reader and its length, and custom ContentType and other headers.
|
||||||
|
type Reader struct {
|
||||||
|
ContentType string
|
||||||
|
ContentLength int64
|
||||||
|
Reader io.Reader
|
||||||
|
Headers map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render (Reader) writes data with custom ContentType and headers.
|
||||||
|
func (r Reader) Render(w http.ResponseWriter) (err error) {
|
||||||
|
r.WriteContentType(w)
|
||||||
|
if r.ContentLength >= 0 {
|
||||||
|
if r.Headers == nil {
|
||||||
|
r.Headers = map[string]string{}
|
||||||
|
}
|
||||||
|
r.Headers["Content-Length"] = strconv.FormatInt(r.ContentLength, 10)
|
||||||
|
}
|
||||||
|
r.writeHeaders(w, r.Headers)
|
||||||
|
_, err = io.Copy(w, r.Reader)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteContentType (Reader) writes custom ContentType.
|
||||||
|
func (r Reader) WriteContentType(w http.ResponseWriter) {
|
||||||
|
writeContentType(w, []string{r.ContentType})
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeHeaders writes custom Header.
|
||||||
|
func (r Reader) writeHeaders(w http.ResponseWriter, headers map[string]string) {
|
||||||
|
header := w.Header()
|
||||||
|
for k, v := range headers {
|
||||||
|
if header.Get(k) == "" {
|
||||||
|
header.Set(k, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
29
vendor/github.com/gin-gonic/gin/render/redirect.go
generated
vendored
Normal file
29
vendor/github.com/gin-gonic/gin/render/redirect.go
generated
vendored
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
// Copyright 2014 Manu Martinez-Almeida. 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
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Redirect contains the http request reference and redirects status code and location.
|
||||||
|
type Redirect struct {
|
||||||
|
Code int
|
||||||
|
Request *http.Request
|
||||||
|
Location string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render (Redirect) redirects the http request to new location and writes redirect response.
|
||||||
|
func (r Redirect) Render(w http.ResponseWriter) error {
|
||||||
|
if (r.Code < http.StatusMultipleChoices || r.Code > http.StatusPermanentRedirect) && r.Code != http.StatusCreated {
|
||||||
|
panic(fmt.Sprintf("Cannot redirect with status code %d", r.Code))
|
||||||
|
}
|
||||||
|
http.Redirect(w, r.Request, r.Location, r.Code)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteContentType (Redirect) don't write any ContentType.
|
||||||
|
func (r Redirect) WriteContentType(http.ResponseWriter) {}
|
41
vendor/github.com/gin-gonic/gin/render/render.go
generated
vendored
Normal file
41
vendor/github.com/gin-gonic/gin/render/render.go
generated
vendored
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
// Copyright 2014 Manu Martinez-Almeida. 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
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
// Render interface is to be implemented by JSON, XML, HTML, YAML and so on.
|
||||||
|
type Render interface {
|
||||||
|
// Render writes data with custom ContentType.
|
||||||
|
Render(http.ResponseWriter) error
|
||||||
|
// WriteContentType writes custom ContentType.
|
||||||
|
WriteContentType(w http.ResponseWriter)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ Render = JSON{}
|
||||||
|
_ Render = IndentedJSON{}
|
||||||
|
_ Render = SecureJSON{}
|
||||||
|
_ Render = JsonpJSON{}
|
||||||
|
_ Render = XML{}
|
||||||
|
_ Render = String{}
|
||||||
|
_ Render = Redirect{}
|
||||||
|
_ Render = Data{}
|
||||||
|
_ Render = HTML{}
|
||||||
|
_ HTMLRender = HTMLDebug{}
|
||||||
|
_ HTMLRender = HTMLProduction{}
|
||||||
|
_ Render = YAML{}
|
||||||
|
_ Render = Reader{}
|
||||||
|
_ Render = AsciiJSON{}
|
||||||
|
_ Render = ProtoBuf{}
|
||||||
|
_ Render = TOML{}
|
||||||
|
)
|
||||||
|
|
||||||
|
func writeContentType(w http.ResponseWriter, value []string) {
|
||||||
|
header := w.Header()
|
||||||
|
if val := header["Content-Type"]; len(val) == 0 {
|
||||||
|
header["Content-Type"] = value
|
||||||
|
}
|
||||||
|
}
|
41
vendor/github.com/gin-gonic/gin/render/text.go
generated
vendored
Normal file
41
vendor/github.com/gin-gonic/gin/render/text.go
generated
vendored
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
// Copyright 2014 Manu Martinez-Almeida. 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
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin/internal/bytesconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// String contains the given interface object slice and its format.
|
||||||
|
type String struct {
|
||||||
|
Format string
|
||||||
|
Data []any
|
||||||
|
}
|
||||||
|
|
||||||
|
var plainContentType = []string{"text/plain; charset=utf-8"}
|
||||||
|
|
||||||
|
// Render (String) writes data with custom ContentType.
|
||||||
|
func (r String) Render(w http.ResponseWriter) error {
|
||||||
|
return WriteString(w, r.Format, r.Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteContentType (String) writes Plain ContentType.
|
||||||
|
func (r String) WriteContentType(w http.ResponseWriter) {
|
||||||
|
writeContentType(w, plainContentType)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteString writes data according to its format and write custom ContentType.
|
||||||
|
func WriteString(w http.ResponseWriter, format string, data []any) (err error) {
|
||||||
|
writeContentType(w, plainContentType)
|
||||||
|
if len(data) > 0 {
|
||||||
|
_, err = fmt.Fprintf(w, format, data...)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = w.Write(bytesconv.StringToBytes(format))
|
||||||
|
return
|
||||||
|
}
|
36
vendor/github.com/gin-gonic/gin/render/toml.go
generated
vendored
Normal file
36
vendor/github.com/gin-gonic/gin/render/toml.go
generated
vendored
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
// Copyright 2022 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
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/pelletier/go-toml/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TOML contains the given interface object.
|
||||||
|
type TOML struct {
|
||||||
|
Data any
|
||||||
|
}
|
||||||
|
|
||||||
|
var TOMLContentType = []string{"application/toml; charset=utf-8"}
|
||||||
|
|
||||||
|
// Render (TOML) marshals the given interface object and writes data with custom ContentType.
|
||||||
|
func (r TOML) Render(w http.ResponseWriter) error {
|
||||||
|
r.WriteContentType(w)
|
||||||
|
|
||||||
|
bytes, err := toml.Marshal(r.Data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = w.Write(bytes)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteContentType (TOML) writes TOML ContentType for response.
|
||||||
|
func (r TOML) WriteContentType(w http.ResponseWriter) {
|
||||||
|
writeContentType(w, TOMLContentType)
|
||||||
|
}
|
28
vendor/github.com/gin-gonic/gin/render/xml.go
generated
vendored
Normal file
28
vendor/github.com/gin-gonic/gin/render/xml.go
generated
vendored
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
// Copyright 2014 Manu Martinez-Almeida. 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
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// XML contains the given interface object.
|
||||||
|
type XML struct {
|
||||||
|
Data any
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
r.WriteContentType(w)
|
||||||
|
return xml.NewEncoder(w).Encode(r.Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteContentType (XML) writes XML ContentType for response.
|
||||||
|
func (r XML) WriteContentType(w http.ResponseWriter) {
|
||||||
|
writeContentType(w, xmlContentType)
|
||||||
|
}
|
36
vendor/github.com/gin-gonic/gin/render/yaml.go
generated
vendored
Normal file
36
vendor/github.com/gin-gonic/gin/render/yaml.go
generated
vendored
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
// Copyright 2014 Manu Martinez-Almeida. 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
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// YAML contains the given interface object.
|
||||||
|
type YAML struct {
|
||||||
|
Data any
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
r.WriteContentType(w)
|
||||||
|
|
||||||
|
bytes, err := yaml.Marshal(r.Data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = w.Write(bytes)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteContentType (YAML) writes YAML ContentType for response.
|
||||||
|
func (r YAML) WriteContentType(w http.ResponseWriter) {
|
||||||
|
writeContentType(w, yamlContentType)
|
||||||
|
}
|
126
vendor/github.com/gin-gonic/gin/response_writer.go
generated
vendored
Normal file
126
vendor/github.com/gin-gonic/gin/response_writer.go
generated
vendored
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package gin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
noWritten = -1
|
||||||
|
defaultStatus = http.StatusOK
|
||||||
|
)
|
||||||
|
|
||||||
|
// ResponseWriter ...
|
||||||
|
type ResponseWriter interface {
|
||||||
|
http.ResponseWriter
|
||||||
|
http.Hijacker
|
||||||
|
http.Flusher
|
||||||
|
http.CloseNotifier
|
||||||
|
|
||||||
|
// Status returns the HTTP response status code of the current request.
|
||||||
|
Status() int
|
||||||
|
|
||||||
|
// Size returns the number of bytes already written into the response http body.
|
||||||
|
// See Written()
|
||||||
|
Size() int
|
||||||
|
|
||||||
|
// WriteString writes the string into the response body.
|
||||||
|
WriteString(string) (int, error)
|
||||||
|
|
||||||
|
// Written returns true if the response body was already written.
|
||||||
|
Written() bool
|
||||||
|
|
||||||
|
// WriteHeaderNow forces to write the http header (status code + headers).
|
||||||
|
WriteHeaderNow()
|
||||||
|
|
||||||
|
// Pusher get the http.Pusher for server push
|
||||||
|
Pusher() http.Pusher
|
||||||
|
}
|
||||||
|
|
||||||
|
type responseWriter struct {
|
||||||
|
http.ResponseWriter
|
||||||
|
size int
|
||||||
|
status int
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ ResponseWriter = &responseWriter{}
|
||||||
|
|
||||||
|
func (w *responseWriter) reset(writer http.ResponseWriter) {
|
||||||
|
w.ResponseWriter = writer
|
||||||
|
w.size = noWritten
|
||||||
|
w.status = defaultStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *responseWriter) WriteHeader(code int) {
|
||||||
|
if code > 0 && w.status != code {
|
||||||
|
if w.Written() {
|
||||||
|
debugPrint("[WARNING] Headers were already written. Wanted to override status code %d with %d", w.status, code)
|
||||||
|
}
|
||||||
|
w.status = code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *responseWriter) WriteHeaderNow() {
|
||||||
|
if !w.Written() {
|
||||||
|
w.size = 0
|
||||||
|
w.ResponseWriter.WriteHeader(w.status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *responseWriter) Write(data []byte) (n int, err error) {
|
||||||
|
w.WriteHeaderNow()
|
||||||
|
n, err = w.ResponseWriter.Write(data)
|
||||||
|
w.size += n
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *responseWriter) WriteString(s string) (n int, err error) {
|
||||||
|
w.WriteHeaderNow()
|
||||||
|
n, err = io.WriteString(w.ResponseWriter, s)
|
||||||
|
w.size += n
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *responseWriter) Status() int {
|
||||||
|
return w.status
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *responseWriter) Size() int {
|
||||||
|
return w.size
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *responseWriter) Written() bool {
|
||||||
|
return w.size != noWritten
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hijack implements the http.Hijacker interface.
|
||||||
|
func (w *responseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||||
|
if w.size < 0 {
|
||||||
|
w.size = 0
|
||||||
|
}
|
||||||
|
return w.ResponseWriter.(http.Hijacker).Hijack()
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloseNotify implements the http.CloseNotifier interface.
|
||||||
|
func (w *responseWriter) CloseNotify() <-chan bool {
|
||||||
|
return w.ResponseWriter.(http.CloseNotifier).CloseNotify()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush implements the http.Flusher interface.
|
||||||
|
func (w *responseWriter) Flush() {
|
||||||
|
w.WriteHeaderNow()
|
||||||
|
w.ResponseWriter.(http.Flusher).Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *responseWriter) Pusher() (pusher http.Pusher) {
|
||||||
|
if pusher, ok := w.ResponseWriter.(http.Pusher); ok {
|
||||||
|
return pusher
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
248
vendor/github.com/gin-gonic/gin/routergroup.go
generated
vendored
Normal file
248
vendor/github.com/gin-gonic/gin/routergroup.go
generated
vendored
Normal file
@ -0,0 +1,248 @@
|
|||||||
|
// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package gin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"path"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// regEnLetter matches english letters for http method name
|
||||||
|
regEnLetter = regexp.MustCompile("^[A-Z]+$")
|
||||||
|
|
||||||
|
// anyMethods for RouterGroup Any method
|
||||||
|
anyMethods = []string{
|
||||||
|
http.MethodGet, http.MethodPost, http.MethodPut, http.MethodPatch,
|
||||||
|
http.MethodHead, http.MethodOptions, http.MethodDelete, http.MethodConnect,
|
||||||
|
http.MethodTrace,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// IRouter defines all router handle interface includes single and group router.
|
||||||
|
type IRouter interface {
|
||||||
|
IRoutes
|
||||||
|
Group(string, ...HandlerFunc) *RouterGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
// IRoutes defines all router handle interface.
|
||||||
|
type IRoutes interface {
|
||||||
|
Use(...HandlerFunc) IRoutes
|
||||||
|
|
||||||
|
Handle(string, string, ...HandlerFunc) IRoutes
|
||||||
|
Any(string, ...HandlerFunc) IRoutes
|
||||||
|
GET(string, ...HandlerFunc) IRoutes
|
||||||
|
POST(string, ...HandlerFunc) IRoutes
|
||||||
|
DELETE(string, ...HandlerFunc) IRoutes
|
||||||
|
PATCH(string, ...HandlerFunc) IRoutes
|
||||||
|
PUT(string, ...HandlerFunc) IRoutes
|
||||||
|
OPTIONS(string, ...HandlerFunc) IRoutes
|
||||||
|
HEAD(string, ...HandlerFunc) IRoutes
|
||||||
|
|
||||||
|
StaticFile(string, string) IRoutes
|
||||||
|
StaticFileFS(string, string, http.FileSystem) IRoutes
|
||||||
|
Static(string, string) IRoutes
|
||||||
|
StaticFS(string, http.FileSystem) IRoutes
|
||||||
|
}
|
||||||
|
|
||||||
|
// RouterGroup is used internally to configure router, a RouterGroup is associated with
|
||||||
|
// a prefix and an array of handlers (middleware).
|
||||||
|
type RouterGroup struct {
|
||||||
|
Handlers HandlersChain
|
||||||
|
basePath string
|
||||||
|
engine *Engine
|
||||||
|
root bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ IRouter = &RouterGroup{}
|
||||||
|
|
||||||
|
// Use adds middleware to the group, see example code in GitHub.
|
||||||
|
func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {
|
||||||
|
group.Handlers = append(group.Handlers, middleware...)
|
||||||
|
return group.returnObj()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 middleware for authorization could be grouped.
|
||||||
|
func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *RouterGroup {
|
||||||
|
return &RouterGroup{
|
||||||
|
Handlers: group.combineHandlers(handlers),
|
||||||
|
basePath: group.calculateAbsolutePath(relativePath),
|
||||||
|
engine: group.engine,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
return group.basePath
|
||||||
|
}
|
||||||
|
|
||||||
|
func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
|
||||||
|
absolutePath := group.calculateAbsolutePath(relativePath)
|
||||||
|
handlers = group.combineHandlers(handlers)
|
||||||
|
group.engine.addRoute(httpMethod, absolutePath, handlers)
|
||||||
|
return group.returnObj()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle registers a new request handle and middleware with the given path and method.
|
||||||
|
// The last handler should be the real handler, the other ones should be middleware that can and should be shared among different routes.
|
||||||
|
// See the example code in GitHub.
|
||||||
|
//
|
||||||
|
// For GET, POST, PUT, PATCH and DELETE requests the respective shortcut
|
||||||
|
// functions can be used.
|
||||||
|
//
|
||||||
|
// This function is intended for bulk loading and to allow the usage of less
|
||||||
|
// frequently used, non-standardized or custom methods (e.g. for internal
|
||||||
|
// communication with a proxy).
|
||||||
|
func (group *RouterGroup) Handle(httpMethod, relativePath string, handlers ...HandlerFunc) IRoutes {
|
||||||
|
if matched := regEnLetter.MatchString(httpMethod); !matched {
|
||||||
|
panic("http method " + httpMethod + " is not valid")
|
||||||
|
}
|
||||||
|
return group.handle(httpMethod, relativePath, handlers)
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST is a shortcut for router.Handle("POST", path, handle).
|
||||||
|
func (group *RouterGroup) POST(relativePath string, handlers ...HandlerFunc) IRoutes {
|
||||||
|
return group.handle(http.MethodPost, relativePath, handlers)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET is a shortcut for router.Handle("GET", path, handle).
|
||||||
|
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
|
||||||
|
return group.handle(http.MethodGet, relativePath, handlers)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DELETE is a shortcut for router.Handle("DELETE", path, handle).
|
||||||
|
func (group *RouterGroup) DELETE(relativePath string, handlers ...HandlerFunc) IRoutes {
|
||||||
|
return group.handle(http.MethodDelete, relativePath, handlers)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PATCH is a shortcut for router.Handle("PATCH", path, handle).
|
||||||
|
func (group *RouterGroup) PATCH(relativePath string, handlers ...HandlerFunc) IRoutes {
|
||||||
|
return group.handle(http.MethodPatch, relativePath, handlers)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PUT is a shortcut for router.Handle("PUT", path, handle).
|
||||||
|
func (group *RouterGroup) PUT(relativePath string, handlers ...HandlerFunc) IRoutes {
|
||||||
|
return group.handle(http.MethodPut, relativePath, handlers)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OPTIONS is a shortcut for router.Handle("OPTIONS", path, handle).
|
||||||
|
func (group *RouterGroup) OPTIONS(relativePath string, handlers ...HandlerFunc) IRoutes {
|
||||||
|
return group.handle(http.MethodOptions, relativePath, handlers)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HEAD is a shortcut for router.Handle("HEAD", path, handle).
|
||||||
|
func (group *RouterGroup) HEAD(relativePath string, handlers ...HandlerFunc) IRoutes {
|
||||||
|
return group.handle(http.MethodHead, relativePath, handlers)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Any registers a route that matches all the HTTP methods.
|
||||||
|
// GET, POST, PUT, PATCH, HEAD, OPTIONS, DELETE, CONNECT, TRACE.
|
||||||
|
func (group *RouterGroup) Any(relativePath string, handlers ...HandlerFunc) IRoutes {
|
||||||
|
for _, method := range anyMethods {
|
||||||
|
group.handle(method, relativePath, handlers)
|
||||||
|
}
|
||||||
|
|
||||||
|
return group.returnObj()
|
||||||
|
}
|
||||||
|
|
||||||
|
// StaticFile registers a single route in order to serve a single file of the local filesystem.
|
||||||
|
// router.StaticFile("favicon.ico", "./resources/favicon.ico")
|
||||||
|
func (group *RouterGroup) StaticFile(relativePath, filepath string) IRoutes {
|
||||||
|
return group.staticFileHandler(relativePath, func(c *Context) {
|
||||||
|
c.File(filepath)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// StaticFileFS works just like `StaticFile` but a custom `http.FileSystem` can be used instead..
|
||||||
|
// router.StaticFileFS("favicon.ico", "./resources/favicon.ico", Dir{".", false})
|
||||||
|
// Gin by default user: gin.Dir()
|
||||||
|
func (group *RouterGroup) StaticFileFS(relativePath, filepath string, fs http.FileSystem) IRoutes {
|
||||||
|
return group.staticFileHandler(relativePath, func(c *Context) {
|
||||||
|
c.FileFromFS(filepath, fs)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (group *RouterGroup) staticFileHandler(relativePath string, handler HandlerFunc) IRoutes {
|
||||||
|
if strings.Contains(relativePath, ":") || strings.Contains(relativePath, "*") {
|
||||||
|
panic("URL parameters can not be used when serving a static file")
|
||||||
|
}
|
||||||
|
group.GET(relativePath, handler)
|
||||||
|
group.HEAD(relativePath, handler)
|
||||||
|
return group.returnObj()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Static serves files from the given file system root.
|
||||||
|
// Internally a http.FileServer is used, therefore http.NotFound is used instead
|
||||||
|
// of the Router's NotFound handler.
|
||||||
|
// To use the operating system's file system implementation,
|
||||||
|
// use :
|
||||||
|
// router.Static("/static", "/var/www")
|
||||||
|
func (group *RouterGroup) Static(relativePath, root string) IRoutes {
|
||||||
|
return group.StaticFS(relativePath, Dir(root, false))
|
||||||
|
}
|
||||||
|
|
||||||
|
// StaticFS works just like `Static()` but a custom `http.FileSystem` can be used instead.
|
||||||
|
// Gin by default user: gin.Dir()
|
||||||
|
func (group *RouterGroup) StaticFS(relativePath string, fs http.FileSystem) IRoutes {
|
||||||
|
if strings.Contains(relativePath, ":") || strings.Contains(relativePath, "*") {
|
||||||
|
panic("URL parameters can not be used when serving a static folder")
|
||||||
|
}
|
||||||
|
handler := group.createStaticHandler(relativePath, fs)
|
||||||
|
urlPattern := path.Join(relativePath, "/*filepath")
|
||||||
|
|
||||||
|
// Register GET and HEAD handlers
|
||||||
|
group.GET(urlPattern, handler)
|
||||||
|
group.HEAD(urlPattern, handler)
|
||||||
|
return group.returnObj()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (group *RouterGroup) createStaticHandler(relativePath string, fs http.FileSystem) HandlerFunc {
|
||||||
|
absolutePath := group.calculateAbsolutePath(relativePath)
|
||||||
|
fileServer := http.StripPrefix(absolutePath, http.FileServer(fs))
|
||||||
|
|
||||||
|
return func(c *Context) {
|
||||||
|
if _, noListing := fs.(*onlyFilesFS); noListing {
|
||||||
|
c.Writer.WriteHeader(http.StatusNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
file := c.Param("filepath")
|
||||||
|
// Check if file exists and/or if we have permission to access it
|
||||||
|
f, err := fs.Open(file)
|
||||||
|
if err != nil {
|
||||||
|
c.Writer.WriteHeader(http.StatusNotFound)
|
||||||
|
c.handlers = group.engine.noRoute
|
||||||
|
// Reset index
|
||||||
|
c.index = -1
|
||||||
|
return
|
||||||
|
}
|
||||||
|
f.Close()
|
||||||
|
|
||||||
|
fileServer.ServeHTTP(c.Writer, c.Request)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain {
|
||||||
|
finalSize := len(group.Handlers) + len(handlers)
|
||||||
|
assert1(finalSize < int(abortIndex), "too many handlers")
|
||||||
|
mergedHandlers := make(HandlersChain, finalSize)
|
||||||
|
copy(mergedHandlers, group.Handlers)
|
||||||
|
copy(mergedHandlers[len(group.Handlers):], handlers)
|
||||||
|
return mergedHandlers
|
||||||
|
}
|
||||||
|
|
||||||
|
func (group *RouterGroup) calculateAbsolutePath(relativePath string) string {
|
||||||
|
return joinPaths(group.basePath, relativePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (group *RouterGroup) returnObj() IRoutes {
|
||||||
|
if group.root {
|
||||||
|
return group.engine
|
||||||
|
}
|
||||||
|
return group
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user