mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-12-16 00:41:24 +01:00
589 lines
13 KiB
Go
589 lines
13 KiB
Go
|
package fasthttp
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"errors"
|
||
|
"io"
|
||
|
"sort"
|
||
|
"sync"
|
||
|
|
||
|
"github.com/valyala/bytebufferpool"
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
argsNoValue = true
|
||
|
argsHasValue = false
|
||
|
)
|
||
|
|
||
|
// AcquireArgs returns an empty Args object from the pool.
|
||
|
//
|
||
|
// The returned Args may be returned to the pool with ReleaseArgs
|
||
|
// when no longer needed. This allows reducing GC load.
|
||
|
func AcquireArgs() *Args {
|
||
|
return argsPool.Get().(*Args)
|
||
|
}
|
||
|
|
||
|
// ReleaseArgs returns the object acquired via AcquireArgs to the pool.
|
||
|
//
|
||
|
// Do not access the released Args object, otherwise data races may occur.
|
||
|
func ReleaseArgs(a *Args) {
|
||
|
a.Reset()
|
||
|
argsPool.Put(a)
|
||
|
}
|
||
|
|
||
|
var argsPool = &sync.Pool{
|
||
|
New: func() interface{} {
|
||
|
return &Args{}
|
||
|
},
|
||
|
}
|
||
|
|
||
|
// Args represents query arguments.
|
||
|
//
|
||
|
// It is forbidden copying Args instances. Create new instances instead
|
||
|
// and use CopyTo().
|
||
|
//
|
||
|
// Args instance MUST NOT be used from concurrently running goroutines.
|
||
|
type Args struct {
|
||
|
noCopy noCopy //nolint:unused,structcheck
|
||
|
|
||
|
args []argsKV
|
||
|
buf []byte
|
||
|
}
|
||
|
|
||
|
type argsKV struct {
|
||
|
key []byte
|
||
|
value []byte
|
||
|
noValue bool
|
||
|
}
|
||
|
|
||
|
// Reset clears query args.
|
||
|
func (a *Args) Reset() {
|
||
|
a.args = a.args[:0]
|
||
|
}
|
||
|
|
||
|
// CopyTo copies all args to dst.
|
||
|
func (a *Args) CopyTo(dst *Args) {
|
||
|
dst.Reset()
|
||
|
dst.args = copyArgs(dst.args, a.args)
|
||
|
}
|
||
|
|
||
|
// VisitAll calls f for each existing arg.
|
||
|
//
|
||
|
// f must not retain references to key and value after returning.
|
||
|
// Make key and/or value copies if you need storing them after returning.
|
||
|
func (a *Args) VisitAll(f func(key, value []byte)) {
|
||
|
visitArgs(a.args, f)
|
||
|
}
|
||
|
|
||
|
// Len returns the number of query args.
|
||
|
func (a *Args) Len() int {
|
||
|
return len(a.args)
|
||
|
}
|
||
|
|
||
|
// Parse parses the given string containing query args.
|
||
|
func (a *Args) Parse(s string) {
|
||
|
a.buf = append(a.buf[:0], s...)
|
||
|
a.ParseBytes(a.buf)
|
||
|
}
|
||
|
|
||
|
// ParseBytes parses the given b containing query args.
|
||
|
func (a *Args) ParseBytes(b []byte) {
|
||
|
a.Reset()
|
||
|
|
||
|
var s argsScanner
|
||
|
s.b = b
|
||
|
|
||
|
var kv *argsKV
|
||
|
a.args, kv = allocArg(a.args)
|
||
|
for s.next(kv) {
|
||
|
if len(kv.key) > 0 || len(kv.value) > 0 {
|
||
|
a.args, kv = allocArg(a.args)
|
||
|
}
|
||
|
}
|
||
|
a.args = releaseArg(a.args)
|
||
|
}
|
||
|
|
||
|
// String returns string representation of query args.
|
||
|
func (a *Args) String() string {
|
||
|
return string(a.QueryString())
|
||
|
}
|
||
|
|
||
|
// QueryString returns query string for the args.
|
||
|
//
|
||
|
// The returned value is valid until the next call to Args methods.
|
||
|
func (a *Args) QueryString() []byte {
|
||
|
a.buf = a.AppendBytes(a.buf[:0])
|
||
|
return a.buf
|
||
|
}
|
||
|
|
||
|
// Sort sorts Args by key and then value using 'f' as comparison function.
|
||
|
//
|
||
|
// For example args.Sort(bytes.Compare)
|
||
|
func (a *Args) Sort(f func(x, y []byte) int) {
|
||
|
sort.SliceStable(a.args, func(i, j int) bool {
|
||
|
n := f(a.args[i].key, a.args[j].key)
|
||
|
if n == 0 {
|
||
|
return f(a.args[i].value, a.args[j].value) == -1
|
||
|
}
|
||
|
return n == -1
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// AppendBytes appends query string to dst and returns the extended dst.
|
||
|
func (a *Args) AppendBytes(dst []byte) []byte {
|
||
|
for i, n := 0, len(a.args); i < n; i++ {
|
||
|
kv := &a.args[i]
|
||
|
dst = AppendQuotedArg(dst, kv.key)
|
||
|
if !kv.noValue {
|
||
|
dst = append(dst, '=')
|
||
|
if len(kv.value) > 0 {
|
||
|
dst = AppendQuotedArg(dst, kv.value)
|
||
|
}
|
||
|
}
|
||
|
if i+1 < n {
|
||
|
dst = append(dst, '&')
|
||
|
}
|
||
|
}
|
||
|
return dst
|
||
|
}
|
||
|
|
||
|
// WriteTo writes query string to w.
|
||
|
//
|
||
|
// WriteTo implements io.WriterTo interface.
|
||
|
func (a *Args) WriteTo(w io.Writer) (int64, error) {
|
||
|
n, err := w.Write(a.QueryString())
|
||
|
return int64(n), err
|
||
|
}
|
||
|
|
||
|
// Del deletes argument with the given key from query args.
|
||
|
func (a *Args) Del(key string) {
|
||
|
a.args = delAllArgs(a.args, key)
|
||
|
}
|
||
|
|
||
|
// DelBytes deletes argument with the given key from query args.
|
||
|
func (a *Args) DelBytes(key []byte) {
|
||
|
a.args = delAllArgs(a.args, b2s(key))
|
||
|
}
|
||
|
|
||
|
// Add adds 'key=value' argument.
|
||
|
//
|
||
|
// Multiple values for the same key may be added.
|
||
|
func (a *Args) Add(key, value string) {
|
||
|
a.args = appendArg(a.args, key, value, argsHasValue)
|
||
|
}
|
||
|
|
||
|
// AddBytesK adds 'key=value' argument.
|
||
|
//
|
||
|
// Multiple values for the same key may be added.
|
||
|
func (a *Args) AddBytesK(key []byte, value string) {
|
||
|
a.args = appendArg(a.args, b2s(key), value, argsHasValue)
|
||
|
}
|
||
|
|
||
|
// AddBytesV adds 'key=value' argument.
|
||
|
//
|
||
|
// Multiple values for the same key may be added.
|
||
|
func (a *Args) AddBytesV(key string, value []byte) {
|
||
|
a.args = appendArg(a.args, key, b2s(value), argsHasValue)
|
||
|
}
|
||
|
|
||
|
// AddBytesKV adds 'key=value' argument.
|
||
|
//
|
||
|
// Multiple values for the same key may be added.
|
||
|
func (a *Args) AddBytesKV(key, value []byte) {
|
||
|
a.args = appendArg(a.args, b2s(key), b2s(value), argsHasValue)
|
||
|
}
|
||
|
|
||
|
// AddNoValue adds only 'key' as argument without the '='.
|
||
|
//
|
||
|
// Multiple values for the same key may be added.
|
||
|
func (a *Args) AddNoValue(key string) {
|
||
|
a.args = appendArg(a.args, key, "", argsNoValue)
|
||
|
}
|
||
|
|
||
|
// AddBytesKNoValue adds only 'key' as argument without the '='.
|
||
|
//
|
||
|
// Multiple values for the same key may be added.
|
||
|
func (a *Args) AddBytesKNoValue(key []byte) {
|
||
|
a.args = appendArg(a.args, b2s(key), "", argsNoValue)
|
||
|
}
|
||
|
|
||
|
// Set sets 'key=value' argument.
|
||
|
func (a *Args) Set(key, value string) {
|
||
|
a.args = setArg(a.args, key, value, argsHasValue)
|
||
|
}
|
||
|
|
||
|
// SetBytesK sets 'key=value' argument.
|
||
|
func (a *Args) SetBytesK(key []byte, value string) {
|
||
|
a.args = setArg(a.args, b2s(key), value, argsHasValue)
|
||
|
}
|
||
|
|
||
|
// SetBytesV sets 'key=value' argument.
|
||
|
func (a *Args) SetBytesV(key string, value []byte) {
|
||
|
a.args = setArg(a.args, key, b2s(value), argsHasValue)
|
||
|
}
|
||
|
|
||
|
// SetBytesKV sets 'key=value' argument.
|
||
|
func (a *Args) SetBytesKV(key, value []byte) {
|
||
|
a.args = setArgBytes(a.args, key, value, argsHasValue)
|
||
|
}
|
||
|
|
||
|
// SetNoValue sets only 'key' as argument without the '='.
|
||
|
//
|
||
|
// Only key in argumemt, like key1&key2
|
||
|
func (a *Args) SetNoValue(key string) {
|
||
|
a.args = setArg(a.args, key, "", argsNoValue)
|
||
|
}
|
||
|
|
||
|
// SetBytesKNoValue sets 'key' argument.
|
||
|
func (a *Args) SetBytesKNoValue(key []byte) {
|
||
|
a.args = setArg(a.args, b2s(key), "", argsNoValue)
|
||
|
}
|
||
|
|
||
|
// Peek returns query arg value for the given key.
|
||
|
//
|
||
|
// Returned value is valid until the next Args call.
|
||
|
func (a *Args) Peek(key string) []byte {
|
||
|
return peekArgStr(a.args, key)
|
||
|
}
|
||
|
|
||
|
// PeekBytes returns query arg value for the given key.
|
||
|
//
|
||
|
// Returned value is valid until the next Args call.
|
||
|
func (a *Args) PeekBytes(key []byte) []byte {
|
||
|
return peekArgBytes(a.args, key)
|
||
|
}
|
||
|
|
||
|
// PeekMulti returns all the arg values for the given key.
|
||
|
func (a *Args) PeekMulti(key string) [][]byte {
|
||
|
var values [][]byte
|
||
|
a.VisitAll(func(k, v []byte) {
|
||
|
if string(k) == key {
|
||
|
values = append(values, v)
|
||
|
}
|
||
|
})
|
||
|
return values
|
||
|
}
|
||
|
|
||
|
// PeekMultiBytes returns all the arg values for the given key.
|
||
|
func (a *Args) PeekMultiBytes(key []byte) [][]byte {
|
||
|
return a.PeekMulti(b2s(key))
|
||
|
}
|
||
|
|
||
|
// Has returns true if the given key exists in Args.
|
||
|
func (a *Args) Has(key string) bool {
|
||
|
return hasArg(a.args, key)
|
||
|
}
|
||
|
|
||
|
// HasBytes returns true if the given key exists in Args.
|
||
|
func (a *Args) HasBytes(key []byte) bool {
|
||
|
return hasArg(a.args, b2s(key))
|
||
|
}
|
||
|
|
||
|
// ErrNoArgValue is returned when Args value with the given key is missing.
|
||
|
var ErrNoArgValue = errors.New("no Args value for the given key")
|
||
|
|
||
|
// GetUint returns uint value for the given key.
|
||
|
func (a *Args) GetUint(key string) (int, error) {
|
||
|
value := a.Peek(key)
|
||
|
if len(value) == 0 {
|
||
|
return -1, ErrNoArgValue
|
||
|
}
|
||
|
return ParseUint(value)
|
||
|
}
|
||
|
|
||
|
// SetUint sets uint value for the given key.
|
||
|
func (a *Args) SetUint(key string, value int) {
|
||
|
bb := bytebufferpool.Get()
|
||
|
bb.B = AppendUint(bb.B[:0], value)
|
||
|
a.SetBytesV(key, bb.B)
|
||
|
bytebufferpool.Put(bb)
|
||
|
}
|
||
|
|
||
|
// SetUintBytes sets uint value for the given key.
|
||
|
func (a *Args) SetUintBytes(key []byte, value int) {
|
||
|
a.SetUint(b2s(key), value)
|
||
|
}
|
||
|
|
||
|
// GetUintOrZero returns uint value for the given key.
|
||
|
//
|
||
|
// Zero (0) is returned on error.
|
||
|
func (a *Args) GetUintOrZero(key string) int {
|
||
|
n, err := a.GetUint(key)
|
||
|
if err != nil {
|
||
|
n = 0
|
||
|
}
|
||
|
return n
|
||
|
}
|
||
|
|
||
|
// GetUfloat returns ufloat value for the given key.
|
||
|
func (a *Args) GetUfloat(key string) (float64, error) {
|
||
|
value := a.Peek(key)
|
||
|
if len(value) == 0 {
|
||
|
return -1, ErrNoArgValue
|
||
|
}
|
||
|
return ParseUfloat(value)
|
||
|
}
|
||
|
|
||
|
// GetUfloatOrZero returns ufloat value for the given key.
|
||
|
//
|
||
|
// Zero (0) is returned on error.
|
||
|
func (a *Args) GetUfloatOrZero(key string) float64 {
|
||
|
f, err := a.GetUfloat(key)
|
||
|
if err != nil {
|
||
|
f = 0
|
||
|
}
|
||
|
return f
|
||
|
}
|
||
|
|
||
|
// GetBool returns boolean value for the given key.
|
||
|
//
|
||
|
// true is returned for "1", "t", "T", "true", "TRUE", "True", "y", "yes", "Y", "YES", "Yes",
|
||
|
// otherwise false is returned.
|
||
|
func (a *Args) GetBool(key string) bool {
|
||
|
switch b2s(a.Peek(key)) {
|
||
|
// Support the same true cases as strconv.ParseBool
|
||
|
// See: https://github.com/golang/go/blob/4e1b11e2c9bdb0ddea1141eed487be1a626ff5be/src/strconv/atob.go#L12
|
||
|
// and Y and Yes versions.
|
||
|
case "1", "t", "T", "true", "TRUE", "True", "y", "yes", "Y", "YES", "Yes":
|
||
|
return true
|
||
|
default:
|
||
|
return false
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func visitArgs(args []argsKV, f func(k, v []byte)) {
|
||
|
for i, n := 0, len(args); i < n; i++ {
|
||
|
kv := &args[i]
|
||
|
f(kv.key, kv.value)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func copyArgs(dst, src []argsKV) []argsKV {
|
||
|
if cap(dst) < len(src) {
|
||
|
tmp := make([]argsKV, len(src))
|
||
|
copy(tmp, dst)
|
||
|
dst = tmp
|
||
|
}
|
||
|
n := len(src)
|
||
|
dst = dst[:n]
|
||
|
for i := 0; i < n; i++ {
|
||
|
dstKV := &dst[i]
|
||
|
srcKV := &src[i]
|
||
|
dstKV.key = append(dstKV.key[:0], srcKV.key...)
|
||
|
if srcKV.noValue {
|
||
|
dstKV.value = dstKV.value[:0]
|
||
|
} else {
|
||
|
dstKV.value = append(dstKV.value[:0], srcKV.value...)
|
||
|
}
|
||
|
dstKV.noValue = srcKV.noValue
|
||
|
}
|
||
|
return dst
|
||
|
}
|
||
|
|
||
|
func delAllArgsBytes(args []argsKV, key []byte) []argsKV {
|
||
|
return delAllArgs(args, b2s(key))
|
||
|
}
|
||
|
|
||
|
func delAllArgs(args []argsKV, key string) []argsKV {
|
||
|
for i, n := 0, len(args); i < n; i++ {
|
||
|
kv := &args[i]
|
||
|
if key == string(kv.key) {
|
||
|
tmp := *kv
|
||
|
copy(args[i:], args[i+1:])
|
||
|
n--
|
||
|
args[n] = tmp
|
||
|
args = args[:n]
|
||
|
}
|
||
|
}
|
||
|
return args
|
||
|
}
|
||
|
|
||
|
func setArgBytes(h []argsKV, key, value []byte, noValue bool) []argsKV {
|
||
|
return setArg(h, b2s(key), b2s(value), noValue)
|
||
|
}
|
||
|
|
||
|
func setArg(h []argsKV, key, value string, noValue bool) []argsKV {
|
||
|
n := len(h)
|
||
|
for i := 0; i < n; i++ {
|
||
|
kv := &h[i]
|
||
|
if key == string(kv.key) {
|
||
|
if noValue {
|
||
|
kv.value = kv.value[:0]
|
||
|
} else {
|
||
|
kv.value = append(kv.value[:0], value...)
|
||
|
}
|
||
|
kv.noValue = noValue
|
||
|
return h
|
||
|
}
|
||
|
}
|
||
|
return appendArg(h, key, value, noValue)
|
||
|
}
|
||
|
|
||
|
func appendArgBytes(h []argsKV, key, value []byte, noValue bool) []argsKV {
|
||
|
return appendArg(h, b2s(key), b2s(value), noValue)
|
||
|
}
|
||
|
|
||
|
func appendArg(args []argsKV, key, value string, noValue bool) []argsKV {
|
||
|
var kv *argsKV
|
||
|
args, kv = allocArg(args)
|
||
|
kv.key = append(kv.key[:0], key...)
|
||
|
if noValue {
|
||
|
kv.value = kv.value[:0]
|
||
|
} else {
|
||
|
kv.value = append(kv.value[:0], value...)
|
||
|
}
|
||
|
kv.noValue = noValue
|
||
|
return args
|
||
|
}
|
||
|
|
||
|
func allocArg(h []argsKV) ([]argsKV, *argsKV) {
|
||
|
n := len(h)
|
||
|
if cap(h) > n {
|
||
|
h = h[:n+1]
|
||
|
} else {
|
||
|
h = append(h, argsKV{})
|
||
|
}
|
||
|
return h, &h[n]
|
||
|
}
|
||
|
|
||
|
func releaseArg(h []argsKV) []argsKV {
|
||
|
return h[:len(h)-1]
|
||
|
}
|
||
|
|
||
|
func hasArg(h []argsKV, key string) bool {
|
||
|
for i, n := 0, len(h); i < n; i++ {
|
||
|
kv := &h[i]
|
||
|
if key == string(kv.key) {
|
||
|
return true
|
||
|
}
|
||
|
}
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
func peekArgBytes(h []argsKV, k []byte) []byte {
|
||
|
for i, n := 0, len(h); i < n; i++ {
|
||
|
kv := &h[i]
|
||
|
if bytes.Equal(kv.key, k) {
|
||
|
return kv.value
|
||
|
}
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func peekArgStr(h []argsKV, k string) []byte {
|
||
|
for i, n := 0, len(h); i < n; i++ {
|
||
|
kv := &h[i]
|
||
|
if string(kv.key) == k {
|
||
|
return kv.value
|
||
|
}
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
type argsScanner struct {
|
||
|
b []byte
|
||
|
}
|
||
|
|
||
|
func (s *argsScanner) next(kv *argsKV) bool {
|
||
|
if len(s.b) == 0 {
|
||
|
return false
|
||
|
}
|
||
|
kv.noValue = argsHasValue
|
||
|
|
||
|
isKey := true
|
||
|
k := 0
|
||
|
for i, c := range s.b {
|
||
|
switch c {
|
||
|
case '=':
|
||
|
if isKey {
|
||
|
isKey = false
|
||
|
kv.key = decodeArgAppend(kv.key[:0], s.b[:i])
|
||
|
k = i + 1
|
||
|
}
|
||
|
case '&':
|
||
|
if isKey {
|
||
|
kv.key = decodeArgAppend(kv.key[:0], s.b[:i])
|
||
|
kv.value = kv.value[:0]
|
||
|
kv.noValue = argsNoValue
|
||
|
} else {
|
||
|
kv.value = decodeArgAppend(kv.value[:0], s.b[k:i])
|
||
|
}
|
||
|
s.b = s.b[i+1:]
|
||
|
return true
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if isKey {
|
||
|
kv.key = decodeArgAppend(kv.key[:0], s.b)
|
||
|
kv.value = kv.value[:0]
|
||
|
kv.noValue = argsNoValue
|
||
|
} else {
|
||
|
kv.value = decodeArgAppend(kv.value[:0], s.b[k:])
|
||
|
}
|
||
|
s.b = s.b[len(s.b):]
|
||
|
return true
|
||
|
}
|
||
|
|
||
|
func decodeArgAppend(dst, src []byte) []byte {
|
||
|
if bytes.IndexByte(src, '%') < 0 && bytes.IndexByte(src, '+') < 0 {
|
||
|
// fast path: src doesn't contain encoded chars
|
||
|
return append(dst, src...)
|
||
|
}
|
||
|
|
||
|
// slow path
|
||
|
for i := 0; i < len(src); i++ {
|
||
|
c := src[i]
|
||
|
if c == '%' {
|
||
|
if i+2 >= len(src) {
|
||
|
return append(dst, src[i:]...)
|
||
|
}
|
||
|
x2 := hex2intTable[src[i+2]]
|
||
|
x1 := hex2intTable[src[i+1]]
|
||
|
if x1 == 16 || x2 == 16 {
|
||
|
dst = append(dst, '%')
|
||
|
} else {
|
||
|
dst = append(dst, x1<<4|x2)
|
||
|
i += 2
|
||
|
}
|
||
|
} else if c == '+' {
|
||
|
dst = append(dst, ' ')
|
||
|
} else {
|
||
|
dst = append(dst, c)
|
||
|
}
|
||
|
}
|
||
|
return dst
|
||
|
}
|
||
|
|
||
|
// decodeArgAppendNoPlus is almost identical to decodeArgAppend, but it doesn't
|
||
|
// substitute '+' with ' '.
|
||
|
//
|
||
|
// The function is copy-pasted from decodeArgAppend due to the performance
|
||
|
// reasons only.
|
||
|
func decodeArgAppendNoPlus(dst, src []byte) []byte {
|
||
|
if bytes.IndexByte(src, '%') < 0 {
|
||
|
// fast path: src doesn't contain encoded chars
|
||
|
return append(dst, src...)
|
||
|
}
|
||
|
|
||
|
// slow path
|
||
|
for i := 0; i < len(src); i++ {
|
||
|
c := src[i]
|
||
|
if c == '%' {
|
||
|
if i+2 >= len(src) {
|
||
|
return append(dst, src[i:]...)
|
||
|
}
|
||
|
x2 := hex2intTable[src[i+2]]
|
||
|
x1 := hex2intTable[src[i+1]]
|
||
|
if x1 == 16 || x2 == 16 {
|
||
|
dst = append(dst, '%')
|
||
|
} else {
|
||
|
dst = append(dst, x1<<4|x2)
|
||
|
i += 2
|
||
|
}
|
||
|
} else {
|
||
|
dst = append(dst, c)
|
||
|
}
|
||
|
}
|
||
|
return dst
|
||
|
}
|