├── .coveralls.yml ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── client ├── client.go ├── grpc_pool.go ├── grpc_pool_test.go ├── helloworld │ ├── helloworld.pb.go │ └── helloworld.proto ├── naming_client.go ├── option.go └── resolver.go ├── example ├── Makefile ├── README.md ├── apidoc │ └── proto │ │ └── hello │ │ └── hello.proto ├── go.mod ├── go.sum ├── hello.go ├── main.go ├── out │ ├── ff.json │ ├── gen.json │ ├── importPath.json │ └── info.json ├── proto_install.sh ├── proto_source_install.sh └── rpc │ ├── hello.pb.gmsec.go │ └── hello.pb.go ├── global.go ├── go.mod ├── go.sum ├── grpc_opentracing ├── client_interceptors.go ├── doc.go ├── id_extract.go ├── id_extract_test.go ├── interceptors_test.go ├── metadata.go ├── options.go └── server_interceptors.go ├── mdns ├── client.go ├── dns_sd.go ├── dns_sd_test.go ├── server.go ├── server_test.go ├── zone.go └── zone_test.go ├── micro.go ├── micro_test.go ├── naming └── naming.go ├── option.go ├── profile ├── http │ └── http.go ├── pprof │ └── pprof.go └── profile.go ├── registry ├── dns_naming_register.go ├── dns_naming_register_test.go ├── options.go └── register.go ├── server ├── naming_server.go ├── option.go └── server.go └── tracer ├── jaeger.go └── jaeger_test.go /.coveralls.yml: -------------------------------------------------------------------------------- 1 | service_name: travis-pro 2 | repo_token: jJ9wMIhkx9S05vXLUghjQ9HG8F0wPsz3T -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | tmp/ 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - 1.13.x 4 | - master 5 | 6 | go_import_path: github.com/gmsec/micro 7 | 8 | before_install: 9 | - go get -t -v ./... 10 | 11 | script: 12 | - go test -race -coverprofile=coverage.txt -covermode=atomic 13 | 14 | after_success: 15 | - bash <(curl -s https://codecov.io/bash) 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/gmsec/micro.svg?branch=master)](https://travis-ci.org/gmsec/micro) 2 | [![Go Report Card](https://goreportcard.com/badge/github.com/gmsec/micro)](https://goreportcard.com/report/github.com/gmsec/micro) 3 | [![GoDoc](https://godoc.org/github.com/gmsec/micro?status.svg)](https://godoc.org/github.com/gmsec/micro) 4 | [![Mentioned in Awesome Go](https://awesome.re/mentioned-badge.svg)](https://github.com/avelino/awesome-go) 5 | 6 | 7 | # micro 8 | A Go distributed systems development framework 9 | 10 | framework eference resources base on [go-micro](https://github.com/micro/go-micro) 11 | 12 | # support list 13 | 14 | - [grpc-balancer](https://github.com/grpc/grpc-go/tree/master/balancer) 15 | - [grpc-naming](https://github.com/grpc/grpc-go/tree/v1.2.x/naming) 16 | - [dns](github.com/micro/mdns) 17 | - [pool](https://github.com/micro/go-micro/blob/master/client/grpc/grpc_pool.go) 18 | - [etcdv3](https://github.com/etcd-io/etcd) 19 | - [ipaddr](https://github.com/gmsec/micro/tree/master/example/main.go#80) It can be directly connected through IPaddr 20 | 21 | # install 22 | 23 | - code tools: [protoc-gen-gmsec](https://github.com/gmsec/protoc-gen-gmsec) 24 | 25 | - example :[example](https://github.com/gmsec/micro/tree/master/example) 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /client/client.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | // Client is the interface used to make requests to services. 8 | // It supports Request/Response via Transport and Publishing via the Broker. 9 | // It also supports bidirectional streaming of requests. 10 | type Client interface { 11 | Init(...Option) error 12 | Options() Options 13 | String() string 14 | Next() (*poolConn, error) // next connon 15 | //IsIpAddr() bool 16 | // Copy() *Client 17 | } 18 | 19 | // Option used by the Client 20 | type Option func(*Options) 21 | type poolMaxStreams struct{} 22 | type poolMaxIdle struct{} 23 | 24 | var ( 25 | // DefaultPoolMaxStreams maximum streams on a connectioin 26 | // (20) 27 | DefaultPoolMaxStreams = 20 28 | 29 | // DefaultPoolMaxIdle maximum idle conns of a pool 30 | // (50) 31 | DefaultPoolMaxIdle = 50 32 | // // DefaultNamingClient is a default client to use out of the box 33 | // DefaultNamingClient Client = newNamingClient() 34 | // // DefaultIPAddrClient is a default client to use addr to connection 35 | // DefaultIPAddrClient Client = newIPAddrClient() 36 | // DefaultRetries is the default number of times a request is tried 37 | DefaultRetries = 1 38 | // DefaultRequestTimeout is the default request timeout 39 | DefaultRequestTimeout = time.Second * 5 40 | // DefaultPoolSize sets the connection pool size 41 | DefaultPoolSize = 100 42 | // DefaultPoolTTL sets the connection pool ttl 43 | DefaultPoolTTL = time.Minute 44 | 45 | // DefaultPoolTimeout sets the connection pool ttl 46 | DefaultPoolTimeout = 5 * time.Second 47 | 48 | // NewClient returns a new client 49 | NewClient func(...Option) Client = newNamingClient 50 | // NewIPAddrClient returns a new client 51 | NewIPAddrClient func(...Option) Client = newIPAddrClient 52 | ) 53 | 54 | func newNamingClient(opts ...Option) Client { 55 | options := newOptions(opts...) 56 | 57 | // if options.Registry == nil { 58 | // options.Registry = ®istry.Registry{ 59 | // RegNaming: registry.NewDNSNamingRegistry(), 60 | // } 61 | // } 62 | 63 | rc := &namingResolver{ 64 | opts: options, 65 | // register:registry.NewDNSNamingRegistry() 66 | // router: router, 67 | // handlers: make(map[string]Handler), 68 | // subscribers: make(map[Subscriber][]broker.Subscriber), 69 | // exit: make(chan chan error), 70 | // wg: wait(options.Context), 71 | } 72 | // rc.once.Store(false) 73 | rc.pool = newPool(options.PoolSize, options.PoolTTL, rc.poolMaxIdle(), rc.poolMaxStreams(), false, options.TimeOut) 74 | 75 | return rc 76 | } 77 | 78 | func newIPAddrClient(opts ...Option) Client { 79 | options := newOptions(opts...) 80 | rc := &namingResolver{ 81 | //opts: options, 82 | // register:registry.NewDNSNamingRegistry() 83 | // router: router, 84 | // handlers: make(map[string]Handler), 85 | // subscribers: make(map[Subscriber][]broker.Subscriber), 86 | // exit: make(chan chan error), 87 | // wg: wait(options.Context), 88 | } 89 | // rc.once.Store(false) 90 | rc.pool = newPool(options.PoolSize, options.PoolTTL, rc.poolMaxIdle(), rc.poolMaxStreams(), true, options.TimeOut) 91 | 92 | return rc 93 | } 94 | -------------------------------------------------------------------------------- /client/grpc_pool.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "sync" 7 | "time" 8 | 9 | "google.golang.org/grpc" 10 | "google.golang.org/grpc/connectivity" 11 | ) 12 | 13 | type pool struct { 14 | timeOut time.Duration 15 | size int 16 | ttl int64 17 | 18 | // max streams on a *poolConn 19 | maxStreams int 20 | // max idle conns 21 | maxIdle int 22 | 23 | sync.Mutex 24 | conns map[string]*streamsPool 25 | } 26 | 27 | type streamsPool struct { 28 | // head of list 29 | head *poolConn 30 | // busy conns list 31 | busy *poolConn 32 | // the siza of list 33 | count int 34 | // idle conn 35 | idle int 36 | } 37 | 38 | type poolConn struct { 39 | // grpc conn 40 | *grpc.ClientConn 41 | err error 42 | addr string 43 | 44 | // pool and streams pool 45 | pool *pool 46 | sp *streamsPool 47 | streams int 48 | created int64 49 | 50 | // list 51 | pre *poolConn 52 | next *poolConn 53 | in bool 54 | } 55 | 56 | func newPool(size int, ttl time.Duration, idle int, ms int, isIPAddr bool, timeOut time.Duration) *pool { 57 | if ms <= 0 { 58 | ms = 1 59 | } 60 | if idle < 0 { 61 | idle = 0 62 | } 63 | return &pool{ 64 | size: size, 65 | ttl: int64(ttl.Seconds()), 66 | timeOut: timeOut, 67 | maxStreams: ms, 68 | maxIdle: idle, 69 | conns: make(map[string]*streamsPool), 70 | } 71 | } 72 | 73 | func (p *pool) getConn(addr string, opts ...grpc.DialOption) (*poolConn, error) { 74 | now := time.Now().Unix() 75 | p.Lock() 76 | sp, ok := p.conns[addr] 77 | if !ok { 78 | sp = &streamsPool{head: &poolConn{}, busy: &poolConn{}, count: 0, idle: 0} 79 | p.conns[addr] = sp 80 | } 81 | // while we have conns check streams and then return one 82 | // otherwise we'll create a new conn 83 | conn := sp.head.next 84 | for conn != nil { 85 | // check conn state 86 | // https://github.com/grpc/grpc/blob/master/doc/connectivity-semantics-and-api.md 87 | switch conn.GetState() { 88 | case connectivity.Connecting: 89 | conn = conn.next 90 | continue 91 | case connectivity.Shutdown: 92 | next := conn.next 93 | if conn.streams == 0 { 94 | removeConn(conn) 95 | sp.idle-- 96 | } 97 | conn = next 98 | continue 99 | case connectivity.TransientFailure: 100 | next := conn.next 101 | if conn.streams == 0 { 102 | removeConn(conn) 103 | conn.ClientConn.Close() 104 | sp.idle-- 105 | } 106 | conn = next 107 | continue 108 | case connectivity.Ready: 109 | case connectivity.Idle: 110 | } 111 | // a old conn 112 | if now-conn.created > p.ttl { 113 | next := conn.next 114 | if conn.streams == 0 { 115 | removeConn(conn) 116 | conn.ClientConn.Close() 117 | sp.idle-- 118 | } 119 | conn = next 120 | continue 121 | } 122 | // a busy conn 123 | if conn.streams >= p.maxStreams { 124 | next := conn.next 125 | removeConn(conn) 126 | addConnAfter(conn, sp.busy) 127 | conn = next 128 | continue 129 | } 130 | // a idle conn 131 | if conn.streams == 0 { 132 | sp.idle-- 133 | } 134 | // a good conn 135 | conn.streams++ 136 | p.Unlock() 137 | return conn, nil 138 | } 139 | p.Unlock() 140 | 141 | // create new conn 142 | ctx, cel := context.WithTimeout(context.Background(), p.timeOut) 143 | defer cel() 144 | cc, err := grpc.DialContext(ctx, addr, opts...) 145 | if err != nil { 146 | //if err == context.DeadlineExceeded { // 超时 147 | return nil, fmt.Errorf("[%v]:%v", addr, err.Error()) 148 | } 149 | conn = &poolConn{cc, nil, addr, p, sp, 1, time.Now().Unix(), nil, nil, false} 150 | 151 | // add conn to streams pool 152 | p.Lock() 153 | if sp.count < p.size { 154 | addConnAfter(conn, sp.head) 155 | } 156 | p.Unlock() 157 | 158 | return conn, nil 159 | } 160 | 161 | func (p *pool) release(addr string, conn *poolConn, err error) { 162 | p.Lock() 163 | p, sp, created := conn.pool, conn.sp, conn.created 164 | // try to add conn 165 | if !conn.in && sp.count < p.size { 166 | addConnAfter(conn, sp.head) 167 | } 168 | if !conn.in { 169 | p.Unlock() 170 | conn.ClientConn.Close() 171 | return 172 | } 173 | // a busy conn 174 | if conn.streams >= p.maxStreams { 175 | removeConn(conn) 176 | addConnAfter(conn, sp.head) 177 | } 178 | conn.streams-- 179 | // if streams == 0, we can do something 180 | if conn.streams == 0 { 181 | // 1. it has errored 182 | // 2. too many idle conn or 183 | // 3. conn is too old 184 | now := time.Now().Unix() 185 | if err != nil || sp.idle >= p.maxIdle || now-created > p.ttl { 186 | removeConn(conn) 187 | p.Unlock() 188 | conn.ClientConn.Close() 189 | return 190 | } 191 | sp.idle++ 192 | } 193 | p.Unlock() 194 | return 195 | } 196 | 197 | func (conn *poolConn) Close() { 198 | conn.pool.release(conn.addr, conn, conn.err) 199 | } 200 | 201 | func removeConn(conn *poolConn) { 202 | if conn.pre != nil { 203 | conn.pre.next = conn.next 204 | } 205 | if conn.next != nil { 206 | conn.next.pre = conn.pre 207 | } 208 | conn.pre = nil 209 | conn.next = nil 210 | conn.in = false 211 | conn.sp.count-- 212 | return 213 | } 214 | 215 | func addConnAfter(conn *poolConn, after *poolConn) { 216 | conn.next = after.next 217 | conn.pre = after 218 | if after.next != nil { 219 | after.next.pre = conn 220 | } 221 | after.next = conn 222 | conn.in = true 223 | conn.sp.count++ 224 | return 225 | } 226 | -------------------------------------------------------------------------------- /client/grpc_pool_test.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net" 7 | "testing" 8 | "time" 9 | 10 | pb "github.com/gmsec/micro/client/helloworld" 11 | "google.golang.org/grpc" 12 | pgrpc "google.golang.org/grpc" 13 | ) 14 | 15 | // server is used to implement helloworld.GreeterServer. 16 | type greeterServer struct{} 17 | 18 | // SayHello implements helloworld.GreeterServer 19 | func (g *greeterServer) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) { 20 | if in.Name == "Error" { 21 | return nil, fmt.Errorf("error") 22 | } 23 | return &pb.HelloReply{Message: "Hello " + in.Name}, nil 24 | } 25 | 26 | func testPool(t *testing.T, size int, ttl time.Duration, idle int, ms int) { 27 | // setup server 28 | l, err := net.Listen("tcp", ":0") 29 | if err != nil { 30 | t.Fatalf("failed to listen: %v", err) 31 | } 32 | defer l.Close() 33 | 34 | s := pgrpc.NewServer() 35 | pb.RegisterGreeterServer(s, &greeterServer{}) 36 | 37 | go s.Serve(l) 38 | defer s.Stop() 39 | 40 | // zero pool 41 | p := newPool(size, ttl, idle, ms, false, 3*time.Second) 42 | 43 | for i := 0; i < 10; i++ { 44 | // get a conn 45 | cc, err := p.getConn(l.Addr().String(), grpc.WithInsecure()) 46 | if err != nil { 47 | t.Fatal(err) 48 | } 49 | 50 | rsp := pb.HelloReply{} 51 | 52 | err = cc.Invoke(context.TODO(), "/helloworld.Greeter/SayHello", &pb.HelloRequest{Name: "John"}, &rsp) 53 | if err != nil { 54 | t.Fatal(err) 55 | } 56 | 57 | if rsp.Message != "Hello John" { 58 | t.Fatalf("Got unexpected response %v", rsp.Message) 59 | } 60 | 61 | // release the conn 62 | p.release(l.Addr().String(), cc, nil) 63 | 64 | p.Lock() 65 | if i := p.conns[l.Addr().String()].count; i > size { 66 | p.Unlock() 67 | t.Fatalf("pool size %d is greater than expected %d", i, size) 68 | } 69 | p.Unlock() 70 | } 71 | } 72 | 73 | func TestGRPCPool(t *testing.T) { 74 | testPool(t, 0, time.Minute, 10, 2) 75 | testPool(t, 2, time.Minute, 10, 1) 76 | } 77 | -------------------------------------------------------------------------------- /client/helloworld/helloworld.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // source: helloworld.proto 3 | 4 | package helloworld 5 | 6 | import ( 7 | context "context" 8 | fmt "fmt" 9 | proto "github.com/golang/protobuf/proto" 10 | grpc "google.golang.org/grpc" 11 | codes "google.golang.org/grpc/codes" 12 | status "google.golang.org/grpc/status" 13 | math "math" 14 | ) 15 | 16 | // Reference imports to suppress errors if they are not otherwise used. 17 | var _ = proto.Marshal 18 | var _ = fmt.Errorf 19 | var _ = math.Inf 20 | 21 | // This is a compile-time assertion to ensure that this generated file 22 | // is compatible with the proto package it is being compiled against. 23 | // A compilation error at this line likely means your copy of the 24 | // proto package needs to be updated. 25 | const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package 26 | 27 | // The request message containing the user's name. 28 | type HelloRequest struct { 29 | Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` 30 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 31 | XXX_unrecognized []byte `json:"-"` 32 | XXX_sizecache int32 `json:"-"` 33 | } 34 | 35 | func (m *HelloRequest) Reset() { *m = HelloRequest{} } 36 | func (m *HelloRequest) String() string { return proto.CompactTextString(m) } 37 | func (*HelloRequest) ProtoMessage() {} 38 | func (*HelloRequest) Descriptor() ([]byte, []int) { 39 | return fileDescriptor_17b8c58d586b62f2, []int{0} 40 | } 41 | 42 | func (m *HelloRequest) XXX_Unmarshal(b []byte) error { 43 | return xxx_messageInfo_HelloRequest.Unmarshal(m, b) 44 | } 45 | func (m *HelloRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 46 | return xxx_messageInfo_HelloRequest.Marshal(b, m, deterministic) 47 | } 48 | func (m *HelloRequest) XXX_Merge(src proto.Message) { 49 | xxx_messageInfo_HelloRequest.Merge(m, src) 50 | } 51 | func (m *HelloRequest) XXX_Size() int { 52 | return xxx_messageInfo_HelloRequest.Size(m) 53 | } 54 | func (m *HelloRequest) XXX_DiscardUnknown() { 55 | xxx_messageInfo_HelloRequest.DiscardUnknown(m) 56 | } 57 | 58 | var xxx_messageInfo_HelloRequest proto.InternalMessageInfo 59 | 60 | func (m *HelloRequest) GetName() string { 61 | if m != nil { 62 | return m.Name 63 | } 64 | return "" 65 | } 66 | 67 | // The response message containing the greetings 68 | type HelloReply struct { 69 | Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` 70 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 71 | XXX_unrecognized []byte `json:"-"` 72 | XXX_sizecache int32 `json:"-"` 73 | } 74 | 75 | func (m *HelloReply) Reset() { *m = HelloReply{} } 76 | func (m *HelloReply) String() string { return proto.CompactTextString(m) } 77 | func (*HelloReply) ProtoMessage() {} 78 | func (*HelloReply) Descriptor() ([]byte, []int) { 79 | return fileDescriptor_17b8c58d586b62f2, []int{1} 80 | } 81 | 82 | func (m *HelloReply) XXX_Unmarshal(b []byte) error { 83 | return xxx_messageInfo_HelloReply.Unmarshal(m, b) 84 | } 85 | func (m *HelloReply) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 86 | return xxx_messageInfo_HelloReply.Marshal(b, m, deterministic) 87 | } 88 | func (m *HelloReply) XXX_Merge(src proto.Message) { 89 | xxx_messageInfo_HelloReply.Merge(m, src) 90 | } 91 | func (m *HelloReply) XXX_Size() int { 92 | return xxx_messageInfo_HelloReply.Size(m) 93 | } 94 | func (m *HelloReply) XXX_DiscardUnknown() { 95 | xxx_messageInfo_HelloReply.DiscardUnknown(m) 96 | } 97 | 98 | var xxx_messageInfo_HelloReply proto.InternalMessageInfo 99 | 100 | func (m *HelloReply) GetMessage() string { 101 | if m != nil { 102 | return m.Message 103 | } 104 | return "" 105 | } 106 | 107 | func init() { 108 | proto.RegisterType((*HelloRequest)(nil), "helloworld.HelloRequest") 109 | proto.RegisterType((*HelloReply)(nil), "helloworld.HelloReply") 110 | } 111 | 112 | func init() { proto.RegisterFile("helloworld.proto", fileDescriptor_17b8c58d586b62f2) } 113 | 114 | var fileDescriptor_17b8c58d586b62f2 = []byte{ 115 | // 175 bytes of a gzipped FileDescriptorProto 116 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0xc8, 0x48, 0xcd, 0xc9, 117 | 0xc9, 0x2f, 0xcf, 0x2f, 0xca, 0x49, 0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x42, 0x88, 118 | 0x28, 0x29, 0x71, 0xf1, 0x78, 0x80, 0x78, 0x41, 0xa9, 0x85, 0xa5, 0xa9, 0xc5, 0x25, 0x42, 0x42, 119 | 0x5c, 0x2c, 0x79, 0x89, 0xb9, 0xa9, 0x12, 0x8c, 0x0a, 0x8c, 0x1a, 0x9c, 0x41, 0x60, 0xb6, 0x92, 120 | 0x1a, 0x17, 0x17, 0x54, 0x4d, 0x41, 0x4e, 0xa5, 0x90, 0x04, 0x17, 0x7b, 0x6e, 0x6a, 0x71, 0x71, 121 | 0x62, 0x3a, 0x4c, 0x11, 0x8c, 0x6b, 0xe4, 0xc9, 0xc5, 0xee, 0x5e, 0x94, 0x9a, 0x5a, 0x92, 0x5a, 122 | 0x24, 0x64, 0xc7, 0xc5, 0x11, 0x9c, 0x58, 0x09, 0xd6, 0x25, 0x24, 0xa1, 0x87, 0xe4, 0x02, 0x64, 123 | 0xcb, 0xa4, 0xc4, 0xb0, 0xc8, 0x14, 0xe4, 0x54, 0x2a, 0x31, 0x38, 0x19, 0x70, 0x49, 0x67, 0xe6, 124 | 0xeb, 0xa5, 0x17, 0x15, 0x24, 0xeb, 0xa5, 0x56, 0x24, 0xe6, 0x16, 0xe4, 0xa4, 0x16, 0x23, 0xa9, 125 | 0x75, 0xe2, 0x07, 0x2b, 0x0e, 0x07, 0xb1, 0x03, 0x40, 0x5e, 0x0a, 0x60, 0x4c, 0x62, 0x03, 0xfb, 126 | 0xcd, 0x18, 0x10, 0x00, 0x00, 0xff, 0xff, 0x0f, 0xb7, 0xcd, 0xf2, 0xef, 0x00, 0x00, 0x00, 127 | } 128 | 129 | // Reference imports to suppress errors if they are not otherwise used. 130 | var _ context.Context 131 | var _ grpc.ClientConnInterface 132 | 133 | // This is a compile-time assertion to ensure that this generated file 134 | // is compatible with the grpc package it is being compiled against. 135 | const _ = grpc.SupportPackageIsVersion6 136 | 137 | // GreeterClient is the client API for Greeter service. 138 | // 139 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. 140 | type GreeterClient interface { 141 | // Sends a greeting 142 | SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) 143 | } 144 | 145 | type greeterClient struct { 146 | cc grpc.ClientConnInterface 147 | } 148 | 149 | func NewGreeterClient(cc grpc.ClientConnInterface) GreeterClient { 150 | return &greeterClient{cc} 151 | } 152 | 153 | func (c *greeterClient) SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) { 154 | out := new(HelloReply) 155 | err := c.cc.Invoke(ctx, "/helloworld.Greeter/SayHello", in, out, opts...) 156 | if err != nil { 157 | return nil, err 158 | } 159 | return out, nil 160 | } 161 | 162 | // GreeterServer is the server API for Greeter service. 163 | type GreeterServer interface { 164 | // Sends a greeting 165 | SayHello(context.Context, *HelloRequest) (*HelloReply, error) 166 | } 167 | 168 | // UnimplementedGreeterServer can be embedded to have forward compatible implementations. 169 | type UnimplementedGreeterServer struct { 170 | } 171 | 172 | func (*UnimplementedGreeterServer) SayHello(ctx context.Context, req *HelloRequest) (*HelloReply, error) { 173 | return nil, status.Errorf(codes.Unimplemented, "method SayHello not implemented") 174 | } 175 | 176 | func RegisterGreeterServer(s *grpc.Server, srv GreeterServer) { 177 | s.RegisterService(&_Greeter_serviceDesc, srv) 178 | } 179 | 180 | func _Greeter_SayHello_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 181 | in := new(HelloRequest) 182 | if err := dec(in); err != nil { 183 | return nil, err 184 | } 185 | if interceptor == nil { 186 | return srv.(GreeterServer).SayHello(ctx, in) 187 | } 188 | info := &grpc.UnaryServerInfo{ 189 | Server: srv, 190 | FullMethod: "/helloworld.Greeter/SayHello", 191 | } 192 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 193 | return srv.(GreeterServer).SayHello(ctx, req.(*HelloRequest)) 194 | } 195 | return interceptor(ctx, in, info, handler) 196 | } 197 | 198 | var _Greeter_serviceDesc = grpc.ServiceDesc{ 199 | ServiceName: "helloworld.Greeter", 200 | HandlerType: (*GreeterServer)(nil), 201 | Methods: []grpc.MethodDesc{ 202 | { 203 | MethodName: "SayHello", 204 | Handler: _Greeter_SayHello_Handler, 205 | }, 206 | }, 207 | Streams: []grpc.StreamDesc{}, 208 | Metadata: "helloworld.proto", 209 | } 210 | -------------------------------------------------------------------------------- /client/helloworld/helloworld.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2015 gRPC authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | option java_multiple_files = true; 18 | option java_package = "io.grpc.examples.helloworld"; 19 | option java_outer_classname = "HelloWorldProto"; 20 | 21 | package helloworld; 22 | 23 | // The greeting service definition. 24 | service Greeter { 25 | // Sends a greeting 26 | rpc SayHello (HelloRequest) returns (HelloReply) {} 27 | } 28 | 29 | // The request message containing the user's name. 30 | message HelloRequest { 31 | string name = 1; 32 | } 33 | 34 | // The response message containing the greetings 35 | message HelloReply { 36 | string message = 1; 37 | } 38 | -------------------------------------------------------------------------------- /client/naming_client.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | 7 | "github.com/gmsec/micro/registry" 8 | "github.com/gmsec/micro/tracer" 9 | 10 | "github.com/xxjwxc/public/mylog" 11 | "github.com/xxjwxc/public/tools" 12 | 13 | grpc_opentracing "github.com/grpc-ecosystem/go-grpc-middleware/tracing/opentracing" 14 | "google.golang.org/grpc" 15 | "google.golang.org/grpc/resolver" 16 | ) 17 | 18 | type namingResolver struct { 19 | opts Options 20 | sync.RWMutex 21 | pool *pool 22 | once sync.Once 23 | // marks the serve as started 24 | } 25 | 26 | func (c *namingResolver) Init(opts ...Option) error { 27 | size := c.opts.PoolSize 28 | ttl := c.opts.PoolTTL 29 | 30 | c.Lock() 31 | defer c.Unlock() 32 | 33 | for _, opt := range opts { 34 | opt(&c.opts) 35 | } 36 | 37 | // update pool configuration if the options changed 38 | if size != c.opts.PoolSize || ttl != c.opts.PoolTTL { 39 | c.pool.Lock() 40 | c.pool.size = c.opts.PoolSize 41 | c.pool.ttl = int64(c.opts.PoolTTL.Seconds()) 42 | c.pool.Unlock() 43 | } 44 | 45 | if len(c.opts.serviceName) > 0 && c.opts.Registry != nil { 46 | // init registry parms 47 | c.opts.Registry.RegNaming.Init(registry.WithServiceName(c.opts.serviceName), 48 | registry.WithTimeout(c.opts.RegisterTTL), 49 | ) 50 | } 51 | 52 | return nil 53 | } 54 | func (c *namingResolver) Options() Options { 55 | c.RLock() 56 | opts := c.opts 57 | c.RUnlock() 58 | return opts 59 | } 60 | 61 | func (c *namingResolver) String() string { 62 | return c.opts.name 63 | } 64 | 65 | // initResolver 注册平衡器 66 | func (c *namingResolver) initResolver() { 67 | if c.opts.Registry != nil { 68 | resolver.Register(&resolverBuilder{scheme: c.opts.Scheme, regNaming: c.opts.Registry.RegNaming}) 69 | } 70 | } 71 | 72 | // Next connon 73 | func (c *namingResolver) Next() (*poolConn, error) { 74 | c.once.Do(c.initResolver) 75 | 76 | opt := []grpc.DialOption{ 77 | grpc.WithInsecure(), 78 | grpc.WithBlock(), 79 | } 80 | // 开始注册 81 | if c.opts.Registry != nil { 82 | opt = append(opt, grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy":"round_robin"}`)) //grpc.WithBalancer(grpc.RoundRobin(c.opts.Registry.RegNaming)) 83 | } 84 | trace := tracer.GetTracer() 85 | if trace != nil { 86 | opt = append(opt, grpc.WithUnaryInterceptor(grpc_opentracing.UnaryClientInterceptor( 87 | grpc_opentracing.WithTracer(trace), 88 | ))) 89 | } 90 | var addr string 91 | if len(c.opts.serviceName) > 0 { 92 | addr = fmt.Sprintf("%v:///%v", c.opts.Scheme, c.opts.serviceName) 93 | } else if len(c.opts.serviceIps) > 0 { 94 | addr = c.opts.serviceIps[tools.GetRandInt(0, len(c.opts.serviceIps))] // 随机 95 | } 96 | 97 | cc, err := c.pool.getConn(addr, opt...) 98 | if err != nil { 99 | mylog.Error(err) 100 | return nil, err 101 | } 102 | 103 | // //建立连接 104 | // conn, err := grpc.Dial(c.opts.serviceName, grpc.WithInsecure(), grpc.WithBalancer(b), grpc.WithBlock()) 105 | // if err != nil { 106 | // return conn, err 107 | // } 108 | 109 | // cli := grpc_health_v1.NewHealthClient(conn) 110 | // go func() { 111 | // for { 112 | // resp, err := cli.Check(context.TODO(), &grpc_health_v1.HealthCheckRequest{}) 113 | // if err != nil { 114 | // fmt.Printf("健康检查报错: %+v\n", err) 115 | // // os.Exit(1) 116 | // } 117 | // fmt.Printf("服务健康状态: %+v\n", resp) 118 | // time.Sleep(time.Second * 5) 119 | // } 120 | // }() 121 | 122 | return cc, err 123 | } 124 | 125 | // func Copy() *Client { 126 | 127 | // } 128 | 129 | func (c *namingResolver) poolMaxStreams() int { 130 | if c.opts.Context == nil { 131 | return DefaultPoolMaxStreams 132 | } 133 | v := c.opts.Context.Value(poolMaxStreams{}) 134 | if v == nil { 135 | return DefaultPoolMaxStreams 136 | } 137 | return v.(int) 138 | } 139 | 140 | func (c *namingResolver) poolMaxIdle() int { 141 | if c.opts.Context == nil { 142 | return DefaultPoolMaxIdle 143 | } 144 | v := c.opts.Context.Value(poolMaxIdle{}) 145 | if v == nil { 146 | return DefaultPoolMaxIdle 147 | } 148 | return v.(int) 149 | } 150 | -------------------------------------------------------------------------------- /client/option.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "github.com/gmsec/micro/registry" 8 | ) 9 | 10 | // Options client of options 11 | type Options struct { 12 | // Connection Pool 13 | PoolSize int 14 | PoolTTL time.Duration 15 | TimeOut time.Duration 16 | 17 | Registry *registry.Registry 18 | // registry 19 | // The register expiry time 20 | RegisterTTL time.Duration 21 | // The interval on which to register 22 | RegisterInterval time.Duration 23 | Scheme string // client gmsec (注册名) 24 | 25 | // Client *grpc.ClientConn 26 | 27 | name string 28 | serviceName string 29 | serviceIps []string 30 | 31 | // Other options for implementations of the interface 32 | // can be stored in a context 33 | Context context.Context 34 | } 35 | 36 | func newOptions(options ...Option) Options { 37 | opts := Options{ 38 | Context: context.Background(), 39 | PoolSize: DefaultPoolSize, 40 | PoolTTL: DefaultPoolTTL, 41 | RegisterTTL: time.Millisecond * 100, 42 | TimeOut: DefaultPoolTimeout, 43 | Scheme: "gmsec", 44 | } 45 | 46 | for _, o := range options { 47 | o(&opts) 48 | } 49 | 50 | return opts 51 | } 52 | 53 | // WithServiceName 设置服务名字 54 | func WithServiceName(name string) Option { 55 | return func(o *Options) { 56 | o.serviceName = name 57 | } 58 | } 59 | 60 | // WithServiceIps 设置服务ip列表 61 | func WithServiceIps(ips []string) Option { 62 | return func(o *Options) { 63 | o.serviceIps = ips 64 | } 65 | } 66 | 67 | // WithName 设置客户端名字 68 | func WithName(name string) Option { 69 | return func(o *Options) { 70 | o.name = name 71 | } 72 | } 73 | 74 | // WithScheme 设置客户端服务名 75 | func WithScheme(name string) Option { 76 | return func(o *Options) { 77 | o.Scheme = name 78 | } 79 | } 80 | 81 | // WithPoolSize sets the connection pool size 82 | func WithPoolSize(d int) Option { 83 | return func(o *Options) { 84 | o.PoolSize = d 85 | } 86 | } 87 | 88 | // WithPoolTTL sets the connection pool ttl 89 | func WithPoolTTL(d time.Duration) Option { 90 | return func(o *Options) { 91 | o.PoolTTL = d 92 | } 93 | } 94 | 95 | // WithRegistryNaming 注册naming 服务发现 96 | func WithRegistryNaming(reg registry.RegNaming) Option { 97 | return func(o *Options) { 98 | o.Registry = ®istry.Registry{ 99 | RegNaming: reg, 100 | } 101 | } 102 | } 103 | 104 | // RegisterTTL Register the service with a TTL 105 | func RegisterTTL(t time.Duration) Option { 106 | return func(o *Options) { 107 | o.RegisterTTL = t 108 | } 109 | } 110 | 111 | // RegisterInterval Register the service with at interval 112 | func RegisterInterval(t time.Duration) Option { 113 | return func(o *Options) { 114 | o.RegisterInterval = t 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /client/resolver.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "github.com/gmsec/micro/naming" 5 | "github.com/gmsec/micro/registry" 6 | "github.com/xxjwxc/public/mylog" 7 | "github.com/xxjwxc/public/tools" 8 | "google.golang.org/grpc/grpclog" 9 | "google.golang.org/grpc/resolver" 10 | ) 11 | 12 | type resolverBuilder struct { 13 | scheme string 14 | regNaming registry.RegNaming 15 | } 16 | 17 | func (rb *resolverBuilder) Build(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOptions) (resolver.Resolver, error) { 18 | watcher, err := rb.regNaming.Resolve(target.Endpoint()) 19 | if err != nil { 20 | return nil, err 21 | } 22 | 23 | r := &myResolver{ 24 | target: target, 25 | cc: cc, 26 | watcher: watcher, 27 | addrsStore: make(map[string]*naming.Update), 28 | } 29 | 30 | r.start() 31 | return r, nil 32 | } 33 | func (rb *resolverBuilder) Scheme() string { return rb.scheme } 34 | 35 | type myResolver struct { 36 | target resolver.Target 37 | cc resolver.ClientConn 38 | watcher naming.Watcher 39 | addrsStore map[string]*naming.Update 40 | isClose bool 41 | } 42 | 43 | func (r *myResolver) start() { 44 | go func() { 45 | for { 46 | updates, err := r.watcher.Next() 47 | if err != nil { 48 | grpclog.Warningf("grpc: the naming watcher stops working due to %v.", err) 49 | mylog.Error(err) 50 | break 51 | } 52 | 53 | isUpdate := false 54 | for _, update := range updates { 55 | switch update.Op { 56 | case naming.Add: // 添加 57 | { 58 | if _, ok := r.addrsStore[update.Addr]; !ok { // 有新加才添加 59 | r.addrsStore[update.Addr] = update 60 | isUpdate = true 61 | } 62 | } 63 | case naming.Delete: // 删除 64 | { 65 | delete(r.addrsStore, update.Addr) // map 删除 66 | // todo:r.cc.delete 67 | } 68 | } 69 | mylog.Debugf("watcher(%v):%v", r.target.Endpoint, tools.JSONDecode(update)) 70 | } 71 | 72 | if isUpdate { 73 | var addrs []resolver.Address 74 | for _, v := range r.addrsStore { 75 | addrs = append(addrs, resolver.Address{Addr: v.Addr}) 76 | } 77 | r.cc.UpdateState(resolver.State{Addresses: addrs}) 78 | } 79 | 80 | if r.isClose { 81 | r.watcher.Close() 82 | break 83 | } 84 | } 85 | }() 86 | 87 | } 88 | 89 | func (*myResolver) ResolveNow(o resolver.ResolveNowOptions) { 90 | //fmt.Println(o) 91 | } 92 | 93 | func (r *myResolver) Close() { 94 | r.isClose = true 95 | mylog.Debugf("Close:%v", r.target.Endpoint) 96 | } 97 | -------------------------------------------------------------------------------- /example/Makefile: -------------------------------------------------------------------------------- 1 | NAME := gmsec 2 | gen:# 编译hello.proto 3 | protoc --proto_path="./apidoc/proto/hello/" --gmsec_out=plugins=gmsec:./rpc/ hello.proto 4 | install: 5 | ./proto_install.sh 6 | source_install: 7 | ./proto_install.sh 8 | build: 9 | go build *.go 10 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # install 2 | 3 | [protoc-gen-gmsec]() 4 | 5 | # build 6 | 7 | ``` 8 | protoc --proto_path="./apidoc/proto/hello/" --gmsec_out=plugins=gmsec:./rpc/ hello.proto 9 | go build hello.go main.go 10 | ``` 11 | 12 | # server 13 | 14 | ``` 15 | ./hello -tag=server 16 | ``` 17 | 18 | # client 19 | 20 | ``` 21 | ./hello -tag=client 22 | ``` 23 | 24 | -------------------------------------------------------------------------------- /example/apidoc/proto/hello/hello.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; // 指定proto版本 2 | 3 | package proto; // 指定包名 4 | 5 | option go_package = ".;proto"; 6 | 7 | // 定义Hello服务 8 | service Hello { 9 | // 定义SayHello方法 10 | rpc SayHello(HelloRequest) returns (HelloReply) {} 11 | } 12 | 13 | // HelloRequest 请求结构 14 | message HelloRequest { 15 | string name = 1; // 名字 16 | } 17 | 18 | // HelloReply 响应结构 19 | message HelloReply { 20 | string message = 1; // 消息 21 | } 22 | -------------------------------------------------------------------------------- /example/go.mod: -------------------------------------------------------------------------------- 1 | module gmicro 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/gmsec/micro v0.0.0-20200509132353-c4ecace717cb 7 | github.com/golang/protobuf v1.5.2 8 | github.com/xxjwxc/gowp v0.0.0-20200603130651-4d7368b0e285 9 | github.com/xxjwxc/public v0.0.0-20230103091848-ecbc2d279c6a 10 | google.golang.org/grpc v1.53.0 11 | google.golang.org/protobuf v1.28.1 12 | ) 13 | 14 | replace github.com/gmsec/micro => ../ 15 | -------------------------------------------------------------------------------- /example/hello.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | proto "gmicro/rpc" 7 | 8 | "github.com/xxjwxc/public/tools" 9 | 10 | "google.golang.org/grpc/metadata" 11 | ) 12 | 13 | type hello struct { 14 | } 15 | 16 | func (h *hello) SayHello(ctx context.Context, req *proto.HelloRequest) (*proto.HelloReply, error) { 17 | md, ok := metadata.FromIncomingContext(ctx) 18 | fmt.Println(md) 19 | fmt.Println(ok) 20 | fmt.Println(ctx.Value("HELLO")) 21 | fmt.Println(ctx.Value("WROLD")) 22 | fmt.Println(req) 23 | return &proto.HelloReply{ 24 | Message: tools.GetRandomString(8), 25 | }, nil 26 | } 27 | -------------------------------------------------------------------------------- /example/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "fmt" 7 | proto "gmicro/rpc" 8 | "math/rand" 9 | "os" 10 | "os/signal" 11 | "syscall" 12 | "time" 13 | 14 | "github.com/gmsec/micro" 15 | "github.com/xxjwxc/gowp/workpool" 16 | "github.com/xxjwxc/public/mylog" 17 | ) 18 | 19 | var tag string 20 | 21 | func init() { 22 | addFlag(flag.CommandLine) 23 | } 24 | 25 | func addFlag(fs *flag.FlagSet) { 26 | // env 27 | fs.StringVar(&tag, "tag", "client", "service or client") 28 | } 29 | 30 | func main() { 31 | flag.Parse() 32 | // reg := registry.NewDNSNamingRegistry() 33 | // 初始化服务 34 | service := micro.NewService( 35 | micro.WithName("lp.srv.eg1"), 36 | // micro.WithRegisterTTL(time.Second*30), //指定服务注册时间 37 | micro.WithRegisterInterval(time.Second*15), //让服务在指定时间内重新注册 38 | //micro.WithRegistryNaming(reg), 39 | ) 40 | if tag == "server" { 41 | proto.RegisterHelloServer(service.Server(), &hello{}) 42 | // run server 43 | if err := service.Run(); err != nil { 44 | panic(err) 45 | } 46 | fmt.Println("stop service") 47 | } else { 48 | go func() { // 通过服务发现模块连接 49 | wp := workpool.New(200) //设置最大线程数 50 | for i := 0; i < 2000; i++ { //开启20个请求 51 | wp.Do(func() error { 52 | widthRegistry() 53 | return nil 54 | }) 55 | } 56 | 57 | wp.Wait() 58 | fmt.Println("down") 59 | }() 60 | 61 | go func() { 62 | wp := workpool.New(200) //设置最大线程数 63 | for i := 0; i < 2000; i++ { //开启20个请求 64 | wp.Do(func() error { 65 | widthIPAddr() 66 | return nil 67 | }) 68 | } 69 | 70 | wp.Wait() 71 | fmt.Println("down") 72 | }() 73 | 74 | } 75 | 76 | wait() 77 | } 78 | 79 | // 注册发现模块 80 | func widthRegistry() { 81 | micro.SetClientServiceName(proto.GetHelloName(), "lp.srv.eg1") // set client group 82 | say := proto.GetHelloClient() 83 | 84 | var request proto.HelloRequest 85 | r := rand.Intn(500) 86 | request.Name = fmt.Sprintf("%v", r) 87 | 88 | ctx := context.Background() 89 | 90 | for i := 0; i < 10; i++ { 91 | resp, err := say.SayHello(ctx, &request) 92 | if err != nil { 93 | mylog.Error(err) 94 | fmt.Println("==========err:", err) 95 | } 96 | fmt.Println(resp) 97 | time.Sleep(1 * time.Second) 98 | } 99 | } 100 | 101 | // widthIPAddr 通过ip访问(非服务发现模式) 102 | func widthIPAddr() { 103 | micro.SetClientServiceAddr(proto.GetHelloName(), "127.0.0.1:50051") 104 | // micro.SetClientServiceName(proto.GetHelloName(), "lp.srv.eg1") // set client group 105 | hello := proto.GetHelloClient() 106 | var request proto.HelloRequest 107 | 108 | ctx := context.Background() 109 | for i := 0; i < 10; i++ { 110 | r := rand.Intn(500) 111 | request.Name = fmt.Sprintf("%v", r) 112 | resp, err := hello.SayHello(ctx, &request) 113 | if err != nil { 114 | mylog.Error(err) 115 | fmt.Println("==========err:", err) 116 | } 117 | fmt.Println(resp) 118 | time.Sleep(1 * time.Second) 119 | } 120 | } 121 | 122 | func wait() { 123 | // Go signal notification works by sending `os.Signal` 124 | // values on a channel. We'll create a channel to 125 | // receive these notifications (we'll also make one to 126 | // notify us when the program can exit). 127 | sigs := make(chan os.Signal, 1) 128 | done := make(chan bool, 1) 129 | // `signal.Notify` registers the given channel to 130 | // receive notifications of the specified signals. 131 | signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) 132 | // This goroutine executes a blocking receive for 133 | // signals. When it gets one it'll print it out 134 | // and then notify the program that it can finish. 135 | go func() { 136 | sig := <-sigs 137 | fmt.Println() 138 | fmt.Println(sig) 139 | done <- true 140 | }() 141 | // The program will wait here until it gets the 142 | // expected signal (as indicated by the goroutine 143 | // above sending a value on `done`) and then exit. 144 | fmt.Println("awaiting signal") 145 | <-done 146 | fmt.Println("exiting") 147 | 148 | fmt.Println("down") 149 | } 150 | -------------------------------------------------------------------------------- /example/out/ff.json: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /example/out/gen.json: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /example/out/importPath.json: -------------------------------------------------------------------------------- 1 | "google.golang.org/grpc" 2 | -------------------------------------------------------------------------------- /example/out/info.json: -------------------------------------------------------------------------------- 1 | {"Plugins":"gmsec","ImportPrefix":"","Args":["protoc-gen-gmsec"],"ModelPath":"/Users/xxj/work/workspace/github/xxjwxc/micro/example"} 2 | -------------------------------------------------------------------------------- /example/proto_install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -x 2 | 3 | version="3.11.4" 4 | 5 | # su - xxj -c "qwer" 6 | # download 7 | curl -fLo protobuf.tar.gz https://github.com/protocolbuffers/protobuf/releases/download/v${version}/protoc-${version}-osx-x86_64.zip 8 | mkdir protobuf-${version} 9 | tar -xvf protobuf.tar.gz -C ./protobuf-${version} 10 | cd protobuf-${version} 11 | 12 | # install 13 | xattr -c ./bin/protoc 14 | cp -r ./bin/protoc $GOPATH/bin 15 | cd ../ 16 | rm -rf protobuf-${version}/ 17 | 18 | # install go-grpc 19 | go get -u google.golang.org/grpc 20 | go get -u github.com/golang/protobuf/protoc-gen-go 21 | 22 | echo "SUCCESS" 23 | #end -------------------------------------------------------------------------------- /example/proto_source_install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -x 2 | 3 | version="3.11.4" 4 | 5 | # su - xxj -c "qwer" 6 | # download 7 | curl -fLo protobuf.tar.gz https://github.com/protocolbuffers/protobuf/releases/download/v${version}/protobuf-all-${version}.tar.gz 8 | tar -xvf protobuf.tar.gz 9 | cd protobuf-${version} 10 | 11 | #build 12 | curPath=$(pwd) 13 | ./configure --prefix=$curPath --enable-shared=no CFLAGS="-fPIC -fvisibility=hidden" CXXFLAGS="-fPIC -fvisibility=hidden" || exit 1 14 | make clean 15 | make -j6 || exit 1 16 | make check 17 | make install 18 | xattr -c ./bin/protoc 19 | cp -r ./bin/protoc $GOPATH/bin 20 | cd ../ 21 | rm -rf protobuf-${version}/ 22 | 23 | # install go-grpc 24 | go get -u google.golang.org/grpc 25 | go get -u github.com/golang/protobuf/protoc-gen-go 26 | 27 | echo "SUCCESS" 28 | #end -------------------------------------------------------------------------------- /example/rpc/hello.pb.gmsec.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go-grpc. DO NOT EDIT. 2 | 3 | package proto 4 | 5 | import ( 6 | context "context" 7 | micro "github.com/gmsec/micro" 8 | client "github.com/gmsec/micro/client" 9 | server "github.com/gmsec/micro/server" 10 | grpc "google.golang.org/grpc" 11 | codes "google.golang.org/grpc/codes" 12 | status "google.golang.org/grpc/status" 13 | ) 14 | 15 | // Reference imports to suppress errors if they are not otherwise used. 16 | var _ context.Context 17 | var _ grpc.ClientConnInterface 18 | var _ server.Server 19 | var _ client.Client 20 | var _ micro.Service 21 | 22 | // This is a compile-time assertion to ensure that this generated file 23 | // is compatible with the grpc package it is being compiled against. 24 | const _ = grpc.SupportPackageIsVersion6 25 | 26 | // HelloClient is the client API for Hello service. 27 | // 28 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. 29 | type HelloClient interface { 30 | // 定义SayHello方法 31 | SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) 32 | } 33 | 34 | type helloClient struct { 35 | cc client.Client 36 | } 37 | 38 | // GetHelloName get client name(package.class) 39 | func GetHelloName() string { 40 | return "proto.Hello" 41 | } 42 | 43 | // GetHelloClient get client by clientname 44 | func GetHelloClient() HelloClient { 45 | cc := micro.GetClient(GetHelloName()) 46 | return &helloClient{cc} 47 | } 48 | 49 | // GetHelloClientByName get client by custom name 50 | func GetHelloClientByName(name string) HelloClient { 51 | cc := micro.GetClient(name) 52 | return &helloClient{cc} 53 | } 54 | 55 | func NewHelloClient(cc client.Client) HelloClient { 56 | return &helloClient{cc} 57 | } 58 | 59 | func (c *helloClient) SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) { 60 | conn, err := c.cc.Next() 61 | defer conn.Close() 62 | if err != nil { 63 | return nil, err 64 | } 65 | out := new(HelloReply) 66 | err = conn.Invoke(ctx, "/proto.Hello/SayHello", in, out, opts...) 67 | if err != nil { 68 | return nil, err 69 | } 70 | return out, nil 71 | } 72 | 73 | // HelloServer is the server API for Hello service. 74 | type HelloServer interface { 75 | // 定义SayHello方法 76 | SayHello(context.Context, *HelloRequest) (*HelloReply, error) 77 | } 78 | 79 | // UnimplementedHelloServer can be embedded to have forward compatible implementations. 80 | type UnimplementedHelloServer struct { 81 | } 82 | 83 | func (*UnimplementedHelloServer) SayHello(context.Context, *HelloRequest) (*HelloReply, error) { 84 | return nil, status.Errorf(codes.Unimplemented, "method SayHello not implemented") 85 | } 86 | 87 | func RegisterHelloServer(s server.Server, srv HelloServer) { 88 | s.GetServer().RegisterService(&_Hello_serviceDesc, srv) 89 | } 90 | 91 | func _Hello_SayHello_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 92 | in := new(HelloRequest) 93 | if err := dec(in); err != nil { 94 | return nil, err 95 | } 96 | if interceptor == nil { 97 | return srv.(HelloServer).SayHello(ctx, in) 98 | } 99 | info := &grpc.UnaryServerInfo{ 100 | Server: srv, 101 | FullMethod: "/proto.Hello/SayHello", 102 | } 103 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 104 | return srv.(HelloServer).SayHello(ctx, req.(*HelloRequest)) 105 | } 106 | return interceptor(ctx, in, info, handler) 107 | } 108 | 109 | var _Hello_serviceDesc = grpc.ServiceDesc{ 110 | ServiceName: "proto.Hello", 111 | HandlerType: (*HelloServer)(nil), 112 | Methods: []grpc.MethodDesc{ 113 | { 114 | MethodName: "SayHello", 115 | Handler: _Hello_SayHello_Handler, 116 | }, 117 | }, 118 | Streams: []grpc.StreamDesc{}, 119 | Metadata: "hello.proto", 120 | } 121 | -------------------------------------------------------------------------------- /example/rpc/hello.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // versions: 3 | // protoc-gen-go v1.22.0 4 | // protoc v3.11.4 5 | // source: hello.proto 6 | 7 | package proto 8 | 9 | import ( 10 | proto "github.com/golang/protobuf/proto" 11 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 12 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 13 | reflect "reflect" 14 | sync "sync" 15 | ) 16 | 17 | const ( 18 | // Verify that this generated code is sufficiently up-to-date. 19 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 20 | // Verify that runtime/protoimpl is sufficiently up-to-date. 21 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 22 | ) 23 | 24 | // This is a compile-time assertion that a sufficiently up-to-date version 25 | // of the legacy proto package is being used. 26 | const _ = proto.ProtoPackageIsVersion4 27 | 28 | // HelloRequest 请求结构 29 | type HelloRequest struct { 30 | state protoimpl.MessageState 31 | sizeCache protoimpl.SizeCache 32 | unknownFields protoimpl.UnknownFields 33 | 34 | Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` // 名字 35 | } 36 | 37 | func (x *HelloRequest) Reset() { 38 | *x = HelloRequest{} 39 | if protoimpl.UnsafeEnabled { 40 | mi := &file_hello_proto_msgTypes[0] 41 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 42 | ms.StoreMessageInfo(mi) 43 | } 44 | } 45 | 46 | func (x *HelloRequest) String() string { 47 | return protoimpl.X.MessageStringOf(x) 48 | } 49 | 50 | func (*HelloRequest) ProtoMessage() {} 51 | 52 | func (x *HelloRequest) ProtoReflect() protoreflect.Message { 53 | mi := &file_hello_proto_msgTypes[0] 54 | if protoimpl.UnsafeEnabled && x != nil { 55 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 56 | if ms.LoadMessageInfo() == nil { 57 | ms.StoreMessageInfo(mi) 58 | } 59 | return ms 60 | } 61 | return mi.MessageOf(x) 62 | } 63 | 64 | // Deprecated: Use HelloRequest.ProtoReflect.Descriptor instead. 65 | func (*HelloRequest) Descriptor() ([]byte, []int) { 66 | return file_hello_proto_rawDescGZIP(), []int{0} 67 | } 68 | 69 | func (x *HelloRequest) GetName() string { 70 | if x != nil { 71 | return x.Name 72 | } 73 | return "" 74 | } 75 | 76 | // HelloReply 响应结构 77 | type HelloReply struct { 78 | state protoimpl.MessageState 79 | sizeCache protoimpl.SizeCache 80 | unknownFields protoimpl.UnknownFields 81 | 82 | Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` // 消息 83 | } 84 | 85 | func (x *HelloReply) Reset() { 86 | *x = HelloReply{} 87 | if protoimpl.UnsafeEnabled { 88 | mi := &file_hello_proto_msgTypes[1] 89 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 90 | ms.StoreMessageInfo(mi) 91 | } 92 | } 93 | 94 | func (x *HelloReply) String() string { 95 | return protoimpl.X.MessageStringOf(x) 96 | } 97 | 98 | func (*HelloReply) ProtoMessage() {} 99 | 100 | func (x *HelloReply) ProtoReflect() protoreflect.Message { 101 | mi := &file_hello_proto_msgTypes[1] 102 | if protoimpl.UnsafeEnabled && x != nil { 103 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 104 | if ms.LoadMessageInfo() == nil { 105 | ms.StoreMessageInfo(mi) 106 | } 107 | return ms 108 | } 109 | return mi.MessageOf(x) 110 | } 111 | 112 | // Deprecated: Use HelloReply.ProtoReflect.Descriptor instead. 113 | func (*HelloReply) Descriptor() ([]byte, []int) { 114 | return file_hello_proto_rawDescGZIP(), []int{1} 115 | } 116 | 117 | func (x *HelloReply) GetMessage() string { 118 | if x != nil { 119 | return x.Message 120 | } 121 | return "" 122 | } 123 | 124 | var File_hello_proto protoreflect.FileDescriptor 125 | 126 | var file_hello_proto_rawDesc = []byte{ 127 | 0x0a, 0x0b, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05, 0x70, 128 | 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x22, 0x0a, 0x0c, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x71, 129 | 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 130 | 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x26, 0x0a, 0x0a, 0x48, 0x65, 0x6c, 0x6c, 131 | 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 132 | 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 133 | 0x32, 0x3d, 0x0a, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x12, 0x34, 0x0a, 0x08, 0x53, 0x61, 0x79, 134 | 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x12, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x48, 0x65, 135 | 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x11, 0x2e, 0x70, 0x72, 0x6f, 136 | 0x74, 0x6f, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x42, 137 | 0x09, 0x5a, 0x07, 0x2e, 0x3b, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 138 | 0x6f, 0x33, 139 | } 140 | 141 | var ( 142 | file_hello_proto_rawDescOnce sync.Once 143 | file_hello_proto_rawDescData = file_hello_proto_rawDesc 144 | ) 145 | 146 | func file_hello_proto_rawDescGZIP() []byte { 147 | file_hello_proto_rawDescOnce.Do(func() { 148 | file_hello_proto_rawDescData = protoimpl.X.CompressGZIP(file_hello_proto_rawDescData) 149 | }) 150 | return file_hello_proto_rawDescData 151 | } 152 | 153 | var file_hello_proto_msgTypes = make([]protoimpl.MessageInfo, 2) 154 | var file_hello_proto_goTypes = []interface{}{ 155 | (*HelloRequest)(nil), // 0: proto.HelloRequest 156 | (*HelloReply)(nil), // 1: proto.HelloReply 157 | } 158 | var file_hello_proto_depIdxs = []int32{ 159 | 0, // 0: proto.Hello.SayHello:input_type -> proto.HelloRequest 160 | 1, // 1: proto.Hello.SayHello:output_type -> proto.HelloReply 161 | 1, // [1:2] is the sub-list for method output_type 162 | 0, // [0:1] is the sub-list for method input_type 163 | 0, // [0:0] is the sub-list for extension type_name 164 | 0, // [0:0] is the sub-list for extension extendee 165 | 0, // [0:0] is the sub-list for field type_name 166 | } 167 | 168 | func init() { file_hello_proto_init() } 169 | func file_hello_proto_init() { 170 | if File_hello_proto != nil { 171 | return 172 | } 173 | if !protoimpl.UnsafeEnabled { 174 | file_hello_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { 175 | switch v := v.(*HelloRequest); i { 176 | case 0: 177 | return &v.state 178 | case 1: 179 | return &v.sizeCache 180 | case 2: 181 | return &v.unknownFields 182 | default: 183 | return nil 184 | } 185 | } 186 | file_hello_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { 187 | switch v := v.(*HelloReply); i { 188 | case 0: 189 | return &v.state 190 | case 1: 191 | return &v.sizeCache 192 | case 2: 193 | return &v.unknownFields 194 | default: 195 | return nil 196 | } 197 | } 198 | } 199 | type x struct{} 200 | out := protoimpl.TypeBuilder{ 201 | File: protoimpl.DescBuilder{ 202 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 203 | RawDescriptor: file_hello_proto_rawDesc, 204 | NumEnums: 0, 205 | NumMessages: 2, 206 | NumExtensions: 0, 207 | NumServices: 1, 208 | }, 209 | GoTypes: file_hello_proto_goTypes, 210 | DependencyIndexes: file_hello_proto_depIdxs, 211 | MessageInfos: file_hello_proto_msgTypes, 212 | }.Build() 213 | File_hello_proto = out.File 214 | file_hello_proto_rawDesc = nil 215 | file_hello_proto_goTypes = nil 216 | file_hello_proto_depIdxs = nil 217 | } 218 | -------------------------------------------------------------------------------- /global.go: -------------------------------------------------------------------------------- 1 | package micro 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | 7 | "github.com/xxjwxc/public/mylog" 8 | 9 | "github.com/gmsec/micro/client" 10 | "github.com/gmsec/micro/registry" 11 | ) 12 | 13 | var mut sync.RWMutex 14 | var _mp map[string]Service 15 | var _IpAddrMp map[string]client.Client 16 | var _RegNaming registry.RegNaming 17 | 18 | func init() { 19 | _mp = make(map[string]Service) 20 | _IpAddrMp = make(map[string]client.Client) 21 | } 22 | 23 | // GetService get service 24 | func GetService(name string) Service { 25 | mut.RLock() 26 | defer mut.RUnlock() 27 | 28 | if len(name) > 0 { 29 | s, ok := _mp[name] 30 | if ok { 31 | return s 32 | } 33 | } 34 | 35 | mylog.Info(fmt.Sprintf("[%v]:not fond ,use traverse mode", name)) 36 | for k, v := range _mp { 37 | mylog.Info(k) 38 | return v 39 | } 40 | mylog.ErrorString("please init first.") 41 | return nil 42 | } 43 | 44 | // GetClient get client from cliet name 45 | func GetClient(clientName string) client.Client { 46 | // if _IpAddrMp 47 | c, ok := _IpAddrMp[clientName] 48 | if ok { 49 | return c 50 | } 51 | 52 | s := GetService("") 53 | if s != nil { 54 | return s.Client() 55 | } 56 | return nil 57 | } 58 | 59 | // SetClientServiceName set service name with client name 60 | func SetClientServiceName(clientName, serviceName string) { 61 | if !IsExist(clientName) { 62 | mut.RLock() 63 | defer mut.RUnlock() 64 | var opts []client.Option 65 | if _RegNaming != nil { 66 | opts = append(opts, client.WithRegistryNaming(_RegNaming)) 67 | } 68 | tmp := client.NewClient(opts...) 69 | tmp.Init(client.WithServiceName(serviceName)) 70 | _IpAddrMp[clientName] = tmp 71 | } 72 | } 73 | 74 | // IsExist existed 75 | func IsExist(name string) bool { 76 | mut.RLock() 77 | defer mut.RUnlock() 78 | _, ok := _mp[name] 79 | return ok 80 | } 81 | 82 | func initService(name string, s *service) { 83 | mut.Lock() 84 | defer mut.Unlock() 85 | _mp[name] = s 86 | if _RegNaming == nil { 87 | _RegNaming = s.Options().Registry.RegNaming 88 | } 89 | } 90 | 91 | // SetClientServiceAddr set service address with client name 92 | func SetClientServiceAddr(clientName string, ips ...string) { 93 | if !IsExist(clientName) { 94 | mut.RLock() 95 | defer mut.RUnlock() 96 | tmp := client.NewIPAddrClient() 97 | tmp.Init(client.WithServiceIps(ips)) 98 | _IpAddrMp[clientName] = tmp 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gmsec/micro 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/golang/protobuf v1.5.2 7 | github.com/google/uuid v1.3.0 8 | github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 9 | github.com/miekg/dns v1.1.50 10 | github.com/opentracing/opentracing-go v1.2.0 11 | github.com/stretchr/testify v1.8.1 12 | github.com/uber/jaeger-client-go v2.30.0+incompatible 13 | github.com/xxjwxc/public v0.0.0-20230103091848-ecbc2d279c6a 14 | golang.org/x/net v0.7.0 15 | google.golang.org/grpc v1.53.0 16 | ) 17 | 18 | require ( 19 | github.com/HdrHistogram/hdrhistogram-go v1.1.2 // indirect 20 | github.com/axgle/mahonia v0.0.0-20180208002826-3358181d7394 // indirect 21 | github.com/davecgh/go-spew v1.1.1 // indirect 22 | github.com/gookit/color v1.5.2 // indirect 23 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect 24 | github.com/pkg/errors v0.9.1 // indirect 25 | github.com/pmezard/go-difflib v1.0.0 // indirect 26 | github.com/uber/jaeger-lib v2.4.1+incompatible // indirect 27 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect 28 | go.uber.org/atomic v1.10.0 // indirect 29 | go.uber.org/multierr v1.9.0 // indirect 30 | go.uber.org/zap v1.24.0 // indirect 31 | golang.org/x/mod v0.7.0 // indirect 32 | golang.org/x/sys v0.5.0 // indirect 33 | golang.org/x/text v0.7.0 // indirect 34 | golang.org/x/tools v0.4.0 // indirect 35 | google.golang.org/genproto v0.0.0-20230216225411-c8e22ba71e44 // indirect 36 | google.golang.org/protobuf v1.28.1 // indirect 37 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect 38 | gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect 39 | gopkg.in/yaml.v3 v3.0.1 // indirect 40 | ) 41 | 42 | // replace github.com/xxjwxc/public => ../public 43 | -------------------------------------------------------------------------------- /grpc_opentracing/client_interceptors.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Michal Witkowski. All Rights Reserved. 2 | // See LICENSE for licensing terms. 3 | 4 | package grpc_opentracing 5 | 6 | import ( 7 | "context" 8 | "io" 9 | "sync" 10 | 11 | "github.com/grpc-ecosystem/go-grpc-middleware/util/metautils" 12 | opentracing "github.com/opentracing/opentracing-go" 13 | "github.com/opentracing/opentracing-go/ext" 14 | "github.com/opentracing/opentracing-go/log" 15 | "google.golang.org/grpc" 16 | "google.golang.org/grpc/grpclog" 17 | "google.golang.org/grpc/metadata" 18 | ) 19 | 20 | // UnaryClientInterceptor returns a new unary client interceptor for OpenTracing. 21 | func UnaryClientInterceptor(opts ...Option) grpc.UnaryClientInterceptor { 22 | o := evaluateOptions(opts) 23 | return func(parentCtx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { 24 | if o.filterOutFunc != nil && !o.filterOutFunc(parentCtx, method) { 25 | return invoker(parentCtx, method, req, reply, cc, opts...) 26 | } 27 | newCtx, clientSpan := newClientSpanFromContext(parentCtx, o.tracer, method) 28 | if o.unaryRequestHandlerFunc != nil { 29 | o.unaryRequestHandlerFunc(clientSpan, req) 30 | } 31 | err := invoker(newCtx, method, req, reply, cc, opts...) 32 | finishClientSpan(clientSpan, err) 33 | return err 34 | } 35 | } 36 | 37 | // StreamClientInterceptor returns a new streaming client interceptor for OpenTracing. 38 | func StreamClientInterceptor(opts ...Option) grpc.StreamClientInterceptor { 39 | o := evaluateOptions(opts) 40 | return func(parentCtx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) { 41 | if o.filterOutFunc != nil && !o.filterOutFunc(parentCtx, method) { 42 | return streamer(parentCtx, desc, cc, method, opts...) 43 | } 44 | newCtx, clientSpan := newClientSpanFromContext(parentCtx, o.tracer, method) 45 | clientStream, err := streamer(newCtx, desc, cc, method, opts...) 46 | if err != nil { 47 | finishClientSpan(clientSpan, err) 48 | return nil, err 49 | } 50 | return &tracedClientStream{ClientStream: clientStream, clientSpan: clientSpan}, nil 51 | } 52 | } 53 | 54 | // type serverStreamingRetryingStream is the implementation of grpc.ClientStream that acts as a 55 | // proxy to the underlying call. If any of the RecvMsg() calls fail, it will try to reestablish 56 | // a new ClientStream according to the retry policy. 57 | type tracedClientStream struct { 58 | grpc.ClientStream 59 | mu sync.Mutex 60 | alreadyFinished bool 61 | clientSpan opentracing.Span 62 | } 63 | 64 | func (s *tracedClientStream) Header() (metadata.MD, error) { 65 | h, err := s.ClientStream.Header() 66 | if err != nil { 67 | s.finishClientSpan(err) 68 | } 69 | return h, err 70 | } 71 | 72 | func (s *tracedClientStream) SendMsg(m interface{}) error { 73 | err := s.ClientStream.SendMsg(m) 74 | if err != nil { 75 | s.finishClientSpan(err) 76 | } 77 | return err 78 | } 79 | 80 | func (s *tracedClientStream) CloseSend() error { 81 | err := s.ClientStream.CloseSend() 82 | s.finishClientSpan(err) 83 | return err 84 | } 85 | 86 | func (s *tracedClientStream) RecvMsg(m interface{}) error { 87 | err := s.ClientStream.RecvMsg(m) 88 | if err != nil { 89 | s.finishClientSpan(err) 90 | } 91 | return err 92 | } 93 | 94 | func (s *tracedClientStream) finishClientSpan(err error) { 95 | s.mu.Lock() 96 | defer s.mu.Unlock() 97 | if !s.alreadyFinished { 98 | finishClientSpan(s.clientSpan, err) 99 | s.alreadyFinished = true 100 | } 101 | } 102 | 103 | // ClientAddContextTags returns a context with specified opentracing tags, which 104 | // are used by UnaryClientInterceptor/StreamClientInterceptor when creating a 105 | // new span. 106 | func ClientAddContextTags(ctx context.Context, tags opentracing.Tags) context.Context { 107 | return context.WithValue(ctx, clientSpanTagKey{}, tags) 108 | } 109 | 110 | type clientSpanTagKey struct{} 111 | 112 | func newClientSpanFromContext(ctx context.Context, tracer opentracing.Tracer, fullMethodName string) (context.Context, opentracing.Span) { 113 | var parentSpanCtx opentracing.SpanContext 114 | if parent := opentracing.SpanFromContext(ctx); parent != nil { 115 | parentSpanCtx = parent.Context() 116 | } 117 | opts := []opentracing.StartSpanOption{ 118 | opentracing.ChildOf(parentSpanCtx), 119 | ext.SpanKindRPCClient, 120 | grpcTag, 121 | } 122 | if tagx := ctx.Value(clientSpanTagKey{}); tagx != nil { 123 | if opt, ok := tagx.(opentracing.StartSpanOption); ok { 124 | opts = append(opts, opt) 125 | } 126 | } 127 | clientSpan := tracer.StartSpan(fullMethodName, opts...) 128 | // Make sure we add this to the metadata of the call, so it gets propagated: 129 | md := metautils.ExtractOutgoing(ctx).Clone() 130 | if err := tracer.Inject(clientSpan.Context(), opentracing.HTTPHeaders, metadataTextMap(md)); err != nil { 131 | grpclog.Infof("grpc_opentracing: failed serializing trace information: %v", err) 132 | } 133 | ctxWithMetadata := md.ToOutgoing(ctx) 134 | return opentracing.ContextWithSpan(ctxWithMetadata, clientSpan), clientSpan 135 | } 136 | 137 | func finishClientSpan(clientSpan opentracing.Span, err error) { 138 | if err != nil && err != io.EOF { 139 | ext.Error.Set(clientSpan, true) 140 | clientSpan.LogFields(log.String("event", "error"), log.String("message", err.Error())) 141 | } 142 | clientSpan.Finish() 143 | } 144 | -------------------------------------------------------------------------------- /grpc_opentracing/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Michal Witkowski. All Rights Reserved. 2 | // See LICENSE for licensing terms. 3 | 4 | /* 5 | `grpc_opentracing` adds OpenTracing 6 | 7 | # OpenTracing Interceptors 8 | 9 | These are both client-side and server-side interceptors for OpenTracing. They are a provider-agnostic, with backends 10 | such as Zipkin, or Google Stackdriver Trace. 11 | 12 | For a service that sends out requests and receives requests, you *need* to use both, otherwise downstream requests will 13 | not have the appropriate requests propagated. 14 | 15 | All server-side spans are tagged with grpc_ctxtags information. 16 | 17 | For more information see: 18 | http://opentracing.io/documentation/ 19 | https://github.com/opentracing/specification/blob/master/semantic_conventions.md 20 | */ 21 | package grpc_opentracing 22 | -------------------------------------------------------------------------------- /grpc_opentracing/id_extract.go: -------------------------------------------------------------------------------- 1 | package grpc_opentracing 2 | 3 | import ( 4 | "strings" 5 | 6 | grpc_ctxtags "github.com/grpc-ecosystem/go-grpc-middleware/tags" 7 | opentracing "github.com/opentracing/opentracing-go" 8 | "google.golang.org/grpc/grpclog" 9 | ) 10 | 11 | const ( 12 | TagTraceId = "trace.traceid" 13 | TagSpanId = "trace.spanid" 14 | TagSampled = "trace.sampled" 15 | jaegerNotSampledFlag = "0" 16 | ) 17 | 18 | // injectOpentracingIdsToTags writes trace data to ctxtags. 19 | // This is done in an incredibly hacky way, because the public-facing interface of opentracing doesn't give access to 20 | // the TraceId and SpanId of the SpanContext. Only the Tracer's Inject/Extract methods know what these are. 21 | // Most tracers have them encoded as keys with 'traceid' and 'spanid': 22 | // https://github.com/openzipkin/zipkin-go-opentracing/blob/594640b9ef7e5c994e8d9499359d693c032d738c/propagation_ot.go#L29 23 | // https://github.com/opentracing/basictracer-go/blob/1b32af207119a14b1b231d451df3ed04a72efebf/propagation_ot.go#L26 24 | // Jaeger from Uber use one-key schema with next format '{trace-id}:{span-id}:{parent-span-id}:{flags}' 25 | // https://www.jaegertracing.io/docs/client-libraries/#trace-span-identity 26 | // Datadog uses keys ending with 'trace-id' and 'parent-id' (for span) by default: 27 | // https://github.com/DataDog/dd-trace-go/blob/v1/ddtrace/tracer/textmap.go#L77 28 | func injectOpentracingIdsToTags(traceHeaderName string, span opentracing.Span, tags grpc_ctxtags.Tags) { 29 | if err := span.Tracer().Inject(span.Context(), opentracing.HTTPHeaders, 30 | &tagsCarrier{Tags: tags, traceHeaderName: traceHeaderName}); err != nil { 31 | grpclog.Infof("grpc_opentracing: failed extracting trace info into ctx %v", err) 32 | } 33 | } 34 | 35 | // tagsCarrier is a really hacky way of 36 | type tagsCarrier struct { 37 | grpc_ctxtags.Tags 38 | traceHeaderName string 39 | } 40 | 41 | func (t *tagsCarrier) Set(key, val string) { 42 | key = strings.ToLower(key) 43 | 44 | if key == t.traceHeaderName { 45 | parts := strings.Split(val, ":") 46 | if len(parts) == 4 { 47 | t.Tags.Set(TagTraceId, parts[0]) 48 | t.Tags.Set(TagSpanId, parts[1]) 49 | 50 | if parts[3] != jaegerNotSampledFlag { 51 | t.Tags.Set(TagSampled, "true") 52 | } else { 53 | t.Tags.Set(TagSampled, "false") 54 | } 55 | 56 | return 57 | } 58 | } 59 | 60 | if strings.Contains(key, "traceid") { 61 | t.Tags.Set(TagTraceId, val) // this will most likely be base-16 (hex) encoded 62 | } 63 | 64 | if strings.Contains(key, "spanid") && !strings.Contains(strings.ToLower(key), "parent") { 65 | t.Tags.Set(TagSpanId, val) // this will most likely be base-16 (hex) encoded 66 | } 67 | 68 | if strings.Contains(key, "sampled") { 69 | switch val { 70 | case "true", "false": 71 | t.Tags.Set(TagSampled, val) 72 | } 73 | } 74 | 75 | if strings.HasSuffix(key, "trace-id") { 76 | t.Tags.Set(TagTraceId, val) 77 | } 78 | 79 | if strings.HasSuffix(key, "parent-id") { 80 | t.Tags.Set(TagSpanId, val) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /grpc_opentracing/id_extract_test.go: -------------------------------------------------------------------------------- 1 | package grpc_opentracing 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | grpc_ctxtags "github.com/grpc-ecosystem/go-grpc-middleware/tags" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestTagsCarrier_Set_JaegerTraceFormat(t *testing.T) { 12 | var ( 13 | fakeTraceSampled = 1 14 | fakeInboundTraceId = "deadbeef" 15 | fakeInboundSpanId = "c0decafe" 16 | traceHeaderName = "uber-trace-id" 17 | ) 18 | 19 | traceHeaderValue := fmt.Sprintf("%s:%s:%s:%d", fakeInboundTraceId, fakeInboundSpanId, fakeInboundSpanId, fakeTraceSampled) 20 | 21 | c := &tagsCarrier{ 22 | Tags: grpc_ctxtags.NewTags(), 23 | traceHeaderName: traceHeaderName, 24 | } 25 | 26 | c.Set(traceHeaderName, traceHeaderValue) 27 | 28 | assert.EqualValues(t, map[string]interface{}{ 29 | TagTraceId: fakeInboundTraceId, 30 | TagSpanId: fakeInboundSpanId, 31 | TagSampled: "true", 32 | }, c.Tags.Values()) 33 | } 34 | -------------------------------------------------------------------------------- /grpc_opentracing/interceptors_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Michal Witkowski. All Rights Reserved. 2 | // See LICENSE for licensing terms. 3 | 4 | package grpc_opentracing_test 5 | 6 | import ( 7 | "context" 8 | "errors" 9 | "fmt" 10 | "io" 11 | "net/http" 12 | "strconv" 13 | "strings" 14 | "testing" 15 | 16 | "github.com/opentracing/opentracing-go" 17 | "github.com/opentracing/opentracing-go/log" 18 | "github.com/opentracing/opentracing-go/mocktracer" 19 | "github.com/stretchr/testify/assert" 20 | "github.com/stretchr/testify/require" 21 | "github.com/stretchr/testify/suite" 22 | "google.golang.org/grpc" 23 | "google.golang.org/grpc/codes" 24 | 25 | grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware" 26 | grpc_ctxtags "github.com/grpc-ecosystem/go-grpc-middleware/tags" 27 | grpc_testing "github.com/grpc-ecosystem/go-grpc-middleware/testing" 28 | pb_testproto "github.com/grpc-ecosystem/go-grpc-middleware/testing/testproto" 29 | grpc_opentracing "github.com/grpc-ecosystem/go-grpc-middleware/tracing/opentracing" 30 | ) 31 | 32 | var ( 33 | goodPing = &pb_testproto.PingRequest{Value: "something", SleepTimeMs: 9999} 34 | fakeInboundTraceId = 1337 35 | fakeInboundSpanId = 999 36 | traceHeaderName = "uber-trace-id" 37 | filterFunc = func(ctx context.Context, fullMethodName string) bool { return true } 38 | unaryRequestHandlerFunc = func(span opentracing.Span, req interface{}) { 39 | span.LogFields(log.Bool("unary-request-handler", true)) 40 | } 41 | ) 42 | 43 | type tracingAssertService struct { 44 | pb_testproto.TestServiceServer 45 | T *testing.T 46 | } 47 | 48 | func (s *tracingAssertService) Ping(ctx context.Context, ping *pb_testproto.PingRequest) (*pb_testproto.PingResponse, error) { 49 | assert.NotNil(s.T, opentracing.SpanFromContext(ctx), "handlers must have the spancontext in their context, otherwise propagation will fail") 50 | tags := grpc_ctxtags.Extract(ctx) 51 | assert.True(s.T, tags.Has(grpc_opentracing.TagTraceId), "tags must contain traceid") 52 | assert.True(s.T, tags.Has(grpc_opentracing.TagSpanId), "tags must contain spanid") 53 | assert.True(s.T, tags.Has(grpc_opentracing.TagSampled), "tags must contain sampled") 54 | assert.Equal(s.T, tags.Values()[grpc_opentracing.TagSampled], "true", "sampled must be set to true") 55 | return s.TestServiceServer.Ping(ctx, ping) 56 | } 57 | 58 | func (s *tracingAssertService) PingError(ctx context.Context, ping *pb_testproto.PingRequest) (*pb_testproto.Empty, error) { 59 | assert.NotNil(s.T, opentracing.SpanFromContext(ctx), "handlers must have the spancontext in their context, otherwise propagation will fail") 60 | return s.TestServiceServer.PingError(ctx, ping) 61 | } 62 | 63 | func (s *tracingAssertService) PingList(ping *pb_testproto.PingRequest, stream pb_testproto.TestService_PingListServer) error { 64 | assert.NotNil(s.T, opentracing.SpanFromContext(stream.Context()), "handlers must have the spancontext in their context, otherwise propagation will fail") 65 | tags := grpc_ctxtags.Extract(stream.Context()) 66 | assert.True(s.T, tags.Has(grpc_opentracing.TagTraceId), "tags must contain traceid") 67 | assert.True(s.T, tags.Has(grpc_opentracing.TagSpanId), "tags must contain spanid") 68 | assert.True(s.T, tags.Has(grpc_opentracing.TagSampled), "tags must contain sampled") 69 | assert.Equal(s.T, tags.Values()[grpc_opentracing.TagSampled], "true", "sampled must be set to true") 70 | return s.TestServiceServer.PingList(ping, stream) 71 | } 72 | 73 | func (s *tracingAssertService) PingEmpty(ctx context.Context, empty *pb_testproto.Empty) (*pb_testproto.PingResponse, error) { 74 | assert.NotNil(s.T, opentracing.SpanFromContext(ctx), "handlers must have the spancontext in their context, otherwise propagation will fail") 75 | tags := grpc_ctxtags.Extract(ctx) 76 | assert.True(s.T, tags.Has(grpc_opentracing.TagTraceId), "tags must contain traceid") 77 | assert.True(s.T, tags.Has(grpc_opentracing.TagSpanId), "tags must contain spanid") 78 | assert.True(s.T, tags.Has(grpc_opentracing.TagSampled), "tags must contain sampled") 79 | assert.Equal(s.T, tags.Values()[grpc_opentracing.TagSampled], "false", "sampled must be set to false") 80 | return s.TestServiceServer.PingEmpty(ctx, empty) 81 | } 82 | 83 | func TestTaggingSuite(t *testing.T) { 84 | mockTracer := mocktracer.New() 85 | opts := []grpc_opentracing.Option{ 86 | grpc_opentracing.WithTracer(mockTracer), 87 | grpc_opentracing.WithFilterFunc(filterFunc), 88 | grpc_opentracing.WithTraceHeaderName(traceHeaderName), 89 | grpc_opentracing.WithUnaryRequestHandlerFunc(unaryRequestHandlerFunc), 90 | } 91 | s := &OpentracingSuite{ 92 | mockTracer: mockTracer, 93 | InterceptorTestSuite: makeInterceptorTestSuite(t, opts), 94 | } 95 | suite.Run(t, s) 96 | } 97 | 98 | func TestTaggingSuiteJaeger(t *testing.T) { 99 | mockTracer := mocktracer.New() 100 | mockTracer.RegisterInjector(opentracing.HTTPHeaders, jaegerFormatInjector{}) 101 | mockTracer.RegisterExtractor(opentracing.HTTPHeaders, jaegerFormatExtractor{}) 102 | opts := []grpc_opentracing.Option{ 103 | grpc_opentracing.WithTracer(mockTracer), 104 | grpc_opentracing.WithUnaryRequestHandlerFunc(unaryRequestHandlerFunc), 105 | } 106 | s := &OpentracingSuite{ 107 | mockTracer: mockTracer, 108 | InterceptorTestSuite: makeInterceptorTestSuite(t, opts), 109 | } 110 | suite.Run(t, s) 111 | } 112 | 113 | func makeInterceptorTestSuite(t *testing.T, opts []grpc_opentracing.Option) *grpc_testing.InterceptorTestSuite { 114 | return &grpc_testing.InterceptorTestSuite{ 115 | TestService: &tracingAssertService{TestServiceServer: &grpc_testing.TestPingService{T: t}, T: t}, 116 | ClientOpts: []grpc.DialOption{ 117 | grpc.WithUnaryInterceptor(grpc_opentracing.UnaryClientInterceptor(opts...)), 118 | grpc.WithStreamInterceptor(grpc_opentracing.StreamClientInterceptor(opts...)), 119 | }, 120 | ServerOpts: []grpc.ServerOption{ 121 | grpc_middleware.WithStreamServerChain( 122 | grpc_ctxtags.StreamServerInterceptor(grpc_ctxtags.WithFieldExtractor(grpc_ctxtags.CodeGenRequestFieldExtractor)), 123 | grpc_opentracing.StreamServerInterceptor(opts...)), 124 | grpc_middleware.WithUnaryServerChain( 125 | grpc_ctxtags.UnaryServerInterceptor(grpc_ctxtags.WithFieldExtractor(grpc_ctxtags.CodeGenRequestFieldExtractor)), 126 | grpc_opentracing.UnaryServerInterceptor(opts...)), 127 | }, 128 | } 129 | } 130 | 131 | type OpentracingSuite struct { 132 | *grpc_testing.InterceptorTestSuite 133 | mockTracer *mocktracer.MockTracer 134 | } 135 | 136 | func (s *OpentracingSuite) SetupTest() { 137 | s.mockTracer.Reset() 138 | } 139 | 140 | func (s *OpentracingSuite) createContextFromFakeHttpRequestParent(ctx context.Context, sampled bool, opName string) context.Context { 141 | jFlag := 0 142 | if sampled { 143 | jFlag = 1 144 | } 145 | 146 | if len(opName) == 0 { 147 | opName = "/fake/parent/http/request" 148 | } 149 | 150 | hdr := http.Header{} 151 | hdr.Set(traceHeaderName, fmt.Sprintf("%d:%d:%d:%d", fakeInboundTraceId, fakeInboundSpanId, fakeInboundSpanId, jFlag)) 152 | hdr.Set("mockpfx-ids-traceid", fmt.Sprint(fakeInboundTraceId)) 153 | hdr.Set("mockpfx-ids-spanid", fmt.Sprint(fakeInboundSpanId)) 154 | hdr.Set("mockpfx-ids-sampled", fmt.Sprint(sampled)) 155 | 156 | parentSpanContext, err := s.mockTracer.Extract(opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(hdr)) 157 | require.NoError(s.T(), err, "parsing a fake HTTP request headers shouldn't fail, ever") 158 | fakeSpan := s.mockTracer.StartSpan( 159 | opName, 160 | // this is magical, it attaches the new span to the parent parentSpanContext, and creates an unparented one if empty. 161 | opentracing.ChildOf(parentSpanContext), 162 | ) 163 | fakeSpan.Finish() 164 | return opentracing.ContextWithSpan(ctx, fakeSpan) 165 | } 166 | 167 | func (s *OpentracingSuite) assertTracesCreated(methodName string) (clientSpan *mocktracer.MockSpan, serverSpan *mocktracer.MockSpan) { 168 | spans := s.mockTracer.FinishedSpans() 169 | for _, span := range spans { 170 | s.T().Logf("span: %v, tags: %v", span, span.Tags()) 171 | } 172 | require.Len(s.T(), spans, 3, "should record 3 spans: one fake inbound, one client, one server") 173 | traceIdAssert := fmt.Sprintf("traceId=%d", fakeInboundTraceId) 174 | for _, span := range spans { 175 | assert.Contains(s.T(), span.String(), traceIdAssert, "not part of the fake parent trace: %v", span) 176 | if span.OperationName == methodName { 177 | kind := fmt.Sprintf("%v", span.Tag("span.kind")) 178 | if kind == "client" { 179 | clientSpan = span 180 | } else if kind == "server" { 181 | serverSpan = span 182 | } 183 | assert.EqualValues(s.T(), span.Tag("component"), "gRPC", "span must be tagged with gRPC component") 184 | } 185 | } 186 | require.NotNil(s.T(), clientSpan, "client span must be there") 187 | require.NotNil(s.T(), serverSpan, "server span must be there") 188 | assert.EqualValues(s.T(), serverSpan.Tag("grpc.request.value"), "something", "grpc_ctxtags must be propagated, in this case ones from request fields") 189 | return clientSpan, serverSpan 190 | } 191 | 192 | func (s *OpentracingSuite) TestPing_PropagatesTraces() { 193 | ctx := s.createContextFromFakeHttpRequestParent(s.SimpleCtx(), true, "") 194 | _, err := s.Client.Ping(ctx, goodPing) 195 | require.NoError(s.T(), err, "there must be not be an on a successful call") 196 | s.assertTracesCreated("/mwitkow.testproto.TestService/Ping") 197 | } 198 | 199 | func (s *OpentracingSuite) TestPing_CustomOpName() { 200 | customOpName := "customOpName" 201 | 202 | ctx := s.createContextFromFakeHttpRequestParent(s.SimpleCtx(), true, customOpName) 203 | _, err := s.Client.Ping(ctx, goodPing) 204 | require.NoError(s.T(), err, "there must be not be an error on a successful call") 205 | 206 | spans := s.mockTracer.FinishedSpans() 207 | spanOpNames := make([]string, len(spans)) 208 | for _, span := range spans { 209 | spanOpNames = append(spanOpNames, span.OperationName) 210 | } 211 | 212 | require.Contains(s.T(), spanOpNames, customOpName, "finished spans must contain the custom operation name") 213 | 214 | } 215 | 216 | func (s *OpentracingSuite) TestPing_WithUnaryRequestHandlerFunc() { 217 | ctx := s.createContextFromFakeHttpRequestParent(s.SimpleCtx(), true, "") 218 | _, err := s.Client.Ping(ctx, goodPing) 219 | require.NoError(s.T(), err, "there must be not be an on a successful call") 220 | 221 | var hasLogKey bool 222 | Loop: 223 | for _, span := range s.mockTracer.FinishedSpans() { 224 | for _, record := range span.Logs() { 225 | for _, field := range record.Fields { 226 | if field.Key == "unary-request-handler" { 227 | hasLogKey = true 228 | break Loop 229 | } 230 | } 231 | } 232 | } 233 | require.True(s.T(), hasLogKey, "span field 'unary-request-handler' not found") 234 | } 235 | 236 | func (s *OpentracingSuite) TestPing_ClientContextTags() { 237 | const name = "opentracing.custom" 238 | ctx := grpc_opentracing.ClientAddContextTags( 239 | s.createContextFromFakeHttpRequestParent(s.SimpleCtx(), true, ""), 240 | opentracing.Tags{name: ""}, 241 | ) 242 | 243 | _, err := s.Client.Ping(ctx, goodPing) 244 | require.NoError(s.T(), err, "there must be not be an on a successful call") 245 | 246 | for _, span := range s.mockTracer.FinishedSpans() { 247 | if span.OperationName == "/mwitkow.testproto.TestService/Ping" { 248 | kind := fmt.Sprintf("%v", span.Tag("span.kind")) 249 | if kind == "client" { 250 | assert.Contains(s.T(), span.Tags(), name, "custom opentracing.Tags must be included in context") 251 | } 252 | } 253 | } 254 | } 255 | 256 | func (s *OpentracingSuite) TestPingList_PropagatesTraces() { 257 | ctx := s.createContextFromFakeHttpRequestParent(s.SimpleCtx(), true, "") 258 | stream, err := s.Client.PingList(ctx, goodPing) 259 | require.NoError(s.T(), err, "should not fail on establishing the stream") 260 | for { 261 | _, err := stream.Recv() 262 | if err == io.EOF { 263 | break 264 | } 265 | require.NoError(s.T(), err, "reading stream should not fail") 266 | } 267 | s.assertTracesCreated("/mwitkow.testproto.TestService/PingList") 268 | } 269 | 270 | func (s *OpentracingSuite) TestPingError_PropagatesTraces() { 271 | ctx := s.createContextFromFakeHttpRequestParent(s.SimpleCtx(), true, "") 272 | erroringPing := &pb_testproto.PingRequest{Value: "something", ErrorCodeReturned: uint32(codes.OutOfRange)} 273 | _, err := s.Client.PingError(ctx, erroringPing) 274 | require.Error(s.T(), err, "there must be an error returned here") 275 | clientSpan, serverSpan := s.assertTracesCreated("/mwitkow.testproto.TestService/PingError") 276 | assert.Equal(s.T(), true, clientSpan.Tag("error"), "client span needs to be marked as an error") 277 | assert.Equal(s.T(), true, serverSpan.Tag("error"), "server span needs to be marked as an error") 278 | } 279 | 280 | func (s *OpentracingSuite) TestPingEmpty_NotSampleTraces() { 281 | ctx := s.createContextFromFakeHttpRequestParent(s.SimpleCtx(), false, "") 282 | _, err := s.Client.PingEmpty(ctx, &pb_testproto.Empty{}) 283 | require.NoError(s.T(), err, "there must be not be an on a successful call") 284 | } 285 | 286 | type jaegerFormatInjector struct{} 287 | 288 | func (jaegerFormatInjector) Inject(ctx mocktracer.MockSpanContext, carrier interface{}) error { 289 | w := carrier.(opentracing.TextMapWriter) 290 | flags := 0 291 | if ctx.Sampled { 292 | flags = 1 293 | } 294 | w.Set(traceHeaderName, fmt.Sprintf("%d:%d::%d", ctx.TraceID, ctx.SpanID, flags)) 295 | 296 | return nil 297 | } 298 | 299 | type jaegerFormatExtractor struct{} 300 | 301 | func (jaegerFormatExtractor) Extract(carrier interface{}) (mocktracer.MockSpanContext, error) { 302 | rval := mocktracer.MockSpanContext{Sampled: true} 303 | reader, ok := carrier.(opentracing.TextMapReader) 304 | if !ok { 305 | return rval, opentracing.ErrInvalidCarrier 306 | } 307 | err := reader.ForeachKey(func(key, val string) error { 308 | lowerKey := strings.ToLower(key) 309 | switch { 310 | case lowerKey == traceHeaderName: 311 | parts := strings.Split(val, ":") 312 | if len(parts) != 4 { 313 | return errors.New("invalid trace id format") 314 | } 315 | traceId, err := strconv.Atoi(parts[0]) 316 | if err != nil { 317 | return err 318 | } 319 | rval.TraceID = traceId 320 | spanId, err := strconv.Atoi(parts[1]) 321 | if err != nil { 322 | return err 323 | } 324 | rval.SpanID = spanId 325 | flags, err := strconv.Atoi(parts[3]) 326 | if err != nil { 327 | return err 328 | } 329 | rval.Sampled = flags%2 == 1 330 | } 331 | return nil 332 | }) 333 | if rval.TraceID == 0 || rval.SpanID == 0 { 334 | return rval, opentracing.ErrSpanContextNotFound 335 | } 336 | if err != nil { 337 | return rval, err 338 | } 339 | return rval, nil 340 | } 341 | -------------------------------------------------------------------------------- /grpc_opentracing/metadata.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Michal Witkowski. All Rights Reserved. 2 | // See LICENSE for licensing terms. 3 | 4 | package grpc_opentracing 5 | 6 | import ( 7 | "encoding/base64" 8 | "strings" 9 | 10 | "google.golang.org/grpc/metadata" 11 | ) 12 | 13 | const ( 14 | binHdrSuffix = "-bin" 15 | ) 16 | 17 | // metadataTextMap extends a metadata.MD to be an opentracing textmap 18 | type metadataTextMap metadata.MD 19 | 20 | // Set is a opentracing.TextMapReader interface that extracts values. 21 | func (m metadataTextMap) Set(key, val string) { 22 | // gRPC allows for complex binary values to be written. 23 | encodedKey, encodedVal := encodeKeyValue(key, val) 24 | // The metadata object is a multimap, and previous values may exist, but for opentracing headers, we do not append 25 | // we just override. 26 | m[encodedKey] = []string{encodedVal} 27 | } 28 | 29 | // ForeachKey is a opentracing.TextMapReader interface that extracts values. 30 | func (m metadataTextMap) ForeachKey(callback func(key, val string) error) error { 31 | for k, vv := range m { 32 | for _, v := range vv { 33 | if err := callback(k, v); err != nil { 34 | return err 35 | } 36 | } 37 | } 38 | return nil 39 | } 40 | 41 | // encodeKeyValue encodes key and value qualified for transmission via gRPC. 42 | // note: copy pasted from private values of grpc.metadata 43 | func encodeKeyValue(k, v string) (string, string) { 44 | k = strings.ToLower(k) 45 | if strings.HasSuffix(k, binHdrSuffix) { 46 | val := base64.StdEncoding.EncodeToString([]byte(v)) 47 | v = string(val) 48 | } 49 | return k, v 50 | } 51 | -------------------------------------------------------------------------------- /grpc_opentracing/options.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Michal Witkowski. All Rights Reserved. 2 | // See LICENSE for licensing terms. 3 | 4 | package grpc_opentracing 5 | 6 | import ( 7 | "context" 8 | 9 | "github.com/opentracing/opentracing-go" 10 | ) 11 | 12 | var ( 13 | defaultOptions = &options{ 14 | filterOutFunc: nil, 15 | tracer: nil, 16 | } 17 | ) 18 | 19 | // FilterFunc allows users to provide a function that filters out certain methods from being traced. 20 | // 21 | // If it returns false, the given request will not be traced. 22 | type FilterFunc func(ctx context.Context, fullMethodName string) bool 23 | 24 | // UnaryRequestHandlerFunc is a custom request handler 25 | type UnaryRequestHandlerFunc func(span opentracing.Span, req interface{}) 26 | 27 | // OpNameFunc is a func that allows custom operation names instead of the gRPC method. 28 | type OpNameFunc func(method string) string 29 | 30 | type options struct { 31 | filterOutFunc FilterFunc 32 | tracer opentracing.Tracer 33 | traceHeaderName string 34 | unaryRequestHandlerFunc UnaryRequestHandlerFunc 35 | opNameFunc OpNameFunc 36 | debug bool 37 | } 38 | 39 | func evaluateOptions(opts []Option) *options { 40 | optCopy := &options{} 41 | *optCopy = *defaultOptions 42 | for _, o := range opts { 43 | o(optCopy) 44 | } 45 | if optCopy.tracer == nil { 46 | optCopy.tracer = opentracing.GlobalTracer() 47 | } 48 | if optCopy.traceHeaderName == "" { 49 | optCopy.traceHeaderName = "uber-trace-id" 50 | } 51 | return optCopy 52 | } 53 | 54 | type Option func(*options) 55 | 56 | // WithFilterFunc customizes the function used for deciding whether a given call is traced or not. 57 | func WithFilterFunc(f FilterFunc) Option { 58 | return func(o *options) { 59 | o.filterOutFunc = f 60 | } 61 | } 62 | 63 | func WithDev(debug bool) Option { 64 | return func(o *options) { 65 | o.debug = debug 66 | } 67 | } 68 | 69 | // WithTraceHeaderName customizes the trace header name where trace metadata passed with requests. 70 | // Default one is `uber-trace-id` 71 | func WithTraceHeaderName(name string) Option { 72 | return func(o *options) { 73 | o.traceHeaderName = name 74 | } 75 | } 76 | 77 | // WithTracer sets a custom tracer to be used for this middleware, otherwise the opentracing.GlobalTracer is used. 78 | func WithTracer(tracer opentracing.Tracer) Option { 79 | return func(o *options) { 80 | o.tracer = tracer 81 | } 82 | } 83 | 84 | // WithUnaryRequestHandlerFunc sets a custom handler for the request 85 | func WithUnaryRequestHandlerFunc(f UnaryRequestHandlerFunc) Option { 86 | return func(o *options) { 87 | o.unaryRequestHandlerFunc = f 88 | } 89 | } 90 | 91 | // WithOpName customizes the trace Operation name 92 | func WithOpName(f OpNameFunc) Option { 93 | return func(o *options) { 94 | o.opNameFunc = f 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /grpc_opentracing/server_interceptors.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Michal Witkowski. All Rights Reserved. 2 | // See LICENSE for licensing terms. 3 | 4 | package grpc_opentracing 5 | 6 | import ( 7 | "context" 8 | 9 | grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware" 10 | grpc_ctxtags "github.com/grpc-ecosystem/go-grpc-middleware/tags" 11 | "github.com/grpc-ecosystem/go-grpc-middleware/util/metautils" 12 | "github.com/opentracing/opentracing-go" 13 | "github.com/opentracing/opentracing-go/ext" 14 | "github.com/opentracing/opentracing-go/log" 15 | "github.com/xxjwxc/public/tools" 16 | "google.golang.org/grpc" 17 | "google.golang.org/grpc/grpclog" 18 | ) 19 | 20 | var ( 21 | grpcTag = opentracing.Tag{Key: string(ext.Component), Value: "gRPC"} 22 | ) 23 | 24 | // UnaryServerInterceptor returns a new unary server interceptor for OpenTracing. 25 | func UnaryServerInterceptor(opts ...Option) grpc.UnaryServerInterceptor { 26 | o := evaluateOptions(opts) 27 | return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { 28 | if o.filterOutFunc != nil && !o.filterOutFunc(ctx, info.FullMethod) { 29 | return handler(ctx, req) 30 | } 31 | opName := info.FullMethod 32 | if o.opNameFunc != nil { 33 | opName = o.opNameFunc(info.FullMethod) 34 | } 35 | newCtx, serverSpan := newServerSpanFromInbound(ctx, o.tracer, o.traceHeaderName, opName) 36 | if o.unaryRequestHandlerFunc != nil { 37 | o.unaryRequestHandlerFunc(serverSpan, req) 38 | } 39 | resp, err := handler(newCtx, req) 40 | if o.debug { 41 | serverSpan.SetTag("request", tools.JSONDecode(req)) 42 | serverSpan.SetTag("response", tools.JSONDecode(resp)) 43 | } 44 | finishServerSpan(ctx, serverSpan, err) 45 | return resp, err 46 | } 47 | } 48 | 49 | // StreamServerInterceptor returns a new streaming server interceptor for OpenTracing. 50 | func StreamServerInterceptor(opts ...Option) grpc.StreamServerInterceptor { 51 | o := evaluateOptions(opts) 52 | return func(srv interface{}, stream grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { 53 | if o.filterOutFunc != nil && !o.filterOutFunc(stream.Context(), info.FullMethod) { 54 | return handler(srv, stream) 55 | } 56 | opName := info.FullMethod 57 | if o.opNameFunc != nil { 58 | opName = o.opNameFunc(info.FullMethod) 59 | } 60 | newCtx, serverSpan := newServerSpanFromInbound(stream.Context(), o.tracer, o.traceHeaderName, opName) 61 | wrappedStream := grpc_middleware.WrapServerStream(stream) 62 | wrappedStream.WrappedContext = newCtx 63 | err := handler(srv, wrappedStream) 64 | finishServerSpan(newCtx, serverSpan, err) 65 | return err 66 | } 67 | } 68 | 69 | func newServerSpanFromInbound(ctx context.Context, tracer opentracing.Tracer, traceHeaderName, opName string) (context.Context, opentracing.Span) { 70 | md := metautils.ExtractIncoming(ctx) 71 | parentSpanContext, err := tracer.Extract(opentracing.HTTPHeaders, metadataTextMap(md)) 72 | if err != nil && err != opentracing.ErrSpanContextNotFound { 73 | grpclog.Infof("grpc_opentracing: failed parsing trace information: %v", err) 74 | } 75 | 76 | serverSpan := tracer.StartSpan( 77 | opName, 78 | // this is magical, it attaches the new span to the parent parentSpanContext, and creates an unparented one if empty. 79 | ext.RPCServerOption(parentSpanContext), 80 | grpcTag, 81 | ) 82 | 83 | injectOpentracingIdsToTags(traceHeaderName, serverSpan, grpc_ctxtags.Extract(ctx)) 84 | return opentracing.ContextWithSpan(ctx, serverSpan), serverSpan 85 | } 86 | 87 | func finishServerSpan(ctx context.Context, serverSpan opentracing.Span, err error) { 88 | // Log context information 89 | tags := grpc_ctxtags.Extract(ctx) 90 | for k, v := range tags.Values() { 91 | // Don't tag errors, log them instead. 92 | if vErr, ok := v.(error); ok { 93 | serverSpan.LogKV(k, vErr.Error()) 94 | } else { 95 | serverSpan.SetTag(k, v) 96 | } 97 | } 98 | if err != nil { 99 | ext.Error.Set(serverSpan, true) 100 | serverSpan.LogFields(log.String("event", "error"), log.String("message", err.Error())) 101 | } 102 | serverSpan.Finish() 103 | } 104 | -------------------------------------------------------------------------------- /mdns/client.go: -------------------------------------------------------------------------------- 1 | package mdns 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "net" 8 | "strings" 9 | "sync" 10 | "time" 11 | 12 | "github.com/miekg/dns" 13 | "golang.org/x/net/ipv4" 14 | "golang.org/x/net/ipv6" 15 | ) 16 | 17 | // ServiceEntry is returned after we query for a service 18 | type ServiceEntry struct { 19 | Name string 20 | Host string 21 | AddrV4 net.IP 22 | AddrV6 net.IP 23 | Port int 24 | Info string 25 | InfoFields []string 26 | TTL int 27 | 28 | Addr net.IP // @Deprecated 29 | 30 | hasTXT bool 31 | sent bool 32 | } 33 | 34 | // complete is used to check if we have all the info we need 35 | func (s *ServiceEntry) complete() bool { 36 | return (s.AddrV4 != nil || s.AddrV6 != nil || s.Addr != nil) && s.Port != 0 && s.hasTXT 37 | } 38 | 39 | // QueryParam is used to customize how a Lookup is performed 40 | type QueryParam struct { 41 | Service string // Service to lookup 42 | Domain string // Lookup domain, default "local" 43 | Context context.Context // Context 44 | Timeout time.Duration // Lookup timeout, default 1 second. Ignored if Context is provided 45 | Interface *net.Interface // Multicast interface to use 46 | Entries chan<- *ServiceEntry // Entries Channel 47 | WantUnicastResponse bool // Unicast response desired, as per 5.4 in RFC 48 | } 49 | 50 | // DefaultParams is used to return a default set of QueryParam's 51 | func DefaultParams(service string) *QueryParam { 52 | return &QueryParam{ 53 | Service: service, 54 | Domain: "local", 55 | Timeout: time.Second, 56 | Entries: make(chan *ServiceEntry), 57 | WantUnicastResponse: false, // TODO(reddaly): Change this default. 58 | } 59 | } 60 | 61 | // Query looks up a given service, in a domain, waiting at most 62 | // for a timeout before finishing the query. The results are streamed 63 | // to a channel. Sends will not block, so clients should make sure to 64 | // either read or buffer. 65 | func Query(params *QueryParam) error { 66 | // Create a new client 67 | client, err := newClient() 68 | if err != nil { 69 | return err 70 | } 71 | defer client.Close() 72 | 73 | // Set the multicast interface 74 | if params.Interface != nil { 75 | if err := client.setInterface(params.Interface, false); err != nil { 76 | return err 77 | } 78 | } 79 | 80 | // Ensure defaults are set 81 | if params.Domain == "" { 82 | params.Domain = "local" 83 | } 84 | 85 | if params.Context == nil { 86 | if params.Timeout == 0 { 87 | params.Timeout = time.Second 88 | } 89 | params.Context, _ = context.WithTimeout(context.Background(), params.Timeout) 90 | if err != nil { 91 | return err 92 | } 93 | } 94 | 95 | // Run the query 96 | return client.query(params) 97 | } 98 | 99 | // Listen listens indefinitely for multicast updates 100 | func Listen(entries chan<- *ServiceEntry, exit chan struct{}) error { 101 | // Create a new client 102 | client, err := newClient() 103 | if err != nil { 104 | return err 105 | } 106 | defer client.Close() 107 | 108 | client.setInterface(nil, true) 109 | 110 | // Start listening for response packets 111 | msgCh := make(chan *dns.Msg, 32) 112 | 113 | go client.recv(client.ipv4UnicastConn, msgCh) 114 | go client.recv(client.ipv6UnicastConn, msgCh) 115 | go client.recv(client.ipv4MulticastConn, msgCh) 116 | go client.recv(client.ipv6MulticastConn, msgCh) 117 | 118 | ip := make(map[string]*ServiceEntry) 119 | 120 | for { 121 | select { 122 | case <-exit: 123 | return nil 124 | case <-client.closedCh: 125 | return nil 126 | case m := <-msgCh: 127 | e := messageToEntry(m, ip) 128 | if e == nil { 129 | continue 130 | } 131 | 132 | // Check if this entry is complete 133 | if e.complete() { 134 | if e.sent { 135 | continue 136 | } 137 | e.sent = true 138 | entries <- e 139 | ip = make(map[string]*ServiceEntry) 140 | } else { 141 | // Fire off a node specific query 142 | m := new(dns.Msg) 143 | m.SetQuestion(e.Name, dns.TypePTR) 144 | m.RecursionDesired = false 145 | if err := client.sendQuery(m); err != nil { 146 | log.Printf("[ERR] mdns: Failed to query instance %s: %v", e.Name, err) 147 | } 148 | } 149 | } 150 | } 151 | 152 | return nil 153 | } 154 | 155 | // Lookup is the same as Query, however it uses all the default parameters 156 | func Lookup(service string, entries chan<- *ServiceEntry) error { 157 | params := DefaultParams(service) 158 | params.Entries = entries 159 | return Query(params) 160 | } 161 | 162 | // Client provides a query interface that can be used to 163 | // search for service providers using mDNS 164 | type client struct { 165 | ipv4UnicastConn *net.UDPConn 166 | ipv6UnicastConn *net.UDPConn 167 | 168 | ipv4MulticastConn *net.UDPConn 169 | ipv6MulticastConn *net.UDPConn 170 | 171 | closed bool 172 | closedCh chan struct{} // TODO(reddaly): This doesn't appear to be used. 173 | closeLock sync.Mutex 174 | } 175 | 176 | // NewClient creates a new mdns Client that can be used to query 177 | // for records 178 | func newClient() (*client, error) { 179 | // TODO(reddaly): At least attempt to bind to the port required in the spec. 180 | // Create a IPv4 listener 181 | uconn4, err4 := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IPv4zero, Port: 0}) 182 | uconn6, err6 := net.ListenUDP("udp6", &net.UDPAddr{IP: net.IPv6zero, Port: 0}) 183 | if err4 != nil && err6 != nil { 184 | log.Printf("[ERR] mdns: Failed to bind to udp port: %v %v", err4, err6) 185 | } 186 | 187 | if uconn4 == nil && uconn6 == nil { 188 | return nil, fmt.Errorf("failed to bind to any unicast udp port") 189 | } 190 | 191 | if uconn4 == nil { 192 | uconn4 = &net.UDPConn{} 193 | } 194 | 195 | if uconn6 == nil { 196 | uconn6 = &net.UDPConn{} 197 | } 198 | 199 | mconn4, err4 := net.ListenUDP("udp4", mdnsWildcardAddrIPv4) 200 | mconn6, err6 := net.ListenUDP("udp6", mdnsWildcardAddrIPv6) 201 | if err4 != nil && err6 != nil { 202 | log.Printf("[ERR] mdns: Failed to bind to udp port: %v %v", err4, err6) 203 | } 204 | 205 | if mconn4 == nil && mconn6 == nil { 206 | return nil, fmt.Errorf("failed to bind to any multicast udp port") 207 | } 208 | 209 | if mconn4 == nil { 210 | mconn4 = &net.UDPConn{} 211 | } 212 | 213 | if mconn6 == nil { 214 | mconn6 = &net.UDPConn{} 215 | } 216 | 217 | p1 := ipv4.NewPacketConn(mconn4) 218 | p2 := ipv6.NewPacketConn(mconn6) 219 | p1.SetMulticastLoopback(true) 220 | p2.SetMulticastLoopback(true) 221 | 222 | ifaces, err := net.Interfaces() 223 | if err != nil { 224 | return nil, err 225 | } 226 | 227 | var errCount1, errCount2 int 228 | 229 | for _, iface := range ifaces { 230 | if err := p1.JoinGroup(&iface, &net.UDPAddr{IP: mdnsGroupIPv4}); err != nil { 231 | errCount1++ 232 | } 233 | if err := p2.JoinGroup(&iface, &net.UDPAddr{IP: mdnsGroupIPv6}); err != nil { 234 | errCount2++ 235 | } 236 | } 237 | 238 | if len(ifaces) == errCount1 && len(ifaces) == errCount2 { 239 | return nil, fmt.Errorf("Failed to join multicast group on all interfaces!") 240 | } 241 | 242 | c := &client{ 243 | ipv4MulticastConn: mconn4, 244 | ipv6MulticastConn: mconn6, 245 | ipv4UnicastConn: uconn4, 246 | ipv6UnicastConn: uconn6, 247 | closedCh: make(chan struct{}), 248 | } 249 | return c, nil 250 | } 251 | 252 | // Close is used to cleanup the client 253 | func (c *client) Close() error { 254 | c.closeLock.Lock() 255 | defer c.closeLock.Unlock() 256 | 257 | if c.closed { 258 | return nil 259 | } 260 | c.closed = true 261 | 262 | close(c.closedCh) 263 | 264 | if c.ipv4UnicastConn != nil { 265 | c.ipv4UnicastConn.Close() 266 | } 267 | if c.ipv6UnicastConn != nil { 268 | c.ipv6UnicastConn.Close() 269 | } 270 | if c.ipv4MulticastConn != nil { 271 | c.ipv4MulticastConn.Close() 272 | } 273 | if c.ipv6MulticastConn != nil { 274 | c.ipv6MulticastConn.Close() 275 | } 276 | 277 | return nil 278 | } 279 | 280 | // setInterface is used to set the query interface, uses sytem 281 | // default if not provided 282 | func (c *client) setInterface(iface *net.Interface, loopback bool) error { 283 | p := ipv4.NewPacketConn(c.ipv4UnicastConn) 284 | if err := p.JoinGroup(iface, &net.UDPAddr{IP: mdnsGroupIPv4}); err != nil { 285 | return err 286 | } 287 | p2 := ipv6.NewPacketConn(c.ipv6UnicastConn) 288 | if err := p2.JoinGroup(iface, &net.UDPAddr{IP: mdnsGroupIPv6}); err != nil { 289 | return err 290 | } 291 | p = ipv4.NewPacketConn(c.ipv4MulticastConn) 292 | if err := p.JoinGroup(iface, &net.UDPAddr{IP: mdnsGroupIPv4}); err != nil { 293 | return err 294 | } 295 | p2 = ipv6.NewPacketConn(c.ipv6MulticastConn) 296 | if err := p2.JoinGroup(iface, &net.UDPAddr{IP: mdnsGroupIPv6}); err != nil { 297 | return err 298 | } 299 | 300 | if loopback { 301 | p.SetMulticastLoopback(true) 302 | p2.SetMulticastLoopback(true) 303 | } 304 | 305 | return nil 306 | } 307 | 308 | // query is used to perform a lookup and stream results 309 | func (c *client) query(params *QueryParam) error { 310 | // Create the service name 311 | serviceAddr := fmt.Sprintf("%s.%s.", trimDot(params.Service), trimDot(params.Domain)) 312 | 313 | // Start listening for response packets 314 | msgCh := make(chan *dns.Msg, 32) 315 | go c.recv(c.ipv4UnicastConn, msgCh) 316 | go c.recv(c.ipv6UnicastConn, msgCh) 317 | go c.recv(c.ipv4MulticastConn, msgCh) 318 | go c.recv(c.ipv6MulticastConn, msgCh) 319 | 320 | // Send the query 321 | m := new(dns.Msg) 322 | m.SetQuestion(serviceAddr, dns.TypePTR) 323 | // RFC 6762, section 18.12. Repurposing of Top Bit of qclass in Question 324 | // Section 325 | // 326 | // In the Question Section of a Multicast DNS query, the top bit of the qclass 327 | // field is used to indicate that unicast responses are preferred for this 328 | // particular question. (See Section 5.4.) 329 | if params.WantUnicastResponse { 330 | m.Question[0].Qclass |= 1 << 15 331 | } 332 | m.RecursionDesired = false 333 | if err := c.sendQuery(m); err != nil { 334 | return err 335 | } 336 | 337 | // Map the in-progress responses 338 | inprogress := make(map[string]*ServiceEntry) 339 | 340 | for { 341 | select { 342 | case resp := <-msgCh: 343 | inp := messageToEntry(resp, inprogress) 344 | if inp == nil { 345 | continue 346 | } 347 | 348 | // Check if this entry is complete 349 | if inp.complete() { 350 | if inp.sent { 351 | continue 352 | } 353 | inp.sent = true 354 | select { 355 | case params.Entries <- inp: 356 | case <-params.Context.Done(): 357 | return nil 358 | } 359 | } else { 360 | // Fire off a node specific query 361 | m := new(dns.Msg) 362 | m.SetQuestion(inp.Name, dns.TypePTR) 363 | m.RecursionDesired = false 364 | if err := c.sendQuery(m); err != nil { 365 | log.Printf("[ERR] mdns: Failed to query instance %s: %v", inp.Name, err) 366 | } 367 | } 368 | case <-params.Context.Done(): 369 | return nil 370 | } 371 | } 372 | } 373 | 374 | // sendQuery is used to multicast a query out 375 | func (c *client) sendQuery(q *dns.Msg) error { 376 | buf, err := q.Pack() 377 | if err != nil { 378 | return err 379 | } 380 | if c.ipv4UnicastConn != nil { 381 | c.ipv4UnicastConn.WriteToUDP(buf, ipv4Addr) 382 | } 383 | if c.ipv6UnicastConn != nil { 384 | c.ipv6UnicastConn.WriteToUDP(buf, ipv6Addr) 385 | } 386 | return nil 387 | } 388 | 389 | // recv is used to receive until we get a shutdown 390 | func (c *client) recv(l *net.UDPConn, msgCh chan *dns.Msg) { 391 | if l == nil { 392 | return 393 | } 394 | buf := make([]byte, 65536) 395 | for { 396 | c.closeLock.Lock() 397 | if c.closed { 398 | c.closeLock.Unlock() 399 | return 400 | } 401 | c.closeLock.Unlock() 402 | n, err := l.Read(buf) 403 | if err != nil { 404 | continue 405 | } 406 | msg := new(dns.Msg) 407 | if err := msg.Unpack(buf[:n]); err != nil { 408 | continue 409 | } 410 | select { 411 | case msgCh <- msg: 412 | case <-c.closedCh: 413 | return 414 | } 415 | } 416 | } 417 | 418 | // ensureName is used to ensure the named node is in progress 419 | func ensureName(inprogress map[string]*ServiceEntry, name string) *ServiceEntry { 420 | if inp, ok := inprogress[name]; ok { 421 | return inp 422 | } 423 | inp := &ServiceEntry{ 424 | Name: name, 425 | } 426 | inprogress[name] = inp 427 | return inp 428 | } 429 | 430 | // alias is used to setup an alias between two entries 431 | func alias(inprogress map[string]*ServiceEntry, src, dst string) { 432 | srcEntry := ensureName(inprogress, src) 433 | inprogress[dst] = srcEntry 434 | } 435 | 436 | func messageToEntry(m *dns.Msg, inprogress map[string]*ServiceEntry) *ServiceEntry { 437 | var inp *ServiceEntry 438 | 439 | for _, answer := range append(m.Answer, m.Extra...) { 440 | // TODO(reddaly): Check that response corresponds to serviceAddr? 441 | switch rr := answer.(type) { 442 | case *dns.PTR: 443 | // Create new entry for this 444 | inp = ensureName(inprogress, rr.Ptr) 445 | if inp.complete() { 446 | continue 447 | } 448 | case *dns.SRV: 449 | // Check for a target mismatch 450 | if rr.Target != rr.Hdr.Name { 451 | alias(inprogress, rr.Hdr.Name, rr.Target) 452 | } 453 | 454 | // Get the port 455 | inp = ensureName(inprogress, rr.Hdr.Name) 456 | if inp.complete() { 457 | continue 458 | } 459 | inp.Host = rr.Target 460 | inp.Port = int(rr.Port) 461 | case *dns.TXT: 462 | // Pull out the txt 463 | inp = ensureName(inprogress, rr.Hdr.Name) 464 | if inp.complete() { 465 | continue 466 | } 467 | inp.Info = strings.Join(rr.Txt, "|") 468 | inp.InfoFields = rr.Txt 469 | inp.hasTXT = true 470 | case *dns.A: 471 | // Pull out the IP 472 | inp = ensureName(inprogress, rr.Hdr.Name) 473 | if inp.complete() { 474 | continue 475 | } 476 | inp.Addr = rr.A // @Deprecated 477 | inp.AddrV4 = rr.A 478 | case *dns.AAAA: 479 | // Pull out the IP 480 | inp = ensureName(inprogress, rr.Hdr.Name) 481 | if inp.complete() { 482 | continue 483 | } 484 | inp.Addr = rr.AAAA // @Deprecated 485 | inp.AddrV6 = rr.AAAA 486 | } 487 | 488 | if inp != nil { 489 | inp.TTL = int(answer.Header().Ttl) 490 | } 491 | } 492 | 493 | return inp 494 | } 495 | -------------------------------------------------------------------------------- /mdns/dns_sd.go: -------------------------------------------------------------------------------- 1 | package mdns 2 | 3 | import "github.com/miekg/dns" 4 | 5 | // DNSSDService is a service that complies with the DNS-SD (RFC 6762) and MDNS 6 | // (RFC 6762) specs for local, multicast-DNS-based discovery. 7 | // 8 | // DNSSDService implements the Zone interface and wraps an MDNSService instance. 9 | // To deploy an mDNS service that is compliant with DNS-SD, it's recommended to 10 | // register only the wrapped instance with the server. 11 | // 12 | // Example usage: 13 | // service := &mdns.DNSSDService{ 14 | // MDNSService: &mdns.MDNSService{ 15 | // Instance: "My Foobar Service", 16 | // Service: "_foobar._tcp", 17 | // Port: 8000, 18 | // } 19 | // } 20 | // server, err := mdns.NewServer(&mdns.Config{Zone: service}) 21 | // if err != nil { 22 | // log.Fatalf("Error creating server: %v", err) 23 | // } 24 | // defer server.Shutdown() 25 | type DNSSDService struct { 26 | MDNSService *MDNSService 27 | } 28 | 29 | // Records returns DNS records in response to a DNS question. 30 | // 31 | // This function returns the DNS response of the underlying MDNSService 32 | // instance. It also returns a PTR record for a request for " 33 | // _services._dns-sd._udp.", as described in section 9 of RFC 6763 34 | // ("Service Type Enumeration"), to allow browsing of the underlying MDNSService 35 | // instance. 36 | func (s *DNSSDService) Records(q dns.Question) []dns.RR { 37 | var recs []dns.RR 38 | if q.Name == "_services._dns-sd._udp."+s.MDNSService.Domain+"." { 39 | recs = s.dnssdMetaQueryRecords(q) 40 | } 41 | return append(recs, s.MDNSService.Records(q)...) 42 | } 43 | 44 | // dnssdMetaQueryRecords returns the DNS records in response to a "meta-query" 45 | // issued to browse for DNS-SD services, as per section 9. of RFC6763. 46 | // 47 | // A meta-query has a name of the form "_services._dns-sd._udp." where 48 | // Domain is a fully-qualified domain, such as "local." 49 | func (s *DNSSDService) dnssdMetaQueryRecords(q dns.Question) []dns.RR { 50 | // Intended behavior, as described in the RFC: 51 | // ...it may be useful for network administrators to find the list of 52 | // advertised service types on the network, even if those Service Names 53 | // are just opaque identifiers and not particularly informative in 54 | // isolation. 55 | // 56 | // For this purpose, a special meta-query is defined. A DNS query for PTR 57 | // records with the name "_services._dns-sd._udp." yields a set of 58 | // PTR records, where the rdata of each PTR record is the two-abel 59 | // name, plus the same domain, e.g., "_http._tcp.". 60 | // Including the domain in the PTR rdata allows for slightly better name 61 | // compression in Unicast DNS responses, but only the first two labels are 62 | // relevant for the purposes of service type enumeration. These two-label 63 | // service types can then be used to construct subsequent Service Instance 64 | // Enumeration PTR queries, in this or others, to discover 65 | // instances of that service type. 66 | return []dns.RR{ 67 | &dns.PTR{ 68 | Hdr: dns.RR_Header{ 69 | Name: q.Name, 70 | Rrtype: dns.TypePTR, 71 | Class: dns.ClassINET, 72 | Ttl: defaultTTL, 73 | }, 74 | Ptr: s.MDNSService.serviceAddr, 75 | }, 76 | } 77 | } 78 | 79 | // Announcement returns DNS records that should be broadcast during the initial 80 | // availability of the service, as described in section 8.3 of RFC 6762. 81 | // TODO(reddaly): Add this when Announcement is added to the mdns.Zone interface. 82 | //func (s *DNSSDService) Announcement() []dns.RR { 83 | // return s.MDNSService.Announcement() 84 | //} 85 | -------------------------------------------------------------------------------- /mdns/dns_sd_test.go: -------------------------------------------------------------------------------- 1 | package mdns 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | import "github.com/miekg/dns" 8 | 9 | type mockMDNSService struct{} 10 | 11 | func (s *mockMDNSService) Records(q dns.Question) []dns.RR { 12 | return []dns.RR{ 13 | &dns.PTR{ 14 | Hdr: dns.RR_Header{ 15 | Name: "fakerecord", 16 | Rrtype: dns.TypePTR, 17 | Class: dns.ClassINET, 18 | Ttl: 42, 19 | }, 20 | Ptr: "fake.local.", 21 | }, 22 | } 23 | } 24 | 25 | func (s *mockMDNSService) Announcement() []dns.RR { 26 | return []dns.RR{ 27 | &dns.PTR{ 28 | Hdr: dns.RR_Header{ 29 | Name: "fakeannounce", 30 | Rrtype: dns.TypePTR, 31 | Class: dns.ClassINET, 32 | Ttl: 42, 33 | }, 34 | Ptr: "fake.local.", 35 | }, 36 | } 37 | } 38 | 39 | func TestDNSSDServiceRecords(t *testing.T) { 40 | s := &DNSSDService{ 41 | MDNSService: &MDNSService{ 42 | serviceAddr: "_foobar._tcp.local.", 43 | Domain: "local", 44 | }, 45 | } 46 | q := dns.Question{ 47 | Name: "_services._dns-sd._udp.local.", 48 | Qtype: dns.TypePTR, 49 | Qclass: dns.ClassINET, 50 | } 51 | recs := s.Records(q) 52 | if got, want := len(recs), 1; got != want { 53 | t.Fatalf("s.Records(%v) returned %v records, want %v", q, got, want) 54 | } 55 | 56 | want := dns.RR(&dns.PTR{ 57 | Hdr: dns.RR_Header{ 58 | Name: "_services._dns-sd._udp.local.", 59 | Rrtype: dns.TypePTR, 60 | Class: dns.ClassINET, 61 | Ttl: defaultTTL, 62 | }, 63 | Ptr: "_foobar._tcp.local.", 64 | }) 65 | if got := recs[0]; !reflect.DeepEqual(got, want) { 66 | t.Errorf("s.Records()[0] = %v, want %v", got, want) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /mdns/server.go: -------------------------------------------------------------------------------- 1 | package mdns 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "math/rand" 7 | "net" 8 | "sync" 9 | "time" 10 | 11 | "github.com/miekg/dns" 12 | "golang.org/x/net/ipv4" 13 | "golang.org/x/net/ipv6" 14 | ) 15 | 16 | var ( 17 | mdnsGroupIPv4 = net.ParseIP("224.0.0.251") 18 | mdnsGroupIPv6 = net.ParseIP("ff02::fb") 19 | 20 | // mDNS wildcard addresses 21 | mdnsWildcardAddrIPv4 = &net.UDPAddr{ 22 | IP: net.ParseIP("224.0.0.0"), 23 | Port: 5353, 24 | } 25 | mdnsWildcardAddrIPv6 = &net.UDPAddr{ 26 | IP: net.ParseIP("ff02::"), 27 | Port: 5353, 28 | } 29 | 30 | // mDNS endpoint addresses 31 | ipv4Addr = &net.UDPAddr{ 32 | IP: mdnsGroupIPv4, 33 | Port: 5353, 34 | } 35 | ipv6Addr = &net.UDPAddr{ 36 | IP: mdnsGroupIPv6, 37 | Port: 5353, 38 | } 39 | ) 40 | 41 | // Config is used to configure the mDNS server 42 | type Config struct { 43 | // Zone must be provided to support responding to queries 44 | Zone Zone 45 | 46 | // Iface if provided binds the multicast listener to the given 47 | // interface. If not provided, the system default multicase interface 48 | // is used. 49 | Iface *net.Interface 50 | 51 | // Port If it is not 0, replace the port 5353 with this port number. 52 | Port int 53 | } 54 | 55 | // mDNS server is used to listen for mDNS queries and respond if we 56 | // have a matching local record 57 | type Server struct { 58 | config *Config 59 | 60 | ipv4List *net.UDPConn 61 | ipv6List *net.UDPConn 62 | 63 | shutdown bool 64 | shutdownCh chan struct{} 65 | shutdownLock sync.Mutex 66 | wg sync.WaitGroup 67 | } 68 | 69 | // NewServer is used to create a new mDNS server from a config 70 | func NewServer(config *Config) (*Server, error) { 71 | if config.Port != 0 { 72 | mdnsWildcardAddrIPv4.Port = config.Port 73 | mdnsWildcardAddrIPv6.Port = config.Port 74 | ipv4Addr.Port = config.Port 75 | ipv6Addr.Port = config.Port 76 | } 77 | 78 | // Create the listeners 79 | // Create wildcard connections (because :5353 can be already taken by other apps) 80 | ipv4List, _ := net.ListenUDP("udp4", mdnsWildcardAddrIPv4) 81 | ipv6List, _ := net.ListenUDP("udp6", mdnsWildcardAddrIPv6) 82 | if ipv4List == nil && ipv6List == nil { 83 | return nil, fmt.Errorf("[ERR] mdns: Failed to bind to any udp port!") 84 | } 85 | 86 | if ipv4List == nil { 87 | ipv4List = &net.UDPConn{} 88 | } 89 | if ipv6List == nil { 90 | ipv6List = &net.UDPConn{} 91 | } 92 | 93 | // Join multicast groups to receive announcements 94 | p1 := ipv4.NewPacketConn(ipv4List) 95 | p2 := ipv6.NewPacketConn(ipv6List) 96 | p1.SetMulticastLoopback(true) 97 | p2.SetMulticastLoopback(true) 98 | 99 | if config.Iface != nil { 100 | if err := p1.JoinGroup(config.Iface, &net.UDPAddr{IP: mdnsGroupIPv4}); err != nil { 101 | return nil, err 102 | } 103 | if err := p2.JoinGroup(config.Iface, &net.UDPAddr{IP: mdnsGroupIPv6}); err != nil { 104 | return nil, err 105 | } 106 | } else { 107 | ifaces, err := net.Interfaces() 108 | if err != nil { 109 | return nil, err 110 | } 111 | errCount1, errCount2 := 0, 0 112 | for _, iface := range ifaces { 113 | if err := p1.JoinGroup(&iface, &net.UDPAddr{IP: mdnsGroupIPv4}); err != nil { 114 | errCount1++ 115 | } 116 | if err := p2.JoinGroup(&iface, &net.UDPAddr{IP: mdnsGroupIPv6}); err != nil { 117 | errCount2++ 118 | } 119 | } 120 | if len(ifaces) == errCount1 && len(ifaces) == errCount2 { 121 | return nil, fmt.Errorf("Failed to join multicast group on all interfaces!") 122 | } 123 | } 124 | 125 | s := &Server{ 126 | config: config, 127 | ipv4List: ipv4List, 128 | ipv6List: ipv6List, 129 | shutdownCh: make(chan struct{}), 130 | } 131 | 132 | go s.recv(s.ipv4List) 133 | go s.recv(s.ipv6List) 134 | 135 | s.wg.Add(1) 136 | go s.probe() 137 | 138 | return s, nil 139 | } 140 | 141 | // Shutdown is used to shutdown the listener 142 | func (s *Server) Shutdown() error { 143 | s.shutdownLock.Lock() 144 | defer s.shutdownLock.Unlock() 145 | 146 | if s.shutdown { 147 | return nil 148 | } 149 | 150 | s.shutdown = true 151 | close(s.shutdownCh) 152 | s.unregister() 153 | 154 | if s.ipv4List != nil { 155 | s.ipv4List.Close() 156 | } 157 | if s.ipv6List != nil { 158 | s.ipv6List.Close() 159 | } 160 | 161 | s.wg.Wait() 162 | return nil 163 | } 164 | 165 | // recv is a long running routine to receive packets from an interface 166 | func (s *Server) recv(c *net.UDPConn) { 167 | if c == nil { 168 | return 169 | } 170 | buf := make([]byte, 65536) 171 | for { 172 | s.shutdownLock.Lock() 173 | if s.shutdown { 174 | s.shutdownLock.Unlock() 175 | return 176 | } 177 | s.shutdownLock.Unlock() 178 | n, from, err := c.ReadFrom(buf) 179 | if err != nil { 180 | continue 181 | } 182 | if err := s.parsePacket(buf[:n], from); err != nil { 183 | log.Printf("[ERR] mdns: Failed to handle query: %v", err) 184 | } 185 | } 186 | } 187 | 188 | // parsePacket is used to parse an incoming packet 189 | func (s *Server) parsePacket(packet []byte, from net.Addr) error { 190 | var msg dns.Msg 191 | if err := msg.Unpack(packet); err != nil { 192 | log.Printf("[ERR] mdns: Failed to unpack packet: %v", err) 193 | return err 194 | } 195 | // TODO: This is a bit of a hack 196 | // We decided to ignore some mDNS answers for the time being 197 | // See: https://tools.ietf.org/html/rfc6762#section-7.2 198 | msg.Truncated = false 199 | return s.handleQuery(&msg, from) 200 | } 201 | 202 | // handleQuery is used to handle an incoming query 203 | func (s *Server) handleQuery(query *dns.Msg, from net.Addr) error { 204 | if query.Opcode != dns.OpcodeQuery { 205 | // "In both multicast query and multicast response messages, the OPCODE MUST 206 | // be zero on transmission (only standard queries are currently supported 207 | // over multicast). Multicast DNS messages received with an OPCODE other 208 | // than zero MUST be silently ignored." Note: OpcodeQuery == 0 209 | return fmt.Errorf("mdns: received query with non-zero Opcode %v: %v", query.Opcode, *query) 210 | } 211 | if query.Rcode != 0 { 212 | // "In both multicast query and multicast response messages, the Response 213 | // Code MUST be zero on transmission. Multicast DNS messages received with 214 | // non-zero Response Codes MUST be silently ignored." 215 | return fmt.Errorf("mdns: received query with non-zero Rcode %v: %v", query.Rcode, *query) 216 | } 217 | 218 | // TODO(reddaly): Handle "TC (Truncated) Bit": 219 | // In query messages, if the TC bit is set, it means that additional 220 | // Known-Answer records may be following shortly. A responder SHOULD 221 | // record this fact, and wait for those additional Known-Answer records, 222 | // before deciding whether to respond. If the TC bit is clear, it means 223 | // that the querying host has no additional Known Answers. 224 | if query.Truncated { 225 | return fmt.Errorf("[ERR] mdns: support for DNS requests with high truncated bit not implemented: %v", *query) 226 | } 227 | 228 | var unicastAnswer, multicastAnswer []dns.RR 229 | 230 | // Handle each question 231 | for _, q := range query.Question { 232 | mrecs, urecs := s.handleQuestion(q) 233 | multicastAnswer = append(multicastAnswer, mrecs...) 234 | unicastAnswer = append(unicastAnswer, urecs...) 235 | } 236 | 237 | // See section 18 of RFC 6762 for rules about DNS headers. 238 | resp := func(unicast bool) *dns.Msg { 239 | // 18.1: ID (Query Identifier) 240 | // 0 for multicast response, query.Id for unicast response 241 | id := uint16(0) 242 | if unicast { 243 | id = query.Id 244 | } 245 | 246 | var answer []dns.RR 247 | if unicast { 248 | answer = unicastAnswer 249 | } else { 250 | answer = multicastAnswer 251 | } 252 | if len(answer) == 0 { 253 | return nil 254 | } 255 | 256 | return &dns.Msg{ 257 | MsgHdr: dns.MsgHdr{ 258 | Id: id, 259 | 260 | // 18.2: QR (Query/Response) Bit - must be set to 1 in response. 261 | Response: true, 262 | 263 | // 18.3: OPCODE - must be zero in response (OpcodeQuery == 0) 264 | Opcode: dns.OpcodeQuery, 265 | 266 | // 18.4: AA (Authoritative Answer) Bit - must be set to 1 267 | Authoritative: true, 268 | 269 | // The following fields must all be set to 0: 270 | // 18.5: TC (TRUNCATED) Bit 271 | // 18.6: RD (Recursion Desired) Bit 272 | // 18.7: RA (Recursion Available) Bit 273 | // 18.8: Z (Zero) Bit 274 | // 18.9: AD (Authentic Data) Bit 275 | // 18.10: CD (Checking Disabled) Bit 276 | // 18.11: RCODE (Response Code) 277 | }, 278 | // 18.12 pertains to questions (handled by handleQuestion) 279 | // 18.13 pertains to resource records (handled by handleQuestion) 280 | 281 | // 18.14: Name Compression - responses should be compressed (though see 282 | // caveats in the RFC), so set the Compress bit (part of the dns library 283 | // API, not part of the DNS packet) to true. 284 | Compress: true, 285 | 286 | Answer: answer, 287 | } 288 | } 289 | 290 | if mresp := resp(false); mresp != nil { 291 | if err := s.sendResponse(mresp, from); err != nil { 292 | return fmt.Errorf("mdns: error sending multicast response: %v", err) 293 | } 294 | } 295 | if uresp := resp(true); uresp != nil { 296 | if err := s.sendResponse(uresp, from); err != nil { 297 | return fmt.Errorf("mdns: error sending unicast response: %v", err) 298 | } 299 | } 300 | return nil 301 | } 302 | 303 | // handleQuestion is used to handle an incoming question 304 | // 305 | // The response to a question may be transmitted over multicast, unicast, or 306 | // both. The return values are DNS records for each transmission type. 307 | func (s *Server) handleQuestion(q dns.Question) (multicastRecs, unicastRecs []dns.RR) { 308 | records := s.config.Zone.Records(q) 309 | 310 | if len(records) == 0 { 311 | return nil, nil 312 | } 313 | 314 | // Handle unicast and multicast responses. 315 | // TODO(reddaly): The decision about sending over unicast vs. multicast is not 316 | // yet fully compliant with RFC 6762. For example, the unicast bit should be 317 | // ignored if the records in question are close to TTL expiration. For now, 318 | // we just use the unicast bit to make the decision, as per the spec: 319 | // RFC 6762, section 18.12. Repurposing of Top Bit of qclass in Question 320 | // Section 321 | // 322 | // In the Question Section of a Multicast DNS query, the top bit of the 323 | // qclass field is used to indicate that unicast responses are preferred 324 | // for this particular question. (See Section 5.4.) 325 | if q.Qclass&(1<<15) != 0 { 326 | return nil, records 327 | } 328 | return records, nil 329 | } 330 | 331 | func (s *Server) probe() { 332 | defer s.wg.Done() 333 | 334 | sd, ok := s.config.Zone.(*MDNSService) 335 | if !ok { 336 | return 337 | } 338 | 339 | name := fmt.Sprintf("%s.%s.%s.", sd.Instance, trimDot(sd.Service), trimDot(sd.Domain)) 340 | 341 | q := new(dns.Msg) 342 | q.SetQuestion(name, dns.TypePTR) 343 | q.RecursionDesired = false 344 | 345 | srv := &dns.SRV{ 346 | Hdr: dns.RR_Header{ 347 | Name: name, 348 | Rrtype: dns.TypeSRV, 349 | Class: dns.ClassINET, 350 | Ttl: defaultTTL, 351 | }, 352 | Priority: 0, 353 | Weight: 0, 354 | Port: uint16(sd.Port), 355 | Target: sd.HostName, 356 | } 357 | txt := &dns.TXT{ 358 | Hdr: dns.RR_Header{ 359 | Name: name, 360 | Rrtype: dns.TypeTXT, 361 | Class: dns.ClassINET, 362 | Ttl: defaultTTL, 363 | }, 364 | Txt: sd.TXT, 365 | } 366 | q.Ns = []dns.RR{srv, txt} 367 | 368 | randomizer := rand.New(rand.NewSource(time.Now().UnixNano())) 369 | 370 | for i := 0; i < 3; i++ { 371 | if err := s.SendMulticast(q); err != nil { 372 | log.Println("[ERR] mdns: failed to send probe:", err.Error()) 373 | } 374 | time.Sleep(time.Duration(randomizer.Intn(250)) * time.Millisecond) 375 | } 376 | 377 | resp := new(dns.Msg) 378 | resp.MsgHdr.Response = true 379 | 380 | // set for query 381 | q.SetQuestion(name, dns.TypeANY) 382 | 383 | resp.Answer = append(resp.Answer, s.config.Zone.Records(q.Question[0])...) 384 | 385 | // reset 386 | q.SetQuestion(name, dns.TypePTR) 387 | 388 | // From RFC6762 389 | // The Multicast DNS responder MUST send at least two unsolicited 390 | // responses, one second apart. To provide increased robustness against 391 | // packet loss, a responder MAY send up to eight unsolicited responses, 392 | // provided that the interval between unsolicited responses increases by 393 | // at least a factor of two with every response sent. 394 | timeout := 1 * time.Second 395 | timer := time.NewTimer(timeout) 396 | for i := 0; i < 3; i++ { 397 | if err := s.SendMulticast(resp); err != nil { 398 | log.Println("[ERR] mdns: failed to send announcement:", err.Error()) 399 | } 400 | select { 401 | case <-timer.C: 402 | timeout *= 2 403 | timer.Reset(timeout) 404 | case <-s.shutdownCh: 405 | timer.Stop() 406 | return 407 | } 408 | } 409 | } 410 | 411 | // multicastResponse us used to send a multicast response packet 412 | func (s *Server) SendMulticast(msg *dns.Msg) error { 413 | buf, err := msg.Pack() 414 | if err != nil { 415 | return err 416 | } 417 | if s.ipv4List != nil { 418 | s.ipv4List.WriteToUDP(buf, ipv4Addr) 419 | } 420 | if s.ipv6List != nil { 421 | s.ipv6List.WriteToUDP(buf, ipv6Addr) 422 | } 423 | return nil 424 | } 425 | 426 | // sendResponse is used to send a response packet 427 | func (s *Server) sendResponse(resp *dns.Msg, from net.Addr) error { 428 | // TODO(reddaly): Respect the unicast argument, and allow sending responses 429 | // over multicast. 430 | buf, err := resp.Pack() 431 | if err != nil { 432 | return err 433 | } 434 | 435 | // Determine the socket to send from 436 | addr := from.(*net.UDPAddr) 437 | if addr.IP.To4() != nil { 438 | _, err = s.ipv4List.WriteToUDP(buf, addr) 439 | return err 440 | } else { 441 | _, err = s.ipv6List.WriteToUDP(buf, addr) 442 | return err 443 | } 444 | } 445 | 446 | func (s *Server) unregister() error { 447 | sd, ok := s.config.Zone.(*MDNSService) 448 | if !ok { 449 | return nil 450 | } 451 | 452 | sd.TTL = 0 453 | name := fmt.Sprintf("%s.%s.%s.", sd.Instance, trimDot(sd.Service), trimDot(sd.Domain)) 454 | 455 | q := new(dns.Msg) 456 | q.SetQuestion(name, dns.TypeANY) 457 | 458 | resp := new(dns.Msg) 459 | resp.MsgHdr.Response = true 460 | resp.Answer = append(resp.Answer, s.config.Zone.Records(q.Question[0])...) 461 | 462 | return s.SendMulticast(resp) 463 | } 464 | -------------------------------------------------------------------------------- /mdns/server_test.go: -------------------------------------------------------------------------------- 1 | package mdns 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | func TestServer_StartStop(t *testing.T) { 9 | s := makeService(t) 10 | serv, err := NewServer(&Config{Zone: s}) 11 | if err != nil { 12 | t.Fatalf("err: %v", err) 13 | } 14 | defer serv.Shutdown() 15 | } 16 | 17 | func TestServer_Lookup(t *testing.T) { 18 | serv, err := NewServer(&Config{Zone: makeServiceWithServiceName(t, "_foobar._tcp")}) 19 | if err != nil { 20 | t.Fatalf("err: %v", err) 21 | } 22 | defer serv.Shutdown() 23 | 24 | entries := make(chan *ServiceEntry, 1) 25 | found := false 26 | doneCh := make(chan struct{}) 27 | go func() { 28 | select { 29 | case e := <-entries: 30 | if e.Name != "hostname._foobar._tcp.local." { 31 | t.Fatalf("bad: %v", e) 32 | } 33 | if e.Port != 80 { 34 | t.Fatalf("bad: %v", e) 35 | } 36 | if e.Info != "Local web server" { 37 | t.Fatalf("bad: %v", e) 38 | } 39 | found = true 40 | 41 | case <-time.After(80 * time.Millisecond): 42 | t.Fatalf("timeout") 43 | } 44 | close(doneCh) 45 | }() 46 | 47 | params := &QueryParam{ 48 | Service: "_foobar._tcp", 49 | Domain: "local", 50 | Timeout: 50 * time.Millisecond, 51 | Entries: entries, 52 | } 53 | err = Query(params) 54 | if err != nil { 55 | t.Fatalf("err: %v", err) 56 | } 57 | <-doneCh 58 | if !found { 59 | t.Fatalf("record not found") 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /mdns/zone.go: -------------------------------------------------------------------------------- 1 | package mdns 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "os" 7 | "strings" 8 | 9 | "github.com/miekg/dns" 10 | ) 11 | 12 | const ( 13 | // defaultTTL is the default TTL value in returned DNS records in seconds. 14 | defaultTTL = 120 15 | ) 16 | 17 | // Zone is the interface used to integrate with the server and 18 | // to serve records dynamically 19 | type Zone interface { 20 | // Records returns DNS records in response to a DNS question. 21 | Records(q dns.Question) []dns.RR 22 | } 23 | 24 | // MDNSService is used to export a named service by implementing a Zone 25 | type MDNSService struct { 26 | Instance string // Instance name (e.g. "hostService name") 27 | Service string // Service name (e.g. "_http._tcp.") 28 | Domain string // If blank, assumes "local" 29 | HostName string // Host machine DNS name (e.g. "mymachine.net.") 30 | Port int // Service Port 31 | IPs []net.IP // IP addresses for the service's host 32 | TXT []string // Service TXT records 33 | TTL uint32 34 | serviceAddr string // Fully qualified service address 35 | instanceAddr string // Fully qualified instance address 36 | enumAddr string // _services._dns-sd._udp. 37 | } 38 | 39 | // validateFQDN returns an error if the passed string is not a fully qualified 40 | // hdomain name (more specifically, a hostname). 41 | func validateFQDN(s string) error { 42 | if len(s) == 0 { 43 | return fmt.Errorf("FQDN must not be blank") 44 | } 45 | if s[len(s)-1] != '.' { 46 | return fmt.Errorf("FQDN must end in period: %s", s) 47 | } 48 | // TODO(reddaly): Perform full validation. 49 | 50 | return nil 51 | } 52 | 53 | // NewMDNSService returns a new instance of MDNSService. 54 | // 55 | // If domain, hostName, or ips is set to the zero value, then a default value 56 | // will be inferred from the operating system. 57 | // 58 | // TODO(reddaly): This interface may need to change to account for "unique 59 | // record" conflict rules of the mDNS protocol. Upon startup, the server should 60 | // check to ensure that the instance name does not conflict with other instance 61 | // names, and, if required, select a new name. There may also be conflicting 62 | // hostName A/AAAA records. 63 | func NewMDNSService(instance, service, domain, hostName string, port int, ips []net.IP, txt []string) (*MDNSService, error) { 64 | // Sanity check inputs 65 | if instance == "" { 66 | return nil, fmt.Errorf("missing service instance name") 67 | } 68 | if service == "" { 69 | return nil, fmt.Errorf("missing service name") 70 | } 71 | if port == 0 { 72 | return nil, fmt.Errorf("missing service port") 73 | } 74 | 75 | // Set default domain 76 | if domain == "" { 77 | domain = "local." 78 | } 79 | if err := validateFQDN(domain); err != nil { 80 | return nil, fmt.Errorf("domain %q is not a fully-qualified domain name: %v", domain, err) 81 | } 82 | 83 | // Get host information if no host is specified. 84 | if hostName == "" { 85 | var err error 86 | hostName, err = os.Hostname() 87 | if err != nil { 88 | return nil, fmt.Errorf("could not determine host: %v", err) 89 | } 90 | hostName = fmt.Sprintf("%s.", hostName) 91 | } 92 | if err := validateFQDN(hostName); err != nil { 93 | return nil, fmt.Errorf("hostName %q is not a fully-qualified domain name: %v", hostName, err) 94 | } 95 | 96 | if len(ips) == 0 { 97 | var err error 98 | ips, err = net.LookupIP(trimDot(hostName)) 99 | if err != nil { 100 | // Try appending the host domain suffix and lookup again 101 | // (required for Linux-based hosts) 102 | tmpHostName := fmt.Sprintf("%s%s", hostName, domain) 103 | 104 | ips, err = net.LookupIP(trimDot(tmpHostName)) 105 | 106 | if err != nil { 107 | return nil, fmt.Errorf("could not determine host IP addresses for %s", hostName) 108 | } 109 | } 110 | } 111 | for _, ip := range ips { 112 | if ip.To4() == nil && ip.To16() == nil { 113 | return nil, fmt.Errorf("invalid IP address in IPs list: %v", ip) 114 | } 115 | } 116 | 117 | return &MDNSService{ 118 | Instance: instance, 119 | Service: service, 120 | Domain: domain, 121 | HostName: hostName, 122 | Port: port, 123 | IPs: ips, 124 | TXT: txt, 125 | TTL: defaultTTL, 126 | serviceAddr: fmt.Sprintf("%s.%s.", trimDot(service), trimDot(domain)), 127 | instanceAddr: fmt.Sprintf("%s.%s.%s.", instance, trimDot(service), trimDot(domain)), 128 | enumAddr: fmt.Sprintf("_services._dns-sd._udp.%s.", trimDot(domain)), 129 | }, nil 130 | } 131 | 132 | // trimDot is used to trim the dots from the start or end of a string 133 | func trimDot(s string) string { 134 | return strings.Trim(s, ".") 135 | } 136 | 137 | // Records returns DNS records in response to a DNS question. 138 | func (m *MDNSService) Records(q dns.Question) []dns.RR { 139 | switch q.Name { 140 | case m.enumAddr: 141 | return m.serviceEnum(q) 142 | case m.serviceAddr: 143 | return m.serviceRecords(q) 144 | case m.instanceAddr: 145 | return m.instanceRecords(q) 146 | case m.HostName: 147 | if q.Qtype == dns.TypeA || q.Qtype == dns.TypeAAAA { 148 | return m.instanceRecords(q) 149 | } 150 | fallthrough 151 | default: 152 | return nil 153 | } 154 | } 155 | 156 | func (m *MDNSService) serviceEnum(q dns.Question) []dns.RR { 157 | switch q.Qtype { 158 | case dns.TypeANY: 159 | fallthrough 160 | case dns.TypePTR: 161 | rr := &dns.PTR{ 162 | Hdr: dns.RR_Header{ 163 | Name: q.Name, 164 | Rrtype: dns.TypePTR, 165 | Class: dns.ClassINET, 166 | Ttl: m.TTL, 167 | }, 168 | Ptr: m.serviceAddr, 169 | } 170 | return []dns.RR{rr} 171 | default: 172 | return nil 173 | } 174 | } 175 | 176 | // serviceRecords is called when the query matches the service name 177 | func (m *MDNSService) serviceRecords(q dns.Question) []dns.RR { 178 | switch q.Qtype { 179 | case dns.TypeANY: 180 | fallthrough 181 | case dns.TypePTR: 182 | // Build a PTR response for the service 183 | rr := &dns.PTR{ 184 | Hdr: dns.RR_Header{ 185 | Name: q.Name, 186 | Rrtype: dns.TypePTR, 187 | Class: dns.ClassINET, 188 | Ttl: m.TTL, 189 | }, 190 | Ptr: m.instanceAddr, 191 | } 192 | servRec := []dns.RR{rr} 193 | 194 | // Get the instance records 195 | instRecs := m.instanceRecords(dns.Question{ 196 | Name: m.instanceAddr, 197 | Qtype: dns.TypeANY, 198 | }) 199 | 200 | // Return the service record with the instance records 201 | return append(servRec, instRecs...) 202 | default: 203 | return nil 204 | } 205 | } 206 | 207 | // serviceRecords is called when the query matches the instance name 208 | func (m *MDNSService) instanceRecords(q dns.Question) []dns.RR { 209 | switch q.Qtype { 210 | case dns.TypeANY: 211 | // Get the SRV, which includes A and AAAA 212 | recs := m.instanceRecords(dns.Question{ 213 | Name: m.instanceAddr, 214 | Qtype: dns.TypeSRV, 215 | }) 216 | 217 | // Add the TXT record 218 | recs = append(recs, m.instanceRecords(dns.Question{ 219 | Name: m.instanceAddr, 220 | Qtype: dns.TypeTXT, 221 | })...) 222 | return recs 223 | 224 | case dns.TypeA: 225 | var rr []dns.RR 226 | for _, ip := range m.IPs { 227 | if ip4 := ip.To4(); ip4 != nil { 228 | rr = append(rr, &dns.A{ 229 | Hdr: dns.RR_Header{ 230 | Name: m.HostName, 231 | Rrtype: dns.TypeA, 232 | Class: dns.ClassINET, 233 | Ttl: m.TTL, 234 | }, 235 | A: ip4, 236 | }) 237 | } 238 | } 239 | return rr 240 | 241 | case dns.TypeAAAA: 242 | var rr []dns.RR 243 | for _, ip := range m.IPs { 244 | if ip.To4() != nil { 245 | // TODO(reddaly): IPv4 addresses could be encoded in IPv6 format and 246 | // putinto AAAA records, but the current logic puts ipv4-encodable 247 | // addresses into the A records exclusively. Perhaps this should be 248 | // configurable? 249 | continue 250 | } 251 | 252 | if ip16 := ip.To16(); ip16 != nil { 253 | rr = append(rr, &dns.AAAA{ 254 | Hdr: dns.RR_Header{ 255 | Name: m.HostName, 256 | Rrtype: dns.TypeAAAA, 257 | Class: dns.ClassINET, 258 | Ttl: m.TTL, 259 | }, 260 | AAAA: ip16, 261 | }) 262 | } 263 | } 264 | return rr 265 | 266 | case dns.TypeSRV: 267 | // Create the SRV Record 268 | srv := &dns.SRV{ 269 | Hdr: dns.RR_Header{ 270 | Name: q.Name, 271 | Rrtype: dns.TypeSRV, 272 | Class: dns.ClassINET, 273 | Ttl: m.TTL, 274 | }, 275 | Priority: 10, 276 | Weight: 1, 277 | Port: uint16(m.Port), 278 | Target: m.HostName, 279 | } 280 | recs := []dns.RR{srv} 281 | 282 | // Add the A record 283 | recs = append(recs, m.instanceRecords(dns.Question{ 284 | Name: m.instanceAddr, 285 | Qtype: dns.TypeA, 286 | })...) 287 | 288 | // Add the AAAA record 289 | recs = append(recs, m.instanceRecords(dns.Question{ 290 | Name: m.instanceAddr, 291 | Qtype: dns.TypeAAAA, 292 | })...) 293 | return recs 294 | 295 | case dns.TypeTXT: 296 | txt := &dns.TXT{ 297 | Hdr: dns.RR_Header{ 298 | Name: q.Name, 299 | Rrtype: dns.TypeTXT, 300 | Class: dns.ClassINET, 301 | Ttl: m.TTL, 302 | }, 303 | Txt: m.TXT, 304 | } 305 | return []dns.RR{txt} 306 | } 307 | return nil 308 | } 309 | -------------------------------------------------------------------------------- /mdns/zone_test.go: -------------------------------------------------------------------------------- 1 | package mdns 2 | 3 | import ( 4 | "bytes" 5 | "net" 6 | "reflect" 7 | "testing" 8 | 9 | "github.com/miekg/dns" 10 | ) 11 | 12 | func makeService(t *testing.T) *MDNSService { 13 | return makeServiceWithServiceName(t, "_http._tcp") 14 | } 15 | 16 | func makeServiceWithServiceName(t *testing.T, service string) *MDNSService { 17 | m, err := NewMDNSService( 18 | "hostname", 19 | service, 20 | "local.", 21 | "testhost.", 22 | 80, // port 23 | []net.IP{net.IP([]byte{192, 168, 0, 42}), net.ParseIP("2620:0:1000:1900:b0c2:d0b2:c411:18bc")}, 24 | []string{"Local web server"}) // TXT 25 | 26 | if err != nil { 27 | t.Fatalf("err: %v", err) 28 | } 29 | 30 | return m 31 | } 32 | 33 | func TestNewMDNSService_BadParams(t *testing.T) { 34 | for _, test := range []struct { 35 | testName string 36 | hostName string 37 | domain string 38 | }{ 39 | { 40 | "NewMDNSService should fail when passed hostName that is not a legal fully-qualified domain name", 41 | "hostname", // not legal FQDN - should be "hostname." or "hostname.local.", etc. 42 | "local.", // legal 43 | }, 44 | { 45 | "NewMDNSService should fail when passed domain that is not a legal fully-qualified domain name", 46 | "hostname.", // legal 47 | "local", // should be "local." 48 | }, 49 | } { 50 | _, err := NewMDNSService( 51 | "instance name", 52 | "_http._tcp", 53 | test.domain, 54 | test.hostName, 55 | 80, // port 56 | []net.IP{net.IP([]byte{192, 168, 0, 42})}, 57 | []string{"Local web server"}) // TXT 58 | if err == nil { 59 | t.Fatalf("%s: error expected, but got none", test.testName) 60 | } 61 | } 62 | } 63 | 64 | func TestMDNSService_BadAddr(t *testing.T) { 65 | s := makeService(t) 66 | q := dns.Question{ 67 | Name: "random", 68 | Qtype: dns.TypeANY, 69 | } 70 | recs := s.Records(q) 71 | if len(recs) != 0 { 72 | t.Fatalf("bad: %v", recs) 73 | } 74 | } 75 | 76 | func TestMDNSService_ServiceAddr(t *testing.T) { 77 | s := makeService(t) 78 | q := dns.Question{ 79 | Name: "_http._tcp.local.", 80 | Qtype: dns.TypeANY, 81 | } 82 | recs := s.Records(q) 83 | if got, want := len(recs), 5; got != want { 84 | t.Fatalf("got %d records, want %d: %v", got, want, recs) 85 | } 86 | 87 | if ptr, ok := recs[0].(*dns.PTR); !ok { 88 | t.Errorf("recs[0] should be PTR record, got: %v, all records: %v", recs[0], recs) 89 | } else if got, want := ptr.Ptr, "hostname._http._tcp.local."; got != want { 90 | t.Fatalf("bad PTR record %v: got %v, want %v", ptr, got, want) 91 | } 92 | 93 | if _, ok := recs[1].(*dns.SRV); !ok { 94 | t.Errorf("recs[1] should be SRV record, got: %v, all reccords: %v", recs[1], recs) 95 | } 96 | if _, ok := recs[2].(*dns.A); !ok { 97 | t.Errorf("recs[2] should be A record, got: %v, all records: %v", recs[2], recs) 98 | } 99 | if _, ok := recs[3].(*dns.AAAA); !ok { 100 | t.Errorf("recs[3] should be AAAA record, got: %v, all records: %v", recs[3], recs) 101 | } 102 | if _, ok := recs[4].(*dns.TXT); !ok { 103 | t.Errorf("recs[4] should be TXT record, got: %v, all records: %v", recs[4], recs) 104 | } 105 | 106 | q.Qtype = dns.TypePTR 107 | if recs2 := s.Records(q); !reflect.DeepEqual(recs, recs2) { 108 | t.Fatalf("PTR question should return same result as ANY question: ANY => %v, PTR => %v", recs, recs2) 109 | } 110 | } 111 | 112 | func TestMDNSService_InstanceAddr_ANY(t *testing.T) { 113 | s := makeService(t) 114 | q := dns.Question{ 115 | Name: "hostname._http._tcp.local.", 116 | Qtype: dns.TypeANY, 117 | } 118 | recs := s.Records(q) 119 | if len(recs) != 4 { 120 | t.Fatalf("bad: %v", recs) 121 | } 122 | if _, ok := recs[0].(*dns.SRV); !ok { 123 | t.Fatalf("bad: %v", recs[0]) 124 | } 125 | if _, ok := recs[1].(*dns.A); !ok { 126 | t.Fatalf("bad: %v", recs[1]) 127 | } 128 | if _, ok := recs[2].(*dns.AAAA); !ok { 129 | t.Fatalf("bad: %v", recs[2]) 130 | } 131 | if _, ok := recs[3].(*dns.TXT); !ok { 132 | t.Fatalf("bad: %v", recs[3]) 133 | } 134 | } 135 | 136 | func TestMDNSService_InstanceAddr_SRV(t *testing.T) { 137 | s := makeService(t) 138 | q := dns.Question{ 139 | Name: "hostname._http._tcp.local.", 140 | Qtype: dns.TypeSRV, 141 | } 142 | recs := s.Records(q) 143 | if len(recs) != 3 { 144 | t.Fatalf("bad: %v", recs) 145 | } 146 | srv, ok := recs[0].(*dns.SRV) 147 | if !ok { 148 | t.Fatalf("bad: %v", recs[0]) 149 | } 150 | if _, ok := recs[1].(*dns.A); !ok { 151 | t.Fatalf("bad: %v", recs[1]) 152 | } 153 | if _, ok := recs[2].(*dns.AAAA); !ok { 154 | t.Fatalf("bad: %v", recs[2]) 155 | } 156 | 157 | if srv.Port != uint16(s.Port) { 158 | t.Fatalf("bad: %v", recs[0]) 159 | } 160 | } 161 | 162 | func TestMDNSService_InstanceAddr_A(t *testing.T) { 163 | s := makeService(t) 164 | q := dns.Question{ 165 | Name: "hostname._http._tcp.local.", 166 | Qtype: dns.TypeA, 167 | } 168 | recs := s.Records(q) 169 | if len(recs) != 1 { 170 | t.Fatalf("bad: %v", recs) 171 | } 172 | a, ok := recs[0].(*dns.A) 173 | if !ok { 174 | t.Fatalf("bad: %v", recs[0]) 175 | } 176 | if !bytes.Equal(a.A, []byte{192, 168, 0, 42}) { 177 | t.Fatalf("bad: %v", recs[0]) 178 | } 179 | } 180 | 181 | func TestMDNSService_InstanceAddr_AAAA(t *testing.T) { 182 | s := makeService(t) 183 | q := dns.Question{ 184 | Name: "hostname._http._tcp.local.", 185 | Qtype: dns.TypeAAAA, 186 | } 187 | recs := s.Records(q) 188 | if len(recs) != 1 { 189 | t.Fatalf("bad: %v", recs) 190 | } 191 | a4, ok := recs[0].(*dns.AAAA) 192 | if !ok { 193 | t.Fatalf("bad: %v", recs[0]) 194 | } 195 | ip6 := net.ParseIP("2620:0:1000:1900:b0c2:d0b2:c411:18bc") 196 | if got := len(ip6); got != net.IPv6len { 197 | t.Fatalf("test IP failed to parse (len = %d, want %d)", got, net.IPv6len) 198 | } 199 | if !bytes.Equal(a4.AAAA, ip6) { 200 | t.Fatalf("bad: %v", recs[0]) 201 | } 202 | } 203 | 204 | func TestMDNSService_InstanceAddr_TXT(t *testing.T) { 205 | s := makeService(t) 206 | q := dns.Question{ 207 | Name: "hostname._http._tcp.local.", 208 | Qtype: dns.TypeTXT, 209 | } 210 | recs := s.Records(q) 211 | if len(recs) != 1 { 212 | t.Fatalf("bad: %v", recs) 213 | } 214 | txt, ok := recs[0].(*dns.TXT) 215 | if !ok { 216 | t.Fatalf("bad: %v", recs[0]) 217 | } 218 | if got, want := txt.Txt, s.TXT; !reflect.DeepEqual(got, want) { 219 | t.Fatalf("TXT record mismatch for %v: got %v, want %v", recs[0], got, want) 220 | } 221 | } 222 | 223 | func TestMDNSService_HostNameQuery(t *testing.T) { 224 | s := makeService(t) 225 | for _, test := range []struct { 226 | q dns.Question 227 | want []dns.RR 228 | }{ 229 | { 230 | dns.Question{Name: "testhost.", Qtype: dns.TypeA}, 231 | []dns.RR{&dns.A{ 232 | Hdr: dns.RR_Header{ 233 | Name: "testhost.", 234 | Rrtype: dns.TypeA, 235 | Class: dns.ClassINET, 236 | Ttl: 120, 237 | }, 238 | A: net.IP([]byte{192, 168, 0, 42}), 239 | }}, 240 | }, 241 | { 242 | dns.Question{Name: "testhost.", Qtype: dns.TypeAAAA}, 243 | []dns.RR{&dns.AAAA{ 244 | Hdr: dns.RR_Header{ 245 | Name: "testhost.", 246 | Rrtype: dns.TypeAAAA, 247 | Class: dns.ClassINET, 248 | Ttl: 120, 249 | }, 250 | AAAA: net.ParseIP("2620:0:1000:1900:b0c2:d0b2:c411:18bc"), 251 | }}, 252 | }, 253 | } { 254 | if got := s.Records(test.q); !reflect.DeepEqual(got, test.want) { 255 | t.Errorf("hostname query failed: s.Records(%v) = %v, want %v", test.q, got, test.want) 256 | } 257 | } 258 | } 259 | 260 | func TestMDNSService_serviceEnum_PTR(t *testing.T) { 261 | s := makeService(t) 262 | q := dns.Question{ 263 | Name: "_services._dns-sd._udp.local.", 264 | Qtype: dns.TypePTR, 265 | } 266 | recs := s.Records(q) 267 | if len(recs) != 1 { 268 | t.Fatalf("bad: %v", recs) 269 | } 270 | if ptr, ok := recs[0].(*dns.PTR); !ok { 271 | t.Errorf("recs[0] should be PTR record, got: %v, all records: %v", recs[0], recs) 272 | } else if got, want := ptr.Ptr, "_http._tcp.local."; got != want { 273 | t.Fatalf("bad PTR record %v: got %v, want %v", ptr, got, want) 274 | } 275 | } 276 | -------------------------------------------------------------------------------- /micro.go: -------------------------------------------------------------------------------- 1 | package micro 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "os/signal" 7 | "sync" 8 | "syscall" 9 | 10 | "github.com/xxjwxc/public/mylog" 11 | 12 | "github.com/gmsec/micro/client" 13 | "github.com/gmsec/micro/profile" 14 | "github.com/gmsec/micro/profile/pprof" 15 | "github.com/gmsec/micro/registry" 16 | "github.com/gmsec/micro/server" 17 | ) 18 | 19 | // Service is an interface that wraps the lower level libraries 20 | // within go-micro. Its a convenience method for building 21 | // and initialising services. 22 | type Service interface { 23 | // The service name 24 | Name() string 25 | // Init initialises options 26 | Init(...Option) 27 | // Options returns the current options 28 | Options() Options 29 | // Client is used to call services 30 | Client() client.Client 31 | // Server is for handling requests and events 32 | Server() server.Server 33 | // Run the service 34 | Run() error 35 | // The service implementation 36 | String() string 37 | // stop 38 | Stop() error 39 | 40 | // stop signal 41 | NotifyStop() 42 | } 43 | 44 | type service struct { 45 | opts Options 46 | once sync.Once 47 | 48 | // Before and After funcs 49 | BeforeStart []func() error 50 | BeforeStop []func() error 51 | AfterStart []func() error 52 | AfterStop []func() error 53 | 54 | cc chan os.Signal 55 | } 56 | 57 | // Option ... 58 | type Option func(*Options) 59 | 60 | // NewService newservice 61 | func NewService(opts ...Option) Service { 62 | return newService(opts...) 63 | } 64 | 65 | func newService(opts ...Option) Service { 66 | options := newOptions(opts...) 67 | 68 | // options.Client = &clientWrapper{ 69 | // options.Client, 70 | // metadata.Metadata{ 71 | // HeaderPrefix + "From-Service": options.Server.Options().Name, 72 | // }, 73 | // } 74 | 75 | s := &service{ 76 | opts: options, 77 | cc: make(chan os.Signal, 1), 78 | } 79 | s.Init() 80 | 81 | if !IsExist(s.Name()) { 82 | initService(s.Name(), s) 83 | } else { 84 | mylog.Info(fmt.Sprintf("service [%v] existed.", s.Name())) 85 | } 86 | return s 87 | } 88 | 89 | func (s *service) Client() client.Client { 90 | return s.opts.Client 91 | } 92 | 93 | func (s *service) Server() server.Server { 94 | return s.opts.Server 95 | } 96 | 97 | func (s *service) Name() string { 98 | return s.opts.Server.Options().Name 99 | } 100 | 101 | // Init initialises options. Additionally it calls cmd.Init 102 | // which parses command line flags. cmd.Init is only called 103 | // on first Init. 104 | func (s *service) Init(opts ...Option) { 105 | // process options 106 | for _, o := range opts { 107 | o(&s.opts) 108 | } 109 | 110 | if s.opts.Registry == nil { 111 | s.opts.Server = server.NewServer() 112 | s.opts.Registry = ®istry.Registry{ 113 | RegNaming: registry.NewDNSNamingRegistry(), 114 | } 115 | 116 | s.opts.Client = client.NewClient(client.WithRegistryNaming(s.opts.Registry.RegNaming)) 117 | 118 | s.opts.Server.Init(server.WithRegistryNaming(s.opts.Registry.RegNaming)) 119 | s.opts.Client.Init(client.WithRegistryNaming(s.opts.Registry.RegNaming)) 120 | } 121 | 122 | // init registry parms 123 | s.opts.Registry.RegNaming.Init(registry.WithAddrs(s.opts.Server.Options().Address), 124 | registry.WithNodeID(s.opts.Server.Options().ID), 125 | registry.WithServiceName(s.opts.Server.Options().Name), 126 | registry.WithTimeout(s.opts.Server.Options().RegisterTTL), 127 | ) 128 | 129 | s.once.Do(func() { 130 | // setup the plugins 131 | // TODO:once done 132 | }) 133 | } 134 | 135 | // Options all of plugs is options 136 | func (s *service) Options() Options { 137 | return s.opts 138 | } 139 | 140 | func (s *service) Run() error { 141 | // start the profiler 142 | // TODO: set as an option to the service, don't just use pprof 143 | if prof := os.Getenv("MICRO_DEBUG_PROFILE"); len(prof) > 0 { 144 | service := s.opts.Server.Options().Name 145 | version := s.opts.Server.Options().Version 146 | id := s.opts.Server.Options().ID 147 | profiler := pprof.NewProfile( 148 | profile.Name(service + "." + version + "." + id), 149 | ) 150 | if err := profiler.Start(); err != nil { 151 | return err 152 | } 153 | defer profiler.Stop() 154 | } 155 | 156 | if err := s.Start(); err != nil { 157 | return err 158 | } 159 | 160 | // s.cc = make(chan os.Signal, 1) 161 | signal.Notify(s.cc, syscall.SIGTERM, syscall.SIGINT, syscall.SIGQUIT) 162 | 163 | select { 164 | // wait on kill signal 165 | case <-s.cc: 166 | // wait on context cancel 167 | case <-s.opts.Context.Done(): 168 | } 169 | return s.Stop() 170 | } 171 | 172 | // String returns name of Server implementation 173 | func (s *service) String() string { 174 | return "gmsec" 175 | } 176 | 177 | // Start starts the default server 178 | func (s *service) Start() error { 179 | // for _, fn := range s.BeforeStart { 180 | // if err := fn(); err != nil { 181 | // return err 182 | // } 183 | // } 184 | 185 | ///start func 186 | s.opts.Server.Start() 187 | 188 | // register service 189 | 190 | /// ------- 191 | 192 | // for _, fn := range s.AfterStart { 193 | // if err := fn(); err != nil { 194 | // return err 195 | // } 196 | // } 197 | 198 | return nil 199 | } 200 | 201 | // NotifyStop 发送停止信号 202 | func (s *service) NotifyStop() { 203 | s.cc <- syscall.SIGINT 204 | } 205 | 206 | // Stop stops the default server 207 | func (s *service) Stop() error { 208 | 209 | var gerr error 210 | 211 | // for _, fn := range s.BeforeStop { 212 | // if err := fn(); err != nil { 213 | // gerr = err 214 | // } 215 | // } 216 | 217 | s.opts.Server.Stop() 218 | 219 | // for _, fn := range s.AfterStop { 220 | // if err := fn(); err != nil { 221 | // gerr = err 222 | // } 223 | // } 224 | 225 | return gerr 226 | } 227 | -------------------------------------------------------------------------------- /micro_test.go: -------------------------------------------------------------------------------- 1 | package micro 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "testing" 7 | "time" 8 | 9 | "github.com/gmsec/micro/registry" 10 | ) 11 | 12 | func TestMain(t *testing.T) { 13 | 14 | os.Setenv("MICRO_DEBUG_PROFILE", "true") 15 | // 初始化服务 16 | service := NewService( 17 | WithName("lp.srv.eg1"), 18 | WithRegisterTTL(time.Second*30), //指定服务注册时间 19 | WithRegisterInterval(time.Second*15), //让服务在指定时间内重新注册 20 | ) 21 | 22 | reg := registry.NewDNSNamingRegistry() 23 | service.Init(WithRegistryNaming(reg)) 24 | 25 | // server 26 | go func() { 27 | // RegisterHelloServer(service.Server(), &hello{}) 28 | // run server 29 | if err := service.Run(); err != nil { 30 | panic(err) 31 | } 32 | fmt.Println("stop service") 33 | }() 34 | 35 | // client 36 | SetClientServiceName("proto.Hello", "lp.srv.eg1") // set client group 37 | GetService("lp.srv.eg1") 38 | GetClient("proto.Hello") 39 | 40 | service.Options() 41 | service.Client() 42 | service.Server() 43 | service.String() 44 | service.Stop() 45 | 46 | // process, _ := os.FindProcess(os.Getpid()) 47 | // process.Signal(syscall.SIGTERM) 48 | time.Sleep(1 * time.Second) 49 | } 50 | -------------------------------------------------------------------------------- /naming/naming.go: -------------------------------------------------------------------------------- 1 | package naming 2 | 3 | // type RegisterOption func(*RegisterOptions) 4 | // type WatchOption func(*WatchOptions) 5 | 6 | // Operation defines the corresponding operations for a name resolution change. 7 | // 8 | // Deprecated: please use package resolver. 9 | type Operation uint8 10 | 11 | // Update defines a name resolution update. Notice that it is not valid having both 12 | // empty string Addr and nil Metadata in an Update. 13 | type Update struct { 14 | // Op indicates the operation of the update. 15 | Op Operation 16 | // Addr is the updated address. It is empty string if there is no address update. 17 | Addr string 18 | // Metadata is the updated metadata. It is nil if there is no metadata update. 19 | // Metadata is not required for a custom naming implementation. 20 | Metadata interface{} 21 | } 22 | 23 | const ( 24 | // Add indicates a new address is added. 25 | Add Operation = iota 26 | // Delete indicates an existing address is deleted. 27 | Delete 28 | ) 29 | 30 | // Watcher watches for the updates on the specified target. 31 | type Watcher interface { 32 | // Next blocks until an update or error happens. It may return one or more 33 | // updates. The first call should get the full set of the results. It should 34 | // return an error if and only if Watcher cannot recover. 35 | Next() ([]*Update, error) 36 | // Close closes the Watcher. 37 | Close() 38 | } 39 | -------------------------------------------------------------------------------- /option.go: -------------------------------------------------------------------------------- 1 | package micro 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "github.com/gmsec/micro/client" 8 | "github.com/gmsec/micro/registry" 9 | "github.com/gmsec/micro/server" 10 | "github.com/gmsec/micro/tracer" 11 | ) 12 | 13 | // Options ... 14 | type Options struct { 15 | // Broker broker.Broker 16 | // Cmd cmd.Cmd 17 | Client client.Client 18 | Server server.Server 19 | Registry *registry.Registry 20 | 21 | // Registry registry.Registry 22 | // Transport transport.Transport 23 | 24 | // // Before and After funcs 25 | // BeforeStart []func() error 26 | // BeforeStop []func() error 27 | // AfterStart []func() error 28 | // AfterStop []func() error 29 | 30 | // // Other options for implementations of the interface 31 | // // can be stored in a context 32 | Context context.Context 33 | } 34 | 35 | // newOptions default of option define 36 | func newOptions(opts ...Option) Options { 37 | // reg := registry.NewDNSNamingRegistry() 38 | opt := Options{ 39 | Client: client.NewClient(), 40 | Server: server.NewServer(), 41 | // Registry: ®istry.Registry{ 42 | // RegNaming: reg, 43 | // }, 44 | Context: context.Background(), 45 | } 46 | 47 | for _, o := range opts { 48 | o(&opt) 49 | } 50 | 51 | if opt.Registry == nil { // 默认注册 服务发现 52 | opt.Registry = ®istry.Registry{ 53 | RegNaming: registry.NewDNSNamingRegistry(), 54 | } 55 | opt.Client.Init(client.WithRegistryNaming(opt.Registry.RegNaming)) 56 | opt.Server.Init(server.WithRegistryNaming(opt.Registry.RegNaming)) 57 | } 58 | return opt 59 | } 60 | 61 | // WithName of the service . Specify service name (Group) 62 | func WithName(n string) Option { 63 | tracer.SetServiceName(n) 64 | return func(o *Options) { 65 | o.Server.Init(server.Name(n)) 66 | o.Client.Init(client.WithServiceName(n)) 67 | //o.Server.Name = n 68 | } 69 | } 70 | 71 | // WithRegisterTTL the service with a TTL 72 | func WithRegisterTTL(t time.Duration) Option { 73 | return func(o *Options) { 74 | o.Server.Init(server.RegisterTTL(t)) 75 | o.Client.Init(client.RegisterTTL(t)) 76 | } 77 | } 78 | 79 | // WithRegisterInterval the service with at interval. 80 | func WithRegisterInterval(t time.Duration) Option { 81 | return func(o *Options) { 82 | o.Server.Init(server.RegisterInterval(t)) 83 | } 84 | } 85 | 86 | // WithRegistryNaming Register for naming service discovery 87 | func WithRegistryNaming(reg registry.RegNaming) Option { 88 | return func(o *Options) { 89 | // o.Server = server.NewServer() 90 | // o.Client = client.NewClient() 91 | o.Registry = ®istry.Registry{ 92 | RegNaming: reg, 93 | } 94 | o.Server.Init(server.WithRegistryNaming(o.Registry.RegNaming)) 95 | o.Client.Init(client.WithRegistryNaming(o.Registry.RegNaming)) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /profile/http/http.go: -------------------------------------------------------------------------------- 1 | // Package http enables the http profiler 2 | package http 3 | 4 | import ( 5 | "context" 6 | "net/http" 7 | "net/http/pprof" 8 | "sync" 9 | 10 | "github.com/gmsec/micro/profile" 11 | ) 12 | 13 | type httpProfile struct { 14 | sync.Mutex 15 | running bool 16 | server *http.Server 17 | } 18 | 19 | var ( 20 | // DefaultAddress ... 21 | DefaultAddress = ":6060" 22 | ) 23 | 24 | // Start the profiler 25 | func (h *httpProfile) Start() error { 26 | h.Lock() 27 | defer h.Unlock() 28 | 29 | if h.running { 30 | return nil 31 | } 32 | 33 | go func() { 34 | if err := h.server.ListenAndServe(); err != nil { 35 | h.Lock() 36 | h.running = false 37 | h.Unlock() 38 | } 39 | }() 40 | 41 | h.running = true 42 | 43 | return nil 44 | } 45 | 46 | // Stop the profiler 47 | func (h *httpProfile) Stop() error { 48 | h.Lock() 49 | defer h.Unlock() 50 | 51 | if !h.running { 52 | return nil 53 | } 54 | 55 | h.running = false 56 | 57 | return h.server.Shutdown(context.TODO()) 58 | } 59 | 60 | func (h *httpProfile) String() string { 61 | return "http" 62 | } 63 | 64 | // NewProfile ... 65 | func NewProfile(opts ...profile.Option) profile.Profile { 66 | mux := http.NewServeMux() 67 | 68 | mux.HandleFunc("/debug/pprof/", pprof.Index) 69 | mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) 70 | mux.HandleFunc("/debug/pprof/profile", pprof.Profile) 71 | mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol) 72 | mux.HandleFunc("/debug/pprof/trace", pprof.Trace) 73 | 74 | return &httpProfile{ 75 | server: &http.Server{ 76 | Addr: DefaultAddress, 77 | Handler: mux, 78 | }, 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /profile/pprof/pprof.go: -------------------------------------------------------------------------------- 1 | // Package pprof provides a pprof profiler 2 | package pprof 3 | 4 | import ( 5 | "os" 6 | "path/filepath" 7 | "runtime" 8 | "runtime/pprof" 9 | "sync" 10 | "time" 11 | 12 | "github.com/gmsec/micro/profile" 13 | "github.com/xxjwxc/public/tools" 14 | ) 15 | 16 | type profiler struct { 17 | opts profile.Options 18 | 19 | sync.Mutex 20 | running bool 21 | exit chan bool 22 | 23 | // where the cpu profile is written 24 | cpuFile *os.File 25 | // where the mem profile is written 26 | memFile *os.File 27 | } 28 | 29 | func (p *profiler) writeHeap(f *os.File) { 30 | defer f.Close() 31 | 32 | t := time.NewTicker(time.Second * 30) 33 | defer t.Stop() 34 | 35 | for { 36 | select { 37 | case <-t.C: 38 | runtime.GC() 39 | pprof.WriteHeapProfile(f) 40 | case <-p.exit: 41 | return 42 | } 43 | } 44 | } 45 | 46 | func (p *profiler) Start() error { 47 | p.Lock() 48 | defer p.Unlock() 49 | 50 | if p.running { 51 | return nil 52 | } 53 | 54 | tmp := "/tmp" 55 | if runtime.GOOS == "windows" { // windows系统 56 | tmp = filepath.Join(tools.GetCurrentDirectory(), "/tmp") 57 | tools.BuildDir(tmp + "/") 58 | } 59 | // create exit channel 60 | p.exit = make(chan bool) 61 | 62 | cpuFile := filepath.Join(tmp, "cpu.pprof") 63 | memFile := filepath.Join(tmp, "mem.pprof") 64 | 65 | if len(p.opts.Name) > 0 { 66 | cpuFile = filepath.Join(tmp, p.opts.Name+".cpu.pprof") 67 | memFile = filepath.Join(tmp, p.opts.Name+".mem.pprof") 68 | } 69 | 70 | f1, err := os.Create(cpuFile) 71 | if err != nil { 72 | return err 73 | } 74 | 75 | f2, err := os.Create(memFile) 76 | if err != nil { 77 | return err 78 | } 79 | 80 | // start cpu profiling 81 | if err := pprof.StartCPUProfile(f1); err != nil { 82 | return err 83 | } 84 | 85 | // write the heap periodically 86 | go p.writeHeap(f2) 87 | 88 | // set cpu file 89 | p.cpuFile = f1 90 | // set mem file 91 | p.memFile = f2 92 | 93 | p.running = true 94 | 95 | return nil 96 | } 97 | 98 | func (p *profiler) Stop() error { 99 | p.Lock() 100 | defer p.Unlock() 101 | 102 | select { 103 | case <-p.exit: 104 | return nil 105 | default: 106 | close(p.exit) 107 | pprof.StopCPUProfile() 108 | p.cpuFile.Close() 109 | p.running = false 110 | p.cpuFile = nil 111 | p.memFile = nil 112 | return nil 113 | } 114 | } 115 | 116 | func (p *profiler) String() string { 117 | return "pprof" 118 | } 119 | 120 | // NewProfile new profile 121 | func NewProfile(opts ...profile.Option) profile.Profile { 122 | var options profile.Options 123 | for _, o := range opts { 124 | o(&options) 125 | } 126 | p := new(profiler) 127 | p.opts = options 128 | return p 129 | } 130 | -------------------------------------------------------------------------------- /profile/profile.go: -------------------------------------------------------------------------------- 1 | // Package profile is for profilers 2 | package profile 3 | 4 | // Profile list 5 | type Profile interface { 6 | // Start the profiler 7 | Start() error 8 | // Stop the profiler 9 | Stop() error 10 | // Name of the profiler 11 | String() string 12 | } 13 | 14 | var ( 15 | // DefaultProfile ... 16 | DefaultProfile Profile = new(noop) 17 | ) 18 | 19 | type noop struct{} 20 | 21 | func (p *noop) Start() error { 22 | return nil 23 | } 24 | 25 | func (p *noop) Stop() error { 26 | return nil 27 | } 28 | 29 | func (p *noop) String() string { 30 | return "noop" 31 | } 32 | 33 | // Options opts define 34 | type Options struct { 35 | // Name to use for the profile 36 | Name string 37 | } 38 | 39 | // Option opts list 40 | type Option func(o *Options) 41 | 42 | // Name of the profile 43 | func Name(n string) Option { 44 | return func(o *Options) { 45 | o.Name = n 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /registry/dns_naming_register.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "net" 7 | "strconv" 8 | "strings" 9 | "sync" 10 | "time" 11 | 12 | "github.com/gmsec/micro/naming" 13 | "github.com/xxjwxc/public/mylog" 14 | 15 | "github.com/gmsec/micro/mdns" 16 | "github.com/google/uuid" 17 | "github.com/xxjwxc/public/tools" 18 | ) 19 | 20 | var ( 21 | // use a .micro domain rather than .local 22 | mdnsDomain = "gmsec" 23 | ) 24 | 25 | // DNSNamingRegister dns default register 26 | type DNSNamingRegister struct { 27 | opts Options 28 | // the mdns domain 29 | domain string 30 | sync.Mutex 31 | node *mdns.Server 32 | 33 | // listener 34 | // listener chan *mdns.ServiceEntry 35 | } 36 | 37 | // NewDNSNamingRegistry returns a new default dns naming registry which is mdns 38 | func NewDNSNamingRegistry(opts ...Option) RegNaming { 39 | return newDNSNamingRegistry(opts...) 40 | } 41 | 42 | func newDNSNamingRegistry(opts ...Option) RegNaming { 43 | options := Options{ 44 | Context: context.Background(), 45 | Timeout: time.Millisecond * 100, 46 | KeepHeartTimeout: time.Second * 15, 47 | NodeID: uuid.New().String(), 48 | ServiceName: "gmsec.service", 49 | } 50 | for _, o := range opts { 51 | o(&options) 52 | } 53 | 54 | // set the domain 55 | domain := mdnsDomain 56 | 57 | d, ok := options.Context.Value("mdns.domain").(string) 58 | if ok { 59 | domain = d 60 | } 61 | 62 | return &DNSNamingRegister{ 63 | opts: options, 64 | domain: domain, 65 | } 66 | } 67 | 68 | // Init init option 69 | func (r *DNSNamingRegister) Init(opts ...Option) error { 70 | for _, o := range opts { 71 | o(&r.opts) 72 | } 73 | 74 | return nil 75 | } 76 | 77 | func (r *DNSNamingRegister) String() string { 78 | return r.opts.ServiceName 79 | } 80 | 81 | // Register register & add new node 82 | func (r *DNSNamingRegister) Register(address string, Metadata interface{}) error { 83 | r.Lock() 84 | defer r.Unlock() 85 | // r.opts.Timeout = time.Millisecond * 100 86 | 87 | r.opts.Addrs = []string{address} 88 | host, pt, err := net.SplitHostPort(address) 89 | if err != nil { 90 | return err 91 | } 92 | port, _ := strconv.Atoi(pt) 93 | 94 | // to add someone service 95 | txt := naming.Update{ 96 | Op: naming.Add, 97 | Addr: address, 98 | Metadata: port, 99 | } 100 | 101 | if r.node != nil { 102 | r.node.Shutdown() 103 | } 104 | 105 | s, err := mdns.NewMDNSService( 106 | r.opts.NodeID, 107 | r.opts.ServiceName, 108 | r.domain+".", 109 | "", 110 | port, 111 | []net.IP{net.ParseIP(host)}, 112 | encode(&txt), 113 | ) 114 | if err != nil { 115 | return err 116 | } 117 | r.node, err = mdns.NewServer(&mdns.Config{Zone: &mdns.DNSSDService{MDNSService: s}}) 118 | if err != nil { 119 | return err 120 | } 121 | 122 | return nil 123 | } 124 | 125 | // Deregister 注销 126 | func (r *DNSNamingRegister) Deregister() error { 127 | r.Lock() 128 | defer r.Unlock() 129 | if r.node != nil { 130 | r.node.Shutdown() 131 | r.node = nil 132 | } 133 | 134 | return nil 135 | } 136 | 137 | // Resolve resolve begin 138 | func (r *DNSNamingRegister) Resolve(target string) (naming.Watcher, error) { 139 | r.Lock() 140 | defer r.Unlock() 141 | ctx, cancel := context.WithCancel(context.Background()) 142 | w := &dnsNamingWatcher{ 143 | node: r.node, 144 | timeout: r.opts.Timeout, 145 | serviceName: target, 146 | domain: r.domain, 147 | ctx: ctx, 148 | cancel: cancel, 149 | } 150 | return w, nil 151 | } 152 | 153 | type dnsNamingWatcher struct { 154 | sync.Mutex 155 | node *mdns.Server 156 | timeout time.Duration 157 | 158 | serviceName string 159 | domain string 160 | 161 | // watch mabey 162 | cancel context.CancelFunc 163 | ctx context.Context 164 | isInit bool 165 | // c *etcd.Client 166 | // target string 167 | // wch etcd.WatchChan 168 | // err error 169 | } 170 | 171 | // Next gets the next set of updates from the etcd resolver. 172 | // Calls to Next should be serialized; concurrent calls are not safe since 173 | // there is no way to reconcile the update ordering. 174 | func (r *dnsNamingWatcher) Next() ([]*naming.Update, error) { 175 | r.Lock() 176 | defer r.Unlock() 177 | 178 | if !r.isInit { // first 179 | r.isInit = true 180 | timeout := r.timeout 181 | defer func() { 182 | if timeout > time.Second*3 { 183 | timeout = time.Second * 3 184 | } 185 | r.timeout = timeout 186 | }() 187 | r.timeout = time.Millisecond * 100 188 | return r.getService() 189 | } 190 | 191 | return r.getService() 192 | } 193 | 194 | func (r *dnsNamingWatcher) getService() ([]*naming.Update, error) { 195 | var serviceList []*naming.Update 196 | entries := make(chan *mdns.ServiceEntry, 10) 197 | done := make(chan bool) 198 | 199 | p := mdns.DefaultParams(r.serviceName) 200 | // set context with timeout 201 | var cancel context.CancelFunc 202 | p.Context, cancel = context.WithTimeout(context.Background(), r.timeout) 203 | defer cancel() 204 | // set entries channel 205 | p.Entries = entries 206 | // set the domain 207 | p.Domain = r.domain 208 | 209 | go func() { 210 | for { 211 | select { 212 | case e := <-entries: 213 | // list record so skip 214 | if p.Domain != r.domain { 215 | continue 216 | } 217 | if e.TTL == 0 { 218 | continue 219 | } 220 | 221 | var upInfo naming.Update 222 | if len(e.InfoFields) > 0 { 223 | tmp, err := decode(e.InfoFields) 224 | if err != nil { 225 | mylog.ErrorString(err.Error()) 226 | } else { 227 | upInfo = *tmp 228 | } 229 | } 230 | if len(upInfo.Addr) == 0 { 231 | continue 232 | } 233 | 234 | serviceList = append(serviceList, &upInfo) 235 | case <-p.Context.Done(): 236 | close(done) 237 | return 238 | } 239 | } 240 | }() 241 | 242 | // execute the query 243 | if err := mdns.Query(p); err != nil { 244 | return nil, err 245 | } 246 | 247 | // wait for completion 248 | <-done 249 | 250 | return serviceList, nil 251 | } 252 | 253 | // Close close watcher 254 | func (r *dnsNamingWatcher) Close() { r.cancel() } 255 | 256 | // Options get opts list 257 | func (r *DNSNamingRegister) Options() Options { 258 | return r.opts 259 | } 260 | 261 | func encode(txt *naming.Update) []string { 262 | b, _ := json.Marshal(txt) 263 | 264 | // var buf bytes.Buffer 265 | // defer buf.Reset() 266 | 267 | // w := zlib.NewWriter(&buf) 268 | 269 | // w.Write(b) 270 | // w.Close() 271 | encoded := tools.ByteToHex(b) // hex.EncodeToString(b) 272 | // split encoded string 273 | var record []string 274 | 275 | for len(encoded) > 255 { 276 | record = append(record, encoded[:255]) 277 | encoded = encoded[255:] 278 | } 279 | 280 | record = append(record, encoded) 281 | return record 282 | } 283 | 284 | func decode(record []string) (*naming.Update, error) { 285 | hr := tools.HexToBye(strings.Join(record, "")) // hex.DecodeString(encoded) 286 | 287 | var txt *naming.Update 288 | if err := json.Unmarshal(hr, &txt); err != nil { 289 | return nil, err 290 | } 291 | 292 | return txt, nil 293 | } 294 | -------------------------------------------------------------------------------- /registry/dns_naming_register_test.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | "time" 7 | 8 | "github.com/google/uuid" 9 | ) 10 | 11 | func TestRegister(t *testing.T) { 12 | dnsName := NewDNSNamingRegistry(WithTimeout(3*time.Second), WithAddrs(":0"), WithServiceName("test.server"), WithNodeID(uuid.New().String())) 13 | defer dnsName.Deregister() 14 | 15 | fmt.Println(dnsName.String()) 16 | fmt.Println(dnsName.Register("0.0.0.0:12345", 12345)) 17 | 18 | watch, err := dnsName.Resolve("test.server") 19 | fmt.Println(err) 20 | 21 | for i := 0; i < 100; i++ { 22 | up, e := watch.Next() 23 | fmt.Println(e) 24 | for _, v := range up { 25 | fmt.Println(v) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /registry/options.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | import ( 4 | "context" 5 | "time" 6 | ) 7 | 8 | // Options opts define 9 | type Options struct { 10 | Addrs []string 11 | Timeout time.Duration 12 | KeepHeartTimeout time.Duration 13 | // Secure bool 14 | // TLSConfig *tls.Config 15 | NodeID string 16 | ServiceName string 17 | // Other options for implementations of the interface 18 | // can be stored in a context 19 | Context context.Context 20 | } 21 | 22 | // Option opts list func 23 | type Option func(*Options) 24 | 25 | // type RegisterOptions struct { 26 | // TTL time.Duration 27 | // // Other options for implementations of the interface 28 | // // can be stored in a context 29 | // Context context.Context 30 | // } 31 | 32 | // type WatchOptions struct { 33 | // // Specify a service to watch 34 | // // If blank, the watch is for all services 35 | // Service string 36 | // // Other options for implementations of the interface 37 | // // can be stored in a context 38 | // Context context.Context 39 | // } 40 | 41 | // WithAddrs is the registry addresses to use 42 | func WithAddrs(addrs ...string) Option { 43 | return func(o *Options) { 44 | o.Addrs = addrs 45 | } 46 | } 47 | 48 | // WithTimeout set timeout 49 | func WithTimeout(t time.Duration) Option { 50 | return func(o *Options) { 51 | o.Timeout = t 52 | } 53 | } 54 | 55 | // WithKeepHeartTimeout set heart timeout 56 | func WithKeepHeartTimeout(t time.Duration) Option { 57 | return func(o *Options) { 58 | o.KeepHeartTimeout = t 59 | } 60 | } 61 | 62 | // WithSecure communication with the registry 63 | // func WithSecure(b bool) Option { 64 | // return func(o *Options) { 65 | // o.Secure = b 66 | // } 67 | // } 68 | 69 | // // WithTLSConfig TLS Config 70 | // func WithTLSConfig(t *tls.Config) Option { 71 | // return func(o *Options) { 72 | // o.TLSConfig = t 73 | // } 74 | // } 75 | 76 | // func RegisterTTL(t time.Duration) RegisterOption { 77 | // return func(o *RegisterOptions) { 78 | // o.TTL = t 79 | // } 80 | // } 81 | 82 | // Watch a service 83 | // func WatchService(name string) WatchOption { 84 | // return func(o *WatchOptions) { 85 | // o.Service = name 86 | // } 87 | // } 88 | 89 | // WithServiceName 设置服务名字 90 | func WithServiceName(name string) Option { 91 | return func(o *Options) { 92 | o.ServiceName = name 93 | } 94 | } 95 | 96 | // WithNodeID 设置节点id 97 | func WithNodeID(nodeID string) Option { 98 | return func(o *Options) { 99 | o.NodeID = nodeID 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /registry/register.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | import "github.com/gmsec/micro/naming" 4 | 5 | // RegNaming grpc自带服务发现 6 | type RegNaming interface { 7 | Init(...Option) error 8 | // Options returns the current options 9 | Options() Options 10 | 11 | Resolve(target string) (naming.Watcher, error) 12 | //SetNodeID(nodeID string) // 本实例节点唯一id 13 | Register(address string, Metadata interface{}) error // 注册,新加 14 | Deregister() error // 注销服务 15 | String() string 16 | } 17 | 18 | // Registry some of registry model 19 | type Registry struct { 20 | RegNaming RegNaming 21 | // outher model 22 | } 23 | 24 | // type RegisterOption func(*RegisterOptions) 25 | // type WatchOption func(*WatchOptions) 26 | -------------------------------------------------------------------------------- /server/naming_server.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "sync" 7 | "time" 8 | 9 | "github.com/gmsec/micro/registry" 10 | "github.com/gmsec/micro/tracer" 11 | 12 | "github.com/xxjwxc/public/dev" 13 | "github.com/xxjwxc/public/mylog" 14 | 15 | "github.com/gmsec/micro/grpc_opentracing" 16 | "google.golang.org/grpc" 17 | "google.golang.org/grpc/health" 18 | "google.golang.org/grpc/health/grpc_health_v1" 19 | ) 20 | 21 | // var ( 22 | // DefaultnamingResolver registry.RegistryNaming = 23 | // ) 24 | /* 25 | github.com/gmsec/micro/naming 26 | grpc 默认支持项,多语言同时接入时推荐使用 27 | */ 28 | 29 | type namingResolver struct { 30 | opts Options 31 | sync.RWMutex 32 | // marks the serve as started 33 | started bool 34 | //exit chan chan error 35 | } 36 | 37 | // NewNamingResolver new one 38 | func NewNamingResolver(opts ...Option) *namingResolver { 39 | resp := &namingResolver{} 40 | 41 | resp.Init(opts...) 42 | 43 | // if resp.opts.Registry == nil { 44 | // resp.opts.Registry = ®istry.Registry{ 45 | // RegNaming: registry.NewDNSNamingRegistry(), 46 | // } 47 | // } 48 | 49 | return resp 50 | } 51 | 52 | func (s *namingResolver) Options() Options { 53 | s.RLock() 54 | opts := s.opts 55 | s.RUnlock() 56 | return opts 57 | } 58 | 59 | func (s *namingResolver) Init(opts ...Option) error { 60 | s.Lock() 61 | defer s.Unlock() 62 | 63 | for _, opt := range opts { 64 | opt(&s.opts) 65 | } 66 | 67 | return nil 68 | } 69 | 70 | // Handle(Handler) error 71 | // NewHandler(interface{}, ...HandlerOption) Handler 72 | // NewSubscriber(string, interface{}, ...SubscriberOption) Subscriber 73 | // Subscribe(Subscriber) error 74 | func (s *namingResolver) Start() error { 75 | gs := s.GetServer() 76 | s.Lock() 77 | defer s.Unlock() 78 | lis := s.opts.getListener() 79 | 80 | if s.started { 81 | return nil 82 | } 83 | 84 | if s.opts.Registry == nil { // default naming register 85 | s.opts.Registry = ®istry.Registry{ 86 | RegNaming: registry.NewDNSNamingRegistry(), 87 | } 88 | } 89 | 90 | // init registry parms 91 | s.opts.Registry.RegNaming.Init(registry.WithAddrs(s.opts.Address), 92 | registry.WithNodeID(s.opts.ID), 93 | registry.WithServiceName(s.opts.Name), 94 | registry.WithTimeout(s.opts.RegisterTTL), 95 | ) 96 | 97 | // 开始注册 98 | reg := s.opts.Registry.RegNaming 99 | if err := reg.Register(s.opts.Address, nil); err != nil { 100 | mylog.ErrorString("gRPC Server register error:") 101 | mylog.Error(err) 102 | } 103 | 104 | // 健康检查 105 | hsrv := health.NewServer() 106 | grpc_health_v1.RegisterHealthServer(gs, hsrv) 107 | hsrv.SetServingStatus("", grpc_health_v1.HealthCheckResponse_SERVING) 108 | 109 | // micro: go ts.Accept(s.accept) 110 | go func() { 111 | mylog.Info(fmt.Sprintf("grpc server in: %v", s.opts.Address)) 112 | if err := gs.Serve(lis); err != nil { 113 | mylog.ErrorString("gRPC Server start error:") 114 | mylog.Error(err) 115 | } 116 | }() 117 | 118 | s.started = true 119 | 120 | return nil 121 | } 122 | func (s *namingResolver) Stop() error { 123 | s.RLock() 124 | if !s.started { 125 | s.RUnlock() 126 | return nil 127 | } 128 | s.RUnlock() 129 | 130 | gs := s.GetServer() 131 | reg := s.opts.Registry.RegNaming 132 | reg.Deregister() 133 | tracer.CloseTracer() // 关闭链路追踪 134 | 135 | // paus one second 136 | select { 137 | case <-time.After(time.Second): 138 | gs.Stop() 139 | } 140 | 141 | s.Lock() 142 | s.started = false 143 | s.Unlock() 144 | 145 | return nil 146 | } 147 | 148 | // String name string 149 | func (s *namingResolver) String() string { 150 | return "naming_resolver" 151 | } 152 | 153 | // GetServer get server 154 | func (s *namingResolver) GetServer() *grpc.Server { 155 | s.Lock() 156 | defer s.Unlock() 157 | 158 | if s.opts.Server == nil { 159 | trace := tracer.GetTracer() 160 | if trace != nil { 161 | s.opts.Server = grpc.NewServer(grpc.UnaryInterceptor( 162 | grpc_opentracing.UnaryServerInterceptor(grpc_opentracing.WithTracer(trace), 163 | grpc_opentracing.WithDev(dev.IsDev())))) 164 | } else { 165 | s.opts.Server = grpc.NewServer() 166 | } 167 | } 168 | 169 | return s.opts.Server 170 | } 171 | 172 | // GetListener listener 173 | func (s *namingResolver) GetListener() net.Listener { 174 | s.Lock() 175 | defer s.Unlock() 176 | return s.opts.getListener() 177 | } 178 | 179 | // SetListener 设置listener 180 | func (s *namingResolver) SetListener(l net.Listener) bool { 181 | s.Lock() 182 | defer s.Unlock() 183 | return s.opts.setListener(l) 184 | } 185 | 186 | func (s *namingResolver) SetAddress(add string) { 187 | s.opts.Address = add 188 | } 189 | 190 | func (s *namingResolver) GetAddress() string { 191 | return s.opts.Address 192 | } 193 | -------------------------------------------------------------------------------- /server/option.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "net" 5 | "time" 6 | 7 | "github.com/gmsec/micro/registry" 8 | 9 | "github.com/xxjwxc/public/mylog" 10 | 11 | "google.golang.org/grpc" 12 | ) 13 | 14 | // Options is a simple micro server abstraction 15 | type Options struct { 16 | Name string 17 | Address string 18 | // Advertise string 19 | ID string 20 | Version string 21 | 22 | // registry 23 | // The register expiry time 24 | RegisterTTL time.Duration 25 | // The interval on which to register 26 | RegisterInterval time.Duration 27 | 28 | Registry *registry.Registry 29 | 30 | Server *grpc.Server 31 | Listener net.Listener 32 | } 33 | 34 | // Name Server name 35 | func Name(n string) Option { 36 | return func(o *Options) { 37 | o.Name = n 38 | } 39 | } 40 | 41 | // RegisterTTL Register the service with a TTL 42 | func RegisterTTL(t time.Duration) Option { 43 | return func(o *Options) { 44 | o.RegisterTTL = t 45 | } 46 | } 47 | 48 | // RegisterInterval Register the service with at interval 49 | func RegisterInterval(t time.Duration) Option { 50 | return func(o *Options) { 51 | o.RegisterInterval = t 52 | } 53 | } 54 | 55 | func newOptions(opt ...Option) Options { 56 | opts := Options{ 57 | // Codecs: make(map[string]codec.NewCodec), 58 | // Metadata: map[string]string{}, 59 | RegisterInterval: DefaultRegisterInterval, 60 | RegisterTTL: DefaultRegisterTTL, 61 | } 62 | 63 | for _, o := range opt { 64 | o(&opts) 65 | } 66 | 67 | if len(opts.Address) == 0 { 68 | opts.Address = DefaultAddress 69 | } 70 | 71 | if len(opts.Name) == 0 { 72 | opts.Name = DefaultName 73 | } 74 | 75 | if len(opts.ID) == 0 { 76 | opts.ID = DefaultID 77 | } 78 | 79 | if len(opts.Version) == 0 { 80 | opts.Version = DefaultVersion 81 | } 82 | 83 | return opts 84 | } 85 | 86 | func (obj *Options) getListener() net.Listener { 87 | if obj.Listener == nil { 88 | //起服务 89 | lis, err := net.Listen("tcp", obj.Address) 90 | if err != nil { 91 | mylog.Fatal("failed to listen: ", err) 92 | } 93 | obj.Listener = lis 94 | obj.Address = lis.Addr().String() 95 | } 96 | 97 | return obj.Listener 98 | } 99 | 100 | func (obj *Options) setListener(lis net.Listener) bool { 101 | if obj.Listener == nil { 102 | //起服务 103 | obj.Listener = lis 104 | obj.Address = lis.Addr().String() 105 | return true 106 | } 107 | 108 | return false 109 | } 110 | 111 | // WithRegistryNaming 注册naming 服务发现 112 | func WithRegistryNaming(reg registry.RegNaming) Option { 113 | return func(o *Options) { 114 | o.Registry = ®istry.Registry{ 115 | RegNaming: reg, 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /server/server.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "net" 5 | "time" 6 | 7 | "github.com/google/uuid" 8 | "google.golang.org/grpc" 9 | ) 10 | 11 | // Server is a simple micro server abstraction 12 | type Server interface { 13 | Options() Options 14 | Init(...Option) error 15 | Start() error 16 | Stop() error 17 | String() string 18 | GetServer() *grpc.Server 19 | GetListener() net.Listener 20 | SetListener(net.Listener) bool 21 | SetAddress(add string) 22 | GetAddress() string 23 | } 24 | 25 | // Option option list 26 | type Option func(*Options) 27 | 28 | var ( 29 | // DefaultAddress default addr 30 | DefaultAddress = ":0" 31 | // DefaultName default name 32 | DefaultName = "go.micro.server" 33 | // DefaultVersion version 34 | DefaultVersion = "latest" 35 | // DefaultID node of id 36 | DefaultID = uuid.New().String() 37 | // DefaultNamingServer ... 38 | // DefaultNamingServer Server = newNamingServer() 39 | // DefaultRegisterInterval ... 40 | DefaultRegisterInterval = time.Second * 30 41 | // DefaultRegisterTTL ... 42 | DefaultRegisterTTL = time.Minute 43 | 44 | // NewServer creates a new server 45 | NewServer func(...Option) Server = newNamingServer 46 | ) 47 | 48 | func newNamingServer(opts ...Option) Server { 49 | options := newOptions(opts...) 50 | 51 | // if options.Registry == nil { 52 | // options.Registry = ®istry.Registry{ 53 | // RegNaming: registry.NewDNSNamingRegistry(), 54 | // } 55 | // } 56 | 57 | return &namingResolver{ 58 | opts: options, 59 | // register:registry.NewDNSNamingRegistry() 60 | // router: router, 61 | // handlers: make(map[string]Handler), 62 | // subscribers: make(map[Subscriber][]broker.Subscriber), 63 | // exit: make(chan chan error), 64 | // wg: wait(options.Context), 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /tracer/jaeger.go: -------------------------------------------------------------------------------- 1 | package tracer 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io" 7 | "time" 8 | 9 | "github.com/opentracing/opentracing-go" 10 | "github.com/opentracing/opentracing-go/ext" 11 | "github.com/opentracing/opentracing-go/log" 12 | "github.com/uber/jaeger-client-go" 13 | jaegercfg "github.com/uber/jaeger-client-go/config" 14 | "github.com/xxjwxc/public/dev" 15 | "github.com/xxjwxc/public/message" 16 | "github.com/xxjwxc/public/mylog" 17 | ) 18 | 19 | type jaegerInfo struct { 20 | addr string 21 | serviceName string 22 | percent float64 23 | head string 24 | maxTagValueLength int 25 | tracer opentracing.Tracer 26 | closer io.Closer 27 | } 28 | 29 | var _jaegerInfo *jaegerInfo 30 | 31 | // WithTracer addr:地址,percent 概率采集 32 | func WithTracer(head, addr string, percent int, maxTagValueLength int) { 33 | if maxTagValueLength <= 0 { 34 | maxTagValueLength = jaeger.DefaultMaxTagValueLength 35 | } 36 | if _jaegerInfo == nil { 37 | _jaegerInfo = &jaegerInfo{ 38 | addr: addr, 39 | head: head, 40 | percent: float64(percent) * 0.01, 41 | maxTagValueLength: maxTagValueLength, 42 | } 43 | } 44 | _jaegerInfo.addr = addr 45 | // initTrace() 46 | } 47 | 48 | func SetServiceName(service string) { 49 | if _jaegerInfo == nil { 50 | _jaegerInfo = &jaegerInfo{ 51 | serviceName: service, 52 | } 53 | } 54 | _jaegerInfo.serviceName = service 55 | initTrace() 56 | } 57 | 58 | func initTrace() { 59 | serviceName := _jaegerInfo.serviceName 60 | if len(_jaegerInfo.head) > 0 { 61 | serviceName = fmt.Sprintf("%v_%v", _jaegerInfo.head, _jaegerInfo.serviceName) 62 | } 63 | if len(_jaegerInfo.addr) > 0 && len(serviceName) > 0 { 64 | jcfg := jaegercfg.Configuration{ 65 | Sampler: &jaegercfg.SamplerConfig{ 66 | Type: jaeger.SamplerTypeProbabilistic, 67 | Param: _jaegerInfo.percent, 68 | }, 69 | ServiceName: serviceName, 70 | } 71 | 72 | report := &jaegercfg.ReporterConfig{ 73 | LogSpans: dev.IsDev(), 74 | BufferFlushInterval: 1 * time.Second, 75 | LocalAgentHostPort: _jaegerInfo.addr, 76 | } 77 | 78 | var err error 79 | reporter, _ := report.NewReporter(_jaegerInfo.serviceName, jaeger.NewNullMetrics(), jaeger.StdLogger) 80 | _jaegerInfo.tracer, _jaegerInfo.closer, err = jcfg.NewTracer( 81 | jaegercfg.Reporter(reporter), 82 | jaegercfg.MaxTagValueLength(_jaegerInfo.maxTagValueLength), 83 | ) 84 | 85 | if err != nil { 86 | mylog.Error(err) 87 | } 88 | } 89 | } 90 | 91 | func GetTracer() opentracing.Tracer { 92 | if _jaegerInfo == nil { 93 | return nil 94 | } 95 | 96 | return _jaegerInfo.tracer 97 | } 98 | 99 | func CloseTracer() { 100 | if _jaegerInfo == nil { 101 | return 102 | } 103 | 104 | if _jaegerInfo.closer != nil { 105 | _jaegerInfo.closer.Close() 106 | } 107 | } 108 | 109 | type SpanOption func(span opentracing.Span) 110 | 111 | func SpanWithError(err error) SpanOption { 112 | return func(span opentracing.Span) { 113 | if err != nil { 114 | ext.Error.Set(span, true) 115 | span.LogFields(log.String("event", "error"), log.String("msg", err.Error())) 116 | } 117 | } 118 | } 119 | 120 | // example: 121 | // SpanWithLog( 122 | // 123 | // "event", "soft error", 124 | // "type", "cache timeout", 125 | // "waited.millis", 1500) 126 | func SpanWithLog(arg ...interface{}) SpanOption { 127 | return func(span opentracing.Span) { 128 | span.LogKV(arg...) 129 | } 130 | } 131 | 132 | func Start(spanName string, ctx context.Context) (newCtx context.Context, finish func(...SpanOption), err error) { 133 | if ctx == nil { 134 | ctx, _ = context.WithTimeout(context.Background(), 3*time.Second) 135 | } 136 | 137 | _trace := GetTracer() 138 | if _trace == nil { 139 | err = message.GetError(message.InvalidArgument) 140 | return 141 | } 142 | 143 | span, newCtx := opentracing.StartSpanFromContextWithTracer(ctx, _trace, spanName, 144 | opentracing.Tag{Key: string(ext.Component), Value: "func"}, 145 | ) 146 | 147 | finish = func(ops ...SpanOption) { 148 | for _, o := range ops { 149 | o(span) 150 | } 151 | span.Finish() 152 | } 153 | 154 | return 155 | } 156 | 157 | /**** 在业务代码中使用 158 | 159 | 有时候只监控一个"api"是不够的,还需要监控到程序中的代码片段(如方法),可以这样封装一个方法 160 | 使用 161 | 162 | newCtx, finish := tracer.Start("DoSomeThing", ctx) 163 | err := DoSomeThing(newCtx) 164 | finish(tracer.SpanWithError(err)) 165 | if err != nil{ 166 | ... 167 | } 168 | ***/ 169 | -------------------------------------------------------------------------------- /tracer/jaeger_test.go: -------------------------------------------------------------------------------- 1 | package tracer 2 | 3 | import ( 4 | "context" 5 | "log" 6 | "testing" 7 | "time" 8 | 9 | "github.com/opentracing/opentracing-go" 10 | "github.com/uber/jaeger-client-go" 11 | jaegercfg "github.com/uber/jaeger-client-go/config" 12 | ) 13 | 14 | func TestJaeger(t *testing.T) { 15 | cfg := jaegercfg.Configuration{ 16 | Sampler: &jaegercfg.SamplerConfig{ 17 | Type: jaeger.SamplerTypeConst, 18 | Param: 1, 19 | }, 20 | Reporter: &jaegercfg.ReporterConfig{ 21 | LogSpans: true, 22 | BufferFlushInterval: 1 * time.Second, 23 | LocalAgentHostPort: "192.155.1.150:6831", // 替换host 24 | }, 25 | } 26 | 27 | closer, err := cfg.InitGlobalTracer( 28 | "haihuman.srv.jagertest", 29 | ) 30 | if err != nil { 31 | log.Printf("Could not initialize jaeger tracer: %s", err.Error()) 32 | return 33 | } 34 | 35 | var ctx = context.TODO() 36 | span1, ctx := opentracing.StartSpanFromContext(ctx, "span_1") 37 | time.Sleep(time.Second / 2) 38 | 39 | span11, _ := opentracing.StartSpanFromContext(ctx, "span_1-1") 40 | time.Sleep(time.Second / 2) 41 | span11.Finish() 42 | 43 | span1.Finish() 44 | 45 | defer closer.Close() 46 | } 47 | --------------------------------------------------------------------------------