├── .circleci
└── config.yml
├── .github
└── workflows
│ └── go.yml
├── .gitignore
├── .travis.yml
├── LICENSE
├── Makefile
├── README-zh_CN.md
├── README.md
├── bf
├── bitset
│ ├── bitset.go
│ └── bitset_test.go
├── bloomfilter.go
├── bloomfilter
│ ├── bloomfilter.go
│ └── bloomfilter_test.go
├── hasher.go
├── hasher_test.go
├── rotate
│ ├── compressor.go
│ ├── rotate.go
│ └── rotate_test.go
├── rpc
│ ├── client
│ │ ├── client.go
│ │ └── main.go
│ ├── rpc.go
│ ├── rpc_test.go
│ └── server
│ │ └── server.go
└── testutils
│ └── testutils.go
├── cmd
├── check.go
├── cmd.go
├── graph.go
├── root.go
└── run.go
├── config
├── config.go
├── config_test.go
├── parser.go
└── uri.go
├── core
├── melody
│ ├── backend_factory.go
│ ├── encoding.go
│ ├── executor.go
│ ├── handler_factory.go
│ ├── plugin.go
│ ├── proxy_factory.go
│ ├── router_engine.go
│ └── sd.go
└── version.go
├── coverage.sh
├── docs
├── DNS SRV.md
├── DNS.md
├── Docker常用启动脚本.md
├── ETCD使用介绍.md
├── HTTP公钥固定.md
├── JWT.md
├── MIME嗅探防御.md
├── Melody Server.xmind
├── Middleware命名空间.md
├── TLS和SSL协议.md
├── VuePress入门使用.md
├── img
│ ├── example.png
│ ├── melody.png
│ ├── min-melody-logo.png
│ ├── p-1.png
│ ├── p-2.png
│ ├── p-3.png
│ └── struct.png
├── questions.md
├── 一致性算法.md
├── 令牌桶算法.md
├── 关于HTTP严格传输安全.md
├── 关键的结构体说明.md
├── 布隆过滤器.md
├── 点劫持保护.md
└── 相关博客链接.md
├── encoding
├── encoding.go
├── json.go
├── register.go
└── string.go
├── go.mod
├── go.sum
├── logging
├── log.go
└── log_test.go
├── main.go
├── main_test.go
├── melody-cases
├── Makefile
├── coupon
│ └── main.go
├── finance
│ └── main.go
├── melody.json
├── profile
│ └── main.go
└── role
│ └── main.go
├── melody.json
├── middleware
├── melody-alert
│ ├── checker.go
│ ├── config.go
│ └── model
│ │ └── warning.go
├── melody-bloomfilter
│ └── bloomfilter.go
├── melody-botmonitor
│ ├── gin
│ │ ├── monitor.go
│ │ └── monitor_test.go
│ ├── melody
│ │ └── config.go
│ ├── monitor.go
│ ├── monitor_bechmark_test.go
│ └── monitor_test.go
├── melody-circuitbreaker
│ ├── gobreaker.go
│ └── proxy
│ │ ├── proxy.go
│ │ └── proxy_test.go
├── melody-consul
│ ├── client.go
│ └── config.go
├── melody-cors
│ ├── cors.go
│ ├── cors_test.go
│ └── gin
│ │ ├── cors.go
│ │ └── cors_test.go
├── melody-etcd
│ ├── client.go
│ ├── config.go
│ ├── subscriber.go
│ └── subscriber_test.go
├── melody-gelf
│ └── log.go
├── melody-gologging
│ └── log.go
├── melody-httpsecure
│ ├── gin
│ │ ├── httpsecure.go
│ │ └── httpsecure_test.go
│ ├── httpsecure.go
│ └── httpsecure_test.go
├── melody-influxdb
│ ├── buffer.go
│ ├── config.go
│ ├── counter
│ │ └── counter.go
│ ├── gauge
│ │ └── gauge.go
│ ├── handler.go
│ ├── histogram
│ │ └── histogram.go
│ ├── influxdb.go
│ ├── middleware
│ │ └── cors.go
│ ├── refresh
│ │ └── refresh.go
│ ├── response
│ │ └── response.go
│ └── ws
│ │ ├── alert_handler.go
│ │ ├── convert
│ │ └── convert.go
│ │ ├── debug_handler.go
│ │ ├── handler
│ │ └── chart.go
│ │ ├── query.go
│ │ ├── requests_handler.go
│ │ ├── router_handler.go
│ │ ├── runtime_handler.go
│ │ ├── struct.go
│ │ ├── template.go
│ │ ├── timer.go
│ │ └── websocket.go
├── melody-jose
│ ├── fixtures
│ │ ├── jwks.json
│ │ ├── private.json
│ │ ├── public.json
│ │ └── symmetric.json
│ ├── gin
│ │ ├── jose.go
│ │ ├── jose_example_test.go
│ │ └── jose_test.go
│ ├── jose.go
│ ├── jose_test.go
│ ├── jwk.go
│ ├── jwk_example_test.go
│ ├── jwk_test.go
│ ├── jws.go
│ ├── jws_test.go
│ ├── rejecter.go
│ └── rejecter_test.go
├── melody-jsonschema
│ ├── jsonschema.go
│ └── jsonschema_test.go
├── melody-logstash
│ └── logger.go
├── melody-martian
│ ├── context.go
│ ├── martian.go
│ ├── martian_test.go
│ ├── register.go
│ ├── register
│ │ └── register.go
│ └── static.go
├── melody-metrics
│ ├── gin
│ │ └── metrics.go
│ ├── metrics.go
│ ├── proxy.go
│ ├── router.go
│ └── stats.go
├── melody-opencensus
│ └── opencensus.go
├── melody-ratelimit
│ ├── juju
│ │ ├── juju.go
│ │ ├── proxy
│ │ │ ├── proxy.go
│ │ │ ├── proxy_benchmark_test.go
│ │ │ └── proxy_test.go
│ │ └── router
│ │ │ ├── gin
│ │ │ └── gin.go
│ │ │ ├── router.go
│ │ │ └── router_test.go
│ ├── melodyrate.go
│ ├── melodyrate_test.go
│ └── pseudoFNV64a.go
├── melody-rss
│ └── rss.go
├── melody-viper
│ └── viper.go
└── melody-xml
│ └── xml.go
├── plugin
└── plugin.go
├── proxy
├── balancing.go
├── concurrent.go
├── concurrent_test.go
├── factory.go
├── factory_test.go
├── formatter.go
├── formatter_test.go
├── http.go
├── http_response.go
├── http_test.go
├── merging.go
├── merging_test.go
├── proxy.go
├── proxy_test.go
├── register.go
├── register_test.go
├── request.go
├── shadow.go
├── shadow_test.go
├── static.go
└── static_test.go
├── register
├── internal
│ └── untyped.go
├── register.go
└── register_test.go
├── router
├── gin
│ ├── debug.go
│ ├── endpoint.go
│ ├── render.go
│ ├── render_test.go
│ ├── router.go
│ └── router_test.go
└── router.go
├── sd
├── dnssrv
│ ├── subscriber.go
│ └── subscriber_test.go
├── loadbalancing.go
├── register.go
└── subscriber.go
└── transport
└── http
├── client
├── executor.go
└── status.go
└── server
├── plugin
├── plugin.go
└── server.go
└── server.go
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | # Golang CircleCI 2.0 configuration file
2 | #
3 | # Check https://circleci.com/docs/2.0/language-go/ for more details
4 | version: 2
5 | jobs:
6 | build:
7 | docker:
8 | # specify the version
9 | - image: circleci/golang:1.14
10 |
11 | # Specify service dependencies here if necessary
12 | # CircleCI maintains a library of pre-built images
13 | # documented at https://circleci.com/docs/2.0/circleci-images/
14 | # - image: circleci/postgres:9.4
15 |
16 | #### TEMPLATE_NOTE: go expects specific checkout path representing url
17 | #### expecting it in the form of
18 | #### /go/src/github.com/circleci/go-tool
19 | #### /go/src/bitbucket.org/circleci/go-tool
20 | working_directory: /go/src/github.com/{{ORG_NAME}}/{{REPO_NAME}}
21 | steps:
22 | - checkout
23 |
24 | # specify any bash command here prefixed with `run: `
25 | - run: go get -v -t -d ./...
26 | - run: make test
27 | - run: ls -la
28 | - run: pwd
--------------------------------------------------------------------------------
/.github/workflows/go.yml:
--------------------------------------------------------------------------------
1 | name: Melody code build
2 | on:
3 | push:
4 | branches:
5 | - '*'
6 | paths-ignore:
7 | - './docs/**'
8 | - '**.md'
9 | pull_request:
10 | branches:
11 | - '*'
12 | paths-ignore:
13 | - './docs/**'
14 | - '**.md'
15 | jobs:
16 | build:
17 | name: Build
18 | runs-on: ubuntu-latest
19 | steps:
20 |
21 | - name: Set up Go 1.13
22 | uses: actions/setup-go@v1
23 | with:
24 | go-version: 1.13
25 | id: go
26 |
27 | - name: Check out code into the Go module directory
28 | uses: actions/checkout@v2
29 |
30 | - name: Get dependencies
31 | run: |
32 |
33 | go get -v -t -d ./...
34 | if [ -f Gopkg.toml ]; then
35 | curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
36 | dep ensure
37 | fi
38 |
39 | - name: Test and Build
40 | run: make all
41 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by .ignore support plugin (hsz.mobi)
2 | ### JetBrains template
3 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio
4 |
5 | *.iml
6 |
7 | ## Directory-based project format:
8 | .vscode
9 | .idea/
10 | # if you remove the above rule, at least ignore the following:
11 |
12 | # User-specific stuff:
13 | # .idea/workspace.xml
14 | # .idea/tasks.xml
15 | # .idea/dictionaries
16 |
17 | # Sensitive or high-churn files:
18 | # .idea/dataSources.ids
19 | # .idea/dataSources.xml
20 | # .idea/sqlDataSources.xml
21 | # .idea/dynamic.xml
22 | # .idea/uiDesigner.xml
23 |
24 | # Gradle:
25 | # .idea/gradle.xml
26 | # .idea/libraries
27 | *.log
28 |
29 | # Mongo Explorer plugin:
30 | # .idea/mongoSettings.xml
31 |
32 | ## File-based project format:
33 | *.ipr
34 | *.iws
35 |
36 | ## Plugin-specific files:
37 |
38 | # IntelliJ
39 | /out/
40 |
41 | # mpeltonen/sbt-idea plugin
42 | .idea_modules/
43 |
44 | # JIRA plugin
45 | atlassian-ide-plugin.xml
46 |
47 | # Crashlytics plugin (for Android Studio and IntelliJ)
48 | com_crashlytics_export_strings.xml
49 | crashlytics.properties
50 | crashlytics-build.properties
51 | ### Maven template
52 | target/
53 | pom.xml.tag
54 | pom.xml.releaseBackup
55 | pom.xml.versionsBackup
56 | pom.xml.next
57 | release.properties
58 | dependency-reduced-pom.xml
59 | buildNumber.properties
60 | .mvn/timing.properties
61 | melody.exe
62 | *.out
63 | *.cover
64 | .cover/
65 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: go
2 |
3 | go:
4 | - "1.13.x"
5 | - "1.12.x"
6 | - "1.11.x"
7 |
8 | before_install:
9 | - mkdir -p /home/travis/gopath-tmp
10 | - export GOPATH="/home/travis/gopath-tmp"
11 |
12 | script:
13 | - make coveralls
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: all deps test build benchmark coveralls
2 |
3 | DEP_VERSION=0.5.0
4 | OS := $(shell uname | tr '[:upper:]' '[:lower:]')
5 | GIT_COMMIT := $(shell git rev-parse --short=7 HEAD)
6 |
7 |
8 | all: test build
9 |
10 | test:
11 | go generate ./...
12 | go test -cover -race ./...
13 |
14 | benchmark:
15 | @mkdir -p bench_res
16 | @touch bench_res/${GIT_COMMIT}.out
17 | @go test -run none -bench . -benchmem ./... >> bench_res/${GIT_COMMIT}.out
18 |
19 | build:
20 | @echo "Build ..."
21 | @go build
22 | @echo "You can use melody now!"
23 |
24 | coveralls: all
25 | go get github.com/mattn/goveralls
26 | go install github.com/mattn/goveralls
27 | sh coverage.sh --coveralls
28 |
--------------------------------------------------------------------------------
/bf/bitset/bitset.go:
--------------------------------------------------------------------------------
1 | package bitset
2 |
3 | import (
4 | "github.com/tmthrgd/go-bitset"
5 | "melody/bf"
6 | )
7 |
8 | type BitSet struct {
9 | bs bitset.Bitset
10 | hasher bf.Hash
11 | }
12 |
13 | // NewBitSet constructor for BitSet with an array of m bits
14 | func NewBitSet(m uint) *BitSet {
15 | return &BitSet{bitset.New(m), bf.MD5}
16 | }
17 |
18 | // Add element to bitset
19 | func (bs *BitSet) Add(elem []byte) {
20 | bs.bs.Set(bs.hasher(elem)[0] % bs.bs.Len())
21 | }
22 |
23 | // Check element in bitset
24 | func (bs *BitSet) Check(elem []byte) bool {
25 | return bs.bs.IsSet(bs.hasher(elem)[0] % bs.bs.Len())
26 | }
27 |
28 | // Union 组合两个 bitSet
29 | func (bs *BitSet) Union(that interface{}) (float64, error) {
30 | other, ok := that.(*BitSet)
31 | if !ok {
32 | return bs.getCount(), bf.ErrImpossibleToTreat
33 | }
34 |
35 | bs.bs.Union(bs.bs, other.bs)
36 | return bs.getCount(), nil
37 | }
38 |
39 | func (bs *BitSet) getCount() float64 {
40 | return float64(bs.bs.Count()) / float64(bs.bs.Len())
41 | }
42 |
--------------------------------------------------------------------------------
/bf/bitset/bitset_test.go:
--------------------------------------------------------------------------------
1 | package bitset
2 |
3 | import (
4 | "melody/bf"
5 | "melody/bf/testutils"
6 | "testing"
7 | )
8 |
9 | func TestBitSet(t *testing.T) {
10 | testutils.CallSet(t, NewBitSet(24))
11 | }
12 |
13 | func TestBitSet_Union_ok(t *testing.T) {
14 | set1 := NewBitSet(24)
15 | set2 := NewBitSet(24)
16 |
17 | testutils.CallSetUnion(t, set1, set2)
18 | }
19 |
20 | func TestBitSet_Union_ko(t *testing.T) {
21 | set1 := NewBitSet(24)
22 | set2 := 24
23 |
24 | if _, err := set1.Union(set2); err != bf.ErrImpossibleToTreat {
25 | t.Errorf("Unexpected error, %v", err)
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/bf/bloomfilter.go:
--------------------------------------------------------------------------------
1 | // Package bloomfilter contains common data and interfaces needed to implement bloomfilters.
2 | //
3 | // 理论基础: http://llimllib.github.io/bloomfilter-tutorial/zh_CN/
4 | // 我们实现了三种 bloomfilter :衍生自bitSet的、 sliding bloomfilters、 rpc bloomfilter
5 | package bf
6 |
7 | import "math"
8 |
9 | type BloomFilter interface {
10 | Add([]byte)
11 | Check([]byte) bool
12 | Union(interface{}) (float64, error)
13 | }
14 |
15 | // P - 失误几率
16 | // N - 存到过滤器的element的个数
17 | // HashName - "default" or "optimal"
18 | type Config struct {
19 | N uint
20 | P float64
21 | HashName string
22 | }
23 |
24 | // 给 sliding bf 的 previous 用的
25 | var EmptyConfig = Config{
26 | N: 2,
27 | P: .5,
28 | }
29 |
30 | // M bit array 的长度
31 | func M(n uint, p float64) uint {
32 | return uint(math.Ceil(-(float64(n) * math.Log(p)) / math.Log(math.Pow(2.0, math.Log(2.0)))))
33 | }
34 |
35 | // K hash函数的个数
36 | func K(m, n uint) uint {
37 | return uint(math.Ceil(math.Log(2.0) * float64(m) / float64(n)))
38 | }
39 |
40 | type EmptySet int
41 |
42 | // Check implementation for EmptySet
43 | func (e EmptySet) Check(_ []byte) bool { return false }
44 |
45 | // Add implementation for EmptySet
46 | func (e EmptySet) Add(_ []byte) {}
47 |
48 | // Union implementation for EmptySet
49 | func (e EmptySet) Union(interface{}) (float64, error) { return -1, nil }
50 |
--------------------------------------------------------------------------------
/bf/bloomfilter/bloomfilter_test.go:
--------------------------------------------------------------------------------
1 | package bloomfilter
2 |
3 | import (
4 | "bytes"
5 | "encoding/gob"
6 | "melody/bf"
7 | "melody/bf/testutils"
8 | "strings"
9 | "testing"
10 | )
11 |
12 | func TestBloomFilter(t *testing.T) {
13 | testutils.CallSet(t, New(testutils.TestCfg))
14 | }
15 |
16 | func TestBloomFilter_Union_ok(t *testing.T) {
17 | set1 := New(testutils.TestCfg)
18 | set2 := New(testutils.TestCfg)
19 |
20 | testutils.CallSetUnion(t, set1, set2)
21 | }
22 |
23 | func TestBloomFilter_Union_koIncorrectType(t *testing.T) {
24 | set1 := New(testutils.TestCfg)
25 | set2 := 24
26 |
27 | if _, err := set1.Union(set2); err != bf.ErrImpossibleToTreat {
28 | t.Errorf("Unexpected error, %v", err)
29 | }
30 | }
31 |
32 | func TestBloomFilter_Union_koDifferentM(t *testing.T) {
33 | set1 := New(testutils.TestCfg)
34 | set2 := New(testutils.TestCfg)
35 | set2.m = 111
36 | if _, err := set1.Union(set2); err == nil || !strings.Contains(err.Error(), "!= m2(111)") {
37 | t.Errorf("Unexpected error, %v", err)
38 | }
39 | }
40 |
41 | func TestBloomFilter_Union_koDifferentK(t *testing.T) {
42 | set1 := New(testutils.TestCfg)
43 | set2 := New(testutils.TestCfg)
44 | set2.k = 111
45 | if _, err := set1.Union(set2); err == nil || !strings.Contains(err.Error(), "!= k2(111)") {
46 | t.Errorf("Unexpected error, %v", err)
47 | }
48 | }
49 |
50 | func TestBloomFilter_gobEncoder(t *testing.T) {
51 | bf1 := New(testutils.TestCfg)
52 | bf1.Add([]byte("casa"))
53 | bf1.Add([]byte("grrrrr"))
54 | bf1.Add([]byte("something"))
55 |
56 | serialized := new(bytes.Buffer)
57 | if err := gob.NewEncoder(serialized).Encode(bf1); err != nil {
58 | t.Errorf("error encoding BF, %s", err.Error())
59 | }
60 |
61 | bf2 := new(BloomFilter)
62 | if err := gob.NewDecoder(serialized).Decode(bf2); err != nil {
63 | t.Errorf("error encoding BF, %s", err.Error())
64 | }
65 |
66 | if !bf2.Check([]byte("casa")) {
67 | t.Error("error: \"casa\" not found")
68 | }
69 | if !bf2.Check([]byte("grrrrr")) {
70 | t.Error("error: \"grrrrr\" not found")
71 | }
72 | if !bf2.Check([]byte("something")) {
73 | t.Error("error: \"something\" not found")
74 | }
75 | }
76 |
77 | func TestUnmarshalBinary_ko(t *testing.T) {
78 | set1 := New(testutils.TestCfg)
79 | if err := set1.UnmarshalBinary([]byte{}); err == nil {
80 | t.Error("should have given error")
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/bf/hasher.go:
--------------------------------------------------------------------------------
1 | package bf
2 |
3 | import (
4 | "crypto/md5"
5 | "crypto/sha1"
6 | "encoding/binary"
7 | "fmt"
8 | "hash"
9 | "hash/crc64"
10 | "hash/fnv"
11 | )
12 |
13 | type Hash func([]byte) []uint
14 | type HashFactory func(uint) []Hash
15 |
16 | const (
17 | HASHER_DEFAULT = "default"
18 | HASHER_OPTIMAL = "optimal"
19 | )
20 |
21 | var (
22 | defaultHashers = []Hash{
23 | MD5,
24 | CRC64,
25 | SHA1,
26 | FNV64,
27 | FNV128,
28 | }
29 |
30 | HashFactoryNames = map[string]HashFactory{
31 | HASHER_DEFAULT: DefaultHashFactory,
32 | HASHER_OPTIMAL: OptimalHashFactory,
33 | }
34 |
35 | ErrImpossibleToTreat = fmt.Errorf("unable to union")
36 |
37 | MD5 = HashWrapper(md5.New())
38 | SHA1 = HashWrapper(sha1.New())
39 | CRC64 = HashWrapper(crc64.New(crc64.MakeTable(crc64.ECMA)))
40 | FNV64 = HashWrapper(fnv.New64())
41 | FNV128 = HashWrapper(fnv.New128())
42 | )
43 |
44 | // 取 K 个hash函数
45 | func DefaultHashFactory(k uint) []Hash {
46 | if k > uint(len(defaultHashers)) {
47 | k = uint(len(defaultHashers))
48 | }
49 | return defaultHashers[:k]
50 | }
51 |
52 | // 最优hash工厂
53 | // FNV能快速hash大量数据并保持较小的冲突率,它的高度分散使它适用于hash一些非常相近的字符串,
54 | // 比如URL,hostname,文件名,text,IP地址等。
55 | func OptimalHashFactory(k uint) []Hash {
56 | return []Hash{
57 | func(b []byte) []uint {
58 | hs := FNV128(b)
59 | out := make([]uint, k)
60 |
61 | for i := range out {
62 | // 128位 => len(out) = 2
63 | out[i] = hs[0] + uint(i)*hs[1]
64 | }
65 | return out
66 | },
67 | }
68 | }
69 |
70 | func HashWrapper(h hash.Hash) Hash {
71 | return func(elem []byte) []uint {
72 | h.Reset()
73 | h.Write(elem)
74 | result := h.Sum(nil)
75 | // 例如md5 128位 = 16字节 len(result) = 16
76 | out := make([]uint, len(result)/8) // len(out) = 2
77 | for i := 0; i < len(result)/8; i++ {
78 | // binary.LittleEndian.Uint64(result[i*8 : (i+1)*8])
79 | // 取 result 中连续8个字节转换成 uint64 why???
80 | // Little-Endian就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。
81 | out[i] = uint(binary.LittleEndian.Uint64(result[i*8 : (i+1)*8]))
82 | }
83 | return out
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/bf/hasher_test.go:
--------------------------------------------------------------------------------
1 | package bf
2 |
3 | import (
4 | "fmt"
5 | "reflect"
6 | "testing"
7 | )
8 |
9 | func TestHasher(t *testing.T) {
10 | for _, hash := range defaultHashers {
11 | array1 := []byte{1, 2, 3}
12 | if !reflect.DeepEqual(hash(array1), hash(array1)) {
13 | t.Error("undeterministic")
14 | }
15 | }
16 | }
17 |
18 | func BenchmarkHasher(b *testing.B) {
19 | for k, hash := range defaultHashers {
20 | b.Run(fmt.Sprintf("hasher %d", k), func(b *testing.B) {
21 | for i := 0; i < b.N; i++ {
22 | array1 := []byte{1, 2, 3}
23 | hash(array1)
24 | }
25 | })
26 | }
27 | }
28 |
29 | func TestDefaultHashFactory(t *testing.T) {
30 | for _, hash := range DefaultHashFactory(23) {
31 | array1 := []byte{1, 2, 3}
32 | if !reflect.DeepEqual(hash(array1), hash(array1)) {
33 | t.Error("undeterministic")
34 | }
35 | }
36 | }
37 |
38 | func TestOptimalHashFactory(t *testing.T) {
39 | for _, hash := range OptimalHashFactory(23) {
40 | array1 := []byte{1, 2, 3}
41 | if !reflect.DeepEqual(hash(array1), hash(array1)) {
42 | t.Error("undeterministic")
43 | }
44 | }
45 | }
46 |
47 | func BenchmarkOptimalHashFactory(b *testing.B) {
48 | for k, hash := range OptimalHashFactory(23) {
49 | b.Run(fmt.Sprintf("hasher %d", k), func(b *testing.B) {
50 | for i := 0; i < b.N; i++ {
51 | array1 := []byte{1, 2, 3}
52 | hash(array1)
53 | }
54 | })
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/bf/rotate/compressor.go:
--------------------------------------------------------------------------------
1 | package rotate
2 |
3 | import (
4 | "compress/gzip"
5 | "io"
6 | )
7 |
8 | type Compressor interface {
9 | NewWriter(io.Writer) io.WriteCloser
10 | NewReader(io.Reader) (io.Reader, error)
11 | }
12 |
13 | var compressor Compressor = new(Gzip)
14 |
15 | func SetCompressor(c Compressor) {
16 | compressor = c
17 | }
18 |
19 | type Gzip int
20 |
21 | func (g *Gzip) NewWriter(w io.Writer) io.WriteCloser {
22 | return gzip.NewWriter(w)
23 | }
24 |
25 | func (g *Gzip) NewReader(r io.Reader) (io.Reader, error) {
26 | return gzip.NewReader(r)
27 | }
28 |
--------------------------------------------------------------------------------
/bf/rpc/client/client.go:
--------------------------------------------------------------------------------
1 | // Package client implements an rpc client for the BloomFilter, along with Add and Check methods.
2 | package main
3 |
4 | import (
5 | "errors"
6 | "fmt"
7 | "melody/bf/rotate"
8 | rpc_bf "melody/bf/rpc"
9 | "net/rpc"
10 | )
11 |
12 | // BloomFilter rpc client type
13 | type BloomFilter struct {
14 | client *rpc.Client
15 | }
16 |
17 | // New creates a new bloomfilter rpc client with address
18 | func New(address string) (*BloomFilter, error) {
19 | client, err := rpc.Dial("tcp", address)
20 | if err != nil {
21 | return nil, err
22 | }
23 | return &BloomFilter{client}, nil
24 | }
25 |
26 | // Add element through bloomfilter rpc client
27 | func (b *BloomFilter) Add(elem []byte) {
28 | var addOutput rpc_bf.AddOutput
29 | if err := b.client.Call("BloomFilterRPC.Add", rpc_bf.AddInput{[][]byte{elem}}, &addOutput); err != nil {
30 | fmt.Println("error on adding bloomfilter:", err.Error())
31 | }
32 | }
33 |
34 | // Check present element through bloomfilter rpc client
35 | func (b *BloomFilter) Check(elem []byte) bool {
36 | var checkOutput rpc_bf.CheckOutput
37 | if err := b.client.Call("BloomFilterRPC.Check", rpc_bf.CheckInput{[][]byte{elem}}, &checkOutput); err != nil {
38 | fmt.Println("error on check bloomfilter:", err.Error())
39 | return false
40 | }
41 | for _, v := range checkOutput.Checks {
42 | if !v {
43 | return false
44 | }
45 | }
46 | return true
47 | }
48 |
49 | // Union element through bloomfilter rpc client with sliding bloomfilters
50 | func (b *BloomFilter) Union(that interface{}) (float64, error) {
51 | v, ok := that.(*rotate.BloomFilter)
52 | if !ok {
53 | return -1.0, errors.New("invalide argument to Union, expected rotate.BloomFilter")
54 | }
55 | var unionOutput rpc_bf.UnionOutput
56 | if err := b.client.Call("BloomFilterRPC.Union", rpc_bf.UnionInput{v}, &unionOutput); err != nil {
57 | fmt.Println("error on union bloomfilter:", err.Error())
58 | return -1.0, err
59 | }
60 |
61 | return unionOutput.Capacity, nil
62 | }
63 |
64 | func (b *BloomFilter) Close() {
65 | b.client.Close()
66 | }
67 |
--------------------------------------------------------------------------------
/bf/rpc/client/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import "fmt"
4 |
5 | func main() {
6 | bloomFilter, err := New("127.0.0.1:9999")
7 | if err != nil {
8 | fmt.Println("err: " + err.Error())
9 | return
10 | }
11 | defer bloomFilter.Close()
12 |
13 | bloomFilter.Add([]byte("BEARER eyJhbGciOiJSUzI1NiIsImtpZCI6Im1lbG9keSJ9.eyJOaWNrTmFtZSI6ImthcmwiLCJSb2xlSWQiOiIyIiwiVVVJRCI6IjZmMTU4NWQ1LTYzNmUtMTFlYS1hZDk3LTI4N2ZjZjEzZjZmNSIsImV4cCI6MTU4OTM2OTA4MSwiaWF0IjoxNTg5MzYxODgxLCJpc3MiOiJNZWxvZHkifQ.Sr_yYN_UN-E4KakGvhqw43YgCMH6uwjrPHZ02cuFNE-PhH-ujLSsKWgQdt9VSGZ_D1YiSqIvjWv5hC-zKsM0CLt-EQ0JyKbEkErSRKs_GmiV1S-8eSbNTKSlWeE8UzfdsQoJwUwFPJb8VzPrQTXEqokMPE6GoAeU1y73jBhPJvAQHmcw3_kbjsoDQ252_UnBujlXc5XEqdSxjYKg85nN5z-Rl28k39KMLf4yhpUfSt2qa2ENkz8RliqlucAPKvOWw03zLC-KRfWQ8ekw1R4rAHWNsL3p0UOYlZCJfNReYBhFuB470-GdP-f54tGNzhh6zPqbA7qBrObdIIPVkCaarA"))
14 | }
15 |
--------------------------------------------------------------------------------
/bf/rpc/rpc.go:
--------------------------------------------------------------------------------
1 | package rpc_bf
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "melody/bf/rotate"
7 | )
8 |
9 | var (
10 | ErrNoBloomFilterInitialized = fmt.Errorf("BloomFilter not initialized")
11 | bf *rotate.BloomFilter
12 | )
13 |
14 | type Config struct {
15 | rotate.Config
16 | Port int
17 | }
18 |
19 | type BloomFilterRPC int
20 |
21 | type BloomFilter struct {
22 | BloomFilterRPC
23 | }
24 |
25 | func (b *BloomFilter) Get() *rotate.BloomFilter {
26 | return bf
27 | }
28 |
29 | func (b *BloomFilter) Close() {
30 | if bf != nil {
31 | bf.Close()
32 | }
33 | }
34 |
35 | // New 初始化了 df 这个变量,并且返回一个新的 bf
36 | // 如果想使用 bf 请使用 Get 方法
37 | func New(ctx context.Context, cfg Config) *BloomFilter {
38 | if bf != nil {
39 | bf.Close()
40 | }
41 |
42 | bf = rotate.New(ctx, cfg.Config)
43 |
44 | return new(BloomFilter)
45 | }
46 |
47 | // Add 函数的 input
48 | // 使用二维数组可以批量 Add
49 | type AddInput struct {
50 | Elems [][]byte
51 | }
52 |
53 | // Add 函数的 output
54 | type AddOutput struct {
55 | Count int
56 | }
57 |
58 | func (b *BloomFilterRPC) Add(in AddInput, out *AddOutput) error {
59 | fmt.Println("add:", in.Elems)
60 | defer func() { fmt.Println("added elements:", out.Count) }()
61 |
62 | if bf == nil {
63 | out.Count = 0
64 | return ErrNoBloomFilterInitialized
65 | }
66 |
67 | k := 0
68 | for _, elem := range in.Elems {
69 | bf.Add(elem)
70 | k++
71 | }
72 | out.Count = k
73 |
74 | return nil
75 | }
76 |
77 | // CheckInput 函数的 input
78 | type CheckInput struct {
79 | Elems [][]byte
80 | }
81 |
82 | // CheckOutput 函数的 output
83 | type CheckOutput struct {
84 | Checks []bool
85 | }
86 |
87 | func (b *BloomFilterRPC) Check(in CheckInput, out *CheckOutput) error {
88 | fmt.Println("check:", in.Elems)
89 | defer func() { fmt.Println("checked elements:", out.Checks) }()
90 |
91 | checkRes := make([]bool, len(in.Elems))
92 |
93 | if bf == nil {
94 | out.Checks = checkRes
95 | return ErrNoBloomFilterInitialized
96 | }
97 |
98 | for i, elem := range in.Elems {
99 | checkRes[i] = bf.Check(elem)
100 | }
101 | out.Checks = checkRes
102 |
103 | return nil
104 | }
105 |
106 | // union 函数的 input
107 | type UnionInput struct {
108 | BF *rotate.BloomFilter
109 | }
110 |
111 | // union 函数的 output
112 | type UnionOutput struct {
113 | Capacity float64
114 | }
115 |
116 | func (b *BloomFilterRPC) Union(in UnionInput, out *UnionOutput) error {
117 | fmt.Println("union:", in.BF)
118 | defer func() { fmt.Println("union resulting capacity:", out.Capacity) }()
119 |
120 | if bf == nil {
121 | out.Capacity = 0
122 | return ErrNoBloomFilterInitialized
123 | }
124 |
125 | var err error
126 | out.Capacity, err = bf.Union(in.BF)
127 |
128 | return err
129 | }
130 |
--------------------------------------------------------------------------------
/bf/rpc/server/server.go:
--------------------------------------------------------------------------------
1 | // Package server implements an rpc server for the bloomfilter, registering a bloomfilter and accepting a tcp listener.
2 | package server
3 |
4 | import (
5 | "context"
6 | "fmt"
7 | rpc_bf "melody/bf/rpc"
8 | "net"
9 | "net/rpc"
10 | )
11 |
12 | func New(ctx context.Context, cfg rpc_bf.Config) *rpc_bf.BloomFilter {
13 | bf := rpc_bf.New(ctx, cfg)
14 |
15 | go Serve(ctx, cfg.Port, bf)
16 |
17 | return bf
18 | }
19 |
20 | // Serve creates an rpc server, registers a bloomfilter, accepts a tcp listener and closes when catching context done
21 | func Serve(ctx context.Context, port int, bf *rpc_bf.BloomFilter) {
22 | s := rpc.NewServer()
23 |
24 | if err := s.Register(&bf.BloomFilterRPC); err != nil {
25 | fmt.Println("server register error:", err.Error())
26 | return
27 | }
28 |
29 | l, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
30 | if err != nil {
31 | fmt.Println("unable to setup RPC listener:", err.Error())
32 | return
33 | }
34 |
35 | go func() {
36 | for {
37 | select {
38 | case <-ctx.Done():
39 | l.Close()
40 | bf.Close()
41 | return
42 | }
43 | }
44 | }()
45 |
46 | s.Accept(l)
47 | }
48 |
--------------------------------------------------------------------------------
/bf/testutils/testutils.go:
--------------------------------------------------------------------------------
1 | // Package testutils contains utils for the tests.
2 | package testutils
3 |
4 | import (
5 | "melody/bf"
6 | "testing"
7 | )
8 |
9 | var (
10 | TestCfg = bf.Config{
11 | N: 100,
12 | P: 0.001,
13 | HashName: bf.HASHER_OPTIMAL,
14 | }
15 |
16 | TestCfg2 = bf.Config{
17 | N: 100,
18 | P: 0.00001,
19 | HashName: bf.HASHER_OPTIMAL,
20 | }
21 |
22 | TestCfg3 = bf.Config{
23 | N: 100,
24 | P: 0.001,
25 | HashName: bf.HASHER_DEFAULT,
26 | }
27 | )
28 |
29 | func CallSet(t *testing.T, set bf.BloomFilter) {
30 | set.Add([]byte{1, 2, 3})
31 | if !set.Check([]byte{1, 2, 3}) {
32 | t.Error("failed check")
33 | }
34 |
35 | if set.Check([]byte{1, 2, 4}) {
36 | t.Error("unexpected check")
37 | }
38 | }
39 |
40 | func CallSetUnion(t *testing.T, set1, set2 bf.BloomFilter) {
41 | elem := []byte{1, 2, 3}
42 | set1.Add(elem)
43 | if !set1.Check(elem) {
44 | t.Error("failed add set1 before union")
45 | return
46 | }
47 |
48 | if set2.Check(elem) {
49 | t.Error("unexpected check to union of set2")
50 | return
51 | }
52 |
53 | if _, err := set2.Union(set1); err != nil {
54 | t.Error("failed union set1 to set2", err.Error())
55 | return
56 | }
57 |
58 | if !set2.Check(elem) {
59 | t.Error("failed union check of set1 to set2")
60 | return
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/cmd/check.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "os"
5 |
6 | "github.com/spf13/cobra"
7 | )
8 |
9 | func checkFunc(cmd *cobra.Command, args []string) {
10 | //检查配置文件是否为空
11 | if cfgFilePath == "" {
12 | cmd.Println("Please, provide the path to your config file")
13 | return
14 | }
15 | cmd.Printf("Parsing configuration file: %s\n", cfgFilePath)
16 | v, err := parser.Parse(cfgFilePath)
17 |
18 | //如果开启了debug
19 | if debug {
20 | cmd.Printf("Parsed configuration: CacheTTL: %s, Port: %d\n", v.CacheTTL.String(), v.Port)
21 | cmd.Printf("Hosts: %v\n", v.Host)
22 |
23 | cmd.Printf("Extra (%d):\n", len(v.ExtraConfig))
24 | for k, e := range v.ExtraConfig {
25 | cmd.Printf(" %s: %v\n", k, e)
26 | }
27 |
28 | cmd.Printf("Endpoints (%d):\n", len(v.Endpoints))
29 | for _, endpoint := range v.Endpoints {
30 | cmd.Printf("\tEndpoint: %s, Method: %s, CacheTTL: %s, Concurrent: %d, QueryString: %v\n",
31 | endpoint.Endpoint, endpoint.Method, endpoint.CacheTTL.String(),
32 | endpoint.ConcurrentCalls, endpoint.QueryString)
33 |
34 | cmd.Printf("\tExtra (%d):\n", len(endpoint.ExtraConfig))
35 | for k, e := range endpoint.ExtraConfig {
36 | cmd.Printf("\t %s: %v\n", k, e)
37 | }
38 |
39 | cmd.Printf("\tBackends (%d):\n", len(endpoint.Backends))
40 | for _, backend := range endpoint.Backends {
41 | cmd.Printf("\t\tURL: %s, Method: %s\n", backend.URLPattern, backend.Method)
42 | cmd.Printf("\t\t\tTimeout: %s, Target: %s, Mapping: %v, BL: %v, WL: %v, Group: %v\n",
43 | backend.Timeout, backend.Target, backend.Mapping, backend.Blacklist, backend.Whitelist,
44 | backend.Group)
45 | cmd.Printf("\t\t\tHosts: %v\n", backend.Host)
46 | cmd.Printf("\t\t\tExtra (%d):\n", len(backend.ExtraConfig))
47 | for k, e := range backend.ExtraConfig {
48 | cmd.Printf("\t\t\t %s: %v\n", k, e)
49 | }
50 | }
51 | }
52 |
53 | }
54 | //如果错误
55 | if err != nil {
56 | cmd.Println("ERROR parsing the configuration file.\n", err.Error())
57 | os.Exit(1)
58 | return
59 | }
60 |
61 | cmd.Println("Syntax OK!")
62 | }
63 |
--------------------------------------------------------------------------------
/cmd/cmd.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "fmt"
5 | "melody/config"
6 | "os"
7 | )
8 |
9 | //Executor defines the func that contains some prepration handles of start server.
10 | type Executor func(config.ServiceConfig)
11 |
12 | //Execute for other method to call.
13 | func Execute(configParser config.Parser, executor Executor) {
14 | parser = configParser
15 | run = executor
16 | if err := rootCmd.Execute(); err != nil {
17 | fmt.Println(err)
18 | os.Exit(1)
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/cmd/graph.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "bytes"
5 | "github.com/spf13/cobra"
6 | "io"
7 | "melody/config"
8 | "os"
9 | "text/template"
10 | )
11 |
12 | func graphFunc(cmd *cobra.Command, args []string) {
13 | if cfgFilePath == "" {
14 | cmd.Println("Please provide the path to your melody config file ")
15 | return
16 | }
17 | serviceConfig, err := parser.Parse(cfgFilePath)
18 | if err != nil {
19 | cmd.Printf("ERROR parsing the melody config file: %s\n", err.Error())
20 | os.Exit(-1)
21 | }
22 | writeToDot(os.Stdout, serviceConfig, cmd)
23 | }
24 |
25 | func writeToDot(writer io.Writer, config config.ServiceConfig, cmd *cobra.Command) {
26 | t := template.New("dot")
27 | var buf bytes.Buffer
28 | if err := template.Must(t.Parse(tmplGraph)).Execute(&buf, config); err != nil {
29 | cmd.Println("ERROR convert data to dot error:", err)
30 | }
31 | buf.WriteTo(writer)
32 | }
33 |
34 | const tmplGraph = `digraph melody { {{ $port := .Port }}
35 | label="Melody Gateway";
36 | labeljust="l";
37 | fontname="Ubuntu";
38 | fontsize="13";
39 | rankdir="LR";
40 | bgcolor="aliceblue";
41 | style="solid";
42 | penwidth="0.5";
43 | pad="0.0";
44 | nodesep="0.35";
45 |
46 | node [shape="ellipse" style="filled" fillcolor="honeydew" fontname="Ubuntu" penwidth="1.0" margin="0.05,0.0"];
47 |
48 | {{ range $i, $endpoint := .Endpoints }}
49 | {{printf "subgraph \"cluster_%s\" {" .Endpoint }}
50 | label="{{ .Endpoint }}";
51 | bgcolor="lightgray";
52 | shape="box";
53 | style="solid";
54 |
55 | "{{ .Endpoint }}" [ shape=record, label="{ { Timeout | {{.Timeout.String}} } | { CacheTTL | {{.CacheTTL.String}} } | { Output | {{.OutputEncoding}} } | { QueryString | {{.QueryString}} } }" ]
56 | {{ if .ExtraConfig }}"extra_{{$i}}" [ shape=record, label="{ {ExtraConfig} {{ range $key, $value := .ExtraConfig }} | { {{ $key }} {{ range $k, $v := $value }}| { {{$k}} | {{$v}} } {{ end }} }{{ end }} }" ]{{ end }}
57 | {{ range $j, $backend := .Backends }}
58 | {{printf "subgraph \"cluster_%s\" {" .URLPattern }}
59 | label="{{ .URLPattern }}";
60 | bgcolor="beige";
61 | shape="box";
62 | style="solid";
63 | "in_{{$i}}_{{$j}}" [ shape=record, label="{ {sd|{{ if .SD }}{{ .SD }}{{ else }}static{{ end }} } | { Hosts | {{.Host}} } | { Encoding | {{ if .Encoding }}{{ .Encoding }}{{ else }}JSON{{ end }} } }" ]
64 | {{ if .ExtraConfig }}"extra_{{$i}}_{{$j}}" [ shape=record, label="{ { ExtraConfig {{ range $key, $v := .ExtraConfig }} | {{ $key }} {{ end }} } }" ]{{ end }}
65 | {{println "}" }}
66 | "{{ $endpoint.Endpoint }}" -> in_{{$i}}_{{$j}} [ label="x{{ .ConcurrentCalls }}"]{{ end }}
67 | {{ println "}" }}{{ end }}
68 | {{ range .Endpoints }}
69 | ":{{ $port }}" -> "{{ .Endpoint }}" [ label="{{ .Method }}"]{{ end }}
70 | }
71 | `
72 |
--------------------------------------------------------------------------------
/cmd/run.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | import (
4 | "github.com/spf13/cobra"
5 | "os"
6 | )
7 |
8 | func runFunc(cmd *cobra.Command, args []string) {
9 | if cfgFilePath == "" {
10 | cmd.Println("Please provide the path to your melody config file ")
11 | return
12 | }
13 | //Parse config file
14 | serviceConfig, err := parser.Parse(cfgFilePath)
15 | if err != nil {
16 | //Show config file parse error and exit
17 | cmd.Printf("ERROR parsing the melody config file: %s\n", err.Error())
18 | os.Exit(-1)
19 | }
20 | //Judge is debug
21 | serviceConfig.Debug = serviceConfig.Debug || debug
22 | //Run with service config
23 | run(serviceConfig)
24 | }
25 |
--------------------------------------------------------------------------------
/config/parser.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "io/ioutil"
7 | "os"
8 | )
9 |
10 | //Parser returns a ServiceConfig struct according to the providing configFile name.
11 | type Parser interface {
12 | Parse(configFile string) (ServiceConfig, error)
13 | }
14 |
15 | // NewParseError returns a new ParseError
16 | func NewParseError(err error, configFile string, offset int) *ParseError {
17 | b, _ := ioutil.ReadFile(configFile)
18 | row, col := getErrorRowCol(b, offset)
19 | return &ParseError{
20 | ConfigFile: configFile,
21 | Err: err,
22 | Offset: offset,
23 | Row: row,
24 | Col: col,
25 | }
26 | }
27 |
28 | //CheckErr returns error when parse config file
29 | func CheckErr(err error, configFile string) error {
30 | switch e := err.(type) {
31 | case *json.SyntaxError:
32 | return NewParseError(err, configFile, int(e.Offset))
33 | case *json.UnmarshalTypeError:
34 | return NewParseError(err, configFile, int(e.Offset))
35 | case *os.PathError:
36 | return fmt.Errorf(
37 | "'%s' (%s): %s",
38 | configFile,
39 | e.Op,
40 | e.Err.Error(),
41 | )
42 | default:
43 | return fmt.Errorf("'%s': %v", configFile, err)
44 | }
45 | }
46 |
47 | func getErrorRowCol(source []byte, offset int) (row, col int) {
48 | for i := 0; i < offset; i++ {
49 | v := source[i]
50 | if v == '\r' {
51 | continue
52 | }
53 | if v == '\n' {
54 | col = 0
55 | row++
56 | continue
57 | }
58 | col++
59 | }
60 | return
61 | }
62 |
63 | // ParseError is an error containing details regarding the row and column where
64 | // an parse error occurred
65 | type ParseError struct {
66 | ConfigFile string
67 | Offset int
68 | Row int
69 | Col int
70 | Err error
71 | }
72 |
73 | // Error returns the error message for the ParseError
74 | func (p *ParseError) Error() string {
75 | return fmt.Sprintf(
76 | "'%s': %v, offset: %v, row: %v, col: %v",
77 | p.ConfigFile,
78 | p.Err.Error(),
79 | p.Offset,
80 | p.Row,
81 | p.Col,
82 | )
83 | }
84 |
--------------------------------------------------------------------------------
/config/uri.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "errors"
5 | "regexp"
6 | "strings"
7 | )
8 |
9 | const (
10 | defaultHttp = `http://`
11 | )
12 |
13 | var (
14 | endpointURLKeysPattern = regexp.MustCompile(`/\{([a-zA-Z\-_0-9]+)\}`)
15 | hostPattern = regexp.MustCompile(`(https?://)?([a-zA-Z0-9\._\-]+)(:[0-9]{2,6})?/?`)
16 | errInvalidHost = errors.New("invalid host")
17 | )
18 |
19 | //URIParser defines all method that uri needed
20 | type URIParser interface {
21 | CleanHosts([]string) []string
22 | CleanHost(string) string
23 | CleanPath(string) string
24 | GetEndpointPath(string, []string) string
25 | }
26 |
27 | func NewURIParser() URIParser {
28 | return URI(RoutingPattern)
29 | }
30 |
31 | //URI to implement URIParser
32 | type URI int
33 |
34 | func (U URI) CleanHosts(hosts []string) []string {
35 | var cleans []string
36 | for i := range hosts {
37 | cleans = append(cleans, U.CleanHost(hosts[i]))
38 | }
39 |
40 | return cleans
41 | }
42 |
43 | func (U URI) CleanHost(host string) string {
44 | matches := hostPattern.FindAllStringSubmatch(host, -1)
45 | if len(matches) != 1 {
46 | panic(errInvalidHost)
47 | }
48 |
49 | keys := matches[0][1:]
50 | if keys[0] == "" {
51 | keys[0] = defaultHttp
52 | }
53 |
54 | return strings.Join(keys, "")
55 | }
56 |
57 | // 确保 path 前面有 /
58 | func (U URI) CleanPath(path string) string {
59 | return "/" + strings.TrimPrefix(path, "/")
60 | }
61 |
62 | func (U URI) GetEndpointPath(path string, params []string) string {
63 | endPoint := path
64 | if U == ColonRouterPatternBuilder {
65 | for p := range params {
66 | parts := strings.Split(endPoint, "?")
67 | parts[0] = strings.Replace(parts[0], "{"+params[p]+"}", ":"+params[p], -1)
68 | endPoint = strings.Join(parts, "?")
69 | }
70 | }
71 |
72 | return endPoint
73 | }
74 |
--------------------------------------------------------------------------------
/core/melody/backend_factory.go:
--------------------------------------------------------------------------------
1 | package melody
2 |
3 | import (
4 | "context"
5 | "melody/config"
6 | "melody/logging"
7 | circuitbreaker "melody/middleware/melody-circuitbreaker/proxy"
8 | martian "melody/middleware/melody-martian"
9 | metrics "melody/middleware/melody-metrics/gin"
10 | juju "melody/middleware/melody-ratelimit/juju/proxy"
11 | "melody/proxy"
12 | "melody/transport/http/client"
13 | )
14 |
15 | // NewBackendFactory 创建BackendFactory,实际去请求每一个backend
16 | func NewBackendFactoryWithContext(ctx context.Context, logger logging.Logger, metrics *metrics.Metrics) proxy.BackendFactory {
17 | clientFactory := client.NewHTTPClient
18 | httpRequestExecutor := client.DefaultHTTPRequestExecutor(clientFactory)
19 | backendFactory := func(backend *config.Backend) proxy.Proxy {
20 | return proxy.NewHTTPProxyWithHTTPRequestExecutor(backend, httpRequestExecutor, backend.Decoder)
21 | }
22 | backendFactory = martian.NewBackendFactory(logger, httpRequestExecutor)
23 | backendFactory = juju.BackendFactory(backendFactory)
24 | // 使用断路器
25 | backendFactory = circuitbreaker.BackendFactory(backendFactory, logger)
26 | backendFactory = metrics.NewBackendFactory("backend", backendFactory)
27 | return backendFactory
28 | }
29 |
--------------------------------------------------------------------------------
/core/melody/encoding.go:
--------------------------------------------------------------------------------
1 | package melody
2 |
3 | import (
4 | rss "melody/middleware/melody-rss"
5 | xml "melody/middleware/melody-xml"
6 | )
7 |
8 | // RegisterEncoders 注册额外的编码器
9 | func RegisterEncoders() {
10 | xml.Register()
11 | rss.Register()
12 | }
13 |
--------------------------------------------------------------------------------
/core/melody/handler_factory.go:
--------------------------------------------------------------------------------
1 | package melody
2 |
3 | import (
4 | "melody/logging"
5 | botmonitor "melody/middleware/melody-botmonitor/gin"
6 | jose "melody/middleware/melody-jose"
7 | ginjose "melody/middleware/melody-jose/gin"
8 | metrics "melody/middleware/melody-metrics/gin"
9 | juju "melody/middleware/melody-ratelimit/juju/router/gin"
10 | router "melody/router/gin"
11 | )
12 |
13 | // NewHandlerFactory 返回一个Handler工厂
14 | // 根据不同的EndpointConfig定制Handler
15 | // 这里的Handler旨在处理Endpoint层的逻辑
16 | func NewHandlerFactory(logger logging.Logger, rejecter jose.RejecterFactory, metrics *metrics.Metrics) router.HandlerFactory {
17 | handlerFactory := router.EndpointHandler
18 | handlerFactory = juju.NewRateLimiterMw(handlerFactory)
19 | handlerFactory = ginjose.HandlerFactory(handlerFactory, logger, rejecter)
20 | handlerFactory = botmonitor.New(handlerFactory, logger)
21 | handlerFactory = metrics.NewHTTPHandleFactory(handlerFactory)
22 | return handlerFactory
23 | }
24 |
--------------------------------------------------------------------------------
/core/melody/plugin.go:
--------------------------------------------------------------------------------
1 | package melody
2 |
3 | import (
4 | "melody/logging"
5 | )
6 |
7 | // LoadPlugins 加载并注册插件
8 | func LoadPlugins(folder, pattern string, logger logging.Logger) {
9 |
10 | // TODO load client plugin
11 |
12 | // load server plugin
13 | logger.Info("http handler plugins begin to load.")
14 | // n, err = server.Load(
15 | // folder,
16 | // pattern,
17 | // server.RegisterHandler,
18 | // )
19 | // if err != nil {
20 | // logger.Warning("loading plugins:", err)
21 | // }
22 | // logger.Info("total http handler plugins loaded:", n)
23 | }
24 |
--------------------------------------------------------------------------------
/core/melody/proxy_factory.go:
--------------------------------------------------------------------------------
1 | package melody
2 |
3 | import (
4 | "melody/logging"
5 | jsonschema "melody/middleware/melody-jsonschema"
6 | metrics "melody/middleware/melody-metrics/gin"
7 | "melody/proxy"
8 | )
9 |
10 | func NewProxyFactory(logger logging.Logger, backend proxy.BackendFactory, metrics *metrics.Metrics) proxy.Factory {
11 | // 完成了默认的ProxyFactory
12 | proxyFactory := proxy.NewDefaultFactory(backend, logger)
13 | proxyFactory = proxy.NewShadowFactory(proxyFactory)
14 | proxyFactory = jsonschema.ProxyFactory(proxyFactory)
15 | proxyFactory = metrics.NewProxyFactory("endpoint", proxyFactory)
16 | return proxyFactory
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/core/melody/router_engine.go:
--------------------------------------------------------------------------------
1 | package melody
2 |
3 | import (
4 | "io"
5 | "melody/config"
6 | "melody/logging"
7 |
8 | botmonitor "melody/middleware/melody-botmonitor/gin"
9 | cors "melody/middleware/melody-cors/gin"
10 | httpsecure "melody/middleware/melody-httpsecure/gin"
11 |
12 | "github.com/gin-gonic/gin"
13 | )
14 |
15 | // NewEngine 返回一个基于gin的默认Engine
16 | func NewEngine(cfg config.ServiceConfig, logger logging.Logger, gelf io.Writer) *gin.Engine {
17 | if !cfg.Debug {
18 | gin.SetMode(gin.ReleaseMode)
19 | }
20 | engine := gin.New()
21 | engine.Use(gin.LoggerWithConfig(gin.LoggerConfig{Output: gelf}), gin.Recovery())
22 |
23 | // 默认 重定向全部打开
24 | engine.RedirectTrailingSlash = true
25 | engine.RedirectFixedPath = true
26 | engine.HandleMethodNotAllowed = true
27 | // 注册跨域middleware
28 | if mw := cors.New(cfg.ExtraConfig); mw != nil {
29 | engine.Use(mw)
30 | }
31 | //http secure middleware
32 | if err := httpsecure.Register(cfg.ExtraConfig, engine); err != nil {
33 | logger.Warning(err)
34 | }
35 | //TODO lua register
36 | //botmonitor middleware
37 | botmonitor.Register(cfg, logger, engine)
38 | return engine
39 | }
40 |
--------------------------------------------------------------------------------
/core/melody/sd.go:
--------------------------------------------------------------------------------
1 | package melody
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "melody/config"
7 | "melody/logging"
8 | consul "melody/middleware/melody-consul"
9 | etcd "melody/middleware/melody-etcd"
10 | "melody/sd"
11 | "melody/sd/dnssrv"
12 | )
13 |
14 | // RegisterSubscriberFactories registers all the available sd adaptors
15 | // sd = service discovery
16 | func RegisterSubscriberFactories(ctx context.Context, cfg config.ServiceConfig, logger logging.Logger) func(n string, p int) {
17 | // setup the etcdReg if necessary
18 | // etcd Raft
19 | etcdReg, err := etcd.New(ctx, cfg.ExtraConfig)
20 |
21 | if err != nil {
22 | logger.Warning("building the etcd client:", err.Error())
23 | }
24 |
25 | register := sd.GetRegister()
26 | register.Register("etcd", etcd.SubscriberFactory(ctx, etcdReg))
27 | // register.Get("etcd")(backend) 会得到此backend中以第一个host为prefix的key的所有value的一个slice
28 | // etcdReg.GetEntries(host[0])
29 |
30 | // register the dns service discovery
31 | // 同上 register.Get("dns")(backend)
32 | register.Register("dns", dnssrv.SubscriberFactory)
33 |
34 | return func(name string, port int) {
35 | if err := consul.Register(ctx, cfg.ExtraConfig, port, name, logger); err != nil {
36 | logger.Error(fmt.Sprintf("Couldn't register %s:%d in consul: %s", name, port, err.Error()))
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/core/version.go:
--------------------------------------------------------------------------------
1 | package core
2 |
3 | import "fmt"
4 |
5 | const (
6 | // MelodyVersion export the project version.
7 | MelodyVersion = "1.0.0"
8 | // MelodyHeaderKey
9 | MelodyHeaderKey = "X-Melody"
10 | //
11 | )
12 |
13 | var (
14 | // MelodyUserAgent setted to backend
15 | MelodyUserAgent = fmt.Sprintf("Melody Version %s", MelodyVersion)
16 | MelodyHeaderValue = fmt.Sprintf("Version %s", MelodyVersion)
17 | )
18 |
--------------------------------------------------------------------------------
/coverage.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # Generate test coverage statistics for Go packages.
3 | #
4 | # Works around the fact that `go test -coverprofile` currently does not work
5 | # with multiple packages, see https://code.google.com/p/go/issues/detail?id=6909
6 | #
7 | # Usage: script/coverage [--html|--coveralls]
8 | #
9 | # --html Additionally create HTML report and open it in browser
10 | # --coveralls Push coverage statistics to coveralls.io
11 | #
12 | # File taken from https://github.com/mlafeldt/chef-runner/blob/v0.7.0/script/coverage
13 |
14 | set -e
15 |
16 | workdir=.cover
17 | profile="$workdir/cover.out"
18 | mode=count
19 |
20 | generate_cover_data() {
21 | rm -rf "$workdir"
22 | mkdir "$workdir"
23 |
24 | for pkg in "$@"; do
25 | f="$workdir/$(echo $pkg | tr / -).cover"
26 | go test -covermode="$mode" -coverprofile="$f" "$pkg"
27 | done
28 |
29 | echo "mode: $mode" >"$profile"
30 | grep -h -v "^mode:" "$workdir"/*.cover >>"$profile"
31 | }
32 |
33 | show_cover_report() {
34 | go tool cover -${1}="$profile"
35 | }
36 |
37 | push_to_coveralls() {
38 | echo "Pushing coverage statistics to coveralls.io"
39 | $GOPATH/bin/goveralls -coverprofile="$profile" -repotoken="o4RsPgcCBCApE0hpTyIK5T5SGqM625l5H"
40 | }
41 |
42 | generate_cover_data $(go list ./... | grep -v /examples/)
43 | show_cover_report func
44 | case "$1" in
45 | "")
46 | ;;
47 | --html)
48 | show_cover_report html ;;
49 | --coveralls)
50 | push_to_coveralls ;;
51 | *)
52 | echo >&2 "error: invalid option: $1"; exit 1 ;;
53 | esac
--------------------------------------------------------------------------------
/docs/DNS SRV.md:
--------------------------------------------------------------------------------
1 | # DNS #
2 | 定义:域名系统(服务)协议(DNS)是一种分布式网络目录服务,主要用于域名与 IP 地址的相互转换,以及控制因特网的电子邮件的发送。
3 |
4 | ps:dns系统是一个最终一致性分布式系统,即添加的记录并不会及时生效于每一台服务器,但最终肯定会让每台服务器一致。
5 |
6 | # SRV(service record) #
7 | DNS SRV 是 DNS 记录中的一种,用来查询指定服务的地址。SRV中除了记录服务器的地址,还记录了服务的端口,并且可以设置每个服务地址的优先级和权重。
8 |
9 | SRV 的 DNS 类型代码为33。
10 | SRV 的记录格式为:
11 | ```
12 | _Service._Proto.Name TTL Class SRV Priority Weight Port Target
13 |
14 | Service: 服务名称,前缀“_”是为防止与DNS Label(普通域名)冲突
15 | Proto: 服务使用的通信协议,TCP、UDP、其它标准协议或者自定义的协议
16 | Name: 提供服务的域名
17 | TTL: 缓存有效时间
18 | Class: 类别
19 | Priority: 该记录的优先级,数值越小表示优先级越高,范围0-65535
20 | Weight: 该记录的权重,数值越高权重越高,范围0-65535
21 | Port: 服务端口号,0-65535
22 | Target: host 地址
23 | ```
24 | 客户端查询到多条记录的时候,使用优先级最高的记录。对相同优先级的记录,按照权重选择,记录的权重越高,被选择的可能性越高。
25 |
26 | 选择的时候,将所有记录的权重值累加,得到一个选择区间\[0,sum\],每个记录在\[0,sum\]中占据一段连续的、长度为自身权重值区间。然后生成一个\[0,sum\]中的随机数,随机数落在的区间所属的记录就是被选择的记录。
--------------------------------------------------------------------------------
/docs/DNS.md:
--------------------------------------------------------------------------------
1 | # DNS
2 | 参考 https://draveness.me/whys-the-design-dns-udp-tcp
3 | ## 概述
4 | 在绝大多数情况下,DNS 都是使用 UDP 协议进行通信的,DNS 协议在设计之初也推荐我们在进行域名解析时首先使用 UDP,这确实能解决很多需求,但是不能解决全部的问题。
5 |
6 | 实际上,DNS 不仅使用了 UDP 协议,也使用了 TCP 协议
7 |
8 | 对 DNS 协议进行简单的介绍:DNS 查询的类型不止包含 A 记录、CNAME 记录等常见查询,还包含 AXFR 类型的特殊查询,这种特殊查询主要用于 DNS 区域传输,它的作用就是在多个命名服务器之间快速迁移记录,由于查询返回的响应比较大,所以会使用 TCP 协议来传输数据包。
9 |
10 | 区域传输:DNS 主从复制,就是将主 DNS 服务器的解析库复制传送至从 DNS 服务器,进而从服务器就可以进行正向、反向解析了。从服务器向主服务器查询更新数据,保证数据一致性,此为区域传送。也可以说,DNS 区域传输,就是 DNS 主从复制的实现方法,DNS 主从复制是 DNS 区域传输的表现形式。
11 |
12 | RFC 文档中与 UDP/TCP 协议相关内容的总结:
13 |
14 | DNS 在被设计之初就可以同时使用 TCP 和 UDP 协议,对于绝大多数的 DNS 查询来说都会使用 UDP 数据报进行传输,TCP 协议只会在区域传输的场景中使用,其中 UDP 数据包只会传输最大 512 字节的数据,多余的会被截断;
15 |
16 | RFC1123 预测了 DNS 记录中存储的数据会越来越多,同时也第一次显式的指出了发现 UDP 包被截断时应该通过 TCP 协议重试。
17 |
18 | 由于互联网的发展,人们发现 IPv4 已经不够分配了,所以引入了更长的 IPv6,DNS 也进行了协议上的支持,随后又增加了在鉴权和安全方面的支持,但是也带来了巨大的 DNS 记录,UDP 数据包被截断变得非常常见。
19 |
20 | RFC7766 才终于提出了使用 TCP 协议作为主要协议来解决 UDP 无法解决的问题,TCP 协议也不再只是一种重试时使用的机制,随后出现的 DNS over TLS 和 DNS over HTTP 也都是对 DNS 协议的一种补充。
21 |
22 | ## 为什么小数据包用 UDP?
23 |
24 | 以前的 DNS 查询的数据包较小、机制简单;
25 |
26 | UDP 协议的额外开销小、有着更好的性能表现,而 TCP 需要三次握手,相对于当时的小数据包来说是很大的开销;
27 |
28 | ## 为什么大数据包用 TCP?
29 |
30 | DNS 查询由于 DNSSEC 和 IPv6 的引入迅速膨胀,导致 DNS 响应经常超过 MTU(传送链路的最大传输单元,也就是单个数据包大小的上限,一般为 1500 字节) 造成数据的分片和丢失,我们需要依靠更加可靠的 TCP 协议(通过序列号、重传等机制能够保证消息的不重不漏,消息接受方的 TCP 栈会对分片的数据重新进行拼装,DNS 等应用层协议可以直接使用处理好的完整数据)完成数据的传输;
31 |
32 | 随着 DNS 查询中包含的数据不断增加,TCP 协议头以及三次握手带来的额外开销比例逐渐降低,不再是占据总传输数据大小的主要部分;
33 |
--------------------------------------------------------------------------------
/docs/ETCD使用介绍.md:
--------------------------------------------------------------------------------
1 | # ETCD #
2 | [中文文档](https://github.com/doczhcn/etcd/blob/master/documentation/index.md) - 翻译自ETCD官方文档
3 | ## 简介 ##
4 | etcd 是一个**分布式键值对存储**,设计用来可靠而快速的保存关键数据并提供访问。通过分布式锁,leader选举和写屏障(write barriers)来实现可靠的分布式协作。etcd集群是为高可用,**持久性数据存储和检索**而准备。
5 |
6 | 注意到 etcd 本质上其实是个分布式的数据库,只不过它可以保证每一台机器上的数据是一致的!
7 |
8 | 例如启动 mysql server 一样,etcd 也需要开启 etcd server。
9 |
10 | etcd client 用来对 etcd server 进行数据的 CRUD。
--------------------------------------------------------------------------------
/docs/HTTP公钥固定.md:
--------------------------------------------------------------------------------
1 | # HTTP公钥固定(Public Key Pinning)
2 |
3 | ## 什么是HPKP?
4 |
5 | HTTP公钥固定(HPKP)是一种安全功能,它告诉Web客户端将特定加密公钥与某个Web服务器相关联,以降低使用伪造证书进行“MITM攻击(中间人攻击)”的风险。
6 |
7 | 为了确保TLS会话中使用的服务器公钥的真实性,此公钥将包装到X.509证书中,该证书通常由证书颁发机构(CA)签名。诸如浏览器之类的Web客户端信任许多这些CA,它们都可以为任意域名创建证书。如果攻击者能够破坏单个CA,则他们可以对各种TLS连接执行MITM攻击。 HPKP可以通过告知客户端哪个公钥属于某个Web服务器来规避HTTPS协议的威胁。
8 |
9 | HPKP是首次使用信任(TOFU)技术。 Web服务器第一次通过特殊的HTTP头告诉客户端哪些公钥属于它,客户端将该信息存储在给定的时间段内。当客户端再次访问服务器时,它希望证书链中至少有一个证书包含一个公钥,其指纹已通过HPKP获知。如果服务器提供未知的公钥,则客户端应向用户发出警告。
10 |
11 | ## 如何启用HPKP?
12 |
13 | 要为您的站点启用此功能,您需要在通过HTTPS访问站点时返回Public-Key-Pins HTTP标头:
14 | ```shell script
15 | Public-Key-Pins: pin-sha256="base64=="; max-age=expireTime [; includeSubDomains][; report-uri="reportURI"]
16 | ```
17 |
--------------------------------------------------------------------------------
/docs/JWT.md:
--------------------------------------------------------------------------------
1 | # JWT(JSON Web Token) #
2 | 传统的 session、cookie 验证用户登录状态不便于分布式集群系统。(服务器有状态)
3 |
4 | JWT 可以轻松的在分布式集群系统中解决验证登录态的问题。(服务器无状态)
5 |
6 | JWT 是在服务器身份验证之后,将生成一个JSON对象并将其发送回用户。
7 |
8 | 之后,当用户与服务器通信时,客户在请求中发回JSON对象。
9 | 服务器仅依赖于这个JSON对象来标识用户。为了防止用户篡改数据,服务器将在生成对象时添加签名。
10 |
11 | 服务器不保存任何会话数据,即服务器变为无状态,使其更容易扩展。
12 |
13 | 一个 JWT 实际上就是一个字符串,它由三部分组成,**头部**、**有效载荷**与**签名**。
14 |
15 | ## 头部(Header) ##
16 | 头部用于描述关于该JWT的最基本的信息,例如其类型以及签名所用的算法等。这也可以被表示成一个JSON对象。
17 | ```json
18 | {
19 | "typ": "JWT",
20 | "alg": "HS256"
21 | }
22 | ```
23 | 在上面的代码中,alg属性表示签名使用的算法,默认为HMAC SHA256(写为HS256);
24 | typ属性表示令牌的类型,JWT令牌统一写为JWT。
25 | 最后,使用Base64 URL算法将上述JSON对象转换为字符串保存。
26 |
27 | ## 有效载荷(Payload) ##
28 | 有效载荷部分,是JWT的主体内容部分,也是一个JSON对象,包含需要传递的数据。这些有效信息包含三个部分:
29 |
30 | (1) 七个默认字段供选择。(建议但不强制使用)
31 |
32 | ```
33 | iss: jwt签发者
34 | sub: jwt所面向的用户
35 | aud: 接收jwt的一方
36 | exp: jwt的过期时间,这个过期时间必须要大于签发时间
37 | nbf: 定义在什么时间之前,该jwt都是不可用的.
38 | iat: jwt的签发时间
39 | jti: jwt的唯一身份标识,主要用来作为一次性token。
40 | ```
41 | (2) 公共的声明
42 |
43 | 公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.
44 | 但不建议添加敏感信息,因为该部分在客户端可解密.
45 |
46 | (3) 私有的声明
47 |
48 | 私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息。
49 |
50 | 有效载荷的JSON对象也使用Base64 URL算法转换为字符串保存。
51 |
52 | ## 签名(Signature) ##
53 | JWT 的第三部分是一个签证信息,这个签证信息由三部分组成:
54 | ```
55 | Header (base64URL后的)
56 | Payload (base64URL后的)
57 | Secret
58 | ```
59 | 这个部分需要Header和Payload使用`.`连接组成的字符串,然后通过Header中声明的加密方式进行加盐Secret组合加密,然后就构成了JWT的第三部分。
60 |
61 | **注意**:Secret是保存在服务器端的,JWT的签发生成也是在服务器端的,Secret就是用来进行JWT的签发和JWT的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个Secret, 那就意味着客户端是可以自我签发JWT了。
62 |
--------------------------------------------------------------------------------
/docs/MIME嗅探防御.md:
--------------------------------------------------------------------------------
1 | # MIME sniffing(MIME 嗅探)
2 |
3 | 产生这个问题的主要原因是,http报文中的content-type头在早期版本的浏览器中(例如IE7/6),浏览器并不光会凭靠content-type头进行不同类型的解析,还会`对response内容进行自我解析`,例如text/plain将文件内容直接显示,jpeg格式进行jpeg的渲染,例如传输的content-type头格式为 text/plain ,将文件内容直接显示,response中内容为
4 | ```
5 |
6 | ```
7 | IE浏览器会将其自动嗅探,并认为是text/html类型,并执行相应的渲染逻辑,这就很容易引发XSS攻击。 攻击者可以将图片文件内容写入xss攻击语句,并上传到共享的网站上,当用户请求该文件时,老旧浏览器收到response进行违背content-type的html解析,从而触发MIME嗅探攻击,我所知的解决方法为资源服务器使用与主网站不同的域名,在同源政策下,阻止跨域请求并解析的行为,从而达到阻止这一类嗅探攻击的目的。
--------------------------------------------------------------------------------
/docs/Melody Server.xmind:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flipped-aurora/melody/13a529e1e41c1b5eb567bde2196fe199ae958584/docs/Melody Server.xmind
--------------------------------------------------------------------------------
/docs/VuePress入门使用.md:
--------------------------------------------------------------------------------
1 | # VuePress 搭建文档手册
2 |
3 | ## 前提
4 |
5 | - node 环境
6 | - npm
7 |
8 | ## 安装
9 |
10 | ```
11 | npm install -g vuepress
12 | ```
13 |
14 | ## 开始
15 |
16 | 1. 新建个文件夹
17 | 2. 初始化,生成pakcage.json
18 | ```
19 | npm init -y
20 | ```
21 | 3. vscode打开项目
22 | 4. 新建`docs`目录
23 | 5. `docs下`新建`.vuepress`目录
24 | 6. `.vuepress`下新建`config.json`
25 | ```config.json
26 | module.exports = {
27 | title: "Melody Docs",
28 | description: "Documents of melody"
29 | }
30 | ```
31 | 7. `docs`下新建`README.md`
32 | 8. 尝试启动
33 | ```
34 | sudo vuepress dev docs
35 | ```
36 |
--------------------------------------------------------------------------------
/docs/img/example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flipped-aurora/melody/13a529e1e41c1b5eb567bde2196fe199ae958584/docs/img/example.png
--------------------------------------------------------------------------------
/docs/img/melody.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flipped-aurora/melody/13a529e1e41c1b5eb567bde2196fe199ae958584/docs/img/melody.png
--------------------------------------------------------------------------------
/docs/img/min-melody-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flipped-aurora/melody/13a529e1e41c1b5eb567bde2196fe199ae958584/docs/img/min-melody-logo.png
--------------------------------------------------------------------------------
/docs/img/p-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flipped-aurora/melody/13a529e1e41c1b5eb567bde2196fe199ae958584/docs/img/p-1.png
--------------------------------------------------------------------------------
/docs/img/p-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flipped-aurora/melody/13a529e1e41c1b5eb567bde2196fe199ae958584/docs/img/p-2.png
--------------------------------------------------------------------------------
/docs/img/p-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flipped-aurora/melody/13a529e1e41c1b5eb567bde2196fe199ae958584/docs/img/p-3.png
--------------------------------------------------------------------------------
/docs/img/struct.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/flipped-aurora/melody/13a529e1e41c1b5eb567bde2196fe199ae958584/docs/img/struct.png
--------------------------------------------------------------------------------
/docs/questions.md:
--------------------------------------------------------------------------------
1 | 1. etcd在网关中是具体用来做什么的?为什么只取backends\[0\]作为machine?
2 | 2. dns srv记录具体是用来做什么的?本地如何添加srv记录?
3 | 3. consul是来监听bloomfilter rpc server 的吗?
4 | 4. rotate bloomfilter 比 普通的 bloomfilter好在哪里?为什么这么设计?有什么是rotate可以完成,而普通的不能完成的?
5 | 5. rpc input 为什么要用 [][]byte 而不是 []byte?
--------------------------------------------------------------------------------
/docs/令牌桶算法.md:
--------------------------------------------------------------------------------
1 | ## Token_bucket 令牌桶算法简介
2 |
3 | ### 一、情景和问题
4 |
5 | 某天我们突然发现自己的接口请求量突然涨到之前的10倍,没多久该接口几乎不可使用,并引发连锁反应导致整个系统崩溃。如何应对这种情况呢?生活给了我们答案:比如老式电闸都安装了保险丝,一旦有人使用超大功率的设备,保险丝就会烧断以保护各个电器不被强电流给烧坏。同理我们的接口也需要安装上“保险丝”,以防止非预期的请求对系统压力过大而引起的系统瘫痪,当流量过大时,可以采取拒绝或者引流等机制。
6 |
7 |
8 |
9 | ### 二、令牌桶算法
10 |
11 | [令牌桶算法](https://baike.baidu.com/item/%E4%BB%A4%E7%89%8C%E6%A1%B6%E7%AE%97%E6%B3%95)是[网络流量](https://baike.baidu.com/item/%E7%BD%91%E7%BB%9C%E6%B5%81%E9%87%8F)整形(Traffic Shaping)和速率限制(Rate Limiting)中最常使用的一种算法。典型情况下,[令牌桶算法](https://baike.baidu.com/item/%E4%BB%A4%E7%89%8C%E6%A1%B6%E7%AE%97%E6%B3%95)用来控制发送到网络上的数据的数目,并允许突发数据的发送。
12 |
13 | 令牌桶这种控制机制基于令牌桶中是否存在令牌来指示什么时候可以发送流量。令牌桶中的每一个令牌都代表一个字节(对于流量整形来说代表一个bit,就traffic policing来讲代表一个byte。参见CCIE Routing and Switching Official Exam Certification Guide 2nd Edition)。如果令牌桶中存在令牌,则允许发送流量;而如果令牌桶中不存在令牌,则不允许发送流量。因此,如果突发门限被合理地配置并且令牌桶中有足够的令牌,那么流量就可以以峰值速率发送。
14 |
15 | 如图所示:随着时间流逝,系统会按恒定1/QPS时间间隔(如果QPS=100,则间隔是10ms)往桶里加入Token,如果桶已经满了就不再加了.新请求来临时,会各自拿走一个Token,如果没有Token可拿了就阻塞或者拒绝服务.
16 |
17 | 
18 |
19 | 令牌桶的另外一个好处是可以方便的改变速度. 一旦需要提高速率,则按需提高放入桶中的令牌的速率. 一般会定时(比如100毫秒)往桶中增加一定数量的令牌, 有些变种算法则实时的计算应该增加的令牌的数量.
20 |
21 |
22 |
23 | ### 三、算法文字描述
24 |
25 | 假如用户配置的平均发送速率为r,则每隔1/r秒一个令牌被加入到桶中;
26 |
27 | 假设桶最多可以存发b个令牌。如果令牌到达时令牌桶已经满了,那么这个令牌会被丢弃;
28 |
29 | 当一个n个字节的[数据包](https://baike.baidu.com/item/%E6%95%B0%E6%8D%AE%E5%8C%85)到达时,就从令牌桶中删除n个令牌,并且数据包被发送到网络;
30 |
31 | 如果令牌桶中少于n个令牌,那么不会删除令牌,并且认为这个数据包在[流量限制](https://baike.baidu.com/item/%E6%B5%81%E9%87%8F%E9%99%90%E5%88%B6)之外;
32 |
33 | 算法允许最长b个字节的突发,但从长期运行结果看,[数据包](https://baike.baidu.com/item/%E6%95%B0%E6%8D%AE%E5%8C%85)的速率被限制成常量r。对于在[流量限制](https://baike.baidu.com/item/%E6%B5%81%E9%87%8F%E9%99%90%E5%88%B6)外的[数据包](https://baike.baidu.com/item/%E6%95%B0%E6%8D%AE%E5%8C%85)可以以不同的方式处理:
34 |
35 | 它们可以被丢弃;
36 |
37 | 它们可以排放在[队列](https://baike.baidu.com/item/%E9%98%9F%E5%88%97)中以便当令牌桶中累积了足够多的令牌时再传输;
38 |
39 | 它们可以继续发送,但需要做特殊标记,网络过载的时候将这些特殊标记的包丢弃。
40 |
41 | 注意:[令牌桶算法](https://baike.baidu.com/item/%E4%BB%A4%E7%89%8C%E6%A1%B6%E7%AE%97%E6%B3%95)不能与另外一种常见算法“[漏桶算法](https://baike.baidu.com/item/%E6%BC%8F%E6%A1%B6%E7%AE%97%E6%B3%95)(Leaky Bucket)”相混淆。这两种算法的主要区别在于“[漏桶算法](https://baike.baidu.com/item/%E6%BC%8F%E6%A1%B6%E7%AE%97%E6%B3%95)”能够强行限制数据的传输速率,而“[令牌桶算法](https://baike.baidu.com/item/%E4%BB%A4%E7%89%8C%E6%A1%B6%E7%AE%97%E6%B3%95)”在能够限制数据的平均传输数据外,还允许某种程度的突发传输。在“[令牌桶算法](https://baike.baidu.com/item/%E4%BB%A4%E7%89%8C%E6%A1%B6%E7%AE%97%E6%B3%95)”中,只要令牌桶中存在令牌,那么就允许突发地传输数据直到达到用户配置的门限,因此它适合于具有突发特性的流量。
--------------------------------------------------------------------------------
/docs/关于HTTP严格传输安全.md:
--------------------------------------------------------------------------------
1 | # HTTP Strict Transport Security (HSTS) - HTTP严格传输安全
2 |
3 | ## 1. 由来:启用HTTPS也不能保证足够安全
4 |
5 | 很多网站对外只开放HTTPS,但用户再访问某个网站的时候,在浏览器中却往往只输入网站域名,例如(example.com), 而不是(https://example.com),不过浏览器依然能正确的使用HTTPS发起请求,这个是由于浏览器在背后的协作。
6 |
7 | 简单说就是浏览器向server发起了一次HTTP请求,在得到一个重定向响应后,发起一次HTTPS请求并得到最终的响应内容,这看起来是个不错的用户体验。
8 |
9 | 但在建立起HTTPS连接之前存在一次明文的HTTP请求和重定向,使得攻击者可以以中间人的方式劫持这次请求,从而进行后续的攻击,例如例如窃听、篡改、跳转钓鱼网站等。
10 |
11 | #### 劫持请求并跳转钓鱼网站为例
12 |
13 | 1. 浏览器发起一次明文HTTP请求,但实际会被攻击者拦截下来
14 | 2. 攻击者作为代理,把当前请求转发到钓鱼王占
15 | 3. 钓鱼网站返回假冒的网页内容
16 | 4. 攻击者吧假冒的网页内容返回给浏览器
17 |
18 | 是整个过程中,攻击者直接劫持了HTTP请求,并返回内容给浏览器,根本不给浏览器建立HTTPS连接的机会,因此浏览器会误以为真是网站通过HTTP对外提供服务,自然就不会向用户报告当前的连接不安全,于是攻击者可以对请求和响应进行窃听和篡改。
19 |
20 | ## 2. 解决方法:使用HSTS
21 |
22 | 劫持出现在HTTPS连接前的HTTP连接,那么解决这个问题的思路自然就变成了如何避免出现这样的HTTP请求,我们期望的浏览器行为是:用户输入域名(example.com),浏览器直接将其转换为HTTPS请求(https://example.com),直接略过上述的HTTP请求和重定向,从而使得中间人攻击失效
23 |
24 | #### 期望行为
25 |
26 | 1. 用户在浏览器输入域名,浏览器得知该域名应该使用HTTPS进行通信
27 | 2. 浏览器直接向网站发起HTTPS请求
28 | 3. 网站返回相应内容
29 |
30 | 但是浏览器如何知道该域名应该是用HTTPS通信呢?
31 |
32 | ### 2.1 HSTS
33 |
34 | HSTS全称是HTTP Strict Transport Security, 他是一个Web安全策略机制(web security policy mechanism)
35 |
36 | HSTS最为核心的是一个HTTP响应头(HTTP Response Header)。通过这响应头让浏览器得知,在接下来的一段时间内,当前域名只能通过HTTPS进行访问,并且在浏览器发现当前连接不安全的情况下,强行拒绝用户的后续访问要求
37 |
38 | HSTS Header的语法:
39 |
40 | ```
41 | Strict-Transport-Security: [; includeSubDomains][; preload]
42 | ```
43 |
44 | 其中
45 |
46 | - max-age 必填参数,秒为单位的数值,代表着HSTS Header的过期时间,通常设置为1年,即31536000秒
47 | - includeSubDomains 可选参数,如果包含,则意味着当前域名机器子域名均开启HSTS保护
48 | - preload 可选参数,只有当你申请将自己的域名加入到浏览器内置列表的时候才需要使用到它
49 |
50 | #### 让浏览器直接发送HTTPS请求
51 |
52 | 只需要在响应头中加入
53 | ```
54 | Strict-Transport-Security: max-age=31536000; includeSubDomains
55 | ```
56 |
57 | 就可以告诉浏览器该域名或子域名在接下来的一年内,对其应强制性使用HTTPS
58 |
59 |
60 | 那么如果有效期过了怎么办? 其实HSTS Header存在于每个响应之中,随着用户和网站的交互,这个有效时间时刻都在刷新,再加上有效期通常被设置为1年,所以只要两次请求间隔小于一年,基本上不会出现风险。
61 |
62 | ## 3. 攻击者依然有机可乘
63 |
64 | 但是还有个问题,HSTS Header存在于响应头中,要拿到响应头还是得发送一次HTTP请求,在第一次访问网站的时候,这时候中间人依然可以将这个HTTP请求劫持下来,继续中间人攻击。
65 |
66 | ## 4. 防御到底
67 |
68 | 针对上面的情况,HSTS也作出了应对, 在浏览器中内置一个列表,只要是在这个列表里的域名,无论何时、何种情况,浏览器都只使用HTTPS发起连接,这个列表由Google Chromium维护,主流的浏览器都在使用
--------------------------------------------------------------------------------
/docs/点劫持保护.md:
--------------------------------------------------------------------------------
1 | # 点劫持保护
2 |
3 | 点击劫持中间件和装饰器提供了简捷易用的,对点击劫持的保护。这种攻击在恶意站点诱导用户点击另一个站点的被覆盖元素时出现,另一个站点已经加载到了隐藏的frame或iframe中。
4 |
5 | ## 示例
6 |
7 | 假设一个在线商店拥有一个页面,已登录的用户可以点击“现在购买”来购买一个商品。用户为了方便,可以选择一直保持商店的登录状态。一个攻击者的站点可能在他们自己的页面上会创建一个“我喜欢Ponies”的按钮,并且在一个透明的iframe中加载商店的页面,把“现在购买”的按钮隐藏起来覆盖在“我喜欢Ponies”上。如果用户访问了攻击者的站点,点击“我喜欢Ponies”按钮会触发对“现在购买”按钮的无意识的点击,不知不觉中购买了商品。
8 |
9 | ## 防御
10 |
11 | 现代浏览器遵循X-Frame-Options协议头,它表明一个资源是否允许加载到frame或者iframe中。如果响应包含值为SAMEORIGIN的协议头,浏览器会在frame中只加载同源请求的的资源。如果协议头设置为DENY,浏览器会在加载frame时屏蔽所有资源,无论请求来自于哪个站点。
12 |
13 | ## 支持的浏览器
14 |
15 | - Internet Explorer 8+
16 | - Firefox 3.6.9+
17 | - Opera 10.5+
18 | - Safari 4+
19 | - Chrome 4.1+
--------------------------------------------------------------------------------
/docs/相关博客链接.md:
--------------------------------------------------------------------------------
1 |
2 | **英文文献:**
3 |
4 | [How-to-Choose-The-Right-API-Gateway-For-Your-Platform](https://www.moesif.com/blog/technical/api-gateways/How-to-Choose-The-Right-API-Gateway-For-Your-Platform-Comparison-Of-Kong-Tyk-Apigee-And-Alternatives/)
5 |
6 | **中文文献:**
7 |
8 | [谈谈微服务中的API网关](https://www.cnblogs.com/savorboard/p/api-gateway.html)
9 |
10 | [API网关在API安全性中的作用](https://www.jianshu.com/p/0cc79fe3e617)
11 |
12 | [基于HTTP反向代理的Web安全网关技术研究](http://kns.cnki.net/KCMS/detail/detail.aspx?filename=1016290366.nh&dbname=CMFD201701&dbcode=cdmd&uid=&v=MDkxODRHTEd4SHRMS3FaRWJQSVIrZm5zNHlSWWFtejExUEhia3FXQTBGckNVUjdxZlplUnRGeURuV3I3T1ZGMjY=) - 来源知网:其中在该文献页面中可以找到相关的英文或者中文资料(相似文献或参考文献)
13 |
14 | [微服务:监控体系,容器监控](https://blog.csdn.net/fly910905/article/details/100059983?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task)
15 |
16 | [基于Raft一致性协议的高可用性实现](https://kns.cnki.net/kcms/detail/detail.aspx?filename=HDSZ201505016&dbcode=CJFQ&dbname=CJFD2015&v=)
17 |
18 | [API 网关知识看这篇就足够了!](https://blog.csdn.net/qq_34337272/article/details/102962588)
19 |
20 | [京东10亿级调用量背后的高可用网关系统架构实践!](http://www.yunweipai.com/archives/23653.html)
21 |
22 |
23 | **其他网站:**
24 |
25 | [搜论文、期刊](http://www.wanfangdata.com.cn/index.html) - 帐号密码均为 hhgxy2020
26 |
27 | [krakend](https://www.krakend.io/)
28 |
29 | [kong](https://konghq.com/)
30 |
31 | **使用文档:**
32 |
33 | [InfluxDB 中文文档](https://jasper-zhang1.gitbooks.io/influxdb/)
34 |
35 | [InfluxDB 学习、语法](https://www.linuxdaxue.com/influxdb-continuous-queries.html)
36 |
--------------------------------------------------------------------------------
/encoding/encoding.go:
--------------------------------------------------------------------------------
1 | package encoding
2 |
3 | import "io"
4 |
5 | const NOOP = "no-op"
6 |
7 | // NoOpDecoder implements the Decoder interface
8 | func NoOpDecoder(_ io.Reader, _ *map[string]interface{}) error { return nil }
9 |
10 | func NoOpDecoderFactory(_ bool) func(io.Reader, *map[string]interface{}) error { return NoOpDecoder }
11 |
12 | // Decoder a accept a param that can be read , and a target map pointer to write
13 | type Decoder func(io.Reader, *map[string]interface{}) error
14 |
15 | // DecoderFactory returns a Decoder
16 | // 1. EntityDecoder {}
17 | // 2. CollectionDecoder []
18 | type DecoderFactory func(bool) func(io.Reader, *map[string]interface{}) error
19 |
20 | func Get(key string) DecoderFactory {
21 | return decoders.Get(key)
22 | }
23 |
24 | func Register(key string, factory func(bool) func(io.Reader, *map[string]interface{}) error) error {
25 | return decoders.Register(key, factory)
26 | }
27 |
--------------------------------------------------------------------------------
/encoding/json.go:
--------------------------------------------------------------------------------
1 | package encoding
2 |
3 | import (
4 | "encoding/json"
5 | "io"
6 | )
7 |
8 | const JSON = "json"
9 |
10 | func NewJSONDecoder(isCollection bool) func(io.Reader, *map[string]interface{}) error {
11 | if isCollection {
12 | return JSONCollectionDecoder()
13 | } else {
14 | return JSONDecoder()
15 | }
16 | }
17 |
18 | func JSONDecoder() Decoder {
19 | return func(reader io.Reader, i *map[string]interface{}) error {
20 | d := json.NewDecoder(reader)
21 | d.UseNumber()
22 | return d.Decode(i)
23 | }
24 | }
25 |
26 | func JSONCollectionDecoder() Decoder {
27 | return func(reader io.Reader, i *map[string]interface{}) error {
28 | var collection []interface{}
29 | d := json.NewDecoder(reader)
30 | d.UseNumber()
31 | if err := d.Decode(&collection); err != nil {
32 | return err
33 | }
34 | *i = map[string]interface{}{"list": collection}
35 | return nil
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/encoding/register.go:
--------------------------------------------------------------------------------
1 | package encoding
2 |
3 | import (
4 | "io"
5 | "melody/register"
6 | )
7 |
8 | var (
9 | decoders = initDecoderRegister()
10 | defaultDecoders = map[string]func(bool) func(io.Reader, *map[string]interface{}) error{
11 | JSON: NewJSONDecoder,
12 | STRING: NewStringDecoder,
13 | NOOP: NoOpDecoderFactory,
14 | }
15 | )
16 |
17 | type DecoderRegister struct {
18 | data register.Untyped
19 | }
20 |
21 | func (d *DecoderRegister) Get(s string) DecoderFactory {
22 | // if can not get decoder:key = s,
23 | // return json decoder
24 | for _, v := range []string{s, JSON} {
25 | if v, ok := d.data.Get(v); ok {
26 | decoderFactory, ok := v.(func(bool) func(io.Reader, *map[string]interface{}) error)
27 | if ok {
28 | return decoderFactory
29 | }
30 | }
31 | }
32 |
33 | return NewJSONDecoder
34 | }
35 |
36 | func (d *DecoderRegister) Register(name string, factory func(bool) func(io.Reader, *map[string]interface{}) error) error {
37 | d.data.Register(name, factory)
38 | return nil
39 | }
40 |
41 | func initDecoderRegister() *DecoderRegister {
42 | decoder := &DecoderRegister{data: register.New()}
43 | for k, v := range defaultDecoders {
44 | decoder.data.Register(k, v)
45 | }
46 | return decoder
47 | }
48 |
--------------------------------------------------------------------------------
/encoding/string.go:
--------------------------------------------------------------------------------
1 | package encoding
2 |
3 | import (
4 | "io"
5 | "io/ioutil"
6 | )
7 |
8 | const STRING = "string"
9 |
10 | func NewStringDecoder(_ bool) func(io.Reader, *map[string]interface{}) error {
11 | return StringDecoder()
12 | }
13 |
14 | func StringDecoder() Decoder {
15 | return func(reader io.Reader, i *map[string]interface{}) error {
16 | data, err := ioutil.ReadAll(reader)
17 | if err != nil {
18 | return err
19 | }
20 | *i = map[string]interface{}{"content": string(data)}
21 | return nil
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/logging/log.go:
--------------------------------------------------------------------------------
1 | package logging
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "io/ioutil"
7 | "log"
8 | "os"
9 | "strings"
10 | )
11 |
12 | // Logger show logging information with level
13 | type Logger interface {
14 | Debug(v ...interface{})
15 | Info(v ...interface{})
16 | Warning(v ...interface{})
17 | Error(v ...interface{})
18 | Critical(v ...interface{})
19 | Fatal(v ...interface{})
20 | }
21 |
22 | const (
23 | // LEVEL_DEBUG = 0
24 | LEVEL_DEBUG = iota
25 | // LEVEL_INFO = 1
26 | LEVEL_INFO
27 | // LEVEL_WARNING = 2
28 | LEVEL_WARNING
29 | // LEVEL_ERROR = 3
30 | LEVEL_ERROR
31 | // LEVEL_CRITICAL = 4
32 | LEVEL_CRITICAL
33 | )
34 |
35 | var (
36 | ErrInvalidLogLevel = fmt.Errorf("invalid log level")
37 | defaultLogger = logger{Level: LEVEL_CRITICAL, Prefix: ""}
38 | logLevels = map[string]int{
39 | "DEBUG": LEVEL_DEBUG,
40 | "INFO": LEVEL_INFO,
41 | "WARNING": LEVEL_WARNING,
42 | "ERROR": LEVEL_ERROR,
43 | "CRITICAL": LEVEL_CRITICAL,
44 | }
45 | // NoOp is the NO-OP logger
46 | NoOp, _ = NewLogger("CRITICAL", ioutil.Discard, "")
47 | )
48 |
49 | //NewLogger returns a base logger
50 | func NewLogger(level string, out io.Writer, prefix string) (Logger, error) {
51 | log.SetOutput(out)
52 | l, ok := logLevels[strings.ToUpper(level)]
53 | if !ok {
54 | return defaultLogger, ErrInvalidLogLevel
55 | }
56 | return logger{Level: l, Prefix: prefix}, nil
57 | }
58 |
59 | type logger struct {
60 | Level int
61 | Prefix string
62 | }
63 |
64 | // Debug logs a message using DEBUG as log level.
65 | func (l logger) Debug(v ...interface{}) {
66 | if l.Level > LEVEL_DEBUG {
67 | return
68 | }
69 | l.prependLog("DEBUG:", v)
70 | }
71 |
72 | // Info logs a message using INFO as log level.
73 | func (l logger) Info(v ...interface{}) {
74 | if l.Level > LEVEL_INFO {
75 | return
76 | }
77 | l.prependLog("INFO:", v)
78 | }
79 |
80 | // Warning logs a message using WARNING as log level.
81 | func (l logger) Warning(v ...interface{}) {
82 | if l.Level > LEVEL_WARNING {
83 | return
84 | }
85 | l.prependLog("WARNING:", v)
86 | }
87 |
88 | // Error logs a message using ERROR as log level.
89 | func (l logger) Error(v ...interface{}) {
90 | if l.Level > LEVEL_ERROR {
91 | return
92 | }
93 | l.prependLog("ERROR:", v)
94 | }
95 |
96 | // Critical logs a message using CRITICAL as log level.
97 | func (l logger) Critical(v ...interface{}) {
98 | l.prependLog("CRITICAL:", v)
99 | }
100 |
101 | // Fatal is equivalent to l.Critical(fmt.Sprint()) followed by a call to os.Exit(1).
102 | func (l logger) Fatal(v ...interface{}) {
103 | l.prependLog("FATAL:", v)
104 | os.Exit(1)
105 | }
106 |
107 | func (l logger) prependLog(level string, v []interface{}) {
108 | log.Println(append([]interface{}{l.Prefix, level}, v...)...)
109 | }
110 |
--------------------------------------------------------------------------------
/logging/log_test.go:
--------------------------------------------------------------------------------
1 | package logging
2 |
3 | import (
4 | "bytes"
5 | "os"
6 | "os/exec"
7 | "regexp"
8 | "testing"
9 | )
10 |
11 | const (
12 | debugMsg = "Debug msg"
13 | infoMsg = "Info msg"
14 | warningMsg = "Warning msg"
15 | errorMsg = "Error msg"
16 | criticalMsg = "Critical msg"
17 | fatalMsg = "Fatal msg"
18 | )
19 |
20 | func TestNewLogger(t *testing.T) {
21 | levels := []string{"DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"}
22 | regexps := []*regexp.Regexp{
23 | regexp.MustCompile(debugMsg),
24 | regexp.MustCompile(infoMsg),
25 | regexp.MustCompile(warningMsg),
26 | regexp.MustCompile(errorMsg),
27 | regexp.MustCompile(criticalMsg),
28 | }
29 |
30 | for i, level := range levels {
31 | output := logSomeStuff(level)
32 | for j := i; j < len(regexps); j++ {
33 | if !regexps[j].MatchString(output) {
34 | t.Errorf("The output doesn't contain the expected msg for the level: %s. [%s]", level, output)
35 | }
36 | }
37 | }
38 | }
39 |
40 | func TestNewLogger_unknownLevel(t *testing.T) {
41 | _, err := NewLogger("UNKNOWN", bytes.NewBuffer(make([]byte, 1024)), "pref")
42 | if err == nil {
43 | t.Error("The factory didn't return the expected error")
44 | return
45 | }
46 | if err != ErrInvalidLogLevel {
47 | t.Errorf("The factory didn't return the expected error. Got: %s", err.Error())
48 | }
49 | }
50 |
51 | func TestNewLogger_fatal(t *testing.T) {
52 | if os.Getenv("BE_CRASHER") == "1" {
53 | l, err := NewLogger("Critical", bytes.NewBuffer(make([]byte, 1024)), "pref")
54 | if err != nil {
55 | t.Error("The factory returned an expected error:", err.Error())
56 | return
57 | }
58 | l.Fatal("crash!!!")
59 | return
60 | }
61 | cmd := exec.Command(os.Args[0], "-test.run=TestNewLogger_fatal")
62 | cmd.Env = append(os.Environ(), "BE_CRASHER=1")
63 | err := cmd.Run()
64 | if e, ok := err.(*exec.ExitError); ok && !e.Success() {
65 | return
66 | }
67 | t.Fatalf("process ran with err %v, want exit status 1", err)
68 | }
69 |
70 | func logSomeStuff(level string) string {
71 | buff := bytes.NewBuffer(make([]byte, 1024))
72 | logger, _ := NewLogger(level, buff, "pref")
73 |
74 | logger.Debug(debugMsg)
75 | logger.Info(infoMsg)
76 | logger.Warning(warningMsg)
77 | logger.Error(errorMsg)
78 | logger.Critical(criticalMsg)
79 |
80 | return buff.String()
81 | }
82 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "log"
6 | "melody/cmd"
7 | "melody/core/melody"
8 | viper "melody/middleware/melody-viper"
9 | "os"
10 | "os/signal"
11 | "syscall"
12 | )
13 |
14 | func main() {
15 | sigs := make(chan os.Signal, 1)
16 | signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
17 | ctx, cancel := context.WithCancel(context.Background())
18 | defer cancel()
19 |
20 | go func() {
21 | select {
22 | case sig := <-sigs:
23 | log.Println("Signal intercepted:", sig)
24 | cancel()
25 | case <-ctx.Done():
26 | }
27 | }()
28 |
29 | melody.RegisterEncoders()
30 |
31 | parser := viper.New()
32 | cmd.Execute(parser, melody.NewExecutor(ctx))
33 | }
34 |
--------------------------------------------------------------------------------
/main_test.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "testing"
5 | )
6 |
7 | func TestCommandRun(t *testing.T) {
8 |
9 | }
10 |
--------------------------------------------------------------------------------
/melody-cases/Makefile:
--------------------------------------------------------------------------------
1 | p:
2 | go run profile/main.go
3 |
4 | r:
5 | go run role/main.go
6 |
7 | c:
8 | go run coupon/main.go
9 |
10 | f:
11 | go run finance/main.go
--------------------------------------------------------------------------------
/melody-cases/coupon/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "github.com/gin-gonic/gin"
6 | "net/http"
7 | )
8 |
9 | func main() {
10 | profileEngine := gin.Default()
11 |
12 | profileEngine.GET("/coupon/:uuid", func(context *gin.Context) {
13 | uuid := context.Param("uuid")
14 | context.JSON(http.StatusOK, map[string]interface{}{
15 | "id": 1,
16 | "uuid": uuid,
17 | "coupons": []map[string]interface{}{
18 | {
19 | "title": "5元优惠券",
20 | "condition": "age > 20",
21 | "start": "xxxx-xx-xx",
22 | "end": "xxxx-xx-xx",
23 | },
24 | {
25 | "title": "7元优惠券",
26 | "condition": "none",
27 | "start": "xxxx-xx-xx",
28 | "end": "xxxx-xx-xx",
29 | },
30 | {
31 | "title": "7元优惠券",
32 | "condition": "none",
33 | "start": "xxxx-xx-xx",
34 | "end": "xxxx-xx-xx",
35 | },
36 | {
37 | "title": "5元优惠券",
38 | "condition": "vip true",
39 | "start": "xxxx-xx-xx",
40 | "end": "xxxx-xx-xx",
41 | },
42 | },
43 | })
44 | })
45 | fmt.Println(profileEngine.Run(":9003"))
46 | }
47 |
--------------------------------------------------------------------------------
/melody-cases/finance/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "github.com/gin-gonic/gin"
6 | "net/http"
7 | )
8 |
9 | func main() {
10 | profileEngine := gin.Default()
11 |
12 | profileEngine.GET("/finance/:uuid", func(context *gin.Context) {
13 | uuid := context.Param("uuid")
14 | context.JSON(http.StatusOK, map[string]interface{}{
15 | "id": 1,
16 | "uuid": uuid,
17 | "name": "Grant",
18 | "level": "Gold member",
19 | "active_balance": 5000.22,
20 | "frozen_balance": 1000.00,
21 | "borrow_balance": 50000.00,
22 | })
23 | })
24 | fmt.Println(profileEngine.Run(":9004"))
25 | }
26 |
--------------------------------------------------------------------------------
/melody-cases/melody.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 1,
3 | "output_encoding": "json",
4 | "disable_rest": false,
5 | "extra_config": {
6 | "melody_gologging": {
7 | "prefix": "[演示]",
8 | "level": "DEBUG",
9 | "format": "default",
10 | "syslog": false,
11 | "stdout": true
12 | },
13 | "melody_metrics": {
14 | "endpoint_disabled": false,
15 | "proxy_disabled": false,
16 | "router_disabled": false,
17 | "backend_disabled": false,
18 | "collection_time": "10s"
19 | },
20 | "melody_influxdb": {
21 | "address": "http://127.0.0.1:8086",
22 | "username": "",
23 | "password": "",
24 | "db": "demo",
25 | "buffer_size": 1024,
26 | "ttl": "5s",
27 | "time_out": "1s",
28 | "data_server_enable": true,
29 | "data_server_port": ":8001",
30 | "data_server_query_enable": true
31 | },
32 | "melody_alert": {
33 | "NumGC": "100",
34 | "NumGoroutine": "100",
35 | "Sys": "200m",
36 | "HeapSys": "150m",
37 | "StackSys": "50m",
38 | "MCacheSys": "50m",
39 | "MSpanSys": "20m"
40 | }
41 | },
42 | "endpoints": [
43 | {
44 | "endpoint": "/user/info/{id}",
45 | "method": "POST",
46 | "output_encoding": "JSON",
47 | "extra_config": {
48 | "melody_proxy": {
49 | "sequential": true
50 | },
51 | "melody_alert": {
52 | "time": "10ms",
53 | "size": "1024"
54 | }
55 | },
56 | "backends": [
57 | {
58 | "url_pattern": "/profile/{id}",
59 | "method": "GET",
60 | "host": [
61 | "127.0.0.1:9001"
62 | ],
63 | "encoding": "json",
64 | "sd": "static",
65 | "group": "profile_info"
66 | },
67 | {
68 | "url_pattern": "/role/{resp0_profile_info.role_id}",
69 | "method": "GET",
70 | "host": [
71 | "127.0.0.1:9002"
72 | ],
73 | "encoding": "json",
74 | "sd": "static",
75 | "group": "role_info"
76 | }
77 | ],
78 | "headers_to_pass": [
79 | "*"
80 | ]
81 | }
82 | ],
83 | "name": "计算机设计大赛演示",
84 | "port": "8000"
85 | }
--------------------------------------------------------------------------------
/melody-cases/profile/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "net/http"
6 |
7 | "github.com/gin-gonic/gin"
8 | )
9 |
10 | func main() {
11 | profileEngine := gin.Default()
12 | profileEngine.GET("/profile/:id", func(context *gin.Context) {
13 | id := context.Param("id")
14 | context.JSON(http.StatusOK, map[string]interface{}{
15 | "id": id,
16 | "uuid": "15316368801",
17 | "name": "Grant",
18 | "age": 22,
19 | "role_id": 1,
20 | "vip": false,
21 | "password": "xxxxxxx",
22 | "nick_name": "Grant",
23 | "avatar": "grant.jpg",
24 | "birth": "xxxx-xx-xx",
25 | })
26 | })
27 | fmt.Println(profileEngine.Run(":9001"))
28 | }
29 |
--------------------------------------------------------------------------------
/melody-cases/role/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "github.com/gin-gonic/gin"
6 | "net/http"
7 | )
8 |
9 | func main() {
10 | profileEngine := gin.Default()
11 |
12 | profileEngine.GET("/role/:id", func(context *gin.Context) {
13 | roleId := context.Param("id")
14 | context.JSON(http.StatusOK, map[string]interface{}{
15 | "role_id": roleId,
16 | "role_name": "Administrator",
17 | "role_tag": "Admin",
18 | "authorities": []map[string]interface{}{
19 | {
20 | "tag": "user add",
21 | "path": "/user/add",
22 | "method": "post",
23 | "active": true,
24 | },
25 | {
26 | "tag": "user delete",
27 | "path": "/user/delete",
28 | "method": "get",
29 | "active": true,
30 | },
31 | },
32 | })
33 | })
34 | fmt.Println(profileEngine.Run(":9002"))
35 | }
36 |
--------------------------------------------------------------------------------
/middleware/melody-alert/config.go:
--------------------------------------------------------------------------------
1 | package alert
2 |
3 | import (
4 | "errors"
5 | "melody/config"
6 | )
7 |
8 | const (
9 | namespace = "melody_alert"
10 | )
11 |
12 | func NewChecker(cfg *config.ServiceConfig) (Checker, error) {
13 | // 解析Service
14 | m, err := parseConfig(cfg.ExtraConfig)
15 | if err != nil {
16 | return nil, err
17 | }
18 |
19 | // 解析Endpoint
20 | for _, endpointConfig := range cfg.Endpoints {
21 | endpointM, err := parseConfig(endpointConfig.ExtraConfig)
22 | if err == nil {
23 | m[endpointConfig.Endpoint] = endpointM
24 | }
25 | }
26 |
27 | checker, err := newChecker(m)
28 | if err != nil {
29 | return nil, err
30 | }
31 | return checker, nil
32 | }
33 |
34 | func parseConfig(extraConfig config.ExtraConfig) (map[string]interface{}, error) {
35 | if _, ok := extraConfig[namespace]; !ok {
36 | return nil, errors.New("no melody_alert")
37 | }
38 |
39 | if fm, ok := extraConfig[namespace].(map[string]interface{}); !ok {
40 | return nil, errors.New("no fields")
41 | } else {
42 | return fm, nil
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/middleware/melody-alert/model/warning.go:
--------------------------------------------------------------------------------
1 | package model
2 |
3 | import (
4 | "sync"
5 | )
6 |
7 | type Warning struct {
8 | Id int64 `gorm:"id" json:"id"`
9 | Description string `gorm:"description" json:"description"`
10 | TaskName string `gorm:"task_name" json:"task_name"`
11 | CurValue int64 `gorm:"cur_value" json:"cur_value"`
12 | Threshold int64 `gorm:"threshold" json:"threshold"`
13 | Ctime int64 `gorm:"ctime" json:"ctime"`
14 | Handled int `gorm:"handled" json:"handled"`
15 | }
16 |
17 | var (
18 | Id = new(IdWorker)
19 | WarningList = NewWarningList()
20 | )
21 |
22 | type Warnings struct {
23 | Warnings []Warning `json:"warnings"`
24 | Lock sync.RWMutex `json:"-"`
25 | WatchChan chan Warning `json:"-"`
26 | }
27 |
28 | func NewWarningList() Warnings {
29 | return Warnings{
30 | Warnings: make([]Warning, 0),
31 | Lock: sync.RWMutex{},
32 | WatchChan: make(chan Warning),
33 | }
34 | }
35 |
36 | func (ws *Warnings) Add(warning Warning) {
37 | ws.Lock.Lock()
38 | ws.Warnings = append(ws.Warnings, warning)
39 | ws.Lock.Unlock()
40 | ws.WatchChan <- warning
41 | }
42 |
43 | func (ws *Warnings) ChangeStatus(id int64) {
44 | ws.Lock.Lock()
45 | if ws.Warnings[id-1].Handled == 0 {
46 | ws.Warnings[id-1].Handled = 1
47 | } else {
48 | ws.Warnings[id-1].Handled = 0
49 | }
50 | ws.Lock.Unlock()
51 | }
52 |
53 | type IdWorker struct {
54 | Id int64
55 | Lock sync.RWMutex
56 | }
57 |
58 | func (id *IdWorker) inc() {
59 | id.Lock.Lock()
60 | id.Id++
61 | id.Lock.Unlock()
62 | }
63 |
64 | func (id *IdWorker) GetId() int64 {
65 | id.inc()
66 | return id.Id
67 | }
68 |
--------------------------------------------------------------------------------
/middleware/melody-bloomfilter/bloomfilter.go:
--------------------------------------------------------------------------------
1 | package bloomfilter
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 | "errors"
7 | "melody/bf"
8 | rpc_bf "melody/bf/rpc"
9 | "melody/bf/rpc/server"
10 | "melody/config"
11 | "melody/logging"
12 | "net/http"
13 | )
14 |
15 | const Namespace = "melody_bloomfilter"
16 |
17 | var (
18 | errNoConfig = errors.New("no config for the bloomfilter")
19 | errWrongConfig = errors.New("invalid config for the bloomfilter")
20 | )
21 |
22 | type Config struct {
23 | rpc_bf.Config
24 | TokenKeys []string
25 | Headers []string
26 | }
27 |
28 | type Rejecter struct {
29 | BF bf.BloomFilter
30 | TokenKeys []string
31 | Headers []string
32 | }
33 |
34 | // Register registers a bloomfilter given a config and registers the service with consul
35 | func Register(ctx context.Context, serviceName string, cfg config.ServiceConfig,
36 | logger logging.Logger, register func(n string, p int)) (Rejecter, error) {
37 |
38 | data, ok := cfg.ExtraConfig[Namespace]
39 | if !ok {
40 | logger.Debug(errNoConfig.Error())
41 | return nopRejecter, errNoConfig
42 | }
43 |
44 | raw, err := json.Marshal(data)
45 | if err != nil {
46 | logger.Debug(errWrongConfig.Error())
47 | return nopRejecter, errWrongConfig
48 | }
49 |
50 | var rpcConfig Config
51 | if err := json.Unmarshal(raw, &rpcConfig); err != nil {
52 | logger.Debug(err.Error(), string(raw))
53 | return nopRejecter, err
54 | }
55 |
56 | rpcBF := server.New(ctx, rpcConfig.Config)
57 | register(serviceName, rpcConfig.Port)
58 |
59 | return Rejecter{
60 | BF: rpcBF.Get(),
61 | TokenKeys: rpcConfig.TokenKeys,
62 | Headers: rpcConfig.Headers,
63 | }, nil
64 | }
65 |
66 | func (r *Rejecter) RejectToken(claims map[string]interface{}) bool {
67 | for _, k := range r.TokenKeys {
68 | v, ok := claims[k]
69 | if !ok {
70 | continue
71 | }
72 | data, ok := v.(string)
73 | if !ok {
74 | continue
75 | }
76 | if r.BF.Check([]byte(k + "-" + data)) {
77 | return true
78 | }
79 | }
80 | return false
81 | }
82 |
83 | func (r *Rejecter) RejectHeader(header http.Header) bool {
84 | for _, k := range r.Headers {
85 | data := header.Get(k)
86 | if data == "" {
87 | continue
88 | }
89 | if r.BF.Check([]byte(k + "-" + data)) {
90 | return true
91 | }
92 | }
93 | return false
94 | }
95 |
96 | var nopRejecter = Rejecter{BF: new(bf.EmptySet)}
97 |
--------------------------------------------------------------------------------
/middleware/melody-botmonitor/gin/monitor.go:
--------------------------------------------------------------------------------
1 | package gin
2 |
3 | import (
4 | "errors"
5 | "net/http"
6 |
7 | "github.com/gin-gonic/gin"
8 | "melody/config"
9 | "melody/logging"
10 | botmonitor "melody/middleware/melody-botmonitor"
11 | melody "melody/middleware/melody-botmonitor/melody"
12 | "melody/proxy"
13 | melodygin "melody/router/gin"
14 | )
15 |
16 | // Register 检查配置,按照需要在gin引擎上注册一个bot检测中间件
17 | func Register(cfg config.ServiceConfig, l logging.Logger, engine *gin.Engine) {
18 | detectorCfg, err := melody.ParseConfig(cfg.ExtraConfig)
19 | if err == melody.ErrNoConfig {
20 | l.Debug("botmonitor middleware: ", err.Error())
21 | return
22 | }
23 | if err != nil {
24 | l.Warning("botmonitor middleware: ", err.Error())
25 | return
26 | }
27 | d, err := botmonitor.New(detectorCfg)
28 | if err != nil {
29 | l.Warning("botmonitor middleware: unable to createt the LRU detector:", err.Error())
30 | return
31 | }
32 | engine.Use(middleware(d))
33 | }
34 |
35 | // New 检查配置
36 | func New(hf melodygin.HandlerFactory, l logging.Logger) melodygin.HandlerFactory {
37 | return func(cfg *config.EndpointConfig, p proxy.Proxy) gin.HandlerFunc {
38 | next := hf(cfg, p)
39 |
40 | detectorCfg, err := melody.ParseConfig(cfg.ExtraConfig)
41 | if err == melody.ErrNoConfig {
42 | l.Debug("botmonitor: ", err.Error())
43 | return next
44 | }
45 | if err != nil {
46 | l.Warning("botmonitor: ", err.Error())
47 | return next
48 | }
49 |
50 | d, err := botmonitor.New(detectorCfg)
51 | if err != nil {
52 | l.Warning("botmonitor: unable to create the LRU detector:", err.Error())
53 | return next
54 | }
55 | return handler(d, next)
56 | }
57 | }
58 |
59 | func middleware(f botmonitor.DetectorFunc) gin.HandlerFunc {
60 | return func(c *gin.Context) {
61 | if f(c.Request) {
62 | c.AbortWithError(http.StatusForbidden, errBotRejected)
63 | return
64 | }
65 |
66 | c.Next()
67 | }
68 | }
69 |
70 | func handler(f botmonitor.DetectorFunc, next gin.HandlerFunc) gin.HandlerFunc {
71 | return func(c *gin.Context) {
72 | if f(c.Request) {
73 | c.AbortWithError(http.StatusForbidden, errBotRejected)
74 | return
75 | }
76 |
77 | next(c)
78 | }
79 | }
80 |
81 | var errBotRejected = errors.New("bot rejected")
82 |
--------------------------------------------------------------------------------
/middleware/melody-botmonitor/gin/monitor_test.go:
--------------------------------------------------------------------------------
1 | package gin
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "net/http"
7 | "net/http/httptest"
8 | "testing"
9 |
10 | "github.com/gin-gonic/gin"
11 | "melody/config"
12 | "melody/logging"
13 | melody "melody/middleware/melody-botmonitor/melody"
14 | "melody/proxy"
15 | melodygin "melody/router/gin"
16 | )
17 |
18 | func TestRegister(t *testing.T) {
19 | gin.SetMode(gin.TestMode)
20 |
21 | engine := gin.New()
22 |
23 | cfg := config.ServiceConfig{
24 | ExtraConfig: config.ExtraConfig{
25 | melody.Namespace: map[string]interface{}{
26 | "blacklist": []string{"a", "b"},
27 | "whitelist": []string{"c", "Pingdom.com_bot_version_1.1"},
28 | "patterns": []string{
29 | `(Pingdom.com_bot_version_)(\d+)\.(\d+)`,
30 | `(facebookexternalhit)/(\d+)\.(\d+)`,
31 | },
32 | },
33 | },
34 | }
35 |
36 | Register(cfg, logging.NoOp, engine)
37 |
38 | engine.GET("/", func(c *gin.Context) {
39 | c.String(200, "hi!")
40 | })
41 |
42 | if err := testDetection(engine); err != nil {
43 | t.Error(err)
44 | }
45 | }
46 |
47 | func TestNew(t *testing.T) {
48 | gin.SetMode(gin.TestMode)
49 |
50 | engine := gin.New()
51 |
52 | cfg := &config.EndpointConfig{
53 | ExtraConfig: config.ExtraConfig{
54 | melody.Namespace: map[string]interface{}{
55 | "blacklist": []string{"a", "b"},
56 | "whitelist": []string{"c", "Pingdom.com_bot_version_1.1"},
57 | "patterns": []string{
58 | `(Pingdom.com_bot_version_)(\d+)\.(\d+)`,
59 | `(facebookexternalhit)/(\d+)\.(\d+)`,
60 | },
61 | },
62 | },
63 | }
64 |
65 | proxyfunc := func(_ context.Context, _ *proxy.Request) (*proxy.Response, error) {
66 | return &proxy.Response{IsComplete: true}, nil
67 | }
68 |
69 | engine.GET("/", New(melodygin.EndpointHandler, logging.NoOp)(cfg, proxyfunc))
70 |
71 | if err := testDetection(engine); err != nil {
72 | t.Error(err)
73 | }
74 | }
75 |
76 | func testDetection(engine *gin.Engine) error {
77 | for i, ua := range []string{
78 | "abcd",
79 | "",
80 | "c",
81 | "Pingdom.com_bot_version_1.1",
82 | } {
83 | req, _ := http.NewRequest("GET", "http://example.com", nil)
84 | req.Header.Add("User-Agent", ua)
85 |
86 | w := httptest.NewRecorder()
87 | engine.ServeHTTP(w, req)
88 |
89 | if w.Result().StatusCode != 200 {
90 | return fmt.Errorf("the req #%d has been detected as a bot: %s", i, ua)
91 | }
92 | }
93 |
94 | for i, ua := range []string{
95 | "a",
96 | "b",
97 | "facebookexternalhit/1.1",
98 | "Pingdom.com_bot_version_1.2",
99 | } {
100 | req, _ := http.NewRequest("GET", "http://example.com", nil)
101 | req.Header.Add("User-Agent", ua)
102 |
103 | w := httptest.NewRecorder()
104 | engine.ServeHTTP(w, req)
105 |
106 | if w.Result().StatusCode != http.StatusForbidden {
107 | return fmt.Errorf("the req #%d has not been detected as a bot: %s", i, ua)
108 | }
109 | }
110 | return nil
111 | }
112 |
--------------------------------------------------------------------------------
/middleware/melody-botmonitor/melody/config.go:
--------------------------------------------------------------------------------
1 | package melody
2 |
3 | import (
4 | "encoding/json"
5 | "errors"
6 |
7 | "melody/config"
8 | botmonitor "melody/middleware/melody-botmonitor"
9 | )
10 |
11 | // Namespace 命名空间
12 | const Namespace = "melody_botmonitor"
13 |
14 | // ErrNoConfig 当没有为模块定义配置时,将返回ErrNoConfig
15 | var ErrNoConfig = errors.New("no config defined for the module")
16 |
17 | // ParseConfig 从ExtraConfig 中提取模块配置,并返回一个适合使用botmonitor包的结构
18 | func ParseConfig(cfg config.ExtraConfig) (botmonitor.Config, error) {
19 | res := botmonitor.Config{}
20 | e, ok := cfg[Namespace]
21 | if !ok {
22 | return res, ErrNoConfig
23 | }
24 | b, err := json.Marshal(e)
25 | if err != nil {
26 | return res, err
27 | }
28 | err = json.Unmarshal(b, &res)
29 | return res, err
30 | }
31 |
--------------------------------------------------------------------------------
/middleware/melody-botmonitor/monitor.go:
--------------------------------------------------------------------------------
1 | package botmonitor
2 |
3 | import (
4 | "net/http"
5 | "regexp"
6 |
7 | lru "github.com/hashicorp/golang-lru"
8 | )
9 |
10 | // Config 配置定义了检测器的行为
11 | type Config struct {
12 | Blacklist []string
13 | Whitelist []string
14 | Patterns []string
15 | CacheSize int
16 | }
17 |
18 | // DetectorFunc 是一个func,如果一个请求是由一个机器人发出的,它就会进行chek
19 | type DetectorFunc func(r *http.Request) bool
20 |
21 | // New 根据参数返回带有或不带LRU缓存的检测器函数
22 | func New(cfg Config) (DetectorFunc, error) {
23 | if cfg.CacheSize == 0 {
24 | d, err := NewDetector(cfg)
25 | return d.IsBot, err
26 | }
27 |
28 | d, err := NewLRU(cfg)
29 | return d.IsBot, err
30 | }
31 |
32 | // NewDetector 创建一个检测器
33 | func NewDetector(cfg Config) (*Detector, error) {
34 | blacklist := make(map[string]struct{}, len(cfg.Blacklist))
35 | for _, e := range cfg.Blacklist {
36 | blacklist[e] = struct{}{}
37 | }
38 | whitelist := make(map[string]struct{}, len(cfg.Whitelist))
39 | for _, e := range cfg.Whitelist {
40 | whitelist[e] = struct{}{}
41 | }
42 | patterns := make([]*regexp.Regexp, len(cfg.Patterns))
43 | for i, p := range cfg.Patterns {
44 | rp, err := regexp.Compile(p)
45 | if err != nil {
46 | return nil, err
47 | }
48 | patterns[i] = rp
49 | }
50 | return &Detector{
51 | blacklist: blacklist,
52 | whitelist: whitelist,
53 | patterns: patterns,
54 | }, nil
55 | }
56 |
57 | // Detector (检测器)是一种能够检测机器人发出的请求的结构体
58 | type Detector struct {
59 | blacklist map[string]struct{}
60 | whitelist map[string]struct{}
61 | patterns []*regexp.Regexp
62 | }
63 |
64 | // IsBot : 如果请求是由机器人发出的,则IsBot返回true
65 | func (d *Detector) IsBot(r *http.Request) bool {
66 | userAgent := r.Header.Get("User-Agent")
67 | if userAgent == "" {
68 | return false
69 | }
70 | if _, ok := d.whitelist[userAgent]; ok {
71 | return false
72 | }
73 | if _, ok := d.blacklist[userAgent]; ok {
74 | return true
75 | }
76 | for _, p := range d.patterns {
77 | if p.MatchString(userAgent) {
78 | return true
79 | }
80 | }
81 | return false
82 | }
83 |
84 | // NewLRU 创建一个新的LRUDetector
85 | func NewLRU(cfg Config) (*LRUDetector, error) {
86 | d, err := NewDetector(cfg)
87 | if err != nil {
88 | return nil, err
89 | }
90 |
91 | cache, err := lru.New(cfg.CacheSize)
92 | if err != nil {
93 | return nil, err
94 | }
95 |
96 | return &LRUDetector{
97 | detectorFunc: d.IsBot,
98 | cache: cache,
99 | }, nil
100 | }
101 |
102 | // LRUDetector 是一种能够检测bot发出的请求并缓存结果以供将来重用的结构
103 | type LRUDetector struct {
104 | detectorFunc DetectorFunc
105 | cache *lru.Cache
106 | }
107 |
108 | // IsBot 如果请求是由机器人发出的,则IsBot返回true
109 | func (d *LRUDetector) IsBot(r *http.Request) bool {
110 | userAgent := r.Header.Get("User-Agent")
111 | cached, ok := d.cache.Get(userAgent)
112 | if ok {
113 | return cached.(bool)
114 | }
115 |
116 | res := d.detectorFunc(r)
117 | d.cache.Add(userAgent, res)
118 |
119 | return res
120 | }
121 |
--------------------------------------------------------------------------------
/middleware/melody-botmonitor/monitor_bechmark_test.go:
--------------------------------------------------------------------------------
1 | package botmonitor
2 |
3 | import (
4 | "net/http"
5 | "testing"
6 | )
7 |
8 | func BenchmarkDetector(b *testing.B) {
9 | d, err := New(Config{
10 | Blacklist: []string{"a", "b"},
11 | Whitelist: []string{"c", "Pingdom.com_bot_version_1.1"},
12 | Patterns: []string{
13 | `(Pingdom.com_bot_version_)(\d+)\.(\d+)`,
14 | `(facebookexternalhit)/(\d+)\.(\d+)`,
15 | },
16 | })
17 | if err != nil {
18 | b.Error(err)
19 | return
20 | }
21 |
22 | becnhDetection(b, d)
23 | }
24 |
25 | func BenchmarkLRUDetector(b *testing.B) {
26 | d, err := New(Config{
27 | Blacklist: []string{"a", "b"},
28 | Whitelist: []string{"c", "Pingdom.com_bot_version_1.1"},
29 | Patterns: []string{
30 | `(Pingdom.com_bot_version_)(\d+)\.(\d+)`,
31 | `(facebookexternalhit)/(\d+)\.(\d+)`,
32 | },
33 | CacheSize: 10000,
34 | })
35 | if err != nil {
36 | b.Error(err)
37 | return
38 | }
39 |
40 | becnhDetection(b, d)
41 | }
42 |
43 | func becnhDetection(b *testing.B, f DetectorFunc) {
44 | for _, tc := range []struct {
45 | name string
46 | ua string
47 | }{
48 | {"ok_1", "abcd"},
49 | {"ok_2", ""},
50 | {"ok_3", "c"},
51 | {"ok_4", "Pingdom.com_bot_version_1.1"},
52 | {"ko_1", "a"},
53 | {"ko_2", "b"},
54 | {"ko_3", "facebookexternalhit/1.1"},
55 | {"ko_4", "Pingdom.com_bot_version_1.2"},
56 | } {
57 |
58 | req, _ := http.NewRequest("GET", "http://example.com", nil)
59 | req.Header.Add("User-Agent", tc.ua)
60 | b.Run(tc.name, func(b *testing.B) {
61 | for i := 0; i < b.N; i++ {
62 | f(req)
63 | }
64 | })
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/middleware/melody-botmonitor/monitor_test.go:
--------------------------------------------------------------------------------
1 | package botmonitor
2 |
3 | import (
4 | "fmt"
5 | "net/http"
6 | "testing"
7 | )
8 |
9 | func TestNew_noLRU(t *testing.T) {
10 | d, err := New(Config{
11 | Blacklist: []string{"a", "b"},
12 | Whitelist: []string{"c", "Pingdom.com_bot_version_1.1"},
13 | Patterns: []string{
14 | `(Pingdom.com_bot_version_)(\d+)\.(\d+)`,
15 | `(facebookexternalhit)/(\d+)\.(\d+)`,
16 | },
17 | })
18 | if err != nil {
19 | t.Error(err)
20 | return
21 | }
22 |
23 | if err := testDetection(d); err != nil {
24 | t.Error(err)
25 | }
26 | }
27 |
28 | func TestNew_LRU(t *testing.T) {
29 | d, err := New(Config{
30 | Blacklist: []string{"a", "b"},
31 | Whitelist: []string{"c", "Pingdom.com_bot_version_1.1"},
32 | Patterns: []string{
33 | `(Pingdom.com_bot_version_)(\d+)\.(\d+)`,
34 | `(facebookexternalhit)/(\d+)\.(\d+)`,
35 | },
36 | CacheSize: 10000,
37 | })
38 | if err != nil {
39 | t.Error(err)
40 | return
41 | }
42 |
43 | if err := testDetection(d); err != nil {
44 | t.Error(err)
45 | }
46 | }
47 |
48 | func testDetection(f DetectorFunc) error {
49 | for i, ua := range []string{
50 | "abcd",
51 | "",
52 | "c",
53 | "Pingdom.com_bot_version_1.1",
54 | } {
55 | req, _ := http.NewRequest("GET", "http://example.com", nil)
56 | req.Header.Add("User-Agent", ua)
57 | if f(req) {
58 | return fmt.Errorf("the req #%d has been detected as a bot: %s", i, ua)
59 | }
60 | }
61 |
62 | for i, ua := range []string{
63 | "a",
64 | "b",
65 | "facebookexternalhit/1.1",
66 | "Pingdom.com_bot_version_1.2",
67 | } {
68 | req, _ := http.NewRequest("GET", "http://example.com", nil)
69 | req.Header.Add("User-Agent", ua)
70 | if !f(req) {
71 | return fmt.Errorf("the req #%d has not been detected as a bot: %s", i, ua)
72 | }
73 | }
74 | return nil
75 | }
76 |
--------------------------------------------------------------------------------
/middleware/melody-circuitbreaker/gobreaker.go:
--------------------------------------------------------------------------------
1 | package gobreaker
2 |
3 | import (
4 | "fmt"
5 | "github.com/sony/gobreaker"
6 | "melody/config"
7 | "melody/logging"
8 | "time"
9 | )
10 |
11 | const Namespace = "melody_circuitbreaker"
12 |
13 | type Config struct {
14 | //给定的时间间隔(秒)
15 | Interval int
16 | //等待时间窗口(秒)
17 | Timeout int
18 | //连续故障数
19 | MaxErrors int
20 | //断路器状态发生改变时,是否log
21 | LogStatusChange bool
22 | }
23 |
24 | // 空实现
25 | var DefaultCfg = Config{}
26 |
27 | // 获取断路器配置
28 | func ConfigGetter(e config.ExtraConfig) interface{} {
29 | v, ok := e[Namespace]
30 | if !ok {
31 | return DefaultCfg
32 | }
33 | temp, ok := v.(map[string]interface{})
34 | if !ok {
35 | return DefaultCfg
36 | }
37 | cfg := Config{}
38 | if i, ok := temp["interval"]; ok {
39 | switch in := i.(type) {
40 | case int:
41 | cfg.Interval = in
42 | case int64:
43 | cfg.Interval = int(in)
44 | }
45 | }
46 | if v, ok := temp["timeout"]; ok {
47 | switch i := v.(type) {
48 | case int:
49 | cfg.Timeout = i
50 | case float64:
51 | cfg.Timeout = int(i)
52 | }
53 | }
54 | if v, ok := temp["maxErrors"]; ok {
55 | switch i := v.(type) {
56 | case int:
57 | cfg.MaxErrors = i
58 | case float64:
59 | cfg.MaxErrors = int(i)
60 | }
61 | }
62 | value, ok := temp["logStatusChange"].(bool)
63 | cfg.LogStatusChange = ok && value
64 |
65 | return cfg
66 | }
67 |
68 | // 基于sony的断路器,包装断路器对象
69 | func NewCircuitBreaker(config Config, logger logging.Logger) *gobreaker.CircuitBreaker {
70 | settings := gobreaker.Settings{
71 | Name: "Melody CircuitBreaker",
72 | Interval: time.Duration(config.Interval) * time.Second,
73 | Timeout: time.Duration(config.Timeout) * time.Second,
74 | ReadyToTrip: func(counts gobreaker.Counts) bool {
75 | return counts.ConsecutiveFailures > uint32(config.MaxErrors)
76 | },
77 | }
78 |
79 | if config.LogStatusChange {
80 | settings.OnStateChange = func(name string, from gobreaker.State, to gobreaker.State) {
81 | logger.Warning(fmt.Sprintf("circuit breaker named '%s' went from '%s' to '%s'", name, from.String(), to.String()))
82 | }
83 | }
84 |
85 | return gobreaker.NewCircuitBreaker(settings)
86 | }
87 |
--------------------------------------------------------------------------------
/middleware/melody-circuitbreaker/proxy/proxy.go:
--------------------------------------------------------------------------------
1 | package proxy
2 |
3 | import (
4 | "context"
5 | "melody/config"
6 | "melody/logging"
7 | gobreaker "melody/middleware/melody-circuitbreaker"
8 | "melody/proxy"
9 | )
10 |
11 | func BackendFactory(next proxy.BackendFactory, logger logging.Logger) proxy.BackendFactory {
12 | return func(backend *config.Backend) proxy.Proxy {
13 | return NewMiddleware(backend, logger)(next(backend))
14 | }
15 | }
16 |
17 | func NewMiddleware(remote *config.Backend, logger logging.Logger) proxy.Middleware {
18 | config := gobreaker.ConfigGetter(remote.ExtraConfig).(gobreaker.Config)
19 | if config == gobreaker.DefaultCfg {
20 | return proxy.EmptyMiddleware
21 | }
22 |
23 | breaker := gobreaker.NewCircuitBreaker(config, logger)
24 |
25 | return func(next ...proxy.Proxy) proxy.Proxy {
26 | if len(next) > 1 {
27 | panic(proxy.ErrTooManyProxies)
28 | }
29 |
30 | return func(ctx context.Context, request *proxy.Request) (response *proxy.Response, err error) {
31 | res, err := breaker.Execute(func() (i interface{}, err error) {
32 | return next[0](ctx, request)
33 | })
34 | if err != nil {
35 | return nil, err
36 | }
37 | return res.(*proxy.Response), err
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/middleware/melody-consul/client.go:
--------------------------------------------------------------------------------
1 | package consul
2 |
3 | import (
4 | "context"
5 | "github.com/go-contrib/uuid"
6 | "github.com/hashicorp/consul/api"
7 | "melody/config"
8 | "melody/logging"
9 | )
10 |
11 | func Register(ctx context.Context, e config.ExtraConfig, port int, serviceName string, logger logging.Logger) error {
12 | cfg, err := parse(e, port)
13 | if err != nil {
14 | return err
15 | }
16 |
17 | cfg.Name = serviceName
18 |
19 | return register(ctx, cfg, logger)
20 | }
21 |
22 | func register(ctx context.Context, cfg Config, logger logging.Logger) error {
23 | consulConfig := api.DefaultConfig()
24 | consulConfig.Address = cfg.Address
25 | c, err := api.NewClient(consulConfig)
26 | if err != nil {
27 | return err
28 | }
29 |
30 | service := &api.AgentServiceRegistration{
31 | Name: cfg.Name,
32 | Port: cfg.Port,
33 | Tags: cfg.Tags,
34 | ID: uuid.NewV1().String(),
35 | }
36 |
37 | if err := c.Agent().ServiceRegister(service); err != nil {
38 | return err
39 | }
40 |
41 | go func() {
42 | <-ctx.Done()
43 |
44 | if err := c.Agent().ServiceDeregister(service.ID); err != nil {
45 | logger.Info("error when trying to deregister service", service.ID, ":", err)
46 | }
47 | }()
48 |
49 | return nil
50 | }
51 |
--------------------------------------------------------------------------------
/middleware/melody-consul/config.go:
--------------------------------------------------------------------------------
1 | package consul
2 |
3 | import (
4 | "fmt"
5 | "melody/config"
6 | )
7 |
8 | type Config struct {
9 | Address, Name string
10 | Port int
11 | Tags []string
12 | }
13 |
14 | // Namespace is the key to use to store and access the custom config data
15 | const Namespace = "melody_consul"
16 |
17 | var (
18 | // ErrNoConfig is the error to be returned when there is no config with the consul namespace
19 | ErrNoConfig = fmt.Errorf("unable to create the consul client: no config")
20 | // ErrBadConfig is the error to be returned when the config is not well defined
21 | ErrBadConfig = fmt.Errorf("unable to create the consul client with the received config")
22 | // ErrNoMachines is the error to be returned when the config has not defined one or more servers
23 | ErrNoMachines = fmt.Errorf("unable to create the consul client without a set of servers")
24 | )
25 |
26 | func parse(e config.ExtraConfig, port int) (Config, error) {
27 | cfg := Config{
28 | Name: "melody",
29 | Port: port,
30 | }
31 | v, ok := e[Namespace]
32 | if !ok {
33 | return cfg, ErrNoConfig
34 | }
35 | tmp, ok := v.(map[string]interface{})
36 | if !ok {
37 | return cfg, ErrBadConfig
38 | }
39 | a, ok := tmp["address"]
40 | if !ok {
41 | return cfg, ErrNoMachines
42 | }
43 | cfg.Address, ok = a.(string)
44 |
45 | if !ok {
46 | return cfg, ErrNoMachines
47 | }
48 |
49 | if a, ok = tmp["name"]; ok {
50 | cfg.Name, ok = a.(string)
51 | }
52 |
53 | cfg.Tags = parseTags(tmp)
54 |
55 | return cfg, nil
56 | }
57 |
58 | func parseTags(cfg map[string]interface{}) []string {
59 | result := []string{}
60 | tags, ok := cfg["tags"]
61 | if !ok {
62 | return result
63 | }
64 | tgs, ok := tags.([]interface{})
65 | if !ok {
66 | return result
67 | }
68 | for _, tg := range tgs {
69 | if t, ok := tg.(string); ok {
70 | result = append(result, t)
71 | }
72 | }
73 |
74 | return result
75 | }
76 |
--------------------------------------------------------------------------------
/middleware/melody-cors/cors.go:
--------------------------------------------------------------------------------
1 | package cors
2 |
3 | import (
4 | "melody/config"
5 | "time"
6 | )
7 |
8 | // Namespace the key of corss domain
9 | const Namespace = "melody_cors"
10 |
11 | // Config holds the configuration of CORS
12 | type Config struct {
13 | AllowOrigins []string
14 | AllowMethods []string
15 | AllowHeaders []string
16 | ExposeHeaders []string
17 | AllowCredentials bool
18 | MaxAge time.Duration
19 | }
20 |
21 | // GetConfig 获取cors配置strcut
22 | func GetConfig(e config.ExtraConfig) interface{} {
23 | v, ok := e[Namespace]
24 | if !ok {
25 | return nil
26 | }
27 |
28 | temp, ok := v.(map[string]interface{})
29 | if !ok {
30 | return nil
31 | }
32 |
33 | cfg := Config{}
34 | cfg.AllowOrigins = getList(temp, "allow_origins")
35 | cfg.AllowMethods = getList(temp, "allow_methods")
36 | cfg.AllowHeaders = getList(temp, "allow_headers")
37 | cfg.ExposeHeaders = getList(temp, "expose_headers")
38 | if cr, ok := temp["allow_credentials"]; ok {
39 | if cre, ok := cr.(bool); ok {
40 | cfg.AllowCredentials = cre
41 | }
42 | }
43 |
44 | if a, ok := temp["max_age"]; ok {
45 | if d, err := time.ParseDuration(a.(string)); err == nil {
46 | cfg.MaxAge = d
47 | }
48 | }
49 |
50 | return cfg
51 | }
52 |
53 | // getList return array data from map via key
54 | func getList(data map[string]interface{}, name string) []string {
55 | out := []string{}
56 | if vs, ok := data[name]; ok {
57 | if v, ok := vs.([]interface{}); ok {
58 | for _, s := range v {
59 | if j, ok := s.(string); ok {
60 | out = append(out, j)
61 | }
62 | }
63 | }
64 | }
65 | return out
66 | }
67 |
--------------------------------------------------------------------------------
/middleware/melody-cors/cors_test.go:
--------------------------------------------------------------------------------
1 | package cors
2 |
3 | import (
4 | "encoding/json"
5 | "testing"
6 | "time"
7 | )
8 |
9 | func TestConfigGetter(t *testing.T) {
10 | sampleCfg := map[string]interface{}{}
11 | serialized := []byte(`{ "melody_cors": {
12 | "allow_origins": [ "http://localhost", "http://www.example.com" ],
13 | "allow_headers": [ "X-Test", "Content-Type"],
14 | "allow_methods": [ "POST", "GET" ],
15 | "expose_headers": [ "Content-Type" ],
16 | "allow_credentials": false,
17 | "max_age": "24h"
18 | }
19 | }`)
20 | json.Unmarshal(serialized, &sampleCfg)
21 | testCfg := GetConfig(sampleCfg).(Config)
22 |
23 | if len(testCfg.AllowOrigins) != 2 {
24 | t.Error("Should have exactly 2 allowed origins.\n")
25 | }
26 | for i, v := range []string{"http://localhost", "http://www.example.com"} {
27 | if testCfg.AllowOrigins[i] != v {
28 | t.Errorf("Invalid value %s should be %s\n", testCfg.AllowOrigins[i], v)
29 | }
30 | }
31 | if len(testCfg.AllowHeaders) != 2 {
32 | t.Error("Should have exactly 2 allowed headers.\n")
33 | }
34 | for i, v := range []string{"X-Test", "Content-Type"} {
35 | if testCfg.AllowHeaders[i] != v {
36 | t.Errorf("Invalid value %s should be %s\n", testCfg.AllowHeaders[i], v)
37 | }
38 | }
39 | if len(testCfg.AllowMethods) != 2 {
40 | t.Error("Should have exactly 2 allowed headers.\n")
41 | }
42 | for i, v := range []string{"POST", "GET"} {
43 | if testCfg.AllowMethods[i] != v {
44 | t.Errorf("Invalid value %s should be %s\n", testCfg.AllowMethods[i], v)
45 | }
46 | }
47 | if len(testCfg.ExposeHeaders) != 1 {
48 | t.Error("Should have exactly 2 allowed headers.\n")
49 | }
50 | for i, v := range []string{"Content-Type"} {
51 | if testCfg.ExposeHeaders[i] != v {
52 | t.Errorf("Invalid value %s should be %s\n", testCfg.ExposeHeaders[i], v)
53 | }
54 | }
55 | if testCfg.AllowCredentials {
56 | t.Error("Allow Credentials should be disabled.\n")
57 | }
58 |
59 | if testCfg.MaxAge != 24*time.Hour {
60 | t.Errorf("Unexpected collection time: %v\n", testCfg.MaxAge)
61 | }
62 | }
63 |
64 | func TestDefaultConfiguration(t *testing.T) {
65 | sampleCfg := map[string]interface{}{}
66 | serialized := []byte(`{ "melody_cors": {
67 | "allow_origins": [ "http://www.example.com" ]
68 | }}`)
69 | json.Unmarshal(serialized, &sampleCfg)
70 | defaultCfg := GetConfig(sampleCfg).(Config)
71 | if defaultCfg.AllowOrigins[0] != "http://www.example.com" {
72 | t.Error("Wrong AllowOrigin.\n")
73 | }
74 | }
75 |
76 | func TestWrongConfiguration(t *testing.T) {
77 | sampleCfg := map[string]interface{}{}
78 | if _, ok := GetConfig(sampleCfg).(Config); ok {
79 | t.Error("The config should be nil\n")
80 | }
81 | badCfg := map[string]interface{}{Namespace: "test"}
82 | if _, ok := GetConfig(badCfg).(Config); ok {
83 | t.Error("The config should be nil\n")
84 | }
85 | }
86 |
87 | func TestEmptyConfiguration(t *testing.T) {
88 | noOriginCfg := map[string]interface{}{}
89 | serialized := []byte(`{ "melody_cors": {
90 | }
91 | }`)
92 | json.Unmarshal(serialized, &noOriginCfg)
93 | if v, ok := GetConfig(noOriginCfg).(Config); !ok {
94 | t.Errorf("The configuration should not be empty: %v\n", v)
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/middleware/melody-cors/gin/cors.go:
--------------------------------------------------------------------------------
1 | package gin
2 |
3 | import (
4 | "melody/config"
5 |
6 | gincors "melody/middleware/melody-cors"
7 |
8 | "github.com/gin-gonic/gin"
9 | "github.com/rs/cors"
10 | wrapper "github.com/rs/cors/wrapper/gin"
11 | )
12 |
13 | // New 创建cors middleware
14 | func New(extra config.ExtraConfig) gin.HandlerFunc {
15 | c := gincors.GetConfig(extra)
16 | if c == nil {
17 | return nil
18 | }
19 |
20 | t, ok := c.(gincors.Config)
21 | if !ok {
22 | return nil
23 | }
24 |
25 | return wrapper.New(
26 | cors.Options{
27 | AllowedOrigins: t.AllowOrigins,
28 | AllowedMethods: t.AllowMethods,
29 | AllowedHeaders: t.AllowHeaders,
30 | ExposedHeaders: t.ExposeHeaders,
31 | AllowCredentials: t.AllowCredentials,
32 | MaxAge: int(t.MaxAge.Seconds()),
33 | })
34 | }
35 |
--------------------------------------------------------------------------------
/middleware/melody-etcd/config.go:
--------------------------------------------------------------------------------
1 | package etcd
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "melody/config"
7 | "time"
8 | )
9 |
10 | // Namespace is the key to use to store and access the custom config data
11 | const Namespace = "melody_etcd"
12 |
13 | var (
14 | // ErrNoConfig is the error to be returned when there is no config with the etcd namespace
15 | ErrNoConfig = fmt.Errorf("unable to create the etcd client: no config")
16 | // ErrBadConfig is the error to be returned when the config is not well defined
17 | ErrBadConfig = fmt.Errorf("unable to create the etcd client with the received config")
18 | // ErrNoMachines is the error to be returned when the config has not defined one or more servers
19 | ErrNoMachines = fmt.Errorf("unable to create the etcd client without a set of servers")
20 | )
21 |
22 | // New creates an etcd client with the config extracted from the extra config param
23 | func New(ctx context.Context, e config.ExtraConfig) (Client, error) {
24 | v, ok := e[Namespace]
25 | if !ok {
26 | return nil, ErrNoConfig
27 | }
28 | tmp, ok := v.(map[string]interface{})
29 | if !ok {
30 | return nil, ErrBadConfig
31 | }
32 | machines, err := parseMachines(tmp)
33 | if err != nil {
34 | return nil, err
35 | }
36 | etcdClient, err := NewClient(ctx, machines, parseOptions(tmp))
37 |
38 | return etcdClient, err
39 | }
40 |
41 | func parseMachines(cfg map[string]interface{}) ([]string, error) {
42 | result := []string{}
43 | machines, ok := cfg["machines"]
44 | if !ok {
45 | return result, ErrNoMachines
46 | }
47 | ms, ok := machines.([]interface{})
48 | if !ok {
49 | return result, ErrNoMachines
50 | }
51 | for _, m := range ms {
52 | if machine, ok := m.(string); ok {
53 | result = append(result, machine)
54 | }
55 | }
56 | if len(result) == 0 {
57 | return result, ErrNoMachines
58 | }
59 | return result, nil
60 | }
61 |
62 | func parseOptions(cfg map[string]interface{}) ClientOptions {
63 | options := ClientOptions{}
64 | v, ok := cfg["options"]
65 | if !ok {
66 | return options
67 | }
68 | tmp := v.(map[string]interface{})
69 |
70 | if o, ok := tmp["cert"]; ok {
71 | options.Cert = o.(string)
72 | }
73 |
74 | if o, ok := tmp["key"]; ok {
75 | options.Key = o.(string)
76 | }
77 |
78 | if o, ok := tmp["cacert"]; ok {
79 | options.CACert = o.(string)
80 | }
81 |
82 | if o, ok := tmp["dial_timeout"]; ok {
83 | if d, err := parseDuration(o); err == nil {
84 | options.DialTimeout = d
85 | }
86 | }
87 |
88 | if o, ok := tmp["dial_keepalive"]; ok {
89 | if d, err := parseDuration(o); err == nil {
90 | options.DialKeepAlive = d
91 | }
92 | }
93 |
94 | return options
95 | }
96 |
97 | func parseDuration(v interface{}) (time.Duration, error) {
98 | s, ok := v.(string)
99 | if !ok {
100 | return 0, fmt.Errorf("unable to parse %v as a time.Duration\n", v)
101 | }
102 | return time.ParseDuration(s)
103 | }
104 |
--------------------------------------------------------------------------------
/middleware/melody-etcd/subscriber.go:
--------------------------------------------------------------------------------
1 | package etcd
2 |
3 | import (
4 | "context"
5 | "errors"
6 | "melody/config"
7 | "melody/sd"
8 | "sync"
9 | )
10 |
11 | var (
12 | subscribers = map[string]sd.Subscriber{}
13 | subscribersMutex = &sync.Mutex{}
14 | fallbackSubscriberFactory = sd.FixedSubscriberFactory
15 | NoClientError = errors.New("errors: no SD client")
16 | )
17 |
18 | // SubscriberFactory builds a an etcd subscriber SubscriberFactory with the received etcd client
19 | func SubscriberFactory(ctx context.Context, c Client) sd.SubscriberFactory {
20 | return func(cfg *config.Backend) sd.Subscriber {
21 | if len(cfg.Host) == 0 {
22 | return fallbackSubscriberFactory(cfg)
23 | }
24 | subscribersMutex.Lock()
25 | defer subscribersMutex.Unlock()
26 | // 为什么只拿 backend 中的第一个 host
27 | if sf, ok := subscribers[cfg.Host[0]]; ok {
28 | return sf
29 | }
30 | sf, err := NewSubscriber(ctx, c, cfg.Host[0])
31 | if err != nil {
32 | return fallbackSubscriberFactory(cfg)
33 | }
34 | subscribers[cfg.Host[0]] = sf
35 | return sf
36 | }
37 | }
38 |
39 | // Code taken from https://github.com/go-kit/kit/blob/master/sd/etcd/instancer.go
40 |
41 | // Subscriber keeps instances stored in a certain etcd keyspace cached in a fixed subscriber. Any kind of
42 | // change in that keyspace is watched and will update the Subscriber's list of hosts.
43 | type Subscriber struct {
44 | cache *sd.FixedSubscriber
45 | mutex *sync.RWMutex
46 | client Client
47 | prefix string
48 | ctx context.Context
49 | }
50 |
51 | // NewSubscriber returns an etcd subscriber. It will start watching the given
52 | // prefix for changes, and update the subscribers.
53 | func NewSubscriber(ctx context.Context, c Client, prefix string) (*Subscriber, error) {
54 | s := &Subscriber{
55 | client: c,
56 | prefix: prefix,
57 | cache: &sd.FixedSubscriber{},
58 | ctx: ctx,
59 | mutex: &sync.RWMutex{},
60 | }
61 |
62 | if s.client == nil {
63 | return nil, NoClientError
64 | }
65 |
66 | // 获取 key 为 s.prefix 的所有value
67 | instances, err := s.client.GetEntries(s.prefix)
68 | if err != nil {
69 | return nil, err
70 | }
71 | // 更新缓存中的 value
72 | *(s.cache) = sd.FixedSubscriber(instances)
73 |
74 | go s.loop()
75 |
76 | return s, nil
77 | }
78 |
79 | // Hosts implements the subscriber interface
80 | func (s Subscriber) Hosts() ([]string, error) {
81 | s.mutex.RLock()
82 | defer s.mutex.RUnlock()
83 | return s.cache.Hosts()
84 | }
85 |
86 | func (s *Subscriber) loop() {
87 | ch := make(chan struct{})
88 | // 监听 key 为 s.prefix 的 value,一旦 value 改变,则给 ch 这个 chan 发送消息,引起缓存更新
89 | go s.client.WatchPrefix(s.prefix, ch)
90 | for {
91 | select {
92 | case <-ch:
93 | instances, err := s.client.GetEntries(s.prefix)
94 | if err != nil {
95 | continue
96 | }
97 | s.mutex.Lock()
98 | *(s.cache) = sd.FixedSubscriber(instances)
99 | s.mutex.Unlock()
100 |
101 | case <-s.ctx.Done():
102 | return
103 | }
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/middleware/melody-gelf/log.go:
--------------------------------------------------------------------------------
1 | package gelf
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "gopkg.in/Graylog2/go-gelf.v2/gelf"
7 | "io"
8 | "melody/config"
9 | )
10 |
11 | const Namespace = "melody_gelf"
12 |
13 | var (
14 | tcpWriter = gelf.NewTCPWriter
15 | udpWriter = gelf.NewUDPWriter
16 | ErrorConfig = fmt.Errorf("not found extra config about melody-gelf module")
17 | ErrorMissAddress = errors.New("miss gelf address to send log")
18 | )
19 |
20 | type Config struct {
21 | Addr string
22 | TCPEnable bool
23 | }
24 |
25 | func NewWriter(cfg config.ExtraConfig) (io.Writer, error) {
26 | g, ok := GetConfig(cfg).(Config)
27 | if !ok {
28 | return nil, ErrorConfig
29 | }
30 |
31 | if g.Addr == "" {
32 | return nil, ErrorMissAddress
33 | }
34 |
35 | if g.TCPEnable {
36 | return tcpWriter(g.Addr)
37 | }
38 |
39 | return udpWriter(g.Addr)
40 | }
41 |
42 | func GetConfig(cfg config.ExtraConfig) interface{} {
43 | v, ok := cfg[Namespace]
44 |
45 | if !ok {
46 | return nil
47 | }
48 |
49 | t, ok := v.(map[string]interface{})
50 | if !ok {
51 | return nil
52 | }
53 |
54 | addr, ok := t["addr"]
55 | if !ok {
56 | return nil
57 | }
58 |
59 | enable, ok := t["tcp_enable"]
60 | if !ok {
61 | return nil
62 | }
63 |
64 | return Config{
65 | Addr: addr.(string),
66 | TCPEnable: enable.(bool),
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/middleware/melody-httpsecure/gin/httpsecure.go:
--------------------------------------------------------------------------------
1 | package gin
2 |
3 | import (
4 | "errors"
5 | "melody/config"
6 | httpsecure "melody/middleware/melody-httpsecure"
7 |
8 | "github.com/gin-gonic/gin"
9 | "github.com/unrolled/secure"
10 | )
11 |
12 | var (
13 | errorNoSecureConfig = errors.New("no extra config for http secure middleware")
14 | )
15 |
16 | // Register http security middleware in engine
17 | func Register(c config.ExtraConfig, engine *gin.Engine) error {
18 | v, ok := httpsecure.GetConfig(c).(secure.Options)
19 | if !ok {
20 | return errorNoSecureConfig
21 | }
22 |
23 | engine.Use(secureMw(v))
24 |
25 | return nil
26 | }
27 |
28 | func secureMw(o secure.Options) gin.HandlerFunc {
29 | secureMiddle := secure.New(o)
30 | return func(c *gin.Context) {
31 | err := secureMiddle.Process(c.Writer, c.Request)
32 | if err != nil {
33 | c.Abort()
34 | return
35 | }
36 |
37 | if status := c.Writer.Status(); status > 300 && status < 399 {
38 | c.Abort()
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/middleware/melody-httpsecure/gin/httpsecure_test.go:
--------------------------------------------------------------------------------
1 | package gin
2 |
3 | import (
4 | "melody/config"
5 | httpsecure "melody/middleware/melody-httpsecure"
6 | "net/http"
7 | "net/http/httptest"
8 | "testing"
9 |
10 | "github.com/gin-gonic/gin"
11 | )
12 |
13 | func TestRegister(t *testing.T) {
14 | gin.SetMode(gin.TestMode)
15 | engine := gin.New()
16 | cfg := config.ExtraConfig{
17 | httpsecure.Namespace: map[string]interface{}{
18 | "allowed_hosts": []interface{}{"host1", "subdomain1.host2", "subdomain2.host2"},
19 | },
20 | }
21 | if err := Register(cfg, engine); err != nil {
22 | t.Error(err)
23 | return
24 | }
25 | engine.GET("/should_access", func(c *gin.Context) {
26 | c.JSON(200, gin.H{"status": "ok"})
27 | })
28 | engine.GET("/never_access", func(c *gin.Context) {
29 | t.Error("unexpected request!", c.Request.URL.String())
30 | c.JSON(418, gin.H{"status": "ko"})
31 | })
32 | engine.GET("/no_headers", func(c *gin.Context) {
33 | c.Redirect(http.StatusPermanentRedirect, "/target")
34 | })
35 |
36 | for status, URLs := range map[int][]string{
37 | http.StatusOK: {
38 | "http://host1/should_access",
39 | "https://host1/should_access",
40 | "http://subdomain1.host2/should_access",
41 | "https://subdomain2.host2/should_access",
42 | },
43 | http.StatusInternalServerError: {
44 | "http://unknown/never_access",
45 | "https://subdomain.host1/never_access",
46 | "http://host2/never_access",
47 | "https://subdomain3.host2/never_access",
48 | },
49 | } {
50 | for _, URL := range URLs {
51 | w := httptest.NewRecorder()
52 | req, _ := http.NewRequest("GET", URL, nil)
53 | engine.ServeHTTP(w, req)
54 | if w.Result().StatusCode != status {
55 | t.Errorf("request %s unexpected status code! want %d, have %d\n", URL, status, w.Result().StatusCode)
56 | }
57 | }
58 | }
59 |
60 | URL := "https://host1/no_headers"
61 | w := httptest.NewRecorder()
62 | req, _ := http.NewRequest("GET", URL, nil)
63 | engine.ServeHTTP(w, req)
64 | if w.Result().StatusCode != http.StatusPermanentRedirect {
65 | t.Errorf("request %s unexpected status code! want %d, have %d\n", URL, http.StatusPermanentRedirect, w.Result().StatusCode)
66 | }
67 | if w.Result().Header.Get("Location") != "/target" {
68 | t.Error("unexpected value for the location header:", w.Result().Header.Get("Location"))
69 | }
70 | if len(w.Result().Header) != 2 {
71 | t.Error("unexpected number of headers:", len(w.Result().Header), w.Result().Header)
72 | }
73 | }
74 |
75 | func TestRegister_ko(t *testing.T) {
76 | err := Register(config.ExtraConfig{}, nil)
77 | if err != errorNoSecureConfig {
78 | t.Error("expecting errNoConfig. got:", err)
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/middleware/melody-httpsecure/httpsecure.go:
--------------------------------------------------------------------------------
1 | package httpsecure
2 |
3 | import (
4 | "melody/config"
5 |
6 | "github.com/unrolled/secure"
7 | )
8 |
9 | // Namespace key of http secure
10 | const Namespace = "melody_httpsecure"
11 |
12 | // GetConfig return a httpsecure config interface
13 | func GetConfig(e config.ExtraConfig) interface{} {
14 | v, ok := e[Namespace]
15 | if !ok {
16 | return nil
17 | }
18 |
19 | t, ok := v.(map[string]interface{})
20 | if !ok {
21 | return nil
22 | }
23 |
24 | c := secure.Options{}
25 |
26 | setInt64(t, "sts_seconds", &c.STSSeconds)
27 |
28 | setStrings(t, "allowed_hosts", &c.AllowedHosts)
29 | setStrings(t, "host_proxy_headers", &c.HostsProxyHeaders)
30 |
31 | setString(t, "custom_frame_option_value", &c.CustomFrameOptionsValue)
32 | setString(t, "content_security_policy", &c.ContentSecurityPolicy)
33 | setString(t, "public_key", &c.PublicKey)
34 | setString(t, "ssl_host", &c.SSLHost)
35 | setString(t, "referrer_policy", &c.ReferrerPolicy)
36 |
37 | setBool(t, "content_type_nosniff", &c.ContentTypeNosniff)
38 | setBool(t, "browser_xss_filter", &c.BrowserXssFilter)
39 | setBool(t, "is_development", &c.IsDevelopment)
40 | setBool(t, "sts_include_subdomains", &c.STSIncludeSubdomains)
41 | setBool(t, "frame_deny", &c.FrameDeny)
42 | setBool(t, "ssl_redirect", &c.SSLRedirect)
43 |
44 | return c
45 | }
46 |
47 | func setStrings(t map[string]interface{}, key string, s *[]string) {
48 | if v, ok := t[key]; ok {
49 | var result []string
50 | for _, s := range v.([]interface{}) {
51 | if str, ok := s.(string); ok {
52 | result = append(result, str)
53 | }
54 | }
55 | *s = result
56 | }
57 | }
58 |
59 | func setString(t map[string]interface{}, key string, s *string) {
60 | if v, ok := t[key]; ok {
61 | if str, ok := v.(string); ok {
62 | *s = str
63 | }
64 | }
65 | }
66 |
67 | func setBool(t map[string]interface{}, key string, b *bool) {
68 | if v, ok := t[key]; ok {
69 | if str, ok := v.(bool); ok {
70 | *b = str
71 | }
72 | }
73 | }
74 |
75 | func setInt64(t map[string]interface{}, key string, i *int64) {
76 | if v, ok := t[key]; ok {
77 | switch a := v.(type) {
78 | case int64:
79 | *i = a
80 | case int:
81 | *i = int64(a)
82 | case float64:
83 | *i = int64(a)
84 | }
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/middleware/melody-httpsecure/httpsecure_test.go:
--------------------------------------------------------------------------------
1 | package httpsecure
2 |
3 | import (
4 | "testing"
5 | )
6 |
7 | func Test_SetStrings(t *testing.T) {
8 | m := map[string]interface{}{"hosts": []interface{}{"127.0.0.1", "192.168.1.1"}}
9 | a := []string{}
10 | setStrings(m, "hosts", &a)
11 | if len(a) != 2 || a[0] != "127.0.0.1" {
12 | t.Error("set string array error", a)
13 | }
14 | }
15 |
16 | func Test_SetString(t *testing.T) {
17 | m := map[string]interface{}{"header": "yes"}
18 | var a string
19 | setString(m, "header", &a)
20 | if a != "yes" {
21 | t.Error("set string error", a)
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/middleware/melody-influxdb/buffer.go:
--------------------------------------------------------------------------------
1 | package influxdb
2 |
3 | import "github.com/influxdata/influxdb/client/v2"
4 |
5 | func NewBuffer(size int) *Buffer {
6 | return &Buffer{
7 | data: []client.BatchPoints{},
8 | size: size,
9 | }
10 | }
11 |
12 | type Buffer struct {
13 | data []client.BatchPoints
14 | size int
15 | }
16 |
17 | func (b *Buffer) Add(ps ...client.BatchPoints) {
18 | b.data = append(b.data, ps...)
19 | if len(b.data) > b.size {
20 | b.data = b.data[len(b.data)-b.size:]
21 | }
22 | }
23 |
24 | func (b *Buffer) Elements() []client.BatchPoints {
25 | var res []client.BatchPoints
26 | res, b.data = b.data, []client.BatchPoints{}
27 | return res
28 | }
29 |
--------------------------------------------------------------------------------
/middleware/melody-influxdb/config.go:
--------------------------------------------------------------------------------
1 | package influxdb
2 |
3 | import (
4 | "errors"
5 | "melody/config"
6 | "time"
7 | )
8 |
9 | const (
10 | Namespace = "melody_influxdb"
11 | defaultDB = "melody_data"
12 | defaultAddress = "*:8086"
13 | dataServerDefaultListenPort = ":8001"
14 | dataServerDefaultWebSocketPort = ":8002"
15 | )
16 |
17 | var configErr = errors.New("load influx config error")
18 |
19 | // influxdbConfig 描述metrics输出的influxDB的信息
20 | type influxdbConfig struct {
21 | address string
22 | username string
23 | password string
24 | ttl time.Duration
25 | db string
26 | bufferSize int
27 | timeout time.Duration
28 | dataServerEnable bool
29 | dataServerPort string
30 | dataServerQueryEnable bool
31 | }
32 |
33 | func getConfig(config config.ExtraConfig) interface{} {
34 | v, ok := config[Namespace]
35 | if !ok {
36 | return nil
37 | }
38 |
39 | mapStruct, ok := v.(map[string]interface{})
40 | if !ok {
41 | return nil
42 | }
43 |
44 | influx := influxdbConfig{}
45 |
46 | if value, ok := mapStruct["address"]; ok {
47 | influx.address = value.(string)
48 | } else {
49 | influx.address = defaultAddress
50 | }
51 |
52 | if value, ok := mapStruct["username"]; ok {
53 | influx.username = value.(string)
54 | }
55 |
56 | if value, ok := mapStruct["password"]; ok {
57 | influx.password = value.(string)
58 | }
59 |
60 | if value, ok := mapStruct["data_server_enable"]; ok {
61 | influx.dataServerEnable = value.(bool)
62 | }
63 |
64 | if value, ok := mapStruct["data_server_query_enable"]; ok {
65 | influx.dataServerQueryEnable = value.(bool)
66 | }
67 |
68 | if value, ok := mapStruct["data_server_port"]; ok || value != "" {
69 | influx.dataServerPort = value.(string)
70 | } else {
71 | influx.dataServerPort = dataServerDefaultListenPort
72 | }
73 |
74 | if value, ok := mapStruct["db"]; ok {
75 | influx.db = value.(string)
76 | } else {
77 | influx.db = defaultDB
78 | }
79 |
80 | if size, ok := mapStruct["buffer_size"]; ok {
81 | if s, ok := size.(int); ok {
82 | influx.bufferSize = s
83 | }
84 | }
85 |
86 | if ttl, ok := mapStruct["ttl"]; ok {
87 | t, ok := ttl.(string)
88 | if !ok {
89 | return nil
90 | }
91 | var err error
92 | influx.ttl, err = time.ParseDuration(t)
93 | if err != nil {
94 | return nil
95 | }
96 | }
97 |
98 | if timeout, ok := mapStruct["time_out"]; ok {
99 | t, ok := timeout.(string)
100 | if !ok {
101 | return nil
102 | }
103 | var err error
104 | influx.timeout, err = time.ParseDuration(t)
105 | if err != nil {
106 | return nil
107 | }
108 | }
109 |
110 | return influx
111 | }
112 |
--------------------------------------------------------------------------------
/middleware/melody-influxdb/gauge/gauge.go:
--------------------------------------------------------------------------------
1 | package gauge
2 |
3 | import (
4 | "melody/logging"
5 | alert "melody/middleware/melody-alert"
6 | "time"
7 |
8 | "github.com/influxdata/influxdb/client/v2"
9 | )
10 |
11 | func Points(hostname string, now time.Time, counters map[string]int64, logger logging.Logger, checker alert.Checker) []*client.Point {
12 | res := make([]*client.Point, 4)
13 |
14 | in := map[string]interface{}{
15 | "gauge": int(counters["melody.router.connected-gauge"]),
16 | }
17 | incoming, err := client.NewPoint("router", map[string]string{"host": hostname, "direction": "in"}, in, now)
18 | if err != nil {
19 | logger.Error("creating incoming connection counters point:", err.Error())
20 | return res
21 | }
22 | res[0] = incoming
23 |
24 | out := map[string]interface{}{
25 | "gauge": int(counters["melody.router.disconnected-gauge"]),
26 | }
27 | outgoing, err := client.NewPoint("router", map[string]string{"host": hostname, "direction": "out"}, out, now)
28 | if err != nil {
29 | logger.Error("creating outgoing connection counters point:", err.Error())
30 | return res
31 | }
32 | res[1] = outgoing
33 |
34 | debug := map[string]interface{}{}
35 | runtime := map[string]interface{}{}
36 |
37 | for k, v := range counters {
38 | if k == "melody.router.connected-gauge" || k == "melody.router.disconnected-gauge" {
39 | continue
40 | }
41 | if k[:21] == "melody.service.debug." {
42 | debug[k[21:]] = int(v)
43 | continue
44 | }
45 | if k[:23] == "melody.service.runtime." {
46 | runtime[k[23:]] = int(v)
47 | continue
48 | }
49 | logger.Debug("unknown gauge key:", k)
50 | }
51 |
52 | debugPoint, err := client.NewPoint("debug", map[string]string{"host": hostname}, debug, now)
53 | if err != nil {
54 | logger.Error("creating debug counters point:", err.Error())
55 | return res
56 | }
57 | res[2] = debugPoint
58 |
59 | runtimePoint, err := client.NewPoint("runtime", map[string]string{"host": hostname}, runtime, now)
60 | if err != nil {
61 | logger.Error("creating runtime counters point:", err.Error())
62 | return res
63 | }
64 | res[3] = runtimePoint
65 |
66 | _ = checker("", "numgc", int64(debug["GCStats.NumGC"].(int)))
67 | _ = checker("", "numgoroutine", int64(runtime["NumGoroutine"].(int)))
68 | _ = checker("", "sys", int64(runtime["MemStats.Sys"].(int)))
69 | _ = checker("", "heapsys", int64(runtime["MemStats.HeapSys"].(int)))
70 | _ = checker("", "stacksys", int64(runtime["MemStats.StackSys"].(int)))
71 | _ = checker("", "mcachesys", int64(runtime["MemStats.MCacheSys"].(int)))
72 | _ = checker("", "mspansys", int64(runtime["MemStats.MSpanSys"].(int)))
73 |
74 | return res
75 | }
76 |
--------------------------------------------------------------------------------
/middleware/melody-influxdb/middleware/cors.go:
--------------------------------------------------------------------------------
1 | package middleware
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 | "net/http"
6 | )
7 |
8 | func Cors() gin.HandlerFunc {
9 | return func(c *gin.Context) {
10 | method := c.Request.Method
11 | c.Header("Access-Control-Allow-Origin", "*")
12 | c.Header("Access-Control-Allow-Methods", "*")
13 | c.Header("Access-Control-Allow-Headers", "Content-Type,AccessToken,X-CSRF-Token, Authorization, Token")
14 | c.Header("Access-Control-Allow-Credentials", "true")
15 |
16 | if method == "OPTIONS" {
17 | c.AbortWithStatus(http.StatusNoContent)
18 | }
19 | c.Next()
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/middleware/melody-influxdb/refresh/refresh.go:
--------------------------------------------------------------------------------
1 | package refresh
2 |
3 | import "sync"
4 |
5 | var RefreshList = NewRefreshList()
6 |
7 | type List struct {
8 | Head *Refresh
9 | Tail *Refresh
10 | Size int
11 | lock sync.Mutex
12 | }
13 |
14 | type Refresh struct {
15 | Value *chan int
16 | Next *Refresh
17 | Pre *Refresh
18 | }
19 |
20 | func NewRefreshList() *List {
21 | head := &Refresh{}
22 | tail := &Refresh{
23 | Pre: head,
24 | }
25 | head.Next = tail
26 | return &List{
27 | Head: head,
28 | Tail: tail,
29 | }
30 | }
31 |
32 | func (this *List) Add(node *Refresh) {
33 | this.lock.Lock()
34 | node.Pre, node.Next = this.Tail.Pre, this.Tail
35 | node.Pre.Next, node.Next.Pre = node, node
36 | this.Size++
37 | this.lock.Unlock()
38 | }
39 |
40 | func (this *List) Remove(node *Refresh) {
41 | this.lock.Lock()
42 | node.Pre.Next, node.Next.Pre = node.Next, node.Pre
43 | this.Size--
44 | this.lock.Unlock()
45 | }
46 |
47 | func (this *List) Front() *Refresh {
48 | return this.Head.Next
49 | }
50 |
--------------------------------------------------------------------------------
/middleware/melody-influxdb/response/response.go:
--------------------------------------------------------------------------------
1 | package response
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 | "net/http"
6 | )
7 |
8 | type Response struct {
9 | Code int `json:"code"`
10 | Message string `json:"message"`
11 | Data interface{} `json:"data"`
12 | }
13 |
14 | func Ok(c *gin.Context, code int, message string, data interface{}) {
15 | c.JSON(http.StatusOK, Response{
16 | Code: code,
17 | Message: message,
18 | Data: data,
19 | })
20 | }
21 |
22 | func Bad(c *gin.Context, message string, data interface{}) {
23 | c.JSON(http.StatusBadRequest, Response{
24 | Code: http.StatusBadRequest,
25 | Message: message,
26 | Data: data,
27 | })
28 | }
29 |
30 | func NotFound(c *gin.Context, message string, data interface{}) {
31 | c.JSON(http.StatusNotFound, Response{
32 | Code: http.StatusNotFound,
33 | Message: message,
34 | Data: data,
35 | })
36 | }
37 |
--------------------------------------------------------------------------------
/middleware/melody-influxdb/ws/alert_handler.go:
--------------------------------------------------------------------------------
1 | package ws
2 |
3 | import (
4 | "melody/middleware/melody-alert/model"
5 | "net/http"
6 | "strconv"
7 | )
8 |
9 | func (wsc WebSocketClient) GetWarnings() http.HandlerFunc {
10 | return wsc.WebSocketHandler(func(request *http.Request, data map[string]interface{}) (i interface{}, err error) {
11 | pageIndex := 0
12 | if tmp, ok := data["message"]; ok {
13 | if tmp2, ok := tmp.(string); ok {
14 | atoi, _ := strconv.Atoi(tmp2)
15 | pageIndex = atoi - 1
16 | }
17 | }
18 |
19 | total := len(model.WarningList.Warnings)
20 |
21 | start := total - (pageIndex+1)*10
22 | end := total - pageIndex*10
23 |
24 | if start < 0 {
25 | start = 0
26 | }
27 |
28 | result := make([]model.Warning, 0)
29 |
30 | for i := end - 1; i >= start; i-- {
31 | result = append(result, model.WarningList.Warnings[i])
32 | }
33 |
34 | return map[string]interface{}{
35 | "warnings": result,
36 | "total": len(result),
37 | }, nil
38 | })
39 | }
40 |
41 | func (wsc WebSocketClient) WarningsWatch() http.HandlerFunc {
42 | return wsc.WebSocketWatchHandler(func(request *http.Request, data map[string]interface{}) (i interface{}, err error) {
43 | warning := data["warning"]
44 | return map[string]interface{}{
45 | "warning": warning,
46 | }, nil
47 | })
48 | }
49 |
--------------------------------------------------------------------------------
/middleware/melody-influxdb/ws/convert/convert.go:
--------------------------------------------------------------------------------
1 | package convert
2 |
3 | import (
4 | "encoding/json"
5 | "time"
6 | )
7 |
8 | func ObjectToStringTime(v interface{}, format string) (string, bool) {
9 | if n, ok := v.(json.Number); ok {
10 | if ti, err := n.Int64(); err == nil {
11 | t := time.Unix(ti, 0).Format(format)
12 | return t, true
13 | }
14 | }
15 | return "", false
16 | }
17 |
18 | func ObjectToInt(v interface{}) (int64, bool) {
19 | if n, ok := v.(json.Number); ok {
20 | if ti, err := n.Int64(); err == nil {
21 | return ti, true
22 | }
23 | }
24 | return 0, false
25 | }
26 |
27 | func ObjectToFloat(v interface{}) (float64, bool) {
28 | if n, ok := v.(json.Number); ok {
29 | if ti, err := n.Float64(); err == nil {
30 | return ti, true
31 | }
32 | }
33 | return 0, false
34 | }
35 |
--------------------------------------------------------------------------------
/middleware/melody-influxdb/ws/debug_handler.go:
--------------------------------------------------------------------------------
1 | package ws
2 |
3 | import (
4 | "errors"
5 | "melody/middleware/melody-influxdb/ws/convert"
6 | "melody/middleware/melody-influxdb/ws/handler"
7 | "net/http"
8 | )
9 |
10 | func (wsc WebSocketClient) GetDebugNumGC() http.HandlerFunc {
11 | return wsc.WebSocketHandler(func(request *http.Request, data map[string]interface{}) (interface{}, error) {
12 | cmd := wsc.generateCommand(`SELECT sum("GCStats.NumGC")
13 | AS "GCStats.NumGC" FROM "%s"."autogen"."debug" WHERE time > %s - %s AND time <
14 | %s GROUP BY time(%s) FILL(null)`)
15 | resu, err := wsc.executeQuery(cmd)
16 | if err != nil {
17 | return nil, err
18 | }
19 | result := resu[0]
20 | if result.Err != "" {
21 | return nil, errors.New(result.Err)
22 | }
23 | values := result.Series[0].Values
24 | var times []string
25 | var debugNumGC []float64
26 | for _, v := range values {
27 | // time
28 | if t, ok := convert.ObjectToStringTime(v[0], GetTimeFormat()); ok {
29 | times = append(times, t)
30 | } else {
31 | continue
32 | }
33 | // value
34 | if f, ok := convert.ObjectToFloat(v[1]); ok {
35 | debugNumGC = append(debugNumGC, f)
36 | } else {
37 | debugNumGC = append(debugNumGC, 0)
38 | }
39 | }
40 |
41 | return map[string]interface{}{
42 | "title": "debug.NumGC",
43 | "times": times,
44 | "series": []map[string]interface{}{
45 | {
46 | "data": debugNumGC,
47 | "name": "GCNum",
48 | "type": "line",
49 | "smooth": true,
50 | },
51 | },
52 | }, nil
53 | })
54 | }
55 |
56 | func (wsc WebSocketClient) GetDebugAlloc() http.HandlerFunc {
57 | return wsc.WebSocketHandler(func(request *http.Request, data map[string]interface{}) (interface{}, error) {
58 | cmd := wsc.generateCommand(`
59 | SELECT
60 | sum("MemStats.Alloc") AS "sum_MemStats.Alloc"
61 | FROM
62 | "%s"."autogen"."runtime"
63 | WHERE
64 | time > %s - %s AND time < %s
65 | GROUP BY
66 | time(%s) FILL(null)
67 | `)
68 | resu, err := wsc.executeQuery(cmd)
69 | if err != nil {
70 | return nil, err
71 | }
72 | result := resu[0]
73 | if result.Err != "" {
74 | return nil, errors.New(result.Err)
75 | }
76 | values := result.Series[0].Values
77 | var times []string
78 | var alloc []int64
79 | handler.ResultDataHandler(×, values, GetTimeFormat(), &alloc)
80 | return map[string]interface{}{
81 | "title": "debug.Alloc",
82 | "times": times,
83 | "series": []map[string]interface{}{
84 | {
85 | "data": alloc,
86 | "name": "alloc",
87 | "type": "line",
88 | "areaStyle": map[string]interface{}{},
89 | },
90 | },
91 | }, nil
92 | })
93 | }
94 |
--------------------------------------------------------------------------------
/middleware/melody-influxdb/ws/handler/chart.go:
--------------------------------------------------------------------------------
1 | package handler
2 |
3 | import (
4 | "encoding/json"
5 | "melody/middleware/melody-influxdb/ws/convert"
6 | )
7 |
8 | /**
9 | 将InfluxDB返回的结果集映射到数组中
10 | times: 时间数组
11 | data: InfluxDB返回的结果集
12 | format: 时间的格式
13 | lines: 其他数据数组 (一个或以上)
14 | */
15 | func ResultDataHandler(times *[]string, data [][]interface{}, format string, lines ...*[]int64) {
16 | for _, v := range data { // v:[ time , data1 ,data2]
17 | if t, ok := convert.ObjectToStringTime(v[0], format); ok {
18 | *times = append(*times, t)
19 | }
20 | for index := range lines {
21 | if g, ok := convert.ObjectToInt(v[index+1]); ok {
22 | *lines[index] = append(*lines[index], g)
23 | } else {
24 | *lines[index] = append(*lines[index], 0)
25 | }
26 | }
27 | }
28 | }
29 |
30 | func ResultSingleDataHandler(times *[]string, data [][]interface{}, format string, singleField *int64) {
31 | for _, v := range data {
32 | if t, ok := convert.ObjectToStringTime(v[0], format); ok {
33 | *times = append(*times, t)
34 | }
35 | if vv, ok := v[1].(json.Number); ok {
36 | *singleField, _ = vv.Int64()
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/middleware/melody-influxdb/ws/query.go:
--------------------------------------------------------------------------------
1 | package ws
2 |
3 | import (
4 | "errors"
5 | "github.com/influxdata/influxdb/client/v2"
6 | )
7 |
8 | func (wsc WebSocketClient) executeQuery(cmd string) ([]client.Result, error) {
9 | resp, err := wsc.Client.Query(client.NewQuery(cmd, wsc.DB, "s"))
10 | if err != nil {
11 | return nil, err
12 | }
13 | if resp == nil {
14 | return nil, errors.New("error: influx query no resp")
15 | }
16 | if resp.Err != "" {
17 | return nil, errors.New(resp.Err)
18 | }
19 | if len(resp.Results) == 0 {
20 | return nil, errors.New("error: influx query no resp")
21 | }
22 | return resp.Results, nil
23 | }
24 |
25 | func NormalExecuteQuery(c client.Client, cmd, db string) ([]client.Result, error) {
26 | resp, err := c.Query(client.NewQuery(cmd, db, "s"))
27 | if err != nil {
28 | return nil, err
29 | }
30 | if resp == nil {
31 | return nil, errors.New("error: influx query no resp")
32 | }
33 | if resp.Err != "" {
34 | return nil, errors.New(resp.Err)
35 | }
36 | if len(resp.Results) == 0 {
37 | return nil, errors.New("error: influx query no resp")
38 | }
39 | return resp.Results, nil
40 | }
41 |
--------------------------------------------------------------------------------
/middleware/melody-influxdb/ws/struct.go:
--------------------------------------------------------------------------------
1 | package ws
2 |
3 | import (
4 | "github.com/gorilla/websocket"
5 | "github.com/influxdata/influxdb/client/v2"
6 | "melody/config"
7 | "melody/logging"
8 | "net/http"
9 | )
10 |
11 | type WebSocketClient struct {
12 | Client client.Client
13 | Upgrader websocket.Upgrader
14 | Logger logging.Logger
15 | DB string
16 | Cfg *config.ServiceConfig
17 | }
18 |
19 | func (wsc WebSocketClient) RegisterHandleFunc() {
20 | http.HandleFunc("/debug/num/gc", wsc.GetDebugNumGC())
21 | http.HandleFunc("/debug/alloc", wsc.GetDebugAlloc())
22 |
23 | http.HandleFunc("/runtime/num/gc", wsc.GetNumGC())
24 | http.HandleFunc("/runtime/num/goroutine", wsc.GetNumGoroutine())
25 | http.HandleFunc("/runtime/num/frees", wsc.GetNumMemoryFree())
26 | http.HandleFunc("/runtime/num/memory", wsc.GetSysMemory())
27 |
28 | http.HandleFunc("/requests/complete", wsc.GetRequestsComplete())
29 | http.HandleFunc("/requests/error", wsc.GetRequestsError())
30 | http.HandleFunc("/requests/endpoints", wsc.GetRequestsEndpoints())
31 | http.HandleFunc("/requests/backends", wsc.GetRequestsBackends())
32 | http.HandleFunc("/requests/api", wsc.GetRequestsAPI())
33 | http.HandleFunc("/requests/endpoints/pie", wsc.GetRequestsEndpointsPie())
34 | http.HandleFunc("/requests/backends/pie", wsc.GetRequestsBackendsPie())
35 |
36 | http.HandleFunc("/router/direction", wsc.GetRouterDirection())
37 | http.HandleFunc("/router/size", wsc.GetRouterSize())
38 | http.HandleFunc("/router/time", wsc.GetRouterTime())
39 |
40 | http.HandleFunc("/warnings", wsc.GetWarnings())
41 | http.HandleFunc("/warnings/watch", wsc.WarningsWatch())
42 | }
43 |
--------------------------------------------------------------------------------
/middleware/melody-influxdb/ws/template.go:
--------------------------------------------------------------------------------
1 | package ws
2 |
3 | import (
4 | "fmt"
5 | "melody/config"
6 | "strings"
7 | )
8 |
9 | func (wsc WebSocketClient) generateCommand(cmd string) string {
10 | return fmt.Sprintf(cmd, wsc.DB, WsTimeControl.MinTime, WsTimeControl.TimeInterval, WsTimeControl.MaxTime, WsTimeControl.GroupTime)
11 | }
12 |
13 | func (wsc WebSocketClient) generateCommandWithSingle(cmd string) string {
14 | return fmt.Sprintf(cmd, wsc.DB, WsTimeControl.MinTime, WsTimeControl.TimeInterval, WsTimeControl.MaxTime)
15 | }
16 |
17 | func (wsc WebSocketClient) generateCommandWithEndpoints(cmd string, cfg *config.ServiceConfig) string {
18 | var endpointStr []string
19 | for i := range cfg.Endpoints {
20 | endpointStr = append(endpointStr, cfg.Endpoints[i].Endpoint)
21 | }
22 | builder := strings.Builder{}
23 | builder.WriteString("(")
24 | for i := 0; i < len(endpointStr); i++ {
25 | builder.WriteString(`"name"='`)
26 | builder.WriteString(endpointStr[i])
27 | if i == len(endpointStr)-1 {
28 | builder.WriteString("'")
29 | } else {
30 | builder.WriteString("' OR ")
31 | }
32 | }
33 | builder.WriteString(")")
34 | return fmt.Sprintf(
35 | cmd,
36 | wsc.DB,
37 | WsTimeControl.MinTime,
38 | WsTimeControl.TimeInterval,
39 | WsTimeControl.MaxTime,
40 | builder.String(),
41 | WsTimeControl.GroupTime,
42 | )
43 | }
44 |
--------------------------------------------------------------------------------
/middleware/melody-influxdb/ws/timer.go:
--------------------------------------------------------------------------------
1 | package ws
2 |
3 | import (
4 | "regexp"
5 | "time"
6 | )
7 |
8 | type TimeControl struct {
9 | MinTime string `json:"min_time"`
10 | MaxTime string `json:"max_time"`
11 | TimeInterval string `json:"time_interval"`
12 | GroupTime string `json:"group_time"`
13 | RefreshTime time.Duration
14 | RefreshParam string `json:"refresh_time"`
15 | }
16 |
17 | var WsTimeControl TimeControl
18 |
19 | func RegisterWSTimeControl() {
20 | WsTimeControl = TimeControl{
21 | MinTime: "now()",
22 | MaxTime: "now()",
23 | TimeInterval: "15m",
24 | GroupTime: "10s",
25 | RefreshTime: 10 * time.Second,
26 | }
27 | }
28 |
29 | func SetTimeControl(timer TimeControl) {
30 | WsTimeControl = timer
31 | }
32 |
33 | var r = regexp.MustCompile(`([0-9]*)([a-z])`)
34 |
35 | const (
36 | hour = "15:04"
37 | day = "01-02 15:04"
38 | )
39 |
40 | func GetTimeFormat() string {
41 | if WsTimeControl.TimeInterval != "" {
42 | match := r.FindAllStringSubmatch(WsTimeControl.TimeInterval, -1)
43 | m := match[0]
44 | if len(m) == 3 {
45 | unit := m[2]
46 | if unit == "d" {
47 | return day
48 | }
49 | }
50 | }
51 | return hour
52 | }
53 |
--------------------------------------------------------------------------------
/middleware/melody-jose/fixtures/jwks.json:
--------------------------------------------------------------------------------
1 | {
2 | "keys": [
3 | {
4 | "alg": "RS256",
5 | "kty": "RSA",
6 | "use": "sig",
7 | "n": "s3X0UDM3Us5ESCKIiUHK6kgWe4utsH4-NM4LuNOJK4puamtXQRqr786U2T1bhxhIxmRqsGaIOu1bPLUuha-vuqEnQTL4SC-1LzHYt5Q1booyk01jZsBCnhJiOyJ1Y20DKiPJK3g7Zk9Qa8ELhjlx4XDkkSxuQOnKSpqnz-qjgEivX4IdVaGgbtAMY9Wk68QiTYwEWNh8GGLWJY6iIX-6YZ5YISaFSdshouvuflPixRVqCugrnetgbiOEVT3onx2rnPKWqKWnW73J2anFReGubX57dyKY2rDiz00ME6uMH3aBMzh1TSdKvQN6gihVNE3937Mefp9Hn5DSI1oMe7PAEw",
8 | "e": "AQAB",
9 | "kid": "RjA2MTU1NjgyMzFDRDhGMTlGQkQ0RUM5MUJGMkQzQzBCNEYwNTczMQ",
10 | "x5t": "RjA2MTU1NjgyMzFDRDhGMTlGQkQ0RUM5MUJGMkQzQzBCNEYwNTczMQ",
11 | "x5c": [
12 | "MIIC+zCCAeOgAwIBAgIJK4SJPHkUYbLYMA0GCSqGSIb3DQEBCwUAMBsxGTAXBgNVBAMTEHhpYW9jaS5hdXRoMC5jb20wHhcNMjAwMzA2MDQ0NzUzWhcNMzMxMTEzMDQ0NzUzWjAbMRkwFwYDVQQDExB4aWFvY2kuYXV0aDAuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAs3X0UDM3Us5ESCKIiUHK6kgWe4utsH4+NM4LuNOJK4puamtXQRqr786U2T1bhxhIxmRqsGaIOu1bPLUuha+vuqEnQTL4SC+1LzHYt5Q1booyk01jZsBCnhJiOyJ1Y20DKiPJK3g7Zk9Qa8ELhjlx4XDkkSxuQOnKSpqnz+qjgEivX4IdVaGgbtAMY9Wk68QiTYwEWNh8GGLWJY6iIX+6YZ5YISaFSdshouvuflPixRVqCugrnetgbiOEVT3onx2rnPKWqKWnW73J2anFReGubX57dyKY2rDiz00ME6uMH3aBMzh1TSdKvQN6gihVNE3937Mefp9Hn5DSI1oMe7PAEwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQ6jdBLLAINDV7ToqWHoM6aFODQ3zAOBgNVHQ8BAf8EBAMCAoQwDQYJKoZIhvcNAQELBQADggEBAFyiSikrVsuiBnPTclnvymqGWRSI/FY7pdf37BaXhp9Ybtld+Lk8kESlEAnWdig5iMeg4T4vqPQCXcFHdLqFCAFm0RYzXzd+h5Bs3U6Qm6wpPXZJRyeZSY/T/qLUtaN7bzx5PO4HyvyNYGIydH4FekDTEKO8VQB+jUk/KBS4v4QjsPnmRtVH6yLpaOZ5NiIJqGJUVUSZ1cCjnbopGt7ySLRUBo4PON9NtGblZMpMO4orZ4uHRKaVuT5PuD1YP+3FWj/yfTB4+K62vIIWc9uCjQwRXsyLtSW+m/BbyeXOm+NzIrIWAOp0nZqIrAyje392+DYxVb+J2hz35Ec5g2Hvp3U="
13 | ]
14 | }
15 | ]
16 | }
--------------------------------------------------------------------------------
/middleware/melody-jose/fixtures/private.json:
--------------------------------------------------------------------------------
1 | {
2 | "keys": [
3 | {
4 | "kty":"EC",
5 | "crv":"P-256",
6 | "x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4",
7 | "y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM",
8 | "d":"870MB6gfuTJ4HtUnUvYMyJpr5eUZNP4Bk43bVdj3eAE",
9 | "use":"enc",
10 | "kid":"1"
11 | },
12 | {
13 | "kty":"RSA",
14 | "n":"0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw",
15 | "e":"AQAB",
16 | "d":"X4cTteJY_gn4FYPsXB8rdXix5vwsg1FLN5E3EaG6RJoVH-HLLKD9M7dx5oo7GURknchnrRweUkC7hT5fJLM0WbFAKNLWY2vv7B6NqXSzUvxT0_YSfqijwp3RTzlBaCxWp4doFk5N2o8Gy_nHNKroADIkJ46pRUohsXywbReAdYaMwFs9tv8d_cPVY3i07a3t8MN6TNwm0dSawm9v47UiCl3Sk5ZiG7xojPLu4sbg1U2jx4IBTNBznbJSzFHK66jT8bgkuqsk0GjskDJk19Z4qwjwbsnn4j2WBii3RL-Us2lGVkY8fkFzme1z0HbIkfz0Y6mqnOYtqc0X4jfcKoAC8Q",
17 | "p":"83i-7IvMGXoMXCskv73TKr8637FiO7Z27zv8oj6pbWUQyLPQBQxtPVnwD20R-60eTDmD2ujnMt5PoqMrm8RfmNhVWDtjjMmCMjOpSXicFHj7XOuVIYQyqVWlWEh6dN36GVZYk93N8Bc9vY41xy8B9RzzOGVQzXvNEvn7O0nVbfs",
18 | "q":"3dfOR9cuYq-0S-mkFLzgItgMEfFzB2q3hWehMuG0oCuqnb3vobLyumqjVZQO1dIrdwgTnCdpYzBcOfW5r370AFXjiWft_NGEiovonizhKpo9VVS78TzFgxkIdrecRezsZ-1kYd_s1qDbxtkDEgfAITAG9LUnADun4vIcb6yelxk",
19 | "dp":"G4sPXkc6Ya9y8oJW9_ILj4xuppu0lzi_H7VTkS8xj5SdX3coE0oimYwxIi2emTAue0UOa5dpgFGyBJ4c8tQ2VF402XRugKDTP8akYhFo5tAA77Qe_NmtuYZc3C3m3I24G2GvR5sSDxUyAN2zq8Lfn9EUms6rY3Ob8YeiKkTiBj0",
20 | "dq":"s9lAH9fggBsoFR8Oac2R_E2gw282rT2kGOAhvIllETE1efrA6huUUvMfBcMpn8lqeW6vzznYY5SSQF7pMdC_agI3nG8Ibp1BUb0JUiraRNqUfLhcQb_d9GF4Dh7e74WbRsobRonujTYN1xCaP6TO61jvWrX-L18txXw494Q_cgk",
21 | "qi":"GyM_p6JrXySiz1toFgKbWV-JdI3jQ4ypu9rbMWx3rQJBfmt0FoYzgUIZEVFEcOqwemRN81zoDAaa-Bk0KWNGDjJHZDdDmFhW3AN7lI-puxk_mHZGJ11rxyR8O55XLSe3SPmRfKwZI6yU24ZxvQKFYItdldUKGzO6Ia6zTKhAVRU",
22 | "alg":"RS256",
23 | "kid":"2011-04-29"
24 | }
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------
/middleware/melody-jose/fixtures/public.json:
--------------------------------------------------------------------------------
1 | {
2 | "keys": [
3 | {
4 | "kty":"EC",
5 | "crv":"P-256",
6 | "x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4",
7 | "y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM",
8 | "use":"enc",
9 | "kid":"1"
10 | },
11 | {
12 | "kty":"RSA",
13 | "n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw",
14 | "e":"AQAB",
15 | "alg":"RS256",
16 | "kid":"2011-04-29"
17 | }
18 | ]
19 | }
--------------------------------------------------------------------------------
/middleware/melody-jose/fixtures/symmetric.json:
--------------------------------------------------------------------------------
1 | {
2 | "keys": [
3 | {
4 | "kty": "oct",
5 | "alg": "A128KW",
6 | "k": "GawgguFyGrWKav7AX4VKUg",
7 | "kid": "sim1"
8 | },
9 | {
10 | "kty": "oct",
11 | "k": "AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow",
12 | "kid": "sim2",
13 | "alg": "HS256"
14 | }
15 | ]
16 | }
--------------------------------------------------------------------------------
/middleware/melody-jose/jwk_example_test.go:
--------------------------------------------------------------------------------
1 | // +build integration
2 |
3 | package jose
4 |
5 | import "fmt"
6 |
7 | func Example_Auth0Integration() {
8 | fs, _ := DecodeFingerprints([]string{"--MBgDH5WGvL9Bcn5Be30cRcL0f5O-NyoXuWtQdX1aI="})
9 | cfg := SecretProviderConfig{
10 | URI: "https://albert-test.auth0.com/.well-known/jwks.json",
11 | Fingerprints: fs,
12 | }
13 | client, _ := SecretProvider(cfg, nil)
14 |
15 | k, err := client.GetKey("MDNGMjU2M0U3RERFQUEwOUUzQUMwQ0NBN0Y1RUY0OEIxNTRDM0IxMw")
16 | fmt.Println("err:", err)
17 | fmt.Println("is public:", k.IsPublic())
18 | fmt.Println("alg:", k.Algorithm)
19 | fmt.Println("id:", k.KeyID)
20 | // Output:
21 | // err:
22 | // is public: true
23 | // alg: RS256
24 | // id: MDNGMjU2M0U3RERFQUEwOUUzQUMwQ0NBN0Y1RUY0OEIxNTRDM0IxMw
25 | }
26 |
27 | func Example_Auth0Integration_badFingerprint() {
28 | cfg := SecretProviderConfig{
29 | URI: "https://albert-test.auth0.com/.well-known/jwks.json",
30 | Fingerprints: [][]byte{make([]byte, 32)},
31 | }
32 | client, _ := SecretProvider(cfg, nil)
33 |
34 | _, err := client.GetKey("MDNGMjU2M0U3RERFQUEwOUUzQUMwQ0NBN0Y1RUY0OEIxNTRDM0IxMw")
35 | fmt.Println("err:", err)
36 | // Output:
37 | // err: Get https://albert-test.auth0.com/.well-known/jwks.json: JWK client did not find a pinned key
38 | }
39 |
--------------------------------------------------------------------------------
/middleware/melody-jose/jwk_test.go:
--------------------------------------------------------------------------------
1 | //go:generate go run $GOROOT/src/crypto/tls/generate_cert.go --rsa-bits 1024 --host 127.0.0.1,::1,localhost --ca --start-date "Jan 1 00:00:00 2000" --duration=1000000h
2 | package jose
3 |
4 | import (
5 | "crypto/tls"
6 | "io/ioutil"
7 | "net/http"
8 | "net/http/httptest"
9 | "testing"
10 | )
11 |
12 | func TestJWK(t *testing.T) {
13 | cert, err := tls.LoadX509KeyPair("cert.pem", "key.pem")
14 | if err != nil {
15 | t.Error(err)
16 | return
17 | }
18 |
19 | for _, tc := range []struct {
20 | Name string
21 | Alg string
22 | ID []string
23 | }{
24 | {
25 | Name: "public",
26 | ID: []string{"2011-04-29"},
27 | Alg: "RS256",
28 | },
29 | {
30 | Name: "public",
31 | ID: []string{"1"},
32 | },
33 | {
34 | Name: "private",
35 | ID: []string{"2011-04-29"},
36 | Alg: "RS256",
37 | },
38 | {
39 | Name: "private",
40 | ID: []string{"1"},
41 | },
42 | {
43 | Name: "symmetric",
44 | ID: []string{"sim2"},
45 | Alg: "HS256",
46 | },
47 | } {
48 | server := httptest.NewUnstartedServer(jwkEndpoint(tc.Name))
49 | defer server.Close()
50 | server.TLS = &tls.Config{Certificates: []tls.Certificate{cert}}
51 | server.StartTLS()
52 |
53 | secretProvidr, err := SecretProvider(SecretProviderConfig{URI: server.URL, LocalCA: "cert.pem"}, nil)
54 | if err != nil {
55 | t.Error(err)
56 | }
57 | for _, k := range tc.ID {
58 | key, err := secretProvidr.GetKey(k) // 发get请求
59 | if err != nil {
60 | t.Errorf("[%s] extracting the key %s: %s", tc.Name, k, err.Error())
61 | }
62 | if key.Algorithm != tc.Alg {
63 | t.Errorf("wrong alg. have: %s, want: %s", key.Algorithm, tc.Alg)
64 | }
65 | }
66 | }
67 | }
68 |
69 | func TestDialer_DialTLS_ko(t *testing.T) {
70 | d := NewDialer(SecretProviderConfig{})
71 | c, err := d.DialTLS("\t", "addr")
72 | if err == nil {
73 | t.Error(err)
74 | }
75 | if c != nil {
76 | t.Errorf("unexpected connection: %v", c)
77 | }
78 | }
79 |
80 | func Test_decodeFingerprints(t *testing.T) {
81 | _, err := DecodeFingerprints([]string{"not_encoded_message"})
82 | if err == nil {
83 | t.Error(err)
84 | }
85 | }
86 |
87 | func jwkEndpoint(name string) http.HandlerFunc {
88 | data, err := ioutil.ReadFile("./fixtures/" + name + ".json")
89 | return func(rw http.ResponseWriter, _ *http.Request) {
90 | if err != nil {
91 | rw.WriteHeader(500)
92 | return
93 | }
94 | rw.Header().Set("Content-Type", "application/json")
95 | rw.Write(data)
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/middleware/melody-jose/rejecter.go:
--------------------------------------------------------------------------------
1 | package jose
2 |
3 | import (
4 | "melody/config"
5 | "melody/logging"
6 | )
7 |
8 | // Rejecter defines the interface for the components responsible for rejecting tokens.
9 | type Rejecter interface {
10 | Reject(map[string]interface{}) bool
11 | }
12 |
13 | // RejecterFunc is an adapter to use functions as rejecters
14 | type RejecterFunc func(map[string]interface{}) bool
15 |
16 | // Reject calls r(v)
17 | func (r RejecterFunc) Reject(v map[string]interface{}) bool { return r(v) }
18 |
19 | // FixedRejecter is a rejecter that always returns the same bool response
20 | type FixedRejecter bool
21 |
22 | // Reject returns f
23 | func (f FixedRejecter) Reject(_ map[string]interface{}) bool { return bool(f) }
24 |
25 | // RejecterFactory is a builder for rejecters
26 | type RejecterFactory interface {
27 | New(logging.Logger, *config.EndpointConfig) Rejecter
28 | }
29 |
30 | // RejecterFactoryFunc is an adapter to use a function as rejecter factory
31 | type RejecterFactoryFunc func(logging.Logger, *config.EndpointConfig) Rejecter
32 |
33 | // New calls f(l, cfg)
34 | func (f RejecterFactoryFunc) New(l logging.Logger, cfg *config.EndpointConfig) Rejecter {
35 | return f(l, cfg)
36 | }
37 |
38 | // NopRejecterFactory is a factory returning rejecters accepting all the tokens
39 | type NopRejecterFactory struct{}
40 |
41 | // New returns a fixed rejecter that accepts all the tokens
42 | func (NopRejecterFactory) New(_ logging.Logger, _ *config.EndpointConfig) Rejecter {
43 | return FixedRejecter(false)
44 | }
45 |
46 | // ChainedRejecterFactory returns rejecters chaining every rejecter contained in tne collection
47 | type ChainedRejecterFactory []RejecterFactory
48 |
49 | // New returns a chainned rejected that evaluates all the rejecters until v is rejected or the chain
50 | // is finished
51 | func (c ChainedRejecterFactory) New(l logging.Logger, cfg *config.EndpointConfig) Rejecter {
52 | rejecters := []Rejecter{}
53 | for _, rf := range c {
54 | rejecters = append(rejecters, rf.New(l, cfg))
55 | }
56 | return RejecterFunc(func(v map[string]interface{}) bool {
57 | for _, r := range rejecters {
58 | if r.Reject(v) {
59 | return true
60 | }
61 | }
62 | return false
63 | })
64 | }
65 |
--------------------------------------------------------------------------------
/middleware/melody-jose/rejecter_test.go:
--------------------------------------------------------------------------------
1 | package jose
2 |
3 | import (
4 | "melody/config"
5 | "melody/logging"
6 | "testing"
7 | )
8 |
9 | func TestChainedRejecterFactory(t *testing.T) {
10 | rf := ChainedRejecterFactory([]RejecterFactory{
11 | NopRejecterFactory{},
12 | RejecterFactoryFunc(func(_ logging.Logger, _ *config.EndpointConfig) Rejecter {
13 | return RejecterFunc(func(in map[string]interface{}) bool {
14 | v, ok := in["key"].(int)
15 | return ok && v == 42
16 | })
17 | }),
18 | })
19 |
20 | rejecter := rf.New(nil, nil)
21 |
22 | for _, tc := range []struct {
23 | name string
24 | in map[string]interface{}
25 | expected bool
26 | }{
27 | {
28 | name: "empty",
29 | in: map[string]interface{}{},
30 | },
31 | {
32 | name: "reject",
33 | in: map[string]interface{}{"key": 42},
34 | expected: true,
35 | },
36 | {
37 | name: "pass-1",
38 | in: map[string]interface{}{"key": "42"},
39 | },
40 | {
41 | name: "pass-2",
42 | in: map[string]interface{}{"key": 9876},
43 | },
44 | } {
45 | if v := rejecter.Reject(tc.in); tc.expected != v {
46 | t.Errorf("unexpected result for %s. have %v, want %v", tc.name, v, tc.expected)
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/middleware/melody-jsonschema/jsonschema.go:
--------------------------------------------------------------------------------
1 | package jsonschema
2 |
3 | import (
4 | "bytes"
5 | "context"
6 | "encoding/json"
7 | "errors"
8 | "fmt"
9 | "io/ioutil"
10 | "melody/config"
11 | "melody/proxy"
12 | "strings"
13 |
14 | "github.com/xeipuuv/gojsonschema"
15 | )
16 |
17 | const Namespace = "melody_jsonschema"
18 |
19 | var ErrEmptyBody = errors.New("could not validate an empty body")
20 |
21 | // ProxyFactory ...
22 | func ProxyFactory(pf proxy.Factory) proxy.FactoryFunc {
23 | return proxy.FactoryFunc(func(cfg *config.EndpointConfig) (proxy.Proxy, error) {
24 | next, err := pf.New(cfg)
25 | if err != nil {
26 | return proxy.NoopProxy, err
27 | }
28 | schemaLoader, ok := configGetter(cfg.ExtraConfig).(gojsonschema.JSONLoader)
29 | if !ok || schemaLoader == nil {
30 | return next, nil
31 | }
32 | return newProxy(schemaLoader, next), nil
33 | })
34 | }
35 |
36 | func newProxy(schemaLoader gojsonschema.JSONLoader, next proxy.Proxy) proxy.Proxy {
37 | return func(ctx context.Context, r *proxy.Request) (*proxy.Response, error) {
38 | if r.Body == nil {
39 | return nil, ErrEmptyBody
40 | }
41 | body, err := ioutil.ReadAll(r.Body)
42 | if err != nil {
43 | return nil, err
44 | }
45 | r.Body.Close()
46 | r.Body = ioutil.NopCloser(bytes.NewBuffer(body))
47 |
48 | result, err := gojsonschema.Validate(schemaLoader, gojsonschema.NewBytesLoader(body))
49 | if err != nil {
50 | return nil, err
51 | }
52 | if !result.Valid() {
53 | return nil, &validationError{errs: result.Errors()}
54 | }
55 |
56 | return next(ctx, r)
57 | }
58 | }
59 |
60 | func configGetter(cfg config.ExtraConfig) interface{} {
61 | v, ok := cfg[Namespace]
62 | if !ok {
63 | return nil
64 | }
65 | buf := new(bytes.Buffer)
66 | if err := json.NewEncoder(buf).Encode(v); err != nil {
67 | return nil
68 | }
69 | return gojsonschema.NewBytesLoader(buf.Bytes())
70 | }
71 |
72 | type validationError struct {
73 | errs []gojsonschema.ResultError
74 | }
75 |
76 | func (v *validationError) Error() string {
77 | errs := make([]string, len(v.errs))
78 | for i, desc := range v.errs {
79 | errs[i] = fmt.Sprintf("- %s", desc)
80 | }
81 | return strings.Join(errs, "\n")
82 | }
83 |
--------------------------------------------------------------------------------
/middleware/melody-martian/context.go:
--------------------------------------------------------------------------------
1 | package martian
2 |
3 | import "context"
4 |
5 | // NewContext 返回一个包装了接收到的父类的上下文
6 | func NewContext(parent context.Context) *Context {
7 | return &Context{
8 | Context: parent,
9 | }
10 | }
11 |
12 | // Context 提供单个请求/响应对的信息。
13 | type Context struct {
14 | context.Context
15 | skipRoundTrip bool
16 | }
17 |
18 | // SkipRoundTrip 标记上下文以跳过往返
19 | func (c *Context) SkipRoundTrip() {
20 | c.skipRoundTrip = true
21 | }
22 |
23 | // SkippingRoundTrip 返回跳过往返旅程的标志
24 | func (c *Context) SkippingRoundTrip() bool {
25 | return c.skipRoundTrip
26 | }
27 |
28 | var _ context.Context = &Context{Context: context.Background()}
29 |
--------------------------------------------------------------------------------
/middleware/melody-martian/register.go:
--------------------------------------------------------------------------------
1 | package martian
2 |
3 | import (
4 | "melody/middleware/melody-martian/register"
5 |
6 | "github.com/google/martian/parse"
7 | )
8 |
9 | // Register 从 krakend-martian 寄存器获取所有修饰符,并将它们注册到 martian 解析器中
10 | func Register() {
11 | for k, component := range register.Get() {
12 | parse.Register(k, func(b []byte) (*parse.Result, error) {
13 | v, err := component.NewFromJSON(b)
14 | if err != nil {
15 | return nil, err
16 | }
17 |
18 | return parse.NewResult(v, toModifierType(component.Scope))
19 | })
20 | }
21 | }
22 |
23 | func toModifierType(scopes []register.Scope) []parse.ModifierType {
24 | modifierType := make([]parse.ModifierType, len(scopes))
25 | for k, s := range scopes {
26 | modifierType[k] = parse.ModifierType(s)
27 | }
28 | return modifierType
29 | }
30 |
--------------------------------------------------------------------------------
/middleware/melody-martian/register/register.go:
--------------------------------------------------------------------------------
1 | package register
2 |
3 | import "sync"
4 |
5 | const (
6 | // ScopeRequest 修改HTTP请求。
7 | ScopeRequest Scope = "request"
8 | // ScopeResponse 修改HTTP响应。
9 | ScopeResponse Scope = "response"
10 | )
11 |
12 | // Register 是包含所有martian组件的结构体
13 | type Register map[string]Component
14 |
15 | // Scope 定义组件的范围
16 | type Scope string
17 |
18 | // Component 组件包含范围和模块工厂
19 | type Component struct {
20 | Scope []Scope
21 | NewFromJSON func(b []byte) (interface{}, error)
22 | }
23 |
24 | var (
25 | register = Register{}
26 | mutex = &sync.RWMutex{}
27 | )
28 |
29 | // Set 将接收到的数据添加到寄存器中
30 | func Set(name string, scope []Scope, f func(b []byte) (interface{}, error)) {
31 | mutex.Lock()
32 | register[name] = Component{
33 | Scope: scope,
34 | NewFromJSON: f,
35 | }
36 | mutex.Unlock()
37 | }
38 |
39 | // Get 获取寄存器的副本
40 | func Get() Register {
41 | mutex.RLock()
42 | r := make(Register, len(register))
43 | for k, v := range register {
44 | r[k] = v
45 | }
46 | mutex.RUnlock()
47 | return r
48 | }
49 |
--------------------------------------------------------------------------------
/middleware/melody-martian/static.go:
--------------------------------------------------------------------------------
1 | package martian
2 |
3 | import (
4 | "encoding/json"
5 | "net/http"
6 |
7 | "github.com/google/martian/parse"
8 | "github.com/google/martian/static"
9 | )
10 |
11 | // StaticModifier is a martian.
12 | type StaticModifier struct {
13 | *static.Modifier
14 | }
15 |
16 | type staticJSON struct {
17 | ExplicitPaths map[string]string `json:"explicitPaths"`
18 | RootPath string `json:"rootPath"`
19 | Scope []parse.ModifierType `json:"scope"`
20 | }
21 |
22 | // NewStaticModifier 构造一个static.Modifier,它采用一个路径来服务文件,以及一个可选的请求路径到本地文件路径的映射(仍然以rootPath为根)。
23 | func NewStaticModifier(rootPath string) *StaticModifier {
24 | return &StaticModifier{
25 | Modifier: static.NewModifier(rootPath),
26 | }
27 | }
28 |
29 | // ModifyRequest 将上下文标记为跳过往返,并将所有https请求降级为http。
30 | func (s *StaticModifier) ModifyRequest(req *http.Request) error {
31 | ctx := NewContext(req.Context())
32 | ctx.SkipRoundTrip()
33 |
34 | if req.URL.Scheme == "https" {
35 | req.URL.Scheme = "http"
36 | }
37 |
38 | *req = *req.WithContext(ctx)
39 |
40 | return nil
41 | }
42 |
43 | func staticModifierFromJSON(b []byte) (*parse.Result, error) {
44 | msg := &staticJSON{}
45 | if err := json.Unmarshal(b, msg); err != nil {
46 | return nil, err
47 | }
48 |
49 | mod := NewStaticModifier(msg.RootPath)
50 | mod.SetExplicitPathMappings(msg.ExplicitPaths)
51 | return parse.NewResult(mod, msg.Scope)
52 | }
53 |
--------------------------------------------------------------------------------
/middleware/melody-metrics/router.go:
--------------------------------------------------------------------------------
1 | package metrics
2 |
3 | import "github.com/rcrowley/go-metrics"
4 |
5 | type RouterMetrics struct {
6 | ProxyMetrics
7 | connected metrics.Counter
8 | disconnected metrics.Counter
9 | connectedTotal metrics.Counter
10 | disconnectedTotal metrics.Counter
11 | connectedGauge metrics.Gauge
12 | disconnectedGauge metrics.Gauge
13 | }
14 |
15 | func NewRouterMetrics(parent *metrics.Registry) *RouterMetrics {
16 | r := metrics.NewPrefixedChildRegistry(*parent, "router.")
17 |
18 | return &RouterMetrics{
19 | ProxyMetrics: ProxyMetrics{register: r},
20 | connected: metrics.NewRegisteredCounter("connected", r),
21 | disconnected: metrics.NewRegisteredCounter("disconnected", r),
22 | connectedTotal: metrics.NewRegisteredCounter("connected-total", r),
23 | disconnectedTotal: metrics.NewRegisteredCounter("disconnected-total", r),
24 | connectedGauge: metrics.NewRegisteredGauge("connected-gauge", r),
25 | disconnectedGauge: metrics.NewRegisteredGauge("disconnected-gauge", r),
26 | }
27 | }
28 |
29 | func (rm *RouterMetrics) Aggregate() {
30 | con := rm.connected.Count()
31 | rm.connectedGauge.Update(con)
32 | rm.connectedTotal.Inc(con)
33 | rm.connected.Clear()
34 | discon := rm.disconnected.Count()
35 | rm.disconnectedGauge.Update(discon)
36 | rm.disconnectedTotal.Inc(discon)
37 | rm.disconnected.Clear()
38 | }
39 |
40 | func (rm *RouterMetrics) Disconnection() {
41 | rm.disconnected.Inc(1)
42 | }
43 |
44 | func (rm *RouterMetrics) RegisterResponseWriterMetrics(name string) {
45 | rm.Counter("response", name, "status")
46 | rm.Histogram("response", name, "size")
47 | rm.Histogram("response", name, "time")
48 | }
49 |
--------------------------------------------------------------------------------
/middleware/melody-metrics/stats.go:
--------------------------------------------------------------------------------
1 | package metrics
2 |
3 | import "time"
4 |
5 | func NewStats() Stats {
6 | return Stats{
7 | Time: time.Now().UnixNano(),
8 | Counters: map[string]int64{},
9 | Gauges: map[string]int64{},
10 | Histograms: map[string]HistogramData{},
11 | }
12 | }
13 |
14 | type Stats struct {
15 | Time int64
16 | Counters map[string]int64
17 | Gauges map[string]int64
18 | Histograms map[string]HistogramData
19 | }
20 |
21 | // HistogramData is a snapshot of an actual histogram
22 | type HistogramData struct {
23 | Max int64
24 | Min int64
25 | Mean float64
26 | Stddev float64
27 | Variance float64
28 | Percentiles []float64
29 | }
30 |
--------------------------------------------------------------------------------
/middleware/melody-ratelimit/juju/juju.go:
--------------------------------------------------------------------------------
1 | package juju
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/juju/ratelimit"
7 |
8 | melodyrate "melody/middleware/melody-ratelimit"
9 | )
10 |
11 | // NewLimiter 创建了一个新的 Limiter
12 | func NewLimiter(maxRate float64, capacity int64) Limiter {
13 | return Limiter{ratelimit.NewBucketWithRate(maxRate, capacity)}
14 | }
15 |
16 | // Limiter 是对 ratelimit.Bucket struct 的简单包装
17 | type Limiter struct {
18 | limiter *ratelimit.Bucket
19 | }
20 |
21 | // Allow 检查是否可以从 bucket 中提取一个token
22 | func (l Limiter) Allow() bool {
23 | return l.limiter.TakeAvailable(1) > 0
24 | }
25 |
26 | // NewLimiterStore 使用接收的后端返回一个用于持久性的LimiterStore
27 | func NewLimiterStore(maxRate float64, capacity int64, backend melodyrate.Backend) melodyrate.LimiterStore {
28 | f := func() interface{} { return NewLimiter(maxRate, capacity) }
29 | return func(t string) melodyrate.Limiter {
30 | return backend.Load(t, f).(Limiter)
31 | }
32 | }
33 |
34 | // NewMemoryStore 使用内存后端返回一个 LimiterStore
35 | func NewMemoryStore(maxRate float64, capacity int64) melodyrate.LimiterStore {
36 | return NewLimiterStore(maxRate, capacity, melodyrate.DefaultShardedMemoryBackend(context.Background()))
37 | }
38 |
--------------------------------------------------------------------------------
/middleware/melody-ratelimit/juju/proxy/proxy.go:
--------------------------------------------------------------------------------
1 | package proxy
2 |
3 | import (
4 | "context"
5 |
6 | "melody/config"
7 | "melody/proxy"
8 |
9 | melodyrate "melody/middleware/melody-ratelimit"
10 | "melody/middleware/melody-ratelimit/juju"
11 | )
12 |
13 | // Namespace 命名空间
14 | const Namespace = "melody_ratelimit_proxy"
15 |
16 | // Config 是包含限定符参数的自定义配置结构
17 | type Config struct {
18 | MaxRate float64
19 | Capacity int64
20 | }
21 |
22 | // BackendFactory 添加了一个包装内部工厂的速率限制中间件
23 | func BackendFactory(next proxy.BackendFactory) proxy.BackendFactory {
24 | return func(cfg *config.Backend) proxy.Proxy {
25 | return NewMiddleware(cfg)(next(cfg))
26 | }
27 | }
28 |
29 | // NewMiddleware 基于对下一个代理的额外配置参数或回退构建中间件
30 | func NewMiddleware(remote *config.Backend) proxy.Middleware {
31 | cfg := ConfigGetter(remote.ExtraConfig).(Config)
32 | if cfg == ZeroCfg || cfg.MaxRate <= 0 {
33 | return proxy.EmptyMiddleware
34 | }
35 | tb := juju.NewLimiter(cfg.MaxRate, cfg.Capacity)
36 | return func(next ...proxy.Proxy) proxy.Proxy {
37 | if len(next) > 1 {
38 | panic(proxy.ErrTooManyProxies)
39 | }
40 | return func(ctx context.Context, request *proxy.Request) (*proxy.Response, error) {
41 | if !tb.Allow() {
42 | return nil, melodyrate.ErrLimited
43 | }
44 | return next[0](ctx, request)
45 | }
46 | }
47 | }
48 |
49 | // ZeroCfg 是配置结构的空值
50 | var ZeroCfg = Config{}
51 |
52 | //ConfigGetter 实现config.ConfigGetter接口。它解析速率适配器的额外配置,如果出了问题,则返回一个ZeroCfg。
53 | func ConfigGetter(e config.ExtraConfig) interface{} {
54 | v, ok := e[Namespace]
55 | if !ok {
56 | return ZeroCfg
57 | }
58 | tmp, ok := v.(map[string]interface{})
59 | if !ok {
60 | return ZeroCfg
61 | }
62 | cfg := Config{}
63 | if v, ok := tmp["maxRate"]; ok {
64 | switch val := v.(type) {
65 | case float64:
66 | cfg.MaxRate = val
67 | case int:
68 | cfg.MaxRate = float64(val)
69 | case int64:
70 | cfg.MaxRate = float64(val)
71 | }
72 | }
73 | if v, ok := tmp["capacity"]; ok {
74 | switch val := v.(type) {
75 | case int64:
76 | cfg.Capacity = val
77 | case int:
78 | cfg.Capacity = int64(val)
79 | case float64:
80 | cfg.Capacity = int64(val)
81 | }
82 | }
83 | return cfg
84 | }
85 |
--------------------------------------------------------------------------------
/middleware/melody-ratelimit/juju/proxy/proxy_benchmark_test.go:
--------------------------------------------------------------------------------
1 | package proxy
2 |
3 | import (
4 | "context"
5 | "testing"
6 |
7 | "melody/config"
8 | "melody/proxy"
9 | )
10 |
11 | func BenchmarkNewMiddleware_ok(b *testing.B) {
12 | p := NewMiddleware(&config.Backend{
13 | ExtraConfig: map[string]interface{}{Namespace: map[string]interface{}{"maxRate": 10000000000000.0, "capacity": 100000000000.0}},
14 | })(dummyProxy(&proxy.Response{}, nil))
15 | b.ResetTimer()
16 | for i := 0; i < b.N; i++ {
17 | p(context.Background(), &proxy.Request{
18 | Path: "/tupu",
19 | })
20 | }
21 | }
22 |
23 | func BenchmarkNewCMiddleware_ko(b *testing.B) {
24 | p := NewMiddleware(&config.Backend{
25 | ExtraConfig: map[string]interface{}{Namespace: map[string]interface{}{"maxRate": 1.0, "capacity": 1.0}},
26 | })(dummyProxy(&proxy.Response{}, nil))
27 | b.ResetTimer()
28 | for i := 0; i < b.N; i++ {
29 | p(context.Background(), &proxy.Request{
30 | Path: "/tupu",
31 | })
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/middleware/melody-ratelimit/juju/proxy/proxy_test.go:
--------------------------------------------------------------------------------
1 | package proxy
2 |
3 | import (
4 | "context"
5 | "sync/atomic"
6 | "testing"
7 |
8 | "melody/config"
9 |
10 | "melody/proxy"
11 |
12 | melodyrate "melody/middleware/melody-ratelimit"
13 | )
14 |
15 | func TestNewMiddleware_multipleNext(t *testing.T) {
16 | defer func() {
17 | if r := recover(); r != proxy.ErrTooManyProxies {
18 | t.Errorf("The code did not panic\n")
19 | }
20 | }()
21 | NewMiddleware(&config.Backend{})(proxy.NoopProxy, proxy.NoopProxy)
22 | }
23 |
24 | func TestNewMiddleware_zeroConfig(t *testing.T) {
25 | for _, cfg := range []*config.Backend{
26 | {},
27 | {ExtraConfig: map[string]interface{}{Namespace: 42}},
28 | } {
29 | resp := proxy.Response{}
30 | mdw := NewMiddleware(cfg)
31 | p := mdw(dummyProxy(&resp, nil))
32 |
33 | request := proxy.Request{
34 | Path: "/tupu",
35 | }
36 |
37 | for i := 0; i < 100; i++ {
38 | r, err := p(context.Background(), &request)
39 | if err != nil {
40 | t.Error(err.Error())
41 | return
42 | }
43 | if &resp != r {
44 | t.Fail()
45 | }
46 | }
47 | }
48 | }
49 |
50 | func TestNewMiddleware_ok(t *testing.T) {
51 | resp := proxy.Response{}
52 | mdw := NewMiddleware(&config.Backend{
53 | ExtraConfig: map[string]interface{}{Namespace: map[string]interface{}{"maxRate": 10000.0, "capacity": 10000.0}},
54 | })
55 | p := mdw(dummyProxy(&resp, nil))
56 |
57 | request := proxy.Request{
58 | Path: "/tupu",
59 | }
60 |
61 | for i := 0; i < 1000; i++ {
62 | r, err := p(context.Background(), &request)
63 | if err != nil {
64 | t.Error(err.Error())
65 | return
66 | }
67 | if &resp != r {
68 | t.Fail()
69 | }
70 | }
71 | }
72 |
73 | func TestNewMiddleware_ko(t *testing.T) {
74 | expected := proxy.Response{}
75 | calls := uint64(0)
76 | mdw := NewMiddleware(&config.Backend{
77 | ExtraConfig: map[string]interface{}{Namespace: map[string]interface{}{"maxRate": 1.0, "capacity": 1.0}},
78 | })
79 | p := mdw(func(_ context.Context, _ *proxy.Request) (*proxy.Response, error) {
80 | total := atomic.AddUint64(&calls, 1)
81 | if total > 2 {
82 | t.Error("This proxy shouldn't been executed!")
83 | }
84 | return &expected, nil
85 | })
86 |
87 | request := proxy.Request{
88 | Path: "/tupu",
89 | }
90 |
91 | for i := 0; i < 100; i++ {
92 | p(context.Background(), &request)
93 | }
94 |
95 | r, err := p(context.Background(), &request)
96 | if err != melodyrate.ErrLimited {
97 | t.Errorf("error expected")
98 | }
99 | if nil != r {
100 | t.Error("unexpected response")
101 | }
102 | if calls != 1 {
103 | t.Error("unexpected number of calls to the proxy")
104 | }
105 | }
106 |
107 | func dummyProxy(r *proxy.Response, err error) proxy.Proxy {
108 | return func(_ context.Context, _ *proxy.Request) (*proxy.Response, error) {
109 | return r, err
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/middleware/melody-ratelimit/juju/router/router.go:
--------------------------------------------------------------------------------
1 | package router
2 |
3 | import (
4 | "fmt"
5 |
6 | "melody/config"
7 | )
8 |
9 | // Namespace 命名空间是用来存储和访问路由器自定义配置数据
10 | const Namespace = "melody_ratelimit_router"
11 |
12 | // Config 是包含路由器中间件参数的自定义配置结构
13 | type Config struct {
14 | MaxRate int64
15 | Strategy string
16 | ClientMaxRate int64
17 | Key string
18 | }
19 |
20 | // ZeroCfg ZeroCfg 是Config struct的零值
21 | var ZeroCfg = Config{}
22 |
23 | // ConfigGetter 实现config.ConfigGetter接口。它解析速率适配器的extra config,如果出了问题,则返回一个ZeroCfg。
24 | func ConfigGetter(e config.ExtraConfig) interface{} {
25 | v, ok := e[Namespace]
26 | if !ok {
27 | return ZeroCfg
28 | }
29 | tmp, ok := v.(map[string]interface{})
30 | if !ok {
31 | return ZeroCfg
32 | }
33 | cfg := Config{}
34 | if v, ok := tmp["maxRate"]; ok {
35 | switch val := v.(type) {
36 | case int64:
37 | cfg.MaxRate = val
38 | case int:
39 | cfg.MaxRate = int64(val)
40 | case float64:
41 | cfg.MaxRate = int64(val)
42 | }
43 | }
44 | if v, ok := tmp["strategy"]; ok {
45 | cfg.Strategy = fmt.Sprintf("%v", v)
46 | }
47 | if v, ok := tmp["clientMaxRate"]; ok {
48 | switch val := v.(type) {
49 | case int64:
50 | cfg.ClientMaxRate = val
51 | case int:
52 | cfg.ClientMaxRate = int64(val)
53 | case float64:
54 | cfg.ClientMaxRate = int64(val)
55 | }
56 | }
57 | if v, ok := tmp["key"]; ok {
58 | cfg.Key = fmt.Sprintf("%v", v)
59 | }
60 | return cfg
61 | }
62 |
--------------------------------------------------------------------------------
/middleware/melody-ratelimit/juju/router/router_test.go:
--------------------------------------------------------------------------------
1 | package router
2 |
3 | import (
4 | "encoding/json"
5 | "testing"
6 |
7 | "melody/config"
8 | )
9 |
10 | func TestConfigGetter(t *testing.T) {
11 | serializedCfg := []byte(`{
12 | "melody_ratelimit_router": {
13 | "maxRate":10
14 | }
15 | }`)
16 | var dat config.ExtraConfig
17 | if err := json.Unmarshal(serializedCfg, &dat); err != nil {
18 | t.Error(err.Error())
19 | }
20 | cfg := ConfigGetter(dat).(Config)
21 | if cfg.MaxRate != 10 {
22 | t.Errorf("wrong value for MaxRate. Want: 10, have: %d", cfg.MaxRate)
23 | }
24 | if cfg.ClientMaxRate != 0 {
25 | t.Errorf("wrong value for ClientMaxRate. Want: 0, have: %d", cfg.ClientMaxRate)
26 | }
27 | if cfg.Strategy != "" {
28 | t.Errorf("wrong value for Strategy. Want: '', have: %s", cfg.Strategy)
29 | }
30 | if cfg.Key != "" {
31 | t.Errorf("wrong value for Key. Want: '', have: %s", cfg.Key)
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/middleware/melody-ratelimit/melodyrate_test.go:
--------------------------------------------------------------------------------
1 | package melodyrate
2 |
3 | //import (
4 | // "context"
5 | // "fmt"
6 | // "runtime"
7 | // "sync"
8 | // "testing"
9 | // "time"
10 | //)
11 | //
12 | //func TestMemoryBackend(t *testing.T) {
13 | // for _, tc := range []struct {
14 | // name string
15 | // s int
16 | // f func(context.Context, time.Duration) Backend
17 | // }{
18 | // {name: "memory", s: 1, f: func(ctx context.Context, ttl time.Duration) Backend { return NewMemoryBackend(ctx, ttl) }},
19 | // {name: "sharded", s: 256, f: func(ctx context.Context, ttl time.Duration) Backend {
20 | // return NewShardedMemoryBackend(ctx, 256, ttl, PseudoFNV64a)
21 | // }},
22 | // } {
23 | // t.Run(tc.name, func(t *testing.T) {
24 | // testBackend(t, tc.s, tc.f)
25 | // })
26 | // }
27 | //}
28 | //
29 | //func testBackend(t *testing.T, storesInit int, f func(context.Context, time.Duration) Backend) {
30 | // ttl := time.Second
31 | // ctx, cancel := context.WithCancel(context.Background())
32 | // defer cancel()
33 | //
34 | // mb := f(ctx, ttl)
35 | // total := 1000 * runtime.NumCPU()
36 | //
37 | // <-time.After(ttl)
38 | //
39 | // wg := new(sync.WaitGroup)
40 | //
41 | // for w := 0; w < 10; w++ {
42 | // wg.Add(1)
43 | // go func() {
44 | // for i := 0; i < total; i++ {
45 | // mb.Store(fmt.Sprintf("key-%d", i), i)
46 | // }
47 | // wg.Done()
48 | // }()
49 | // }
50 | // wg.Wait()
51 | //
52 | // noResult := func() interface{} { return nil }
53 | //
54 | // for i := 0; i < total; i++ {
55 | // v := mb.Load(fmt.Sprintf("key-%d", i), noResult)
56 | // if v == nil {
57 | // t.Errorf("key %d not present", i)
58 | // return
59 | // }
60 | // if res, ok := v.(int); !ok || res != i {
61 | // t.Errorf("unexpected value. want: %d, have: %v", i, v)
62 | // return
63 | // }
64 | // }
65 | //
66 | // <-time.After(2 * ttl)
67 | //
68 | // for i := 0; i < total; i++ {
69 | // if v := mb.Load(fmt.Sprintf("key-%d", i), noResult); v != nil {
70 | // t.Errorf("key %d present after 2 TTL: %v", i, v)
71 | // return
72 | // }
73 | // }
74 | //}
75 |
--------------------------------------------------------------------------------
/middleware/melody-ratelimit/pseudoFNV64a.go:
--------------------------------------------------------------------------------
1 | package melodyrate
2 |
3 | const (
4 | offset64 uint64 = 14695981039346656037
5 | prime64 = 1099511628211
6 | )
7 |
8 | // PseudoFNV64a 接受一个string
9 | func PseudoFNV64a(s string) uint64 {
10 | h := offset64
11 | for i := 0; i < len(s); i++ {
12 | h ^= uint64(s[i])
13 | h *= prime64
14 | }
15 | return h
16 | }
17 |
--------------------------------------------------------------------------------
/middleware/melody-rss/rss.go:
--------------------------------------------------------------------------------
1 | package rss
2 |
3 | import (
4 | "io"
5 | "melody/encoding"
6 |
7 | "github.com/mmcdole/gofeed"
8 | )
9 |
10 | // Name is the key for the rss encoding
11 | const Name = "rss"
12 |
13 | func Register() error {
14 | return encoding.Register(Name, DecoderFactory)
15 | }
16 |
17 | func DecoderFactory(_ bool) func(io.Reader, *map[string]interface{}) error {
18 | return NewDecoder()
19 | }
20 |
21 | // NewDecoder returns the RSS decoder
22 | func NewDecoder() func(io.Reader, *map[string]interface{}) error {
23 | fp := gofeed.NewParser()
24 | return func(r io.Reader, v *map[string]interface{}) error {
25 | feed, err := fp.Parse(r)
26 | if err != nil {
27 | return err
28 | }
29 | *(v) = map[string]interface{}{
30 | "items": feed.Items,
31 | "author": feed.Author,
32 | "categories": feed.Categories,
33 | "custom": feed.Custom,
34 | "copyright": feed.Copyright,
35 | "description": feed.Description,
36 | "type": feed.FeedType,
37 | "language": feed.Language,
38 | "title": feed.Title,
39 | "published": feed.Published,
40 | "updated": feed.Updated,
41 | }
42 | if feed.Image != nil {
43 | (*v)["img_url"] = feed.Image.URL
44 | }
45 | return nil
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/middleware/melody-viper/viper.go:
--------------------------------------------------------------------------------
1 | package viper
2 |
3 | import (
4 | "github.com/spf13/viper"
5 | "melody/config"
6 | "reflect"
7 | "unsafe"
8 | )
9 |
10 | //ViperParser extends viper
11 | type ViperParser struct {
12 | viper *viper.Viper
13 | }
14 |
15 | //New return new parser extends viper
16 | func New() ViperParser {
17 | return ViperParser{
18 | viper: viper.New(),
19 | }
20 | }
21 |
22 | //实现 config 中 parser 接口
23 | func (p ViperParser) Parse(configFile string) (config.ServiceConfig, error) {
24 | p.viper.SetConfigFile(configFile)
25 | p.viper.AutomaticEnv()
26 | var cfg config.ServiceConfig
27 |
28 | if err := p.viper.ReadInConfig(); err != nil {
29 | return cfg, checkErr(err, configFile)
30 | }
31 |
32 | if err := p.viper.Unmarshal(&cfg); err != nil {
33 | return cfg, checkErr(err, configFile)
34 | }
35 |
36 | if err := cfg.Init(); err != nil {
37 | return cfg, config.CheckErr(err, configFile)
38 | }
39 |
40 | return cfg, nil
41 | }
42 |
43 | func checkErr(err error, configFile string) error {
44 | switch e := err.(type) {
45 | case viper.ConfigParseError:
46 | var subErr error
47 | rs := reflect.ValueOf(&e).Elem()
48 | rf := rs.Field(0)
49 | ri := reflect.ValueOf(&subErr).Elem()
50 |
51 | rf = reflect.NewAt(rf.Type(), unsafe.Pointer(rf.UnsafeAddr())).Elem()
52 |
53 | ri.Set(rf)
54 |
55 | return checkErr(subErr, configFile)
56 | default:
57 | return config.CheckErr(err, configFile)
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/middleware/melody-xml/xml.go:
--------------------------------------------------------------------------------
1 | package xml
2 |
3 | import (
4 | "github.com/clbanning/mxj"
5 | "io"
6 | "melody/encoding"
7 | )
8 |
9 | const XML = "xml"
10 |
11 | type xmlReader struct {
12 | r io.Reader
13 | }
14 |
15 | func (x xmlReader) Read(p []byte) (n int, err error) {
16 | n, err = x.r.Read(p)
17 |
18 | if err != io.EOF {
19 | return n, err
20 | }
21 |
22 | if len(p) == n {
23 | return n, nil
24 | }
25 |
26 | p[n] = ([]byte("\n"))[0]
27 | return n + 1, err
28 | }
29 |
30 | func Register() {
31 | encoding.Register(XML, NewXMLDecoder)
32 | }
33 | func NewXMLDecoder(isCollection bool) func(io.Reader, *map[string]interface{}) error {
34 | if isCollection {
35 | return CollectionDecoder()
36 | } else {
37 | return Decoder()
38 | }
39 | }
40 |
41 | func CollectionDecoder() encoding.Decoder {
42 | return func(reader io.Reader, i *map[string]interface{}) error {
43 | mv, err := mxj.NewMapXmlReader(xmlReader{r: reader})
44 | if err != nil {
45 | return err
46 | }
47 | *i = map[string]interface{}{"collection": mv}
48 | return nil
49 | }
50 | }
51 |
52 | func Decoder() encoding.Decoder {
53 | return func(reader io.Reader, i *map[string]interface{}) error {
54 | mp, err := mxj.NewMapXmlReader(xmlReader{r: reader})
55 | if err != nil {
56 | return err
57 | }
58 | *i = mp
59 | return nil
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/plugin/plugin.go:
--------------------------------------------------------------------------------
1 | package plugin
2 |
3 | import (
4 | "io/ioutil"
5 | "strings"
6 | )
7 |
8 | // Scan ... 读取插件
9 | func Scan(folder, pattern string) ([]string, error) {
10 | files, erro := ioutil.ReadDir(folder)
11 | if erro != nil {
12 | return []string{}, erro
13 | }
14 |
15 | plugins := []string{}
16 | for _, file := range files {
17 | if !file.IsDir() && strings.Contains(file.Name(), pattern) {
18 | plugins = append(plugins, folder+file.Name())
19 | }
20 | }
21 |
22 | return plugins, nil
23 | }
24 |
--------------------------------------------------------------------------------
/proxy/balancing.go:
--------------------------------------------------------------------------------
1 | package proxy
2 |
3 | import (
4 | "context"
5 | "melody/sd"
6 | "net/url"
7 | "strings"
8 | )
9 |
10 | func NewLoadBalancedMiddlewareWithSubscriber(subscriber sd.Subscriber) Middleware {
11 | return newLoadBalancedMiddleware(sd.NewBalancer(subscriber))
12 | }
13 |
14 | func newLoadBalancedMiddleware(lb sd.Balancer) Middleware {
15 | return func(next ...Proxy) Proxy {
16 | if len(next) > 1 {
17 | panic(ErrTooManyProxies)
18 | }
19 | return func(ctx context.Context, request *Request) (*Response, error) {
20 | host, err := lb.Host()
21 | if err != nil {
22 | return nil, err
23 | }
24 | r := request.Clone()
25 |
26 | var b strings.Builder
27 | b.WriteString(host)
28 | b.WriteString(r.Path)
29 | r.URL, err = url.Parse(b.String())
30 | if err != nil {
31 | return nil, err
32 | }
33 | if len(r.Query) > 0 {
34 | r.URL.RawQuery += "&" + r.Query.Encode()
35 | }
36 |
37 | return next[0](ctx, &r)
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/proxy/concurrent.go:
--------------------------------------------------------------------------------
1 | package proxy
2 |
3 | import (
4 | "context"
5 | "errors"
6 | "melody/config"
7 | "time"
8 | )
9 |
10 | var errNullResult = errors.New("invalid response")
11 |
12 | func NewConcurrentCallMiddleware(backend *config.Backend) Middleware {
13 | // Check proxy
14 | if backend.ConcurrentCalls == 1 {
15 | panic(ErrTooManyProxies)
16 | }
17 |
18 | serviceTimeout := time.Duration(75*backend.Timeout.Nanoseconds()/100) * time.Nanosecond
19 |
20 | return func(proxy ...Proxy) Proxy {
21 | if len(proxy) > 1 {
22 | panic(ErrTooManyProxies)
23 | }
24 |
25 | return func(ctx context.Context, request *Request) (*Response, error) {
26 | totalCtx, cancel := context.WithTimeout(ctx, serviceTimeout)
27 |
28 | results := make(chan *Response, backend.ConcurrentCalls)
29 | failed := make(chan error, backend.ConcurrentCalls)
30 |
31 | for i := 0; i < backend.ConcurrentCalls; i++ {
32 | go processConcurrentCall(totalCtx, proxy[0], request, results, failed)
33 | }
34 |
35 | var response *Response
36 | var err error
37 |
38 | for i := 0; i < backend.ConcurrentCalls; i++ {
39 | select {
40 | case response = <-results:
41 | if response != nil && response.IsComplete {
42 | cancel()
43 | return response, nil
44 | }
45 | case err = <-failed:
46 | case <-ctx.Done():
47 | }
48 | }
49 | cancel()
50 | return response, err
51 | }
52 | }
53 | }
54 |
55 | func processConcurrentCall(ctx context.Context, proxy Proxy, request *Request, responses chan *Response, errors chan error) {
56 | localCtx, cancel := context.WithCancel(ctx)
57 |
58 | resp, err := proxy(localCtx, request)
59 |
60 | if err != nil {
61 | errors <- err
62 | cancel()
63 | return
64 | }
65 |
66 | if resp == nil {
67 | errors <- errNullResult
68 | cancel()
69 | return
70 | }
71 |
72 | select {
73 | case responses <- resp:
74 | case <-ctx.Done():
75 | errors <- ctx.Err()
76 | }
77 | cancel()
78 | }
79 |
--------------------------------------------------------------------------------
/proxy/factory.go:
--------------------------------------------------------------------------------
1 | package proxy
2 |
3 | import (
4 | "melody/config"
5 | "melody/logging"
6 | "melody/sd"
7 | )
8 |
9 | // Factory 作为ProxyFactory的标准
10 | type Factory interface {
11 | New(cfg *config.EndpointConfig) (Proxy, error)
12 | }
13 |
14 | type FactoryFunc func(*config.EndpointConfig) (Proxy, error)
15 |
16 | // New implements the Factory interface
17 | func (f FactoryFunc) New(cfg *config.EndpointConfig) (Proxy, error) { return f(cfg) }
18 |
19 | type defaultFactory struct {
20 | backendFactory BackendFactory
21 | logger logging.Logger
22 | subscriberFactory sd.SubscriberFactory
23 | }
24 |
25 | func NewDefaultFactory(factory BackendFactory, logger logging.Logger) Factory {
26 | return NewDefaultFactoryWithSubscriberFactory(factory, logger, sd.GetSubscriber)
27 | }
28 |
29 | func NewDefaultFactoryWithSubscriberFactory(factory BackendFactory, logger logging.Logger, subscriberFactory sd.SubscriberFactory) Factory {
30 | return defaultFactory{
31 | backendFactory: factory,
32 | logger: logger,
33 | subscriberFactory: subscriberFactory,
34 | }
35 | }
36 |
37 | func (d defaultFactory) New(cfg *config.EndpointConfig) (p Proxy, err error) {
38 | switch len(cfg.Backends) {
39 | case 0:
40 | err = ErrNoBackends
41 | case 1:
42 | p, err = d.NewSingle(cfg)
43 | default:
44 | p, err = d.NewMulti(cfg)
45 | }
46 |
47 | if err != nil {
48 | return
49 | }
50 | // 执行顺序:⑥
51 | p = NewStaticDataMiddleware(cfg)(p)
52 | return
53 | }
54 |
55 | func (d defaultFactory) NewStack(backend *config.Backend) (p Proxy) {
56 | // 根据config.Backend定制backendProxy 执行顺序:④
57 | p = d.backendFactory(backend)
58 | // 均衡中间件注册(在此处调用对应的服务发现) 执行顺序:③
59 | p = NewLoadBalancedMiddlewareWithSubscriber(d.subscriberFactory(backend))(p)
60 | if backend.ConcurrentCalls > 1 {
61 | // 并发调用 > 1 执行顺序:②
62 | p = NewConcurrentCallMiddleware(backend)(p)
63 | }
64 | // 基础的Request构造器 执行顺序:①
65 | p = NewRequestBuilderMiddleware(backend)(p)
66 | return
67 | }
68 |
69 | func (d defaultFactory) NewSingle(endpointConfig *config.EndpointConfig) (Proxy, error) {
70 | return d.NewStack(endpointConfig.Backends[0]), nil
71 | }
72 |
73 | func (d defaultFactory) NewMulti(endpointConfig *config.EndpointConfig) (p Proxy, err error) {
74 |
75 | backendProxies := make([]Proxy, len(endpointConfig.Backends))
76 | for i, v := range endpointConfig.Backends {
77 | backendProxies[i] = d.NewStack(v)
78 | }
79 | // 执行顺序:⑤
80 | p = NewMergeDataMiddleware(endpointConfig)(backendProxies...)
81 | return
82 | }
83 |
--------------------------------------------------------------------------------
/proxy/http_response.go:
--------------------------------------------------------------------------------
1 | package proxy
2 |
3 | import (
4 | "context"
5 | "melody/encoding"
6 | "net/http"
7 | )
8 |
9 | // HTTPResponseParserConfig 封装了解码器和格式化器,作用于proxy.Response
10 | type HTTPResponseParserConfig struct {
11 | Decoder encoding.Decoder
12 | EntityFormatter EntityFormatter
13 | }
14 |
15 | // NoOpHTTPResponseParser 不对http.Response做任何format,直接封装成proxy.Response
16 | func NoOpHTTPResponseParser(ctx context.Context, resp *http.Response) (*Response, error) {
17 | return &Response{
18 | Data: map[string]interface{}{},
19 | IsComplete: true,
20 | Io: NewReadCloserWrapper(ctx, resp.Body),
21 | Metadata: Metadata{
22 | Headers: resp.Header,
23 | StatusCode: resp.StatusCode,
24 | },
25 | }, nil
26 | }
27 |
28 | func DefaultHTTPResponseParserFactory(cfg HTTPResponseParserConfig) HTTPResponseParser {
29 | return func(ctx context.Context, resp *http.Response) (*Response, error) {
30 | defer resp.Body.Close()
31 |
32 | var data map[string]interface{}
33 | err := cfg.Decoder(resp.Body, &data)
34 | if err != nil {
35 | return nil, err
36 | }
37 | response := Response{
38 | Data: data,
39 | IsComplete: true,
40 | }
41 | response = cfg.EntityFormatter.Format(response)
42 | return &response, nil
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/proxy/http_test.go:
--------------------------------------------------------------------------------
1 | package proxy
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 | "fmt"
7 | "melody/config"
8 | "melody/encoding"
9 | "net/http"
10 | "net/http/httptest"
11 | "net/url"
12 | "testing"
13 | "time"
14 | )
15 |
16 | func TestNewHTTPProxy_ok(t *testing.T) {
17 | expectedMethod := "GET"
18 | backendServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
19 | if r.ContentLength != 11 {
20 | t.Errorf("unexpected request size. Want: 11. Have: %d", r.ContentLength)
21 | }
22 | if h := r.Header.Get("Content-Length"); h != "11" {
23 | t.Errorf("unexpected content-length header. Want: 11. Have: %s", h)
24 | }
25 | if r.Method != expectedMethod {
26 | t.Errorf("Wrong request method. Want: %s. Have: %s", expectedMethod, r.Method)
27 | }
28 | if h := r.Header.Get("X-First"); h != "first" {
29 | t.Errorf("unexpected first header: %s", h)
30 | }
31 | if h := r.Header.Get("X-Second"); h != "second" {
32 | t.Errorf("unexpected second header: %s", h)
33 | }
34 | r.Header.Del("X-Second")
35 | fmt.Fprintf(w, "{\"supu\":42, \"tupu\":true, \"foo\": \"bar\"}")
36 | }))
37 | defer backendServer.Close()
38 |
39 | rpURL, _ := url.Parse(backendServer.URL)
40 | backend := config.Backend{
41 | Decoder: encoding.JSONDecoder(),
42 | }
43 | request := Request{
44 | Method: expectedMethod,
45 | Path: "/",
46 | URL: rpURL,
47 | Body: newDummyReadCloser(`{"abc": 42}`),
48 | Headers: map[string][]string{
49 | "X-First": {"first"},
50 | "X-Second": {"second"},
51 | "Content-Length": {"11"},
52 | },
53 | }
54 | mustEnd := time.After(time.Duration(150) * time.Millisecond)
55 |
56 | result, err := HTTPProxyFactory(http.DefaultClient)(&backend)(context.Background(), &request)
57 | if err != nil {
58 | t.Errorf("The proxy returned an unexpected error: %s\n", err.Error())
59 | return
60 | }
61 | if result == nil {
62 | t.Errorf("The proxy returned a null result\n")
63 | return
64 | }
65 | select {
66 | case <-mustEnd:
67 | t.Errorf("Error: expected response")
68 | return
69 | default:
70 | }
71 |
72 | tmp, ok := result.Data["supu"]
73 | if !ok {
74 | t.Errorf("The proxy returned an unexpected result: %v\n", result)
75 | }
76 | supuValue, err := tmp.(json.Number).Int64()
77 | if err != nil || supuValue != 42 {
78 | t.Errorf("The proxy returned an unexpected result: %v\n", supuValue)
79 | }
80 | if v, ok := result.Data["tupu"]; !ok || !v.(bool) {
81 | t.Errorf("The proxy returned an unexpected result: %v\n", result)
82 | }
83 | if v, ok := result.Data["foo"]; !ok || v.(string) != "bar" {
84 | t.Errorf("The proxy returned an unexpected result: %v\n", result)
85 | }
86 | if v, ok := request.Headers["X-Second"]; !ok || len(v) != 1 {
87 | t.Errorf("the proxy request headers were changed: %v", request.Headers)
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/proxy/proxy.go:
--------------------------------------------------------------------------------
1 | package proxy
2 |
3 | import (
4 | "context"
5 | "errors"
6 | "io"
7 | "melody/config"
8 | )
9 |
10 | // Namespace to be used in extra config
11 | const Namespace = "melody_proxy"
12 |
13 | var (
14 | // ErrNoBackends is the error returned when an endpoint has no backends defined
15 | ErrNoBackends = errors.New("all endpoints must have at least one backend")
16 | // ErrTooManyBackends is the error returned when an endpoint has too many backends defined
17 | ErrTooManyBackends = errors.New("too many backends for this proxy")
18 | // ErrTooManyProxies is the error returned when a middleware has too many proxies defined
19 | ErrTooManyProxies = errors.New("too many proxies for this proxy middleware")
20 | // ErrNotEnoughProxies is the error returned when an endpoint has not enough proxies defined
21 | ErrNotEnoughProxies = errors.New("not enough proxies for this endpoint")
22 | )
23 |
24 | // Proxy 定义了代理
25 | // 输入: context, request
26 | // 出入: response, error
27 | type Proxy func(context.Context, *Request) (*Response, error)
28 |
29 | type BackendFactory func(backend *config.Backend) Proxy
30 |
31 | // Metadata 包含了response header 和 response code
32 | type Metadata struct {
33 | Headers map[string][]string
34 | StatusCode int
35 | }
36 |
37 | // Response 作为proxy的输出
38 | type Response struct {
39 | Data map[string]interface{}
40 | IsComplete bool
41 | Io io.Reader
42 | Metadata Metadata
43 | }
44 |
45 | type Middleware func(...Proxy) Proxy
46 |
47 | func EmptyMiddleware(next ...Proxy) Proxy {
48 | if len(next) > 1 {
49 | panic(ErrTooManyProxies)
50 | }
51 | return next[0]
52 | }
53 |
54 | type readCloserWrapper struct {
55 | ctx context.Context
56 | rc io.ReadCloser
57 | }
58 |
59 | func (r readCloserWrapper) Read(p []byte) (n int, err error) {
60 | return r.rc.Read(p)
61 | }
62 |
63 | func (r readCloserWrapper) closeWhenCancel() {
64 | <-r.ctx.Done()
65 | r.rc.Close()
66 | }
67 |
68 | func NewReadCloserWrapper(ctx context.Context, reader io.ReadCloser) io.Reader {
69 | wrapper := readCloserWrapper{
70 | ctx: ctx,
71 | rc: reader,
72 | }
73 | go wrapper.closeWhenCancel()
74 | return wrapper
75 | }
76 |
77 | func NoopProxy(_ context.Context, _ *Request) (*Response, error) { return nil, nil }
78 |
--------------------------------------------------------------------------------
/proxy/proxy_test.go:
--------------------------------------------------------------------------------
1 | package proxy
2 |
3 | import (
4 | "context"
5 | "io"
6 | "strings"
7 | "testing"
8 | "time"
9 | )
10 |
11 | func newDummyReadCloser(content string) io.ReadCloser {
12 | return dummyReadCloser{strings.NewReader(content)}
13 | }
14 |
15 | type dummyReadCloser struct {
16 | reader io.Reader
17 | }
18 |
19 | func (d dummyReadCloser) Read(p []byte) (int, error) {
20 | return d.reader.Read(p)
21 | }
22 |
23 | func (d dummyReadCloser) Close() error {
24 | return nil
25 | }
26 |
27 | func explosiveProxy(t *testing.T) Proxy {
28 | return func(ctx context.Context, _ *Request) (*Response, error) {
29 | t.Error("This proxy shouldn't been executed!")
30 | return &Response{}, nil
31 | }
32 | }
33 |
34 | func dummyProxy(r *Response) Proxy {
35 | return func(_ context.Context, _ *Request) (*Response, error) {
36 | return r, nil
37 | }
38 | }
39 |
40 | func delayedProxy(t *testing.T, timeout time.Duration, r *Response) Proxy {
41 | return func(ctx context.Context, _ *Request) (*Response, error) {
42 | select {
43 | case <-ctx.Done():
44 | return nil, ctx.Err()
45 | case <-time.After(timeout):
46 | return r, nil
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/proxy/register.go:
--------------------------------------------------------------------------------
1 | package proxy
2 |
3 | import "melody/register"
4 |
5 | type Register struct {
6 | *combinerRegister
7 | }
8 |
9 | type combinerRegister struct {
10 | data register.Untyped
11 | fallback ResponseCombiner
12 | }
13 |
14 | func NewRegister() *Register {
15 | return &Register{
16 | responseCombiners,
17 | }
18 | }
19 |
20 | func newCombinerRegister(data map[string]ResponseCombiner, fallback ResponseCombiner) *combinerRegister {
21 | r := register.New()
22 | for k, v := range data {
23 | r.Register(k, v)
24 | }
25 | return &combinerRegister{r, fallback}
26 | }
27 |
28 | func (r *combinerRegister) GetResponseCombiner(name string) (ResponseCombiner, bool) {
29 | v, ok := r.data.Get(name)
30 | if !ok {
31 | return r.fallback, ok
32 | }
33 | if rc, ok := v.(ResponseCombiner); ok {
34 | return rc, ok
35 | }
36 | return r.fallback, ok
37 | }
38 |
39 | func (r *combinerRegister) SetResponseCombiner(name string, rc ResponseCombiner) {
40 | r.data.Register(name, rc)
41 | }
42 |
--------------------------------------------------------------------------------
/proxy/register_test.go:
--------------------------------------------------------------------------------
1 | package proxy
2 |
3 | import (
4 | "testing"
5 | )
6 |
7 | func TestNewRegister_responseCombiner_ok(t *testing.T) {
8 | r := NewRegister()
9 | r.SetResponseCombiner("name1", func(total int, parts []*Response) *Response {
10 | if total < 0 || total >= len(parts) {
11 | return nil
12 | }
13 | return parts[total]
14 | })
15 |
16 | rc, ok := r.GetResponseCombiner("name1")
17 | if !ok {
18 | t.Error("expecting response combiner")
19 | return
20 | }
21 |
22 | result := rc(0, []*Response{{IsComplete: true, Data: map[string]interface{}{"a": 42}}})
23 |
24 | if result == nil {
25 | t.Error("expecting result")
26 | return
27 | }
28 |
29 | if !result.IsComplete {
30 | t.Error("expecting a complete result")
31 | return
32 | }
33 |
34 | if len(result.Data) != 1 {
35 | t.Error("unexpected result size:", len(result.Data))
36 | return
37 | }
38 | }
39 |
40 | func TestNewRegister_responseCombiner_fallbackIfErrored(t *testing.T) {
41 | r := NewRegister()
42 |
43 | r.data.Register("errored", true)
44 |
45 | rc, ok := r.GetResponseCombiner("errored")
46 | if !ok {
47 | t.Error("expecting response combiner")
48 | return
49 | }
50 |
51 | original := &Response{IsComplete: true, Data: map[string]interface{}{"a": 42}}
52 |
53 | result := rc(0, []*Response{original})
54 |
55 | if result != original {
56 | t.Error("unexpected result:", result)
57 | return
58 | }
59 | }
60 |
61 | func TestNewRegister_responseCombiner_fallbackIfUnknown(t *testing.T) {
62 | r := NewRegister()
63 |
64 | rc, ok := r.GetResponseCombiner("unknown")
65 | if ok {
66 | t.Error("the response combiner should not be found")
67 | return
68 | }
69 |
70 | original := &Response{IsComplete: true, Data: map[string]interface{}{"a": 42}}
71 |
72 | result := rc(0, []*Response{original})
73 |
74 | if result != original {
75 | t.Error("unexpected result:", result)
76 | return
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/proxy/request.go:
--------------------------------------------------------------------------------
1 | package proxy
2 |
3 | import (
4 | "bytes"
5 | "io"
6 | "io/ioutil"
7 | "net/url"
8 | )
9 |
10 | // Request 包含了从Endpoint发送
11 | // 到backends的数据
12 | type Request struct {
13 | Method string
14 | URL *url.URL
15 | Query url.Values
16 | Path string
17 | Body io.ReadCloser
18 | Params map[string]string
19 | Headers map[string][]string
20 | }
21 |
22 | func (r *Request) Clone() Request {
23 | return Request{
24 | Method: r.Method,
25 | URL: r.URL,
26 | Query: r.Query,
27 | Path: r.Path,
28 | Body: r.Body,
29 | Params: r.Params,
30 | Headers: r.Headers,
31 | }
32 | }
33 |
34 | // CloneRequest 返回一个请求副本
35 | func CloneRequest(r *Request) *Request {
36 | clone := r.Clone()
37 | clone.Headers = CloneRequestHeaders(r.Headers)
38 | clone.Params = CloneRequestParams(r.Params)
39 | if r.Body == nil {
40 | return &clone
41 | }
42 | buf := new(bytes.Buffer)
43 | buf.ReadFrom(r.Body)
44 | r.Body.Close()
45 |
46 | r.Body = ioutil.NopCloser(bytes.NewReader(buf.Bytes()))
47 | clone.Body = ioutil.NopCloser(buf)
48 |
49 | return &clone
50 | }
51 |
52 | // CloneRequestHeaders 返回一个接收到的请求头的副本
53 | func CloneRequestHeaders(headers map[string][]string) map[string][]string {
54 | m := make(map[string][]string, len(headers))
55 | for k, vs := range headers {
56 | tmp := make([]string, len(vs))
57 | copy(tmp, vs)
58 | m[k] = tmp
59 | }
60 | return m
61 | }
62 |
63 | // CloneRequestParams 返回接收到的请求参数的副本
64 | func CloneRequestParams(params map[string]string) map[string]string {
65 | m := make(map[string]string, len(params))
66 | for k, v := range params {
67 | m[k] = v
68 | }
69 | return m
70 | }
71 |
72 | func (r *Request) GeneratePath(URLPattern string) {
73 | if len(r.Params) == 0 {
74 | r.Path = URLPattern
75 | return
76 | }
77 | buff := []byte(URLPattern)
78 | for k, v := range r.Params {
79 | key := []byte{}
80 | key = append(key, "{{."...)
81 | key = append(key, k...)
82 | key = append(key, "}}"...)
83 | buff = bytes.Replace(buff, key, []byte(v), -1)
84 | }
85 | r.Path = string(buff)
86 | }
87 |
--------------------------------------------------------------------------------
/proxy/shadow.go:
--------------------------------------------------------------------------------
1 | package proxy
2 |
3 | import (
4 | "context"
5 | "melody/config"
6 | )
7 |
8 | const (
9 | shadowKey = "shadow"
10 | )
11 |
12 | type shadowFactory struct {
13 | f Factory
14 | }
15 |
16 | // New check the Backends for an ExtraConfig with the "shadow" param to true
17 | // 实现工厂接口
18 | func (s shadowFactory) New(cfg *config.EndpointConfig) (p Proxy, err error) {
19 | if len(cfg.Backends) == 0 {
20 | err = ErrNoBackends
21 | return
22 | }
23 |
24 | shadow := []*config.Backend{}
25 | regular := []*config.Backend{}
26 |
27 | for _, b := range cfg.Backends {
28 | if isShadowBackend(b) {
29 | shadow = append(shadow, b)
30 | continue
31 | }
32 | regular = append(regular, b)
33 | }
34 |
35 | cfg.Backends = regular
36 |
37 | p, err = s.f.New(cfg)
38 |
39 | if len(shadow) > 0 {
40 | cfg.Backends = shadow
41 | pShadow, _ := s.f.New(cfg)
42 | p = ShadowMiddleware(p, pShadow)
43 | }
44 |
45 | return
46 | }
47 |
48 | // NewShadowFactory 使用提供的工厂创建一个新的shadowFactory
49 | func NewShadowFactory(f Factory) Factory {
50 | return shadowFactory{f}
51 | }
52 |
53 | // ShadowMiddleware 是一个创建shadowProxy的中间件
54 | func ShadowMiddleware(next ...Proxy) Proxy {
55 | switch len(next) {
56 | case 0:
57 | panic(ErrNotEnoughProxies)
58 | case 1:
59 | return next[0]
60 | case 2:
61 | return NewShadowProxy(next[0], next[1])
62 | default:
63 | panic(ErrTooManyProxies)
64 | }
65 | }
66 |
67 | // NewShadowProxy 返回一个向p1和p2发送请求但忽略p2响应的代理
68 | func NewShadowProxy(p1, p2 Proxy) Proxy {
69 | return func(ctx context.Context, request *Request) (*Response, error) {
70 | go p2(newcontextWrapper(ctx), CloneRequest(request))
71 | return p1(ctx, request)
72 | }
73 | }
74 |
75 | func isShadowBackend(c *config.Backend) bool {
76 | if v, ok := c.ExtraConfig[Namespace]; ok {
77 | if e, ok := v.(map[string]interface{}); ok {
78 | if v, ok := e[shadowKey]; ok {
79 | c, ok := v.(bool)
80 | return ok && c
81 | }
82 | }
83 | }
84 | return false
85 | }
86 |
87 | type contextWrapper struct {
88 | context.Context
89 | data context.Context
90 | }
91 |
92 | func (c contextWrapper) Value(key interface{}) interface{} {
93 | return c.data.Value(key)
94 | }
95 |
96 | func newcontextWrapper(data context.Context) contextWrapper {
97 | return contextWrapper{
98 | Context: context.Background(),
99 | data: data,
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/proxy/static.go:
--------------------------------------------------------------------------------
1 | package proxy
2 |
3 | import (
4 | "context"
5 | "melody/config"
6 | )
7 |
8 | const (
9 | staticKey = "static"
10 |
11 | staticAlwaysStrategy = "always"
12 | staticIfSuccessStrategy = "success"
13 | staticIfErroredStrategy = "errored"
14 | staticIfCompleteStrategy = "complete"
15 | staticIfIncompleteStrategy = "incomplete"
16 | )
17 |
18 | type staticConfig struct {
19 | Data map[string]interface{}
20 | Strategy string
21 | Match func(*Response, error) bool
22 | }
23 |
24 | // NewStaticDataMiddleware 定义了插入静态数据中间件
25 | func NewStaticDataMiddleware(endpoint *config.EndpointConfig) Middleware {
26 | v, ok := getStaticConfig(endpoint.ExtraConfig)
27 | if !ok {
28 | return EmptyMiddleware
29 | }
30 |
31 | return func(proxy ...Proxy) Proxy {
32 | if len(proxy) > 1 {
33 | panic(ErrTooManyProxies)
34 | }
35 | return func(ctx context.Context, request *Request) (response *Response, e error) {
36 | result, err := proxy[0](ctx, request)
37 | if !v.Match(result, err) {
38 | return result, err
39 | }
40 |
41 | if result == nil {
42 | result = &Response{Data: map[string]interface{}{}}
43 | }
44 |
45 | for k, v := range v.Data {
46 | result.Data[k] = v
47 | }
48 |
49 | return result, err
50 | }
51 | }
52 | }
53 |
54 | func getStaticConfig(extraConfig config.ExtraConfig) (staticConfig, bool) {
55 | v, ok := extraConfig[Namespace]
56 | if !ok {
57 | return staticConfig{}, ok
58 | }
59 | e, ok := v.(map[string]interface{})
60 | if !ok {
61 | return staticConfig{}, ok
62 | }
63 | v, ok = e[staticKey]
64 | if !ok {
65 | return staticConfig{}, ok
66 | }
67 | tmp, ok := v.(map[string]interface{})
68 | if !ok {
69 | return staticConfig{}, ok
70 | }
71 | data, ok := tmp["data"].(map[string]interface{})
72 | if !ok {
73 | return staticConfig{}, ok
74 | }
75 |
76 | name, ok := tmp["strategy"].(string)
77 | if !ok {
78 | name = staticAlwaysStrategy
79 | }
80 | cfg := staticConfig{
81 | Data: data,
82 | Strategy: name,
83 | Match: staticAlwaysMatch,
84 | }
85 | switch name {
86 | case staticAlwaysStrategy:
87 | cfg.Match = staticAlwaysMatch
88 | case staticIfSuccessStrategy:
89 | cfg.Match = staticIfSuccessMatch
90 | case staticIfErroredStrategy:
91 | cfg.Match = staticIfErroredMatch
92 | case staticIfCompleteStrategy:
93 | cfg.Match = staticIfCompleteMatch
94 | case staticIfIncompleteStrategy:
95 | cfg.Match = staticIfIncompleteMatch
96 | }
97 | return cfg, true
98 | }
99 |
100 | func staticAlwaysMatch(_ *Response, _ error) bool { return true }
101 | func staticIfSuccessMatch(_ *Response, err error) bool { return err == nil }
102 | func staticIfErroredMatch(_ *Response, err error) bool { return err != nil }
103 | func staticIfCompleteMatch(r *Response, err error) bool {
104 | return err == nil && r != nil && r.IsComplete
105 | }
106 | func staticIfIncompleteMatch(r *Response, _ error) bool { return r == nil || !r.IsComplete }
107 |
--------------------------------------------------------------------------------
/register/internal/untyped.go:
--------------------------------------------------------------------------------
1 | package internal
2 |
3 | import "sync"
4 |
5 | func New() *Untyped {
6 | return &Untyped{
7 | data: map[string]interface{}{},
8 | mutex: &sync.RWMutex{},
9 | }
10 | }
11 |
12 | type Untyped struct {
13 | data map[string]interface{}
14 | mutex *sync.RWMutex
15 | }
16 |
17 | func (u *Untyped) Register(name string, v interface{}) {
18 | u.mutex.Lock()
19 | u.data[name] = v
20 | u.mutex.Unlock()
21 | }
22 |
23 | func (u *Untyped) Get(name string) (interface{}, bool) {
24 | u.mutex.RLock()
25 | v, ok := u.data[name]
26 | u.mutex.RUnlock()
27 | return v, ok
28 | }
29 |
30 | func (u *Untyped) Clone() map[string]interface{} {
31 | u.mutex.RLock()
32 | clone := make(map[string]interface{}, len(u.data))
33 | for k, v := range u.data {
34 | clone[k] = v
35 | }
36 | u.mutex.RUnlock()
37 | return clone
38 | }
39 |
--------------------------------------------------------------------------------
/register/register.go:
--------------------------------------------------------------------------------
1 | package register
2 |
3 | import "melody/register/internal"
4 |
5 | type Untyped interface {
6 | Register(name string, v interface{})
7 | Get(name string) (interface{}, bool)
8 | Clone() map[string]interface{}
9 | }
10 |
11 | func New() Untyped {
12 | return internal.New()
13 | }
14 |
15 | func NewSpaces() *Namespaced {
16 | return &Namespaced{New()}
17 | }
18 |
19 | type Namespaced struct {
20 | data Untyped
21 | }
22 |
23 | func (n *Namespaced) Get(namespace string) (Untyped, bool) {
24 | v, ok := n.data.Get(namespace)
25 | if !ok {
26 | return nil, ok
27 | }
28 | register, ok := v.(Untyped)
29 | return register, ok
30 | }
31 |
32 | func (n *Namespaced) Register(namespace, name string, v interface{}) {
33 | if register, ok := n.Get(namespace); ok {
34 | register.Register(name, v)
35 | return
36 | }
37 |
38 | register := New()
39 | register.Register(name, v)
40 | n.data.Register(namespace, register)
41 | }
42 |
43 | func (n *Namespaced) AddNamespace(namespace string) {
44 | if _, ok := n.Get(namespace); ok {
45 | return
46 | }
47 | n.data.Register(namespace, New())
48 | }
49 |
--------------------------------------------------------------------------------
/register/register_test.go:
--------------------------------------------------------------------------------
1 | package register
2 |
3 | import "testing"
4 |
5 | func Test(t *testing.T) {
6 | r := NewSpaces()
7 | r.Register("namespace1", "name1", 42)
8 | r.AddNamespace("namespace1")
9 | r.AddNamespace("namespace2")
10 | r.Register("namespace2", "name2", true)
11 |
12 | nr, ok := r.Get("namespace1")
13 | if !ok {
14 | t.Error("namespace1 not found")
15 | return
16 | }
17 | if _, ok := nr.Get("name2"); ok {
18 | t.Error("name2 found into namespace1")
19 | return
20 | }
21 | v1, ok := nr.Get("name1")
22 | if !ok {
23 | t.Error("name1 not found")
24 | return
25 | }
26 | if i, ok := v1.(int); !ok || i != 42 {
27 | t.Error("unexpected value:", v1)
28 | }
29 |
30 | nr, ok = r.Get("namespace2")
31 | if !ok {
32 | t.Error("namespace2 not found")
33 | return
34 | }
35 | if _, ok := nr.Get("name1"); ok {
36 | t.Error("name1 found into namespace2")
37 | return
38 | }
39 | v2, ok := nr.Get("name2")
40 | if !ok {
41 | t.Error("name2 not found")
42 | return
43 | }
44 | if b, ok := v2.(bool); !ok || !b {
45 | t.Error("unexpected value:", v2)
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/router/gin/debug.go:
--------------------------------------------------------------------------------
1 | package gin
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 |
6 | "melody/logging"
7 | )
8 |
9 | // DebugHandler creates a dummy handler function, useful for quick integration tests
10 | func DebugHandler(logger logging.Logger) gin.HandlerFunc {
11 | return func(c *gin.Context) {
12 | c.JSON(200, gin.H{
13 | "message": "pong",
14 | })
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/router/router.go:
--------------------------------------------------------------------------------
1 | package router
2 |
3 | import (
4 | "context"
5 | "melody/config"
6 | http "melody/transport/http/server"
7 | )
8 |
9 | const (
10 | HeaderCompleteResponseValue = http.HeaderCompleteResponseValue
11 | HeaderInCompleteResponseValue = http.HeaderIncompleteResponseValue
12 | HeaderCompleteKey = http.HeaderCompleteKey
13 | )
14 |
15 | var (
16 | // DefaultRunServer default run server func
17 | DefaultRunServer = http.RunServer
18 | // PassHeaders 默认放行的请求头
19 | PassHeaders = http.HeadersToSend
20 | // UserAgentHeaderValue 代理的请求头标签值
21 | UserAgentHeaderValue = http.UserAgentHeaderValue
22 | ErrorInternalError = http.ErrorInternalError
23 | DefaultToHTTPError = http.DefaultToHTTPError
24 | InitHTTPDefaultTransport = http.InitHTTPDefaultTransport
25 | )
26 |
27 | // Router 暴露出去的接口
28 | type Router interface {
29 | Run(config.ServiceConfig)
30 | }
31 |
32 | // ToHTTPError change error -> http status code
33 | type ToHTTPError http.ToHTTPError
34 |
35 | // Factory 暴露出去的接口
36 | type Factory interface {
37 | New() Router
38 | NewWithContext(context.Context) Router
39 | }
40 |
--------------------------------------------------------------------------------
/sd/dnssrv/subscriber.go:
--------------------------------------------------------------------------------
1 | // Package dnssrv defines some implementations for a dns based service discovery
2 | package dnssrv
3 |
4 | import (
5 | "fmt"
6 | "melody/config"
7 | "melody/sd"
8 | "net"
9 | "sync"
10 | "time"
11 | )
12 |
13 | // Namespace is the key for the dns sd module
14 | const Namespace = "dns"
15 |
16 | // Register registers the dns sd subscriber factory
17 | func Register() error {
18 | return sd.RegisterSubscriberFactory(Namespace, SubscriberFactory)
19 | }
20 |
21 | var (
22 | // TTL is the duration of the cached data
23 | TTL = 30 * time.Second
24 | // DefaultLookup id the function for the DNS resolution
25 | DefaultLookup = net.LookupSRV
26 | )
27 |
28 | // SubscriberFactory builds a DNS_SRV Subscriber with the received config
29 | func SubscriberFactory(cfg *config.Backend) sd.Subscriber {
30 | return New(cfg.Host[0])
31 | }
32 |
33 | // New creates a DNS subscriber with the default values
34 | func New(name string) sd.Subscriber {
35 | return NewDetailed(name, DefaultLookup, TTL)
36 | }
37 |
38 | // NewDetailed creates a DNS subscriber with the received values
39 | func NewDetailed(name string, lookup lookup, ttl time.Duration) sd.Subscriber {
40 | s := subscriber{name, &sd.FixedSubscriber{}, &sync.Mutex{}, ttl, lookup}
41 | s.update()
42 | go s.loop()
43 | return s
44 | }
45 |
46 | type lookup func(service, proto, name string) (cname string, addrs []*net.SRV, err error)
47 |
48 | type subscriber struct {
49 | name string
50 | cache *sd.FixedSubscriber
51 | mutex *sync.Mutex
52 | ttl time.Duration
53 | lookup lookup
54 | }
55 |
56 | // Hosts implements the subscriber interface
57 | func (s subscriber) Hosts() ([]string, error) {
58 | s.mutex.Lock()
59 | defer s.mutex.Unlock()
60 | return s.cache.Hosts()
61 | }
62 |
63 | func (s subscriber) loop() {
64 | for {
65 | <-time.After(s.ttl)
66 | s.update()
67 | }
68 | }
69 |
70 | func (s subscriber) update() {
71 | instances, err := s.resolve()
72 | if err != nil {
73 | return
74 | }
75 | s.mutex.Lock()
76 | *(s.cache) = sd.FixedSubscriber(instances)
77 | s.mutex.Unlock()
78 | }
79 |
80 | func (s subscriber) resolve() ([]string, error) {
81 | _, addrs, err := s.lookup("", "", s.name)
82 | if err != nil {
83 | return []string{}, err
84 | }
85 | instances := make([]string, len(addrs))
86 | for i, addr := range addrs {
87 | instances[i] = fmt.Sprintf("http://%s", net.JoinHostPort(addr.Target, fmt.Sprint(addr.Port)))
88 | }
89 | return instances, nil
90 | }
91 |
--------------------------------------------------------------------------------
/sd/dnssrv/subscriber_test.go:
--------------------------------------------------------------------------------
1 | package dnssrv
2 |
3 | import (
4 | "errors"
5 | "melody/config"
6 | "melody/sd"
7 | "net"
8 | "testing"
9 | "time"
10 | )
11 |
12 | func TestSubscriber_New(t *testing.T) {
13 | if err := Register(); err != nil {
14 | t.Error("registering the dns module:", err.Error())
15 | }
16 | srvSet := []*net.SRV{
17 | {
18 | Port: 80,
19 | Target: "127.0.0.1",
20 | },
21 | {
22 | Port: 81,
23 | Target: "127.0.0.1",
24 | },
25 | }
26 | DefaultLookup = func(service, proto, name string) (cname string, addrs []*net.SRV, err error) {
27 | return "cname", srvSet, nil
28 | }
29 |
30 | s := sd.GetSubscriber(&config.Backend{Host: []string{"www.baidu.com"}, SD: Namespace})
31 | hosts, err := s.Hosts()
32 |
33 | if err != nil {
34 | t.Error("Getting the hosts:", err.Error())
35 | }
36 | if len(hosts) != 2 {
37 | t.Error("Wrong number of hosts:", len(hosts))
38 | }
39 | if hosts[0] != "http://127.0.0.1:80" {
40 | t.Error("Wrong host #0 (expected http://127.0.0.1:80):", hosts[0])
41 | }
42 | if hosts[1] != "http://127.0.0.1:81" {
43 | t.Error("Wrong host #1 (expected http://127.0.0.1:81):", hosts[1])
44 | }
45 | }
46 |
47 | func TestSubscriber_LoockupError(t *testing.T) {
48 | errToReturn := errors.New("Some random error")
49 | defaultLookup := func(service, proto, name string) (cname string, addrs []*net.SRV, err error) {
50 | return "cname", []*net.SRV{}, errToReturn
51 | }
52 | ttl := 1 * time.Millisecond
53 | s := NewDetailed("some.example.tld", defaultLookup, ttl)
54 | hosts, err := s.Hosts()
55 | if err != nil {
56 | t.Error("Unexpected error!", err)
57 | }
58 | if len(hosts) != 0 {
59 | t.Error("Wrong number of hosts:", len(hosts))
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/sd/loadbalancing.go:
--------------------------------------------------------------------------------
1 | package sd
2 |
3 | import (
4 | "errors"
5 | "runtime"
6 | "sync/atomic"
7 |
8 | "github.com/valyala/fastrand"
9 | )
10 |
11 | // Balancer 用一个负载均衡策略去选择使用哪一个backend
12 | type Balancer interface {
13 | Host() (string, error)
14 | }
15 |
16 | var ErrNoHosts = errors.New("no hosts available")
17 |
18 | // NewBalancer 按照可用 processors 的数量来返回最合适的 balancer
19 | // If GOMAXPROCS = 1, 返回一个轮询LB,因为原子计数器没有竞争
20 | // If GOMAXPROCS > 1, 返回基于CPU数量而优化的伪随机LB
21 | func NewBalancer(subscriber Subscriber) Balancer {
22 | if p := runtime.GOMAXPROCS(-1); p == 1 { // runtime.GOMAXPROCS 返回可同时执行的最大CPU数
23 | return NewRoundRobinLB(subscriber)
24 | }
25 | return NewRandomLB(subscriber)
26 | }
27 |
28 | // NewRoundRobinLB returns a new balancer using a round robin strategy
29 | func NewRoundRobinLB(subscriber Subscriber) Balancer {
30 | if s, ok := subscriber.(FixedSubscriber); ok && len(s) == 1 {
31 | return nopBalancer(s[0])
32 | }
33 | return &roundRobinLB{
34 | balancer: balancer{subscriber: subscriber},
35 | counter: 0,
36 | }
37 | }
38 |
39 | type roundRobinLB struct {
40 | balancer
41 | counter uint64
42 | }
43 |
44 | // Host implements the balancer interface
45 | func (r *roundRobinLB) Host() (string, error) {
46 | hosts, err := r.hosts()
47 | if err != nil {
48 | return "", err
49 | }
50 | // atomic.AddUint64 原子性的将val的值添加到*addr并返回新值
51 | offset := (atomic.AddUint64(&r.counter, 1) - 1) % uint64(len(hosts))
52 | return hosts[offset], nil
53 | }
54 |
55 | // NewRandomLB 使用 fastrand 伪随机数生成器
56 | func NewRandomLB(subscriber Subscriber) Balancer {
57 | if s, ok := subscriber.(FixedSubscriber); ok && len(s) == 1 {
58 | return nopBalancer(s[0])
59 | }
60 | return &randomLB{
61 | balancer: balancer{subscriber: subscriber},
62 | rand: fastrand.Uint32n,
63 | }
64 | }
65 |
66 | type randomLB struct {
67 | balancer
68 | rand func(uint32) uint32
69 | }
70 |
71 | // Host implements the balancer interface
72 | func (r *randomLB) Host() (string, error) {
73 | hosts, err := r.hosts()
74 | if err != nil {
75 | return "", err
76 | }
77 | return hosts[int(r.rand(uint32(len(hosts))))], nil
78 | }
79 |
80 | type balancer struct {
81 | subscriber Subscriber
82 | }
83 |
84 | func (b *balancer) hosts() ([]string, error) {
85 | hs, err := b.subscriber.Hosts()
86 | if err != nil {
87 | return hs, err
88 | }
89 | if len(hs) <= 0 {
90 | return hs, ErrNoHosts
91 | }
92 | return hs, nil
93 | }
94 |
95 | type nopBalancer string
96 |
97 | func (b nopBalancer) Host() (string, error) { return string(b), nil }
98 |
--------------------------------------------------------------------------------
/sd/register.go:
--------------------------------------------------------------------------------
1 | package sd
2 |
3 | import (
4 | "melody/config"
5 | "melody/register"
6 | )
7 |
8 | // RegisterSubscriberFactory registers the received factory
9 | func RegisterSubscriberFactory(name string, sf SubscriberFactory) error {
10 | return subscriberFactories.Register(name, sf)
11 | }
12 |
13 | // GetSubscriber 在此处选择调用哪种服务发现
14 | func GetSubscriber(cfg *config.Backend) Subscriber {
15 | return subscriberFactories.Get(cfg.SD)(cfg)
16 | }
17 |
18 | // GetRegister returns the package register
19 | func GetRegister() *Register {
20 | return subscriberFactories
21 | }
22 |
23 | // Register is a SD register
24 | type Register struct {
25 | data register.Untyped
26 | }
27 |
28 | func initRegister() *Register {
29 | return &Register{register.New()}
30 | }
31 |
32 | // Register implements the RegisterSetter interface
33 | func (r *Register) Register(name string, sf SubscriberFactory) error {
34 | r.data.Register(name, sf)
35 | return nil
36 | }
37 |
38 | // Get implements the RegisterGetter interface
39 | func (r *Register) Get(name string) SubscriberFactory {
40 | tmp, ok := r.data.Get(name)
41 | if !ok {
42 | return FixedSubscriberFactory
43 | }
44 | sf, ok := tmp.(SubscriberFactory)
45 | if !ok {
46 | return FixedSubscriberFactory
47 | }
48 | return sf
49 | }
50 |
51 | var subscriberFactories = initRegister()
52 |
--------------------------------------------------------------------------------
/sd/subscriber.go:
--------------------------------------------------------------------------------
1 | // Package sd defines some interfaces and implementations for service discovery
2 | package sd
3 |
4 | import "melody/config"
5 |
6 | // Subscriber keeps the set of backend hosts up to date
7 | type Subscriber interface {
8 | Hosts() ([]string, error)
9 | }
10 |
11 | // SubscriberFunc type is an adapter to allow the use of ordinary functions as subscribers.
12 | // If f is a function with the appropriate signature, SubscriberFunc(f) is a Subscriber that calls f.
13 | type SubscriberFunc func() ([]string, error)
14 |
15 | // Hosts implements the Subscriber interface by executing the wrapped function
16 | func (f SubscriberFunc) Hosts() ([]string, error) { return f() }
17 |
18 | // FixedSubscriber has a constant set of backend hosts and they never get updated
19 | type FixedSubscriber []string
20 |
21 | // Hosts implements the subscriber interface
22 | func (s FixedSubscriber) Hosts() ([]string, error) { return s, nil }
23 |
24 | // SubscriberFactory builds subscribers with the received config
25 | type SubscriberFactory func(*config.Backend) Subscriber
26 |
27 | // FixedSubscriberFactory builds a FixedSubscriber with the received config
28 | func FixedSubscriberFactory(cfg *config.Backend) Subscriber {
29 | return FixedSubscriber(cfg.Host)
30 | }
31 |
--------------------------------------------------------------------------------
/transport/http/client/executor.go:
--------------------------------------------------------------------------------
1 | package client
2 |
3 | import (
4 | "context"
5 | "net/http"
6 | )
7 |
8 | var defaultHTTPClient = &http.Client{}
9 |
10 | // HTTPRequestExecutor 定义了代理的client,由该方法去实现代理
11 | type HTTPRequestExecutor func(context.Context, *http.Request) (*http.Response, error)
12 |
13 | // HTTPClientFactory 根据context定制client
14 | type HTTPClientFactory func(context.Context) *http.Client
15 |
16 | func NewHTTPClient(context.Context) *http.Client {
17 | return defaultHTTPClient
18 | }
19 |
20 | // DefaultHTTPRequestExecutor 默认的request执行器
21 | func DefaultHTTPRequestExecutor(clientFactory HTTPClientFactory) HTTPRequestExecutor {
22 | return func(ctx context.Context, request *http.Request) (response *http.Response, e error) {
23 | return clientFactory(ctx).Do(request.WithContext(ctx))
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/transport/http/client/status.go:
--------------------------------------------------------------------------------
1 | package client
2 |
3 | import (
4 | "bytes"
5 | "context"
6 | "errors"
7 | "io/ioutil"
8 | "melody/config"
9 | "net/http"
10 | )
11 |
12 | const Namespace = "melody_http"
13 |
14 | var ErrInvalidStatusCode = errors.New("Invalid status code")
15 |
16 | // HTTPStatusHandler 将接受到的response中status code格式化
17 | type HTTPStatusHandler func(context.Context, *http.Response) (*http.Response, error)
18 |
19 | // HTTPResponseError 在某个Backend发生错误时,将封装进该对象的实例
20 | type HTTPResponseError struct {
21 | Code int `json:"http_status_code"`
22 | Msg string `json:"http_body,omitempty"`
23 | name string
24 | }
25 |
26 | // NoOpHTTPStatusHandler 空实现
27 | func NoOpHTTPStatusHandler(_ context.Context, resp *http.Response) (*http.Response, error) {
28 | return resp, nil
29 | }
30 |
31 | func GetHTTPStatusHandler(remote *config.Backend) HTTPStatusHandler {
32 | if e, ok := remote.ExtraConfig[Namespace]; ok {
33 | if m, ok := e.(map[string]interface{}); ok {
34 | if v, ok := m["return_error_details"]; ok {
35 | if b, ok := v.(string); ok && b != "" {
36 | return DetailedHTTPStatusHandler(DefaultHTTPStatusHandler, b)
37 | }
38 | }
39 | }
40 | }
41 | return DefaultHTTPStatusHandler
42 | }
43 |
44 | // DetailedHTTPStatusHandler
45 | func DetailedHTTPStatusHandler(next HTTPStatusHandler, name string) HTTPStatusHandler {
46 | return func(ctx context.Context, resp *http.Response) (*http.Response, error) {
47 | if r, err := next(ctx, resp); err == nil {
48 | return r, nil
49 | }
50 |
51 | body, err := ioutil.ReadAll(resp.Body)
52 | if err != nil {
53 | body = []byte{}
54 | }
55 | resp.Body.Close()
56 | resp.Body = ioutil.NopCloser(bytes.NewBuffer(body))
57 |
58 | return resp, HTTPResponseError{
59 | Code: resp.StatusCode,
60 | Msg: string(body),
61 | name: name,
62 | }
63 | }
64 | }
65 |
66 | func DefaultHTTPStatusHandler(ctx context.Context, resp *http.Response) (*http.Response, error) {
67 | if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {
68 | return nil, ErrInvalidStatusCode
69 | }
70 |
71 | return resp, nil
72 | }
73 |
74 | // Error returns the error message
75 | func (r HTTPResponseError) Error() string {
76 | return r.Msg
77 | }
78 |
79 | // Name returns the name of the error
80 | func (r HTTPResponseError) Name() string {
81 | return r.name
82 | }
83 |
84 | // StatusCode returns the status code returned by the backend
85 | func (r HTTPResponseError) StatusCode() int {
86 | return r.Code
87 | }
88 |
--------------------------------------------------------------------------------
/transport/http/server/plugin/plugin.go:
--------------------------------------------------------------------------------
1 | package plugin
2 |
3 | import (
4 | "context"
5 | "melody/register"
6 | "net/http"
7 | )
8 |
9 | var serverRegister = register.NewSpaces()
10 |
11 | // RegisterHandler ...
12 | func RegisterHandler(
13 | name string,
14 | handler func(context.Context, map[string]interface{}, http.Handler) (http.Handler, error),
15 | ) {
16 | serverRegister.Register(Namespace, name, handler)
17 | }
18 |
--------------------------------------------------------------------------------
/transport/http/server/plugin/server.go:
--------------------------------------------------------------------------------
1 | package plugin
2 |
3 | import (
4 | "context"
5 | "melody/config"
6 | "melody/logging"
7 | "net/http"
8 | )
9 |
10 | // Namespace 是否运行使用server handler
11 | const Namespace = "http_server_handler"
12 |
13 | // RunServer 定义了运行http.Server的函数结构
14 | type RunServer func(context.Context, config.ServiceConfig, http.Handler) error
15 |
16 | // New 返回下一个RunServer
17 | func New(logger logging.Logger, next RunServer) RunServer {
18 | return func(ctx context.Context, cfg config.ServiceConfig, handler http.Handler) error {
19 | // 根据配置文件检察是否开启handler
20 | v, ok := cfg.ExtraConfig[Namespace]
21 | if !ok {
22 | logger.Debug("http server handler: no extra config")
23 | return next(ctx, cfg, handler)
24 | }
25 | extra, ok := v.(map[string]interface{})
26 | if !ok {
27 | logger.Debug("http server handler: wrong extra config type")
28 | return next(ctx, cfg, handler)
29 | }
30 |
31 | // 加载插件
32 | r, ok := serverRegister.Get(Namespace)
33 | if !ok {
34 | logger.Debug("http server handler: no plugins registered for the module")
35 | return next(ctx, cfg, handler)
36 | }
37 |
38 | name, nameOk := extra["name"].(string)
39 | fifoRaw, fifoOk := extra["name"].([]interface{})
40 | if !nameOk && !fifoOk {
41 | logger.Debug("http server handler: no plugins required in the extra config")
42 | return next(ctx, cfg, handler)
43 | }
44 |
45 | fifo := []string{}
46 |
47 | if !fifoOk {
48 | fifo = []string{name}
49 | } else {
50 | for _, x := range fifoRaw {
51 | if v, ok := x.(string); ok {
52 | fifo = append(fifo, v)
53 | }
54 | }
55 | }
56 |
57 | for _, name := range fifo {
58 | rawHf, ok := r.Get(name)
59 | if !ok {
60 | logger.Debug("http server handler: no plugin resgistered as", name)
61 | return next(ctx, cfg, handler)
62 | }
63 |
64 | hf, ok := rawHf.(func(context.Context, map[string]interface{}, http.Handler) (http.Handler, error))
65 | if !ok {
66 | logger.Warning("http server handler: wrong plugin handler type:", name)
67 | return next(ctx, cfg, handler)
68 | }
69 |
70 | handlerWrapper, err := hf(context.Background(), extra, handler)
71 | if err != nil {
72 | logger.Warning("http server handler: error getting the plugin handler:", err.Error())
73 | return next(ctx, cfg, handler)
74 | }
75 |
76 | logger.Debug("http server handler: injecting plugin", name)
77 | handler = handlerWrapper
78 | }
79 |
80 | return next(ctx, cfg, handler)
81 | }
82 | }
83 |
--------------------------------------------------------------------------------