Remove vendoring

Signed-off-by: Drasko Draskovic <drasko.draskovic@gmail.com>
This commit is contained in:
Drasko Draskovic
2023-10-19 17:31:55 +02:00
parent 91c9274885
commit 96e3c48fcb
1456 changed files with 0 additions and 549232 deletions
-20
View File
@@ -1,20 +0,0 @@
Copyright (C) 2013 Blake Mizerany
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.
File diff suppressed because it is too large Load Diff
-316
View File
@@ -1,316 +0,0 @@
// Package quantile computes approximate quantiles over an unbounded data
// stream within low memory and CPU bounds.
//
// A small amount of accuracy is traded to achieve the above properties.
//
// Multiple streams can be merged before calling Query to generate a single set
// of results. This is meaningful when the streams represent the same type of
// data. See Merge and Samples.
//
// For more detailed information about the algorithm used, see:
//
// Effective Computation of Biased Quantiles over Data Streams
//
// http://www.cs.rutgers.edu/~muthu/bquant.pdf
package quantile
import (
"math"
"sort"
)
// Sample holds an observed value and meta information for compression. JSON
// tags have been added for convenience.
type Sample struct {
Value float64 `json:",string"`
Width float64 `json:",string"`
Delta float64 `json:",string"`
}
// Samples represents a slice of samples. It implements sort.Interface.
type Samples []Sample
func (a Samples) Len() int { return len(a) }
func (a Samples) Less(i, j int) bool { return a[i].Value < a[j].Value }
func (a Samples) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
type invariant func(s *stream, r float64) float64
// NewLowBiased returns an initialized Stream for low-biased quantiles
// (e.g. 0.01, 0.1, 0.5) where the needed quantiles are not known a priori, but
// error guarantees can still be given even for the lower ranks of the data
// distribution.
//
// The provided epsilon is a relative error, i.e. the true quantile of a value
// returned by a query is guaranteed to be within (1±Epsilon)*Quantile.
//
// See http://www.cs.rutgers.edu/~muthu/bquant.pdf for time, space, and error
// properties.
func NewLowBiased(epsilon float64) *Stream {
ƒ := func(s *stream, r float64) float64 {
return 2 * epsilon * r
}
return newStream(ƒ)
}
// NewHighBiased returns an initialized Stream for high-biased quantiles
// (e.g. 0.01, 0.1, 0.5) where the needed quantiles are not known a priori, but
// error guarantees can still be given even for the higher ranks of the data
// distribution.
//
// The provided epsilon is a relative error, i.e. the true quantile of a value
// returned by a query is guaranteed to be within 1-(1±Epsilon)*(1-Quantile).
//
// See http://www.cs.rutgers.edu/~muthu/bquant.pdf for time, space, and error
// properties.
func NewHighBiased(epsilon float64) *Stream {
ƒ := func(s *stream, r float64) float64 {
return 2 * epsilon * (s.n - r)
}
return newStream(ƒ)
}
// NewTargeted returns an initialized Stream concerned with a particular set of
// quantile values that are supplied a priori. Knowing these a priori reduces
// space and computation time. The targets map maps the desired quantiles to
// their absolute errors, i.e. the true quantile of a value returned by a query
// is guaranteed to be within (Quantile±Epsilon).
//
// See http://www.cs.rutgers.edu/~muthu/bquant.pdf for time, space, and error properties.
func NewTargeted(targetMap map[float64]float64) *Stream {
// Convert map to slice to avoid slow iterations on a map.
// ƒ is called on the hot path, so converting the map to a slice
// beforehand results in significant CPU savings.
targets := targetMapToSlice(targetMap)
ƒ := func(s *stream, r float64) float64 {
var m = math.MaxFloat64
var f float64
for _, t := range targets {
if t.quantile*s.n <= r {
f = (2 * t.epsilon * r) / t.quantile
} else {
f = (2 * t.epsilon * (s.n - r)) / (1 - t.quantile)
}
if f < m {
m = f
}
}
return m
}
return newStream(ƒ)
}
type target struct {
quantile float64
epsilon float64
}
func targetMapToSlice(targetMap map[float64]float64) []target {
targets := make([]target, 0, len(targetMap))
for quantile, epsilon := range targetMap {
t := target{
quantile: quantile,
epsilon: epsilon,
}
targets = append(targets, t)
}
return targets
}
// Stream computes quantiles for a stream of float64s. It is not thread-safe by
// design. Take care when using across multiple goroutines.
type Stream struct {
*stream
b Samples
sorted bool
}
func newStream(ƒ invariant) *Stream {
x := &stream{ƒ: ƒ}
return &Stream{x, make(Samples, 0, 500), true}
}
// Insert inserts v into the stream.
func (s *Stream) Insert(v float64) {
s.insert(Sample{Value: v, Width: 1})
}
func (s *Stream) insert(sample Sample) {
s.b = append(s.b, sample)
s.sorted = false
if len(s.b) == cap(s.b) {
s.flush()
}
}
// Query returns the computed qth percentiles value. If s was created with
// NewTargeted, and q is not in the set of quantiles provided a priori, Query
// will return an unspecified result.
func (s *Stream) Query(q float64) float64 {
if !s.flushed() {
// Fast path when there hasn't been enough data for a flush;
// this also yields better accuracy for small sets of data.
l := len(s.b)
if l == 0 {
return 0
}
i := int(math.Ceil(float64(l) * q))
if i > 0 {
i -= 1
}
s.maybeSort()
return s.b[i].Value
}
s.flush()
return s.stream.query(q)
}
// Merge merges samples into the underlying streams samples. This is handy when
// merging multiple streams from separate threads, database shards, etc.
//
// ATTENTION: This method is broken and does not yield correct results. The
// underlying algorithm is not capable of merging streams correctly.
func (s *Stream) Merge(samples Samples) {
sort.Sort(samples)
s.stream.merge(samples)
}
// Reset reinitializes and clears the list reusing the samples buffer memory.
func (s *Stream) Reset() {
s.stream.reset()
s.b = s.b[:0]
}
// Samples returns stream samples held by s.
func (s *Stream) Samples() Samples {
if !s.flushed() {
return s.b
}
s.flush()
return s.stream.samples()
}
// Count returns the total number of samples observed in the stream
// since initialization.
func (s *Stream) Count() int {
return len(s.b) + s.stream.count()
}
func (s *Stream) flush() {
s.maybeSort()
s.stream.merge(s.b)
s.b = s.b[:0]
}
func (s *Stream) maybeSort() {
if !s.sorted {
s.sorted = true
sort.Sort(s.b)
}
}
func (s *Stream) flushed() bool {
return len(s.stream.l) > 0
}
type stream struct {
n float64
l []Sample
ƒ invariant
}
func (s *stream) reset() {
s.l = s.l[:0]
s.n = 0
}
func (s *stream) insert(v float64) {
s.merge(Samples{{v, 1, 0}})
}
func (s *stream) merge(samples Samples) {
// TODO(beorn7): This tries to merge not only individual samples, but
// whole summaries. The paper doesn't mention merging summaries at
// all. Unittests show that the merging is inaccurate. Find out how to
// do merges properly.
var r float64
i := 0
for _, sample := range samples {
for ; i < len(s.l); i++ {
c := s.l[i]
if c.Value > sample.Value {
// Insert at position i.
s.l = append(s.l, Sample{})
copy(s.l[i+1:], s.l[i:])
s.l[i] = Sample{
sample.Value,
sample.Width,
math.Max(sample.Delta, math.Floor(s.ƒ(s, r))-1),
// TODO(beorn7): How to calculate delta correctly?
}
i++
goto inserted
}
r += c.Width
}
s.l = append(s.l, Sample{sample.Value, sample.Width, 0})
i++
inserted:
s.n += sample.Width
r += sample.Width
}
s.compress()
}
func (s *stream) count() int {
return int(s.n)
}
func (s *stream) query(q float64) float64 {
t := math.Ceil(q * s.n)
t += math.Ceil(s.ƒ(s, t) / 2)
p := s.l[0]
var r float64
for _, c := range s.l[1:] {
r += p.Width
if r+c.Width+c.Delta > t {
return p.Value
}
p = c
}
return p.Value
}
func (s *stream) compress() {
if len(s.l) < 2 {
return
}
x := s.l[len(s.l)-1]
xi := len(s.l) - 1
r := s.n - 1 - x.Width
for i := len(s.l) - 2; i >= 0; i-- {
c := s.l[i]
if c.Width+x.Width+x.Delta <= s.ƒ(s, r) {
x.Width += c.Width
s.l[xi] = x
// Remove element at i.
copy(s.l[i:], s.l[i+1:])
s.l = s.l[:len(s.l)-1]
xi -= 1
} else {
x = c
xi = i
}
r -= c.Width
}
}
func (s *stream) samples() Samples {
samples := make(Samples, len(s.l))
copy(samples, s.l)
return samples
}
-4
View File
@@ -1,4 +0,0 @@
coverage.txt
bin
card.png
dist
-8
View File
@@ -1,8 +0,0 @@
linters:
enable:
- thelper
- gofumpt
- tparallel
- unconvert
- unparam
- wastedassign
-3
View File
@@ -1,3 +0,0 @@
includes:
- from_url:
url: https://raw.githubusercontent.com/caarlos0/.goreleaserfiles/main/lib.yml
-21
View File
@@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) 2015-2022 Carlos Alexandro Becker
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.
-37
View File
@@ -1,37 +0,0 @@
SOURCE_FILES?=./...
TEST_PATTERN?=.
export GO111MODULE := on
setup:
go mod tidy
.PHONY: setup
build:
go build
.PHONY: build
test:
go test -v -failfast -race -coverpkg=./... -covermode=atomic -coverprofile=coverage.txt $(SOURCE_FILES) -run $(TEST_PATTERN) -timeout=2m
.PHONY: test
cover: test
go tool cover -html=coverage.txt
.PHONY: cover
fmt:
gofumpt -w -l .
.PHONY: fmt
lint:
golangci-lint run ./...
.PHONY: lint
ci: build test
.PHONY: ci
card:
wget -O card.png -c "https://og.caarlos0.dev/**env**: parse envs to structs.png?theme=light&md=1&fontSize=100px&images=https://github.com/caarlos0.png"
.PHONY: card
.DEFAULT_GOAL := ci
-551
View File
@@ -1,551 +0,0 @@
# env
[![Build Status](https://img.shields.io/github/actions/workflow/status/caarlos0/env/build.yml?branch=main&style=for-the-badge)](https://github.com/caarlos0/env/actions?workflow=build)
[![Coverage Status](https://img.shields.io/codecov/c/gh/caarlos0/env.svg?logo=codecov&style=for-the-badge)](https://codecov.io/gh/caarlos0/env)
[![](http://img.shields.io/badge/godoc-reference-5272B4.svg?style=for-the-badge)](https://pkg.go.dev/github.com/caarlos0/env/v7)
A simple and zero-dependencies library to parse environment variables into structs.
## Example
Get the module with:
```sh
go get github.com/caarlos0/env/v7
```
The usage looks like this:
```go
package main
import (
"fmt"
"time"
"github.com/caarlos0/env/v7"
)
type config struct {
Home string `env:"HOME"`
Port int `env:"PORT" envDefault:"3000"`
Password string `env:"PASSWORD,unset"`
IsProduction bool `env:"PRODUCTION"`
Hosts []string `env:"HOSTS" envSeparator:":"`
Duration time.Duration `env:"DURATION"`
TempFolder string `env:"TEMP_FOLDER" envDefault:"${HOME}/tmp" envExpand:"true"`
}
func main() {
cfg := config{}
if err := env.Parse(&cfg); err != nil {
fmt.Printf("%+v\n", err)
}
fmt.Printf("%+v\n", cfg)
}
```
You can run it like this:
```sh
$ PRODUCTION=true HOSTS="host1:host2:host3" DURATION=1s go run main.go
{Home:/your/home Port:3000 IsProduction:true Hosts:[host1 host2 host3] Duration:1s}
```
## Caveats
> **Warning**
>
> **This is important!**
- _Unexported fields_ are **ignored**
## Supported types and defaults
Out of the box all built-in types are supported, plus a few others that
are commonly used.
Complete list:
- `string`
- `bool`
- `int`
- `int8`
- `int16`
- `int32`
- `int64`
- `uint`
- `uint8`
- `uint16`
- `uint32`
- `uint64`
- `float32`
- `float64`
- `time.Duration`
- `encoding.TextUnmarshaler`
- `url.URL`
Pointers, slices and slices of pointers, and maps of those types are also
supported.
You can also use/define a [custom parser func](#custom-parser-funcs) for any
other type you want.
You can also use custom keys and values in your maps, as long as you provide a
parser function for them.
If you set the `envDefault` tag for something, this value will be used in the
case of absence of it in the environment.
By default, slice types will split the environment value on `,`; you can change
this behavior by setting the `envSeparator` tag.
If you set the `envExpand` tag, environment variables (either in `${var}` or
`$var` format) in the string will be replaced according with the actual value
of the variable.
## Custom Parser Funcs
If you have a type that is not supported out of the box by the lib, you are able
to use (or define) and pass custom parsers (and their associated `reflect.Type`)
to the `env.ParseWithFuncs()` function.
In addition to accepting a struct pointer (same as `Parse()`), this function
also accepts a `map[reflect.Type]env.ParserFunc`.
If you add a custom parser for, say `Foo`, it will also be used to parse
`*Foo` and `[]Foo` types.
Check the examples in the [go doc](http://pkg.go.dev/github.com/caarlos0/env/v7)
for more info.
### A note about `TextUnmarshaler` and `time.Time`
Env supports by default anything that implements the `TextUnmarshaler` interface.
That includes things like `time.Time` for example.
The upside is that depending on the format you need, you don't need to change anything.
The downside is that if you do need time in another format, you'll need to create your own type.
Its fairly straightforward:
```go
type MyTime time.Time
func (t *MyTime) UnmarshalText(text []byte) error {
tt, err := time.Parse("2006-01-02", string(text))
*t = MyTime(tt)
return err
}
type Config struct {
SomeTime MyTime `env:"SOME_TIME"`
}
```
And then you can parse `Config` with `env.Parse`.
## Required fields
The `env` tag option `required` (e.g., `env:"tagKey,required"`) can be added to ensure that some environment variable is set.
In the example above, an error is returned if the `config` struct is changed to:
```go
type config struct {
SecretKey string `env:"SECRET_KEY,required"`
}
```
## Not Empty fields
While `required` demands the environment variable to be set, it doesn't check its value.
If you want to make sure the environment is set and not empty, you need to use the `notEmpty` tag option instead (`env:"SOME_ENV,notEmpty"`).
Example:
```go
type config struct {
SecretKey string `env:"SECRET_KEY,notEmpty"`
}
```
## Unset environment variable after reading it
The `env` tag option `unset` (e.g., `env:"tagKey,unset"`) can be added
to ensure that some environment variable is unset after reading it.
Example:
```go
type config struct {
SecretKey string `env:"SECRET_KEY,unset"`
}
```
## From file
The `env` tag option `file` (e.g., `env:"tagKey,file"`) can be added
to in order to indicate that the value of the variable shall be loaded from a file. The path of that file is given
by the environment variable associated with it
Example below
```go
package main
import (
"fmt"
"time"
"github.com/caarlos0/env/v7"
)
type config struct {
Secret string `env:"SECRET,file"`
Password string `env:"PASSWORD,file" envDefault:"/tmp/password"`
Certificate string `env:"CERTIFICATE,file" envDefault:"${CERTIFICATE_FILE}" envExpand:"true"`
}
func main() {
cfg := config{}
if err := env.Parse(&cfg); err != nil {
fmt.Printf("%+v\n", err)
}
fmt.Printf("%+v\n", cfg)
}
```
```sh
$ echo qwerty > /tmp/secret
$ echo dvorak > /tmp/password
$ echo coleman > /tmp/certificate
$ SECRET=/tmp/secret \
CERTIFICATE_FILE=/tmp/certificate \
go run main.go
{Secret:qwerty Password:dvorak Certificate:coleman}
```
## Options
### Use field names as environment variables by default
If you don't want to set the `env` tag on every field, you can use the
`UseFieldNameByDefault` option.
It will use the field name as environment variable name.
Here's an example:
```go
package main
import (
"fmt"
"log"
"github.com/caarlos0/env/v7"
)
type Config struct {
Username string // will use $USERNAME
Password string // will use $PASSWORD
UserFullName string // will use $USER_FULL_NAME
}
func main() {
cfg := &Config{}
opts := &env.Options{UseFieldNameByDefault: true}
// Load env vars.
if err := env.Parse(cfg, opts); err != nil {
log.Fatal(err)
}
// Print the loaded data.
fmt.Printf("%+v\n", cfg)
}
```
### Environment
By setting the `Options.Environment` map you can tell `Parse` to add those `keys` and `values`
as env vars before parsing is done. These envs are stored in the map and never actually set by `os.Setenv`.
This option effectively makes `env` ignore the OS environment variables: only the ones provided in the option are used.
This can make your testing scenarios a bit more clean and easy to handle.
```go
package main
import (
"fmt"
"log"
"github.com/caarlos0/env/v7"
)
type Config struct {
Password string `env:"PASSWORD"`
}
func main() {
cfg := &Config{}
opts := &env.Options{Environment: map[string]string{
"PASSWORD": "MY_PASSWORD",
}}
// Load env vars.
if err := env.Parse(cfg, opts); err != nil {
log.Fatal(err)
}
// Print the loaded data.
fmt.Printf("%+v\n", cfg)
}
```
### Changing default tag name
You can change what tag name to use for setting the env vars by setting the `Options.TagName`
variable.
For example
```go
package main
import (
"fmt"
"log"
"github.com/caarlos0/env/v7"
)
type Config struct {
Password string `json:"PASSWORD"`
}
func main() {
cfg := &Config{}
opts := &env.Options{TagName: "json"}
// Load env vars.
if err := env.Parse(cfg, opts); err != nil {
log.Fatal(err)
}
// Print the loaded data.
fmt.Printf("%+v\n", cfg)
}
```
### Prefixes
You can prefix sub-structs env tags, as well as a whole `env.Parse` call.
Here's an example flexing it a bit:
```go
package main
import (
"fmt"
"log"
"github.com/caarlos0/env/v7"
)
type Config struct {
Home string `env:"HOME"`
}
type ComplexConfig struct {
Foo Config `envPrefix:"FOO_"`
Clean Config
Bar Config `envPrefix:"BAR_"`
Blah string `env:"BLAH"`
}
func main() {
cfg := ComplexConfig{}
if err := Parse(&cfg, Options{
Prefix: "T_",
Environment: map[string]string{
"T_FOO_HOME": "/foo",
"T_BAR_HOME": "/bar",
"T_BLAH": "blahhh",
"T_HOME": "/clean",
},
}); err != nil {
log.Fatal(err)
}
// Load env vars.
if err := env.Parse(cfg, opts); err != nil {
log.Fatal(err)
}
// Print the loaded data.
fmt.Printf("%+v\n", cfg)
}
```
### On set hooks
You might want to listen to value sets and, for example, log something or do some other kind of logic.
You can do this by passing a `OnSet` option:
```go
package main
import (
"fmt"
"log"
"github.com/caarlos0/env/v7"
)
type Config struct {
Username string `env:"USERNAME" envDefault:"admin"`
Password string `env:"PASSWORD"`
}
func main() {
cfg := &Config{}
opts := &env.Options{
OnSet: func(tag string, value interface{}, isDefault bool) {
fmt.Printf("Set %s to %v (default? %v)\n", tag, value, isDefault)
},
}
// Load env vars.
if err := env.Parse(cfg, opts); err != nil {
log.Fatal(err)
}
// Print the loaded data.
fmt.Printf("%+v\n", cfg)
}
```
## Making all fields to required
You can make all fields that don't have a default value be required by setting the `RequiredIfNoDef: true` in the `Options`.
For example
```go
package main
import (
"fmt"
"log"
"github.com/caarlos0/env/v7"
)
type Config struct {
Username string `env:"USERNAME" envDefault:"admin"`
Password string `env:"PASSWORD"`
}
func main() {
cfg := &Config{}
opts := &env.Options{RequiredIfNoDef: true}
// Load env vars.
if err := env.Parse(cfg, opts); err != nil {
log.Fatal(err)
}
// Print the loaded data.
fmt.Printf("%+v\n", cfg)
}
```
## Defaults from code
You may define default value also in code, by initialising the config data before it's filled by `env.Parse`.
Default values defined as struct tags will overwrite existing values during Parse.
```go
package main
import (
"fmt"
"log"
"github.com/caarlos0/env/v7"
)
type Config struct {
Username string `env:"USERNAME" envDefault:"admin"`
Password string `env:"PASSWORD"`
}
func main() {
var cfg = Config{
Username: "test",
Password: "123456",
}
if err := env.Parse(&cfg); err != nil {
fmt.Println("failed:", err)
}
fmt.Printf("%+v", cfg) // {Username:admin Password:123456}
}
```
## Error handling
You can handle the errors the library throws like so:
```go
package main
import (
"fmt"
"log"
"github.com/caarlos0/env/v7"
)
type Config struct {
Username string `env:"USERNAME" envDefault:"admin"`
Password string `env:"PASSWORD"`
}
func main() {
var cfg Config
err := env.Parse(&cfg)
if e, ok := err.(*env.AggregateError); ok {
for _, er := range e.Errors {
switch v := er.(type) {
case env.ParseError:
// handle it
case env.NotStructPtrError:
// handle it
case env.NoParserError:
// handle it
case env.NoSupportedTagOptionError:
// handle it
default:
fmt.Printf("Unknown error type %v", v)
}
}
}
fmt.Printf("%+v", cfg) // {Username:admin Password:123456}
}
```
> **Info**
>
> If you want to check if an specific error is in the chain, you can also use
> `errors.Is()`.
## Stargazers over time
[![Stargazers over time](https://starchart.cc/caarlos0/env.svg)](https://starchart.cc/caarlos0/env)
-540
View File
@@ -1,540 +0,0 @@
package env
import (
"encoding"
"fmt"
"net/url"
"os"
"reflect"
"strconv"
"strings"
"time"
"unicode"
)
// nolint: gochecknoglobals
var (
defaultBuiltInParsers = map[reflect.Kind]ParserFunc{
reflect.Bool: func(v string) (interface{}, error) {
return strconv.ParseBool(v)
},
reflect.String: func(v string) (interface{}, error) {
return v, nil
},
reflect.Int: func(v string) (interface{}, error) {
i, err := strconv.ParseInt(v, 10, 32)
return int(i), err
},
reflect.Int16: func(v string) (interface{}, error) {
i, err := strconv.ParseInt(v, 10, 16)
return int16(i), err
},
reflect.Int32: func(v string) (interface{}, error) {
i, err := strconv.ParseInt(v, 10, 32)
return int32(i), err
},
reflect.Int64: func(v string) (interface{}, error) {
return strconv.ParseInt(v, 10, 64)
},
reflect.Int8: func(v string) (interface{}, error) {
i, err := strconv.ParseInt(v, 10, 8)
return int8(i), err
},
reflect.Uint: func(v string) (interface{}, error) {
i, err := strconv.ParseUint(v, 10, 32)
return uint(i), err
},
reflect.Uint16: func(v string) (interface{}, error) {
i, err := strconv.ParseUint(v, 10, 16)
return uint16(i), err
},
reflect.Uint32: func(v string) (interface{}, error) {
i, err := strconv.ParseUint(v, 10, 32)
return uint32(i), err
},
reflect.Uint64: func(v string) (interface{}, error) {
i, err := strconv.ParseUint(v, 10, 64)
return i, err
},
reflect.Uint8: func(v string) (interface{}, error) {
i, err := strconv.ParseUint(v, 10, 8)
return uint8(i), err
},
reflect.Float64: func(v string) (interface{}, error) {
return strconv.ParseFloat(v, 64)
},
reflect.Float32: func(v string) (interface{}, error) {
f, err := strconv.ParseFloat(v, 32)
return float32(f), err
},
}
)
func defaultTypeParsers() map[reflect.Type]ParserFunc {
return map[reflect.Type]ParserFunc{
reflect.TypeOf(url.URL{}): func(v string) (interface{}, error) {
u, err := url.Parse(v)
if err != nil {
return nil, newParseValueError("unable to parse URL", err)
}
return *u, nil
},
reflect.TypeOf(time.Nanosecond): func(v string) (interface{}, error) {
s, err := time.ParseDuration(v)
if err != nil {
return nil, newParseValueError("unable to parse duration", err)
}
return s, err
},
}
}
// ParserFunc defines the signature of a function that can be used within `CustomParsers`.
type ParserFunc func(v string) (interface{}, error)
// OnSetFn is a hook that can be run when a value is set.
type OnSetFn func(tag string, value interface{}, isDefault bool)
// Options for the parser.
type Options struct {
// Environment keys and values that will be accessible for the service.
Environment map[string]string
// TagName specifies another tagname to use rather than the default env.
TagName string
// RequiredIfNoDef automatically sets all env as required if they do not
// declare 'envDefault'.
RequiredIfNoDef bool
// OnSet allows to run a function when a value is set.
OnSet OnSetFn
// Prefix define a prefix for each key.
Prefix string
// UseFieldNameByDefault defines whether or not env should use the field
// name by default if the `env` key is missing.
UseFieldNameByDefault bool
// Sets to true if we have already configured once.
configured bool
}
// configure will do the basic configurations and defaults.
func configure(opts []Options) []Options {
// If we have already configured the first item
// of options will have been configured set to true.
if len(opts) > 0 && opts[0].configured {
return opts
}
// Created options with defaults.
opt := Options{
TagName: "env",
Environment: toMap(os.Environ()),
configured: true,
}
// Loop over all opts structs and set
// to opt if value is not default/empty.
for _, item := range opts {
if item.Environment != nil {
opt.Environment = item.Environment
}
if item.TagName != "" {
opt.TagName = item.TagName
}
if item.OnSet != nil {
opt.OnSet = item.OnSet
}
if item.Prefix != "" {
opt.Prefix = item.Prefix
}
opt.UseFieldNameByDefault = item.UseFieldNameByDefault
opt.RequiredIfNoDef = item.RequiredIfNoDef
}
return []Options{opt}
}
func getOnSetFn(opts []Options) OnSetFn {
return opts[0].OnSet
}
// getTagName returns the tag name.
func getTagName(opts []Options) string {
return opts[0].TagName
}
// getEnvironment returns the environment map.
func getEnvironment(opts []Options) map[string]string {
return opts[0].Environment
}
// Parse parses a struct containing `env` tags and loads its values from
// environment variables.
func Parse(v interface{}, opts ...Options) error {
return ParseWithFuncs(v, map[reflect.Type]ParserFunc{}, opts...)
}
// ParseWithFuncs is the same as `Parse` except it also allows the user to pass
// in custom parsers.
func ParseWithFuncs(v interface{}, funcMap map[reflect.Type]ParserFunc, opts ...Options) error {
opts = configure(opts)
ptrRef := reflect.ValueOf(v)
if ptrRef.Kind() != reflect.Ptr {
return newAggregateError(NotStructPtrError{})
}
ref := ptrRef.Elem()
if ref.Kind() != reflect.Struct {
return newAggregateError(NotStructPtrError{})
}
parsers := defaultTypeParsers()
for k, v := range funcMap {
parsers[k] = v
}
return doParse(ref, parsers, opts)
}
func doParse(ref reflect.Value, funcMap map[reflect.Type]ParserFunc, opts []Options) error {
refType := ref.Type()
var agrErr AggregateError
for i := 0; i < refType.NumField(); i++ {
refField := ref.Field(i)
refTypeField := refType.Field(i)
if err := doParseField(refField, refTypeField, funcMap, opts); err != nil {
if val, ok := err.(AggregateError); ok {
agrErr.Errors = append(agrErr.Errors, val.Errors...)
} else {
agrErr.Errors = append(agrErr.Errors, err)
}
}
}
if len(agrErr.Errors) == 0 {
return nil
}
return agrErr
}
func doParseField(refField reflect.Value, refTypeField reflect.StructField, funcMap map[reflect.Type]ParserFunc, opts []Options) error {
if !refField.CanSet() {
return nil
}
if reflect.Ptr == refField.Kind() && !refField.IsNil() {
return ParseWithFuncs(refField.Interface(), funcMap, optsWithPrefix(refTypeField, opts)...)
}
if reflect.Struct == refField.Kind() && refField.CanAddr() && refField.Type().Name() == "" {
return ParseWithFuncs(refField.Addr().Interface(), funcMap, optsWithPrefix(refTypeField, opts)...)
}
value, err := get(refTypeField, opts)
if err != nil {
return err
}
if value != "" {
return set(refField, refTypeField, value, funcMap)
}
if reflect.Struct == refField.Kind() {
return doParse(refField, funcMap, optsWithPrefix(refTypeField, opts))
}
return nil
}
const underscore rune = '_'
func toEnvName(input string) string {
var output []rune
for i, c := range input {
if i > 0 && output[i-1] != underscore && c != underscore && unicode.ToUpper(c) == c {
output = append(output, underscore)
}
output = append(output, unicode.ToUpper(c))
}
return string(output)
}
func get(field reflect.StructField, opts []Options) (val string, err error) {
var exists bool
var isDefault bool
var loadFile bool
var unset bool
var notEmpty bool
required := opts[0].RequiredIfNoDef
prefix := opts[0].Prefix
ownKey, tags := parseKeyForOption(field.Tag.Get(getTagName(opts)))
if ownKey == "" && opts[0].UseFieldNameByDefault {
ownKey = toEnvName(field.Name)
}
key := prefix + ownKey
for _, tag := range tags {
switch tag {
case "":
continue
case "file":
loadFile = true
case "required":
required = true
case "unset":
unset = true
case "notEmpty":
notEmpty = true
default:
return "", newNoSupportedTagOptionError(tag)
}
}
expand := strings.EqualFold(field.Tag.Get("envExpand"), "true")
defaultValue, defExists := field.Tag.Lookup("envDefault")
val, exists, isDefault = getOr(key, defaultValue, defExists, getEnvironment(opts))
if expand {
val = os.ExpandEnv(val)
}
if unset {
defer os.Unsetenv(key)
}
if required && !exists && len(ownKey) > 0 {
return "", newEnvVarIsNotSet(key)
}
if notEmpty && val == "" {
return "", newEmptyEnvVarError(key)
}
if loadFile && val != "" {
filename := val
val, err = getFromFile(filename)
if err != nil {
return "", newLoadFileContentError(filename, key, err)
}
}
if onSetFn := getOnSetFn(opts); onSetFn != nil {
onSetFn(key, val, isDefault)
}
return val, err
}
// split the env tag's key into the expected key and desired option, if any.
func parseKeyForOption(key string) (string, []string) {
opts := strings.Split(key, ",")
return opts[0], opts[1:]
}
func getFromFile(filename string) (value string, err error) {
b, err := os.ReadFile(filename)
return string(b), err
}
func getOr(key, defaultValue string, defExists bool, envs map[string]string) (string, bool, bool) {
value, exists := envs[key]
switch {
case (!exists || key == "") && defExists:
return defaultValue, true, true
case exists && value == "" && defExists:
return defaultValue, true, true
case !exists:
return "", false, false
}
return value, true, false
}
func set(field reflect.Value, sf reflect.StructField, value string, funcMap map[reflect.Type]ParserFunc) error {
if tm := asTextUnmarshaler(field); tm != nil {
if err := tm.UnmarshalText([]byte(value)); err != nil {
return newParseError(sf, err)
}
return nil
}
typee := sf.Type
fieldee := field
if typee.Kind() == reflect.Ptr {
typee = typee.Elem()
fieldee = field.Elem()
}
parserFunc, ok := funcMap[typee]
if ok {
val, err := parserFunc(value)
if err != nil {
return newParseError(sf, err)
}
fieldee.Set(reflect.ValueOf(val))
return nil
}
parserFunc, ok = defaultBuiltInParsers[typee.Kind()]
if ok {
val, err := parserFunc(value)
if err != nil {
return newParseError(sf, err)
}
fieldee.Set(reflect.ValueOf(val).Convert(typee))
return nil
}
switch field.Kind() {
case reflect.Slice:
return handleSlice(field, value, sf, funcMap)
case reflect.Map:
return handleMap(field, value, sf, funcMap)
}
return newNoParserError(sf)
}
func handleSlice(field reflect.Value, value string, sf reflect.StructField, funcMap map[reflect.Type]ParserFunc) error {
separator := sf.Tag.Get("envSeparator")
if separator == "" {
separator = ","
}
parts := strings.Split(value, separator)
typee := sf.Type.Elem()
if typee.Kind() == reflect.Ptr {
typee = typee.Elem()
}
if _, ok := reflect.New(typee).Interface().(encoding.TextUnmarshaler); ok {
return parseTextUnmarshalers(field, parts, sf)
}
parserFunc, ok := funcMap[typee]
if !ok {
parserFunc, ok = defaultBuiltInParsers[typee.Kind()]
if !ok {
return newNoParserError(sf)
}
}
result := reflect.MakeSlice(sf.Type, 0, len(parts))
for _, part := range parts {
r, err := parserFunc(part)
if err != nil {
return newParseError(sf, err)
}
v := reflect.ValueOf(r).Convert(typee)
if sf.Type.Elem().Kind() == reflect.Ptr {
v = reflect.New(typee)
v.Elem().Set(reflect.ValueOf(r).Convert(typee))
}
result = reflect.Append(result, v)
}
field.Set(result)
return nil
}
func handleMap(field reflect.Value, value string, sf reflect.StructField, funcMap map[reflect.Type]ParserFunc) error {
keyType := sf.Type.Key()
keyParserFunc, ok := funcMap[keyType]
if !ok {
keyParserFunc, ok = defaultBuiltInParsers[keyType.Kind()]
if !ok {
return newNoParserError(sf)
}
}
elemType := sf.Type.Elem()
elemParserFunc, ok := funcMap[elemType]
if !ok {
elemParserFunc, ok = defaultBuiltInParsers[elemType.Kind()]
if !ok {
return newNoParserError(sf)
}
}
separator := sf.Tag.Get("envSeparator")
if separator == "" {
separator = ","
}
result := reflect.MakeMap(sf.Type)
for _, part := range strings.Split(value, separator) {
pairs := strings.Split(part, ":")
if len(pairs) != 2 {
return newParseError(sf, fmt.Errorf(`%q should be in "key:value" format`, part))
}
key, err := keyParserFunc(pairs[0])
if err != nil {
return newParseError(sf, err)
}
elem, err := elemParserFunc(pairs[1])
if err != nil {
return newParseError(sf, err)
}
result.SetMapIndex(reflect.ValueOf(key).Convert(keyType), reflect.ValueOf(elem).Convert(elemType))
}
field.Set(result)
return nil
}
func asTextUnmarshaler(field reflect.Value) encoding.TextUnmarshaler {
if reflect.Ptr == field.Kind() {
if field.IsNil() {
field.Set(reflect.New(field.Type().Elem()))
}
} else if field.CanAddr() {
field = field.Addr()
}
tm, ok := field.Interface().(encoding.TextUnmarshaler)
if !ok {
return nil
}
return tm
}
func parseTextUnmarshalers(field reflect.Value, data []string, sf reflect.StructField) error {
s := len(data)
elemType := field.Type().Elem()
slice := reflect.MakeSlice(reflect.SliceOf(elemType), s, s)
for i, v := range data {
sv := slice.Index(i)
kind := sv.Kind()
if kind == reflect.Ptr {
sv = reflect.New(elemType.Elem())
} else {
sv = sv.Addr()
}
tm := sv.Interface().(encoding.TextUnmarshaler)
if err := tm.UnmarshalText([]byte(v)); err != nil {
return newParseError(sf, err)
}
if kind == reflect.Ptr {
slice.Index(i).Set(sv)
}
}
field.Set(slice)
return nil
}
func optsWithPrefix(field reflect.StructField, opts []Options) []Options {
subOpts := make([]Options, len(opts))
copy(subOpts, opts)
if prefix := field.Tag.Get("envPrefix"); prefix != "" {
subOpts[0].Prefix += prefix
}
return subOpts
}
-15
View File
@@ -1,15 +0,0 @@
//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
// +build darwin dragonfly freebsd linux netbsd openbsd solaris
package env
import "strings"
func toMap(env []string) map[string]string {
r := map[string]string{}
for _, e := range env {
p := strings.SplitN(e, "=", 2)
r[p[0]] = p[1]
}
return r
}
-25
View File
@@ -1,25 +0,0 @@
package env
import "strings"
func toMap(env []string) map[string]string {
r := map[string]string{}
for _, e := range env {
p := strings.SplitN(e, "=", 2)
// On Windows, environment variables can start with '='. If so, Split at next character.
// See env_windows.go in the Go source: https://github.com/golang/go/blob/master/src/syscall/env_windows.go#L58
prefixEqualSign := false
if len(e) > 0 && e[0] == '=' {
e = e[1:]
prefixEqualSign = true
}
p = strings.SplitN(e, "=", 2)
if prefixEqualSign {
p[0] = "=" + p[0]
}
r[p[0]] = p[1]
}
return r
}
-164
View File
@@ -1,164 +0,0 @@
package env
import (
"fmt"
"reflect"
"strings"
)
// An aggregated error wrapper to combine gathered errors. This allows either to display all errors or convert them individually
// List of the available errors
// ParseError
// NotStructPtrError
// NoParserError
// NoSupportedTagOptionError
// EnvVarIsNotSetError
// EmptyEnvVarError
// LoadFileContentError
// ParseValueError
type AggregateError struct {
Errors []error
}
func newAggregateError(initErr error) error {
return AggregateError{
[]error{
initErr,
},
}
}
func (e AggregateError) Error() string {
var sb strings.Builder
sb.WriteString("env:")
for _, err := range e.Errors {
sb.WriteString(fmt.Sprintf(" %v;", err.Error()))
}
return strings.TrimRight(sb.String(), ";")
}
// Is conforms with errors.Is.
func (e AggregateError) Is(err error) bool {
for _, ie := range e.Errors {
if reflect.TypeOf(ie) == reflect.TypeOf(err) {
return true
}
}
return false
}
// The error occurs when it's impossible to convert the value for given type.
type ParseError struct {
Name string
Type reflect.Type
Err error
}
func newParseError(sf reflect.StructField, err error) error {
return ParseError{sf.Name, sf.Type, err}
}
func (e ParseError) Error() string {
return fmt.Sprintf(`parse error on field "%s" of type "%s": %v`, e.Name, e.Type, e.Err)
}
// The error occurs when pass something that is not a pointer to a Struct to Parse
type NotStructPtrError struct{}
func (e NotStructPtrError) Error() string {
return "expected a pointer to a Struct"
}
// This error occurs when there is no parser provided for given type
// Supported types and defaults: https://github.com/caarlos0/env#supported-types-and-defaults
// How to create a custom parser: https://github.com/caarlos0/env#custom-parser-funcs
type NoParserError struct {
Name string
Type reflect.Type
}
func newNoParserError(sf reflect.StructField) error {
return NoParserError{sf.Name, sf.Type}
}
func (e NoParserError) Error() string {
return fmt.Sprintf(`no parser found for field "%s" of type "%s"`, e.Name, e.Type)
}
// This error occurs when the given tag is not supported
// In-built supported tags: "", "file", "required", "unset", "notEmpty", "envDefault", "envExpand", "envSeparator"
// How to create a custom tag: https://github.com/caarlos0/env#changing-default-tag-name
type NoSupportedTagOptionError struct {
Tag string
}
func newNoSupportedTagOptionError(tag string) error {
return NoSupportedTagOptionError{tag}
}
func (e NoSupportedTagOptionError) Error() string {
return fmt.Sprintf("tag option %q not supported", e.Tag)
}
// This error occurs when the required variable is not set
// Read about required fields: https://github.com/caarlos0/env#required-fields
type EnvVarIsNotSetError struct {
Key string
}
func newEnvVarIsNotSet(key string) error {
return EnvVarIsNotSetError{key}
}
func (e EnvVarIsNotSetError) Error() string {
return fmt.Sprintf(`required environment variable %q is not set`, e.Key)
}
// This error occurs when the variable which must be not empty is existing but has an empty value
// Read about not empty fields: https://github.com/caarlos0/env#not-empty-fields
type EmptyEnvVarError struct {
Key string
}
func newEmptyEnvVarError(key string) error {
return EmptyEnvVarError{key}
}
func (e EmptyEnvVarError) Error() string {
return fmt.Sprintf("environment variable %q should not be empty", e.Key)
}
// This error occurs when it's impossible to load the value from the file
// Read about From file feature: https://github.com/caarlos0/env#from-file
type LoadFileContentError struct {
Filename string
Key string
Err error
}
func newLoadFileContentError(filename, key string, err error) error {
return LoadFileContentError{filename, key, err}
}
func (e LoadFileContentError) Error() string {
return fmt.Sprintf(`could not load content of file "%s" from variable %s: %v`, e.Filename, e.Key, e.Err)
}
// This error occurs when it's impossible to convert value using given parser
// Supported types and defaults: https://github.com/caarlos0/env#supported-types-and-defaults
// How to create a custom parser: https://github.com/caarlos0/env#custom-parser-funcs
type ParseValueError struct {
Msg string
Err error
}
func newParseValueError(message string, err error) error {
return ParseValueError{message, err}
}
func (e ParseValueError) Error() string {
return fmt.Sprintf("%s: %v", e.Msg, e.Err)
}
-25
View File
@@ -1,25 +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
# IDEs
.idea/
-20
View File
@@ -1,20 +0,0 @@
The MIT License (MIT)
Copyright (c) 2014 Cenk Altı
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.
-32
View File
@@ -1,32 +0,0 @@
# Exponential Backoff [![GoDoc][godoc image]][godoc] [![Build Status][travis image]][travis] [![Coverage Status][coveralls image]][coveralls]
This is a Go port of the exponential backoff algorithm from [Google's HTTP Client Library for Java][google-http-java-client].
[Exponential backoff][exponential backoff wiki]
is an algorithm that uses feedback to multiplicatively decrease the rate of some process,
in order to gradually find an acceptable rate.
The retries exponentially increase and stop increasing when a certain threshold is met.
## Usage
Import path is `github.com/cenkalti/backoff/v4`. Please note the version part at the end.
Use https://pkg.go.dev/github.com/cenkalti/backoff/v4 to view the documentation.
## Contributing
* I would like to keep this library as small as possible.
* Please don't send a PR without opening an issue and discussing it first.
* If proposed change is not a common use case, I will probably not accept it.
[godoc]: https://pkg.go.dev/github.com/cenkalti/backoff/v4
[godoc image]: https://godoc.org/github.com/cenkalti/backoff?status.png
[travis]: https://travis-ci.org/cenkalti/backoff
[travis image]: https://travis-ci.org/cenkalti/backoff.png?branch=master
[coveralls]: https://coveralls.io/github/cenkalti/backoff?branch=master
[coveralls image]: https://coveralls.io/repos/github/cenkalti/backoff/badge.svg?branch=master
[google-http-java-client]: https://github.com/google/google-http-java-client/blob/da1aa993e90285ec18579f1553339b00e19b3ab5/google-http-client/src/main/java/com/google/api/client/util/ExponentialBackOff.java
[exponential backoff wiki]: http://en.wikipedia.org/wiki/Exponential_backoff
[advanced example]: https://pkg.go.dev/github.com/cenkalti/backoff/v4?tab=doc#pkg-examples
-66
View File
@@ -1,66 +0,0 @@
// Package backoff implements backoff algorithms for retrying operations.
//
// Use Retry function for retrying operations that may fail.
// If Retry does not meet your needs,
// copy/paste the function into your project and modify as you wish.
//
// There is also Ticker type similar to time.Ticker.
// You can use it if you need to work with channels.
//
// See Examples section below for usage examples.
package backoff
import "time"
// BackOff is a backoff policy for retrying an operation.
type BackOff interface {
// NextBackOff returns the duration to wait before retrying the operation,
// or backoff. Stop to indicate that no more retries should be made.
//
// Example usage:
//
// duration := backoff.NextBackOff();
// if (duration == backoff.Stop) {
// // Do not retry operation.
// } else {
// // Sleep for duration and retry operation.
// }
//
NextBackOff() time.Duration
// Reset to initial state.
Reset()
}
// Stop indicates that no more retries should be made for use in NextBackOff().
const Stop time.Duration = -1
// ZeroBackOff is a fixed backoff policy whose backoff time is always zero,
// meaning that the operation is retried immediately without waiting, indefinitely.
type ZeroBackOff struct{}
func (b *ZeroBackOff) Reset() {}
func (b *ZeroBackOff) NextBackOff() time.Duration { return 0 }
// StopBackOff is a fixed backoff policy that always returns backoff.Stop for
// NextBackOff(), meaning that the operation should never be retried.
type StopBackOff struct{}
func (b *StopBackOff) Reset() {}
func (b *StopBackOff) NextBackOff() time.Duration { return Stop }
// ConstantBackOff is a backoff policy that always returns the same backoff delay.
// This is in contrast to an exponential backoff policy,
// which returns a delay that grows longer as you call NextBackOff() over and over again.
type ConstantBackOff struct {
Interval time.Duration
}
func (b *ConstantBackOff) Reset() {}
func (b *ConstantBackOff) NextBackOff() time.Duration { return b.Interval }
func NewConstantBackOff(d time.Duration) *ConstantBackOff {
return &ConstantBackOff{Interval: d}
}
-62
View File
@@ -1,62 +0,0 @@
package backoff
import (
"context"
"time"
)
// BackOffContext is a backoff policy that stops retrying after the context
// is canceled.
type BackOffContext interface { // nolint: golint
BackOff
Context() context.Context
}
type backOffContext struct {
BackOff
ctx context.Context
}
// WithContext returns a BackOffContext with context ctx
//
// ctx must not be nil
func WithContext(b BackOff, ctx context.Context) BackOffContext { // nolint: golint
if ctx == nil {
panic("nil context")
}
if b, ok := b.(*backOffContext); ok {
return &backOffContext{
BackOff: b.BackOff,
ctx: ctx,
}
}
return &backOffContext{
BackOff: b,
ctx: ctx,
}
}
func getContext(b BackOff) context.Context {
if cb, ok := b.(BackOffContext); ok {
return cb.Context()
}
if tb, ok := b.(*backOffTries); ok {
return getContext(tb.delegate)
}
return context.Background()
}
func (b *backOffContext) Context() context.Context {
return b.ctx
}
func (b *backOffContext) NextBackOff() time.Duration {
select {
case <-b.ctx.Done():
return Stop
default:
return b.BackOff.NextBackOff()
}
}
-161
View File
@@ -1,161 +0,0 @@
package backoff
import (
"math/rand"
"time"
)
/*
ExponentialBackOff is a backoff implementation that increases the backoff
period for each retry attempt using a randomization function that grows exponentially.
NextBackOff() is calculated using the following formula:
randomized interval =
RetryInterval * (random value in range [1 - RandomizationFactor, 1 + RandomizationFactor])
In other words NextBackOff() will range between the randomization factor
percentage below and above the retry interval.
For example, given the following parameters:
RetryInterval = 2
RandomizationFactor = 0.5
Multiplier = 2
the actual backoff period used in the next retry attempt will range between 1 and 3 seconds,
multiplied by the exponential, that is, between 2 and 6 seconds.
Note: MaxInterval caps the RetryInterval and not the randomized interval.
If the time elapsed since an ExponentialBackOff instance is created goes past the
MaxElapsedTime, then the method NextBackOff() starts returning backoff.Stop.
The elapsed time can be reset by calling Reset().
Example: Given the following default arguments, for 10 tries the sequence will be,
and assuming we go over the MaxElapsedTime on the 10th try:
Request # RetryInterval (seconds) Randomized Interval (seconds)
1 0.5 [0.25, 0.75]
2 0.75 [0.375, 1.125]
3 1.125 [0.562, 1.687]
4 1.687 [0.8435, 2.53]
5 2.53 [1.265, 3.795]
6 3.795 [1.897, 5.692]
7 5.692 [2.846, 8.538]
8 8.538 [4.269, 12.807]
9 12.807 [6.403, 19.210]
10 19.210 backoff.Stop
Note: Implementation is not thread-safe.
*/
type ExponentialBackOff struct {
InitialInterval time.Duration
RandomizationFactor float64
Multiplier float64
MaxInterval time.Duration
// After MaxElapsedTime the ExponentialBackOff returns Stop.
// It never stops if MaxElapsedTime == 0.
MaxElapsedTime time.Duration
Stop time.Duration
Clock Clock
currentInterval time.Duration
startTime time.Time
}
// Clock is an interface that returns current time for BackOff.
type Clock interface {
Now() time.Time
}
// Default values for ExponentialBackOff.
const (
DefaultInitialInterval = 500 * time.Millisecond
DefaultRandomizationFactor = 0.5
DefaultMultiplier = 1.5
DefaultMaxInterval = 60 * time.Second
DefaultMaxElapsedTime = 15 * time.Minute
)
// NewExponentialBackOff creates an instance of ExponentialBackOff using default values.
func NewExponentialBackOff() *ExponentialBackOff {
b := &ExponentialBackOff{
InitialInterval: DefaultInitialInterval,
RandomizationFactor: DefaultRandomizationFactor,
Multiplier: DefaultMultiplier,
MaxInterval: DefaultMaxInterval,
MaxElapsedTime: DefaultMaxElapsedTime,
Stop: Stop,
Clock: SystemClock,
}
b.Reset()
return b
}
type systemClock struct{}
func (t systemClock) Now() time.Time {
return time.Now()
}
// SystemClock implements Clock interface that uses time.Now().
var SystemClock = systemClock{}
// Reset the interval back to the initial retry interval and restarts the timer.
// Reset must be called before using b.
func (b *ExponentialBackOff) Reset() {
b.currentInterval = b.InitialInterval
b.startTime = b.Clock.Now()
}
// NextBackOff calculates the next backoff interval using the formula:
// Randomized interval = RetryInterval * (1 ± RandomizationFactor)
func (b *ExponentialBackOff) NextBackOff() time.Duration {
// Make sure we have not gone over the maximum elapsed time.
elapsed := b.GetElapsedTime()
next := getRandomValueFromInterval(b.RandomizationFactor, rand.Float64(), b.currentInterval)
b.incrementCurrentInterval()
if b.MaxElapsedTime != 0 && elapsed+next > b.MaxElapsedTime {
return b.Stop
}
return next
}
// GetElapsedTime returns the elapsed time since an ExponentialBackOff instance
// is created and is reset when Reset() is called.
//
// The elapsed time is computed using time.Now().UnixNano(). It is
// safe to call even while the backoff policy is used by a running
// ticker.
func (b *ExponentialBackOff) GetElapsedTime() time.Duration {
return b.Clock.Now().Sub(b.startTime)
}
// Increments the current interval by multiplying it with the multiplier.
func (b *ExponentialBackOff) incrementCurrentInterval() {
// Check for overflow, if overflow is detected set the current interval to the max interval.
if float64(b.currentInterval) >= float64(b.MaxInterval)/b.Multiplier {
b.currentInterval = b.MaxInterval
} else {
b.currentInterval = time.Duration(float64(b.currentInterval) * b.Multiplier)
}
}
// Returns a random value from the following interval:
// [currentInterval - randomizationFactor * currentInterval, currentInterval + randomizationFactor * currentInterval].
func getRandomValueFromInterval(randomizationFactor, random float64, currentInterval time.Duration) time.Duration {
if randomizationFactor == 0 {
return currentInterval // make sure no randomness is used when randomizationFactor is 0.
}
var delta = randomizationFactor * float64(currentInterval)
var minInterval = float64(currentInterval) - delta
var maxInterval = float64(currentInterval) + delta
// Get a random value from the range [minInterval, maxInterval].
// The formula used below has a +1 because if the minInterval is 1 and the maxInterval is 3 then
// we want a 33% chance for selecting either 1, 2 or 3.
return time.Duration(minInterval + (random * (maxInterval - minInterval + 1)))
}
-146
View File
@@ -1,146 +0,0 @@
package backoff
import (
"errors"
"time"
)
// An OperationWithData is executing by RetryWithData() or RetryNotifyWithData().
// The operation will be retried using a backoff policy if it returns an error.
type OperationWithData[T any] func() (T, error)
// An Operation is executing by Retry() or RetryNotify().
// The operation will be retried using a backoff policy if it returns an error.
type Operation func() error
func (o Operation) withEmptyData() OperationWithData[struct{}] {
return func() (struct{}, error) {
return struct{}{}, o()
}
}
// Notify is a notify-on-error function. It receives an operation error and
// backoff delay if the operation failed (with an error).
//
// NOTE that if the backoff policy stated to stop retrying,
// the notify function isn't called.
type Notify func(error, time.Duration)
// Retry the operation o until it does not return error or BackOff stops.
// o is guaranteed to be run at least once.
//
// If o returns a *PermanentError, the operation is not retried, and the
// wrapped error is returned.
//
// Retry sleeps the goroutine for the duration returned by BackOff after a
// failed operation returns.
func Retry(o Operation, b BackOff) error {
return RetryNotify(o, b, nil)
}
// RetryWithData is like Retry but returns data in the response too.
func RetryWithData[T any](o OperationWithData[T], b BackOff) (T, error) {
return RetryNotifyWithData(o, b, nil)
}
// RetryNotify calls notify function with the error and wait duration
// for each failed attempt before sleep.
func RetryNotify(operation Operation, b BackOff, notify Notify) error {
return RetryNotifyWithTimer(operation, b, notify, nil)
}
// RetryNotifyWithData is like RetryNotify but returns data in the response too.
func RetryNotifyWithData[T any](operation OperationWithData[T], b BackOff, notify Notify) (T, error) {
return doRetryNotify(operation, b, notify, nil)
}
// RetryNotifyWithTimer calls notify function with the error and wait duration using the given Timer
// for each failed attempt before sleep.
// A default timer that uses system timer is used when nil is passed.
func RetryNotifyWithTimer(operation Operation, b BackOff, notify Notify, t Timer) error {
_, err := doRetryNotify(operation.withEmptyData(), b, notify, t)
return err
}
// RetryNotifyWithTimerAndData is like RetryNotifyWithTimer but returns data in the response too.
func RetryNotifyWithTimerAndData[T any](operation OperationWithData[T], b BackOff, notify Notify, t Timer) (T, error) {
return doRetryNotify(operation, b, notify, t)
}
func doRetryNotify[T any](operation OperationWithData[T], b BackOff, notify Notify, t Timer) (T, error) {
var (
err error
next time.Duration
res T
)
if t == nil {
t = &defaultTimer{}
}
defer func() {
t.Stop()
}()
ctx := getContext(b)
b.Reset()
for {
res, err = operation()
if err == nil {
return res, nil
}
var permanent *PermanentError
if errors.As(err, &permanent) {
return res, permanent.Err
}
if next = b.NextBackOff(); next == Stop {
if cerr := ctx.Err(); cerr != nil {
return res, cerr
}
return res, err
}
if notify != nil {
notify(err, next)
}
t.Start(next)
select {
case <-ctx.Done():
return res, ctx.Err()
case <-t.C():
}
}
}
// PermanentError signals that the operation should not be retried.
type PermanentError struct {
Err error
}
func (e *PermanentError) Error() string {
return e.Err.Error()
}
func (e *PermanentError) Unwrap() error {
return e.Err
}
func (e *PermanentError) Is(target error) bool {
_, ok := target.(*PermanentError)
return ok
}
// Permanent wraps the given err in a *PermanentError.
func Permanent(err error) error {
if err == nil {
return nil
}
return &PermanentError{
Err: err,
}
}
-97
View File
@@ -1,97 +0,0 @@
package backoff
import (
"context"
"sync"
"time"
)
// Ticker holds a channel that delivers `ticks' of a clock at times reported by a BackOff.
//
// Ticks will continue to arrive when the previous operation is still running,
// so operations that take a while to fail could run in quick succession.
type Ticker struct {
C <-chan time.Time
c chan time.Time
b BackOff
ctx context.Context
timer Timer
stop chan struct{}
stopOnce sync.Once
}
// NewTicker returns a new Ticker containing a channel that will send
// the time at times specified by the BackOff argument. Ticker is
// guaranteed to tick at least once. The channel is closed when Stop
// method is called or BackOff stops. It is not safe to manipulate the
// provided backoff policy (notably calling NextBackOff or Reset)
// while the ticker is running.
func NewTicker(b BackOff) *Ticker {
return NewTickerWithTimer(b, &defaultTimer{})
}
// NewTickerWithTimer returns a new Ticker with a custom timer.
// A default timer that uses system timer is used when nil is passed.
func NewTickerWithTimer(b BackOff, timer Timer) *Ticker {
if timer == nil {
timer = &defaultTimer{}
}
c := make(chan time.Time)
t := &Ticker{
C: c,
c: c,
b: b,
ctx: getContext(b),
timer: timer,
stop: make(chan struct{}),
}
t.b.Reset()
go t.run()
return t
}
// Stop turns off a ticker. After Stop, no more ticks will be sent.
func (t *Ticker) Stop() {
t.stopOnce.Do(func() { close(t.stop) })
}
func (t *Ticker) run() {
c := t.c
defer close(c)
// Ticker is guaranteed to tick at least once.
afterC := t.send(time.Now())
for {
if afterC == nil {
return
}
select {
case tick := <-afterC:
afterC = t.send(tick)
case <-t.stop:
t.c = nil // Prevent future ticks from being sent to the channel.
return
case <-t.ctx.Done():
return
}
}
}
func (t *Ticker) send(tick time.Time) <-chan time.Time {
select {
case t.c <- tick:
case <-t.stop:
return nil
}
next := t.b.NextBackOff()
if next == Stop {
t.Stop()
return nil
}
t.timer.Start(next)
return t.timer.C()
}
-35
View File
@@ -1,35 +0,0 @@
package backoff
import "time"
type Timer interface {
Start(duration time.Duration)
Stop()
C() <-chan time.Time
}
// defaultTimer implements Timer interface using time.Timer
type defaultTimer struct {
timer *time.Timer
}
// C returns the timers channel which receives the current time when the timer fires.
func (t *defaultTimer) C() <-chan time.Time {
return t.timer.C
}
// Start starts the timer to fire after the given duration
func (t *defaultTimer) Start(duration time.Duration) {
if t.timer == nil {
t.timer = time.NewTimer(duration)
} else {
t.timer.Reset(duration)
}
}
// Stop is called when the timer is not used anymore and resources may be freed.
func (t *defaultTimer) Stop() {
if t.timer != nil {
t.timer.Stop()
}
}
-38
View File
@@ -1,38 +0,0 @@
package backoff
import "time"
/*
WithMaxRetries creates a wrapper around another BackOff, which will
return Stop if NextBackOff() has been called too many times since
the last time Reset() was called
Note: Implementation is not thread-safe.
*/
func WithMaxRetries(b BackOff, max uint64) BackOff {
return &backOffTries{delegate: b, maxTries: max}
}
type backOffTries struct {
delegate BackOff
maxTries uint64
numTries uint64
}
func (b *backOffTries) NextBackOff() time.Duration {
if b.maxTries == 0 {
return Stop
}
if b.maxTries > 0 {
if b.maxTries <= b.numTries {
return Stop
}
b.numTries++
}
return b.delegate.NextBackOff()
}
func (b *backOffTries) Reset() {
b.numTries = 0
b.delegate.Reset()
}
-22
View File
@@ -1,22 +0,0 @@
Copyright (c) 2016 Caleb Spare
MIT License
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.
-72
View File
@@ -1,72 +0,0 @@
# xxhash
[![Go Reference](https://pkg.go.dev/badge/github.com/cespare/xxhash/v2.svg)](https://pkg.go.dev/github.com/cespare/xxhash/v2)
[![Test](https://github.com/cespare/xxhash/actions/workflows/test.yml/badge.svg)](https://github.com/cespare/xxhash/actions/workflows/test.yml)
xxhash is a Go implementation of the 64-bit [xxHash] algorithm, XXH64. This is a
high-quality hashing algorithm that is much faster than anything in the Go
standard library.
This package provides a straightforward API:
```
func Sum64(b []byte) uint64
func Sum64String(s string) uint64
type Digest struct{ ... }
func New() *Digest
```
The `Digest` type implements hash.Hash64. Its key methods are:
```
func (*Digest) Write([]byte) (int, error)
func (*Digest) WriteString(string) (int, error)
func (*Digest) Sum64() uint64
```
The package is written with optimized pure Go and also contains even faster
assembly implementations for amd64 and arm64. If desired, the `purego` build tag
opts into using the Go code even on those architectures.
[xxHash]: http://cyan4973.github.io/xxHash/
## Compatibility
This package is in a module and the latest code is in version 2 of the module.
You need a version of Go with at least "minimal module compatibility" to use
github.com/cespare/xxhash/v2:
* 1.9.7+ for Go 1.9
* 1.10.3+ for Go 1.10
* Go 1.11 or later
I recommend using the latest release of Go.
## Benchmarks
Here are some quick benchmarks comparing the pure-Go and assembly
implementations of Sum64.
| input size | purego | asm |
| ---------- | --------- | --------- |
| 4 B | 1.3 GB/s | 1.2 GB/s |
| 16 B | 2.9 GB/s | 3.5 GB/s |
| 100 B | 6.9 GB/s | 8.1 GB/s |
| 4 KB | 11.7 GB/s | 16.7 GB/s |
| 10 MB | 12.0 GB/s | 17.3 GB/s |
These numbers were generated on Ubuntu 20.04 with an Intel Xeon Platinum 8252C
CPU using the following commands under Go 1.19.2:
```
benchstat <(go test -tags purego -benchtime 500ms -count 15 -bench 'Sum64$')
benchstat <(go test -benchtime 500ms -count 15 -bench 'Sum64$')
```
## Projects using this package
- [InfluxDB](https://github.com/influxdata/influxdb)
- [Prometheus](https://github.com/prometheus/prometheus)
- [VictoriaMetrics](https://github.com/VictoriaMetrics/VictoriaMetrics)
- [FreeCache](https://github.com/coocood/freecache)
- [FastCache](https://github.com/VictoriaMetrics/fastcache)
-10
View File
@@ -1,10 +0,0 @@
#!/bin/bash
set -eu -o pipefail
# Small convenience script for running the tests with various combinations of
# arch/tags. This assumes we're running on amd64 and have qemu available.
go test ./...
go test -tags purego ./...
GOARCH=arm64 go test
GOARCH=arm64 go test -tags purego
-228
View File
@@ -1,228 +0,0 @@
// Package xxhash implements the 64-bit variant of xxHash (XXH64) as described
// at http://cyan4973.github.io/xxHash/.
package xxhash
import (
"encoding/binary"
"errors"
"math/bits"
)
const (
prime1 uint64 = 11400714785074694791
prime2 uint64 = 14029467366897019727
prime3 uint64 = 1609587929392839161
prime4 uint64 = 9650029242287828579
prime5 uint64 = 2870177450012600261
)
// Store the primes in an array as well.
//
// The consts are used when possible in Go code to avoid MOVs but we need a
// contiguous array of the assembly code.
var primes = [...]uint64{prime1, prime2, prime3, prime4, prime5}
// Digest implements hash.Hash64.
type Digest struct {
v1 uint64
v2 uint64
v3 uint64
v4 uint64
total uint64
mem [32]byte
n int // how much of mem is used
}
// New creates a new Digest that computes the 64-bit xxHash algorithm.
func New() *Digest {
var d Digest
d.Reset()
return &d
}
// Reset clears the Digest's state so that it can be reused.
func (d *Digest) Reset() {
d.v1 = primes[0] + prime2
d.v2 = prime2
d.v3 = 0
d.v4 = -primes[0]
d.total = 0
d.n = 0
}
// Size always returns 8 bytes.
func (d *Digest) Size() int { return 8 }
// BlockSize always returns 32 bytes.
func (d *Digest) BlockSize() int { return 32 }
// Write adds more data to d. It always returns len(b), nil.
func (d *Digest) Write(b []byte) (n int, err error) {
n = len(b)
d.total += uint64(n)
memleft := d.mem[d.n&(len(d.mem)-1):]
if d.n+n < 32 {
// This new data doesn't even fill the current block.
copy(memleft, b)
d.n += n
return
}
if d.n > 0 {
// Finish off the partial block.
c := copy(memleft, b)
d.v1 = round(d.v1, u64(d.mem[0:8]))
d.v2 = round(d.v2, u64(d.mem[8:16]))
d.v3 = round(d.v3, u64(d.mem[16:24]))
d.v4 = round(d.v4, u64(d.mem[24:32]))
b = b[c:]
d.n = 0
}
if len(b) >= 32 {
// One or more full blocks left.
nw := writeBlocks(d, b)
b = b[nw:]
}
// Store any remaining partial block.
copy(d.mem[:], b)
d.n = len(b)
return
}
// Sum appends the current hash to b and returns the resulting slice.
func (d *Digest) Sum(b []byte) []byte {
s := d.Sum64()
return append(
b,
byte(s>>56),
byte(s>>48),
byte(s>>40),
byte(s>>32),
byte(s>>24),
byte(s>>16),
byte(s>>8),
byte(s),
)
}
// Sum64 returns the current hash.
func (d *Digest) Sum64() uint64 {
var h uint64
if d.total >= 32 {
v1, v2, v3, v4 := d.v1, d.v2, d.v3, d.v4
h = rol1(v1) + rol7(v2) + rol12(v3) + rol18(v4)
h = mergeRound(h, v1)
h = mergeRound(h, v2)
h = mergeRound(h, v3)
h = mergeRound(h, v4)
} else {
h = d.v3 + prime5
}
h += d.total
b := d.mem[:d.n&(len(d.mem)-1)]
for ; len(b) >= 8; b = b[8:] {
k1 := round(0, u64(b[:8]))
h ^= k1
h = rol27(h)*prime1 + prime4
}
if len(b) >= 4 {
h ^= uint64(u32(b[:4])) * prime1
h = rol23(h)*prime2 + prime3
b = b[4:]
}
for ; len(b) > 0; b = b[1:] {
h ^= uint64(b[0]) * prime5
h = rol11(h) * prime1
}
h ^= h >> 33
h *= prime2
h ^= h >> 29
h *= prime3
h ^= h >> 32
return h
}
const (
magic = "xxh\x06"
marshaledSize = len(magic) + 8*5 + 32
)
// MarshalBinary implements the encoding.BinaryMarshaler interface.
func (d *Digest) MarshalBinary() ([]byte, error) {
b := make([]byte, 0, marshaledSize)
b = append(b, magic...)
b = appendUint64(b, d.v1)
b = appendUint64(b, d.v2)
b = appendUint64(b, d.v3)
b = appendUint64(b, d.v4)
b = appendUint64(b, d.total)
b = append(b, d.mem[:d.n]...)
b = b[:len(b)+len(d.mem)-d.n]
return b, nil
}
// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface.
func (d *Digest) UnmarshalBinary(b []byte) error {
if len(b) < len(magic) || string(b[:len(magic)]) != magic {
return errors.New("xxhash: invalid hash state identifier")
}
if len(b) != marshaledSize {
return errors.New("xxhash: invalid hash state size")
}
b = b[len(magic):]
b, d.v1 = consumeUint64(b)
b, d.v2 = consumeUint64(b)
b, d.v3 = consumeUint64(b)
b, d.v4 = consumeUint64(b)
b, d.total = consumeUint64(b)
copy(d.mem[:], b)
d.n = int(d.total % uint64(len(d.mem)))
return nil
}
func appendUint64(b []byte, x uint64) []byte {
var a [8]byte
binary.LittleEndian.PutUint64(a[:], x)
return append(b, a[:]...)
}
func consumeUint64(b []byte) ([]byte, uint64) {
x := u64(b)
return b[8:], x
}
func u64(b []byte) uint64 { return binary.LittleEndian.Uint64(b) }
func u32(b []byte) uint32 { return binary.LittleEndian.Uint32(b) }
func round(acc, input uint64) uint64 {
acc += input * prime2
acc = rol31(acc)
acc *= prime1
return acc
}
func mergeRound(acc, val uint64) uint64 {
val = round(0, val)
acc ^= val
acc = acc*prime1 + prime4
return acc
}
func rol1(x uint64) uint64 { return bits.RotateLeft64(x, 1) }
func rol7(x uint64) uint64 { return bits.RotateLeft64(x, 7) }
func rol11(x uint64) uint64 { return bits.RotateLeft64(x, 11) }
func rol12(x uint64) uint64 { return bits.RotateLeft64(x, 12) }
func rol18(x uint64) uint64 { return bits.RotateLeft64(x, 18) }
func rol23(x uint64) uint64 { return bits.RotateLeft64(x, 23) }
func rol27(x uint64) uint64 { return bits.RotateLeft64(x, 27) }
func rol31(x uint64) uint64 { return bits.RotateLeft64(x, 31) }
-209
View File
@@ -1,209 +0,0 @@
//go:build !appengine && gc && !purego
// +build !appengine
// +build gc
// +build !purego
#include "textflag.h"
// Registers:
#define h AX
#define d AX
#define p SI // pointer to advance through b
#define n DX
#define end BX // loop end
#define v1 R8
#define v2 R9
#define v3 R10
#define v4 R11
#define x R12
#define prime1 R13
#define prime2 R14
#define prime4 DI
#define round(acc, x) \
IMULQ prime2, x \
ADDQ x, acc \
ROLQ $31, acc \
IMULQ prime1, acc
// round0 performs the operation x = round(0, x).
#define round0(x) \
IMULQ prime2, x \
ROLQ $31, x \
IMULQ prime1, x
// mergeRound applies a merge round on the two registers acc and x.
// It assumes that prime1, prime2, and prime4 have been loaded.
#define mergeRound(acc, x) \
round0(x) \
XORQ x, acc \
IMULQ prime1, acc \
ADDQ prime4, acc
// blockLoop processes as many 32-byte blocks as possible,
// updating v1, v2, v3, and v4. It assumes that there is at least one block
// to process.
#define blockLoop() \
loop: \
MOVQ +0(p), x \
round(v1, x) \
MOVQ +8(p), x \
round(v2, x) \
MOVQ +16(p), x \
round(v3, x) \
MOVQ +24(p), x \
round(v4, x) \
ADDQ $32, p \
CMPQ p, end \
JLE loop
// func Sum64(b []byte) uint64
TEXT ·Sum64(SB), NOSPLIT|NOFRAME, $0-32
// Load fixed primes.
MOVQ ·primes+0(SB), prime1
MOVQ ·primes+8(SB), prime2
MOVQ ·primes+24(SB), prime4
// Load slice.
MOVQ b_base+0(FP), p
MOVQ b_len+8(FP), n
LEAQ (p)(n*1), end
// The first loop limit will be len(b)-32.
SUBQ $32, end
// Check whether we have at least one block.
CMPQ n, $32
JLT noBlocks
// Set up initial state (v1, v2, v3, v4).
MOVQ prime1, v1
ADDQ prime2, v1
MOVQ prime2, v2
XORQ v3, v3
XORQ v4, v4
SUBQ prime1, v4
blockLoop()
MOVQ v1, h
ROLQ $1, h
MOVQ v2, x
ROLQ $7, x
ADDQ x, h
MOVQ v3, x
ROLQ $12, x
ADDQ x, h
MOVQ v4, x
ROLQ $18, x
ADDQ x, h
mergeRound(h, v1)
mergeRound(h, v2)
mergeRound(h, v3)
mergeRound(h, v4)
JMP afterBlocks
noBlocks:
MOVQ ·primes+32(SB), h
afterBlocks:
ADDQ n, h
ADDQ $24, end
CMPQ p, end
JG try4
loop8:
MOVQ (p), x
ADDQ $8, p
round0(x)
XORQ x, h
ROLQ $27, h
IMULQ prime1, h
ADDQ prime4, h
CMPQ p, end
JLE loop8
try4:
ADDQ $4, end
CMPQ p, end
JG try1
MOVL (p), x
ADDQ $4, p
IMULQ prime1, x
XORQ x, h
ROLQ $23, h
IMULQ prime2, h
ADDQ ·primes+16(SB), h
try1:
ADDQ $4, end
CMPQ p, end
JGE finalize
loop1:
MOVBQZX (p), x
ADDQ $1, p
IMULQ ·primes+32(SB), x
XORQ x, h
ROLQ $11, h
IMULQ prime1, h
CMPQ p, end
JL loop1
finalize:
MOVQ h, x
SHRQ $33, x
XORQ x, h
IMULQ prime2, h
MOVQ h, x
SHRQ $29, x
XORQ x, h
IMULQ ·primes+16(SB), h
MOVQ h, x
SHRQ $32, x
XORQ x, h
MOVQ h, ret+24(FP)
RET
// func writeBlocks(d *Digest, b []byte) int
TEXT ·writeBlocks(SB), NOSPLIT|NOFRAME, $0-40
// Load fixed primes needed for round.
MOVQ ·primes+0(SB), prime1
MOVQ ·primes+8(SB), prime2
// Load slice.
MOVQ b_base+8(FP), p
MOVQ b_len+16(FP), n
LEAQ (p)(n*1), end
SUBQ $32, end
// Load vN from d.
MOVQ s+0(FP), d
MOVQ 0(d), v1
MOVQ 8(d), v2
MOVQ 16(d), v3
MOVQ 24(d), v4
// We don't need to check the loop condition here; this function is
// always called with at least one block of data to process.
blockLoop()
// Copy vN back to d.
MOVQ v1, 0(d)
MOVQ v2, 8(d)
MOVQ v3, 16(d)
MOVQ v4, 24(d)
// The number of bytes written is p minus the old base pointer.
SUBQ b_base+8(FP), p
MOVQ p, ret+32(FP)
RET
-183
View File
@@ -1,183 +0,0 @@
//go:build !appengine && gc && !purego
// +build !appengine
// +build gc
// +build !purego
#include "textflag.h"
// Registers:
#define digest R1
#define h R2 // return value
#define p R3 // input pointer
#define n R4 // input length
#define nblocks R5 // n / 32
#define prime1 R7
#define prime2 R8
#define prime3 R9
#define prime4 R10
#define prime5 R11
#define v1 R12
#define v2 R13
#define v3 R14
#define v4 R15
#define x1 R20
#define x2 R21
#define x3 R22
#define x4 R23
#define round(acc, x) \
MADD prime2, acc, x, acc \
ROR $64-31, acc \
MUL prime1, acc
// round0 performs the operation x = round(0, x).
#define round0(x) \
MUL prime2, x \
ROR $64-31, x \
MUL prime1, x
#define mergeRound(acc, x) \
round0(x) \
EOR x, acc \
MADD acc, prime4, prime1, acc
// blockLoop processes as many 32-byte blocks as possible,
// updating v1, v2, v3, and v4. It assumes that n >= 32.
#define blockLoop() \
LSR $5, n, nblocks \
PCALIGN $16 \
loop: \
LDP.P 16(p), (x1, x2) \
LDP.P 16(p), (x3, x4) \
round(v1, x1) \
round(v2, x2) \
round(v3, x3) \
round(v4, x4) \
SUB $1, nblocks \
CBNZ nblocks, loop
// func Sum64(b []byte) uint64
TEXT ·Sum64(SB), NOSPLIT|NOFRAME, $0-32
LDP b_base+0(FP), (p, n)
LDP ·primes+0(SB), (prime1, prime2)
LDP ·primes+16(SB), (prime3, prime4)
MOVD ·primes+32(SB), prime5
CMP $32, n
CSEL LT, prime5, ZR, h // if n < 32 { h = prime5 } else { h = 0 }
BLT afterLoop
ADD prime1, prime2, v1
MOVD prime2, v2
MOVD $0, v3
NEG prime1, v4
blockLoop()
ROR $64-1, v1, x1
ROR $64-7, v2, x2
ADD x1, x2
ROR $64-12, v3, x3
ROR $64-18, v4, x4
ADD x3, x4
ADD x2, x4, h
mergeRound(h, v1)
mergeRound(h, v2)
mergeRound(h, v3)
mergeRound(h, v4)
afterLoop:
ADD n, h
TBZ $4, n, try8
LDP.P 16(p), (x1, x2)
round0(x1)
// NOTE: here and below, sequencing the EOR after the ROR (using a
// rotated register) is worth a small but measurable speedup for small
// inputs.
ROR $64-27, h
EOR x1 @> 64-27, h, h
MADD h, prime4, prime1, h
round0(x2)
ROR $64-27, h
EOR x2 @> 64-27, h, h
MADD h, prime4, prime1, h
try8:
TBZ $3, n, try4
MOVD.P 8(p), x1
round0(x1)
ROR $64-27, h
EOR x1 @> 64-27, h, h
MADD h, prime4, prime1, h
try4:
TBZ $2, n, try2
MOVWU.P 4(p), x2
MUL prime1, x2
ROR $64-23, h
EOR x2 @> 64-23, h, h
MADD h, prime3, prime2, h
try2:
TBZ $1, n, try1
MOVHU.P 2(p), x3
AND $255, x3, x1
LSR $8, x3, x2
MUL prime5, x1
ROR $64-11, h
EOR x1 @> 64-11, h, h
MUL prime1, h
MUL prime5, x2
ROR $64-11, h
EOR x2 @> 64-11, h, h
MUL prime1, h
try1:
TBZ $0, n, finalize
MOVBU (p), x4
MUL prime5, x4
ROR $64-11, h
EOR x4 @> 64-11, h, h
MUL prime1, h
finalize:
EOR h >> 33, h
MUL prime2, h
EOR h >> 29, h
MUL prime3, h
EOR h >> 32, h
MOVD h, ret+24(FP)
RET
// func writeBlocks(d *Digest, b []byte) int
TEXT ·writeBlocks(SB), NOSPLIT|NOFRAME, $0-40
LDP ·primes+0(SB), (prime1, prime2)
// Load state. Assume v[1-4] are stored contiguously.
MOVD d+0(FP), digest
LDP 0(digest), (v1, v2)
LDP 16(digest), (v3, v4)
LDP b_base+8(FP), (p, n)
blockLoop()
// Store updated state.
STP (v1, v2), 0(digest)
STP (v3, v4), 16(digest)
BIC $31, n
MOVD n, ret+32(FP)
RET
-15
View File
@@ -1,15 +0,0 @@
//go:build (amd64 || arm64) && !appengine && gc && !purego
// +build amd64 arm64
// +build !appengine
// +build gc
// +build !purego
package xxhash
// Sum64 computes the 64-bit xxHash digest of b.
//
//go:noescape
func Sum64(b []byte) uint64
//go:noescape
func writeBlocks(d *Digest, b []byte) int
-76
View File
@@ -1,76 +0,0 @@
//go:build (!amd64 && !arm64) || appengine || !gc || purego
// +build !amd64,!arm64 appengine !gc purego
package xxhash
// Sum64 computes the 64-bit xxHash digest of b.
func Sum64(b []byte) uint64 {
// A simpler version would be
// d := New()
// d.Write(b)
// return d.Sum64()
// but this is faster, particularly for small inputs.
n := len(b)
var h uint64
if n >= 32 {
v1 := primes[0] + prime2
v2 := prime2
v3 := uint64(0)
v4 := -primes[0]
for len(b) >= 32 {
v1 = round(v1, u64(b[0:8:len(b)]))
v2 = round(v2, u64(b[8:16:len(b)]))
v3 = round(v3, u64(b[16:24:len(b)]))
v4 = round(v4, u64(b[24:32:len(b)]))
b = b[32:len(b):len(b)]
}
h = rol1(v1) + rol7(v2) + rol12(v3) + rol18(v4)
h = mergeRound(h, v1)
h = mergeRound(h, v2)
h = mergeRound(h, v3)
h = mergeRound(h, v4)
} else {
h = prime5
}
h += uint64(n)
for ; len(b) >= 8; b = b[8:] {
k1 := round(0, u64(b[:8]))
h ^= k1
h = rol27(h)*prime1 + prime4
}
if len(b) >= 4 {
h ^= uint64(u32(b[:4])) * prime1
h = rol23(h)*prime2 + prime3
b = b[4:]
}
for ; len(b) > 0; b = b[1:] {
h ^= uint64(b[0]) * prime5
h = rol11(h) * prime1
}
h ^= h >> 33
h *= prime2
h ^= h >> 29
h *= prime3
h ^= h >> 32
return h
}
func writeBlocks(d *Digest, b []byte) int {
v1, v2, v3, v4 := d.v1, d.v2, d.v3, d.v4
n := len(b)
for len(b) >= 32 {
v1 = round(v1, u64(b[0:8:len(b)]))
v2 = round(v2, u64(b[8:16:len(b)]))
v3 = round(v3, u64(b[16:24:len(b)]))
v4 = round(v4, u64(b[24:32:len(b)]))
b = b[32:len(b):len(b)]
}
d.v1, d.v2, d.v3, d.v4 = v1, v2, v3, v4
return n - len(b)
}
-16
View File
@@ -1,16 +0,0 @@
//go:build appengine
// +build appengine
// This file contains the safe implementations of otherwise unsafe-using code.
package xxhash
// Sum64String computes the 64-bit xxHash digest of s.
func Sum64String(s string) uint64 {
return Sum64([]byte(s))
}
// WriteString adds more data to d. It always returns len(s), nil.
func (d *Digest) WriteString(s string) (n int, err error) {
return d.Write([]byte(s))
}
-58
View File
@@ -1,58 +0,0 @@
//go:build !appengine
// +build !appengine
// This file encapsulates usage of unsafe.
// xxhash_safe.go contains the safe implementations.
package xxhash
import (
"unsafe"
)
// In the future it's possible that compiler optimizations will make these
// XxxString functions unnecessary by realizing that calls such as
// Sum64([]byte(s)) don't need to copy s. See https://go.dev/issue/2205.
// If that happens, even if we keep these functions they can be replaced with
// the trivial safe code.
// NOTE: The usual way of doing an unsafe string-to-[]byte conversion is:
//
// var b []byte
// bh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
// bh.Data = (*reflect.StringHeader)(unsafe.Pointer(&s)).Data
// bh.Len = len(s)
// bh.Cap = len(s)
//
// Unfortunately, as of Go 1.15.3 the inliner's cost model assigns a high enough
// weight to this sequence of expressions that any function that uses it will
// not be inlined. Instead, the functions below use a different unsafe
// conversion designed to minimize the inliner weight and allow both to be
// inlined. There is also a test (TestInlining) which verifies that these are
// inlined.
//
// See https://github.com/golang/go/issues/42739 for discussion.
// Sum64String computes the 64-bit xxHash digest of s.
// It may be faster than Sum64([]byte(s)) by avoiding a copy.
func Sum64String(s string) uint64 {
b := *(*[]byte)(unsafe.Pointer(&sliceHeader{s, len(s)}))
return Sum64(b)
}
// WriteString adds more data to d. It always returns len(s), nil.
// It may be faster than Write([]byte(s)) by avoiding a copy.
func (d *Digest) WriteString(s string) (n int, err error) {
d.Write(*(*[]byte)(unsafe.Pointer(&sliceHeader{s, len(s)})))
// d.Write always returns len(s), nil.
// Ignoring the return output and returning these fixed values buys a
// savings of 6 in the inliner's cost model.
return len(s), nil
}
// sliceHeader is similar to reflect.SliceHeader, but it assumes that the layout
// of the first two words is the same as the layout of a string.
type sliceHeader struct {
s string
cap int
}
-1
View File
@@ -1 +0,0 @@
.idea/
-28
View File
@@ -1,28 +0,0 @@
Maintainer
----------
DigitalOcean, Inc
Original Authors
----------------
Ben LeMasurier <blemasurier@digitalocean.com>
Matt Layher <mlayher@digitalocean.com>
Contributors
------------
Justin Kim <justin@digitalocean.com>
Ricky Medina <rm@do.co>
Charlie Drage <charlie@charliedrage.com>
Michael Koppmann <me@mkoppmann.at>
Simarpreet Singh <simar@linux.com>
Alexander Polyakov <apolyakov@beget.com>
Amanda Andrade <amanda.andrade@serpro.gov.br>
Geoff Hickey <ghickey@digitalocean.com>
Yuriy Taraday <yorik.sar@gmail.com>
Sylvain Baubeau <sbaubeau@redhat.com>
David Schneider <dsbrng25b@gmail.com>
Alec Hothan <ahothan@gmail.com>
Akos Varga <vrgakos@gmail.com>
Peter Kurfer <peter.kurfer@gmail.com>
Sam Roberts <sroberts@digitalocean.com>
Moritz Wanzenböck <moritz.wanzenboeck@linbit.com>
Jenni Griesmann <jgriesmann@digitalocean.com>
-30
View File
@@ -1,30 +0,0 @@
Contributing
============
The `go-libvirt` project makes use of the [GitHub Flow](https://guides.github.com/introduction/flow/)
for contributions.
If you'd like to contribute to the project, please
[open an issue](https://github.com/digitalocean/go-libvirt/issues/new) or find an
[existing issue](https://github.com/digitalocean/go-libvirt/issues) that you'd like
to take on. This ensures that efforts are not duplicated, and that a new feature
aligns with the focus of the rest of the repository.
Once your suggestion has been submitted and discussed, please be sure that your
code meets the following criteria:
- code is completely `gofmt`'d
- new features or codepaths have appropriate test coverage
- `go test ./...` passes
- `go vet ./...` passes
- `golint ./...` returns no warnings, including documentation comment warnings
In addition, if this is your first time contributing to the `go-libvirt` project,
add your name and email address to the
[AUTHORS](https://github.com/digitalocean/go-libvirt/blob/master/AUTHORS) file
under the "Contributors" section using the format:
`First Last <email@example.com>`.
Finally, submit a pull request for review!
Feel free to join us in [`#go-libvirt` on libera chat](https://web.libera.chat/)
if you'd like to discuss the project.
-195
View File
@@ -1,195 +0,0 @@
Apache License
==============
_Version 2.0, January 2004_
_&lt;<http://www.apache.org/licenses/>&gt;_
### Terms and Conditions for use, reproduction, and distribution
#### 1. Definitions
“License” shall mean the terms and conditions for use, reproduction, and
distribution as defined by Sections 1 through 9 of this document.
“Licensor” shall mean the copyright owner or entity authorized by the copyright
owner that is granting the License.
“Legal Entity” shall mean the union of the acting entity and all other entities
that control, are controlled by, or are under common control with that entity.
For the purposes of this definition, “control” means **(i)** the power, direct or
indirect, to cause the direction or management of such entity, whether by
contract or otherwise, or **(ii)** ownership of fifty percent (50%) or more of the
outstanding shares, or **(iii)** beneficial ownership of such entity.
“You” (or “Your”) shall mean an individual or Legal Entity exercising
permissions granted by this License.
“Source” form shall mean the preferred form for making modifications, including
but not limited to software source code, documentation source, and configuration
files.
“Object” form shall mean any form resulting from mechanical transformation or
translation of a Source form, including but not limited to compiled object code,
generated documentation, and conversions to other media types.
“Work” shall mean the work of authorship, whether in Source or Object form, made
available under the License, as indicated by a copyright notice that is included
in or attached to the work (an example is provided in the Appendix below).
“Derivative Works” shall mean any work, whether in Source or Object form, that
is based on (or derived from) the Work and for which the editorial revisions,
annotations, elaborations, or other modifications represent, as a whole, an
original work of authorship. For the purposes of this License, Derivative Works
shall not include works that remain separable from, or merely link (or bind by
name) to the interfaces of, the Work and Derivative Works thereof.
“Contribution” shall mean any work of authorship, including the original version
of the Work and any modifications or additions to that Work or Derivative Works
thereof, that is intentionally submitted to Licensor for inclusion in the Work
by the copyright owner or by an individual or Legal Entity authorized to submit
on behalf of the copyright owner. For the purposes of this definition,
“submitted” means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems, and
issue tracking systems that are managed by, or on behalf of, the Licensor for
the purpose of discussing and improving the Work, but excluding communication
that is conspicuously marked or otherwise designated in writing by the copyright
owner as “Not a Contribution.”
“Contributor” shall mean Licensor and any individual or Legal Entity on behalf
of whom a Contribution has been received by Licensor and subsequently
incorporated within the Work.
#### 2. Grant of Copyright License
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the Work and such
Derivative Works in Source or Object form.
#### 3. Grant of Patent License
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable (except as stated in this section) patent license to make, have
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
such license applies only to those patent claims licensable by such Contributor
that are necessarily infringed by their Contribution(s) alone or by combination
of their Contribution(s) with the Work to which such Contribution(s) was
submitted. If You institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
Contribution incorporated within the Work constitutes direct or contributory
patent infringement, then any patent licenses granted to You under this License
for that Work shall terminate as of the date such litigation is filed.
#### 4. Redistribution
You may reproduce and distribute copies of the Work or Derivative Works thereof
in any medium, with or without modifications, and in Source or Object form,
provided that You meet the following conditions:
* **(a)** You must give any other recipients of the Work or Derivative Works a copy of
this License; and
* **(b)** You must cause any modified files to carry prominent notices stating that You
changed the files; and
* **(c)** You must retain, in the Source form of any Derivative Works that You distribute,
all copyright, patent, trademark, and attribution notices from the Source form
of the Work, excluding those notices that do not pertain to any part of the
Derivative Works; and
* **(d)** If the Work includes a “NOTICE” text file as part of its distribution, then any
Derivative Works that You distribute must include a readable copy of the
attribution notices contained within such NOTICE file, excluding those notices
that do not pertain to any part of the Derivative Works, in at least one of the
following places: within a NOTICE text file distributed as part of the
Derivative Works; within the Source form or documentation, if provided along
with the Derivative Works; or, within a display generated by the Derivative
Works, if and wherever such third-party notices normally appear. The contents of
the NOTICE file are for informational purposes only and do not modify the
License. You may add Your own attribution notices within Derivative Works that
You distribute, alongside or as an addendum to the NOTICE text from the Work,
provided that such additional attribution notices cannot be construed as
modifying the License.
You may add Your own copyright statement to Your modifications and may provide
additional or different license terms and conditions for use, reproduction, or
distribution of Your modifications, or for any such Derivative Works as a whole,
provided Your use, reproduction, and distribution of the Work otherwise complies
with the conditions stated in this License.
#### 5. Submission of Contributions
Unless You explicitly state otherwise, any Contribution intentionally submitted
for inclusion in the Work by You to the Licensor shall be under the terms and
conditions of this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify the terms of
any separate license agreement you may have executed with Licensor regarding
such Contributions.
#### 6. Trademarks
This License does not grant permission to use the trade names, trademarks,
service marks, or product names of the Licensor, except as required for
reasonable and customary use in describing the origin of the Work and
reproducing the content of the NOTICE file.
#### 7. Disclaimer of Warranty
Unless required by applicable law or agreed to in writing, Licensor provides the
Work (and each Contributor provides its Contributions) on an “AS IS” BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
including, without limitation, any warranties or conditions of TITLE,
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
solely responsible for determining the appropriateness of using or
redistributing the Work and assume any risks associated with Your exercise of
permissions under this License.
#### 8. Limitation of Liability
In no event and under no legal theory, whether in tort (including negligence),
contract, or otherwise, unless required by applicable law (such as deliberate
and grossly negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special, incidental,
or consequential damages of any character arising as a result of this License or
out of the use or inability to use the Work (including but not limited to
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
any and all other commercial damages or losses), even if such Contributor has
been advised of the possibility of such damages.
#### 9. Accepting Warranty or Additional Liability
While redistributing the Work or Derivative Works thereof, You may choose to
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
other liability obligations and/or rights consistent with this License. However,
in accepting such obligations, You may act only on Your own behalf and on Your
sole responsibility, not on behalf of any other Contributor, and only if You
agree to indemnify, defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason of your
accepting any such warranty or additional liability.
_END OF TERMS AND CONDITIONS_
### APPENDIX: How to apply the Apache License to your work
To apply the Apache License to your work, attach the following boilerplate
notice, with the fields enclosed by brackets `[]` replaced with your own
identifying information. (Don't include the brackets!) The text should be
enclosed in the appropriate comment syntax for the file format. We also
recommend that a file or class name and description of purpose be included on
the same “printed page” as the copyright notice for easier identification within
third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-283
View File
@@ -1,283 +0,0 @@
libvirt
[![GoDoc](http://godoc.org/github.com/digitalocean/go-libvirt?status.svg)](http://godoc.org/github.com/digitalocean/go-libvirt)
[![Build Status](https://github.com/digitalocean/go-libvirt/actions/workflows/main.yml/badge.svg)](https://github.com/digitalocean/go-libvirt/actions/)
[![Report Card](https://goreportcard.com/badge/github.com/digitalocean/go-libvirt)](https://goreportcard.com/report/github.com/digitalocean/go-libvirt)
====
Package `go-libvirt` provides a pure Go interface for interacting with libvirt.
Rather than using libvirt's C bindings, this package makes use of
libvirt's RPC interface, as documented [here](https://libvirt.org/kbase/internals/rpc.html).
Connections to the libvirt server may be local, or remote. RPC packets are encoded
using the XDR standard as defined by [RFC 4506](https://tools.ietf.org/html/rfc4506.html).
libvirt's RPC interface is quite extensive, and changes from one version to the
next, so this project uses a pair of code generators to build the go bindings.
The code generators should be run whenever you want to build go-libvirt for a
new version of libvirt. See the next section for directions on re-generating
go-libvirt.
[Pull requests are welcome](https://github.com/digitalocean/go-libvirt/blob/master/CONTRIBUTING.md)!
Feel free to join us in [`#go-libvirt` on libera chat](https://web.libera.chat/)
if you'd like to discuss the project.
Running the Code Generators
---------------------------
The code generator doesn't run automatically when you build go-libvirt. It's
meant to be run manually any time you change the version of libvirt you're
using. When you download go-libvirt it will come with generated files
corresponding to a particular version of libvirt. You can use the library as-is,
but the generated code may be missing libvirt functions, if you're using a newer
version of libvirt, or it may have extra functions that will return
'unimplemented' errors if you try to call them. If this is a problem, you should
re-run the code generator. To do this, follow these steps:
- First, download a copy of the libvirt sources corresponding to the version you
want to use.
- Change directories into where you've unpacked your distribution of libvirt.
- The second step depends on the version of libvirt you'd like to build against.
It's not necessary to actually build libvirt, but it is necessary to run libvirt's
"configure" step because it generates required files.
- For libvirt < v6.7.0:
- `$ mkdir build; cd build`
- `$ ../autogen.sh`
- For libvirt >= v6.7.0:
- `$ meson setup build`
- Finally, set the environment variable `LIBVIRT_SOURCE` to the directory you
put libvirt into, and run `go generate ./...` from the go-libvirt directory.
This runs both of the go-libvirt's code generators.
How to Use This Library
-----------------------
Once you've vendored go-libvirt into your project, you'll probably want to call
some libvirt functions. There's some example code below showing how to connect
to libvirt and make one such call, but once you get past the introduction you'll
next want to call some other libvirt functions. How do you find them?
Start with the [libvirt API reference](https://libvirt.org/html/index.html).
Let's say you want to gracefully shutdown a VM, and after reading through the
libvirt docs you determine that virDomainShutdown() is the function you want to
call to do that. Where's that function in go-libvirt? We transform the names
slightly when building the go bindings. There's no need for a global prefix like
"vir" in Go, since all our functions are inside the package namespace, so we
drop it. That means the Go function for `virDomainShutdown()` is just `DomainShutdown()`,
and sure enough, you can find the Go function `DomainShutdown()` in libvirt.gen.go,
with parameters and return values equivalent to those documented in the API
reference.
Suppose you then decide you need more control over your shutdown, so you switch
over to `virDomainShutdownFlags()`. As its name suggests, this function takes a
flag parameter which has possible values specified in an enum called
`virDomainShutdownFlagValues`. Flag types like this are a little tricky for the
code generator, because the C functions just take an integer type - only the
libvirt documentation actually ties the flags to the enum types. In most cases
though we're able to generate a wrapper function with a distinct flag type,
making it easier for Go tooling to suggest possible flag values while you're
working. Checking the documentation for this function:
`godoc github.com/digitalocean/go-libvirt DomainShutdownFlags`
returns this:
`func (l *Libvirt) DomainShutdownFlags(Dom Domain, Flags DomainShutdownFlagValues) (err error)`
If you want to see the possible flag values, `godoc` can help again:
```
$ godoc github.com/digitalocean/go-libvirt DomainShutdownFlagValues
type DomainShutdownFlagValues int32
DomainShutdownFlagValues as declared in libvirt/libvirt-domain.h:1121
const (
DomainShutdownDefault DomainShutdownFlagValues = iota
DomainShutdownAcpiPowerBtn DomainShutdownFlagValues = 1
DomainShutdownGuestAgent DomainShutdownFlagValues = 2
DomainShutdownInitctl DomainShutdownFlagValues = 4
DomainShutdownSignal DomainShutdownFlagValues = 8
DomainShutdownParavirt DomainShutdownFlagValues = 16
)
DomainShutdownFlagValues enumeration from libvirt/libvirt-domain.h:1121
```
One other suggestion: most of the code in go-libvirt is now generated, but a few
hand-written routines still exist in libvirt.go, and wrap calls to the generated
code with slightly different parameters or return values. We suggest avoiding
these hand-written routines and calling the generated routines in libvirt.gen.go
instead. Over time these handwritten routines will be removed from go-libvirt.
Warning
-------
While these package are reasonably well-tested and have seen some use inside of
DigitalOcean, there may be subtle bugs which could cause the packages to act
in unexpected ways. Use at your own risk!
In addition, the API is not considered stable at this time. If you would like
to include package `libvirt` in a project, we highly recommend vendoring it into
your project.
Example
-------
```go
package main
import (
"fmt"
"log"
"net"
"time"
"github.com/digitalocean/go-libvirt"
)
func main() {
// This dials libvirt on the local machine, but you can substitute the first
// two parameters with "tcp", "<ip address>:<port>" to connect to libvirt on
// a remote machine.
c, err := net.DialTimeout("unix", "/var/run/libvirt/libvirt-sock", 2*time.Second)
if err != nil {
log.Fatalf("failed to dial libvirt: %v", err)
}
l := libvirt.New(c)
if err := l.Connect(); err != nil {
log.Fatalf("failed to connect: %v", err)
}
v, err := l.Version()
if err != nil {
log.Fatalf("failed to retrieve libvirt version: %v", err)
}
fmt.Println("Version:", v)
domains, err := l.Domains()
if err != nil {
log.Fatalf("failed to retrieve domains: %v", err)
}
fmt.Println("ID\tName\t\tUUID")
fmt.Printf("--------------------------------------------------------\n")
for _, d := range domains {
fmt.Printf("%d\t%s\t%x\n", d.ID, d.Name, d.UUID)
}
if err := l.Disconnect(); err != nil {
log.Fatalf("failed to disconnect: %v", err)
}
}
```
```
Version: 1.3.4
ID Name UUID
--------------------------------------------------------
1 Test-1 dc329f87d4de47198cfd2e21c6105b01
2 Test-2 dc229f87d4de47198cfd2e21c6105b01
```
Example (Connect to libvirt via TLS over TCP)
-------
```go
package main
import (
"crypto/tls"
"crypto/x509"
"fmt"
"io/ioutil"
"log"
"github.com/digitalocean/go-libvirt"
)
func main() {
// This dials libvirt on the local machine
// It connects to libvirt via TLS over TCP
// To connect to a remote machine, you need to have the ca/cert/key of it.
keyFileXML, err := ioutil.ReadFile("/etc/pki/libvirt/private/clientkey.pem")
if err != nil {
log.Fatalf("%v", err)
}
certFileXML, err := ioutil.ReadFile("/etc/pki/libvirt/clientcert.pem")
if err != nil {
log.Fatalf("%v", err)
}
caFileXML, err := ioutil.ReadFile("/etc/pki/CA/cacert.pem")
if err != nil {
log.Fatalf("%v", err)
}
cert, err := tls.X509KeyPair([]byte(certFileXML), []byte(keyFileXML))
if err != nil {
log.Fatalf("%v", err)
}
roots := x509.NewCertPool()
roots.AppendCertsFromPEM([]byte(caFileXML))
config := &tls.Config{
Certificates: []tls.Certificate{cert},
RootCAs: roots,
}
// Use host name or IP which is valid in certificate
addr := "10.10.10.10"
port := "16514"
c, err := tls.Dial("tcp", addr + ":" + port, config)
if err != nil {
log.Fatalf("failed to dial libvirt: %v", err)
}
// Drop a byte before libvirt.New(c)
// More details at https://github.com/digitalocean/go-libvirt/issues/89
// Remove this line if the issue does not exist any more
c.Read(make([]byte, 1))
l := libvirt.New(c)
if err := l.Connect(); err != nil {
log.Fatalf("failed to connect: %v", err)
}
v, err := l.Version()
if err != nil {
log.Fatalf("failed to retrieve libvirt version: %v", err)
}
fmt.Println("Version:", v)
// Return both running and stopped VMs
flags := libvirt.ConnectListDomainsActive | libvirt.ConnectListDomainsInactive
domains, _, err := l.ConnectListAllDomains(1, flags)
if err != nil {
log.Fatalf("failed to retrieve domains: %v", err)
}
fmt.Println("ID\tName\t\tUUID")
fmt.Println("--------------------------------------------------------")
for _, d := range domains {
fmt.Printf("%d\t%s\t%x\n", d.ID, d.Name, d.UUID)
}
if err := l.Disconnect(); err != nil {
log.Fatalf("failed to disconnect: %v", err)
}
}
```
Running the Integration Tests
-----------------------------
GitHub actions workflows are defined in [.github/workflows](.github/workflows)
and can be triggered manually in the GitHub UI after pushing a branch. There
are not currently convenient scripts for setting up and running integration tests
locally, but installing libvirt and defining only the artifacts described by the
files in testdata should be sufficient to be able to run the integration test file
against.
File diff suppressed because it is too large Load Diff
-70
View File
@@ -1,70 +0,0 @@
// Copyright 2016 The go-libvirt Authors.
//
// 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 libvirt is a pure Go interface to libvirt.
//
// Rather than using Libvirt's C bindings, this package makes use of Libvirt's
// RPC interface, as documented here: https://libvirt.org/internals/rpc.html.
// Connections to the libvirt server may be local, or remote. RPC packets are
// encoded using the XDR standard as defined by RFC 4506.
//
// Example usage:
//
// package main
//
// import (
// "fmt"
// "log"
// "net"
// "time"
//
// "github.com/digitalocean/go-libvirt"
// )
//
// func main() {
// // This dials libvirt on the local machine, but you can substitute the first
// // two parameters with "tcp", "<ip address>:<port>" to connect to libvirt on
// // a remote machine.
// c, err := net.DialTimeout("unix", "/var/run/libvirt/libvirt-sock", 2*time.Second)
// if err != nil {
// log.Fatalf("failed to dial libvirt: %v", err)
// }
//
// l := libvirt.New(c)
// if err := l.Connect(); err != nil {
// log.Fatalf("failed to connect: %v", err)
// }
//
// v, err := l.Version()
// if err != nil {
// log.Fatalf("failed to retrieve libvirt version: %v", err)
// }
// fmt.Println("Version:", v)
//
// domains, err := l.Domains()
// if err != nil {
// log.Fatalf("failed to retrieve domains: %v", err)
// }
//
// fmt.Println("ID\tName\t\tUUID")
// fmt.Printf("--------------------------------------------------------\n")
// for _, d := range domains {
// fmt.Printf("%d\t%s\t%x\n", d.ID, d.Name, d.UUID)
// }
//
// if err := l.Disconnect(); err != nil {
// log.Fatalf("failed to disconnect: %v", err)
// }
// }
package libvirt
@@ -1,47 +0,0 @@
// Copyright 2018 The go-libvirt Authors.
//
// 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.
//
// Code generated by internal/lvgen/generate.go. DO NOT EDIT.
//
// To regenerate, run 'go generate' in internal/lvgen.
//
package constants
// These are libvirt procedure numbers which correspond to each respective
// API call between remote_internal driver and libvirtd. Each procedure is
// identified by a unique number.
const (
// From enums:
// QEMUProcDomainMonitorCommand is libvirt's QEMU_PROC_DOMAIN_MONITOR_COMMAND
QEMUProcDomainMonitorCommand = 1
// QEMUProcDomainAttach is libvirt's QEMU_PROC_DOMAIN_ATTACH
QEMUProcDomainAttach = 2
// QEMUProcDomainAgentCommand is libvirt's QEMU_PROC_DOMAIN_AGENT_COMMAND
QEMUProcDomainAgentCommand = 3
// QEMUProcConnectDomainMonitorEventRegister is libvirt's QEMU_PROC_CONNECT_DOMAIN_MONITOR_EVENT_REGISTER
QEMUProcConnectDomainMonitorEventRegister = 4
// QEMUProcConnectDomainMonitorEventDeregister is libvirt's QEMU_PROC_CONNECT_DOMAIN_MONITOR_EVENT_DEREGISTER
QEMUProcConnectDomainMonitorEventDeregister = 5
// QEMUProcDomainMonitorEvent is libvirt's QEMU_PROC_DOMAIN_MONITOR_EVENT
QEMUProcDomainMonitorEvent = 6
// From consts:
// QEMUProgram is libvirt's QEMU_PROGRAM
QEMUProgram = 0x20008087
// QEMUProtocolVersion is libvirt's QEMU_PROTOCOL_VERSION
QEMUProtocolVersion = 1
)
File diff suppressed because it is too large Load Diff
-20
View File
@@ -1,20 +0,0 @@
// Copyright 2020 The go-libvirt Authors.
//
// 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 event
// Event represents an internal Event.
type Event interface {
GetCallbackID() int32
}
-157
View File
@@ -1,157 +0,0 @@
// Copyright 2020 The go-libvirt Authors.
//
// 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 event
import (
"context"
)
// emptyEvent is used as a zero-value. Clients will never receive one of these;
// they are only here to satisfy the compiler. See the comments in process() for
// more information.
type emptyEvent struct{}
func (emptyEvent) GetCallbackID() int32 { return 0 }
// Stream is an unbounded buffered event channel. The implementation
// consists of a pair of unbuffered channels and a goroutine to manage them.
// Client behavior will not cause incoming events to block.
type Stream struct {
// Program specifies the source of the events - libvirt or QEMU.
Program uint32
// CallbackID is returned by the event registration call.
CallbackID int32
// manage unbounded channel behavior.
queue []Event
qlen chan (chan int)
in, out chan Event
// terminates processing
shutdown context.CancelFunc
}
// NewStream configures a new Event Stream. Incoming events are appended to a
// queue, which is then relayed to the listening client. Client behavior will
// not cause incoming events to block. It is the responsibility of the caller
// to terminate the Stream via Shutdown() when no longer in use.
func NewStream(program uint32, cbID int32) *Stream {
s := &Stream{
Program: program,
CallbackID: cbID,
in: make(chan Event),
out: make(chan Event),
qlen: make(chan (chan int)),
}
// Start the processing loop, which will return a routine we can use to
// shut the queue down later.
s.shutdown = s.start()
return s
}
// Len will return the current count of events in the internal queue for a
// stream. It does this by sending a message to the stream's process() loop,
// which will then write the current length to the channel contained in that
// message.
func (s *Stream) Len() int {
// Send a request to the process() loop to get the current length of the
// queue
ch := make(chan int)
s.qlen <- ch
return <-ch
}
// Recv returns the next available event from the Stream's queue.
func (s *Stream) Recv() chan Event {
return s.out
}
// Push appends a new event to the queue.
func (s *Stream) Push(e Event) {
s.in <- e
}
// Shutdown gracefully terminates Stream processing, releasing all internal
// resources. Events which have not yet been received by the client will be
// dropped. Subsequent calls to Shutdown() are idempotent.
func (s *Stream) Shutdown() {
if s.shutdown != nil {
s.shutdown()
}
}
// start starts the event processing loop, which will continue to run until
// terminated by the returned context.CancelFunc.
func (s *Stream) start() context.CancelFunc {
ctx, cancel := context.WithCancel(context.Background())
go s.process(ctx)
return cancel
}
// process manages an Stream's lifecycle until canceled by the provided context.
// Incoming events are appended to a queue which is then relayed to the
// listening client. New events pushed onto the queue will not block if the
// client is not actively polling for them; the stream will buffer them
// internally.
func (s *Stream) process(ctx context.Context) {
// Close the output channel so that clients know this stream is finished.
// We don't close s.in to avoid creating a race with the stream's Push()
// function.
defer close(s.out)
// This function is used to retrieve the next event from the queue, to be
// sent to the client. If there are no more events to send, it returns a nil
// channel and a zero-value event.
nextEvent := func() (chan Event, Event) {
sendCh := chan Event(nil)
next := Event(emptyEvent{})
if len(s.queue) > 0 {
sendCh = s.out
next = s.queue[0]
}
return sendCh, next
}
// The select statement in this loop relies on the fact that a send to a nil
// channel will block forever. If we have no entries in the queue, the
// sendCh variable will be nil, so the clause that attempts to send an event
// to the client will never complete. Clients will never receive an
// emptyEvent.
for {
sendCh, nextEvt := nextEvent()
select {
// new event received, append to queue
case e := <-s.in:
s.queue = append(s.queue, e)
case lenCh := <-s.qlen:
lenCh <- len(s.queue)
// client received an event, pop from queue
case sendCh <- nextEvt:
s.queue = s.queue[1:]
// shutdown requested
case <-ctx.Done():
return
}
}
}
-13
View File
@@ -1,13 +0,0 @@
Copyright (c) 2012-2014 Dave Collins <dave@davec.name>
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
@@ -1,896 +0,0 @@
/*
* Copyright (c) 2012-2014 Dave Collins <dave@davec.name>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package xdr
import (
"fmt"
"io"
"math"
"reflect"
"time"
)
var (
errMaxSlice = "data exceeds max slice limit"
errIODecode = "%s while decoding %d bytes"
)
/*
Unmarshal parses XDR-encoded data into the value pointed to by v reading from
reader r and returning the total number of bytes read. An addressable pointer
must be provided since Unmarshal needs to both store the result of the decode as
well as obtain target type information. Unmarhsal traverses v recursively and
automatically indirects pointers through arbitrary depth, allocating them as
necessary, to decode the data into the underlying value pointed to.
Unmarshal uses reflection to determine the type of the concrete value contained
by v and performs a mapping of underlying XDR types to Go types as follows:
Go Type <- XDR Type
--------------------
int8, int16, int32, int <- XDR Integer
uint8, uint16, uint32, uint <- XDR Unsigned Integer
int64 <- XDR Hyper Integer
uint64 <- XDR Unsigned Hyper Integer
bool <- XDR Boolean
float32 <- XDR Floating-Point
float64 <- XDR Double-Precision Floating-Point
string <- XDR String
byte <- XDR Integer
[]byte <- XDR Variable-Length Opaque Data
[#]byte <- XDR Fixed-Length Opaque Data
[]<type> <- XDR Variable-Length Array
[#]<type> <- XDR Fixed-Length Array
struct <- XDR Structure
map <- XDR Variable-Length Array of two-element XDR Structures
time.Time <- XDR String encoded with RFC3339 nanosecond precision
Notes and Limitations:
* Automatic unmarshalling of variable and fixed-length arrays of uint8s
requires a special struct tag `xdropaque:"false"` since byte slices
and byte arrays are assumed to be opaque data and byte is a Go alias
for uint8 thus indistinguishable under reflection
* Cyclic data structures are not supported and will result in infinite
loops
If any issues are encountered during the unmarshalling process, an
UnmarshalError is returned with a human readable description as well as
an ErrorCode value for further inspection from sophisticated callers. Some
potential issues are unsupported Go types, attempting to decode a value which is
too large to fit into a specified Go type, and exceeding max slice limitations.
*/
func Unmarshal(r io.Reader, v interface{}) (int, error) {
d := Decoder{r: r}
return d.Decode(v)
}
// UnmarshalLimited is identical to Unmarshal but it sets maxReadSize in order
// to cap reads.
func UnmarshalLimited(r io.Reader, v interface{}, maxSize uint) (int, error) {
d := Decoder{r: r, maxReadSize: maxSize}
return d.Decode(v)
}
// TypeDecoder lets a caller provide a custom decode routine for a custom type.
type TypeDecoder interface {
Decode(*Decoder, reflect.Value) (int, error)
}
// A Decoder wraps an io.Reader that is expected to provide an XDR-encoded byte
// stream and provides several exposed methods to manually decode various XDR
// primitives without relying on reflection. The NewDecoder function can be
// used to get a new Decoder directly.
//
// Typically, Unmarshal should be used instead of manual decoding. A Decoder
// is exposed so it is possible to perform manual decoding should it be
// necessary in complex scenarios where automatic reflection-based decoding
// won't work.
type Decoder struct {
r io.Reader
// maxReadSize is the default maximum bytes an element can contain. 0
// is unlimited and provides backwards compatability. Setting it to a
// non-zero value caps reads.
maxReadSize uint
// customTypes is a map allowing the caller to provide decoder routines for
// custom types known only to itself.
customTypes map[string]TypeDecoder
}
// DecodeInt treats the next 4 bytes as an XDR encoded integer and returns the
// result as an int32 along with the number of bytes actually read.
//
// An UnmarshalError is returned if there are insufficient bytes remaining.
//
// Reference:
// RFC Section 4.1 - Integer
// 32-bit big-endian signed integer in range [-2147483648, 2147483647]
func (d *Decoder) DecodeInt() (int32, int, error) {
var buf [4]byte
n, err := io.ReadFull(d.r, buf[:])
if err != nil {
msg := fmt.Sprintf(errIODecode, err.Error(), 4)
err := unmarshalError("DecodeInt", ErrIO, msg, buf[:n], err)
return 0, n, err
}
rv := int32(buf[3]) | int32(buf[2])<<8 |
int32(buf[1])<<16 | int32(buf[0])<<24
return rv, n, nil
}
// DecodeUint treats the next 4 bytes as an XDR encoded unsigned integer and
// returns the result as a uint32 along with the number of bytes actually read.
//
// An UnmarshalError is returned if there are insufficient bytes remaining.
//
// Reference:
// RFC Section 4.2 - Unsigned Integer
// 32-bit big-endian unsigned integer in range [0, 4294967295]
func (d *Decoder) DecodeUint() (uint32, int, error) {
var buf [4]byte
n, err := io.ReadFull(d.r, buf[:])
if err != nil {
msg := fmt.Sprintf(errIODecode, err.Error(), 4)
err := unmarshalError("DecodeUint", ErrIO, msg, buf[:n], err)
return 0, n, err
}
rv := uint32(buf[3]) | uint32(buf[2])<<8 |
uint32(buf[1])<<16 | uint32(buf[0])<<24
return rv, n, nil
}
// DecodeEnum treats the next 4 bytes as an XDR encoded enumeration value and
// returns the result as an int32 after verifying that the value is in the
// provided map of valid values. It also returns the number of bytes actually
// read.
//
// An UnmarshalError is returned if there are insufficient bytes remaining or
// the parsed enumeration value is not one of the provided valid values.
//
// Reference:
// RFC Section 4.3 - Enumeration
// Represented as an XDR encoded signed integer
func (d *Decoder) DecodeEnum(validEnums map[int32]bool) (int32, int, error) {
val, n, err := d.DecodeInt()
if err != nil {
return 0, n, err
}
if !validEnums[val] {
err := unmarshalError("DecodeEnum", ErrBadEnumValue,
"invalid enum", val, nil)
return 0, n, err
}
return val, n, nil
}
// DecodeBool treats the next 4 bytes as an XDR encoded boolean value and
// returns the result as a bool along with the number of bytes actually read.
//
// An UnmarshalError is returned if there are insufficient bytes remaining or
// the parsed value is not a 0 or 1.
//
// Reference:
// RFC Section 4.4 - Boolean
// Represented as an XDR encoded enumeration where 0 is false and 1 is true
func (d *Decoder) DecodeBool() (bool, int, error) {
val, n, err := d.DecodeInt()
if err != nil {
return false, n, err
}
switch val {
case 0:
return false, n, nil
case 1:
return true, n, nil
}
err = unmarshalError("DecodeBool", ErrBadEnumValue, "bool not 0 or 1",
val, nil)
return false, n, err
}
// DecodeHyper treats the next 8 bytes as an XDR encoded hyper value and
// returns the result as an int64 along with the number of bytes actually read.
//
// An UnmarshalError is returned if there are insufficient bytes remaining.
//
// Reference:
// RFC Section 4.5 - Hyper Integer
// 64-bit big-endian signed integer in range [-9223372036854775808, 9223372036854775807]
func (d *Decoder) DecodeHyper() (int64, int, error) {
var buf [8]byte
n, err := io.ReadFull(d.r, buf[:])
if err != nil {
msg := fmt.Sprintf(errIODecode, err.Error(), 8)
err := unmarshalError("DecodeHyper", ErrIO, msg, buf[:n], err)
return 0, n, err
}
rv := int64(buf[7]) | int64(buf[6])<<8 |
int64(buf[5])<<16 | int64(buf[4])<<24 |
int64(buf[3])<<32 | int64(buf[2])<<40 |
int64(buf[1])<<48 | int64(buf[0])<<56
return rv, n, err
}
// DecodeUhyper treats the next 8 bytes as an XDR encoded unsigned hyper value
// and returns the result as a uint64 along with the number of bytes actually
// read.
//
// An UnmarshalError is returned if there are insufficient bytes remaining.
//
// Reference:
// RFC Section 4.5 - Unsigned Hyper Integer
// 64-bit big-endian unsigned integer in range [0, 18446744073709551615]
func (d *Decoder) DecodeUhyper() (uint64, int, error) {
var buf [8]byte
n, err := io.ReadFull(d.r, buf[:])
if err != nil {
msg := fmt.Sprintf(errIODecode, err.Error(), 8)
err := unmarshalError("DecodeUhyper", ErrIO, msg, buf[:n], err)
return 0, n, err
}
rv := uint64(buf[7]) | uint64(buf[6])<<8 |
uint64(buf[5])<<16 | uint64(buf[4])<<24 |
uint64(buf[3])<<32 | uint64(buf[2])<<40 |
uint64(buf[1])<<48 | uint64(buf[0])<<56
return rv, n, nil
}
// DecodeFloat treats the next 4 bytes as an XDR encoded floating point and
// returns the result as a float32 along with the number of bytes actually read.
//
// An UnmarshalError is returned if there are insufficient bytes remaining.
//
// Reference:
// RFC Section 4.6 - Floating Point
// 32-bit single-precision IEEE 754 floating point
func (d *Decoder) DecodeFloat() (float32, int, error) {
var buf [4]byte
n, err := io.ReadFull(d.r, buf[:])
if err != nil {
msg := fmt.Sprintf(errIODecode, err.Error(), 4)
err := unmarshalError("DecodeFloat", ErrIO, msg, buf[:n], err)
return 0, n, err
}
val := uint32(buf[3]) | uint32(buf[2])<<8 |
uint32(buf[1])<<16 | uint32(buf[0])<<24
return math.Float32frombits(val), n, nil
}
// DecodeDouble treats the next 8 bytes as an XDR encoded double-precision
// floating point and returns the result as a float64 along with the number of
// bytes actually read.
//
// An UnmarshalError is returned if there are insufficient bytes remaining.
//
// Reference:
// RFC Section 4.7 - Double-Precision Floating Point
// 64-bit double-precision IEEE 754 floating point
func (d *Decoder) DecodeDouble() (float64, int, error) {
var buf [8]byte
n, err := io.ReadFull(d.r, buf[:])
if err != nil {
msg := fmt.Sprintf(errIODecode, err.Error(), 8)
err := unmarshalError("DecodeDouble", ErrIO, msg, buf[:n], err)
return 0, n, err
}
val := uint64(buf[7]) | uint64(buf[6])<<8 |
uint64(buf[5])<<16 | uint64(buf[4])<<24 |
uint64(buf[3])<<32 | uint64(buf[2])<<40 |
uint64(buf[1])<<48 | uint64(buf[0])<<56
return math.Float64frombits(val), n, nil
}
// RFC Section 4.8 - Quadruple-Precision Floating Point
// 128-bit quadruple-precision floating point
// Not Implemented
// DecodeFixedOpaque treats the next 'size' bytes as XDR encoded opaque data and
// returns the result as a byte slice along with the number of bytes actually
// read.
//
// An UnmarshalError is returned if there are insufficient bytes remaining to
// satisfy the passed size, including the necessary padding to make it a
// multiple of 4.
//
// Reference:
// RFC Section 4.9 - Fixed-Length Opaque Data
// Fixed-length uninterpreted data zero-padded to a multiple of four
func (d *Decoder) DecodeFixedOpaque(size int32) ([]byte, int, error) {
// Nothing to do if size is 0.
if size == 0 {
return nil, 0, nil
}
pad := (4 - (size % 4)) % 4
paddedSize := size + pad
if uint(paddedSize) > uint(math.MaxInt32) {
err := unmarshalError("DecodeFixedOpaque", ErrOverflow,
errMaxSlice, paddedSize, nil)
return nil, 0, err
}
buf := make([]byte, paddedSize)
n, err := io.ReadFull(d.r, buf)
if err != nil {
msg := fmt.Sprintf(errIODecode, err.Error(), paddedSize)
err := unmarshalError("DecodeFixedOpaque", ErrIO, msg, buf[:n],
err)
return nil, n, err
}
return buf[0:size], n, nil
}
// DecodeOpaque treats the next bytes as variable length XDR encoded opaque
// data and returns the result as a byte slice along with the number of bytes
// actually read.
//
// An UnmarshalError is returned if there are insufficient bytes remaining or
// the opaque data is larger than the max length of a Go slice.
//
// Reference:
// RFC Section 4.10 - Variable-Length Opaque Data
// Unsigned integer length followed by fixed opaque data of that length
func (d *Decoder) DecodeOpaque() ([]byte, int, error) {
dataLen, n, err := d.DecodeUint()
if err != nil {
return nil, n, err
}
if uint(dataLen) > uint(math.MaxInt32) ||
(d.maxReadSize != 0 && uint(dataLen) > d.maxReadSize) {
err := unmarshalError("DecodeOpaque", ErrOverflow, errMaxSlice,
dataLen, nil)
return nil, n, err
}
rv, n2, err := d.DecodeFixedOpaque(int32(dataLen))
n += n2
if err != nil {
return nil, n, err
}
return rv, n, nil
}
// DecodeString treats the next bytes as a variable length XDR encoded string
// and returns the result as a string along with the number of bytes actually
// read. Character encoding is assumed to be UTF-8 and therefore ASCII
// compatible. If the underlying character encoding is not compatibile with
// this assumption, the data can instead be read as variable-length opaque data
// (DecodeOpaque) and manually converted as needed.
//
// An UnmarshalError is returned if there are insufficient bytes remaining or
// the string data is larger than the max length of a Go slice.
//
// Reference:
// RFC Section 4.11 - String
// Unsigned integer length followed by bytes zero-padded to a multiple of
// four
func (d *Decoder) DecodeString() (string, int, error) {
dataLen, n, err := d.DecodeUint()
if err != nil {
return "", n, err
}
if uint(dataLen) > uint(math.MaxInt32) ||
(d.maxReadSize != 0 && uint(dataLen) > d.maxReadSize) {
err = unmarshalError("DecodeString", ErrOverflow, errMaxSlice,
dataLen, nil)
return "", n, err
}
opaque, n2, err := d.DecodeFixedOpaque(int32(dataLen))
n += n2
if err != nil {
return "", n, err
}
return string(opaque), n, nil
}
// decodeFixedArray treats the next bytes as a series of XDR encoded elements
// of the same type as the array represented by the reflection value and decodes
// each element into the passed array. The ignoreOpaque flag controls whether
// or not uint8 (byte) elements should be decoded individually or as a fixed
// sequence of opaque data. It returns the the number of bytes actually read.
//
// An UnmarshalError is returned if any issues are encountered while decoding
// the array elements.
//
// Reference:
// RFC Section 4.12 - Fixed-Length Array
// Individually XDR encoded array elements
func (d *Decoder) decodeFixedArray(v reflect.Value, ignoreOpaque bool) (int, error) {
// Treat [#]byte (byte is alias for uint8) as opaque data unless
// ignored.
if !ignoreOpaque && v.Type().Elem().Kind() == reflect.Uint8 {
data, n, err := d.DecodeFixedOpaque(int32(v.Len()))
if err != nil {
return n, err
}
reflect.Copy(v, reflect.ValueOf(data))
return n, nil
}
// Decode each array element.
var n int
for i := 0; i < v.Len(); i++ {
n2, err := d.decode(v.Index(i))
n += n2
if err != nil {
return n, err
}
}
return n, nil
}
// decodeArray treats the next bytes as a variable length series of XDR encoded
// elements of the same type as the array represented by the reflection value.
// The number of elements is obtained by first decoding the unsigned integer
// element count. Then each element is decoded into the passed array. The
// ignoreOpaque flag controls whether or not uint8 (byte) elements should be
// decoded individually or as a variable sequence of opaque data. It returns
// the number of bytes actually read.
//
// An UnmarshalError is returned if any issues are encountered while decoding
// the array elements.
//
// Reference:
// RFC Section 4.13 - Variable-Length Array
// Unsigned integer length followed by individually XDR encoded array
// elements
func (d *Decoder) decodeArray(v reflect.Value, ignoreOpaque bool) (int, error) {
dataLen, n, err := d.DecodeUint()
if err != nil {
return n, err
}
if uint(dataLen) > uint(math.MaxInt32) ||
(d.maxReadSize != 0 && uint(dataLen) > d.maxReadSize) {
err := unmarshalError("decodeArray", ErrOverflow, errMaxSlice,
dataLen, nil)
return n, err
}
// Allocate storage for the slice elements (the underlying array) if
// existing slice does not have enough capacity.
sliceLen := int(dataLen)
if v.Cap() < sliceLen {
v.Set(reflect.MakeSlice(v.Type(), sliceLen, sliceLen))
}
if v.Len() < sliceLen {
v.SetLen(sliceLen)
}
// Treat []byte (byte is alias for uint8) as opaque data unless ignored.
if !ignoreOpaque && v.Type().Elem().Kind() == reflect.Uint8 {
data, n2, err := d.DecodeFixedOpaque(int32(sliceLen))
n += n2
if err != nil {
return n, err
}
v.SetBytes(data)
return n, nil
}
// Decode each slice element.
for i := 0; i < sliceLen; i++ {
n2, err := d.decode(v.Index(i))
n += n2
if err != nil {
return n, err
}
}
return n, nil
}
// decodeStruct treats the next bytes as a series of XDR encoded elements
// of the same type as the exported fields of the struct represented by the
// passed reflection value. Pointers are automatically indirected and
// allocated as necessary. It returns the the number of bytes actually read.
//
// An UnmarshalError is returned if any issues are encountered while decoding
// the elements.
//
// Reference:
// RFC Section 4.14 - Structure
// XDR encoded elements in the order of their declaration in the struct
func (d *Decoder) decodeStruct(v reflect.Value) (int, error) {
var n int
vt := v.Type()
for i := 0; i < v.NumField(); i++ {
// Skip unexported fields.
vtf := vt.Field(i)
if vtf.PkgPath != "" {
continue
}
// Indirect through pointers allocating them as needed and
// ensure the field is settable.
vf := v.Field(i)
vf, err := d.indirect(vf)
if err != nil {
return n, err
}
if !vf.CanSet() {
msg := fmt.Sprintf("can't decode to unsettable '%v'",
vf.Type().String())
err := unmarshalError("decodeStruct", ErrNotSettable,
msg, nil, nil)
return n, err
}
// Handle non-opaque data to []uint8 and [#]uint8 based on
// struct tag.
tag := vtf.Tag.Get("xdropaque")
if tag == "false" {
switch vf.Kind() {
case reflect.Slice:
n2, err := d.decodeArray(vf, true)
n += n2
if err != nil {
return n, err
}
continue
case reflect.Array:
n2, err := d.decodeFixedArray(vf, true)
n += n2
if err != nil {
return n, err
}
continue
}
}
// Decode each struct field.
n2, err := d.decode(vf)
n += n2
if err != nil {
return n, err
}
}
return n, nil
}
// RFC Section 4.15 - Discriminated Union
// RFC Section 4.16 - Void
// RFC Section 4.17 - Constant
// RFC Section 4.18 - Typedef
// RFC Section 4.19 - Optional data
// RFC Sections 4.15 though 4.19 only apply to the data specification language
// which is not implemented by this package. In the case of discriminated
// unions, struct tags are used to perform a similar function.
// decodeMap treats the next bytes as an XDR encoded variable array of 2-element
// structures whose fields are of the same type as the map keys and elements
// represented by the passed reflection value. Pointers are automatically
// indirected and allocated as necessary. It returns the the number of bytes
// actually read.
//
// An UnmarshalError is returned if any issues are encountered while decoding
// the elements.
func (d *Decoder) decodeMap(v reflect.Value) (int, error) {
dataLen, n, err := d.DecodeUint()
if err != nil {
return n, err
}
// Allocate storage for the underlying map if needed.
vt := v.Type()
if v.IsNil() {
v.Set(reflect.MakeMap(vt))
}
// Decode each key and value according to their type.
keyType := vt.Key()
elemType := vt.Elem()
for i := uint32(0); i < dataLen; i++ {
key := reflect.New(keyType).Elem()
n2, err := d.decode(key)
n += n2
if err != nil {
return n, err
}
val := reflect.New(elemType).Elem()
n2, err = d.decode(val)
n += n2
if err != nil {
return n, err
}
v.SetMapIndex(key, val)
}
return n, nil
}
// decodeInterface examines the interface represented by the passed reflection
// value to detect whether it is an interface that can be decoded into and
// if it is, extracts the underlying value to pass back into the decode function
// for decoding according to its type. It returns the the number of bytes
// actually read.
//
// An UnmarshalError is returned if any issues are encountered while decoding
// the interface.
func (d *Decoder) decodeInterface(v reflect.Value) (int, error) {
if v.IsNil() || !v.CanInterface() {
msg := fmt.Sprintf("can't decode to nil interface")
err := unmarshalError("decodeInterface", ErrNilInterface, msg,
nil, nil)
return 0, err
}
// Extract underlying value from the interface and indirect through
// pointers allocating them as needed.
ve := reflect.ValueOf(v.Interface())
ve, err := d.indirect(ve)
if err != nil {
return 0, err
}
if !ve.CanSet() {
msg := fmt.Sprintf("can't decode to unsettable '%v'",
ve.Type().String())
err := unmarshalError("decodeInterface", ErrNotSettable, msg,
nil, nil)
return 0, err
}
return d.decode(ve)
}
// decode is the main workhorse for unmarshalling via reflection. It uses
// the passed reflection value to choose the XDR primitives to decode from
// the encapsulated reader. It is a recursive function,
// so cyclic data structures are not supported and will result in an infinite
// loop. It returns the the number of bytes actually read.
func (d *Decoder) decode(v reflect.Value) (int, error) {
if !v.IsValid() {
msg := fmt.Sprintf("type '%s' is not valid", v.Kind().String())
err := unmarshalError("decode", ErrUnsupportedType, msg, nil, nil)
return 0, err
}
// Indirect through pointers allocating them as needed.
ve, err := d.indirect(v)
if err != nil {
return 0, err
}
// Handle time.Time values by decoding them as an RFC3339 formatted
// string with nanosecond precision. Check the type string rather
// than doing a full blown conversion to interface and type assertion
// since checking a string is much quicker.
switch ve.Type().String() {
case "time.Time":
// Read the value as a string and parse it.
timeString, n, err := d.DecodeString()
if err != nil {
return n, err
}
ttv, err := time.Parse(time.RFC3339, timeString)
if err != nil {
err := unmarshalError("decode", ErrParseTime,
err.Error(), timeString, err)
return n, err
}
ve.Set(reflect.ValueOf(ttv))
return n, nil
}
// If this type is in our custom types map, call the decode routine set up
// for it.
if dt, ok := d.customTypes[ve.Type().String()]; ok {
return dt.Decode(d, v)
}
// Handle native Go types.
switch ve.Kind() {
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int:
i, n, err := d.DecodeInt()
if err != nil {
return n, err
}
if ve.OverflowInt(int64(i)) {
msg := fmt.Sprintf("signed integer too large to fit '%s'",
ve.Kind().String())
err = unmarshalError("decode", ErrOverflow, msg, i, nil)
return n, err
}
ve.SetInt(int64(i))
return n, nil
case reflect.Int64:
i, n, err := d.DecodeHyper()
if err != nil {
return n, err
}
ve.SetInt(i)
return n, nil
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint:
ui, n, err := d.DecodeUint()
if err != nil {
return n, err
}
if ve.OverflowUint(uint64(ui)) {
msg := fmt.Sprintf("unsigned integer too large to fit '%s'",
ve.Kind().String())
err = unmarshalError("decode", ErrOverflow, msg, ui, nil)
return n, err
}
ve.SetUint(uint64(ui))
return n, nil
case reflect.Uint64:
ui, n, err := d.DecodeUhyper()
if err != nil {
return n, err
}
ve.SetUint(ui)
return n, nil
case reflect.Bool:
b, n, err := d.DecodeBool()
if err != nil {
return n, err
}
ve.SetBool(b)
return n, nil
case reflect.Float32:
f, n, err := d.DecodeFloat()
if err != nil {
return n, err
}
ve.SetFloat(float64(f))
return n, nil
case reflect.Float64:
f, n, err := d.DecodeDouble()
if err != nil {
return n, err
}
ve.SetFloat(f)
return n, nil
case reflect.String:
s, n, err := d.DecodeString()
if err != nil {
return n, err
}
ve.SetString(s)
return n, nil
case reflect.Array:
n, err := d.decodeFixedArray(ve, false)
if err != nil {
return n, err
}
return n, nil
case reflect.Slice:
n, err := d.decodeArray(ve, false)
if err != nil {
return n, err
}
return n, nil
case reflect.Struct:
n, err := d.decodeStruct(ve)
if err != nil {
return n, err
}
return n, nil
case reflect.Map:
n, err := d.decodeMap(ve)
if err != nil {
return n, err
}
return n, nil
case reflect.Interface:
n, err := d.decodeInterface(ve)
if err != nil {
return n, err
}
return n, nil
}
// The only unhandled types left are unsupported. At the time of this
// writing the only remaining unsupported types that exist are
// reflect.Uintptr and reflect.UnsafePointer.
msg := fmt.Sprintf("unsupported Go type '%s'", ve.Kind().String())
err = unmarshalError("decode", ErrUnsupportedType, msg, nil, nil)
return 0, err
}
// indirect dereferences pointers allocating them as needed until it reaches
// a non-pointer. This allows transparent decoding through arbitrary levels
// of indirection.
func (d *Decoder) indirect(v reflect.Value) (reflect.Value, error) {
rv := v
for rv.Kind() == reflect.Ptr {
// Allocate pointer if needed.
isNil := rv.IsNil()
if isNil && !rv.CanSet() {
msg := fmt.Sprintf("unable to allocate pointer for '%v'",
rv.Type().String())
err := unmarshalError("indirect", ErrNotSettable, msg,
nil, nil)
return rv, err
}
if isNil {
rv.Set(reflect.New(rv.Type().Elem()))
}
rv = rv.Elem()
}
return rv, nil
}
// Decode operates identically to the Unmarshal function with the exception of
// using the reader associated with the Decoder as the source of XDR-encoded
// data instead of a user-supplied reader. See the Unmarhsal documentation for
// specifics.
func (d *Decoder) Decode(v interface{}) (int, error) {
if v == nil {
msg := "can't unmarshal to nil interface"
return 0, unmarshalError("Unmarshal", ErrNilInterface, msg, nil,
nil)
}
vv := reflect.ValueOf(v)
if vv.Kind() != reflect.Ptr {
msg := fmt.Sprintf("can't unmarshal to non-pointer '%v' - use "+
"& operator", vv.Type().String())
err := unmarshalError("Unmarshal", ErrBadArguments, msg, nil, nil)
return 0, err
}
if vv.IsNil() && !vv.CanSet() {
msg := fmt.Sprintf("can't unmarshal to unsettable '%v' - use "+
"& operator", vv.Type().String())
err := unmarshalError("Unmarshal", ErrNotSettable, msg, nil, nil)
return 0, err
}
return d.decode(vv)
}
// NewDecoder returns a Decoder that can be used to manually decode XDR data
// from a provided reader. Typically, Unmarshal should be used instead of
// manually creating a Decoder.
func NewDecoder(r io.Reader) *Decoder {
return &Decoder{r: r}
}
// NewDecoderLimited is identical to NewDecoder but it sets maxReadSize in
// order to cap reads.
func NewDecoderLimited(r io.Reader, maxSize uint) *Decoder {
return &Decoder{r: r, maxReadSize: maxSize}
}
// NewDecoderCustomTypes returns a decoder with support for custom types known
// to the caller. The second parameter is a map of the type name to the decoder
// routine. When the decoder finds a type matching one of the entries in the map
// it will call the custom routine for that type.
func NewDecoderCustomTypes(r io.Reader, maxSize uint, ct map[string]TypeDecoder) *Decoder {
return &Decoder{r: r, maxReadSize: maxSize, customTypes: ct}
}
-171
View File
@@ -1,171 +0,0 @@
/*
* Copyright (c) 2012-2014 Dave Collins <dave@davec.name>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/*
Package xdr implements the data representation portion of the External Data
Representation (XDR) standard protocol as specified in RFC 4506 (obsoletes
RFC 1832 and RFC 1014).
The XDR RFC defines both a data specification language and a data
representation standard. This package implements methods to encode and decode
XDR data per the data representation standard with the exception of 128-bit
quadruple-precision floating points. It does not currently implement parsing of
the data specification language. In other words, the ability to automatically
generate Go code by parsing an XDR data specification file (typically .x
extension) is not supported. In practice, this limitation of the package is
fairly minor since it is largely unnecessary due to the reflection capabilities
of Go as described below.
This package provides two approaches for encoding and decoding XDR data:
1) Marshal/Unmarshal functions which automatically map between XDR and Go types
2) Individual Encoder/Decoder objects to manually work with XDR primitives
For the Marshal/Unmarshal functions, Go reflection capabilities are used to
choose the type of the underlying XDR data based upon the Go type to encode or
the target Go type to decode into. A description of how each type is mapped is
provided below, however one important type worth reviewing is Go structs. In
the case of structs, each exported field (first letter capitalized) is reflected
and mapped in order. As a result, this means a Go struct with exported fields
of the appropriate types listed in the expected order can be used to
automatically encode / decode the XDR data thereby eliminating the need to write
a lot of boilerplate code to encode/decode and error check each piece of XDR
data as is typically required with C based XDR libraries.
Go Type to XDR Type Mappings
The following chart shows an overview of how Go types are mapped to XDR types
for automatic marshalling and unmarshalling. The documentation for the Marshal
and Unmarshal functions has specific details of how the mapping proceeds.
Go Type <-> XDR Type
--------------------
int8, int16, int32, int <-> XDR Integer
uint8, uint16, uint32, uint <-> XDR Unsigned Integer
int64 <-> XDR Hyper Integer
uint64 <-> XDR Unsigned Hyper Integer
bool <-> XDR Boolean
float32 <-> XDR Floating-Point
float64 <-> XDR Double-Precision Floating-Point
string <-> XDR String
byte <-> XDR Integer
[]byte <-> XDR Variable-Length Opaque Data
[#]byte <-> XDR Fixed-Length Opaque Data
[]<type> <-> XDR Variable-Length Array
[#]<type> <-> XDR Fixed-Length Array
struct <-> XDR Structure
map <-> XDR Variable-Length Array of two-element XDR Structures
time.Time <-> XDR String encoded with RFC3339 nanosecond precision
Notes and Limitations:
* Automatic marshalling and unmarshalling of variable and fixed-length
arrays of uint8s require a special struct tag `xdropaque:"false"`
since byte slices and byte arrays are assumed to be opaque data and
byte is a Go alias for uint8 thus indistinguishable under reflection
* Channel, complex, and function types cannot be encoded
* Interfaces without a concrete value cannot be encoded
* Cyclic data structures are not supported and will result in infinite
loops
* Strings are marshalled and unmarshalled with UTF-8 character encoding
which differs from the XDR specification of ASCII, however UTF-8 is
backwards compatible with ASCII so this should rarely cause issues
Encoding
To encode XDR data, use the Marshal function.
func Marshal(w io.Writer, v interface{}) (int, error)
For example, given the following code snippet:
type ImageHeader struct {
Signature [3]byte
Version uint32
IsGrayscale bool
NumSections uint32
}
h := ImageHeader{[3]byte{0xAB, 0xCD, 0xEF}, 2, true, 10}
var w bytes.Buffer
bytesWritten, err := xdr.Marshal(&w, &h)
// Error check elided
The result, encodedData, will then contain the following XDR encoded byte
sequence:
0xAB, 0xCD, 0xEF, 0x00,
0x00, 0x00, 0x00, 0x02,
0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x0A
In addition, while the automatic marshalling discussed above will work for the
vast majority of cases, an Encoder object is provided that can be used to
manually encode XDR primitives for complex scenarios where automatic
reflection-based encoding won't work. The included examples provide a sample of
manual usage via an Encoder.
Decoding
To decode XDR data, use the Unmarshal function.
func Unmarshal(r io.Reader, v interface{}) (int, error)
For example, given the following code snippet:
type ImageHeader struct {
Signature [3]byte
Version uint32
IsGrayscale bool
NumSections uint32
}
// Using output from the Encoding section above.
encodedData := []byte{
0xAB, 0xCD, 0xEF, 0x00,
0x00, 0x00, 0x00, 0x02,
0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x0A,
}
var h ImageHeader
bytesRead, err := xdr.Unmarshal(bytes.NewReader(encodedData), &h)
// Error check elided
The struct instance, h, will then contain the following values:
h.Signature = [3]byte{0xAB, 0xCD, 0xEF}
h.Version = 2
h.IsGrayscale = true
h.NumSections = 10
In addition, while the automatic unmarshalling discussed above will work for the
vast majority of cases, a Decoder object is provided that can be used to
manually decode XDR primitives for complex scenarios where automatic
reflection-based decoding won't work. The included examples provide a sample of
manual usage via a Decoder.
Errors
All errors are either of type UnmarshalError or MarshalError. Both provide
human-readable output as well as an ErrorCode field which can be inspected by
sophisticated callers if necessary.
See the documentation of UnmarshalError, MarshalError, and ErrorCode for further
details.
*/
package xdr
@@ -1,669 +0,0 @@
/*
* Copyright (c) 2012-2014 Dave Collins <dave@davec.name>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package xdr
import (
"fmt"
"io"
"math"
"reflect"
"time"
)
var errIOEncode = "%s while encoding %d bytes"
/*
Marshal writes the XDR encoding of v to writer w and returns the number of bytes
written. It traverses v recursively and automatically indirects pointers
through arbitrary depth to encode the actual value pointed to.
Marshal uses reflection to determine the type of the concrete value contained by
v and performs a mapping of Go types to the underlying XDR types as follows:
Go Type -> XDR Type
--------------------
int8, int16, int32, int -> XDR Integer
uint8, uint16, uint32, uint -> XDR Unsigned Integer
int64 -> XDR Hyper Integer
uint64 -> XDR Unsigned Hyper Integer
bool -> XDR Boolean
float32 -> XDR Floating-Point
float64 -> XDR Double-Precision Floating-Point
string -> XDR String
byte -> XDR Integer
[]byte -> XDR Variable-Length Opaque Data
[#]byte -> XDR Fixed-Length Opaque Data
[]<type> -> XDR Variable-Length Array
[#]<type> -> XDR Fixed-Length Array
struct -> XDR Structure
map -> XDR Variable-Length Array of two-element XDR Structures
time.Time -> XDR String encoded with RFC3339 nanosecond precision
Notes and Limitations:
* Automatic marshalling of variable and fixed-length arrays of uint8s
requires a special struct tag `xdropaque:"false"` since byte slices and
byte arrays are assumed to be opaque data and byte is a Go alias for uint8
thus indistinguishable under reflection
* Channel, complex, and function types cannot be encoded
* Interfaces without a concrete value cannot be encoded
* Cyclic data structures are not supported and will result in infinite loops
* Strings are marshalled with UTF-8 character encoding which differs from
the XDR specification of ASCII, however UTF-8 is backwards compatible with
ASCII so this should rarely cause issues
If any issues are encountered during the marshalling process, a MarshalError is
returned with a human readable description as well as an ErrorCode value for
further inspection from sophisticated callers. Some potential issues are
unsupported Go types, attempting to encode more opaque data than can be
represented by a single opaque XDR entry, and exceeding max slice limitations.
*/
func Marshal(w io.Writer, v interface{}) (int, error) {
enc := Encoder{w: w}
return enc.Encode(v)
}
// An Encoder wraps an io.Writer that will receive the XDR encoded byte stream.
// See NewEncoder.
type Encoder struct {
w io.Writer
}
// EncodeInt writes the XDR encoded representation of the passed 32-bit signed
// integer to the encapsulated writer and returns the number of bytes written.
//
// A MarshalError with an error code of ErrIO is returned if writing the data
// fails.
//
// Reference:
// RFC Section 4.1 - Integer
// 32-bit big-endian signed integer in range [-2147483648, 2147483647]
func (enc *Encoder) EncodeInt(v int32) (int, error) {
var b [4]byte
b[0] = byte(v >> 24)
b[1] = byte(v >> 16)
b[2] = byte(v >> 8)
b[3] = byte(v)
n, err := enc.w.Write(b[:])
if err != nil {
msg := fmt.Sprintf(errIOEncode, err.Error(), 4)
err := marshalError("EncodeInt", ErrIO, msg, b[:n], err)
return n, err
}
return n, nil
}
// EncodeUint writes the XDR encoded representation of the passed 32-bit
// unsigned integer to the encapsulated writer and returns the number of bytes
// written.
//
// A MarshalError with an error code of ErrIO is returned if writing the data
// fails.
//
// Reference:
// RFC Section 4.2 - Unsigned Integer
// 32-bit big-endian unsigned integer in range [0, 4294967295]
func (enc *Encoder) EncodeUint(v uint32) (int, error) {
var b [4]byte
b[0] = byte(v >> 24)
b[1] = byte(v >> 16)
b[2] = byte(v >> 8)
b[3] = byte(v)
n, err := enc.w.Write(b[:])
if err != nil {
msg := fmt.Sprintf(errIOEncode, err.Error(), 4)
err := marshalError("EncodeUint", ErrIO, msg, b[:n], err)
return n, err
}
return n, nil
}
// EncodeEnum treats the passed 32-bit signed integer as an enumeration value
// and, if it is in the list of passed valid enumeration values, writes the XDR
// encoded representation of it to the encapsulated writer. It returns the
// number of bytes written.
//
// A MarshalError is returned if the enumeration value is not one of the
// provided valid values or if writing the data fails.
//
// Reference:
// RFC Section 4.3 - Enumeration
// Represented as an XDR encoded signed integer
func (enc *Encoder) EncodeEnum(v int32, validEnums map[int32]bool) (int, error) {
if !validEnums[v] {
err := marshalError("EncodeEnum", ErrBadEnumValue,
"invalid enum", v, nil)
return 0, err
}
return enc.EncodeInt(v)
}
// EncodeBool writes the XDR encoded representation of the passed boolean to the
// encapsulated writer and returns the number of bytes written.
//
// A MarshalError with an error code of ErrIO is returned if writing the data
// fails.
//
// Reference:
// RFC Section 4.4 - Boolean
// Represented as an XDR encoded enumeration where 0 is false and 1 is true
func (enc *Encoder) EncodeBool(v bool) (int, error) {
i := int32(0)
if v == true {
i = 1
}
return enc.EncodeInt(i)
}
// EncodeHyper writes the XDR encoded representation of the passed 64-bit
// signed integer to the encapsulated writer and returns the number of bytes
// written.
//
// A MarshalError with an error code of ErrIO is returned if writing the data
// fails.
//
// Reference:
// RFC Section 4.5 - Hyper Integer
// 64-bit big-endian signed integer in range [-9223372036854775808, 9223372036854775807]
func (enc *Encoder) EncodeHyper(v int64) (int, error) {
var b [8]byte
b[0] = byte(v >> 56)
b[1] = byte(v >> 48)
b[2] = byte(v >> 40)
b[3] = byte(v >> 32)
b[4] = byte(v >> 24)
b[5] = byte(v >> 16)
b[6] = byte(v >> 8)
b[7] = byte(v)
n, err := enc.w.Write(b[:])
if err != nil {
msg := fmt.Sprintf(errIOEncode, err.Error(), 8)
err := marshalError("EncodeHyper", ErrIO, msg, b[:n], err)
return n, err
}
return n, nil
}
// EncodeUhyper writes the XDR encoded representation of the passed 64-bit
// unsigned integer to the encapsulated writer and returns the number of bytes
// written.
//
// A MarshalError with an error code of ErrIO is returned if writing the data
// fails.
//
// Reference:
// RFC Section 4.5 - Unsigned Hyper Integer
// 64-bit big-endian unsigned integer in range [0, 18446744073709551615]
func (enc *Encoder) EncodeUhyper(v uint64) (int, error) {
var b [8]byte
b[0] = byte(v >> 56)
b[1] = byte(v >> 48)
b[2] = byte(v >> 40)
b[3] = byte(v >> 32)
b[4] = byte(v >> 24)
b[5] = byte(v >> 16)
b[6] = byte(v >> 8)
b[7] = byte(v)
n, err := enc.w.Write(b[:])
if err != nil {
msg := fmt.Sprintf(errIOEncode, err.Error(), 8)
err := marshalError("EncodeUhyper", ErrIO, msg, b[:n], err)
return n, err
}
return n, nil
}
// EncodeFloat writes the XDR encoded representation of the passed 32-bit
// (single-precision) floating point to the encapsulated writer and returns the
// number of bytes written.
//
// A MarshalError with an error code of ErrIO is returned if writing the data
// fails.
//
// Reference:
// RFC Section 4.6 - Floating Point
// 32-bit single-precision IEEE 754 floating point
func (enc *Encoder) EncodeFloat(v float32) (int, error) {
ui := math.Float32bits(v)
return enc.EncodeUint(ui)
}
// EncodeDouble writes the XDR encoded representation of the passed 64-bit
// (double-precision) floating point to the encapsulated writer and returns the
// number of bytes written.
//
// A MarshalError with an error code of ErrIO is returned if writing the data
// fails.
//
// Reference:
// RFC Section 4.7 - Double-Precision Floating Point
// 64-bit double-precision IEEE 754 floating point
func (enc *Encoder) EncodeDouble(v float64) (int, error) {
ui := math.Float64bits(v)
return enc.EncodeUhyper(ui)
}
// RFC Section 4.8 - Quadruple-Precision Floating Point
// 128-bit quadruple-precision floating point
// Not Implemented
// EncodeFixedOpaque treats the passed byte slice as opaque data of a fixed
// size and writes the XDR encoded representation of it to the encapsulated
// writer. It returns the number of bytes written.
//
// A MarshalError with an error code of ErrIO is returned if writing the data
// fails.
//
// Reference:
// RFC Section 4.9 - Fixed-Length Opaque Data
// Fixed-length uninterpreted data zero-padded to a multiple of four
func (enc *Encoder) EncodeFixedOpaque(v []byte) (int, error) {
l := len(v)
pad := (4 - (l % 4)) % 4
// Write the actual bytes.
n, err := enc.w.Write(v)
if err != nil {
msg := fmt.Sprintf(errIOEncode, err.Error(), len(v))
err := marshalError("EncodeFixedOpaque", ErrIO, msg, v[:n], err)
return n, err
}
// Write any padding if needed.
if pad > 0 {
b := make([]byte, pad)
n2, err := enc.w.Write(b)
n += n2
if err != nil {
written := make([]byte, l+n2)
copy(written, v)
copy(written[l:], b[:n2])
msg := fmt.Sprintf(errIOEncode, err.Error(), l+pad)
err := marshalError("EncodeFixedOpaque", ErrIO, msg,
written, err)
return n, err
}
}
return n, nil
}
// EncodeOpaque treats the passed byte slice as opaque data of a variable
// size and writes the XDR encoded representation of it to the encapsulated
// writer. It returns the number of bytes written.
//
// A MarshalError with an error code of ErrIO is returned if writing the data
// fails.
//
// Reference:
// RFC Section 4.10 - Variable-Length Opaque Data
// Unsigned integer length followed by fixed opaque data of that length
func (enc *Encoder) EncodeOpaque(v []byte) (int, error) {
// Length of opaque data.
n, err := enc.EncodeUint(uint32(len(v)))
if err != nil {
return n, err
}
n2, err := enc.EncodeFixedOpaque(v)
n += n2
return n, err
}
// EncodeString writes the XDR encoded representation of the passed string
// to the encapsulated writer and returns the number of bytes written.
// Character encoding is assumed to be UTF-8 and therefore ASCII compatible. If
// the underlying character encoding is not compatible with this assumption, the
// data can instead be written as variable-length opaque data (EncodeOpaque) and
// manually converted as needed.
//
// A MarshalError with an error code of ErrIO is returned if writing the data
// fails.
//
// Reference:
// RFC Section 4.11 - String
// Unsigned integer length followed by bytes zero-padded to a multiple of four
func (enc *Encoder) EncodeString(v string) (int, error) {
// Length of string.
n, err := enc.EncodeUint(uint32(len(v)))
if err != nil {
return n, err
}
n2, err := enc.EncodeFixedOpaque([]byte(v))
n += n2
return n, err
}
// encodeFixedArray writes the XDR encoded representation of each element
// in the passed array represented by the reflection value to the encapsulated
// writer and returns the number of bytes written. The ignoreOpaque flag
// controls whether or not uint8 (byte) elements should be encoded individually
// or as a fixed sequence of opaque data.
//
// A MarshalError is returned if any issues are encountered while encoding
// the array elements.
//
// Reference:
// RFC Section 4.12 - Fixed-Length Array
// Individually XDR encoded array elements
func (enc *Encoder) encodeFixedArray(v reflect.Value, ignoreOpaque bool) (int, error) {
// Treat [#]byte (byte is alias for uint8) as opaque data unless ignored.
if !ignoreOpaque && v.Type().Elem().Kind() == reflect.Uint8 {
// Create a slice of the underlying array for better efficiency
// when possible. Can't create a slice of an unaddressable
// value.
if v.CanAddr() {
return enc.EncodeFixedOpaque(v.Slice(0, v.Len()).Bytes())
}
// When the underlying array isn't addressable fall back to
// copying the array into a new slice. This is rather ugly, but
// the inability to create a constant slice from an
// unaddressable array is a limitation of Go.
slice := make([]byte, v.Len(), v.Len())
reflect.Copy(reflect.ValueOf(slice), v)
return enc.EncodeFixedOpaque(slice)
}
// Encode each array element.
var n int
for i := 0; i < v.Len(); i++ {
n2, err := enc.encode(v.Index(i))
n += n2
if err != nil {
return n, err
}
}
return n, nil
}
// encodeArray writes an XDR encoded integer representing the number of
// elements in the passed slice represented by the reflection value followed by
// the XDR encoded representation of each element in slice to the encapsulated
// writer and returns the number of bytes written. The ignoreOpaque flag
// controls whether or not uint8 (byte) elements should be encoded individually
// or as a variable sequence of opaque data.
//
// A MarshalError is returned if any issues are encountered while encoding
// the array elements.
//
// Reference:
// RFC Section 4.13 - Variable-Length Array
// Unsigned integer length followed by individually XDR encoded array elements
func (enc *Encoder) encodeArray(v reflect.Value, ignoreOpaque bool) (int, error) {
numItems := uint32(v.Len())
n, err := enc.EncodeUint(numItems)
if err != nil {
return n, err
}
n2, err := enc.encodeFixedArray(v, ignoreOpaque)
n += n2
return n, err
}
// encodeStruct writes an XDR encoded representation of each value in the
// exported fields of the struct represented by the passed reflection value to
// the encapsulated writer and returns the number of bytes written. Pointers
// are automatically indirected through arbitrary depth to encode the actual
// value pointed to.
//
// A MarshalError is returned if any issues are encountered while encoding
// the elements.
//
// Reference:
// RFC Section 4.14 - Structure
// XDR encoded elements in the order of their declaration in the struct
func (enc *Encoder) encodeStruct(v reflect.Value) (int, error) {
var n int
vt := v.Type()
for i := 0; i < v.NumField(); i++ {
// Skip unexported fields and indirect through pointers.
vtf := vt.Field(i)
if vtf.PkgPath != "" {
continue
}
vf := v.Field(i)
vf = enc.indirect(vf)
// Handle non-opaque data to []uint8 and [#]uint8 based on struct tag.
tag := vtf.Tag.Get("xdropaque")
if tag == "false" {
switch vf.Kind() {
case reflect.Slice:
n2, err := enc.encodeArray(vf, true)
n += n2
if err != nil {
return n, err
}
continue
case reflect.Array:
n2, err := enc.encodeFixedArray(vf, true)
n += n2
if err != nil {
return n, err
}
continue
}
}
// Encode each struct field.
n2, err := enc.encode(vf)
n += n2
if err != nil {
return n, err
}
}
return n, nil
}
// RFC Section 4.15 - Discriminated Union
// RFC Section 4.16 - Void
// RFC Section 4.17 - Constant
// RFC Section 4.18 - Typedef
// RFC Section 4.19 - Optional data
// RFC Sections 4.15 though 4.19 only apply to the data specification language
// which is not implemented by this package. In the case of discriminated
// unions, struct tags are used to perform a similar function.
// encodeMap treats the map represented by the passed reflection value as a
// variable-length array of 2-element structures whose fields are of the same
// type as the map keys and elements and writes its XDR encoded representation
// to the encapsulated writer. It returns the number of bytes written.
//
// A MarshalError is returned if any issues are encountered while encoding
// the elements.
func (enc *Encoder) encodeMap(v reflect.Value) (int, error) {
// Number of elements.
n, err := enc.EncodeUint(uint32(v.Len()))
if err != nil {
return n, err
}
// Encode each key and value according to their type.
for _, key := range v.MapKeys() {
n2, err := enc.encode(key)
n += n2
if err != nil {
return n, err
}
n2, err = enc.encode(v.MapIndex(key))
n += n2
if err != nil {
return n, err
}
}
return n, nil
}
// encodeInterface examines the interface represented by the passed reflection
// value to detect whether it is an interface that can be encoded if it is,
// extracts the underlying value to pass back into the encode function for
// encoding according to its type.
//
// A MarshalError is returned if any issues are encountered while encoding
// the interface.
func (enc *Encoder) encodeInterface(v reflect.Value) (int, error) {
if v.IsNil() || !v.CanInterface() {
msg := fmt.Sprintf("can't encode nil interface")
err := marshalError("encodeInterface", ErrNilInterface, msg,
nil, nil)
return 0, err
}
// Extract underlying value from the interface and indirect through pointers.
ve := reflect.ValueOf(v.Interface())
ve = enc.indirect(ve)
return enc.encode(ve)
}
// encode is the main workhorse for marshalling via reflection. It uses
// the passed reflection value to choose the XDR primitives to encode into
// the encapsulated writer and returns the number of bytes written. It is a
// recursive function, so cyclic data structures are not supported and will
// result in an infinite loop.
func (enc *Encoder) encode(v reflect.Value) (int, error) {
if !v.IsValid() {
msg := fmt.Sprintf("type '%s' is not valid", v.Kind().String())
err := marshalError("encode", ErrUnsupportedType, msg, nil, nil)
return 0, err
}
// Indirect through pointers to get at the concrete value.
ve := enc.indirect(v)
// Handle time.Time values by encoding them as an RFC3339 formatted
// string with nanosecond precision. Check the type string before
// doing a full blown conversion to interface and type assertion since
// checking a string is much quicker.
if ve.Type().String() == "time.Time" && ve.CanInterface() {
viface := ve.Interface()
if tv, ok := viface.(time.Time); ok {
return enc.EncodeString(tv.Format(time.RFC3339Nano))
}
}
// Handle native Go types.
switch ve.Kind() {
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int:
return enc.EncodeInt(int32(ve.Int()))
case reflect.Int64:
return enc.EncodeHyper(ve.Int())
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint:
return enc.EncodeUint(uint32(ve.Uint()))
case reflect.Uint64:
return enc.EncodeUhyper(ve.Uint())
case reflect.Bool:
return enc.EncodeBool(ve.Bool())
case reflect.Float32:
return enc.EncodeFloat(float32(ve.Float()))
case reflect.Float64:
return enc.EncodeDouble(ve.Float())
case reflect.String:
return enc.EncodeString(ve.String())
case reflect.Array:
return enc.encodeFixedArray(ve, false)
case reflect.Slice:
return enc.encodeArray(ve, false)
case reflect.Struct:
return enc.encodeStruct(ve)
case reflect.Map:
return enc.encodeMap(ve)
case reflect.Interface:
return enc.encodeInterface(ve)
}
// The only unhandled types left are unsupported. At the time of this
// writing the only remaining unsupported types that exist are
// reflect.Uintptr and reflect.UnsafePointer.
msg := fmt.Sprintf("unsupported Go type '%s'", ve.Kind().String())
err := marshalError("encode", ErrUnsupportedType, msg, nil, nil)
return 0, err
}
// indirect dereferences pointers until it reaches a non-pointer. This allows
// transparent encoding through arbitrary levels of indirection.
func (enc *Encoder) indirect(v reflect.Value) reflect.Value {
rv := v
for rv.Kind() == reflect.Ptr {
rv = rv.Elem()
}
return rv
}
// Encode operates identically to the Marshal function with the exception of
// using the writer associated with the Encoder for the destination of the
// XDR-encoded data instead of a user-supplied writer. See the Marshal
// documentation for specifics.
func (enc *Encoder) Encode(v interface{}) (int, error) {
if v == nil {
msg := "can't marshal nil interface"
err := marshalError("Marshal", ErrNilInterface, msg, nil, nil)
return 0, err
}
vv := reflect.ValueOf(v)
vve := vv
for vve.Kind() == reflect.Ptr {
if vve.IsNil() {
msg := fmt.Sprintf("can't marshal nil pointer '%v'",
vv.Type().String())
err := marshalError("Marshal", ErrBadArguments, msg,
nil, nil)
return 0, err
}
vve = vve.Elem()
}
return enc.encode(vve)
}
// NewEncoder returns an object that can be used to manually choose fields to
// XDR encode to the passed writer w. Typically, Marshal should be used instead
// of manually creating an Encoder. An Encoder, along with several of its
// methods to encode XDR primitives, is exposed so it is possible to perform
// manual encoding of data without relying on reflection should it be necessary
// in complex scenarios where automatic reflection-based encoding won't work.
func NewEncoder(w io.Writer) *Encoder {
return &Encoder{w: w}
}
-177
View File
@@ -1,177 +0,0 @@
/*
* Copyright (c) 2012-2014 Dave Collins <dave@davec.name>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package xdr
import "fmt"
// ErrorCode identifies a kind of error.
type ErrorCode int
const (
// ErrBadArguments indicates arguments passed to the function are not
// what was expected.
ErrBadArguments ErrorCode = iota
// ErrUnsupportedType indicates the Go type is not a supported type for
// marshalling and unmarshalling XDR data.
ErrUnsupportedType
// ErrBadEnumValue indicates an enumeration value is not in the list of
// valid values.
ErrBadEnumValue
// ErrNotSettable indicates an interface value cannot be written to.
// This usually means the interface value was not passed with the &
// operator, but it can also happen if automatic pointer allocation
// fails.
ErrNotSettable
// ErrOverflow indicates that the data in question is too large to fit
// into the corresponding Go or XDR data type. For example, an integer
// decoded from XDR that is too large to fit into a target type of int8,
// or opaque data that exceeds the max length of a Go slice.
ErrOverflow
// ErrNilInterface indicates an interface with no concrete type
// information was encountered. Type information is necessary to
// perform mapping between XDR and Go types.
ErrNilInterface
// ErrIO indicates an error was encountered while reading or writing to
// an io.Reader or io.Writer, respectively. The actual underlying error
// will be available via the Err field of the MarshalError or
// UnmarshalError struct.
ErrIO
// ErrParseTime indicates an error was encountered while parsing an
// RFC3339 formatted time value. The actual underlying error will be
// available via the Err field of the UnmarshalError struct.
ErrParseTime
)
// Map of ErrorCode values back to their constant names for pretty printing.
var errorCodeStrings = map[ErrorCode]string{
ErrBadArguments: "ErrBadArguments",
ErrUnsupportedType: "ErrUnsupportedType",
ErrBadEnumValue: "ErrBadEnumValue",
ErrNotSettable: "ErrNotSettable",
ErrOverflow: "ErrOverflow",
ErrNilInterface: "ErrNilInterface",
ErrIO: "ErrIO",
ErrParseTime: "ErrParseTime",
}
// String returns the ErrorCode as a human-readable name.
func (e ErrorCode) String() string {
if s := errorCodeStrings[e]; s != "" {
return s
}
return fmt.Sprintf("Unknown ErrorCode (%d)", e)
}
// UnmarshalError describes a problem encountered while unmarshaling data.
// Some potential issues are unsupported Go types, attempting to decode a value
// which is too large to fit into a specified Go type, and exceeding max slice
// limitations.
type UnmarshalError struct {
ErrorCode ErrorCode // Describes the kind of error
Func string // Function name
Value interface{} // Value actually parsed where appropriate
Description string // Human readable description of the issue
Err error // The underlying error for IO errors
}
// Error satisfies the error interface and prints human-readable errors.
func (e *UnmarshalError) Error() string {
switch e.ErrorCode {
case ErrBadEnumValue, ErrOverflow, ErrIO, ErrParseTime:
return fmt.Sprintf("xdr:%s: %s - read: '%v'", e.Func,
e.Description, e.Value)
}
return fmt.Sprintf("xdr:%s: %s", e.Func, e.Description)
}
// unmarshalError creates an error given a set of arguments and will copy byte
// slices into the Value field since they might otherwise be changed from from
// the original value.
func unmarshalError(f string, c ErrorCode, desc string, v interface{}, err error) *UnmarshalError {
e := &UnmarshalError{ErrorCode: c, Func: f, Description: desc, Err: err}
switch t := v.(type) {
case []byte:
slice := make([]byte, len(t))
copy(slice, t)
e.Value = slice
default:
e.Value = v
}
return e
}
// IsIO returns a boolean indicating whether the error is known to report that
// the underlying reader or writer encountered an ErrIO.
func IsIO(err error) bool {
switch e := err.(type) {
case *UnmarshalError:
return e.ErrorCode == ErrIO
case *MarshalError:
return e.ErrorCode == ErrIO
}
return false
}
// MarshalError describes a problem encountered while marshaling data.
// Some potential issues are unsupported Go types, attempting to encode more
// opaque data than can be represented by a single opaque XDR entry, and
// exceeding max slice limitations.
type MarshalError struct {
ErrorCode ErrorCode // Describes the kind of error
Func string // Function name
Value interface{} // Value actually parsed where appropriate
Description string // Human readable description of the issue
Err error // The underlying error for IO errors
}
// Error satisfies the error interface and prints human-readable errors.
func (e *MarshalError) Error() string {
switch e.ErrorCode {
case ErrIO:
return fmt.Sprintf("xdr:%s: %s - wrote: '%v'", e.Func,
e.Description, e.Value)
case ErrBadEnumValue:
return fmt.Sprintf("xdr:%s: %s - value: '%v'", e.Func,
e.Description, e.Value)
}
return fmt.Sprintf("xdr:%s: %s", e.Func, e.Description)
}
// marshalError creates an error given a set of arguments and will copy byte
// slices into the Value field since they might otherwise be changed from from
// the original value.
func marshalError(f string, c ErrorCode, desc string, v interface{}, err error) *MarshalError {
e := &MarshalError{ErrorCode: c, Func: f, Description: desc, Err: err}
switch t := v.(type) {
case []byte:
slice := make([]byte, len(t))
copy(slice, t)
e.Value = slice
default:
e.Value = v
}
return e
}
-890
View File
@@ -1,890 +0,0 @@
// Copyright 2018 The go-libvirt Authors.
//
// 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 libvirt
// We'll use c-for-go to extract the consts and typedefs from the libvirt
// sources so we don't have to duplicate them here.
//go:generate scripts/gen-consts.sh
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"net"
"sync"
"syscall"
"time"
"github.com/digitalocean/go-libvirt/internal/constants"
"github.com/digitalocean/go-libvirt/internal/event"
xdr "github.com/digitalocean/go-libvirt/internal/go-xdr/xdr2"
"github.com/digitalocean/go-libvirt/socket"
"github.com/digitalocean/go-libvirt/socket/dialers"
)
// ErrEventsNotSupported is returned by Events() if event streams
// are unsupported by either QEMU or libvirt.
var ErrEventsNotSupported = errors.New("event monitor is not supported")
// ConnectURI defines a type for driver URIs for libvirt
// the defined constants are *not* exhaustive as there are also options
// e.g. to connect remote via SSH
type ConnectURI string
const (
// QEMUSystem connects to a QEMU system mode daemon
QEMUSystem ConnectURI = "qemu:///system"
// QEMUSession connects to a QEMU session mode daemon (unprivileged)
QEMUSession ConnectURI = "qemu:///session"
// XenSystem connects to a Xen system mode daemon
XenSystem ConnectURI = "xen:///system"
//TestDefault connect to default mock driver
TestDefault ConnectURI = "test:///default"
// disconnectedTimeout is how long to wait for disconnect cleanup to
// complete
disconnectTimeout = 5 * time.Second
)
// Libvirt implements libvirt's remote procedure call protocol.
type Libvirt struct {
// socket connection
socket *socket.Socket
// closed after cleanup complete following the underlying connection to
// libvirt being disconnected.
disconnected chan struct{}
// method callbacks
cmux sync.RWMutex
callbacks map[int32]chan response
// event listeners
emux sync.RWMutex
events map[int32]*event.Stream
// next request serial number
s int32
}
// DomainEvent represents a libvirt domain event.
type DomainEvent struct {
CallbackID int32
Domain Domain
Event string
Seconds uint64
Microseconds uint32
Padding uint8
Details []byte
}
// GetCallbackID returns the callback ID of a QEMU domain event.
func (de DomainEvent) GetCallbackID() int32 {
return de.CallbackID
}
// GetCallbackID returns the callback ID of a libvirt lifecycle event.
func (m DomainEventCallbackLifecycleMsg) GetCallbackID() int32 {
return m.CallbackID
}
// GetCallbackID returns the callback ID.
func (e *DomainEventCallbackRebootMsg) GetCallbackID() int32 {
return e.CallbackID
}
// GetCallbackID returns the callback ID.
func (e *DomainEventCallbackRtcChangeMsg) GetCallbackID() int32 {
return e.CallbackID
}
// GetCallbackID returns the callback ID.
func (e *DomainEventCallbackWatchdogMsg) GetCallbackID() int32 {
return e.CallbackID
}
// GetCallbackID returns the callback ID.
func (e *DomainEventCallbackIOErrorMsg) GetCallbackID() int32 {
return e.CallbackID
}
// GetCallbackID returns the callback ID.
func (e *DomainEventCallbackIOErrorReasonMsg) GetCallbackID() int32 {
return e.CallbackID
}
// GetCallbackID returns the callback ID.
func (e *DomainEventCallbackGraphicsMsg) GetCallbackID() int32 {
return e.CallbackID
}
// GetCallbackID returns the callback ID.
func (e *DomainEventCallbackBlockJobMsg) GetCallbackID() int32 {
return e.CallbackID
}
// GetCallbackID returns the callback ID.
func (e *DomainEventCallbackDiskChangeMsg) GetCallbackID() int32 {
return e.CallbackID
}
// GetCallbackID returns the callback ID.
func (e *DomainEventCallbackTrayChangeMsg) GetCallbackID() int32 {
return e.CallbackID
}
// GetCallbackID returns the callback ID.
func (e *DomainEventCallbackPmwakeupMsg) GetCallbackID() int32 {
return e.CallbackID
}
// GetCallbackID returns the callback ID.
func (e *DomainEventCallbackPmsuspendMsg) GetCallbackID() int32 {
return e.CallbackID
}
// GetCallbackID returns the callback ID.
func (e *DomainEventCallbackBalloonChangeMsg) GetCallbackID() int32 {
return e.CallbackID
}
// GetCallbackID returns the callback ID.
func (e *DomainEventCallbackPmsuspendDiskMsg) GetCallbackID() int32 {
return e.CallbackID
}
// GetCallbackID returns the callback ID.
func (e *DomainEventCallbackControlErrorMsg) GetCallbackID() int32 {
return e.CallbackID
}
// GetCallbackID returns the callback ID.
func (e *DomainEventCallbackDeviceRemovedMsg) GetCallbackID() int32 {
return e.CallbackID
}
// GetCallbackID returns the callback ID.
func (e *DomainEventCallbackTunableMsg) GetCallbackID() int32 {
return e.CallbackID
}
// GetCallbackID returns the callback ID.
func (e *DomainEventCallbackDeviceAddedMsg) GetCallbackID() int32 {
return e.CallbackID
}
// GetCallbackID returns the callback ID.
func (e *DomainEventCallbackAgentLifecycleMsg) GetCallbackID() int32 {
return e.CallbackID
}
// GetCallbackID returns the callback ID.
func (e *DomainEventCallbackMigrationIterationMsg) GetCallbackID() int32 {
return e.CallbackID
}
// GetCallbackID returns the callback ID.
func (e *DomainEventCallbackJobCompletedMsg) GetCallbackID() int32 {
return e.CallbackID
}
// GetCallbackID returns the callback ID.
func (e *DomainEventCallbackDeviceRemovalFailedMsg) GetCallbackID() int32 {
return e.CallbackID
}
// GetCallbackID returns the callback ID.
func (e *DomainEventCallbackMetadataChangeMsg) GetCallbackID() int32 {
return e.CallbackID
}
// qemuError represents a QEMU process error.
type qemuError struct {
Error struct {
Class string `json:"class"`
Description string `json:"desc"`
} `json:"error"`
}
// Capabilities returns an XML document describing the host's capabilties.
func (l *Libvirt) Capabilities() ([]byte, error) {
caps, err := l.ConnectGetCapabilities()
return []byte(caps), err
}
// called at connection time, authenticating with all supported auth types
func (l *Libvirt) authenticate() error {
// libvirt requires that we call auth-list prior to connecting,
// even when no authentication is used.
resp, err := l.AuthList()
if err != nil {
return err
}
for _, auth := range resp {
switch auth {
case constants.AuthNone:
case constants.AuthPolkit:
_, err := l.AuthPolkit()
if err != nil {
return err
}
default:
continue
}
break
}
return nil
}
func (l *Libvirt) initLibvirtComms(uri ConnectURI) error {
payload := struct {
Padding [3]byte
Name string
Flags uint32
}{
Padding: [3]byte{0x1, 0x0, 0x0},
Name: string(uri),
Flags: 0,
}
buf, err := encode(&payload)
if err != nil {
return err
}
err = l.authenticate()
if err != nil {
return err
}
_, err = l.request(constants.ProcConnectOpen, constants.Program, buf)
if err != nil {
return err
}
return nil
}
// ConnectToURI establishes communication with the specified libvirt driver
// The underlying libvirt socket connection will be created via the dialer.
// Since the connection can be lost, the Disconnected function can be used
// to monitor for a lost connection.
func (l *Libvirt) ConnectToURI(uri ConnectURI) error {
err := l.socket.Connect()
if err != nil {
return err
}
// Start watching the underlying socket connection immediately.
// If we don't, and Libvirt goes away partway through initLibvirtComms,
// then the callbacks that initLibvirtComms has registered will never
// be closed, and therefore it will be stuck waiting for data from a
// channel that will never arrive.
go l.waitAndDisconnect()
err = l.initLibvirtComms(uri)
if err != nil {
l.socket.Disconnect()
return err
}
l.disconnected = make(chan struct{})
return nil
}
// Connect establishes communication with the libvirt server.
// The underlying libvirt socket connection will be created via the dialer.
// Since the connection can be lost, the Disconnected function can be used
// to monitor for a lost connection.
func (l *Libvirt) Connect() error {
return l.ConnectToURI(QEMUSystem)
}
// Disconnect shuts down communication with the libvirt server and closes the
// underlying net.Conn.
func (l *Libvirt) Disconnect() error {
// Ordering is important here. We want to make sure the connection is closed
// before unsubscribing and deregistering the events and requests, to
// prevent new requests from racing.
_, err := l.request(constants.ProcConnectClose, constants.Program, nil)
// syscall.EINVAL is returned by the socket pkg when things have already
// been disconnected.
if err != nil && err != syscall.EINVAL {
return err
}
err = l.socket.Disconnect()
if err != nil {
return err
}
// wait for the listen goroutine to detect the lost connection and clean up
// to happen once it returns. Safeguard with a timeout.
// Things not fully cleaned up is better than a deadlock.
select {
case <-l.disconnected:
case <-time.After(disconnectTimeout):
}
return err
}
// Disconnected allows callers to detect if the underlying connection
// to libvirt has been closed. If the returned channel is closed, then
// the connection to libvirt has been lost (or disconnected intentionally).
func (l *Libvirt) Disconnected() <-chan struct{} {
return l.disconnected
}
// IsConnected indicates whether or not there is currently a connection to
// libvirtd.
func (l *Libvirt) IsConnected() bool {
select {
case <-l.Disconnected():
return false
default:
return true
}
}
// Domains returns a list of all domains managed by libvirt.
//
// Deprecated: use ConnectListAllDomains instead.
func (l *Libvirt) Domains() ([]Domain, error) {
// these are the flags as passed by `virsh list --all`
flags := ConnectListDomainsActive | ConnectListDomainsInactive
domains, _, err := l.ConnectListAllDomains(1, flags)
return domains, err
}
// DomainState returns state of the domain managed by libvirt.
//
// Deprecated: use DomainGetState instead.
func (l *Libvirt) DomainState(dom string) (DomainState, error) {
d, err := l.lookup(dom)
if err != nil {
return DomainNostate, err
}
state, _, err := l.DomainGetState(d, 0)
return DomainState(state), err
}
// SubscribeQEMUEvents streams domain events until the provided context is
// cancelled. If a problem is encountered setting up the event monitor
// connection an error will be returned. Errors encountered during streaming
// will cause the returned event channel to be closed. QEMU domain events.
func (l *Libvirt) SubscribeQEMUEvents(ctx context.Context, dom string) (<-chan DomainEvent, error) {
d, err := l.lookup(dom)
if err != nil {
return nil, err
}
callbackID, err := l.QEMUConnectDomainMonitorEventRegister([]Domain{d}, nil, 0)
if err != nil {
return nil, err
}
stream := event.NewStream(constants.QEMUProgram, callbackID)
l.addStream(stream)
ch := make(chan DomainEvent)
go func() {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
defer l.unsubscribeQEMUEvents(stream)
defer stream.Shutdown()
defer close(ch)
for {
select {
case ev, ok := <-stream.Recv():
if !ok {
return
}
ch <- *ev.(*DomainEvent)
case <-ctx.Done():
return
}
}
}()
return ch, nil
}
// unsubscribeQEMUEvents stops the flow of events from QEMU through libvirt.
func (l *Libvirt) unsubscribeQEMUEvents(stream *event.Stream) error {
err := l.QEMUConnectDomainMonitorEventDeregister(stream.CallbackID)
l.removeStream(stream.CallbackID)
return err
}
// SubscribeEvents allows the caller to subscribe to any of the event types
// supported by libvirt. The events will continue to be streamed until the
// caller cancels the provided context. After canceling the context, callers
// should wait until the channel is closed to be sure they're collected all the
// events.
func (l *Libvirt) SubscribeEvents(ctx context.Context, eventID DomainEventID,
dom OptDomain) (<-chan interface{}, error) {
callbackID, err := l.ConnectDomainEventCallbackRegisterAny(int32(eventID), nil)
if err != nil {
return nil, err
}
stream := event.NewStream(constants.QEMUProgram, callbackID)
l.addStream(stream)
ch := make(chan interface{})
go func() {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
defer l.unsubscribeEvents(stream)
defer stream.Shutdown()
defer func() { close(ch) }()
for {
select {
case ev, ok := <-stream.Recv():
if !ok {
return
}
ch <- ev
case <-ctx.Done():
return
}
}
}()
return ch, nil
}
// unsubscribeEvents stops the flow of the specified events from libvirt. There
// are two steps to this process: a call to libvirt to deregister our callback,
// and then removing the callback from the list used by the `Route` function. If
// the deregister call fails, we'll return the error, but still remove the
// callback from the list. That's ok; if any events arrive after this point, the
// Route function will drop them when it finds no registered handler.
func (l *Libvirt) unsubscribeEvents(stream *event.Stream) error {
err := l.ConnectDomainEventCallbackDeregisterAny(stream.CallbackID)
l.removeStream(stream.CallbackID)
return err
}
// LifecycleEvents streams lifecycle events until the provided context is
// cancelled. If a problem is encountered setting up the event monitor
// connection, an error will be returned. Errors encountered during streaming
// will cause the returned event channel to be closed.
func (l *Libvirt) LifecycleEvents(ctx context.Context) (<-chan DomainEventLifecycleMsg, error) {
callbackID, err := l.ConnectDomainEventCallbackRegisterAny(int32(DomainEventIDLifecycle), nil)
if err != nil {
return nil, err
}
stream := event.NewStream(constants.Program, callbackID)
l.addStream(stream)
ch := make(chan DomainEventLifecycleMsg)
go func() {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
defer l.unsubscribeEvents(stream)
defer stream.Shutdown()
defer func() { close(ch) }()
for {
select {
case ev, ok := <-stream.Recv():
if !ok {
return
}
ch <- ev.(*DomainEventCallbackLifecycleMsg).Msg
case <-ctx.Done():
return
}
}
}()
return ch, nil
}
// Run executes the given QAPI command against a domain's QEMU instance.
// For a list of available QAPI commands, see:
// http://git.qemu.org/?p=qemu.git;a=blob;f=qapi-schema.json;hb=HEAD
func (l *Libvirt) Run(dom string, cmd []byte) ([]byte, error) {
d, err := l.lookup(dom)
if err != nil {
return nil, err
}
payload := struct {
Domain Domain
Command []byte
Flags uint32
}{
Domain: d,
Command: cmd,
Flags: 0,
}
buf, err := encode(&payload)
if err != nil {
return nil, err
}
res, err := l.request(constants.QEMUProcDomainMonitorCommand, constants.QEMUProgram, buf)
if err != nil {
return nil, err
}
// check for QEMU process errors
if err = getQEMUError(res); err != nil {
return nil, err
}
r := bytes.NewReader(res.Payload)
dec := xdr.NewDecoder(r)
data, _, err := dec.DecodeFixedOpaque(int32(r.Len()))
if err != nil {
return nil, err
}
// drop QMP control characters from start of line, and drop
// any trailing NULL characters from the end
return bytes.TrimRight(data[4:], "\x00"), nil
}
// Secrets returns all secrets managed by the libvirt daemon.
//
// Deprecated: use ConnectListAllSecrets instead.
func (l *Libvirt) Secrets() ([]Secret, error) {
secrets, _, err := l.ConnectListAllSecrets(1, 0)
return secrets, err
}
// StoragePool returns the storage pool associated with the provided name.
// An error is returned if the requested storage pool is not found.
//
// Deprecated: use StoragePoolLookupByName instead.
func (l *Libvirt) StoragePool(name string) (StoragePool, error) {
return l.StoragePoolLookupByName(name)
}
// StoragePools returns a list of defined storage pools. Pools are filtered by
// the provided flags. See StoragePools*.
//
// Deprecated: use ConnectListAllStoragePools instead.
func (l *Libvirt) StoragePools(flags ConnectListAllStoragePoolsFlags) ([]StoragePool, error) {
pools, _, err := l.ConnectListAllStoragePools(1, flags)
return pools, err
}
// Undefine undefines the domain specified by dom, e.g., 'prod-lb-01'.
// The flags argument allows additional options to be specified such as
// cleaning up snapshot metadata. For more information on available
// flags, see DomainUndefine*.
//
// Deprecated: use DomainUndefineFlags instead.
func (l *Libvirt) Undefine(dom string, flags DomainUndefineFlagsValues) error {
d, err := l.lookup(dom)
if err != nil {
return err
}
return l.DomainUndefineFlags(d, flags)
}
// Destroy destroys the domain specified by dom, e.g., 'prod-lb-01'.
// The flags argument allows additional options to be specified such as
// allowing a graceful shutdown with SIGTERM than SIGKILL.
// For more information on available flags, see DomainDestroy*.
//
// Deprecated: use DomainDestroyFlags instead.
func (l *Libvirt) Destroy(dom string, flags DomainDestroyFlagsValues) error {
d, err := l.lookup(dom)
if err != nil {
return err
}
return l.DomainDestroyFlags(d, flags)
}
// XML returns a domain's raw XML definition, akin to `virsh dumpxml <domain>`.
// See DomainXMLFlag* for optional flags.
//
// Deprecated: use DomainGetXMLDesc instead.
func (l *Libvirt) XML(dom string, flags DomainXMLFlags) ([]byte, error) {
d, err := l.lookup(dom)
if err != nil {
return nil, err
}
xml, err := l.DomainGetXMLDesc(d, flags)
return []byte(xml), err
}
// DefineXML defines a domain, but does not start it.
//
// Deprecated: use DomainDefineXMLFlags instead.
func (l *Libvirt) DefineXML(x []byte, flags DomainDefineFlags) error {
_, err := l.DomainDefineXMLFlags(string(x), flags)
return err
}
// Version returns the version of the libvirt daemon.
//
// Deprecated: use ConnectGetLibVersion instead.
func (l *Libvirt) Version() (string, error) {
ver, err := l.ConnectGetLibVersion()
if err != nil {
return "", err
}
// The version is provided as an int following this formula:
// version * 1,000,000 + minor * 1000 + micro
// See src/libvirt-host.c # virConnectGetLibVersion
major := ver / 1000000
ver %= 1000000
minor := ver / 1000
ver %= 1000
micro := ver
versionString := fmt.Sprintf("%d.%d.%d", major, minor, micro)
return versionString, nil
}
// Shutdown shuts down a domain. Note that the guest OS may ignore the request.
// If flags is set to 0 then the hypervisor will choose the method of shutdown it considers best.
//
// Deprecated: use DomainShutdownFlags instead.
func (l *Libvirt) Shutdown(dom string, flags DomainShutdownFlagValues) error {
d, err := l.lookup(dom)
if err != nil {
return err
}
return l.DomainShutdownFlags(d, flags)
}
// Reboot reboots the domain. Note that the guest OS may ignore the request.
// If flags is set to zero, then the hypervisor will choose the method of shutdown it considers best.
//
// Deprecated: use DomainReboot instead.
func (l *Libvirt) Reboot(dom string, flags DomainRebootFlagValues) error {
d, err := l.lookup(dom)
if err != nil {
return err
}
return l.DomainReboot(d, flags)
}
// Reset resets domain immediately without any guest OS shutdown
//
// Deprecated: use DomainReset instead.
func (l *Libvirt) Reset(dom string) error {
d, err := l.lookup(dom)
if err != nil {
return err
}
return l.DomainReset(d, 0)
}
// BlockLimit contains a name and value pair for a Get/SetBlockIOTune limit. The
// Name field is the name of the limit (to see a list of the limits that can be
// applied, execute the 'blkdeviotune' command on a VM in virsh). Callers can
// use the QEMUBlockIO... constants below for the Name value. The Value field is
// the limit to apply.
type BlockLimit struct {
Name string
Value uint64
}
// SetBlockIOTune changes the per-device block I/O tunables within a guest.
// Parameters are the name of the VM, the name of the disk device to which the
// limits should be applied, and 1 or more BlockLimit structs containing the
// actual limits.
//
// The limits which can be applied here are enumerated in the QEMUBlockIO...
// constants above, and you can also see the full list by executing the
// 'blkdeviotune' command on a VM in virsh.
//
// Example usage:
// SetBlockIOTune("vm-name", "vda", BlockLimit{libvirt.QEMUBlockIOWriteBytesSec, 1000000})
//
// Deprecated: use DomainSetBlockIOTune instead.
func (l *Libvirt) SetBlockIOTune(dom string, disk string, limits ...BlockLimit) error {
d, err := l.lookup(dom)
if err != nil {
return err
}
params := make([]TypedParam, len(limits))
for ix, limit := range limits {
tpval := NewTypedParamValueUllong(limit.Value)
params[ix] = TypedParam{Field: limit.Name, Value: *tpval}
}
return l.DomainSetBlockIOTune(d, disk, params, uint32(DomainAffectLive))
}
// GetBlockIOTune returns a slice containing the current block I/O tunables for
// a disk.
//
// Deprecated: use DomainGetBlockIOTune instead.
func (l *Libvirt) GetBlockIOTune(dom string, disk string) ([]BlockLimit, error) {
d, err := l.lookup(dom)
if err != nil {
return nil, err
}
lims, _, err := l.DomainGetBlockIOTune(d, []string{disk}, 32, uint32(TypedParamStringOkay))
if err != nil {
return nil, err
}
var limits []BlockLimit
// now decode each of the returned TypedParams. To do this we read the field
// name and type, then use the type information to decode the value.
for _, lim := range lims {
var l BlockLimit
name := lim.Field
switch lim.Value.I.(type) {
case uint64:
l = BlockLimit{Name: name, Value: lim.Value.I.(uint64)}
}
limits = append(limits, l)
}
return limits, nil
}
// lookup returns a domain as seen by libvirt.
func (l *Libvirt) lookup(name string) (Domain, error) {
return l.DomainLookupByName(name)
}
// getQEMUError checks the provided response for QEMU process errors.
// If an error is found, it is extracted an returned, otherwise nil.
func getQEMUError(r response) error {
pl := bytes.NewReader(r.Payload)
dec := xdr.NewDecoder(pl)
s, _, err := dec.DecodeString()
if err != nil {
return err
}
var e qemuError
if err = json.Unmarshal([]byte(s), &e); err != nil {
return err
}
if e.Error.Description != "" {
return errors.New(e.Error.Description)
}
return nil
}
func (l *Libvirt) waitAndDisconnect() {
// wait for the socket to indicate if/when it's been disconnected
<-l.socket.Disconnected()
// close event streams
l.removeAllStreams()
// Deregister all callbacks to prevent blocking on clients with
// outstanding requests
l.deregisterAll()
select {
case <-l.disconnected:
// l.disconnected is already closed, i.e., Libvirt.ConnectToURI
// was unable to complete all phases of its connection and
// so this hadn't been assigned to an open channel yet (it
// is set to a closed channel in Libvirt.New*)
//
// Just return to avoid closing an already-closed channel.
return
default:
// if we make it here then reading from l.disconnected is blocking,
// which suggests that it is open and must be closed.
}
close(l.disconnected)
}
// NewWithDialer configures a new Libvirt object that can be used to perform
// RPCs via libvirt's socket. The actual connection will not be established
// until Connect is called. The same Libvirt object may be used to re-connect
// multiple times.
func NewWithDialer(dialer socket.Dialer) *Libvirt {
l := &Libvirt{
s: 0,
disconnected: make(chan struct{}),
callbacks: make(map[int32]chan response),
events: make(map[int32]*event.Stream),
}
l.socket = socket.New(dialer, l)
// we start with a closed channel since that indicates no connection
close(l.disconnected)
return l
}
// New configures a new Libvirt RPC connection.
// This function only remains to retain backwards compatability.
// When Libvirt's Connect function is called, the Dial will simply return the
// connection passed in here and start a goroutine listening/reading from it.
// If at any point the Disconnect function is called, any subsequent Connect
// call will simply return an already closed connection.
//
// Deprecated: Please use NewWithDialer.
func New(conn net.Conn) *Libvirt {
return NewWithDialer(dialers.NewAlreadyConnected(conn))
}
// NetworkUpdateCompat is a wrapper over NetworkUpdate which swaps `Command` and `Section` when needed.
// This function must be used instead of NetworkUpdate to be sure that the
// NetworkUpdate call works both with older and newer libvirtd connections.
//
// libvirt on-wire protocol had a bug for a long time where Command and Section
// were reversed. It's been fixed in newer libvirt versions, and backported to
// some older versions. This helper detects what argument order libvirtd expects
// and makes the correct NetworkUpdate call.
func (l *Libvirt) NetworkUpdateCompat(Net Network, Command NetworkUpdateCommand, Section NetworkUpdateSection, ParentIndex int32, XML string, Flags NetworkUpdateFlags) (err error) {
// This is defined in libvirt/src/libvirt_internal.h and thus not available in go-libvirt autogenerated code
const virDrvFeatureNetworkUpdateHasCorrectOrder = 16
hasCorrectOrder, err := l.ConnectSupportsFeature(virDrvFeatureNetworkUpdateHasCorrectOrder)
if err != nil {
return fmt.Errorf("failed to confirm argument order for NetworkUpdate: %w", err)
}
// https://gitlab.com/libvirt/libvirt/-/commit/b0f78d626a18bcecae3a4d165540ab88bfbfc9ee
if hasCorrectOrder == 0 {
return l.NetworkUpdate(Net, uint32(Section), uint32(Command), ParentIndex, XML, Flags)
}
return l.NetworkUpdate(Net, uint32(Command), uint32(Section), ParentIndex, XML, Flags)
}
-64
View File
@@ -1,64 +0,0 @@
# Configuration file for c-for-go, which go-libvirt uses to translate the const
# and type definitions from the C-language sources in the libvirt project into
# Go. This file is used by the c-for-go binary (github.com/xlab/c-for-go), which
# is called when 'go generate' is run. See libvirt.go for the command line used.
---
GENERATOR:
PackageName: libvirt
PackageLicense: |
Copyright 2018 The go-libvirt Authors.
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.
Includes: []
PARSER:
# We can't use environment variables here, but we don't want to process the
# libvirt version installed in the system folders (if any). Instead we'll
# rely on our caller to link the libvirt source directory to lv_source/, and
# run on that code. This isn't ideal, but changes to c-for-go are needed to
# fix it.
IncludePaths: [./lv_source/include, ./lv_source/build/include]
SourcesPaths:
- libvirt/libvirt.h
- libvirt/virterror.h
TRANSLATOR:
ConstRules:
defines: eval
Rules:
global:
- {action: accept, from: "^vir"}
post-global:
- {action: replace, from: "^vir"}
- {load: snakecase}
# Follow golint's capitalization conventions.
- {action: replace, from: "Api([A-Z]|$)", to: "API$1"}
- {action: replace, from: "Cpu([A-Z]|$)", to: "CPU$1"}
- {action: replace, from: "Dns([A-Z]|$)", to: "DNS$1"}
- {action: replace, from: "Eof([A-Z]|$)", to: "EOF$1"}
- {action: replace, from: "Id([A-Z]|$)", to: "ID$1"}
- {action: replace, from: "Ip([A-Z]|$)", to: "IP$1"}
- {action: replace, from: "Tls([A-Z]|$)", to: "TLS$1"}
- {action: replace, from: "Uuid([A-Z]|$)", to: "UUID$1"}
- {action: replace, from: "Uri([A-Z]|$)", to: "URI$1"}
- {action: replace, from: "Vcpu([A-Z]|$)", to: "VCPU$1"}
- {action: replace, from: "Xml([A-Z]|$)", to: "XML$1"}
- {action: replace, from: "Rpc([A-Z]|$)", to: "RPC$1"}
- {action: replace, from: "Ssh([A-Z]|$)", to: "SSH$1"}
- {action: replace, from: "Http([A-Z]|$)", to: "HTTP$1"}
- {transform: unexport, from: "^From"}
const:
- {action: accept, from: "^VIR_"}
# Special case to prevent a collision with a type:
- {action: replace, from: "^VIR_DOMAIN_JOB_OPERATION", to: "VIR_DOMAIN_JOB_OPERATION_STR"}
- {transform: lower}
-292
View File
@@ -1,292 +0,0 @@
// Copyright 2018 The go-libvirt Authors.
//
// 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.
//
// Code generated by internal/lvgen/generate.go. DO NOT EDIT.
//
// To regenerate, run 'go generate' in internal/lvgen.
//
package libvirt
import (
"bytes"
"io"
"github.com/digitalocean/go-libvirt/internal/constants"
"github.com/digitalocean/go-libvirt/internal/go-xdr/xdr2"
)
// References to prevent "imported and not used" errors.
var (
_ = bytes.Buffer{}
_ = io.Copy
_ = constants.Program
_ = xdr.Unmarshal
)
//
// Typedefs:
//
//
// Enums:
//
// QEMUProcedure is libvirt's qemu_procedure
type QEMUProcedure int32
//
// Structs:
//
// QEMUDomainMonitorCommandArgs is libvirt's qemu_domain_monitor_command_args
type QEMUDomainMonitorCommandArgs struct {
Dom Domain
Cmd string
Flags uint32
}
// QEMUDomainMonitorCommandRet is libvirt's qemu_domain_monitor_command_ret
type QEMUDomainMonitorCommandRet struct {
Result string
}
// QEMUDomainAttachArgs is libvirt's qemu_domain_attach_args
type QEMUDomainAttachArgs struct {
PidValue uint32
Flags uint32
}
// QEMUDomainAttachRet is libvirt's qemu_domain_attach_ret
type QEMUDomainAttachRet struct {
Dom Domain
}
// QEMUDomainAgentCommandArgs is libvirt's qemu_domain_agent_command_args
type QEMUDomainAgentCommandArgs struct {
Dom Domain
Cmd string
Timeout int32
Flags uint32
}
// QEMUDomainAgentCommandRet is libvirt's qemu_domain_agent_command_ret
type QEMUDomainAgentCommandRet struct {
Result OptString
}
// QEMUConnectDomainMonitorEventRegisterArgs is libvirt's qemu_connect_domain_monitor_event_register_args
type QEMUConnectDomainMonitorEventRegisterArgs struct {
Dom OptDomain
Event OptString
Flags uint32
}
// QEMUConnectDomainMonitorEventRegisterRet is libvirt's qemu_connect_domain_monitor_event_register_ret
type QEMUConnectDomainMonitorEventRegisterRet struct {
CallbackID int32
}
// QEMUConnectDomainMonitorEventDeregisterArgs is libvirt's qemu_connect_domain_monitor_event_deregister_args
type QEMUConnectDomainMonitorEventDeregisterArgs struct {
CallbackID int32
}
// QEMUDomainMonitorEventMsg is libvirt's qemu_domain_monitor_event_msg
type QEMUDomainMonitorEventMsg struct {
CallbackID int32
Dom Domain
Event string
Seconds int64
Micros uint32
Details OptString
}
// QEMUDomainMonitorCommand is the go wrapper for QEMU_PROC_DOMAIN_MONITOR_COMMAND.
func (l *Libvirt) QEMUDomainMonitorCommand(Dom Domain, Cmd string, Flags uint32) (rResult string, err error) {
var buf []byte
args := QEMUDomainMonitorCommandArgs {
Dom: Dom,
Cmd: Cmd,
Flags: Flags,
}
buf, err = encode(&args)
if err != nil {
return
}
var r response
r, err = l.requestStream(1, constants.QEMUProgram, buf, nil, nil)
if err != nil {
return
}
// Return value unmarshaling
tpd := typedParamDecoder{}
ct := map[string]xdr.TypeDecoder{"libvirt.TypedParam": tpd}
rdr := bytes.NewReader(r.Payload)
dec := xdr.NewDecoderCustomTypes(rdr, 0, ct)
// Result: string
_, err = dec.Decode(&rResult)
if err != nil {
return
}
return
}
// QEMUDomainAttach is the go wrapper for QEMU_PROC_DOMAIN_ATTACH.
func (l *Libvirt) QEMUDomainAttach(PidValue uint32, Flags uint32) (rDom Domain, err error) {
var buf []byte
args := QEMUDomainAttachArgs {
PidValue: PidValue,
Flags: Flags,
}
buf, err = encode(&args)
if err != nil {
return
}
var r response
r, err = l.requestStream(2, constants.QEMUProgram, buf, nil, nil)
if err != nil {
return
}
// Return value unmarshaling
tpd := typedParamDecoder{}
ct := map[string]xdr.TypeDecoder{"libvirt.TypedParam": tpd}
rdr := bytes.NewReader(r.Payload)
dec := xdr.NewDecoderCustomTypes(rdr, 0, ct)
// Dom: Domain
_, err = dec.Decode(&rDom)
if err != nil {
return
}
return
}
// QEMUDomainAgentCommand is the go wrapper for QEMU_PROC_DOMAIN_AGENT_COMMAND.
func (l *Libvirt) QEMUDomainAgentCommand(Dom Domain, Cmd string, Timeout int32, Flags uint32) (rResult OptString, err error) {
var buf []byte
args := QEMUDomainAgentCommandArgs {
Dom: Dom,
Cmd: Cmd,
Timeout: Timeout,
Flags: Flags,
}
buf, err = encode(&args)
if err != nil {
return
}
var r response
r, err = l.requestStream(3, constants.QEMUProgram, buf, nil, nil)
if err != nil {
return
}
// Return value unmarshaling
tpd := typedParamDecoder{}
ct := map[string]xdr.TypeDecoder{"libvirt.TypedParam": tpd}
rdr := bytes.NewReader(r.Payload)
dec := xdr.NewDecoderCustomTypes(rdr, 0, ct)
// Result: OptString
_, err = dec.Decode(&rResult)
if err != nil {
return
}
return
}
// QEMUConnectDomainMonitorEventRegister is the go wrapper for QEMU_PROC_CONNECT_DOMAIN_MONITOR_EVENT_REGISTER.
func (l *Libvirt) QEMUConnectDomainMonitorEventRegister(Dom OptDomain, Event OptString, Flags uint32) (rCallbackID int32, err error) {
var buf []byte
args := QEMUConnectDomainMonitorEventRegisterArgs {
Dom: Dom,
Event: Event,
Flags: Flags,
}
buf, err = encode(&args)
if err != nil {
return
}
var r response
r, err = l.requestStream(4, constants.QEMUProgram, buf, nil, nil)
if err != nil {
return
}
// Return value unmarshaling
tpd := typedParamDecoder{}
ct := map[string]xdr.TypeDecoder{"libvirt.TypedParam": tpd}
rdr := bytes.NewReader(r.Payload)
dec := xdr.NewDecoderCustomTypes(rdr, 0, ct)
// CallbackID: int32
_, err = dec.Decode(&rCallbackID)
if err != nil {
return
}
return
}
// QEMUConnectDomainMonitorEventDeregister is the go wrapper for QEMU_PROC_CONNECT_DOMAIN_MONITOR_EVENT_DEREGISTER.
func (l *Libvirt) QEMUConnectDomainMonitorEventDeregister(CallbackID int32) (err error) {
var buf []byte
args := QEMUConnectDomainMonitorEventDeregisterArgs {
CallbackID: CallbackID,
}
buf, err = encode(&args)
if err != nil {
return
}
_, err = l.requestStream(5, constants.QEMUProgram, buf, nil, nil)
if err != nil {
return
}
return
}
// QEMUDomainMonitorEvent is the go wrapper for QEMU_PROC_DOMAIN_MONITOR_EVENT.
func (l *Libvirt) QEMUDomainMonitorEvent() (err error) {
var buf []byte
_, err = l.requestStream(6, constants.QEMUProgram, buf, nil, nil)
if err != nil {
return
}
return
}
File diff suppressed because it is too large Load Diff
-481
View File
@@ -1,481 +0,0 @@
// Copyright 2018 The go-libvirt Authors.
//
// 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 libvirt
import (
"bytes"
"errors"
"fmt"
"io"
"reflect"
"strings"
"sync/atomic"
"github.com/digitalocean/go-libvirt/internal/constants"
"github.com/digitalocean/go-libvirt/internal/event"
xdr "github.com/digitalocean/go-libvirt/internal/go-xdr/xdr2"
"github.com/digitalocean/go-libvirt/socket"
)
// ErrUnsupported is returned if a procedure is not supported by libvirt
var ErrUnsupported = errors.New("unsupported procedure requested")
// ErrInterrupted is returned if the socket is closed while waiting for the
// result of a procedure call.
var ErrInterrupted = errors.New("procedure interrupted while awaiting response")
// internal rpc response
type response struct {
Payload []byte
Status uint32
}
// Error reponse from libvirt
type Error struct {
Code uint32
Message string
}
func (e Error) Error() string {
return e.Message
}
// checkError is used to check whether an error is a libvirtError, and if it is,
// whether its error code matches the one passed in. It will return false if
// these conditions are not met.
func checkError(err error, expectedError ErrorNumber) bool {
for err != nil {
e, ok := err.(Error)
if ok {
return e.Code == uint32(expectedError)
}
err = errors.Unwrap(err)
}
return false
}
// IsNotFound detects libvirt's ERR_NO_DOMAIN.
func IsNotFound(err error) bool {
return checkError(err, ErrNoDomain)
}
// callback sends RPC responses to respective callers.
func (l *Libvirt) callback(id int32, res response) {
l.cmux.Lock()
defer l.cmux.Unlock()
c, ok := l.callbacks[id]
if !ok {
return
}
c <- res
}
// Route sends incoming packets to their listeners.
func (l *Libvirt) Route(h *socket.Header, buf []byte) {
// Route events to their respective listener
var event event.Event
switch h.Program {
case constants.QEMUProgram:
if h.Procedure != constants.QEMUProcDomainMonitorEvent {
break
}
event = &DomainEvent{}
case constants.Program:
event = eventFromProcedureID(h.Procedure)
}
if event != nil {
err := eventDecoder(buf, event)
if err != nil { // event was malformed, drop.
return
}
l.stream(event)
return
}
// send response to caller
l.callback(h.Serial, response{Payload: buf, Status: h.Status})
}
func eventFromProcedureID(procID uint32) event.Event {
switch procID {
case constants.ProcDomainEventCallbackLifecycle:
return &DomainEventCallbackLifecycleMsg{}
case constants.ProcDomainEventCallbackReboot:
return &DomainEventCallbackRebootMsg{}
case constants.ProcDomainEventCallbackRtcChange:
return &DomainEventCallbackRtcChangeMsg{}
case constants.ProcDomainEventCallbackWatchdog:
return &DomainEventCallbackWatchdogMsg{}
case constants.ProcDomainEventCallbackIOError:
return &DomainEventCallbackIOErrorMsg{}
case constants.ProcDomainEventCallbackIOErrorReason:
return &DomainEventCallbackIOErrorReasonMsg{}
case constants.ProcDomainEventCallbackGraphics:
return &DomainEventCallbackGraphicsMsg{}
case constants.ProcDomainEventCallbackBlockJob:
return &DomainEventCallbackBlockJobMsg{}
case constants.ProcDomainEventCallbackDiskChange:
return &DomainEventCallbackDiskChangeMsg{}
case constants.ProcDomainEventCallbackTrayChange:
return &DomainEventCallbackTrayChangeMsg{}
case constants.ProcDomainEventCallbackPmwakeup:
return &DomainEventCallbackPmwakeupMsg{}
case constants.ProcDomainEventCallbackPmsuspend:
return &DomainEventCallbackPmsuspendMsg{}
case constants.ProcDomainEventCallbackBalloonChange:
return &DomainEventCallbackBalloonChangeMsg{}
case constants.ProcDomainEventCallbackPmsuspendDisk:
return &DomainEventCallbackPmsuspendDiskMsg{}
case constants.ProcDomainEventCallbackControlError:
return &DomainEventCallbackControlErrorMsg{}
case constants.ProcDomainEventCallbackDeviceRemoved:
return &DomainEventCallbackDeviceRemovedMsg{}
case constants.ProcDomainEventCallbackTunable:
return &DomainEventCallbackTunableMsg{}
case constants.ProcDomainEventCallbackDeviceAdded:
return &DomainEventCallbackDeviceAddedMsg{}
case constants.ProcDomainEventCallbackAgentLifecycle:
return &DomainEventCallbackAgentLifecycleMsg{}
case constants.ProcDomainEventCallbackMigrationIteration:
return &DomainEventCallbackMigrationIterationMsg{}
case constants.ProcDomainEventCallbackJobCompleted:
return &DomainEventCallbackJobCompletedMsg{}
case constants.ProcDomainEventCallbackDeviceRemovalFailed:
return &DomainEventCallbackDeviceRemovalFailedMsg{}
case constants.ProcDomainEventCallbackMetadataChange:
return &DomainEventCallbackMetadataChangeMsg{}
}
return nil
}
// serial provides atomic access to the next sequential request serial number.
func (l *Libvirt) serial() int32 {
return atomic.AddInt32(&l.s, 1)
}
// stream decodes and relays domain events to their respective listener.
func (l *Libvirt) stream(e event.Event) {
l.emux.RLock()
defer l.emux.RUnlock()
q, ok := l.events[e.GetCallbackID()]
if !ok {
return
}
q.Push(e)
}
// addStream configures the routing for an event stream.
func (l *Libvirt) addStream(s *event.Stream) {
l.emux.Lock()
defer l.emux.Unlock()
l.events[s.CallbackID] = s
}
// removeStream deletes an event stream. The caller should first notify libvirt
// to stop sending events for this stream. Subsequent calls to removeStream are
// idempotent and return nil.
func (l *Libvirt) removeStream(id int32) error {
l.emux.Lock()
defer l.emux.Unlock()
// if the event is already removed, just return nil
q, ok := l.events[id]
if ok {
delete(l.events, id)
q.Shutdown()
}
return nil
}
// removeAllStreams deletes all event streams. This is meant to be used to
// clean up only once the underlying connection to libvirt is disconnected and
// thus does not attempt to notify libvirt to stop sending events.
func (l *Libvirt) removeAllStreams() {
l.emux.Lock()
defer l.emux.Unlock()
for _, ev := range l.events {
ev.Shutdown()
delete(l.events, ev.CallbackID)
}
}
// register configures a method response callback
func (l *Libvirt) register(id int32, c chan response) {
l.cmux.Lock()
defer l.cmux.Unlock()
l.callbacks[id] = c
}
// deregister destroys a method response callback. It is the responsibility of
// the caller to manage locking (l.cmux) during this call.
func (l *Libvirt) deregister(id int32) {
_, ok := l.callbacks[id]
if !ok {
return
}
close(l.callbacks[id])
delete(l.callbacks, id)
}
// deregisterAll closes all waiting callback channels. This is used to clean up
// if the connection to libvirt is lost. Callers waiting for responses will
// return an error when the response channel is closed, rather than just
// hanging.
func (l *Libvirt) deregisterAll() {
l.cmux.Lock()
defer l.cmux.Unlock()
for id := range l.callbacks {
l.deregister(id)
}
}
// request performs a libvirt RPC request.
// returns response returned by server.
// if response is not OK, decodes error from it and returns it.
func (l *Libvirt) request(proc uint32, program uint32, payload []byte) (response, error) {
return l.requestStream(proc, program, payload, nil, nil)
}
// requestStream performs a libvirt RPC request. The `out` and `in` parameters
// are optional, and should be nil when RPC endpoints don't return a stream.
func (l *Libvirt) requestStream(proc uint32, program uint32, payload []byte,
out io.Reader, in io.Writer) (response, error) {
serial := l.serial()
c := make(chan response)
l.register(serial, c)
defer func() {
l.cmux.Lock()
defer l.cmux.Unlock()
l.deregister(serial)
}()
err := l.socket.SendPacket(serial, proc, program, payload, socket.Call,
socket.StatusOK)
if err != nil {
return response{}, err
}
resp, err := l.getResponse(c)
if err != nil {
return resp, err
}
if out != nil {
abort := make(chan bool)
outErr := make(chan error)
go func() {
outErr <- l.socket.SendStream(serial, proc, program, out, abort)
}()
// Even without incoming stream server sends confirmation once all data is received
resp, err = l.processIncomingStream(c, in)
if err != nil {
abort <- true
return resp, err
}
err = <-outErr
if err != nil {
return response{}, err
}
}
switch in {
case nil:
return resp, nil
default:
return l.processIncomingStream(c, in)
}
}
// processIncomingStream is called once we've successfully sent a request to
// libvirt. It writes the responses back to the stream passed by the caller
// until libvirt sends a packet with statusOK or an error.
func (l *Libvirt) processIncomingStream(c chan response, inStream io.Writer) (response, error) {
for {
resp, err := l.getResponse(c)
if err != nil {
return resp, err
}
// StatusOK indicates end of stream
if resp.Status == socket.StatusOK {
return resp, nil
}
// FIXME: this smells.
// StatusError is handled in getResponse, so this must be StatusContinue
// StatusContinue is only valid here for stream packets
// libvirtd breaks protocol and returns StatusContinue with an
// empty response Payload when the stream finishes
if len(resp.Payload) == 0 {
return resp, nil
}
if inStream != nil {
_, err = inStream.Write(resp.Payload)
if err != nil {
return response{}, err
}
}
}
}
func (l *Libvirt) getResponse(c chan response) (response, error) {
resp, ok := <-c
if !ok {
// The channel was closed before a response was received. This means
// that the socket was unexpectedly closed during the RPC call. In
// this case, we must assume the worst, such as libvirt crashed while
// attempting to execute the call.
return resp, ErrInterrupted
}
if resp.Status == socket.StatusError {
return resp, decodeError(resp.Payload)
}
return resp, nil
}
// encode XDR encodes the provided data.
func encode(data interface{}) ([]byte, error) {
var buf bytes.Buffer
_, err := xdr.Marshal(&buf, data)
return buf.Bytes(), err
}
// decodeError extracts an error message from the provider buffer.
func decodeError(buf []byte) error {
dec := xdr.NewDecoder(bytes.NewReader(buf))
e := struct {
Code uint32
DomainID uint32
Padding uint8
Message string
Level uint32
}{}
_, err := dec.Decode(&e)
if err != nil {
return err
}
if strings.Contains(e.Message, "unknown procedure") {
return ErrUnsupported
}
// if libvirt returns ERR_OK, ignore the error
if ErrorNumber(e.Code) == ErrOk {
return nil
}
return Error{Code: uint32(e.Code), Message: e.Message}
}
// eventDecoder decodes an event from a xdr buffer.
func eventDecoder(buf []byte, e interface{}) error {
dec := xdr.NewDecoder(bytes.NewReader(buf))
_, err := dec.Decode(e)
return err
}
type typedParamDecoder struct{}
// Decode decodes a TypedParam. These are part of the libvirt spec, and not xdr
// proper. TypedParams contain a name, which is called Field for some reason,
// and a Value, which itself has a "discriminant" - an integer enum encoding the
// actual type, and a value, the length of which varies based on the actual
// type.
func (tpd typedParamDecoder) Decode(d *xdr.Decoder, v reflect.Value) (int, error) {
// Get the name of the typed param first
name, n, err := d.DecodeString()
if err != nil {
return n, err
}
val, n2, err := tpd.decodeTypedParamValue(d)
n += n2
if err != nil {
return n, err
}
tp := &TypedParam{Field: name, Value: *val}
v.Set(reflect.ValueOf(*tp))
return n, nil
}
// decodeTypedParamValue decodes the Value part of a TypedParam.
func (typedParamDecoder) decodeTypedParamValue(d *xdr.Decoder) (*TypedParamValue, int, error) {
// All TypedParamValues begin with a uint32 discriminant that tells us what
// type they are.
discriminant, n, err := d.DecodeUint()
if err != nil {
return nil, n, err
}
var n2 int
var tpv *TypedParamValue
switch discriminant {
case 1:
var val int32
n2, err = d.Decode(&val)
tpv = &TypedParamValue{D: discriminant, I: val}
case 2:
var val uint32
n2, err = d.Decode(&val)
tpv = &TypedParamValue{D: discriminant, I: val}
case 3:
var val int64
n2, err = d.Decode(&val)
tpv = &TypedParamValue{D: discriminant, I: val}
case 4:
var val uint64
n2, err = d.Decode(&val)
tpv = &TypedParamValue{D: discriminant, I: val}
case 5:
var val float64
n2, err = d.Decode(&val)
tpv = &TypedParamValue{D: discriminant, I: val}
case 6:
var val int32
n2, err = d.Decode(&val)
tpv = &TypedParamValue{D: discriminant, I: val}
case 7:
var val string
n2, err = d.Decode(&val)
tpv = &TypedParamValue{D: discriminant, I: val}
default:
err = fmt.Errorf("invalid parameter type %v", discriminant)
}
n += n2
return tpv, n, err
}
@@ -1,26 +0,0 @@
package dialers
import (
"net"
)
// AlreadyConnected implements a dialer interface for a connection that was
// established prior to initializing the socket object. This exists solely
// for backwards compatability with the previous implementation of Libvirt
// that took an already established connection.
type AlreadyConnected struct {
c net.Conn
}
// NewAlreadyConnected is a noop dialer to simply use a connection previously
// established. This means any re-dial attempts simply won't work.
func NewAlreadyConnected(c net.Conn) AlreadyConnected {
return AlreadyConnected{c}
}
// Dial just returns the connection previously established.
// If at some point it is disconnected by the client, this obviously does *not*
// re-dial and will simply return the already closed connection.
func (a AlreadyConnected) Dial() (net.Conn, error) {
return a.c, nil
}
-57
View File
@@ -1,57 +0,0 @@
package dialers
import (
"net"
"time"
)
const (
// defaultSocket specifies the default path to the libvirt unix socket.
defaultSocket = "/var/run/libvirt/libvirt-sock"
// defaultLocalTimeout specifies the default libvirt dial timeout.
defaultLocalTimeout = 15 * time.Second
)
// Local implements connecting to a local libvirtd over the unix socket.
type Local struct {
timeout time.Duration
socket string
}
// LocalOption is a function for setting local socket options.
type LocalOption func(*Local)
// WithLocalTimeout sets the dial timeout.
func WithLocalTimeout(timeout time.Duration) LocalOption {
return func(l *Local) {
l.timeout = timeout
}
}
// WithSocket sets the path to the local libvirt socket.
func WithSocket(socket string) LocalOption {
return func(l *Local) {
l.socket = socket
}
}
// NewLocal is a default dialer to simply connect to a locally running libvirt's
// socket.
func NewLocal(opts ...LocalOption) *Local {
l := &Local{
timeout: defaultLocalTimeout,
socket: defaultSocket,
}
for _, opt := range opts {
opt(l)
}
return l
}
// Dial connects to a local socket
func (l *Local) Dial() (net.Conn, error) {
return net.DialTimeout("unix", l.socket, l.timeout)
}
-61
View File
@@ -1,61 +0,0 @@
package dialers
import (
"net"
"time"
)
const (
// defaultRemotePort specifies the default libvirtd port.
defaultRemotePort = "16509"
// defaultRemoteTimeout specifies the default libvirt dial timeout.
defaultRemoteTimeout = 20 * time.Second
)
// Remote implements connecting to a remote server's libvirt using tcp
type Remote struct {
timeout time.Duration
host, port string
}
// RemoteOption is a function for setting remote dialer options.
type RemoteOption func(*Remote)
// WithRemoteTimeout sets the dial timeout.
func WithRemoteTimeout(timeout time.Duration) RemoteOption {
return func(r *Remote) {
r.timeout = timeout
}
}
// UsePort sets the port to dial for libirt on the target host server.
func UsePort(port string) RemoteOption {
return func(r *Remote) {
r.port = port
}
}
// NewRemote is a dialer for connecting to libvirt running on another server.
func NewRemote(hostAddr string, opts ...RemoteOption) *Remote {
r := &Remote{
timeout: defaultRemoteTimeout,
host: hostAddr,
port: defaultRemotePort,
}
for _, opt := range opts {
opt(r)
}
return r
}
// Dial connects to libvirt running on another server.
func (r *Remote) Dial() (net.Conn, error) {
return net.DialTimeout(
"tcp",
net.JoinHostPort(r.host, r.port),
r.timeout,
)
}
-376
View File
@@ -1,376 +0,0 @@
package socket
import (
"bufio"
"encoding/binary"
"errors"
"io"
"net"
"sync"
"syscall"
"time"
"unsafe"
"github.com/digitalocean/go-libvirt/internal/constants"
)
const disconnectTimeout = 5 * time.Second
// request and response statuses
const (
// StatusOK is always set for method calls or events.
// For replies it indicates successful completion of the method.
// For streams it indicates confirmation of the end of file on the stream.
StatusOK = iota
// StatusError for replies indicates that the method call failed
// and error information is being returned. For streams this indicates
// that not all data was sent and the stream has aborted.
StatusError
// StatusContinue is only used for streams.
// This indicates that further data packets will be following.
StatusContinue
)
// request and response types
const (
// Call is used when making calls to the remote server.
Call = iota
// Reply indicates a server reply.
Reply
// Message is an asynchronous notification.
Message
// Stream represents a stream data packet.
Stream
// CallWithFDs is used by a client to indicate the request has
// arguments with file descriptors.
CallWithFDs
// ReplyWithFDs is used by a server to indicate the request has
// arguments with file descriptors.
ReplyWithFDs
)
// Dialer is an interface for connecting to libvirt's underlying socket.
type Dialer interface {
Dial() (net.Conn, error)
}
// Router is an interface used to route packets to the appropriate clients.
type Router interface {
Route(*Header, []byte)
}
// Socket represents a libvirt Socket and its connection state
type Socket struct {
dialer Dialer
router Router
conn net.Conn
reader *bufio.Reader
writer *bufio.Writer
// used to serialize any Socket writes and any updates to conn, r, or w
mu *sync.Mutex
// disconnected is closed when the listen goroutine associated with a
// Socket connection has returned.
disconnected chan struct{}
}
// packet represents a RPC request or response.
type packet struct {
// Size of packet, in bytes, including length.
// Len + Header + Payload
Len uint32
Header Header
}
// Global packet instance, for use with unsafe.Sizeof()
var _p packet
// Header is a libvirt rpc packet header
type Header struct {
// Program identifier
Program uint32
// Program version
Version uint32
// Remote procedure identifier
Procedure uint32
// Call type, e.g., Reply
Type uint32
// Call serial number
Serial int32
// Request status, e.g., StatusOK
Status uint32
}
// New initializes a new type for managing the Socket.
func New(dialer Dialer, router Router) *Socket {
s := &Socket{
dialer: dialer,
router: router,
disconnected: make(chan struct{}),
mu: &sync.Mutex{},
}
// we start with a closed channel since that indicates no connection
close(s.disconnected)
return s
}
// Connect uses the dialer provided on creation to establish
// underlying physical connection to the desired libvirt.
func (s *Socket) Connect() error {
s.mu.Lock()
defer s.mu.Unlock()
if !s.isDisconnected() {
return errors.New("already connected to socket")
}
conn, err := s.dialer.Dial()
if err != nil {
return err
}
s.conn = conn
s.reader = bufio.NewReader(conn)
s.writer = bufio.NewWriter(conn)
s.disconnected = make(chan struct{})
go s.listenAndRoute()
return nil
}
// Disconnect closes the Socket connection to libvirt and waits for the reader
// gorouting to shut down.
func (s *Socket) Disconnect() error {
// just return if we're already disconnected
if s.isDisconnected() {
return nil
}
err := s.conn.Close()
if err != nil {
return err
}
// now we wait for the reader to return so as not to avoid it nil
// referencing
// Put this in a select,
// and have it only nil out the conn value if it doesn't fail
select {
case <-s.disconnected:
case <-time.After(disconnectTimeout):
return errors.New("timed out waiting for Disconnect cleanup")
}
return nil
}
// Disconnected returns a channel that will be closed once the current
// connection is closed. This can happen due to an explicit call to Disconnect
// from the client, or due to non-temporary Read or Write errors encountered.
func (s *Socket) Disconnected() <-chan struct{} {
return s.disconnected
}
// isDisconnected is a non-blocking function to query whether a connection
// is disconnected or not.
func (s *Socket) isDisconnected() bool {
select {
case <-s.disconnected:
return true
default:
return false
}
}
// listenAndRoute reads packets from the Socket and calls the provided
// Router function to route them
func (s *Socket) listenAndRoute() {
// only returns once it detects a non-temporary error related to the
// underlying connection
listen(s.reader, s.router)
// signal any clients listening that the connection has been disconnected
close(s.disconnected)
}
// listen processes incoming data and routes
// responses to their respective callback handler.
func listen(s io.Reader, router Router) {
for {
// response packet length
length, err := pktlen(s)
if err != nil {
if isTemporary(err) {
continue
}
// connection is no longer valid, so shutdown
return
}
// response header
h, err := extractHeader(s)
if err != nil {
// invalid packet
continue
}
// payload: packet length minus what was previously read
size := int(length) - int(unsafe.Sizeof(_p))
buf := make([]byte, size)
_, err = io.ReadFull(s, buf)
if err != nil {
// invalid packet
continue
}
// route response to caller
router.Route(h, buf)
}
}
// isTemporary returns true if the error returned from a read is transient.
// If the error type is an OpError, check whether the net connection
// error condition is temporary (which means we can keep using the
// connection).
// Errors not of the net.OpError type tend to be things like io.EOF,
// syscall.EINVAL, or io.ErrClosedPipe (i.e. all things that
// indicate the connection in use is no longer valid.)
func isTemporary(err error) bool {
opErr, ok := err.(*net.OpError)
if ok {
return opErr.Temporary()
}
return false
}
// pktlen returns the length of an incoming RPC packet. Read errors will
// result in a returned response length of 0 and a non-nil error.
func pktlen(r io.Reader) (uint32, error) {
buf := make([]byte, unsafe.Sizeof(_p.Len))
// extract the packet's length from the header
_, err := io.ReadFull(r, buf)
if err != nil {
return 0, err
}
return binary.BigEndian.Uint32(buf), nil
}
// extractHeader returns the decoded header from an incoming response.
func extractHeader(r io.Reader) (*Header, error) {
buf := make([]byte, unsafe.Sizeof(_p.Header))
// extract the packet's header from r
_, err := io.ReadFull(r, buf)
if err != nil {
return nil, err
}
return &Header{
Program: binary.BigEndian.Uint32(buf[0:4]),
Version: binary.BigEndian.Uint32(buf[4:8]),
Procedure: binary.BigEndian.Uint32(buf[8:12]),
Type: binary.BigEndian.Uint32(buf[12:16]),
Serial: int32(binary.BigEndian.Uint32(buf[16:20])),
Status: binary.BigEndian.Uint32(buf[20:24]),
}, nil
}
// SendPacket sends a packet to libvirt on the socket connection.
func (s *Socket) SendPacket(
serial int32,
proc uint32,
program uint32,
payload []byte,
typ uint32,
status uint32,
) error {
p := packet{
Header: Header{
Program: program,
Version: constants.ProtocolVersion,
Procedure: proc,
Type: typ,
Serial: serial,
Status: status,
},
}
size := int(unsafe.Sizeof(p.Len)) + int(unsafe.Sizeof(p.Header))
if payload != nil {
size += len(payload)
}
p.Len = uint32(size)
if s.isDisconnected() {
// this mirrors what a lot of net code return on use of a no
// longer valid connection
return syscall.EINVAL
}
s.mu.Lock()
defer s.mu.Unlock()
err := binary.Write(s.writer, binary.BigEndian, p)
if err != nil {
return err
}
// write payload
if payload != nil {
err = binary.Write(s.writer, binary.BigEndian, payload)
if err != nil {
return err
}
}
return s.writer.Flush()
}
// SendStream sends a stream of packets to libvirt on the socket connection.
func (s *Socket) SendStream(serial int32, proc uint32, program uint32,
stream io.Reader, abort chan bool) error {
// Keep total packet length under 4 MiB to follow possible limitation in libvirt server code
buf := make([]byte, 4*MiB-unsafe.Sizeof(_p))
for {
select {
case <-abort:
return s.SendPacket(serial, proc, program, nil, Stream, StatusError)
default:
}
n, err := stream.Read(buf)
if n > 0 {
err2 := s.SendPacket(serial, proc, program, buf[:n], Stream, StatusContinue)
if err2 != nil {
return err2
}
}
if err != nil {
if err == io.EOF {
return s.SendPacket(serial, proc, program, nil, Stream, StatusOK)
}
// keep original error
err2 := s.SendPacket(serial, proc, program, nil, Stream, StatusError)
if err2 != nil {
return err2
}
return err
}
}
}
-27
View File
@@ -1,27 +0,0 @@
// Copyright 2016 The go-libvirt Authors.
//
// 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.
// This module provides different units of measurement to make other
// code more readable.
package socket
const (
// B - byte
B = 1
// KiB - kibibyte
KiB = 1024 * B
// MiB - mebibyte
MiB = 1024 * KiB
)
View File
-6
View File
@@ -1,6 +0,0 @@
language: go
go:
- 1.6
- 1.7
- 1.8
-19
View File
@@ -1,19 +0,0 @@
Copyright (c) 2016 Felix Geisendörfer (felix@debuggable.com)
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.
-10
View File
@@ -1,10 +0,0 @@
.PHONY: ci generate clean
ci: clean generate
go test -v ./...
generate:
go generate .
clean:
rm -rf *_generated*.go
-95
View File
@@ -1,95 +0,0 @@
# httpsnoop
Package httpsnoop provides an easy way to capture http related metrics (i.e.
response time, bytes written, and http status code) from your application's
http.Handlers.
Doing this requires non-trivial wrapping of the http.ResponseWriter interface,
which is also exposed for users interested in a more low-level API.
[![GoDoc](https://godoc.org/github.com/felixge/httpsnoop?status.svg)](https://godoc.org/github.com/felixge/httpsnoop)
[![Build Status](https://travis-ci.org/felixge/httpsnoop.svg?branch=master)](https://travis-ci.org/felixge/httpsnoop)
## Usage Example
```go
// myH is your app's http handler, perhaps a http.ServeMux or similar.
var myH http.Handler
// wrappedH wraps myH in order to log every request.
wrappedH := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
m := httpsnoop.CaptureMetrics(myH, w, r)
log.Printf(
"%s %s (code=%d dt=%s written=%d)",
r.Method,
r.URL,
m.Code,
m.Duration,
m.Written,
)
})
http.ListenAndServe(":8080", wrappedH)
```
## Why this package exists
Instrumenting an application's http.Handler is surprisingly difficult.
However if you google for e.g. "capture ResponseWriter status code" you'll find
lots of advise and code examples that suggest it to be a fairly trivial
undertaking. Unfortunately everything I've seen so far has a high chance of
breaking your application.
The main problem is that a `http.ResponseWriter` often implements additional
interfaces such as `http.Flusher`, `http.CloseNotifier`, `http.Hijacker`, `http.Pusher`, and
`io.ReaderFrom`. So the naive approach of just wrapping `http.ResponseWriter`
in your own struct that also implements the `http.ResponseWriter` interface
will hide the additional interfaces mentioned above. This has a high change of
introducing subtle bugs into any non-trivial application.
Another approach I've seen people take is to return a struct that implements
all of the interfaces above. However, that's also problematic, because it's
difficult to fake some of these interfaces behaviors when the underlying
`http.ResponseWriter` doesn't have an implementation. It's also dangerous,
because an application may choose to operate differently, merely because it
detects the presence of these additional interfaces.
This package solves this problem by checking which additional interfaces a
`http.ResponseWriter` implements, returning a wrapped version implementing the
exact same set of interfaces.
Additionally this package properly handles edge cases such as `WriteHeader` not
being called, or called more than once, as well as concurrent calls to
`http.ResponseWriter` methods, and even calls happening after the wrapped
`ServeHTTP` has already returned.
Unfortunately this package is not perfect either. It's possible that it is
still missing some interfaces provided by the go core (let me know if you find
one), and it won't work for applications adding their own interfaces into the
mix. You can however use `httpsnoop.Unwrap(w)` to access the underlying
`http.ResponseWriter` and type-assert the result to its other interfaces.
However, hopefully the explanation above has sufficiently scared you of rolling
your own solution to this problem. httpsnoop may still break your application,
but at least it tries to avoid it as much as possible.
Anyway, the real problem here is that smuggling additional interfaces inside
`http.ResponseWriter` is a problematic design choice, but it probably goes as
deep as the Go language specification itself. But that's okay, I still prefer
Go over the alternatives ;).
## Performance
```
BenchmarkBaseline-8 20000 94912 ns/op
BenchmarkCaptureMetrics-8 20000 95461 ns/op
```
As you can see, using `CaptureMetrics` on a vanilla http.Handler introduces an
overhead of ~500 ns per http request on my machine. However, the margin of
error appears to be larger than that, therefor it should be reasonable to
assume that the overhead introduced by `CaptureMetrics` is absolutely
negligible.
## License
MIT
-86
View File
@@ -1,86 +0,0 @@
package httpsnoop
import (
"io"
"net/http"
"time"
)
// Metrics holds metrics captured from CaptureMetrics.
type Metrics struct {
// Code is the first http response code passed to the WriteHeader func of
// the ResponseWriter. If no such call is made, a default code of 200 is
// assumed instead.
Code int
// Duration is the time it took to execute the handler.
Duration time.Duration
// Written is the number of bytes successfully written by the Write or
// ReadFrom function of the ResponseWriter. ResponseWriters may also write
// data to their underlaying connection directly (e.g. headers), but those
// are not tracked. Therefor the number of Written bytes will usually match
// the size of the response body.
Written int64
}
// CaptureMetrics wraps the given hnd, executes it with the given w and r, and
// returns the metrics it captured from it.
func CaptureMetrics(hnd http.Handler, w http.ResponseWriter, r *http.Request) Metrics {
return CaptureMetricsFn(w, func(ww http.ResponseWriter) {
hnd.ServeHTTP(ww, r)
})
}
// CaptureMetricsFn wraps w and calls fn with the wrapped w and returns the
// resulting metrics. This is very similar to CaptureMetrics (which is just
// sugar on top of this func), but is a more usable interface if your
// application doesn't use the Go http.Handler interface.
func CaptureMetricsFn(w http.ResponseWriter, fn func(http.ResponseWriter)) Metrics {
m := Metrics{Code: http.StatusOK}
m.CaptureMetrics(w, fn)
return m
}
// CaptureMetrics wraps w and calls fn with the wrapped w and updates
// Metrics m with the resulting metrics. This is similar to CaptureMetricsFn,
// but allows one to customize starting Metrics object.
func (m *Metrics) CaptureMetrics(w http.ResponseWriter, fn func(http.ResponseWriter)) {
var (
start = time.Now()
headerWritten bool
hooks = Hooks{
WriteHeader: func(next WriteHeaderFunc) WriteHeaderFunc {
return func(code int) {
next(code)
if !headerWritten {
m.Code = code
headerWritten = true
}
}
},
Write: func(next WriteFunc) WriteFunc {
return func(p []byte) (int, error) {
n, err := next(p)
m.Written += int64(n)
headerWritten = true
return n, err
}
},
ReadFrom: func(next ReadFromFunc) ReadFromFunc {
return func(src io.Reader) (int64, error) {
n, err := next(src)
headerWritten = true
m.Written += n
return n, err
}
},
}
)
fn(Wrap(w, hooks))
m.Duration += time.Since(start)
}
-10
View File
@@ -1,10 +0,0 @@
// Package httpsnoop provides an easy way to capture http related metrics (i.e.
// response time, bytes written, and http status code) from your application's
// http.Handlers.
//
// Doing this requires non-trivial wrapping of the http.ResponseWriter
// interface, which is also exposed for users interested in a more low-level
// API.
package httpsnoop
//go:generate go run codegen/main.go
-436
View File
@@ -1,436 +0,0 @@
// +build go1.8
// Code generated by "httpsnoop/codegen"; DO NOT EDIT
package httpsnoop
import (
"bufio"
"io"
"net"
"net/http"
)
// HeaderFunc is part of the http.ResponseWriter interface.
type HeaderFunc func() http.Header
// WriteHeaderFunc is part of the http.ResponseWriter interface.
type WriteHeaderFunc func(code int)
// WriteFunc is part of the http.ResponseWriter interface.
type WriteFunc func(b []byte) (int, error)
// FlushFunc is part of the http.Flusher interface.
type FlushFunc func()
// CloseNotifyFunc is part of the http.CloseNotifier interface.
type CloseNotifyFunc func() <-chan bool
// HijackFunc is part of the http.Hijacker interface.
type HijackFunc func() (net.Conn, *bufio.ReadWriter, error)
// ReadFromFunc is part of the io.ReaderFrom interface.
type ReadFromFunc func(src io.Reader) (int64, error)
// PushFunc is part of the http.Pusher interface.
type PushFunc func(target string, opts *http.PushOptions) error
// Hooks defines a set of method interceptors for methods included in
// http.ResponseWriter as well as some others. You can think of them as
// middleware for the function calls they target. See Wrap for more details.
type Hooks struct {
Header func(HeaderFunc) HeaderFunc
WriteHeader func(WriteHeaderFunc) WriteHeaderFunc
Write func(WriteFunc) WriteFunc
Flush func(FlushFunc) FlushFunc
CloseNotify func(CloseNotifyFunc) CloseNotifyFunc
Hijack func(HijackFunc) HijackFunc
ReadFrom func(ReadFromFunc) ReadFromFunc
Push func(PushFunc) PushFunc
}
// Wrap returns a wrapped version of w that provides the exact same interface
// as w. Specifically if w implements any combination of:
//
// - http.Flusher
// - http.CloseNotifier
// - http.Hijacker
// - io.ReaderFrom
// - http.Pusher
//
// The wrapped version will implement the exact same combination. If no hooks
// are set, the wrapped version also behaves exactly as w. Hooks targeting
// methods not supported by w are ignored. Any other hooks will intercept the
// method they target and may modify the call's arguments and/or return values.
// The CaptureMetrics implementation serves as a working example for how the
// hooks can be used.
func Wrap(w http.ResponseWriter, hooks Hooks) http.ResponseWriter {
rw := &rw{w: w, h: hooks}
_, i0 := w.(http.Flusher)
_, i1 := w.(http.CloseNotifier)
_, i2 := w.(http.Hijacker)
_, i3 := w.(io.ReaderFrom)
_, i4 := w.(http.Pusher)
switch {
// combination 1/32
case !i0 && !i1 && !i2 && !i3 && !i4:
return struct {
Unwrapper
http.ResponseWriter
}{rw, rw}
// combination 2/32
case !i0 && !i1 && !i2 && !i3 && i4:
return struct {
Unwrapper
http.ResponseWriter
http.Pusher
}{rw, rw, rw}
// combination 3/32
case !i0 && !i1 && !i2 && i3 && !i4:
return struct {
Unwrapper
http.ResponseWriter
io.ReaderFrom
}{rw, rw, rw}
// combination 4/32
case !i0 && !i1 && !i2 && i3 && i4:
return struct {
Unwrapper
http.ResponseWriter
io.ReaderFrom
http.Pusher
}{rw, rw, rw, rw}
// combination 5/32
case !i0 && !i1 && i2 && !i3 && !i4:
return struct {
Unwrapper
http.ResponseWriter
http.Hijacker
}{rw, rw, rw}
// combination 6/32
case !i0 && !i1 && i2 && !i3 && i4:
return struct {
Unwrapper
http.ResponseWriter
http.Hijacker
http.Pusher
}{rw, rw, rw, rw}
// combination 7/32
case !i0 && !i1 && i2 && i3 && !i4:
return struct {
Unwrapper
http.ResponseWriter
http.Hijacker
io.ReaderFrom
}{rw, rw, rw, rw}
// combination 8/32
case !i0 && !i1 && i2 && i3 && i4:
return struct {
Unwrapper
http.ResponseWriter
http.Hijacker
io.ReaderFrom
http.Pusher
}{rw, rw, rw, rw, rw}
// combination 9/32
case !i0 && i1 && !i2 && !i3 && !i4:
return struct {
Unwrapper
http.ResponseWriter
http.CloseNotifier
}{rw, rw, rw}
// combination 10/32
case !i0 && i1 && !i2 && !i3 && i4:
return struct {
Unwrapper
http.ResponseWriter
http.CloseNotifier
http.Pusher
}{rw, rw, rw, rw}
// combination 11/32
case !i0 && i1 && !i2 && i3 && !i4:
return struct {
Unwrapper
http.ResponseWriter
http.CloseNotifier
io.ReaderFrom
}{rw, rw, rw, rw}
// combination 12/32
case !i0 && i1 && !i2 && i3 && i4:
return struct {
Unwrapper
http.ResponseWriter
http.CloseNotifier
io.ReaderFrom
http.Pusher
}{rw, rw, rw, rw, rw}
// combination 13/32
case !i0 && i1 && i2 && !i3 && !i4:
return struct {
Unwrapper
http.ResponseWriter
http.CloseNotifier
http.Hijacker
}{rw, rw, rw, rw}
// combination 14/32
case !i0 && i1 && i2 && !i3 && i4:
return struct {
Unwrapper
http.ResponseWriter
http.CloseNotifier
http.Hijacker
http.Pusher
}{rw, rw, rw, rw, rw}
// combination 15/32
case !i0 && i1 && i2 && i3 && !i4:
return struct {
Unwrapper
http.ResponseWriter
http.CloseNotifier
http.Hijacker
io.ReaderFrom
}{rw, rw, rw, rw, rw}
// combination 16/32
case !i0 && i1 && i2 && i3 && i4:
return struct {
Unwrapper
http.ResponseWriter
http.CloseNotifier
http.Hijacker
io.ReaderFrom
http.Pusher
}{rw, rw, rw, rw, rw, rw}
// combination 17/32
case i0 && !i1 && !i2 && !i3 && !i4:
return struct {
Unwrapper
http.ResponseWriter
http.Flusher
}{rw, rw, rw}
// combination 18/32
case i0 && !i1 && !i2 && !i3 && i4:
return struct {
Unwrapper
http.ResponseWriter
http.Flusher
http.Pusher
}{rw, rw, rw, rw}
// combination 19/32
case i0 && !i1 && !i2 && i3 && !i4:
return struct {
Unwrapper
http.ResponseWriter
http.Flusher
io.ReaderFrom
}{rw, rw, rw, rw}
// combination 20/32
case i0 && !i1 && !i2 && i3 && i4:
return struct {
Unwrapper
http.ResponseWriter
http.Flusher
io.ReaderFrom
http.Pusher
}{rw, rw, rw, rw, rw}
// combination 21/32
case i0 && !i1 && i2 && !i3 && !i4:
return struct {
Unwrapper
http.ResponseWriter
http.Flusher
http.Hijacker
}{rw, rw, rw, rw}
// combination 22/32
case i0 && !i1 && i2 && !i3 && i4:
return struct {
Unwrapper
http.ResponseWriter
http.Flusher
http.Hijacker
http.Pusher
}{rw, rw, rw, rw, rw}
// combination 23/32
case i0 && !i1 && i2 && i3 && !i4:
return struct {
Unwrapper
http.ResponseWriter
http.Flusher
http.Hijacker
io.ReaderFrom
}{rw, rw, rw, rw, rw}
// combination 24/32
case i0 && !i1 && i2 && i3 && i4:
return struct {
Unwrapper
http.ResponseWriter
http.Flusher
http.Hijacker
io.ReaderFrom
http.Pusher
}{rw, rw, rw, rw, rw, rw}
// combination 25/32
case i0 && i1 && !i2 && !i3 && !i4:
return struct {
Unwrapper
http.ResponseWriter
http.Flusher
http.CloseNotifier
}{rw, rw, rw, rw}
// combination 26/32
case i0 && i1 && !i2 && !i3 && i4:
return struct {
Unwrapper
http.ResponseWriter
http.Flusher
http.CloseNotifier
http.Pusher
}{rw, rw, rw, rw, rw}
// combination 27/32
case i0 && i1 && !i2 && i3 && !i4:
return struct {
Unwrapper
http.ResponseWriter
http.Flusher
http.CloseNotifier
io.ReaderFrom
}{rw, rw, rw, rw, rw}
// combination 28/32
case i0 && i1 && !i2 && i3 && i4:
return struct {
Unwrapper
http.ResponseWriter
http.Flusher
http.CloseNotifier
io.ReaderFrom
http.Pusher
}{rw, rw, rw, rw, rw, rw}
// combination 29/32
case i0 && i1 && i2 && !i3 && !i4:
return struct {
Unwrapper
http.ResponseWriter
http.Flusher
http.CloseNotifier
http.Hijacker
}{rw, rw, rw, rw, rw}
// combination 30/32
case i0 && i1 && i2 && !i3 && i4:
return struct {
Unwrapper
http.ResponseWriter
http.Flusher
http.CloseNotifier
http.Hijacker
http.Pusher
}{rw, rw, rw, rw, rw, rw}
// combination 31/32
case i0 && i1 && i2 && i3 && !i4:
return struct {
Unwrapper
http.ResponseWriter
http.Flusher
http.CloseNotifier
http.Hijacker
io.ReaderFrom
}{rw, rw, rw, rw, rw, rw}
// combination 32/32
case i0 && i1 && i2 && i3 && i4:
return struct {
Unwrapper
http.ResponseWriter
http.Flusher
http.CloseNotifier
http.Hijacker
io.ReaderFrom
http.Pusher
}{rw, rw, rw, rw, rw, rw, rw}
}
panic("unreachable")
}
type rw struct {
w http.ResponseWriter
h Hooks
}
func (w *rw) Unwrap() http.ResponseWriter {
return w.w
}
func (w *rw) Header() http.Header {
f := w.w.(http.ResponseWriter).Header
if w.h.Header != nil {
f = w.h.Header(f)
}
return f()
}
func (w *rw) WriteHeader(code int) {
f := w.w.(http.ResponseWriter).WriteHeader
if w.h.WriteHeader != nil {
f = w.h.WriteHeader(f)
}
f(code)
}
func (w *rw) Write(b []byte) (int, error) {
f := w.w.(http.ResponseWriter).Write
if w.h.Write != nil {
f = w.h.Write(f)
}
return f(b)
}
func (w *rw) Flush() {
f := w.w.(http.Flusher).Flush
if w.h.Flush != nil {
f = w.h.Flush(f)
}
f()
}
func (w *rw) CloseNotify() <-chan bool {
f := w.w.(http.CloseNotifier).CloseNotify
if w.h.CloseNotify != nil {
f = w.h.CloseNotify(f)
}
return f()
}
func (w *rw) Hijack() (net.Conn, *bufio.ReadWriter, error) {
f := w.w.(http.Hijacker).Hijack
if w.h.Hijack != nil {
f = w.h.Hijack(f)
}
return f()
}
func (w *rw) ReadFrom(src io.Reader) (int64, error) {
f := w.w.(io.ReaderFrom).ReadFrom
if w.h.ReadFrom != nil {
f = w.h.ReadFrom(f)
}
return f(src)
}
func (w *rw) Push(target string, opts *http.PushOptions) error {
f := w.w.(http.Pusher).Push
if w.h.Push != nil {
f = w.h.Push(f)
}
return f(target, opts)
}
type Unwrapper interface {
Unwrap() http.ResponseWriter
}
// Unwrap returns the underlying http.ResponseWriter from within zero or more
// layers of httpsnoop wrappers.
func Unwrap(w http.ResponseWriter) http.ResponseWriter {
if rw, ok := w.(Unwrapper); ok {
// recurse until rw.Unwrap() returns a non-Unwrapper
return Unwrap(rw.Unwrap())
} else {
return w
}
}
-278
View File
@@ -1,278 +0,0 @@
// +build !go1.8
// Code generated by "httpsnoop/codegen"; DO NOT EDIT
package httpsnoop
import (
"bufio"
"io"
"net"
"net/http"
)
// HeaderFunc is part of the http.ResponseWriter interface.
type HeaderFunc func() http.Header
// WriteHeaderFunc is part of the http.ResponseWriter interface.
type WriteHeaderFunc func(code int)
// WriteFunc is part of the http.ResponseWriter interface.
type WriteFunc func(b []byte) (int, error)
// FlushFunc is part of the http.Flusher interface.
type FlushFunc func()
// CloseNotifyFunc is part of the http.CloseNotifier interface.
type CloseNotifyFunc func() <-chan bool
// HijackFunc is part of the http.Hijacker interface.
type HijackFunc func() (net.Conn, *bufio.ReadWriter, error)
// ReadFromFunc is part of the io.ReaderFrom interface.
type ReadFromFunc func(src io.Reader) (int64, error)
// Hooks defines a set of method interceptors for methods included in
// http.ResponseWriter as well as some others. You can think of them as
// middleware for the function calls they target. See Wrap for more details.
type Hooks struct {
Header func(HeaderFunc) HeaderFunc
WriteHeader func(WriteHeaderFunc) WriteHeaderFunc
Write func(WriteFunc) WriteFunc
Flush func(FlushFunc) FlushFunc
CloseNotify func(CloseNotifyFunc) CloseNotifyFunc
Hijack func(HijackFunc) HijackFunc
ReadFrom func(ReadFromFunc) ReadFromFunc
}
// Wrap returns a wrapped version of w that provides the exact same interface
// as w. Specifically if w implements any combination of:
//
// - http.Flusher
// - http.CloseNotifier
// - http.Hijacker
// - io.ReaderFrom
//
// The wrapped version will implement the exact same combination. If no hooks
// are set, the wrapped version also behaves exactly as w. Hooks targeting
// methods not supported by w are ignored. Any other hooks will intercept the
// method they target and may modify the call's arguments and/or return values.
// The CaptureMetrics implementation serves as a working example for how the
// hooks can be used.
func Wrap(w http.ResponseWriter, hooks Hooks) http.ResponseWriter {
rw := &rw{w: w, h: hooks}
_, i0 := w.(http.Flusher)
_, i1 := w.(http.CloseNotifier)
_, i2 := w.(http.Hijacker)
_, i3 := w.(io.ReaderFrom)
switch {
// combination 1/16
case !i0 && !i1 && !i2 && !i3:
return struct {
Unwrapper
http.ResponseWriter
}{rw, rw}
// combination 2/16
case !i0 && !i1 && !i2 && i3:
return struct {
Unwrapper
http.ResponseWriter
io.ReaderFrom
}{rw, rw, rw}
// combination 3/16
case !i0 && !i1 && i2 && !i3:
return struct {
Unwrapper
http.ResponseWriter
http.Hijacker
}{rw, rw, rw}
// combination 4/16
case !i0 && !i1 && i2 && i3:
return struct {
Unwrapper
http.ResponseWriter
http.Hijacker
io.ReaderFrom
}{rw, rw, rw, rw}
// combination 5/16
case !i0 && i1 && !i2 && !i3:
return struct {
Unwrapper
http.ResponseWriter
http.CloseNotifier
}{rw, rw, rw}
// combination 6/16
case !i0 && i1 && !i2 && i3:
return struct {
Unwrapper
http.ResponseWriter
http.CloseNotifier
io.ReaderFrom
}{rw, rw, rw, rw}
// combination 7/16
case !i0 && i1 && i2 && !i3:
return struct {
Unwrapper
http.ResponseWriter
http.CloseNotifier
http.Hijacker
}{rw, rw, rw, rw}
// combination 8/16
case !i0 && i1 && i2 && i3:
return struct {
Unwrapper
http.ResponseWriter
http.CloseNotifier
http.Hijacker
io.ReaderFrom
}{rw, rw, rw, rw, rw}
// combination 9/16
case i0 && !i1 && !i2 && !i3:
return struct {
Unwrapper
http.ResponseWriter
http.Flusher
}{rw, rw, rw}
// combination 10/16
case i0 && !i1 && !i2 && i3:
return struct {
Unwrapper
http.ResponseWriter
http.Flusher
io.ReaderFrom
}{rw, rw, rw, rw}
// combination 11/16
case i0 && !i1 && i2 && !i3:
return struct {
Unwrapper
http.ResponseWriter
http.Flusher
http.Hijacker
}{rw, rw, rw, rw}
// combination 12/16
case i0 && !i1 && i2 && i3:
return struct {
Unwrapper
http.ResponseWriter
http.Flusher
http.Hijacker
io.ReaderFrom
}{rw, rw, rw, rw, rw}
// combination 13/16
case i0 && i1 && !i2 && !i3:
return struct {
Unwrapper
http.ResponseWriter
http.Flusher
http.CloseNotifier
}{rw, rw, rw, rw}
// combination 14/16
case i0 && i1 && !i2 && i3:
return struct {
Unwrapper
http.ResponseWriter
http.Flusher
http.CloseNotifier
io.ReaderFrom
}{rw, rw, rw, rw, rw}
// combination 15/16
case i0 && i1 && i2 && !i3:
return struct {
Unwrapper
http.ResponseWriter
http.Flusher
http.CloseNotifier
http.Hijacker
}{rw, rw, rw, rw, rw}
// combination 16/16
case i0 && i1 && i2 && i3:
return struct {
Unwrapper
http.ResponseWriter
http.Flusher
http.CloseNotifier
http.Hijacker
io.ReaderFrom
}{rw, rw, rw, rw, rw, rw}
}
panic("unreachable")
}
type rw struct {
w http.ResponseWriter
h Hooks
}
func (w *rw) Unwrap() http.ResponseWriter {
return w.w
}
func (w *rw) Header() http.Header {
f := w.w.(http.ResponseWriter).Header
if w.h.Header != nil {
f = w.h.Header(f)
}
return f()
}
func (w *rw) WriteHeader(code int) {
f := w.w.(http.ResponseWriter).WriteHeader
if w.h.WriteHeader != nil {
f = w.h.WriteHeader(f)
}
f(code)
}
func (w *rw) Write(b []byte) (int, error) {
f := w.w.(http.ResponseWriter).Write
if w.h.Write != nil {
f = w.h.Write(f)
}
return f(b)
}
func (w *rw) Flush() {
f := w.w.(http.Flusher).Flush
if w.h.Flush != nil {
f = w.h.Flush(f)
}
f()
}
func (w *rw) CloseNotify() <-chan bool {
f := w.w.(http.CloseNotifier).CloseNotify
if w.h.CloseNotify != nil {
f = w.h.CloseNotify(f)
}
return f()
}
func (w *rw) Hijack() (net.Conn, *bufio.ReadWriter, error) {
f := w.w.(http.Hijacker).Hijack
if w.h.Hijack != nil {
f = w.h.Hijack(f)
}
return f()
}
func (w *rw) ReadFrom(src io.Reader) (int64, error) {
f := w.w.(io.ReaderFrom).ReadFrom
if w.h.ReadFrom != nil {
f = w.h.ReadFrom(f)
}
return f(src)
}
type Unwrapper interface {
Unwrap() http.ResponseWriter
}
// Unwrap returns the underlying http.ResponseWriter from within zero or more
// layers of httpsnoop wrappers.
func Unwrap(w http.ResponseWriter) http.ResponseWriter {
if rw, ok := w.(Unwrapper); ok {
// recurse until rw.Unwrap() returns a non-Unwrapper
return Unwrap(rw.Unwrap())
} else {
return w
}
}
-22
View File
@@ -1,22 +0,0 @@
The MIT License (MIT)
Copyright (c) 2015 Peter Bourgon
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.
-5
View File
@@ -1,5 +0,0 @@
// Package endpoint defines an abstraction for RPCs.
//
// Endpoints are a fundamental building block for many Go kit components.
// Endpoints are implemented by servers, and called by clients.
package endpoint
-40
View File
@@ -1,40 +0,0 @@
package endpoint
import (
"context"
)
// Endpoint is the fundamental building block of servers and clients.
// It represents a single RPC method.
type Endpoint func(ctx context.Context, request interface{}) (response interface{}, err error)
// Nop is an endpoint that does nothing and returns a nil error.
// Useful for tests.
func Nop(context.Context, interface{}) (interface{}, error) { return struct{}{}, nil }
// Middleware is a chainable behavior modifier for endpoints.
type Middleware func(Endpoint) Endpoint
// Chain is a helper function for composing middlewares. Requests will
// traverse them in the order they're declared. That is, the first middleware
// is treated as the outermost middleware.
func Chain(outer Middleware, others ...Middleware) Middleware {
return func(next Endpoint) Endpoint {
for i := len(others) - 1; i >= 0; i-- { // reverse
next = others[i](next)
}
return outer(next)
}
}
// Failer may be implemented by Go kit response types that contain business
// logic error details. If Failed returns a non-nil error, the Go kit transport
// layer may interpret this as a business logic error, and may encode it
// differently than a regular, successful response.
//
// It's not necessary for your response types to implement Failer, but it may
// help for more sophisticated use cases. The addsvc example shows how Failer
// should be used by a complete application.
type Failer interface {
Failed() error
}
-98
View File
@@ -1,98 +0,0 @@
# package metrics
`package metrics` provides a set of uniform interfaces for service instrumentation.
It has
[counters](http://prometheus.io/docs/concepts/metric_types/#counter),
[gauges](http://prometheus.io/docs/concepts/metric_types/#gauge), and
[histograms](http://prometheus.io/docs/concepts/metric_types/#histogram),
and provides adapters to popular metrics packages, like
[expvar](https://golang.org/pkg/expvar),
[StatsD](https://github.com/etsy/statsd), and
[Prometheus](https://prometheus.io).
## Rationale
Code instrumentation is absolutely essential to achieve
[observability](https://speakerdeck.com/mattheath/observability-in-micro-service-architectures)
into a distributed system.
Metrics and instrumentation tools have coalesced around a few well-defined idioms.
`package metrics` provides a common, minimal interface those idioms for service authors.
## Usage
A simple counter, exported via expvar.
```go
import (
"github.com/go-kit/kit/metrics"
"github.com/go-kit/kit/metrics/expvar"
)
func main() {
var myCount metrics.Counter
myCount = expvar.NewCounter("my_count")
myCount.Add(1)
}
```
A histogram for request duration,
exported via a Prometheus summary with dynamically-computed quantiles.
```go
import (
"time"
stdprometheus "github.com/prometheus/client_golang/prometheus"
"github.com/go-kit/kit/metrics"
"github.com/go-kit/kit/metrics/prometheus"
)
func main() {
var dur metrics.Histogram = prometheus.NewSummaryFrom(stdprometheus.SummaryOpts{
Namespace: "myservice",
Subsystem: "api",
Name: "request_duration_seconds",
Help: "Total time spent serving requests.",
}, []string{})
// ...
}
func handleRequest(dur metrics.Histogram) {
defer func(begin time.Time) { dur.Observe(time.Since(begin).Seconds()) }(time.Now())
// handle request
}
```
A gauge for the number of goroutines currently running, exported via StatsD.
```go
import (
"context"
"net"
"os"
"runtime"
"time"
"github.com/go-kit/kit/metrics"
"github.com/go-kit/kit/metrics/statsd"
)
func main() {
statsd := statsd.New("foo_svc.", log.NewNopLogger())
report := time.NewTicker(5 * time.Second)
defer report.Stop()
go statsd.SendLoop(context.Background(), report.C, "tcp", "statsd.internal:8125")
goroutines := statsd.NewGauge("goroutine_count")
go exportGoroutines(goroutines)
// ...
}
func exportGoroutines(g metrics.Gauge) {
for range time.Tick(time.Second) {
g.Set(float64(runtime.NumGoroutine()))
}
}
```
For more information, see [the package documentation](https://godoc.org/github.com/go-kit/kit/metrics).
-97
View File
@@ -1,97 +0,0 @@
// Package metrics provides a framework for application instrumentation. It's
// primarily designed to help you get started with good and robust
// instrumentation, and to help you migrate from a less-capable system like
// Graphite to a more-capable system like Prometheus. If your organization has
// already standardized on an instrumentation system like Prometheus, and has no
// plans to change, it may make sense to use that system's instrumentation
// library directly.
//
// This package provides three core metric abstractions (Counter, Gauge, and
// Histogram) and implementations for almost all common instrumentation
// backends. Each metric has an observation method (Add, Set, or Observe,
// respectively) used to record values, and a With method to "scope" the
// observation by various parameters. For example, you might have a Histogram to
// record request durations, parameterized by the method that's being called.
//
// var requestDuration metrics.Histogram
// // ...
// requestDuration.With("method", "MyMethod").Observe(time.Since(begin))
//
// This allows a single high-level metrics object (requestDuration) to work with
// many code paths somewhat dynamically. The concept of With is fully supported
// in some backends like Prometheus, and not supported in other backends like
// Graphite. So, With may be a no-op, depending on the concrete implementation
// you choose. Please check the implementation to know for sure. For
// implementations that don't provide With, it's necessary to fully parameterize
// each metric in the metric name, e.g.
//
// // Statsd
// c := statsd.NewCounter("request_duration_MyMethod_200")
// c.Add(1)
//
// // Prometheus
// c := prometheus.NewCounter(stdprometheus.CounterOpts{
// Name: "request_duration",
// ...
// }, []string{"method", "status_code"})
// c.With("method", "MyMethod", "status_code", strconv.Itoa(code)).Add(1)
//
// Usage
//
// Metrics are dependencies, and should be passed to the components that need
// them in the same way you'd construct and pass a database handle, or reference
// to another component. Metrics should *not* be created in the global scope.
// Instead, instantiate metrics in your func main, using whichever concrete
// implementation is appropriate for your organization.
//
// latency := prometheus.NewSummaryFrom(stdprometheus.SummaryOpts{
// Namespace: "myteam",
// Subsystem: "foosvc",
// Name: "request_latency_seconds",
// Help: "Incoming request latency in seconds.",
// }, []string{"method", "status_code"})
//
// Write your components to take the metrics they will use as parameters to
// their constructors. Use the interface types, not the concrete types. That is,
//
// // NewAPI takes metrics.Histogram, not *prometheus.Summary
// func NewAPI(s Store, logger log.Logger, latency metrics.Histogram) *API {
// // ...
// }
//
// func (a *API) ServeFoo(w http.ResponseWriter, r *http.Request) {
// begin := time.Now()
// // ...
// a.latency.Observe(time.Since(begin).Seconds())
// }
//
// Finally, pass the metrics as dependencies when building your object graph.
// This should happen in func main, not in the global scope.
//
// api := NewAPI(store, logger, latency)
// http.ListenAndServe("/", api)
//
// Note that metrics are "write-only" interfaces.
//
// Implementation details
//
// All metrics are safe for concurrent use. Considerable design influence has
// been taken from https://github.com/codahale/metrics and
// https://prometheus.io.
//
// Each telemetry system has different semantics for label values, push vs.
// pull, support for histograms, etc. These properties influence the design of
// their respective packages. This table attempts to summarize the key points of
// distinction.
//
// SYSTEM DIM COUNTERS GAUGES HISTOGRAMS
// dogstatsd n batch, push-aggregate batch, push-aggregate native, batch, push-each
// statsd 1 batch, push-aggregate batch, push-aggregate native, batch, push-each
// graphite 1 batch, push-aggregate batch, push-aggregate synthetic, batch, push-aggregate
// expvar 1 atomic atomic synthetic, batch, in-place expose
// influx n custom custom custom
// prometheus n native native native
// pcp 1 native native native
// cloudwatch n batch push-aggregate batch push-aggregate synthetic, batch, push-aggregate
//
package metrics
-14
View File
@@ -1,14 +0,0 @@
package lv
// LabelValues is a type alias that provides validation on its With method.
// Metrics may include it as a member to help them satisfy With semantics and
// save some code duplication.
type LabelValues []string
// With validates the input, and returns a new aggregate labelValues.
func (lvs LabelValues) With(labelValues ...string) LabelValues {
if len(labelValues)%2 != 0 {
labelValues = append(labelValues, "unknown")
}
return append(lvs, labelValues...)
}
-145
View File
@@ -1,145 +0,0 @@
package lv
import "sync"
// NewSpace returns an N-dimensional vector space.
func NewSpace() *Space {
return &Space{}
}
// Space represents an N-dimensional vector space. Each name and unique label
// value pair establishes a new dimension and point within that dimension. Order
// matters, i.e. [a=1 b=2] identifies a different timeseries than [b=2 a=1].
type Space struct {
mtx sync.RWMutex
nodes map[string]*node
}
// Observe locates the time series identified by the name and label values in
// the vector space, and appends the value to the list of observations.
func (s *Space) Observe(name string, lvs LabelValues, value float64) {
s.nodeFor(name).observe(lvs, value)
}
// Add locates the time series identified by the name and label values in
// the vector space, and appends the delta to the last value in the list of
// observations.
func (s *Space) Add(name string, lvs LabelValues, delta float64) {
s.nodeFor(name).add(lvs, delta)
}
// Walk traverses the vector space and invokes fn for each non-empty time series
// which is encountered. Return false to abort the traversal.
func (s *Space) Walk(fn func(name string, lvs LabelValues, observations []float64) bool) {
s.mtx.RLock()
defer s.mtx.RUnlock()
for name, node := range s.nodes {
f := func(lvs LabelValues, observations []float64) bool { return fn(name, lvs, observations) }
if !node.walk(LabelValues{}, f) {
return
}
}
}
// Reset empties the current space and returns a new Space with the old
// contents. Reset a Space to get an immutable copy suitable for walking.
func (s *Space) Reset() *Space {
s.mtx.Lock()
defer s.mtx.Unlock()
n := NewSpace()
n.nodes, s.nodes = s.nodes, n.nodes
return n
}
func (s *Space) nodeFor(name string) *node {
s.mtx.Lock()
defer s.mtx.Unlock()
if s.nodes == nil {
s.nodes = map[string]*node{}
}
n, ok := s.nodes[name]
if !ok {
n = &node{}
s.nodes[name] = n
}
return n
}
// node exists at a specific point in the N-dimensional vector space of all
// possible label values. The node collects observations and has child nodes
// with greater specificity.
type node struct {
mtx sync.RWMutex
observations []float64
children map[pair]*node
}
type pair struct{ label, value string }
func (n *node) observe(lvs LabelValues, value float64) {
n.mtx.Lock()
defer n.mtx.Unlock()
if len(lvs) <= 0 {
n.observations = append(n.observations, value)
return
}
if len(lvs) < 2 {
panic("too few LabelValues; programmer error!")
}
head, tail := pair{lvs[0], lvs[1]}, lvs[2:]
if n.children == nil {
n.children = map[pair]*node{}
}
child, ok := n.children[head]
if !ok {
child = &node{}
n.children[head] = child
}
child.observe(tail, value)
}
func (n *node) add(lvs LabelValues, delta float64) {
n.mtx.Lock()
defer n.mtx.Unlock()
if len(lvs) <= 0 {
var value float64
if len(n.observations) > 0 {
value = last(n.observations) + delta
} else {
value = delta
}
n.observations = append(n.observations, value)
return
}
if len(lvs) < 2 {
panic("too few LabelValues; programmer error!")
}
head, tail := pair{lvs[0], lvs[1]}, lvs[2:]
if n.children == nil {
n.children = map[pair]*node{}
}
child, ok := n.children[head]
if !ok {
child = &node{}
n.children[head] = child
}
child.add(tail, delta)
}
func (n *node) walk(lvs LabelValues, fn func(LabelValues, []float64) bool) bool {
n.mtx.RLock()
defer n.mtx.RUnlock()
if len(n.observations) > 0 && !fn(lvs, n.observations) {
return false
}
for p, child := range n.children {
if !child.walk(append(lvs, p.label, p.value), fn) {
return false
}
}
return true
}
func last(a []float64) float64 {
return a[len(a)-1]
}
-25
View File
@@ -1,25 +0,0 @@
package metrics
// Counter describes a metric that accumulates values monotonically.
// An example of a counter is the number of received HTTP requests.
type Counter interface {
With(labelValues ...string) Counter
Add(delta float64)
}
// Gauge describes a metric that takes specific values over time.
// An example of a gauge is the current depth of a job queue.
type Gauge interface {
With(labelValues ...string) Gauge
Set(value float64)
Add(delta float64)
}
// Histogram describes a metric that takes repeated observations of the same
// kind of thing, and produces a statistical summary of those observations,
// typically expressed as quantiles or buckets. An example of a histogram is
// HTTP request latencies.
type Histogram interface {
With(labelValues ...string) Histogram
Observe(value float64)
}
-165
View File
@@ -1,165 +0,0 @@
// Package prometheus provides Prometheus implementations for metrics.
// Individual metrics are mapped to their Prometheus counterparts, and
// (depending on the constructor used) may be automatically registered in the
// global Prometheus metrics registry.
package prometheus
import (
"github.com/prometheus/client_golang/prometheus"
"github.com/go-kit/kit/metrics"
"github.com/go-kit/kit/metrics/internal/lv"
)
// Counter implements Counter, via a Prometheus CounterVec.
type Counter struct {
cv *prometheus.CounterVec
lvs lv.LabelValues
}
// NewCounterFrom constructs and registers a Prometheus CounterVec,
// and returns a usable Counter object.
func NewCounterFrom(opts prometheus.CounterOpts, labelNames []string) *Counter {
cv := prometheus.NewCounterVec(opts, labelNames)
prometheus.MustRegister(cv)
return NewCounter(cv)
}
// NewCounter wraps the CounterVec and returns a usable Counter object.
func NewCounter(cv *prometheus.CounterVec) *Counter {
return &Counter{
cv: cv,
}
}
// With implements Counter.
func (c *Counter) With(labelValues ...string) metrics.Counter {
return &Counter{
cv: c.cv,
lvs: c.lvs.With(labelValues...),
}
}
// Add implements Counter.
func (c *Counter) Add(delta float64) {
c.cv.With(makeLabels(c.lvs...)).Add(delta)
}
// Gauge implements Gauge, via a Prometheus GaugeVec.
type Gauge struct {
gv *prometheus.GaugeVec
lvs lv.LabelValues
}
// NewGaugeFrom constructs and registers a Prometheus GaugeVec,
// and returns a usable Gauge object.
func NewGaugeFrom(opts prometheus.GaugeOpts, labelNames []string) *Gauge {
gv := prometheus.NewGaugeVec(opts, labelNames)
prometheus.MustRegister(gv)
return NewGauge(gv)
}
// NewGauge wraps the GaugeVec and returns a usable Gauge object.
func NewGauge(gv *prometheus.GaugeVec) *Gauge {
return &Gauge{
gv: gv,
}
}
// With implements Gauge.
func (g *Gauge) With(labelValues ...string) metrics.Gauge {
return &Gauge{
gv: g.gv,
lvs: g.lvs.With(labelValues...),
}
}
// Set implements Gauge.
func (g *Gauge) Set(value float64) {
g.gv.With(makeLabels(g.lvs...)).Set(value)
}
// Add is supported by Prometheus GaugeVecs.
func (g *Gauge) Add(delta float64) {
g.gv.With(makeLabels(g.lvs...)).Add(delta)
}
// Summary implements Histogram, via a Prometheus SummaryVec. The difference
// between a Summary and a Histogram is that Summaries don't require predefined
// quantile buckets, but cannot be statistically aggregated.
type Summary struct {
sv *prometheus.SummaryVec
lvs lv.LabelValues
}
// NewSummaryFrom constructs and registers a Prometheus SummaryVec,
// and returns a usable Summary object.
func NewSummaryFrom(opts prometheus.SummaryOpts, labelNames []string) *Summary {
sv := prometheus.NewSummaryVec(opts, labelNames)
prometheus.MustRegister(sv)
return NewSummary(sv)
}
// NewSummary wraps the SummaryVec and returns a usable Summary object.
func NewSummary(sv *prometheus.SummaryVec) *Summary {
return &Summary{
sv: sv,
}
}
// With implements Histogram.
func (s *Summary) With(labelValues ...string) metrics.Histogram {
return &Summary{
sv: s.sv,
lvs: s.lvs.With(labelValues...),
}
}
// Observe implements Histogram.
func (s *Summary) Observe(value float64) {
s.sv.With(makeLabels(s.lvs...)).Observe(value)
}
// Histogram implements Histogram via a Prometheus HistogramVec. The difference
// between a Histogram and a Summary is that Histograms require predefined
// quantile buckets, and can be statistically aggregated.
type Histogram struct {
hv *prometheus.HistogramVec
lvs lv.LabelValues
}
// NewHistogramFrom constructs and registers a Prometheus HistogramVec,
// and returns a usable Histogram object.
func NewHistogramFrom(opts prometheus.HistogramOpts, labelNames []string) *Histogram {
hv := prometheus.NewHistogramVec(opts, labelNames)
prometheus.MustRegister(hv)
return NewHistogram(hv)
}
// NewHistogram wraps the HistogramVec and returns a usable Histogram object.
func NewHistogram(hv *prometheus.HistogramVec) *Histogram {
return &Histogram{
hv: hv,
}
}
// With implements Histogram.
func (h *Histogram) With(labelValues ...string) metrics.Histogram {
return &Histogram{
hv: h.hv,
lvs: h.lvs.With(labelValues...),
}
}
// Observe implements Histogram.
func (h *Histogram) Observe(value float64) {
h.hv.With(makeLabels(h.lvs...)).Observe(value)
}
func makeLabels(labelValues ...string) prometheus.Labels {
labels := prometheus.Labels{}
for i := 0; i < len(labelValues); i += 2 {
labels[labelValues[i]] = labelValues[i+1]
}
return labels
}
-36
View File
@@ -1,36 +0,0 @@
package metrics
import "time"
// Timer acts as a stopwatch, sending observations to a wrapped histogram.
// It's a bit of helpful syntax sugar for h.Observe(time.Since(x)).
type Timer struct {
h Histogram
t time.Time
u time.Duration
}
// NewTimer wraps the given histogram and records the current time.
func NewTimer(h Histogram) *Timer {
return &Timer{
h: h,
t: time.Now(),
u: time.Second,
}
}
// ObserveDuration captures the number of seconds since the timer was
// constructed, and forwards that observation to the histogram.
func (t *Timer) ObserveDuration() {
d := float64(time.Since(t.t).Nanoseconds()) / float64(t.u)
if d < 0 {
d = 0
}
t.h.Observe(d)
}
// Unit sets the unit of the float64 emitted by the timer.
// By default, the timer emits seconds.
func (t *Timer) Unit(u time.Duration) {
t.u = u
}
-2
View File
@@ -1,2 +0,0 @@
// Package transport contains helpers applicable to all supported transports.
package transport
-39
View File
@@ -1,39 +0,0 @@
package transport
import (
"context"
"github.com/go-kit/log"
)
// ErrorHandler receives a transport error to be processed for diagnostic purposes.
// Usually this means logging the error.
type ErrorHandler interface {
Handle(ctx context.Context, err error)
}
// LogErrorHandler is a transport error handler implementation which logs an error.
type LogErrorHandler struct {
logger log.Logger
}
func NewLogErrorHandler(logger log.Logger) *LogErrorHandler {
return &LogErrorHandler{
logger: logger,
}
}
func (h *LogErrorHandler) Handle(ctx context.Context, err error) {
h.logger.Log("err", err)
}
// The ErrorHandlerFunc type is an adapter to allow the use of
// ordinary function as ErrorHandler. If f is a function
// with the appropriate signature, ErrorHandlerFunc(f) is a
// ErrorHandler that calls f.
type ErrorHandlerFunc func(ctx context.Context, err error)
// Handle calls f(ctx, err).
func (f ErrorHandlerFunc) Handle(ctx context.Context, err error) {
f(ctx, err)
}
-54
View File
@@ -1,54 +0,0 @@
# grpc
[gRPC](http://www.grpc.io/) is an excellent, modern IDL and transport for
microservices. If you're starting a greenfield project, go-kit strongly
recommends gRPC as your default transport.
One important note is that while gRPC supports streaming requests and replies,
go-kit does not. You can still use streams in your service, but their
implementation will not be able to take advantage of many go-kit features like middleware.
Using gRPC and go-kit together is very simple.
First, define your service using protobuf3. This is explained
[in gRPC documentation](http://www.grpc.io/docs/#defining-a-service).
See
[addsvc.proto](https://github.com/go-kit/examples/blob/master/addsvc/pb/addsvc.proto)
for an example. Make sure the proto definition matches your service's go-kit
(interface) definition.
Next, get the protoc compiler.
You can download pre-compiled binaries from the
[protobuf release page](https://github.com/google/protobuf/releases).
You will unzip a folder called `protoc3` with a subdirectory `bin` containing
an executable. Move that executable somewhere in your `$PATH` and you're good
to go!
It can also be built from source.
```sh
brew install autoconf automake libtool
git clone https://github.com/google/protobuf
cd protobuf
./autogen.sh ; ./configure ; make ; make install
```
Then, compile your service definition, from .proto to .go.
```sh
protoc add.proto --go_out=plugins=grpc:.
```
Finally, write a tiny binding from your service definition to the gRPC
definition. It's a simple conversion from one domain to another.
See
[grpc.go](https://github.com/go-kit/examples/blob/master/addsvc/pkg/addtransport/grpc.go)
for an example.
That's it!
The gRPC binding can be bound to a listener and serve normal gRPC requests.
And within your service, you can use standard go-kit components and idioms.
See [addsvc](https://github.com/go-kit/examples/tree/master/addsvc/) for
a complete working example with gRPC support. And remember: go-kit services
can support multiple transports simultaneously.
-140
View File
@@ -1,140 +0,0 @@
package grpc
import (
"context"
"fmt"
"reflect"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
"github.com/go-kit/kit/endpoint"
)
// Client wraps a gRPC connection and provides a method that implements
// endpoint.Endpoint.
type Client struct {
client *grpc.ClientConn
serviceName string
method string
enc EncodeRequestFunc
dec DecodeResponseFunc
grpcReply reflect.Type
before []ClientRequestFunc
after []ClientResponseFunc
finalizer []ClientFinalizerFunc
}
// NewClient constructs a usable Client for a single remote endpoint.
// Pass an zero-value protobuf message of the RPC response type as
// the grpcReply argument.
func NewClient(
cc *grpc.ClientConn,
serviceName string,
method string,
enc EncodeRequestFunc,
dec DecodeResponseFunc,
grpcReply interface{},
options ...ClientOption,
) *Client {
c := &Client{
client: cc,
method: fmt.Sprintf("/%s/%s", serviceName, method),
enc: enc,
dec: dec,
// We are using reflect.Indirect here to allow both reply structs and
// pointers to these reply structs. New consumers of the client should
// use structs directly, while existing consumers will not break if they
// remain to use pointers to structs.
grpcReply: reflect.TypeOf(
reflect.Indirect(
reflect.ValueOf(grpcReply),
).Interface(),
),
before: []ClientRequestFunc{},
after: []ClientResponseFunc{},
}
for _, option := range options {
option(c)
}
return c
}
// ClientOption sets an optional parameter for clients.
type ClientOption func(*Client)
// ClientBefore sets the RequestFuncs that are applied to the outgoing gRPC
// request before it's invoked.
func ClientBefore(before ...ClientRequestFunc) ClientOption {
return func(c *Client) { c.before = append(c.before, before...) }
}
// ClientAfter sets the ClientResponseFuncs that are applied to the incoming
// gRPC response prior to it being decoded. This is useful for obtaining
// response metadata and adding onto the context prior to decoding.
func ClientAfter(after ...ClientResponseFunc) ClientOption {
return func(c *Client) { c.after = append(c.after, after...) }
}
// ClientFinalizer is executed at the end of every gRPC request.
// By default, no finalizer is registered.
func ClientFinalizer(f ...ClientFinalizerFunc) ClientOption {
return func(s *Client) { s.finalizer = append(s.finalizer, f...) }
}
// Endpoint returns a usable endpoint that will invoke the gRPC specified by the
// client.
func (c Client) Endpoint() endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (response interface{}, err error) {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
if c.finalizer != nil {
defer func() {
for _, f := range c.finalizer {
f(ctx, err)
}
}()
}
ctx = context.WithValue(ctx, ContextKeyRequestMethod, c.method)
req, err := c.enc(ctx, request)
if err != nil {
return nil, err
}
md := &metadata.MD{}
for _, f := range c.before {
ctx = f(ctx, md)
}
ctx = metadata.NewOutgoingContext(ctx, *md)
var header, trailer metadata.MD
grpcReply := reflect.New(c.grpcReply).Interface()
if err = c.client.Invoke(
ctx, c.method, req, grpcReply, grpc.Header(&header),
grpc.Trailer(&trailer),
); err != nil {
return nil, err
}
for _, f := range c.after {
ctx = f(ctx, header, trailer)
}
response, err = c.dec(ctx, grpcReply)
if err != nil {
return nil, err
}
return response, nil
}
}
// ClientFinalizerFunc can be used to perform work at the end of a client gRPC
// request, after the response is returned. The principal
// intended use is for error logging. Additional response parameters are
// provided in the context under keys with the ContextKeyResponse prefix.
// Note: err may be nil. There maybe also no additional response parameters depending on
// when an error occurs.
type ClientFinalizerFunc func(ctx context.Context, err error)
-2
View File
@@ -1,2 +0,0 @@
// Package grpc provides a gRPC binding for endpoints.
package grpc
-29
View File
@@ -1,29 +0,0 @@
package grpc
import (
"context"
)
// DecodeRequestFunc extracts a user-domain request object from a gRPC request.
// It's designed to be used in gRPC servers, for server-side endpoints. One
// straightforward DecodeRequestFunc could be something that decodes from the
// gRPC request message to the concrete request type.
type DecodeRequestFunc func(context.Context, interface{}) (request interface{}, err error)
// EncodeRequestFunc encodes the passed request object into the gRPC request
// object. It's designed to be used in gRPC clients, for client-side endpoints.
// One straightforward EncodeRequestFunc could something that encodes the object
// directly to the gRPC request message.
type EncodeRequestFunc func(context.Context, interface{}) (request interface{}, err error)
// EncodeResponseFunc encodes the passed response object to the gRPC response
// message. It's designed to be used in gRPC servers, for server-side endpoints.
// One straightforward EncodeResponseFunc could be something that encodes the
// object directly to the gRPC response message.
type EncodeResponseFunc func(context.Context, interface{}) (response interface{}, err error)
// DecodeResponseFunc extracts a user-domain response object from a gRPC
// response object. It's designed to be used in gRPC clients, for client-side
// endpoints. One straightforward DecodeResponseFunc could be something that
// decodes from the gRPC response message to the concrete response type.
type DecodeResponseFunc func(context.Context, interface{}) (response interface{}, err error)
-81
View File
@@ -1,81 +0,0 @@
package grpc
import (
"context"
"encoding/base64"
"strings"
"google.golang.org/grpc/metadata"
)
const (
binHdrSuffix = "-bin"
)
// ClientRequestFunc may take information from context and use it to construct
// metadata headers to be transported to the server. ClientRequestFuncs are
// executed after creating the request but prior to sending the gRPC request to
// the server.
type ClientRequestFunc func(context.Context, *metadata.MD) context.Context
// ServerRequestFunc may take information from the received metadata header and
// use it to place items in the request scoped context. ServerRequestFuncs are
// executed prior to invoking the endpoint.
type ServerRequestFunc func(context.Context, metadata.MD) context.Context
// ServerResponseFunc may take information from a request context and use it to
// manipulate the gRPC response metadata headers and trailers. ResponseFuncs are
// only executed in servers, after invoking the endpoint but prior to writing a
// response.
type ServerResponseFunc func(ctx context.Context, header *metadata.MD, trailer *metadata.MD) context.Context
// ClientResponseFunc may take information from a gRPC metadata header and/or
// trailer and make the responses available for consumption. ClientResponseFuncs
// are only executed in clients, after a request has been made, but prior to it
// being decoded.
type ClientResponseFunc func(ctx context.Context, header metadata.MD, trailer metadata.MD) context.Context
// SetRequestHeader returns a ClientRequestFunc that sets the specified metadata
// key-value pair.
func SetRequestHeader(key, val string) ClientRequestFunc {
return func(ctx context.Context, md *metadata.MD) context.Context {
key, val := EncodeKeyValue(key, val)
(*md)[key] = append((*md)[key], val)
return ctx
}
}
// SetResponseHeader returns a ResponseFunc that sets the specified metadata
// key-value pair.
func SetResponseHeader(key, val string) ServerResponseFunc {
return func(ctx context.Context, md *metadata.MD, _ *metadata.MD) context.Context {
key, val := EncodeKeyValue(key, val)
(*md)[key] = append((*md)[key], val)
return ctx
}
}
// SetResponseTrailer returns a ResponseFunc that sets the specified metadata
// key-value pair.
func SetResponseTrailer(key, val string) ServerResponseFunc {
return func(ctx context.Context, _ *metadata.MD, md *metadata.MD) context.Context {
key, val := EncodeKeyValue(key, val)
(*md)[key] = append((*md)[key], val)
return ctx
}
}
// EncodeKeyValue sanitizes a key-value pair for use in gRPC metadata headers.
func EncodeKeyValue(key, val string) (string, string) {
key = strings.ToLower(key)
if strings.HasSuffix(key, binHdrSuffix) {
val = base64.StdEncoding.EncodeToString([]byte(val))
}
return key, val
}
type contextKey int
const (
ContextKeyRequestMethod contextKey = iota
)
-168
View File
@@ -1,168 +0,0 @@
package grpc
import (
"context"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
"github.com/go-kit/kit/endpoint"
"github.com/go-kit/kit/transport"
"github.com/go-kit/log"
)
// Handler which should be called from the gRPC binding of the service
// implementation. The incoming request parameter, and returned response
// parameter, are both gRPC types, not user-domain.
type Handler interface {
ServeGRPC(ctx context.Context, request interface{}) (context.Context, interface{}, error)
}
// Server wraps an endpoint and implements grpc.Handler.
type Server struct {
e endpoint.Endpoint
dec DecodeRequestFunc
enc EncodeResponseFunc
before []ServerRequestFunc
after []ServerResponseFunc
finalizer []ServerFinalizerFunc
errorHandler transport.ErrorHandler
}
// NewServer constructs a new server, which implements wraps the provided
// endpoint and implements the Handler interface. Consumers should write
// bindings that adapt the concrete gRPC methods from their compiled protobuf
// definitions to individual handlers. Request and response objects are from the
// caller business domain, not gRPC request and reply types.
func NewServer(
e endpoint.Endpoint,
dec DecodeRequestFunc,
enc EncodeResponseFunc,
options ...ServerOption,
) *Server {
s := &Server{
e: e,
dec: dec,
enc: enc,
errorHandler: transport.NewLogErrorHandler(log.NewNopLogger()),
}
for _, option := range options {
option(s)
}
return s
}
// ServerOption sets an optional parameter for servers.
type ServerOption func(*Server)
// ServerBefore functions are executed on the gRPC request object before the
// request is decoded.
func ServerBefore(before ...ServerRequestFunc) ServerOption {
return func(s *Server) { s.before = append(s.before, before...) }
}
// ServerAfter functions are executed on the gRPC response writer after the
// endpoint is invoked, but before anything is written to the client.
func ServerAfter(after ...ServerResponseFunc) ServerOption {
return func(s *Server) { s.after = append(s.after, after...) }
}
// ServerErrorLogger is used to log non-terminal errors. By default, no errors
// are logged.
// Deprecated: Use ServerErrorHandler instead.
func ServerErrorLogger(logger log.Logger) ServerOption {
return func(s *Server) { s.errorHandler = transport.NewLogErrorHandler(logger) }
}
// ServerErrorHandler is used to handle non-terminal errors. By default, non-terminal errors
// are ignored.
func ServerErrorHandler(errorHandler transport.ErrorHandler) ServerOption {
return func(s *Server) { s.errorHandler = errorHandler }
}
// ServerFinalizer is executed at the end of every gRPC request.
// By default, no finalizer is registered.
func ServerFinalizer(f ...ServerFinalizerFunc) ServerOption {
return func(s *Server) { s.finalizer = append(s.finalizer, f...) }
}
// ServeGRPC implements the Handler interface.
func (s Server) ServeGRPC(ctx context.Context, req interface{}) (retctx context.Context, resp interface{}, err error) {
// Retrieve gRPC metadata.
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
md = metadata.MD{}
}
if len(s.finalizer) > 0 {
defer func() {
for _, f := range s.finalizer {
f(ctx, err)
}
}()
}
for _, f := range s.before {
ctx = f(ctx, md)
}
var (
request interface{}
response interface{}
grpcResp interface{}
)
request, err = s.dec(ctx, req)
if err != nil {
s.errorHandler.Handle(ctx, err)
return ctx, nil, err
}
response, err = s.e(ctx, request)
if err != nil {
s.errorHandler.Handle(ctx, err)
return ctx, nil, err
}
var mdHeader, mdTrailer metadata.MD
for _, f := range s.after {
ctx = f(ctx, &mdHeader, &mdTrailer)
}
grpcResp, err = s.enc(ctx, response)
if err != nil {
s.errorHandler.Handle(ctx, err)
return ctx, nil, err
}
if len(mdHeader) > 0 {
if err = grpc.SendHeader(ctx, mdHeader); err != nil {
s.errorHandler.Handle(ctx, err)
return ctx, nil, err
}
}
if len(mdTrailer) > 0 {
if err = grpc.SetTrailer(ctx, mdTrailer); err != nil {
s.errorHandler.Handle(ctx, err)
return ctx, nil, err
}
}
return ctx, grpcResp, nil
}
// ServerFinalizerFunc can be used to perform work at the end of an gRPC
// request, after the response has been written to the client.
type ServerFinalizerFunc func(ctx context.Context, err error)
// Interceptor is a grpc UnaryInterceptor that injects the method name into
// context so it can be consumed by Go kit gRPC middlewares. The Interceptor
// typically is added at creation time of the grpc-go server.
// Like this: `grpc.NewServer(grpc.UnaryInterceptor(kitgrpc.Interceptor))`
func Interceptor(
ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler,
) (resp interface{}, err error) {
ctx = context.WithValue(ctx, ContextKeyRequestMethod, info.FullMethod)
return handler(ctx, req)
}
-219
View File
@@ -1,219 +0,0 @@
package http
import (
"bytes"
"context"
"encoding/json"
"encoding/xml"
"io"
"io/ioutil"
"net/http"
"net/url"
"github.com/go-kit/kit/endpoint"
)
// HTTPClient is an interface that models *http.Client.
type HTTPClient interface {
Do(req *http.Request) (*http.Response, error)
}
// Client wraps a URL and provides a method that implements endpoint.Endpoint.
type Client struct {
client HTTPClient
req CreateRequestFunc
dec DecodeResponseFunc
before []RequestFunc
after []ClientResponseFunc
finalizer []ClientFinalizerFunc
bufferedStream bool
}
// NewClient constructs a usable Client for a single remote method.
func NewClient(method string, tgt *url.URL, enc EncodeRequestFunc, dec DecodeResponseFunc, options ...ClientOption) *Client {
return NewExplicitClient(makeCreateRequestFunc(method, tgt, enc), dec, options...)
}
// NewExplicitClient is like NewClient but uses a CreateRequestFunc instead of a
// method, target URL, and EncodeRequestFunc, which allows for more control over
// the outgoing HTTP request.
func NewExplicitClient(req CreateRequestFunc, dec DecodeResponseFunc, options ...ClientOption) *Client {
c := &Client{
client: http.DefaultClient,
req: req,
dec: dec,
}
for _, option := range options {
option(c)
}
return c
}
// ClientOption sets an optional parameter for clients.
type ClientOption func(*Client)
// SetClient sets the underlying HTTP client used for requests.
// By default, http.DefaultClient is used.
func SetClient(client HTTPClient) ClientOption {
return func(c *Client) { c.client = client }
}
// ClientBefore adds one or more RequestFuncs to be applied to the outgoing HTTP
// request before it's invoked.
func ClientBefore(before ...RequestFunc) ClientOption {
return func(c *Client) { c.before = append(c.before, before...) }
}
// ClientAfter adds one or more ClientResponseFuncs, which are applied to the
// incoming HTTP response prior to it being decoded. This is useful for
// obtaining anything off of the response and adding it into the context prior
// to decoding.
func ClientAfter(after ...ClientResponseFunc) ClientOption {
return func(c *Client) { c.after = append(c.after, after...) }
}
// ClientFinalizer adds one or more ClientFinalizerFuncs to be executed at the
// end of every HTTP request. Finalizers are executed in the order in which they
// were added. By default, no finalizer is registered.
func ClientFinalizer(f ...ClientFinalizerFunc) ClientOption {
return func(s *Client) { s.finalizer = append(s.finalizer, f...) }
}
// BufferedStream sets whether the HTTP response body is left open, allowing it
// to be read from later. Useful for transporting a file as a buffered stream.
// That body has to be drained and closed to properly end the request.
func BufferedStream(buffered bool) ClientOption {
return func(c *Client) { c.bufferedStream = buffered }
}
// Endpoint returns a usable Go kit endpoint that calls the remote HTTP endpoint.
func (c Client) Endpoint() endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
ctx, cancel := context.WithCancel(ctx)
var (
resp *http.Response
err error
)
if c.finalizer != nil {
defer func() {
if resp != nil {
ctx = context.WithValue(ctx, ContextKeyResponseHeaders, resp.Header)
ctx = context.WithValue(ctx, ContextKeyResponseSize, resp.ContentLength)
}
for _, f := range c.finalizer {
f(ctx, err)
}
}()
}
req, err := c.req(ctx, request)
if err != nil {
cancel()
return nil, err
}
for _, f := range c.before {
ctx = f(ctx, req)
}
resp, err = c.client.Do(req.WithContext(ctx))
if err != nil {
cancel()
return nil, err
}
// If the caller asked for a buffered stream, we don't cancel the
// context when the endpoint returns. Instead, we should call the
// cancel func when closing the response body.
if c.bufferedStream {
resp.Body = bodyWithCancel{ReadCloser: resp.Body, cancel: cancel}
} else {
defer resp.Body.Close()
defer cancel()
}
for _, f := range c.after {
ctx = f(ctx, resp)
}
response, err := c.dec(ctx, resp)
if err != nil {
return nil, err
}
return response, nil
}
}
// bodyWithCancel is a wrapper for an io.ReadCloser with also a
// cancel function which is called when the Close is used
type bodyWithCancel struct {
io.ReadCloser
cancel context.CancelFunc
}
func (bwc bodyWithCancel) Close() error {
bwc.ReadCloser.Close()
bwc.cancel()
return nil
}
// ClientFinalizerFunc can be used to perform work at the end of a client HTTP
// request, after the response is returned. The principal
// intended use is for error logging. Additional response parameters are
// provided in the context under keys with the ContextKeyResponse prefix.
// Note: err may be nil. There maybe also no additional response parameters
// depending on when an error occurs.
type ClientFinalizerFunc func(ctx context.Context, err error)
// EncodeJSONRequest is an EncodeRequestFunc that serializes the request as a
// JSON object to the Request body. Many JSON-over-HTTP services can use it as
// a sensible default. If the request implements Headerer, the provided headers
// will be applied to the request.
func EncodeJSONRequest(c context.Context, r *http.Request, request interface{}) error {
r.Header.Set("Content-Type", "application/json; charset=utf-8")
if headerer, ok := request.(Headerer); ok {
for k := range headerer.Headers() {
r.Header.Set(k, headerer.Headers().Get(k))
}
}
var b bytes.Buffer
r.Body = ioutil.NopCloser(&b)
return json.NewEncoder(&b).Encode(request)
}
// EncodeXMLRequest is an EncodeRequestFunc that serializes the request as a
// XML object to the Request body. If the request implements Headerer,
// the provided headers will be applied to the request.
func EncodeXMLRequest(c context.Context, r *http.Request, request interface{}) error {
r.Header.Set("Content-Type", "text/xml; charset=utf-8")
if headerer, ok := request.(Headerer); ok {
for k := range headerer.Headers() {
r.Header.Set(k, headerer.Headers().Get(k))
}
}
var b bytes.Buffer
r.Body = ioutil.NopCloser(&b)
return xml.NewEncoder(&b).Encode(request)
}
//
//
//
func makeCreateRequestFunc(method string, target *url.URL, enc EncodeRequestFunc) CreateRequestFunc {
return func(ctx context.Context, request interface{}) (*http.Request, error) {
req, err := http.NewRequest(method, target.String(), nil)
if err != nil {
return nil, err
}
if err = enc(ctx, req, request); err != nil {
return nil, err
}
return req, nil
}
}
-2
View File
@@ -1,2 +0,0 @@
// Package http provides a general purpose HTTP binding for endpoints.
package http
-36
View File
@@ -1,36 +0,0 @@
package http
import (
"context"
"net/http"
)
// DecodeRequestFunc extracts a user-domain request object from an HTTP
// request object. It's designed to be used in HTTP servers, for server-side
// endpoints. One straightforward DecodeRequestFunc could be something that
// JSON decodes from the request body to the concrete request type.
type DecodeRequestFunc func(context.Context, *http.Request) (request interface{}, err error)
// EncodeRequestFunc encodes the passed request object into the HTTP request
// object. It's designed to be used in HTTP clients, for client-side
// endpoints. One straightforward EncodeRequestFunc could be something that JSON
// encodes the object directly to the request body.
type EncodeRequestFunc func(context.Context, *http.Request, interface{}) error
// CreateRequestFunc creates an outgoing HTTP request based on the passed
// request object. It's designed to be used in HTTP clients, for client-side
// endpoints. It's a more powerful version of EncodeRequestFunc, and can be used
// if more fine-grained control of the HTTP request is required.
type CreateRequestFunc func(context.Context, interface{}) (*http.Request, error)
// EncodeResponseFunc encodes the passed response object to the HTTP response
// writer. It's designed to be used in HTTP servers, for server-side
// endpoints. One straightforward EncodeResponseFunc could be something that
// JSON encodes the object directly to the response body.
type EncodeResponseFunc func(context.Context, http.ResponseWriter, interface{}) error
// DecodeResponseFunc extracts a user-domain response object from an HTTP
// response object. It's designed to be used in HTTP clients, for client-side
// endpoints. One straightforward DecodeResponseFunc could be something that
// JSON decodes from the response body to the concrete response type.
type DecodeResponseFunc func(context.Context, *http.Response) (response interface{}, err error)
-257
View File
@@ -1,257 +0,0 @@
package http
import (
"io"
"net/http"
)
type interceptingWriter struct {
http.ResponseWriter
code int
written int64
}
// WriteHeader may not be explicitly called, so care must be taken to
// initialize w.code to its default value of http.StatusOK.
func (w *interceptingWriter) WriteHeader(code int) {
w.code = code
w.ResponseWriter.WriteHeader(code)
}
func (w *interceptingWriter) Write(p []byte) (int, error) {
n, err := w.ResponseWriter.Write(p)
w.written += int64(n)
return n, err
}
// reimplementInterfaces returns a wrapped version of the embedded ResponseWriter
// and selectively implements the same combination of additional interfaces as
// the wrapped one. The interfaces it may implement are: http.Hijacker,
// http.CloseNotifier, http.Pusher, http.Flusher and io.ReaderFrom. The standard
// library is known to assert the existence of these interfaces and behaves
// differently. This implementation is derived from
// https://github.com/felixge/httpsnoop.
func (w *interceptingWriter) reimplementInterfaces() http.ResponseWriter {
var (
hj, i0 = w.ResponseWriter.(http.Hijacker)
cn, i1 = w.ResponseWriter.(http.CloseNotifier)
pu, i2 = w.ResponseWriter.(http.Pusher)
fl, i3 = w.ResponseWriter.(http.Flusher)
rf, i4 = w.ResponseWriter.(io.ReaderFrom)
)
switch {
case !i0 && !i1 && !i2 && !i3 && !i4:
return struct {
http.ResponseWriter
}{w}
case !i0 && !i1 && !i2 && !i3 && i4:
return struct {
http.ResponseWriter
io.ReaderFrom
}{w, rf}
case !i0 && !i1 && !i2 && i3 && !i4:
return struct {
http.ResponseWriter
http.Flusher
}{w, fl}
case !i0 && !i1 && !i2 && i3 && i4:
return struct {
http.ResponseWriter
http.Flusher
io.ReaderFrom
}{w, fl, rf}
case !i0 && !i1 && i2 && !i3 && !i4:
return struct {
http.ResponseWriter
http.Pusher
}{w, pu}
case !i0 && !i1 && i2 && !i3 && i4:
return struct {
http.ResponseWriter
http.Pusher
io.ReaderFrom
}{w, pu, rf}
case !i0 && !i1 && i2 && i3 && !i4:
return struct {
http.ResponseWriter
http.Pusher
http.Flusher
}{w, pu, fl}
case !i0 && !i1 && i2 && i3 && i4:
return struct {
http.ResponseWriter
http.Pusher
http.Flusher
io.ReaderFrom
}{w, pu, fl, rf}
case !i0 && i1 && !i2 && !i3 && !i4:
return struct {
http.ResponseWriter
http.CloseNotifier
}{w, cn}
case !i0 && i1 && !i2 && !i3 && i4:
return struct {
http.ResponseWriter
http.CloseNotifier
io.ReaderFrom
}{w, cn, rf}
case !i0 && i1 && !i2 && i3 && !i4:
return struct {
http.ResponseWriter
http.CloseNotifier
http.Flusher
}{w, cn, fl}
case !i0 && i1 && !i2 && i3 && i4:
return struct {
http.ResponseWriter
http.CloseNotifier
http.Flusher
io.ReaderFrom
}{w, cn, fl, rf}
case !i0 && i1 && i2 && !i3 && !i4:
return struct {
http.ResponseWriter
http.CloseNotifier
http.Pusher
}{w, cn, pu}
case !i0 && i1 && i2 && !i3 && i4:
return struct {
http.ResponseWriter
http.CloseNotifier
http.Pusher
io.ReaderFrom
}{w, cn, pu, rf}
case !i0 && i1 && i2 && i3 && !i4:
return struct {
http.ResponseWriter
http.CloseNotifier
http.Pusher
http.Flusher
}{w, cn, pu, fl}
case !i0 && i1 && i2 && i3 && i4:
return struct {
http.ResponseWriter
http.CloseNotifier
http.Pusher
http.Flusher
io.ReaderFrom
}{w, cn, pu, fl, rf}
case i0 && !i1 && !i2 && !i3 && !i4:
return struct {
http.ResponseWriter
http.Hijacker
}{w, hj}
case i0 && !i1 && !i2 && !i3 && i4:
return struct {
http.ResponseWriter
http.Hijacker
io.ReaderFrom
}{w, hj, rf}
case i0 && !i1 && !i2 && i3 && !i4:
return struct {
http.ResponseWriter
http.Hijacker
http.Flusher
}{w, hj, fl}
case i0 && !i1 && !i2 && i3 && i4:
return struct {
http.ResponseWriter
http.Hijacker
http.Flusher
io.ReaderFrom
}{w, hj, fl, rf}
case i0 && !i1 && i2 && !i3 && !i4:
return struct {
http.ResponseWriter
http.Hijacker
http.Pusher
}{w, hj, pu}
case i0 && !i1 && i2 && !i3 && i4:
return struct {
http.ResponseWriter
http.Hijacker
http.Pusher
io.ReaderFrom
}{w, hj, pu, rf}
case i0 && !i1 && i2 && i3 && !i4:
return struct {
http.ResponseWriter
http.Hijacker
http.Pusher
http.Flusher
}{w, hj, pu, fl}
case i0 && !i1 && i2 && i3 && i4:
return struct {
http.ResponseWriter
http.Hijacker
http.Pusher
http.Flusher
io.ReaderFrom
}{w, hj, pu, fl, rf}
case i0 && i1 && !i2 && !i3 && !i4:
return struct {
http.ResponseWriter
http.Hijacker
http.CloseNotifier
}{w, hj, cn}
case i0 && i1 && !i2 && !i3 && i4:
return struct {
http.ResponseWriter
http.Hijacker
http.CloseNotifier
io.ReaderFrom
}{w, hj, cn, rf}
case i0 && i1 && !i2 && i3 && !i4:
return struct {
http.ResponseWriter
http.Hijacker
http.CloseNotifier
http.Flusher
}{w, hj, cn, fl}
case i0 && i1 && !i2 && i3 && i4:
return struct {
http.ResponseWriter
http.Hijacker
http.CloseNotifier
http.Flusher
io.ReaderFrom
}{w, hj, cn, fl, rf}
case i0 && i1 && i2 && !i3 && !i4:
return struct {
http.ResponseWriter
http.Hijacker
http.CloseNotifier
http.Pusher
}{w, hj, cn, pu}
case i0 && i1 && i2 && !i3 && i4:
return struct {
http.ResponseWriter
http.Hijacker
http.CloseNotifier
http.Pusher
io.ReaderFrom
}{w, hj, cn, pu, rf}
case i0 && i1 && i2 && i3 && !i4:
return struct {
http.ResponseWriter
http.Hijacker
http.CloseNotifier
http.Pusher
http.Flusher
}{w, hj, cn, pu, fl}
case i0 && i1 && i2 && i3 && i4:
return struct {
http.ResponseWriter
http.Hijacker
http.CloseNotifier
http.Pusher
http.Flusher
io.ReaderFrom
}{w, hj, cn, pu, fl, rf}
default:
return struct {
http.ResponseWriter
}{w}
}
}
-133
View File
@@ -1,133 +0,0 @@
package http
import (
"context"
"net/http"
)
// RequestFunc may take information from an HTTP request and put it into a
// request context. In Servers, RequestFuncs are executed prior to invoking the
// endpoint. In Clients, RequestFuncs are executed after creating the request
// but prior to invoking the HTTP client.
type RequestFunc func(context.Context, *http.Request) context.Context
// ServerResponseFunc may take information from a request context and use it to
// manipulate a ResponseWriter. ServerResponseFuncs are only executed in
// servers, after invoking the endpoint but prior to writing a response.
type ServerResponseFunc func(context.Context, http.ResponseWriter) context.Context
// ClientResponseFunc may take information from an HTTP request and make the
// response available for consumption. ClientResponseFuncs are only executed in
// clients, after a request has been made, but prior to it being decoded.
type ClientResponseFunc func(context.Context, *http.Response) context.Context
// SetContentType returns a ServerResponseFunc that sets the Content-Type header
// to the provided value.
func SetContentType(contentType string) ServerResponseFunc {
return SetResponseHeader("Content-Type", contentType)
}
// SetResponseHeader returns a ServerResponseFunc that sets the given header.
func SetResponseHeader(key, val string) ServerResponseFunc {
return func(ctx context.Context, w http.ResponseWriter) context.Context {
w.Header().Set(key, val)
return ctx
}
}
// SetRequestHeader returns a RequestFunc that sets the given header.
func SetRequestHeader(key, val string) RequestFunc {
return func(ctx context.Context, r *http.Request) context.Context {
r.Header.Set(key, val)
return ctx
}
}
// PopulateRequestContext is a RequestFunc that populates several values into
// the context from the HTTP request. Those values may be extracted using the
// corresponding ContextKey type in this package.
func PopulateRequestContext(ctx context.Context, r *http.Request) context.Context {
for k, v := range map[contextKey]string{
ContextKeyRequestMethod: r.Method,
ContextKeyRequestURI: r.RequestURI,
ContextKeyRequestPath: r.URL.Path,
ContextKeyRequestProto: r.Proto,
ContextKeyRequestHost: r.Host,
ContextKeyRequestRemoteAddr: r.RemoteAddr,
ContextKeyRequestXForwardedFor: r.Header.Get("X-Forwarded-For"),
ContextKeyRequestXForwardedProto: r.Header.Get("X-Forwarded-Proto"),
ContextKeyRequestAuthorization: r.Header.Get("Authorization"),
ContextKeyRequestReferer: r.Header.Get("Referer"),
ContextKeyRequestUserAgent: r.Header.Get("User-Agent"),
ContextKeyRequestXRequestID: r.Header.Get("X-Request-Id"),
ContextKeyRequestAccept: r.Header.Get("Accept"),
} {
ctx = context.WithValue(ctx, k, v)
}
return ctx
}
type contextKey int
const (
// ContextKeyRequestMethod is populated in the context by
// PopulateRequestContext. Its value is r.Method.
ContextKeyRequestMethod contextKey = iota
// ContextKeyRequestURI is populated in the context by
// PopulateRequestContext. Its value is r.RequestURI.
ContextKeyRequestURI
// ContextKeyRequestPath is populated in the context by
// PopulateRequestContext. Its value is r.URL.Path.
ContextKeyRequestPath
// ContextKeyRequestProto is populated in the context by
// PopulateRequestContext. Its value is r.Proto.
ContextKeyRequestProto
// ContextKeyRequestHost is populated in the context by
// PopulateRequestContext. Its value is r.Host.
ContextKeyRequestHost
// ContextKeyRequestRemoteAddr is populated in the context by
// PopulateRequestContext. Its value is r.RemoteAddr.
ContextKeyRequestRemoteAddr
// ContextKeyRequestXForwardedFor is populated in the context by
// PopulateRequestContext. Its value is r.Header.Get("X-Forwarded-For").
ContextKeyRequestXForwardedFor
// ContextKeyRequestXForwardedProto is populated in the context by
// PopulateRequestContext. Its value is r.Header.Get("X-Forwarded-Proto").
ContextKeyRequestXForwardedProto
// ContextKeyRequestAuthorization is populated in the context by
// PopulateRequestContext. Its value is r.Header.Get("Authorization").
ContextKeyRequestAuthorization
// ContextKeyRequestReferer is populated in the context by
// PopulateRequestContext. Its value is r.Header.Get("Referer").
ContextKeyRequestReferer
// ContextKeyRequestUserAgent is populated in the context by
// PopulateRequestContext. Its value is r.Header.Get("User-Agent").
ContextKeyRequestUserAgent
// ContextKeyRequestXRequestID is populated in the context by
// PopulateRequestContext. Its value is r.Header.Get("X-Request-Id").
ContextKeyRequestXRequestID
// ContextKeyRequestAccept is populated in the context by
// PopulateRequestContext. Its value is r.Header.Get("Accept").
ContextKeyRequestAccept
// ContextKeyResponseHeaders is populated in the context whenever a
// ServerFinalizerFunc is specified. Its value is of type http.Header, and
// is captured only once the entire response has been written.
ContextKeyResponseHeaders
// ContextKeyResponseSize is populated in the context whenever a
// ServerFinalizerFunc is specified. Its value is of type int64.
ContextKeyResponseSize
)
-225
View File
@@ -1,225 +0,0 @@
package http
import (
"context"
"encoding/json"
"net/http"
"github.com/go-kit/kit/endpoint"
"github.com/go-kit/kit/transport"
"github.com/go-kit/log"
)
// Server wraps an endpoint and implements http.Handler.
type Server struct {
e endpoint.Endpoint
dec DecodeRequestFunc
enc EncodeResponseFunc
before []RequestFunc
after []ServerResponseFunc
errorEncoder ErrorEncoder
finalizer []ServerFinalizerFunc
errorHandler transport.ErrorHandler
}
// NewServer constructs a new server, which implements http.Handler and wraps
// the provided endpoint.
func NewServer(
e endpoint.Endpoint,
dec DecodeRequestFunc,
enc EncodeResponseFunc,
options ...ServerOption,
) *Server {
s := &Server{
e: e,
dec: dec,
enc: enc,
errorEncoder: DefaultErrorEncoder,
errorHandler: transport.NewLogErrorHandler(log.NewNopLogger()),
}
for _, option := range options {
option(s)
}
return s
}
// ServerOption sets an optional parameter for servers.
type ServerOption func(*Server)
// ServerBefore functions are executed on the HTTP request object before the
// request is decoded.
func ServerBefore(before ...RequestFunc) ServerOption {
return func(s *Server) { s.before = append(s.before, before...) }
}
// ServerAfter functions are executed on the HTTP response writer after the
// endpoint is invoked, but before anything is written to the client.
func ServerAfter(after ...ServerResponseFunc) ServerOption {
return func(s *Server) { s.after = append(s.after, after...) }
}
// ServerErrorEncoder is used to encode errors to the http.ResponseWriter
// whenever they're encountered in the processing of a request. Clients can
// use this to provide custom error formatting and response codes. By default,
// errors will be written with the DefaultErrorEncoder.
func ServerErrorEncoder(ee ErrorEncoder) ServerOption {
return func(s *Server) { s.errorEncoder = ee }
}
// ServerErrorLogger is used to log non-terminal errors. By default, no errors
// are logged. This is intended as a diagnostic measure. Finer-grained control
// of error handling, including logging in more detail, should be performed in a
// custom ServerErrorEncoder or ServerFinalizer, both of which have access to
// the context.
// Deprecated: Use ServerErrorHandler instead.
func ServerErrorLogger(logger log.Logger) ServerOption {
return func(s *Server) { s.errorHandler = transport.NewLogErrorHandler(logger) }
}
// ServerErrorHandler is used to handle non-terminal errors. By default, non-terminal errors
// are ignored. This is intended as a diagnostic measure. Finer-grained control
// of error handling, including logging in more detail, should be performed in a
// custom ServerErrorEncoder or ServerFinalizer, both of which have access to
// the context.
func ServerErrorHandler(errorHandler transport.ErrorHandler) ServerOption {
return func(s *Server) { s.errorHandler = errorHandler }
}
// ServerFinalizer is executed at the end of every HTTP request.
// By default, no finalizer is registered.
func ServerFinalizer(f ...ServerFinalizerFunc) ServerOption {
return func(s *Server) { s.finalizer = append(s.finalizer, f...) }
}
// ServeHTTP implements http.Handler.
func (s Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
if len(s.finalizer) > 0 {
iw := &interceptingWriter{w, http.StatusOK, 0}
defer func() {
ctx = context.WithValue(ctx, ContextKeyResponseHeaders, iw.Header())
ctx = context.WithValue(ctx, ContextKeyResponseSize, iw.written)
for _, f := range s.finalizer {
f(ctx, iw.code, r)
}
}()
w = iw.reimplementInterfaces()
}
for _, f := range s.before {
ctx = f(ctx, r)
}
request, err := s.dec(ctx, r)
if err != nil {
s.errorHandler.Handle(ctx, err)
s.errorEncoder(ctx, err, w)
return
}
response, err := s.e(ctx, request)
if err != nil {
s.errorHandler.Handle(ctx, err)
s.errorEncoder(ctx, err, w)
return
}
for _, f := range s.after {
ctx = f(ctx, w)
}
if err := s.enc(ctx, w, response); err != nil {
s.errorHandler.Handle(ctx, err)
s.errorEncoder(ctx, err, w)
return
}
}
// ErrorEncoder is responsible for encoding an error to the ResponseWriter.
// Users are encouraged to use custom ErrorEncoders to encode HTTP errors to
// their clients, and will likely want to pass and check for their own error
// types. See the example shipping/handling service.
type ErrorEncoder func(ctx context.Context, err error, w http.ResponseWriter)
// ServerFinalizerFunc can be used to perform work at the end of an HTTP
// request, after the response has been written to the client. The principal
// intended use is for request logging. In addition to the response code
// provided in the function signature, additional response parameters are
// provided in the context under keys with the ContextKeyResponse prefix.
type ServerFinalizerFunc func(ctx context.Context, code int, r *http.Request)
// NopRequestDecoder is a DecodeRequestFunc that can be used for requests that do not
// need to be decoded, and simply returns nil, nil.
func NopRequestDecoder(ctx context.Context, r *http.Request) (interface{}, error) {
return nil, nil
}
// EncodeJSONResponse is a EncodeResponseFunc that serializes the response as a
// JSON object to the ResponseWriter. Many JSON-over-HTTP services can use it as
// a sensible default. If the response implements Headerer, the provided headers
// will be applied to the response. If the response implements StatusCoder, the
// provided StatusCode will be used instead of 200.
func EncodeJSONResponse(_ context.Context, w http.ResponseWriter, response interface{}) error {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
if headerer, ok := response.(Headerer); ok {
for k, values := range headerer.Headers() {
for _, v := range values {
w.Header().Add(k, v)
}
}
}
code := http.StatusOK
if sc, ok := response.(StatusCoder); ok {
code = sc.StatusCode()
}
w.WriteHeader(code)
if code == http.StatusNoContent {
return nil
}
return json.NewEncoder(w).Encode(response)
}
// DefaultErrorEncoder writes the error to the ResponseWriter, by default a
// content type of text/plain, a body of the plain text of the error, and a
// status code of 500. If the error implements Headerer, the provided headers
// will be applied to the response. If the error implements json.Marshaler, and
// the marshaling succeeds, a content type of application/json and the JSON
// encoded form of the error will be used. If the error implements StatusCoder,
// the provided StatusCode will be used instead of 500.
func DefaultErrorEncoder(_ context.Context, err error, w http.ResponseWriter) {
contentType, body := "text/plain; charset=utf-8", []byte(err.Error())
if marshaler, ok := err.(json.Marshaler); ok {
if jsonBody, marshalErr := marshaler.MarshalJSON(); marshalErr == nil {
contentType, body = "application/json; charset=utf-8", jsonBody
}
}
w.Header().Set("Content-Type", contentType)
if headerer, ok := err.(Headerer); ok {
for k, values := range headerer.Headers() {
for _, v := range values {
w.Header().Add(k, v)
}
}
}
code := http.StatusInternalServerError
if sc, ok := err.(StatusCoder); ok {
code = sc.StatusCode()
}
w.WriteHeader(code)
w.Write(body)
}
// StatusCoder is checked by DefaultErrorEncoder. If an error value implements
// StatusCoder, the StatusCode will be used when encoding the error. By default,
// StatusInternalServerError (500) is used.
type StatusCoder interface {
StatusCode() int
}
// Headerer is checked by DefaultErrorEncoder. If an error value implements
// Headerer, the provided headers will be applied to the response writer, after
// the Content-Type is set.
type Headerer interface {
Headers() http.Header
}
-15
View File
@@ -1,15 +0,0 @@
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
# vendor/
-21
View File
@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2021 Go kit
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.
-156
View File
@@ -1,156 +0,0 @@
# package log
[![Go Reference](https://pkg.go.dev/badge/github.com/go-kit/log.svg)](https://pkg.go.dev/github.com/go-kit/log)
[![Go Report Card](https://goreportcard.com/badge/go-kit/log)](https://goreportcard.com/report/go-kit/log)
[![GitHub Actions](https://github.com/go-kit/log/actions/workflows/test.yml/badge.svg)](https://github.com/go-kit/log/actions/workflows/test.yml)
[![Coverage Status](https://coveralls.io/repos/github/go-kit/log/badge.svg?branch=main)](https://coveralls.io/github/go-kit/log?branch=main)
`package log` provides a minimal interface for structured logging in services.
It may be wrapped to encode conventions, enforce type-safety, provide leveled
logging, and so on. It can be used for both typical application log events,
and log-structured data streams.
## Structured logging
Structured logging is, basically, conceding to the reality that logs are
_data_, and warrant some level of schematic rigor. Using a stricter,
key/value-oriented message format for our logs, containing contextual and
semantic information, makes it much easier to get insight into the
operational activity of the systems we build. Consequently, `package log` is
of the strong belief that "[the benefits of structured logging outweigh the
minimal effort involved](https://www.thoughtworks.com/radar/techniques/structured-logging)".
Migrating from unstructured to structured logging is probably a lot easier
than you'd expect.
```go
// Unstructured
log.Printf("HTTP server listening on %s", addr)
// Structured
logger.Log("transport", "HTTP", "addr", addr, "msg", "listening")
```
## Usage
### Typical application logging
```go
w := log.NewSyncWriter(os.Stderr)
logger := log.NewLogfmtLogger(w)
logger.Log("question", "what is the meaning of life?", "answer", 42)
// Output:
// question="what is the meaning of life?" answer=42
```
### Contextual Loggers
```go
func main() {
var logger log.Logger
logger = log.NewLogfmtLogger(log.NewSyncWriter(os.Stderr))
logger = log.With(logger, "instance_id", 123)
logger.Log("msg", "starting")
NewWorker(log.With(logger, "component", "worker")).Run()
NewSlacker(log.With(logger, "component", "slacker")).Run()
}
// Output:
// instance_id=123 msg=starting
// instance_id=123 component=worker msg=running
// instance_id=123 component=slacker msg=running
```
### Interact with stdlib logger
Redirect stdlib logger to Go kit logger.
```go
import (
"os"
stdlog "log"
kitlog "github.com/go-kit/log"
)
func main() {
logger := kitlog.NewJSONLogger(kitlog.NewSyncWriter(os.Stdout))
stdlog.SetOutput(kitlog.NewStdlibAdapter(logger))
stdlog.Print("I sure like pie")
}
// Output:
// {"msg":"I sure like pie","ts":"2016/01/01 12:34:56"}
```
Or, if, for legacy reasons, you need to pipe all of your logging through the
stdlib log package, you can redirect Go kit logger to the stdlib logger.
```go
logger := kitlog.NewLogfmtLogger(kitlog.StdlibWriter{})
logger.Log("legacy", true, "msg", "at least it's something")
// Output:
// 2016/01/01 12:34:56 legacy=true msg="at least it's something"
```
### Timestamps and callers
```go
var logger log.Logger
logger = log.NewLogfmtLogger(log.NewSyncWriter(os.Stderr))
logger = log.With(logger, "ts", log.DefaultTimestampUTC, "caller", log.DefaultCaller)
logger.Log("msg", "hello")
// Output:
// ts=2016-01-01T12:34:56Z caller=main.go:15 msg=hello
```
## Levels
Log levels are supported via the [level package](https://godoc.org/github.com/go-kit/log/level).
## Supported output formats
- [Logfmt](https://brandur.org/logfmt) ([see also](https://blog.codeship.com/logfmt-a-log-format-thats-easy-to-read-and-write))
- JSON
## Enhancements
`package log` is centered on the one-method Logger interface.
```go
type Logger interface {
Log(keyvals ...interface{}) error
}
```
This interface, and its supporting code like is the product of much iteration
and evaluation. For more details on the evolution of the Logger interface,
see [The Hunt for a Logger Interface](http://go-talks.appspot.com/github.com/ChrisHines/talks/structured-logging/structured-logging.slide#1),
a talk by [Chris Hines](https://github.com/ChrisHines).
Also, please see
[#63](https://github.com/go-kit/kit/issues/63),
[#76](https://github.com/go-kit/kit/pull/76),
[#131](https://github.com/go-kit/kit/issues/131),
[#157](https://github.com/go-kit/kit/pull/157),
[#164](https://github.com/go-kit/kit/issues/164), and
[#252](https://github.com/go-kit/kit/pull/252)
to review historical conversations about package log and the Logger interface.
Value-add packages and suggestions,
like improvements to [the leveled logger](https://godoc.org/github.com/go-kit/log/level),
are of course welcome. Good proposals should
- Be composable with [contextual loggers](https://godoc.org/github.com/go-kit/log#With),
- Not break the behavior of [log.Caller](https://godoc.org/github.com/go-kit/log#Caller) in any wrapped contextual loggers, and
- Be friendly to packages that accept only an unadorned log.Logger.
## Benchmarks & comparisons
There are a few Go logging benchmarks and comparisons that include Go kit's package log.
- [imkira/go-loggers-bench](https://github.com/imkira/go-loggers-bench) includes kit/log
- [uber-common/zap](https://github.com/uber-common/zap), a zero-alloc logging library, includes a comparison with kit/log
-116
View File
@@ -1,116 +0,0 @@
// Package log provides a structured logger.
//
// Structured logging produces logs easily consumed later by humans or
// machines. Humans might be interested in debugging errors, or tracing
// specific requests. Machines might be interested in counting interesting
// events, or aggregating information for off-line processing. In both cases,
// it is important that the log messages are structured and actionable.
// Package log is designed to encourage both of these best practices.
//
// Basic Usage
//
// The fundamental interface is Logger. Loggers create log events from
// key/value data. The Logger interface has a single method, Log, which
// accepts a sequence of alternating key/value pairs, which this package names
// keyvals.
//
// type Logger interface {
// Log(keyvals ...interface{}) error
// }
//
// Here is an example of a function using a Logger to create log events.
//
// func RunTask(task Task, logger log.Logger) string {
// logger.Log("taskID", task.ID, "event", "starting task")
// ...
// logger.Log("taskID", task.ID, "event", "task complete")
// }
//
// The keys in the above example are "taskID" and "event". The values are
// task.ID, "starting task", and "task complete". Every key is followed
// immediately by its value.
//
// Keys are usually plain strings. Values may be any type that has a sensible
// encoding in the chosen log format. With structured logging it is a good
// idea to log simple values without formatting them. This practice allows
// the chosen logger to encode values in the most appropriate way.
//
// Contextual Loggers
//
// A contextual logger stores keyvals that it includes in all log events.
// Building appropriate contextual loggers reduces repetition and aids
// consistency in the resulting log output. With, WithPrefix, and WithSuffix
// add context to a logger. We can use With to improve the RunTask example.
//
// func RunTask(task Task, logger log.Logger) string {
// logger = log.With(logger, "taskID", task.ID)
// logger.Log("event", "starting task")
// ...
// taskHelper(task.Cmd, logger)
// ...
// logger.Log("event", "task complete")
// }
//
// The improved version emits the same log events as the original for the
// first and last calls to Log. Passing the contextual logger to taskHelper
// enables each log event created by taskHelper to include the task.ID even
// though taskHelper does not have access to that value. Using contextual
// loggers this way simplifies producing log output that enables tracing the
// life cycle of individual tasks. (See the Contextual example for the full
// code of the above snippet.)
//
// Dynamic Contextual Values
//
// A Valuer function stored in a contextual logger generates a new value each
// time an event is logged. The Valuer example demonstrates how this feature
// works.
//
// Valuers provide the basis for consistently logging timestamps and source
// code location. The log package defines several valuers for that purpose.
// See Timestamp, DefaultTimestamp, DefaultTimestampUTC, Caller, and
// DefaultCaller. A common logger initialization sequence that ensures all log
// entries contain a timestamp and source location looks like this:
//
// logger := log.NewLogfmtLogger(log.NewSyncWriter(os.Stdout))
// logger = log.With(logger, "ts", log.DefaultTimestampUTC, "caller", log.DefaultCaller)
//
// Concurrent Safety
//
// Applications with multiple goroutines want each log event written to the
// same logger to remain separate from other log events. Package log provides
// two simple solutions for concurrent safe logging.
//
// NewSyncWriter wraps an io.Writer and serializes each call to its Write
// method. Using a SyncWriter has the benefit that the smallest practical
// portion of the logging logic is performed within a mutex, but it requires
// the formatting Logger to make only one call to Write per log event.
//
// NewSyncLogger wraps any Logger and serializes each call to its Log method.
// Using a SyncLogger has the benefit that it guarantees each log event is
// handled atomically within the wrapped logger, but it typically serializes
// both the formatting and output logic. Use a SyncLogger if the formatting
// logger may perform multiple writes per log event.
//
// Error Handling
//
// This package relies on the practice of wrapping or decorating loggers with
// other loggers to provide composable pieces of functionality. It also means
// that Logger.Log must return an error because some
// implementations—especially those that output log data to an io.Writer—may
// encounter errors that cannot be handled locally. This in turn means that
// Loggers that wrap other loggers should return errors from the wrapped
// logger up the stack.
//
// Fortunately, the decorator pattern also provides a way to avoid the
// necessity to check for errors every time an application calls Logger.Log.
// An application required to panic whenever its Logger encounters
// an error could initialize its logger as follows.
//
// fmtlogger := log.NewLogfmtLogger(log.NewSyncWriter(os.Stdout))
// logger := log.LoggerFunc(func(keyvals ...interface{}) error {
// if err := fmtlogger.Log(keyvals...); err != nil {
// panic(err)
// }
// return nil
// })
package log
-91
View File
@@ -1,91 +0,0 @@
package log
import (
"encoding"
"encoding/json"
"fmt"
"io"
"reflect"
)
type jsonLogger struct {
io.Writer
}
// NewJSONLogger returns a Logger that encodes keyvals to the Writer as a
// single JSON object. Each log event produces no more than one call to
// w.Write. The passed Writer must be safe for concurrent use by multiple
// goroutines if the returned Logger will be used concurrently.
func NewJSONLogger(w io.Writer) Logger {
return &jsonLogger{w}
}
func (l *jsonLogger) Log(keyvals ...interface{}) error {
n := (len(keyvals) + 1) / 2 // +1 to handle case when len is odd
m := make(map[string]interface{}, n)
for i := 0; i < len(keyvals); i += 2 {
k := keyvals[i]
var v interface{} = ErrMissingValue
if i+1 < len(keyvals) {
v = keyvals[i+1]
}
merge(m, k, v)
}
enc := json.NewEncoder(l.Writer)
enc.SetEscapeHTML(false)
return enc.Encode(m)
}
func merge(dst map[string]interface{}, k, v interface{}) {
var key string
switch x := k.(type) {
case string:
key = x
case fmt.Stringer:
key = safeString(x)
default:
key = fmt.Sprint(x)
}
// We want json.Marshaler and encoding.TextMarshaller to take priority over
// err.Error() and v.String(). But json.Marshall (called later) does that by
// default so we force a no-op if it's one of those 2 case.
switch x := v.(type) {
case json.Marshaler:
case encoding.TextMarshaler:
case error:
v = safeError(x)
case fmt.Stringer:
v = safeString(x)
}
dst[key] = v
}
func safeString(str fmt.Stringer) (s string) {
defer func() {
if panicVal := recover(); panicVal != nil {
if v := reflect.ValueOf(str); v.Kind() == reflect.Ptr && v.IsNil() {
s = "NULL"
} else {
s = fmt.Sprintf("PANIC in String method: %v", panicVal)
}
}
}()
s = str.String()
return
}
func safeError(err error) (s interface{}) {
defer func() {
if panicVal := recover(); panicVal != nil {
if v := reflect.ValueOf(err); v.Kind() == reflect.Ptr && v.IsNil() {
s = nil
} else {
s = fmt.Sprintf("PANIC in Error method: %v", panicVal)
}
}
}()
s = err.Error()
return
}
-179
View File
@@ -1,179 +0,0 @@
package log
import "errors"
// Logger is the fundamental interface for all log operations. Log creates a
// log event from keyvals, a variadic sequence of alternating keys and values.
// Implementations must be safe for concurrent use by multiple goroutines. In
// particular, any implementation of Logger that appends to keyvals or
// modifies or retains any of its elements must make a copy first.
type Logger interface {
Log(keyvals ...interface{}) error
}
// ErrMissingValue is appended to keyvals slices with odd length to substitute
// the missing value.
var ErrMissingValue = errors.New("(MISSING)")
// With returns a new contextual logger with keyvals prepended to those passed
// to calls to Log. If logger is also a contextual logger created by With,
// WithPrefix, or WithSuffix, keyvals is appended to the existing context.
//
// The returned Logger replaces all value elements (odd indexes) containing a
// Valuer with their generated value for each call to its Log method.
func With(logger Logger, keyvals ...interface{}) Logger {
if len(keyvals) == 0 {
return logger
}
l := newContext(logger)
kvs := append(l.keyvals, keyvals...)
if len(kvs)%2 != 0 {
kvs = append(kvs, ErrMissingValue)
}
return &context{
logger: l.logger,
// Limiting the capacity of the stored keyvals ensures that a new
// backing array is created if the slice must grow in Log or With.
// Using the extra capacity without copying risks a data race that
// would violate the Logger interface contract.
keyvals: kvs[:len(kvs):len(kvs)],
hasValuer: l.hasValuer || containsValuer(keyvals),
sKeyvals: l.sKeyvals,
sHasValuer: l.sHasValuer,
}
}
// WithPrefix returns a new contextual logger with keyvals prepended to those
// passed to calls to Log. If logger is also a contextual logger created by
// With, WithPrefix, or WithSuffix, keyvals is prepended to the existing context.
//
// The returned Logger replaces all value elements (odd indexes) containing a
// Valuer with their generated value for each call to its Log method.
func WithPrefix(logger Logger, keyvals ...interface{}) Logger {
if len(keyvals) == 0 {
return logger
}
l := newContext(logger)
// Limiting the capacity of the stored keyvals ensures that a new
// backing array is created if the slice must grow in Log or With.
// Using the extra capacity without copying risks a data race that
// would violate the Logger interface contract.
n := len(l.keyvals) + len(keyvals)
if len(keyvals)%2 != 0 {
n++
}
kvs := make([]interface{}, 0, n)
kvs = append(kvs, keyvals...)
if len(kvs)%2 != 0 {
kvs = append(kvs, ErrMissingValue)
}
kvs = append(kvs, l.keyvals...)
return &context{
logger: l.logger,
keyvals: kvs,
hasValuer: l.hasValuer || containsValuer(keyvals),
sKeyvals: l.sKeyvals,
sHasValuer: l.sHasValuer,
}
}
// WithSuffix returns a new contextual logger with keyvals appended to those
// passed to calls to Log. If logger is also a contextual logger created by
// With, WithPrefix, or WithSuffix, keyvals is appended to the existing context.
//
// The returned Logger replaces all value elements (odd indexes) containing a
// Valuer with their generated value for each call to its Log method.
func WithSuffix(logger Logger, keyvals ...interface{}) Logger {
if len(keyvals) == 0 {
return logger
}
l := newContext(logger)
// Limiting the capacity of the stored keyvals ensures that a new
// backing array is created if the slice must grow in Log or With.
// Using the extra capacity without copying risks a data race that
// would violate the Logger interface contract.
n := len(l.sKeyvals) + len(keyvals)
if len(keyvals)%2 != 0 {
n++
}
kvs := make([]interface{}, 0, n)
kvs = append(kvs, keyvals...)
if len(kvs)%2 != 0 {
kvs = append(kvs, ErrMissingValue)
}
kvs = append(l.sKeyvals, kvs...)
return &context{
logger: l.logger,
keyvals: l.keyvals,
hasValuer: l.hasValuer,
sKeyvals: kvs,
sHasValuer: l.sHasValuer || containsValuer(keyvals),
}
}
// context is the Logger implementation returned by With, WithPrefix, and
// WithSuffix. It wraps a Logger and holds keyvals that it includes in all
// log events. Its Log method calls bindValues to generate values for each
// Valuer in the context keyvals.
//
// A context must always have the same number of stack frames between calls to
// its Log method and the eventual binding of Valuers to their value. This
// requirement comes from the functional requirement to allow a context to
// resolve application call site information for a Caller stored in the
// context. To do this we must be able to predict the number of logging
// functions on the stack when bindValues is called.
//
// Two implementation details provide the needed stack depth consistency.
//
// 1. newContext avoids introducing an additional layer when asked to
// wrap another context.
// 2. With, WithPrefix, and WithSuffix avoid introducing an additional
// layer by returning a newly constructed context with a merged keyvals
// rather than simply wrapping the existing context.
type context struct {
logger Logger
keyvals []interface{}
sKeyvals []interface{} // suffixes
hasValuer bool
sHasValuer bool
}
func newContext(logger Logger) *context {
if c, ok := logger.(*context); ok {
return c
}
return &context{logger: logger}
}
// Log replaces all value elements (odd indexes) containing a Valuer in the
// stored context with their generated value, appends keyvals, and passes the
// result to the wrapped Logger.
func (l *context) Log(keyvals ...interface{}) error {
kvs := append(l.keyvals, keyvals...)
if len(kvs)%2 != 0 {
kvs = append(kvs, ErrMissingValue)
}
if l.hasValuer {
// If no keyvals were appended above then we must copy l.keyvals so
// that future log events will reevaluate the stored Valuers.
if len(keyvals) == 0 {
kvs = append([]interface{}{}, l.keyvals...)
}
bindValues(kvs[:(len(l.keyvals))])
}
kvs = append(kvs, l.sKeyvals...)
if l.sHasValuer {
bindValues(kvs[len(kvs)-len(l.sKeyvals):])
}
return l.logger.Log(kvs...)
}
// LoggerFunc is an adapter to allow use of ordinary functions as Loggers. If
// f is a function with the appropriate signature, LoggerFunc(f) is a Logger
// object that calls f.
type LoggerFunc func(...interface{}) error
// Log implements Logger by calling f(keyvals...).
func (f LoggerFunc) Log(keyvals ...interface{}) error {
return f(keyvals...)
}
-62
View File
@@ -1,62 +0,0 @@
package log
import (
"bytes"
"io"
"sync"
"github.com/go-logfmt/logfmt"
)
type logfmtEncoder struct {
*logfmt.Encoder
buf bytes.Buffer
}
func (l *logfmtEncoder) Reset() {
l.Encoder.Reset()
l.buf.Reset()
}
var logfmtEncoderPool = sync.Pool{
New: func() interface{} {
var enc logfmtEncoder
enc.Encoder = logfmt.NewEncoder(&enc.buf)
return &enc
},
}
type logfmtLogger struct {
w io.Writer
}
// NewLogfmtLogger returns a logger that encodes keyvals to the Writer in
// logfmt format. Each log event produces no more than one call to w.Write.
// The passed Writer must be safe for concurrent use by multiple goroutines if
// the returned Logger will be used concurrently.
func NewLogfmtLogger(w io.Writer) Logger {
return &logfmtLogger{w}
}
func (l logfmtLogger) Log(keyvals ...interface{}) error {
enc := logfmtEncoderPool.Get().(*logfmtEncoder)
enc.Reset()
defer logfmtEncoderPool.Put(enc)
if err := enc.EncodeKeyvals(keyvals...); err != nil {
return err
}
// Add newline to the end of the buffer
if err := enc.EndRecord(); err != nil {
return err
}
// The Logger interface requires implementations to be safe for concurrent
// use by multiple goroutines. For this implementation that means making
// only one call to l.w.Write() for each call to Log.
if _, err := l.w.Write(enc.buf.Bytes()); err != nil {
return err
}
return nil
}
-8
View File
@@ -1,8 +0,0 @@
package log
type nopLogger struct{}
// NewNopLogger returns a logger that doesn't do anything.
func NewNopLogger() Logger { return nopLogger{} }
func (nopLogger) Log(...interface{}) error { return nil }

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