parent
b28923b086
commit
0eb1f6a62b
@ -0,0 +1,355 @@
|
||||
// go:build go1.18 && !go1.21
|
||||
// +build go1.18,!go1.21
|
||||
|
||||
/*
|
||||
* Copyright 2021 ByteDance Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package loader
|
||||
|
||||
import (
|
||||
`os`
|
||||
`sort`
|
||||
`unsafe`
|
||||
|
||||
`github.com/bytedance/sonic/internal/rt`
|
||||
)
|
||||
|
||||
type funcTab struct {
|
||||
entry uint32
|
||||
funcoff uint32
|
||||
}
|
||||
|
||||
type pcHeader struct {
|
||||
magic uint32 // 0xFFFFFFF0
|
||||
pad1, pad2 uint8 // 0,0
|
||||
minLC uint8 // min instruction size
|
||||
ptrSize uint8 // size of a ptr in bytes
|
||||
nfunc int // number of functions in the module
|
||||
nfiles uint // number of entries in the file tab
|
||||
textStart uintptr // base for function entry PC offsets in this module, equal to moduledata.text
|
||||
funcnameOffset uintptr // offset to the funcnametab variable from pcHeader
|
||||
cuOffset uintptr // offset to the cutab variable from pcHeader
|
||||
filetabOffset uintptr // offset to the filetab variable from pcHeader
|
||||
pctabOffset uintptr // offset to the pctab variable from pcHeader
|
||||
pclnOffset uintptr // offset to the pclntab variable from pcHeader
|
||||
}
|
||||
|
||||
type bitVector struct {
|
||||
n int32 // # of bits
|
||||
bytedata *uint8
|
||||
}
|
||||
|
||||
type ptabEntry struct {
|
||||
name int32
|
||||
typ int32
|
||||
}
|
||||
|
||||
type textSection struct {
|
||||
vaddr uintptr // prelinked section vaddr
|
||||
end uintptr // vaddr + section length
|
||||
baseaddr uintptr // relocated section address
|
||||
}
|
||||
|
||||
type modulehash struct {
|
||||
modulename string
|
||||
linktimehash string
|
||||
runtimehash *string
|
||||
}
|
||||
|
||||
// findfuncbucket is an array of these structures.
|
||||
// Each bucket represents 4096 bytes of the text segment.
|
||||
// Each subbucket represents 256 bytes of the text segment.
|
||||
// To find a function given a pc, locate the bucket and subbucket for
|
||||
// that pc. Add together the idx and subbucket value to obtain a
|
||||
// function index. Then scan the functab array starting at that
|
||||
// index to find the target function.
|
||||
// This table uses 20 bytes for every 4096 bytes of code, or ~0.5% overhead.
|
||||
type findfuncbucket struct {
|
||||
idx uint32
|
||||
_SUBBUCKETS [16]byte
|
||||
}
|
||||
|
||||
type compilationUnit struct {
|
||||
fileNames []string
|
||||
}
|
||||
|
||||
func makeFtab(funcs []_func, maxpc uint32) (ftab []funcTab, pclntabSize int64, startLocations []uint32) {
|
||||
// Allocate space for the pc->func table. This structure consists of a pc offset
|
||||
// and an offset to the func structure. After that, we have a single pc
|
||||
// value that marks the end of the last function in the binary.
|
||||
pclntabSize = int64(len(funcs)*2*int(_PtrSize) + int(_PtrSize))
|
||||
startLocations = make([]uint32, len(funcs))
|
||||
for i, f := range funcs {
|
||||
pclntabSize = rnd(pclntabSize, int64(_PtrSize))
|
||||
//writePCToFunc
|
||||
startLocations[i] = uint32(pclntabSize)
|
||||
pclntabSize += int64(uint8(_FUNC_SIZE)+f.nfuncdata*4+uint8(f.npcdata)*4)
|
||||
}
|
||||
|
||||
ftab = make([]funcTab, 0, len(funcs)+1)
|
||||
|
||||
// write a map of pc->func info offsets
|
||||
for i, f := range funcs {
|
||||
ftab = append(ftab, funcTab{uint32(f.entryOff), uint32(startLocations[i])})
|
||||
}
|
||||
|
||||
// Final entry of table is just end pc offset.
|
||||
ftab = append(ftab, funcTab{maxpc, 0})
|
||||
return
|
||||
}
|
||||
|
||||
// Pcln table format: [...]funcTab + [...]_Func
|
||||
func makePclntable(size int64, startLocations []uint32, funcs []_func, maxpc uint32, pcdataOffs [][]uint32, funcdataOffs [][]uint32) (pclntab []byte) {
|
||||
// Allocate space for the pc->func table. This structure consists of a pc offset
|
||||
// and an offset to the func structure. After that, we have a single pc
|
||||
// value that marks the end of the last function in the binary.
|
||||
pclntab = make([]byte, size, size)
|
||||
|
||||
// write a map of pc->func info offsets
|
||||
offs := 0
|
||||
for i, f := range funcs {
|
||||
byteOrder.PutUint32(pclntab[offs:offs+4], uint32(f.entryOff))
|
||||
byteOrder.PutUint32(pclntab[offs+4:offs+8], uint32(startLocations[i]))
|
||||
offs += 8
|
||||
}
|
||||
// Final entry of table is just end pc offset.
|
||||
byteOrder.PutUint32(pclntab[offs:offs+4], maxpc)
|
||||
|
||||
// write func info table
|
||||
for i := range funcs {
|
||||
off := startLocations[i]
|
||||
|
||||
// write _func structure to pclntab
|
||||
fb := rt.BytesFrom(unsafe.Pointer(&funcs[i]), int(_FUNC_SIZE), int(_FUNC_SIZE))
|
||||
copy(pclntab[off:off+uint32(_FUNC_SIZE)], fb)
|
||||
off += uint32(_FUNC_SIZE)
|
||||
|
||||
// NOTICE: _func.pcdata always starts from PcUnsafePoint, which is index 3
|
||||
for j := 3; j < len(pcdataOffs[i]); j++ {
|
||||
byteOrder.PutUint32(pclntab[off:off+4], uint32(pcdataOffs[i][j]))
|
||||
off += 4
|
||||
}
|
||||
|
||||
// funcdata refs as offsets from gofunc
|
||||
for _, funcdata := range funcdataOffs[i] {
|
||||
byteOrder.PutUint32(pclntab[off:off+4], uint32(funcdata))
|
||||
off += 4
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// findfunc table used to map pc to belonging func,
|
||||
// returns the index in the func table.
|
||||
//
|
||||
// All text section are divided into buckets sized _BUCKETSIZE(4K):
|
||||
// every bucket is divided into _SUBBUCKETS sized _SUB_BUCKETSIZE(64),
|
||||
// and it has a base idx to plus the offset stored in jth subbucket.
|
||||
// see findfunc() in runtime/symtab.go
|
||||
func writeFindfunctab(out *[]byte, ftab []funcTab) (start int) {
|
||||
start = len(*out)
|
||||
|
||||
max := ftab[len(ftab)-1].entry
|
||||
min := ftab[0].entry
|
||||
nbuckets := (max - min + _BUCKETSIZE - 1) / _BUCKETSIZE
|
||||
n := (max - min + _SUB_BUCKETSIZE - 1) / _SUB_BUCKETSIZE
|
||||
|
||||
tab := make([]findfuncbucket, 0, nbuckets)
|
||||
var s, e = 0, 0
|
||||
for i := 0; i<int(nbuckets); i++ {
|
||||
// store the start s-th func of the bucket
|
||||
var fb = findfuncbucket{idx: uint32(s)}
|
||||
|
||||
// find the last e-th func of the bucket
|
||||
var pc = min + uint32((i+1)*_BUCKETSIZE)
|
||||
for ; e < len(ftab)-1 && ftab[e+1].entry <= pc; e++ {}
|
||||
|
||||
for j := 0; j<_SUBBUCKETS && (i*_SUBBUCKETS+j)<int(n); j++ {
|
||||
// store the start func of the subbucket
|
||||
fb._SUBBUCKETS[j] = byte(uint32(s) - fb.idx)
|
||||
|
||||
// find the s-th end func of the subbucket
|
||||
pc = min + uint32(i*_BUCKETSIZE) + uint32((j+1)*_SUB_BUCKETSIZE)
|
||||
for ; s < len(ftab)-1 && ftab[s+1].entry <= pc; s++ {}
|
||||
}
|
||||
|
||||
s = e
|
||||
tab = append(tab, fb)
|
||||
}
|
||||
|
||||
// write findfuncbucket
|
||||
if len(tab) > 0 {
|
||||
size := int(unsafe.Sizeof(findfuncbucket{}))*len(tab)
|
||||
*out = append(*out, rt.BytesFrom(unsafe.Pointer(&tab[0]), size, size)...)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func makeModuledata(name string, filenames []string, funcsp *[]Func, text []byte) (mod *moduledata) {
|
||||
mod = new(moduledata)
|
||||
mod.modulename = name
|
||||
|
||||
// sort funcs by entry
|
||||
funcs := *funcsp
|
||||
sort.Slice(funcs, func(i, j int) bool {
|
||||
return funcs[i].EntryOff < funcs[j].EntryOff
|
||||
})
|
||||
*funcsp = funcs
|
||||
|
||||
// make filename table
|
||||
cu := make([]string, 0, len(filenames))
|
||||
cu = append(cu, filenames...)
|
||||
cutab, filetab, cuOffs := makeFilenametab([]compilationUnit{{cu}})
|
||||
mod.cutab = cutab
|
||||
mod.filetab = filetab
|
||||
|
||||
// make funcname table
|
||||
funcnametab, nameOffs := makeFuncnameTab(funcs)
|
||||
mod.funcnametab = funcnametab
|
||||
|
||||
// mmap() text and funcdata segements
|
||||
p := os.Getpagesize()
|
||||
size := int(rnd(int64(len(text)), int64(p)))
|
||||
addr := mmap(size)
|
||||
// copy the machine code
|
||||
s := rt.BytesFrom(unsafe.Pointer(addr), len(text), size)
|
||||
copy(s, text)
|
||||
// make it executable
|
||||
mprotect(addr, size)
|
||||
|
||||
// assign addresses
|
||||
mod.text = addr
|
||||
mod.etext = addr + uintptr(size)
|
||||
mod.minpc = addr
|
||||
mod.maxpc = addr + uintptr(len(text))
|
||||
|
||||
// make pcdata table
|
||||
// NOTICE: _func only use offset to index pcdata, thus no need mmap() pcdata
|
||||
cuOff := cuOffs[0]
|
||||
pctab, pcdataOffs, _funcs := makePctab(funcs, cuOff, nameOffs)
|
||||
mod.pctab = pctab
|
||||
|
||||
// write func data
|
||||
// NOTICE: _func use mod.gofunc+offset to directly point funcdata, thus need cache funcdata
|
||||
// TODO: estimate accurate capacity
|
||||
cache := make([]byte, 0, len(funcs)*int(_PtrSize))
|
||||
fstart, funcdataOffs := writeFuncdata(&cache, funcs)
|
||||
|
||||
// make pc->func (binary search) func table
|
||||
ftab, pclntSize, startLocations := makeFtab(_funcs, uint32(len(text)))
|
||||
mod.ftab = ftab
|
||||
|
||||
// write pc->func (modmap) findfunc table
|
||||
ffstart := writeFindfunctab(&cache, ftab)
|
||||
|
||||
// cache funcdata and findfuncbucket
|
||||
moduleCache.Lock()
|
||||
moduleCache.m[mod] = cache
|
||||
moduleCache.Unlock()
|
||||
mod.gofunc = uintptr(unsafe.Pointer(&cache[fstart]))
|
||||
mod.findfunctab = uintptr(unsafe.Pointer(&cache[ffstart]))
|
||||
|
||||
// make pclnt table
|
||||
pclntab := makePclntable(pclntSize, startLocations, _funcs, uint32(len(text)), pcdataOffs, funcdataOffs)
|
||||
mod.pclntable = pclntab
|
||||
|
||||
// make pc header
|
||||
mod.pcHeader = &pcHeader {
|
||||
magic : _Magic,
|
||||
minLC : _MinLC,
|
||||
ptrSize : _PtrSize,
|
||||
nfunc : len(funcs),
|
||||
nfiles: uint(len(cu)),
|
||||
textStart: mod.text,
|
||||
funcnameOffset: getOffsetOf(moduledata{}, "funcnametab"),
|
||||
cuOffset: getOffsetOf(moduledata{}, "cutab"),
|
||||
filetabOffset: getOffsetOf(moduledata{}, "filetab"),
|
||||
pctabOffset: getOffsetOf(moduledata{}, "pctab"),
|
||||
pclnOffset: getOffsetOf(moduledata{}, "pclntable"),
|
||||
}
|
||||
|
||||
// sepecial case: gcdata and gcbss must by non-empty
|
||||
mod.gcdata = uintptr(unsafe.Pointer(&emptyByte))
|
||||
mod.gcbss = uintptr(unsafe.Pointer(&emptyByte))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// makePctab generates pcdelta->valuedelta tables for functions,
|
||||
// and returns the table and the entry offset of every kind pcdata in the table.
|
||||
func makePctab(funcs []Func, cuOffset uint32, nameOffset []int32) (pctab []byte, pcdataOffs [][]uint32, _funcs []_func) {
|
||||
_funcs = make([]_func, len(funcs))
|
||||
|
||||
// Pctab offsets of 0 are considered invalid in the runtime. We respect
|
||||
// that by just padding a single byte at the beginning of runtime.pctab,
|
||||
// that way no real offsets can be zero.
|
||||
pctab = make([]byte, 1, 12*len(funcs)+1)
|
||||
pcdataOffs = make([][]uint32, len(funcs))
|
||||
|
||||
for i, f := range funcs {
|
||||
_f := &_funcs[i]
|
||||
|
||||
var writer = func(pc *Pcdata) {
|
||||
var ab []byte
|
||||
var err error
|
||||
if pc != nil {
|
||||
ab, err = pc.MarshalBinary()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
pcdataOffs[i] = append(pcdataOffs[i], uint32(len(pctab)))
|
||||
} else {
|
||||
ab = []byte{0}
|
||||
pcdataOffs[i] = append(pcdataOffs[i], _PCDATA_INVALID_OFFSET)
|
||||
}
|
||||
pctab = append(pctab, ab...)
|
||||
}
|
||||
|
||||
if f.Pcsp != nil {
|
||||
_f.pcsp = uint32(len(pctab))
|
||||
}
|
||||
writer(f.Pcsp)
|
||||
if f.Pcfile != nil {
|
||||
_f.pcfile = uint32(len(pctab))
|
||||
}
|
||||
writer(f.Pcfile)
|
||||
if f.Pcline != nil {
|
||||
_f.pcln = uint32(len(pctab))
|
||||
}
|
||||
writer(f.Pcline)
|
||||
writer(f.PcUnsafePoint)
|
||||
writer(f.PcStackMapIndex)
|
||||
writer(f.PcInlTreeIndex)
|
||||
writer(f.PcArgLiveIndex)
|
||||
|
||||
_f.entryOff = f.EntryOff
|
||||
_f.nameOff = nameOffset[i]
|
||||
_f.args = f.ArgsSize
|
||||
_f.deferreturn = f.DeferReturn
|
||||
// NOTICE: _func.pcdata is always as [PCDATA_UnsafePoint(0) : PCDATA_ArgLiveIndex(3)]
|
||||
_f.npcdata = uint32(_N_PCDATA)
|
||||
_f.cuOffset = cuOffset
|
||||
_f.funcID = f.ID
|
||||
_f.flag = f.Flag
|
||||
_f.nfuncdata = uint8(_N_FUNCDATA)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func registerFunction(name string, pc uintptr, textSize uintptr, fp int, args int, size uintptr, argptrs uintptr, localptrs uintptr) {}
|
@ -0,0 +1,46 @@
|
||||
//go:build !go1.16
|
||||
// +build !go1.16
|
||||
|
||||
/*
|
||||
* Copyright 2021 ByteDance Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package loader
|
||||
|
||||
import (
|
||||
`github.com/bytedance/sonic/internal/loader`
|
||||
)
|
||||
|
||||
// LoadFuncs loads only one function as module, and returns the function pointer
|
||||
// - text: machine code
|
||||
// - funcName: function name
|
||||
// - frameSize: stack frame size.
|
||||
// - argSize: argument total size (in bytes)
|
||||
// - argPtrs: indicates if a slot (8 Bytes) of arguments memory stores pointer, from low to high
|
||||
// - localPtrs: indicates if a slot (8 Bytes) of local variants memory stores pointer, from low to high
|
||||
//
|
||||
// WARN:
|
||||
// - the function MUST has fixed SP offset equaling to this, otherwise it go.gentraceback will fail
|
||||
// - the function MUST has only one stack map for all arguments and local variants
|
||||
func (self Loader) LoadOne(text []byte, funcName string, frameSize int, argSize int, argPtrs []bool, localPtrs []bool) Function {
|
||||
return Function(loader.Loader(text).Load(funcName, frameSize, argSize, argPtrs, localPtrs))
|
||||
}
|
||||
|
||||
// Load loads given machine codes and corresponding function information into go moduledata
|
||||
// and returns runnable function pointer
|
||||
// WARN: this API is experimental, use it carefully
|
||||
func Load(text []byte, funcs []Func, modulename string, filenames []string) (out []Function) {
|
||||
panic("not implemented")
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
//go:build go1.15 && !go1.16
|
||||
// +build go1.15,!go1.16
|
||||
|
||||
/*
|
||||
* Copyright 2021 ByteDance Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package loader
|
||||
|
||||
import (
|
||||
`github.com/bytedance/sonic/internal/loader`
|
||||
)
|
||||
|
||||
func (self Loader) LoadOne(text []byte, funcName string, frameSize int, argSize int, argStackmap []bool, localStackmap []bool) Function {
|
||||
return Function(loader.Loader(text).Load(funcName, frameSize, argSize, argStackmap, localStackmap))
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
// +build appengine plan9
|
||||
|
||||
package maxminddb
|
||||
|
||||
import "io/ioutil"
|
||||
|
||||
// Open takes a string path to a MaxMind DB file and returns a Reader
|
||||
// structure or an error. The database file is opened using a memory map,
|
||||
// except on Google App Engine where mmap is not supported; there the database
|
||||
// is loaded into memory. Use the Close method on the Reader object to return
|
||||
// the resources to the system.
|
||||
func Open(file string) (*Reader, error) {
|
||||
bytes, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return FromBytes(bytes)
|
||||
}
|
||||
|
||||
// Close unmaps the database file from virtual memory and returns the
|
||||
// resources to the system. If called on a Reader opened using FromBytes
|
||||
// or Open on Google App Engine, this method sets the underlying buffer
|
||||
// to nil, returning the resources to the system.
|
||||
func (r *Reader) Close() error {
|
||||
r.buffer = nil
|
||||
return nil
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
//go:build appengine || plan9 || js || wasip1
|
||||
// +build appengine plan9 js wasip1
|
||||
|
||||
package maxminddb
|
||||
|
||||
import "io/ioutil"
|
||||
|
||||
// Open takes a string path to a MaxMind DB file and returns a Reader
|
||||
// structure or an error. The database file is opened using a memory map
|
||||
// on supported platforms. On platforms without memory map support, such
|
||||
// as WebAssembly or Google App Engine, the database is loaded into memory.
|
||||
// Use the Close method on the Reader object to return the resources to the system.
|
||||
func Open(file string) (*Reader, error) {
|
||||
bytes, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return FromBytes(bytes)
|
||||
}
|
||||
|
||||
// Close returns the resources used by the database to the system.
|
||||
func (r *Reader) Close() error {
|
||||
r.buffer = nil
|
||||
return nil
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
*.prof
|
@ -1,10 +0,0 @@
|
||||
language: go
|
||||
go_import_path: github.com/pkg/errors
|
||||
go:
|
||||
- 1.11.x
|
||||
- 1.12.x
|
||||
- 1.13.x
|
||||
- tip
|
||||
|
||||
script:
|
||||
- make check
|
@ -1,23 +0,0 @@
|
||||
Copyright (c) 2015, Dave Cheney <dave@cheney.net>
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
@ -1,44 +0,0 @@
|
||||
PKGS := github.com/pkg/errors
|
||||
SRCDIRS := $(shell go list -f '{{.Dir}}' $(PKGS))
|
||||
GO := go
|
||||
|
||||
check: test vet gofmt misspell unconvert staticcheck ineffassign unparam
|
||||
|
||||
test:
|
||||
$(GO) test $(PKGS)
|
||||
|
||||
vet: | test
|
||||
$(GO) vet $(PKGS)
|
||||
|
||||
staticcheck:
|
||||
$(GO) get honnef.co/go/tools/cmd/staticcheck
|
||||
staticcheck -checks all $(PKGS)
|
||||
|
||||
misspell:
|
||||
$(GO) get github.com/client9/misspell/cmd/misspell
|
||||
misspell \
|
||||
-locale GB \
|
||||
-error \
|
||||
*.md *.go
|
||||
|
||||
unconvert:
|
||||
$(GO) get github.com/mdempsky/unconvert
|
||||
unconvert -v $(PKGS)
|
||||
|
||||
ineffassign:
|
||||
$(GO) get github.com/gordonklaus/ineffassign
|
||||
find $(SRCDIRS) -name '*.go' | xargs ineffassign
|
||||
|
||||
pedantic: check errcheck
|
||||
|
||||
unparam:
|
||||
$(GO) get mvdan.cc/unparam
|
||||
unparam ./...
|
||||
|
||||
errcheck:
|
||||
$(GO) get github.com/kisielk/errcheck
|
||||
errcheck $(PKGS)
|
||||
|
||||
gofmt:
|
||||
@echo Checking code is gofmted
|
||||
@test -z "$(shell gofmt -s -l -d -e $(SRCDIRS) | tee /dev/stderr)"
|
@ -1,59 +0,0 @@
|
||||
# errors [![Travis-CI](https://travis-ci.org/pkg/errors.svg)](https://travis-ci.org/pkg/errors) [![AppVeyor](https://ci.appveyor.com/api/projects/status/b98mptawhudj53ep/branch/master?svg=true)](https://ci.appveyor.com/project/davecheney/errors/branch/master) [![GoDoc](https://godoc.org/github.com/pkg/errors?status.svg)](http://godoc.org/github.com/pkg/errors) [![Report card](https://goreportcard.com/badge/github.com/pkg/errors)](https://goreportcard.com/report/github.com/pkg/errors) [![Sourcegraph](https://sourcegraph.com/github.com/pkg/errors/-/badge.svg)](https://sourcegraph.com/github.com/pkg/errors?badge)
|
||||
|
||||
Package errors provides simple error handling primitives.
|
||||
|
||||
`go get github.com/pkg/errors`
|
||||
|
||||
The traditional error handling idiom in Go is roughly akin to
|
||||
```go
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
```
|
||||
which applied recursively up the call stack results in error reports without context or debugging information. The errors package allows programmers to add context to the failure path in their code in a way that does not destroy the original value of the error.
|
||||
|
||||
## Adding context to an error
|
||||
|
||||
The errors.Wrap function returns a new error that adds context to the original error. For example
|
||||
```go
|
||||
_, err := ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "read failed")
|
||||
}
|
||||
```
|
||||
## Retrieving the cause of an error
|
||||
|
||||
Using `errors.Wrap` constructs a stack of errors, adding context to the preceding error. Depending on the nature of the error it may be necessary to reverse the operation of errors.Wrap to retrieve the original error for inspection. Any error value which implements this interface can be inspected by `errors.Cause`.
|
||||
```go
|
||||
type causer interface {
|
||||
Cause() error
|
||||
}
|
||||
```
|
||||
`errors.Cause` will recursively retrieve the topmost error which does not implement `causer`, which is assumed to be the original cause. For example:
|
||||
```go
|
||||
switch err := errors.Cause(err).(type) {
|
||||
case *MyError:
|
||||
// handle specifically
|
||||
default:
|
||||
// unknown error
|
||||
}
|
||||
```
|
||||
|
||||
[Read the package documentation for more information](https://godoc.org/github.com/pkg/errors).
|
||||
|
||||
## Roadmap
|
||||
|
||||
With the upcoming [Go2 error proposals](https://go.googlesource.com/proposal/+/master/design/go2draft.md) this package is moving into maintenance mode. The roadmap for a 1.0 release is as follows:
|
||||
|
||||
- 0.9. Remove pre Go 1.9 and Go 1.10 support, address outstanding pull requests (if possible)
|
||||
- 1.0. Final release.
|
||||
|
||||
## Contributing
|
||||
|
||||
Because of the Go2 errors changes, this package is not accepting proposals for new functionality. With that said, we welcome pull requests, bug fixes and issue reports.
|
||||
|
||||
Before sending a PR, please discuss your change by raising an issue.
|
||||
|
||||
## License
|
||||
|
||||
BSD-2-Clause
|
@ -1,32 +0,0 @@
|
||||
version: build-{build}.{branch}
|
||||
|
||||
clone_folder: C:\gopath\src\github.com\pkg\errors
|
||||
shallow_clone: true # for startup speed
|
||||
|
||||
environment:
|
||||
GOPATH: C:\gopath
|
||||
|
||||
platform:
|
||||
- x64
|
||||
|
||||
# http://www.appveyor.com/docs/installed-software
|
||||
install:
|
||||
# some helpful output for debugging builds
|
||||
- go version
|
||||
- go env
|
||||
# pre-installed MinGW at C:\MinGW is 32bit only
|
||||
# but MSYS2 at C:\msys64 has mingw64
|
||||
- set PATH=C:\msys64\mingw64\bin;%PATH%
|
||||
- gcc --version
|
||||
- g++ --version
|
||||
|
||||
build_script:
|
||||
- go install -v ./...
|
||||
|
||||
test_script:
|
||||
- set PATH=C:\gopath\bin;%PATH%
|
||||
- go test -v ./...
|
||||
|
||||
#artifacts:
|
||||
# - path: '%GOPATH%\bin\*.exe'
|
||||
deploy: off
|
@ -1,288 +0,0 @@
|
||||
// Package errors provides simple error handling primitives.
|
||||
//
|
||||
// The traditional error handling idiom in Go is roughly akin to
|
||||
//
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// which when applied recursively up the call stack results in error reports
|
||||
// without context or debugging information. The errors package allows
|
||||
// programmers to add context to the failure path in their code in a way
|
||||
// that does not destroy the original value of the error.
|
||||
//
|
||||
// Adding context to an error
|
||||
//
|
||||
// The errors.Wrap function returns a new error that adds context to the
|
||||
// original error by recording a stack trace at the point Wrap is called,
|
||||
// together with the supplied message. For example
|
||||
//
|
||||
// _, err := ioutil.ReadAll(r)
|
||||
// if err != nil {
|
||||
// return errors.Wrap(err, "read failed")
|
||||
// }
|
||||
//
|
||||
// If additional control is required, the errors.WithStack and
|
||||
// errors.WithMessage functions destructure errors.Wrap into its component
|
||||
// operations: annotating an error with a stack trace and with a message,
|
||||
// respectively.
|
||||
//
|
||||
// Retrieving the cause of an error
|
||||
//
|
||||
// Using errors.Wrap constructs a stack of errors, adding context to the
|
||||
// preceding error. Depending on the nature of the error it may be necessary
|
||||
// to reverse the operation of errors.Wrap to retrieve the original error
|
||||
// for inspection. Any error value which implements this interface
|
||||
//
|
||||
// type causer interface {
|
||||
// Cause() error
|
||||
// }
|
||||
//
|
||||
// can be inspected by errors.Cause. errors.Cause will recursively retrieve
|
||||
// the topmost error that does not implement causer, which is assumed to be
|
||||
// the original cause. For example:
|
||||
//
|
||||
// switch err := errors.Cause(err).(type) {
|
||||
// case *MyError:
|
||||
// // handle specifically
|
||||
// default:
|
||||
// // unknown error
|
||||
// }
|
||||
//
|
||||
// Although the causer interface is not exported by this package, it is
|
||||
// considered a part of its stable public interface.
|
||||
//
|
||||
// Formatted printing of errors
|
||||
//
|
||||
// All error values returned from this package implement fmt.Formatter and can
|
||||
// be formatted by the fmt package. The following verbs are supported:
|
||||
//
|
||||
// %s print the error. If the error has a Cause it will be
|
||||
// printed recursively.
|
||||
// %v see %s
|
||||
// %+v extended format. Each Frame of the error's StackTrace will
|
||||
// be printed in detail.
|
||||
//
|
||||
// Retrieving the stack trace of an error or wrapper
|
||||
//
|
||||
// New, Errorf, Wrap, and Wrapf record a stack trace at the point they are
|
||||
// invoked. This information can be retrieved with the following interface:
|
||||
//
|
||||
// type stackTracer interface {
|
||||
// StackTrace() errors.StackTrace
|
||||
// }
|
||||
//
|
||||
// The returned errors.StackTrace type is defined as
|
||||
//
|
||||
// type StackTrace []Frame
|
||||
//
|
||||
// The Frame type represents a call site in the stack trace. Frame supports
|
||||
// the fmt.Formatter interface that can be used for printing information about
|
||||
// the stack trace of this error. For example:
|
||||
//
|
||||
// if err, ok := err.(stackTracer); ok {
|
||||
// for _, f := range err.StackTrace() {
|
||||
// fmt.Printf("%+s:%d\n", f, f)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Although the stackTracer interface is not exported by this package, it is
|
||||
// considered a part of its stable public interface.
|
||||
//
|
||||
// See the documentation for Frame.Format for more details.
|
||||
package errors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// New returns an error with the supplied message.
|
||||
// New also records the stack trace at the point it was called.
|
||||
func New(message string) error {
|
||||
return &fundamental{
|
||||
msg: message,
|
||||
stack: callers(),
|
||||
}
|
||||
}
|
||||
|
||||
// Errorf formats according to a format specifier and returns the string
|
||||
// as a value that satisfies error.
|
||||
// Errorf also records the stack trace at the point it was called.
|
||||
func Errorf(format string, args ...interface{}) error {
|
||||
return &fundamental{
|
||||
msg: fmt.Sprintf(format, args...),
|
||||
stack: callers(),
|
||||
}
|
||||
}
|
||||
|
||||
// fundamental is an error that has a message and a stack, but no caller.
|
||||
type fundamental struct {
|
||||
msg string
|
||||
*stack
|
||||
}
|
||||
|
||||
func (f *fundamental) Error() string { return f.msg }
|
||||
|
||||
func (f *fundamental) Format(s fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 'v':
|
||||
if s.Flag('+') {
|
||||
io.WriteString(s, f.msg)
|
||||
f.stack.Format(s, verb)
|
||||
return
|
||||
}
|
||||
fallthrough
|
||||
case 's':
|
||||
io.WriteString(s, f.msg)
|
||||
case 'q':
|
||||
fmt.Fprintf(s, "%q", f.msg)
|
||||
}
|
||||
}
|
||||
|
||||
// WithStack annotates err with a stack trace at the point WithStack was called.
|
||||
// If err is nil, WithStack returns nil.
|
||||
func WithStack(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
return &withStack{
|
||||
err,
|
||||
callers(),
|
||||
}
|
||||
}
|
||||
|
||||
type withStack struct {
|
||||
error
|
||||
*stack
|
||||
}
|
||||
|
||||
func (w *withStack) Cause() error { return w.error }
|
||||
|
||||
// Unwrap provides compatibility for Go 1.13 error chains.
|
||||
func (w *withStack) Unwrap() error { return w.error }
|
||||
|
||||
func (w *withStack) Format(s fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 'v':
|
||||
if s.Flag('+') {
|
||||
fmt.Fprintf(s, "%+v", w.Cause())
|
||||
w.stack.Format(s, verb)
|
||||
return
|
||||
}
|
||||
fallthrough
|
||||
case 's':
|
||||
io.WriteString(s, w.Error())
|
||||
case 'q':
|
||||
fmt.Fprintf(s, "%q", w.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// Wrap returns an error annotating err with a stack trace
|
||||
// at the point Wrap is called, and the supplied message.
|
||||
// If err is nil, Wrap returns nil.
|
||||
func Wrap(err error, message string) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
err = &withMessage{
|
||||
cause: err,
|
||||
msg: message,
|
||||
}
|
||||
return &withStack{
|
||||
err,
|
||||
callers(),
|
||||
}
|
||||
}
|
||||
|
||||
// Wrapf returns an error annotating err with a stack trace
|
||||
// at the point Wrapf is called, and the format specifier.
|
||||
// If err is nil, Wrapf returns nil.
|
||||
func Wrapf(err error, format string, args ...interface{}) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
err = &withMessage{
|
||||
cause: err,
|
||||
msg: fmt.Sprintf(format, args...),
|
||||
}
|
||||
return &withStack{
|
||||
err,
|
||||
callers(),
|
||||
}
|
||||
}
|
||||
|
||||
// WithMessage annotates err with a new message.
|
||||
// If err is nil, WithMessage returns nil.
|
||||
func WithMessage(err error, message string) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
return &withMessage{
|
||||
cause: err,
|
||||
msg: message,
|
||||
}
|
||||
}
|
||||
|
||||
// WithMessagef annotates err with the format specifier.
|
||||
// If err is nil, WithMessagef returns nil.
|
||||
func WithMessagef(err error, format string, args ...interface{}) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
return &withMessage{
|
||||
cause: err,
|
||||
msg: fmt.Sprintf(format, args...),
|
||||
}
|
||||
}
|
||||
|
||||
type withMessage struct {
|
||||
cause error
|
||||
msg string
|
||||
}
|
||||
|
||||
func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() }
|
||||
func (w *withMessage) Cause() error { return w.cause }
|
||||
|
||||
// Unwrap provides compatibility for Go 1.13 error chains.
|
||||
func (w *withMessage) Unwrap() error { return w.cause }
|
||||
|
||||
func (w *withMessage) Format(s fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 'v':
|
||||
if s.Flag('+') {
|
||||
fmt.Fprintf(s, "%+v\n", w.Cause())
|
||||
io.WriteString(s, w.msg)
|
||||
return
|
||||
}
|
||||
fallthrough
|
||||
case 's', 'q':
|
||||
io.WriteString(s, w.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// Cause returns the underlying cause of the error, if possible.
|
||||
// An error value has a cause if it implements the following
|
||||
// interface:
|
||||
//
|
||||
// type causer interface {
|
||||
// Cause() error
|
||||
// }
|
||||
//
|
||||
// If the error does not implement Cause, the original error will
|
||||
// be returned. If the error is nil, nil will be returned without further
|
||||
// investigation.
|
||||
func Cause(err error) error {
|
||||
type causer interface {
|
||||
Cause() error
|
||||
}
|
||||
|
||||
for err != nil {
|
||||
cause, ok := err.(causer)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
err = cause.Cause()
|
||||
}
|
||||
return err
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
// +build go1.13
|
||||
|
||||
package errors
|
||||
|
||||
import (
|
||||
stderrors "errors"
|
||||
)
|
||||
|
||||
// Is reports whether any error in err's chain matches target.
|
||||
//
|
||||
// The chain consists of err itself followed by the sequence of errors obtained by
|
||||
// repeatedly calling Unwrap.
|
||||
//
|
||||
// An error is considered to match a target if it is equal to that target or if
|
||||
// it implements a method Is(error) bool such that Is(target) returns true.
|
||||
func Is(err, target error) bool { return stderrors.Is(err, target) }
|
||||
|
||||
// As finds the first error in err's chain that matches target, and if so, sets
|
||||
// target to that error value and returns true.
|
||||
//
|
||||
// The chain consists of err itself followed by the sequence of errors obtained by
|
||||
// repeatedly calling Unwrap.
|
||||
//
|
||||
// An error matches target if the error's concrete value is assignable to the value
|
||||
// pointed to by target, or if the error has a method As(interface{}) bool such that
|
||||
// As(target) returns true. In the latter case, the As method is responsible for
|
||||
// setting target.
|
||||
//
|
||||
// As will panic if target is not a non-nil pointer to either a type that implements
|
||||
// error, or to any interface type. As returns false if err is nil.
|
||||
func As(err error, target interface{}) bool { return stderrors.As(err, target) }
|
||||
|
||||
// Unwrap returns the result of calling the Unwrap method on err, if err's
|
||||
// type contains an Unwrap method returning error.
|
||||
// Otherwise, Unwrap returns nil.
|
||||
func Unwrap(err error) error {
|
||||
return stderrors.Unwrap(err)
|
||||
}
|
@ -1,177 +0,0 @@
|
||||
package errors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"path"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Frame represents a program counter inside a stack frame.
|
||||
// For historical reasons if Frame is interpreted as a uintptr
|
||||
// its value represents the program counter + 1.
|
||||
type Frame uintptr
|
||||
|
||||
// pc returns the program counter for this frame;
|
||||
// multiple frames may have the same PC value.
|
||||
func (f Frame) pc() uintptr { return uintptr(f) - 1 }
|
||||
|
||||
// file returns the full path to the file that contains the
|
||||
// function for this Frame's pc.
|
||||
func (f Frame) file() string {
|
||||
fn := runtime.FuncForPC(f.pc())
|
||||
if fn == nil {
|
||||
return "unknown"
|
||||
}
|
||||
file, _ := fn.FileLine(f.pc())
|
||||
return file
|
||||
}
|
||||
|
||||
// line returns the line number of source code of the
|
||||
// function for this Frame's pc.
|
||||
func (f Frame) line() int {
|
||||
fn := runtime.FuncForPC(f.pc())
|
||||
if fn == nil {
|
||||
return 0
|
||||
}
|
||||
_, line := fn.FileLine(f.pc())
|
||||
return line
|
||||
}
|
||||
|
||||
// name returns the name of this function, if known.
|
||||
func (f Frame) name() string {
|
||||
fn := runtime.FuncForPC(f.pc())
|
||||
if fn == nil {
|
||||
return "unknown"
|
||||
}
|
||||
return fn.Name()
|
||||
}
|
||||
|
||||
// Format formats the frame according to the fmt.Formatter interface.
|
||||
//
|
||||
// %s source file
|
||||
// %d source line
|
||||
// %n function name
|
||||
// %v equivalent to %s:%d
|
||||
//
|
||||
// Format accepts flags that alter the printing of some verbs, as follows:
|
||||
//
|
||||
// %+s function name and path of source file relative to the compile time
|
||||
// GOPATH separated by \n\t (<funcname>\n\t<path>)
|
||||
// %+v equivalent to %+s:%d
|
||||
func (f Frame) Format(s fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 's':
|
||||
switch {
|
||||
case s.Flag('+'):
|
||||
io.WriteString(s, f.name())
|
||||
io.WriteString(s, "\n\t")
|
||||
io.WriteString(s, f.file())
|
||||
default:
|
||||
io.WriteString(s, path.Base(f.file()))
|
||||
}
|
||||
case 'd':
|
||||
io.WriteString(s, strconv.Itoa(f.line()))
|
||||
case 'n':
|
||||
io.WriteString(s, funcname(f.name()))
|
||||
case 'v':
|
||||
f.Format(s, 's')
|
||||
io.WriteString(s, ":")
|
||||
f.Format(s, 'd')
|
||||
}
|
||||
}
|
||||
|
||||
// MarshalText formats a stacktrace Frame as a text string. The output is the
|
||||
// same as that of fmt.Sprintf("%+v", f), but without newlines or tabs.
|
||||
func (f Frame) MarshalText() ([]byte, error) {
|
||||
name := f.name()
|
||||
if name == "unknown" {
|
||||
return []byte(name), nil
|
||||
}
|
||||
return []byte(fmt.Sprintf("%s %s:%d", name, f.file(), f.line())), nil
|
||||
}
|
||||
|
||||
// StackTrace is stack of Frames from innermost (newest) to outermost (oldest).
|
||||
type StackTrace []Frame
|
||||
|
||||
// Format formats the stack of Frames according to the fmt.Formatter interface.
|
||||
//
|
||||
// %s lists source files for each Frame in the stack
|
||||
// %v lists the source file and line number for each Frame in the stack
|
||||
//
|
||||
// Format accepts flags that alter the printing of some verbs, as follows:
|
||||
//
|
||||
// %+v Prints filename, function, and line number for each Frame in the stack.
|
||||
func (st StackTrace) Format(s fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 'v':
|
||||
switch {
|
||||
case s.Flag('+'):
|
||||
for _, f := range st {
|
||||
io.WriteString(s, "\n")
|
||||
f.Format(s, verb)
|
||||
}
|
||||
case s.Flag('#'):
|
||||
fmt.Fprintf(s, "%#v", []Frame(st))
|
||||
default:
|
||||
st.formatSlice(s, verb)
|
||||
}
|
||||
case 's':
|
||||
st.formatSlice(s, verb)
|
||||
}
|
||||
}
|
||||
|
||||
// formatSlice will format this StackTrace into the given buffer as a slice of
|
||||
// Frame, only valid when called with '%s' or '%v'.
|
||||
func (st StackTrace) formatSlice(s fmt.State, verb rune) {
|
||||
io.WriteString(s, "[")
|
||||
for i, f := range st {
|
||||
if i > 0 {
|
||||
io.WriteString(s, " ")
|
||||
}
|
||||
f.Format(s, verb)
|
||||
}
|
||||
io.WriteString(s, "]")
|
||||
}
|
||||
|
||||
// stack represents a stack of program counters.
|
||||
type stack []uintptr
|
||||
|
||||
func (s *stack) Format(st fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 'v':
|
||||
switch {
|
||||
case st.Flag('+'):
|
||||
for _, pc := range *s {
|
||||
f := Frame(pc)
|
||||
fmt.Fprintf(st, "\n%+v", f)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *stack) StackTrace() StackTrace {
|
||||
f := make([]Frame, len(*s))
|
||||
for i := 0; i < len(f); i++ {
|
||||
f[i] = Frame((*s)[i])
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
func callers() *stack {
|
||||
const depth = 32
|
||||
var pcs [depth]uintptr
|
||||
n := runtime.Callers(3, pcs[:])
|
||||
var st stack = pcs[0:n]
|
||||
return &st
|
||||
}
|
||||
|
||||
// funcname removes the path prefix component of a function's name reported by func.Name().
|
||||
func funcname(name string) string {
|
||||
i := strings.LastIndex(name, "/")
|
||||
name = name[i+1:]
|
||||
i = strings.Index(name, ".")
|
||||
return name[i+1:]
|
||||
}
|
@ -0,0 +1,98 @@
|
||||
package clientv2
|
||||
|
||||
import (
|
||||
clientV1 "github.com/qiniu/go-sdk/v7/client"
|
||||
"net/http"
|
||||
"sort"
|
||||
)
|
||||
|
||||
type Client interface {
|
||||
Do(req *http.Request) (*http.Response, error)
|
||||
}
|
||||
|
||||
type Handler func(req *http.Request) (*http.Response, error)
|
||||
|
||||
type client struct {
|
||||
coreClient Client
|
||||
interceptors []Interceptor
|
||||
}
|
||||
|
||||
func NewClient(cli Client, interceptors ...Interceptor) Client {
|
||||
if cli == nil {
|
||||
cli = http.DefaultClient
|
||||
}
|
||||
|
||||
var is interceptorList = interceptors
|
||||
is = append(is, newDefaultHeaderInterceptor())
|
||||
is = append(is, newDebugInterceptor())
|
||||
sort.Sort(is)
|
||||
|
||||
// 反转
|
||||
for i, j := 0, len(is)-1; i < j; i, j = i+1, j-1 {
|
||||
is[i], is[j] = is[j], is[i]
|
||||
}
|
||||
|
||||
return &client{
|
||||
coreClient: cli,
|
||||
interceptors: is,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *client) Do(req *http.Request) (*http.Response, error) {
|
||||
handler := func(req *http.Request) (*http.Response, error) {
|
||||
return c.coreClient.Do(req)
|
||||
}
|
||||
|
||||
interceptors := c.interceptors
|
||||
for _, interceptor := range interceptors {
|
||||
h := handler
|
||||
i := interceptor
|
||||
handler = func(r *http.Request) (*http.Response, error) {
|
||||
return i.Intercept(r, h)
|
||||
}
|
||||
}
|
||||
|
||||
resp, err := handler(req)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
if resp == nil {
|
||||
return nil, &clientV1.ErrorInfo{
|
||||
Code: -999,
|
||||
Err: "unknown error, no response",
|
||||
}
|
||||
}
|
||||
|
||||
if resp.StatusCode/100 != 2 {
|
||||
return resp, clientV1.ResponseError(resp)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func Do(c Client, options RequestParams) (*http.Response, error) {
|
||||
req, err := NewRequest(options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c.Do(req)
|
||||
}
|
||||
|
||||
func DoAndDecodeJsonResponse(c Client, options RequestParams, ret interface{}) (*http.Response, error) {
|
||||
resp, err := Do(c, options)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
if ret == nil || resp.ContentLength == 0 {
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
if err = clientV1.DecodeJsonFromReader(resp.Body, ret); err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
package clientv2
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const (
|
||||
InterceptorPriorityDefault InterceptorPriority = 100
|
||||
InterceptorPriorityRetryHosts InterceptorPriority = 200
|
||||
InterceptorPriorityRetrySimple InterceptorPriority = 300
|
||||
InterceptorPrioritySetHeader InterceptorPriority = 400
|
||||
InterceptorPriorityNormal InterceptorPriority = 500
|
||||
InterceptorPriorityAuth InterceptorPriority = 600
|
||||
InterceptorPriorityDebug InterceptorPriority = 700
|
||||
)
|
||||
|
||||
type InterceptorPriority int
|
||||
|
||||
type Interceptor interface {
|
||||
// Priority 数字越小优先级越高
|
||||
Priority() InterceptorPriority
|
||||
|
||||
// Intercept 拦截处理函数
|
||||
Intercept(req *http.Request, handler Handler) (*http.Response, error)
|
||||
}
|
||||
|
||||
type interceptorList []Interceptor
|
||||
|
||||
func (l interceptorList) Less(i, j int) bool {
|
||||
return l[i].Priority() < l[j].Priority()
|
||||
}
|
||||
|
||||
func (l interceptorList) Swap(i, j int) {
|
||||
l[i], l[j] = l[j], l[i]
|
||||
}
|
||||
|
||||
func (l interceptorList) Len() int {
|
||||
return len(l)
|
||||
}
|
||||
|
||||
type simpleInterceptor struct {
|
||||
priority InterceptorPriority
|
||||
handler func(req *http.Request, handler Handler) (*http.Response, error)
|
||||
}
|
||||
|
||||
func NewSimpleInterceptor(interceptorHandler func(req *http.Request, handler Handler) (*http.Response, error)) Interceptor {
|
||||
return NewSimpleInterceptorWithPriority(InterceptorPriorityNormal, interceptorHandler)
|
||||
}
|
||||
|
||||
func NewSimpleInterceptorWithPriority(priority InterceptorPriority, interceptorHandler func(req *http.Request, handler Handler) (*http.Response, error)) Interceptor {
|
||||
if priority <= 0 {
|
||||
priority = InterceptorPriorityNormal
|
||||
}
|
||||
|
||||
return &simpleInterceptor{
|
||||
priority: priority,
|
||||
handler: interceptorHandler,
|
||||
}
|
||||
}
|
||||
|
||||
func (interceptor *simpleInterceptor) Priority() InterceptorPriority {
|
||||
return interceptor.priority
|
||||
}
|
||||
|
||||
func (interceptor *simpleInterceptor) Intercept(req *http.Request, handler Handler) (*http.Response, error) {
|
||||
if interceptor == nil || interceptor.handler == nil {
|
||||
return handler(req)
|
||||
}
|
||||
return interceptor.handler(req, handler)
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package clientv2
|
||||
|
||||
import (
|
||||
"github.com/qiniu/go-sdk/v7/auth"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type AuthConfig struct {
|
||||
Credentials auth.Credentials //
|
||||
TokenType auth.TokenType // 不包含上传
|
||||
}
|
||||
|
||||
type authInterceptor struct {
|
||||
config AuthConfig
|
||||
}
|
||||
|
||||
func NewAuthInterceptor(config AuthConfig) Interceptor {
|
||||
return &authInterceptor{
|
||||
config: config,
|
||||
}
|
||||
}
|
||||
|
||||
func (interceptor *authInterceptor) Priority() InterceptorPriority {
|
||||
return InterceptorPriorityAuth
|
||||
}
|
||||
|
||||
func (interceptor *authInterceptor) Intercept(req *http.Request, handler Handler) (*http.Response, error) {
|
||||
if interceptor == nil || req == nil {
|
||||
return handler(req)
|
||||
}
|
||||
|
||||
err := interceptor.config.Credentials.AddToken(interceptor.config.TokenType, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return handler(req)
|
||||
}
|
@ -0,0 +1,209 @@
|
||||
package clientv2
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
clientV1 "github.com/qiniu/go-sdk/v7/client"
|
||||
"github.com/qiniu/go-sdk/v7/internal/log"
|
||||
"net/http"
|
||||
"net/http/httptrace"
|
||||
"net/http/httputil"
|
||||
)
|
||||
|
||||
type DebugLevel int
|
||||
|
||||
const (
|
||||
DebugLevelPrintNone DebugLevel = 0
|
||||
DebugLevelPrintNormal DebugLevel = 1
|
||||
DebugLevelPrintDetail DebugLevel = 2
|
||||
)
|
||||
|
||||
var (
|
||||
printRequestTrace = false
|
||||
printRequestLevel *DebugLevel = nil
|
||||
printResponseLevel *DebugLevel = nil
|
||||
)
|
||||
|
||||
func PrintRequestTrace(isPrint bool) {
|
||||
printRequestTrace = isPrint
|
||||
}
|
||||
|
||||
func IsPrintRequestTrace() bool {
|
||||
return printRequestTrace
|
||||
}
|
||||
|
||||
func PrintRequest(level DebugLevel) {
|
||||
printRequestLevel = &level
|
||||
}
|
||||
|
||||
func IsPrintRequest() bool {
|
||||
if printRequestLevel != nil {
|
||||
return *printRequestLevel == DebugLevelPrintNormal || *printRequestLevel == DebugLevelPrintDetail
|
||||
}
|
||||
return clientV1.DebugMode
|
||||
}
|
||||
|
||||
func IsPrintRequestBody() bool {
|
||||
if printRequestLevel != nil {
|
||||
return *printRequestLevel == DebugLevelPrintDetail
|
||||
}
|
||||
return clientV1.DeepDebugInfo
|
||||
}
|
||||
|
||||
func PrintResponse(level DebugLevel) {
|
||||
printResponseLevel = &level
|
||||
}
|
||||
|
||||
func IsPrintResponse() bool {
|
||||
if printResponseLevel != nil {
|
||||
return *printResponseLevel == DebugLevelPrintNormal || *printResponseLevel == DebugLevelPrintDetail
|
||||
}
|
||||
return clientV1.DebugMode
|
||||
}
|
||||
|
||||
func IsPrintResponseBody() bool {
|
||||
if printResponseLevel != nil {
|
||||
return *printResponseLevel == DebugLevelPrintDetail
|
||||
}
|
||||
return clientV1.DeepDebugInfo
|
||||
}
|
||||
|
||||
type debugInterceptor struct {
|
||||
}
|
||||
|
||||
func newDebugInterceptor() Interceptor {
|
||||
return &debugInterceptor{}
|
||||
}
|
||||
|
||||
func (interceptor *debugInterceptor) Priority() InterceptorPriority {
|
||||
return InterceptorPriorityDebug
|
||||
}
|
||||
|
||||
func (interceptor *debugInterceptor) Intercept(req *http.Request, handler Handler) (*http.Response, error) {
|
||||
if interceptor == nil {
|
||||
return handler(req)
|
||||
}
|
||||
|
||||
label := interceptor.requestLabel(req)
|
||||
|
||||
if e := interceptor.printRequest(label, req); e != nil {
|
||||
return nil, e
|
||||
}
|
||||
|
||||
req = interceptor.printRequestTrace(label, req)
|
||||
|
||||
resp, err := handler(req)
|
||||
|
||||
if e := interceptor.printResponse(label, resp); e != nil {
|
||||
return nil, e
|
||||
}
|
||||
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func (interceptor *debugInterceptor) requestLabel(req *http.Request) string {
|
||||
if req == nil || req.URL == nil {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("Url:%s", req.URL.String())
|
||||
}
|
||||
|
||||
func (interceptor *debugInterceptor) printRequest(label string, req *http.Request) error {
|
||||
if req == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
printReq := IsPrintRequest()
|
||||
if !printReq {
|
||||
return nil
|
||||
}
|
||||
|
||||
info := label + " request:\n"
|
||||
d, dErr := httputil.DumpRequest(req, IsPrintRequestBody())
|
||||
if dErr != nil {
|
||||
return dErr
|
||||
}
|
||||
info += string(d) + "\n"
|
||||
|
||||
log.Debug(info)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (interceptor *debugInterceptor) printRequestTrace(label string, req *http.Request) *http.Request {
|
||||
if !IsPrintRequestTrace() || req == nil {
|
||||
return req
|
||||
}
|
||||
|
||||
label += "\n"
|
||||
trace := &httptrace.ClientTrace{
|
||||
GetConn: func(hostPort string) {
|
||||
log.Debug(label + fmt.Sprintf("GetConn, %s \n", hostPort))
|
||||
},
|
||||
GotConn: func(connInfo httptrace.GotConnInfo) {
|
||||
remoteAddr := connInfo.Conn.RemoteAddr()
|
||||
log.Debug(label + fmt.Sprintf("GotConn, Network:%s RemoteAddr:%s \n", remoteAddr.Network(), remoteAddr.String()))
|
||||
},
|
||||
PutIdleConn: func(err error) {
|
||||
log.Debug(label + fmt.Sprintf("PutIdleConn, err:%v \n", err))
|
||||
},
|
||||
GotFirstResponseByte: func() {
|
||||
log.Debug(label + fmt.Sprint("GotFirstResponseByte \n"))
|
||||
},
|
||||
Got100Continue: func() {
|
||||
log.Debug(label + fmt.Sprint("Got100Continue \n"))
|
||||
},
|
||||
DNSStart: func(info httptrace.DNSStartInfo) {
|
||||
log.Debug(label + fmt.Sprintf("DNSStart, host:%s \n", info.Host))
|
||||
},
|
||||
DNSDone: func(info httptrace.DNSDoneInfo) {
|
||||
log.Debug(label + fmt.Sprintf("DNSDone, addr:%+v \n", info.Addrs))
|
||||
},
|
||||
ConnectStart: func(network, addr string) {
|
||||
log.Debug(label + fmt.Sprintf("ConnectStart, network:%+v ip:%s \n", network, addr))
|
||||
},
|
||||
ConnectDone: func(network, addr string, err error) {
|
||||
log.Debug(label + fmt.Sprintf("ConnectDone, network:%s ip:%s err:%v \n", network, addr, err))
|
||||
},
|
||||
TLSHandshakeStart: func() {
|
||||
log.Debug(label + fmt.Sprint("TLSHandshakeStart \n"))
|
||||
},
|
||||
TLSHandshakeDone: func(state tls.ConnectionState, err error) {
|
||||
log.Debug(label + fmt.Sprintf("TLSHandshakeDone, state:%+v err:%s \n", state, err))
|
||||
},
|
||||
// go1.10 不支持
|
||||
//WroteHeaderField: func(key string, value []string) {
|
||||
// log.Debug(label + fmt.Sprintf("WroteHeaderField, key:%s value:%s \n", key, value))
|
||||
//},
|
||||
WroteHeaders: func() {
|
||||
log.Debug(label + fmt.Sprint("WroteHeaders \n"))
|
||||
},
|
||||
Wait100Continue: func() {
|
||||
log.Debug(label + fmt.Sprint("Wait100Continue \n"))
|
||||
},
|
||||
WroteRequest: func(info httptrace.WroteRequestInfo) {
|
||||
log.Debug(label + fmt.Sprintf("WroteRequest, err:%v \n", info.Err))
|
||||
},
|
||||
}
|
||||
return req.WithContext(httptrace.WithClientTrace(req.Context(), trace))
|
||||
}
|
||||
|
||||
func (interceptor *debugInterceptor) printResponse(label string, resp *http.Response) error {
|
||||
if resp == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
printResp := IsPrintResponse()
|
||||
if !printResp {
|
||||
return nil
|
||||
}
|
||||
|
||||
info := label + " response:\n"
|
||||
d, dErr := httputil.DumpResponse(resp, IsPrintResponseBody())
|
||||
if dErr != nil {
|
||||
return dErr
|
||||
}
|
||||
info += string(d) + "\n"
|
||||
|
||||
log.Debug(info)
|
||||
return nil
|
||||
}
|
54
vendor/github.com/qiniu/go-sdk/v7/internal/clientv2/interceptor_default_header.go
generated
vendored
54
vendor/github.com/qiniu/go-sdk/v7/internal/clientv2/interceptor_default_header.go
generated
vendored
@ -0,0 +1,54 @@
|
||||
package clientv2
|
||||
|
||||
import (
|
||||
clientV1 "github.com/qiniu/go-sdk/v7/client"
|
||||
"github.com/qiniu/go-sdk/v7/conf"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
type defaultHeaderInterceptor struct {
|
||||
}
|
||||
|
||||
func newDefaultHeaderInterceptor() Interceptor {
|
||||
return &defaultHeaderInterceptor{}
|
||||
}
|
||||
|
||||
func (interceptor *defaultHeaderInterceptor) Priority() InterceptorPriority {
|
||||
return InterceptorPrioritySetHeader
|
||||
}
|
||||
|
||||
func (interceptor *defaultHeaderInterceptor) Intercept(req *http.Request, handler Handler) (resp *http.Response, err error) {
|
||||
if interceptor == nil || req == nil {
|
||||
return handler(req)
|
||||
}
|
||||
|
||||
if req.Header == nil {
|
||||
req.Header = http.Header{}
|
||||
}
|
||||
|
||||
if e := addUseragent(req.Header); e != nil {
|
||||
return nil, e
|
||||
}
|
||||
|
||||
if e := addXQiniuDate(req.Header); e != nil {
|
||||
return nil, e
|
||||
}
|
||||
|
||||
return handler(req)
|
||||
}
|
||||
|
||||
func addUseragent(headers http.Header) error {
|
||||
headers.Set("User-Agent", clientV1.UserAgent)
|
||||
return nil
|
||||
}
|
||||
|
||||
func addXQiniuDate(headers http.Header) error {
|
||||
if conf.IsDisableQiniuTimestampSignature() {
|
||||
return nil
|
||||
}
|
||||
|
||||
timeString := time.Now().UTC().Format("20060102T150405Z")
|
||||
headers.Set("X-Qiniu-Date", timeString)
|
||||
return nil
|
||||
}
|
123
vendor/github.com/qiniu/go-sdk/v7/internal/clientv2/interceptor_retry_hosts.go
generated
vendored
123
vendor/github.com/qiniu/go-sdk/v7/internal/clientv2/interceptor_retry_hosts.go
generated
vendored
182
vendor/github.com/qiniu/go-sdk/v7/internal/clientv2/interceptor_retry_simple.go
generated
vendored
182
vendor/github.com/qiniu/go-sdk/v7/internal/clientv2/interceptor_retry_simple.go
generated
vendored
@ -0,0 +1,182 @@
|
||||
package clientv2
|
||||
|
||||
import (
|
||||
clientv1 "github.com/qiniu/go-sdk/v7/client"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
type RetryConfig struct {
|
||||
RetryMax int // 最大重试次数
|
||||
RetryInterval func() time.Duration // 重试时间间隔
|
||||
ShouldRetry func(req *http.Request, resp *http.Response, err error) bool
|
||||
}
|
||||
|
||||
func (c *RetryConfig) init() {
|
||||
if c == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if c.RetryMax < 0 {
|
||||
c.RetryMax = 0
|
||||
}
|
||||
|
||||
if c.RetryInterval == nil {
|
||||
c.RetryInterval = func() time.Duration {
|
||||
return time.Duration(50+rand.Int()%50) * time.Millisecond
|
||||
}
|
||||
}
|
||||
|
||||
if c.ShouldRetry == nil {
|
||||
c.ShouldRetry = func(req *http.Request, resp *http.Response, err error) bool {
|
||||
return isSimpleRetryable(req, resp, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type simpleRetryInterceptor struct {
|
||||
config RetryConfig
|
||||
}
|
||||
|
||||
func NewSimpleRetryInterceptor(config RetryConfig) Interceptor {
|
||||
return &simpleRetryInterceptor{
|
||||
config: config,
|
||||
}
|
||||
}
|
||||
|
||||
func (interceptor *simpleRetryInterceptor) Priority() InterceptorPriority {
|
||||
return InterceptorPriorityRetrySimple
|
||||
}
|
||||
|
||||
func (interceptor *simpleRetryInterceptor) Intercept(req *http.Request, handler Handler) (resp *http.Response, err error) {
|
||||
if interceptor == nil || req == nil {
|
||||
return handler(req)
|
||||
}
|
||||
|
||||
interceptor.config.init()
|
||||
|
||||
// 不重试
|
||||
if interceptor.config.RetryMax <= 0 {
|
||||
return handler(req)
|
||||
}
|
||||
|
||||
// 可能会被重试多次
|
||||
for i := 0; ; i++ {
|
||||
// Clone 防止后面 Handler 处理对 req 有污染
|
||||
reqBefore := cloneReq(req.Context(), req)
|
||||
resp, err = handler(req)
|
||||
|
||||
if !interceptor.config.ShouldRetry(reqBefore, resp, err) {
|
||||
return resp, err
|
||||
}
|
||||
req = reqBefore
|
||||
|
||||
if i >= interceptor.config.RetryMax {
|
||||
break
|
||||
}
|
||||
|
||||
retryInterval := interceptor.config.RetryInterval()
|
||||
if retryInterval < time.Microsecond {
|
||||
continue
|
||||
}
|
||||
time.Sleep(retryInterval)
|
||||
}
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func isSimpleRetryable(req *http.Request, resp *http.Response, err error) bool {
|
||||
return isRequestRetryable(req) && (isResponseRetryable(resp) || IsErrorRetryable(err))
|
||||
}
|
||||
|
||||
func isRequestRetryable(req *http.Request) bool {
|
||||
if req == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if req.Body == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
if req.GetBody != nil {
|
||||
b, err := req.GetBody()
|
||||
if err != nil || b == nil {
|
||||
return false
|
||||
}
|
||||
req.Body = b
|
||||
return true
|
||||
}
|
||||
|
||||
seeker, ok := req.Body.(io.Seeker)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
_, err := seeker.Seek(0, io.SeekStart)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func isResponseRetryable(resp *http.Response) bool {
|
||||
if resp == nil {
|
||||
return false
|
||||
}
|
||||
return isStatusCodeRetryable(resp.StatusCode)
|
||||
}
|
||||
|
||||
func isStatusCodeRetryable(statusCode int) bool {
|
||||
if statusCode < 500 {
|
||||
return false
|
||||
}
|
||||
|
||||
if statusCode == 501 || statusCode == 509 || statusCode == 573 || statusCode == 579 ||
|
||||
statusCode == 608 || statusCode == 612 || statusCode == 614 || statusCode == 616 || statusCode == 618 ||
|
||||
statusCode == 630 || statusCode == 631 || statusCode == 632 || statusCode == 640 || statusCode == 701 {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func IsErrorRetryable(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
switch t := err.(type) {
|
||||
case *net.OpError:
|
||||
return isNetworkErrorWithOpError(t)
|
||||
case *url.Error:
|
||||
return IsErrorRetryable(t.Err)
|
||||
case net.Error:
|
||||
return t.Timeout()
|
||||
case *clientv1.ErrorInfo:
|
||||
return isStatusCodeRetryable(t.Code)
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func isNetworkErrorWithOpError(err *net.OpError) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
switch t := err.Err.(type) {
|
||||
case *net.DNSError:
|
||||
return true
|
||||
case *os.SyscallError:
|
||||
if errno, ok := t.Err.(syscall.Errno); ok {
|
||||
return errno == syscall.ECONNABORTED ||
|
||||
errno == syscall.ECONNRESET ||
|
||||
errno == syscall.ECONNREFUSED ||
|
||||
errno == syscall.ETIMEDOUT
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
package clientv2
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
const (
|
||||
RequestMethodGet = "GET"
|
||||
RequestMethodPut = "PUT"
|
||||
RequestMethodPost = "POST"
|
||||
RequestMethodHead = "HEAD"
|
||||
RequestMethodDelete = "DELETE"
|
||||
)
|
||||
|
||||
type RequestBodyCreator func(options *RequestParams) (io.Reader, error)
|
||||
|
||||
func RequestBodyCreatorOfJson(object interface{}) RequestBodyCreator {
|
||||
body := object
|
||||
return func(o *RequestParams) (io.Reader, error) {
|
||||
reqBody, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
o.Header.Add("Content-Type", "application/json")
|
||||
return bytes.NewReader(reqBody), nil
|
||||
}
|
||||
}
|
||||
|
||||
func RequestBodyCreatorForm(info map[string][]string) RequestBodyCreator {
|
||||
body := FormStringInfo(info)
|
||||
return func(o *RequestParams) (io.Reader, error) {
|
||||
o.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
||||
return bytes.NewBufferString(body), nil
|
||||
}
|
||||
}
|
||||
|
||||
func FormStringInfo(info map[string][]string) string {
|
||||
if len(info) == 0 {
|
||||
return ""
|
||||
}
|
||||
return url.Values(info).Encode()
|
||||
}
|
||||
|
||||
type RequestParams struct {
|
||||
Context context.Context
|
||||
Method string
|
||||
Url string
|
||||
Header http.Header
|
||||
BodyCreator RequestBodyCreator
|
||||
}
|
||||
|
||||
func (o *RequestParams) init() {
|
||||
if o.Context == nil {
|
||||
o.Context = context.Background()
|
||||
}
|
||||
|
||||
if len(o.Method) == 0 {
|
||||
o.Method = RequestMethodGet
|
||||
}
|
||||
|
||||
if o.Header == nil {
|
||||
o.Header = http.Header{}
|
||||
}
|
||||
|
||||
if o.BodyCreator == nil {
|
||||
o.BodyCreator = func(options *RequestParams) (io.Reader, error) {
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func NewRequest(options RequestParams) (*http.Request, error) {
|
||||
options.init()
|
||||
|
||||
body, cErr := options.BodyCreator(&options)
|
||||
if cErr != nil {
|
||||
return nil, cErr
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(options.Method, options.Url, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req = req.WithContext(options.Context)
|
||||
req.Header = options.Header
|
||||
return req, nil
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue